Browse Source

Merge "Drop Heat related code from horizon"

tags/13.0.0.0b3
Zuul 1 year ago
parent
commit
b4fccffccc
94 changed files with 17 additions and 5977 deletions
  1. 0
    5
      doc/source/admin/manage-services.rst
  2. 0
    1
      doc/source/configuration/customizing.rst
  3. 0
    24
      doc/source/configuration/settings.rst
  4. 1
    1
      doc/source/contributor/quickstart.rst
  5. 0
    3
      doc/source/contributor/ref/local_conf.rst
  6. 0
    1
      doc/source/install/from-source.rst
  7. 0
    1
      doc/source/user/index.rst
  8. 0
    17
      doc/source/user/log-in.rst
  9. 0
    149
      doc/source/user/stacks.rst
  10. 0
    2
      openstack_dashboard/api/__init__.py
  11. 0
    265
      openstack_dashboard/api/heat.py
  12. 0
    2
      openstack_dashboard/api/rest/__init__.py
  13. 0
    51
      openstack_dashboard/api/rest/heat.py
  14. 0
    92
      openstack_dashboard/conf/heat_policy.json
  15. 1
    2
      openstack_dashboard/dashboards/admin/dashboard.py
  16. 1
    2
      openstack_dashboard/dashboards/admin/info/panel.py
  17. 0
    42
      openstack_dashboard/dashboards/admin/info/tables.py
  18. 1
    26
      openstack_dashboard/dashboards/admin/info/tabs.py
  19. 1
    16
      openstack_dashboard/dashboards/admin/info/tests.py
  20. 0
    0
      openstack_dashboard/dashboards/project/stacks/__init__.py
  21. 0
    83
      openstack_dashboard/dashboards/project/stacks/api.py
  22. 0
    488
      openstack_dashboard/dashboards/project/stacks/forms.py
  23. 0
    350
      openstack_dashboard/dashboards/project/stacks/mappings.py
  24. 0
    21
      openstack_dashboard/dashboards/project/stacks/panel.py
  25. 0
    0
      openstack_dashboard/dashboards/project/stacks/resource_types/__init__.py
  26. 0
    23
      openstack_dashboard/dashboards/project/stacks/resource_types/panel.py
  27. 0
    36
      openstack_dashboard/dashboards/project/stacks/resource_types/tables.py
  28. 0
    32
      openstack_dashboard/dashboards/project/stacks/resource_types/tabs.py
  29. 0
    15
      openstack_dashboard/dashboards/project/stacks/resource_types/templates/stacks.resource_types/_details.html
  30. 0
    52
      openstack_dashboard/dashboards/project/stacks/resource_types/tests.py
  31. 0
    22
      openstack_dashboard/dashboards/project/stacks/resource_types/urls.py
  32. 0
    78
      openstack_dashboard/dashboards/project/stacks/resource_types/views.py
  33. 0
    44
      openstack_dashboard/dashboards/project/stacks/sro.py
  34. 0
    413
      openstack_dashboard/dashboards/project/stacks/tables.py
  35. 0
    173
      openstack_dashboard/dashboards/project/stacks/tabs.py
  36. 0
    0
      openstack_dashboard/dashboards/project/stacks/template_versions/__init__.py
  37. 0
    23
      openstack_dashboard/dashboards/project/stacks/template_versions/panel.py
  38. 0
    52
      openstack_dashboard/dashboards/project/stacks/template_versions/tables.py
  39. 0
    51
      openstack_dashboard/dashboards/project/stacks/template_versions/tabs.py
  40. 0
    3
      openstack_dashboard/dashboards/project/stacks/template_versions/templates/stacks.template_versions/_details.html
  41. 0
    7
      openstack_dashboard/dashboards/project/stacks/template_versions/templates/stacks.template_versions/index.html
  42. 0
    79
      openstack_dashboard/dashboards/project/stacks/template_versions/tests.py
  43. 0
    24
      openstack_dashboard/dashboards/project/stacks/template_versions/urls.py
  44. 0
    61
      openstack_dashboard/dashboards/project/stacks/template_versions/views.py
  45. 0
    7
      openstack_dashboard/dashboards/project/stacks/templates/stacks/_change_template.html
  46. 0
    6
      openstack_dashboard/dashboards/project/stacks/templates/stacks/_create.html
  47. 0
    3
      openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_events.html
  48. 0
    55
      openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_overview.html
  49. 0
    3
      openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_resources.html
  50. 0
    9
      openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_topology.html
  51. 0
    6
      openstack_dashboard/dashboards/project/stacks/templates/stacks/_preview.html
  52. 0
    58
      openstack_dashboard/dashboards/project/stacks/templates/stacks/_preview_details.html
  53. 0
    7
      openstack_dashboard/dashboards/project/stacks/templates/stacks/_preview_template.html
  54. 0
    10
      openstack_dashboard/dashboards/project/stacks/templates/stacks/_resource_info.html
  55. 0
    38
      openstack_dashboard/dashboards/project/stacks/templates/stacks/_resource_overview.html
  56. 0
    7
      openstack_dashboard/dashboards/project/stacks/templates/stacks/_select_template.html
  57. 0
    14
      openstack_dashboard/dashboards/project/stacks/templates/stacks/_stack_info.html
  58. 0
    5
      openstack_dashboard/dashboards/project/stacks/templates/stacks/_stack_template.html
  59. 0
    6
      openstack_dashboard/dashboards/project/stacks/templates/stacks/_update.html
  60. 0
    7
      openstack_dashboard/dashboards/project/stacks/templates/stacks/change_template.html
  61. 0
    7
      openstack_dashboard/dashboards/project/stacks/templates/stacks/create.html
  62. 0
    7
      openstack_dashboard/dashboards/project/stacks/templates/stacks/preview.html
  63. 0
    7
      openstack_dashboard/dashboards/project/stacks/templates/stacks/preview_details.html
  64. 0
    7
      openstack_dashboard/dashboards/project/stacks/templates/stacks/preview_template.html
  65. 0
    7
      openstack_dashboard/dashboards/project/stacks/templates/stacks/select_template.html
  66. 0
    7
      openstack_dashboard/dashboards/project/stacks/templates/stacks/update.html
  67. 0
    1003
      openstack_dashboard/dashboards/project/stacks/tests.py
  68. 0
    38
      openstack_dashboard/dashboards/project/stacks/urls.py
  69. 0
    358
      openstack_dashboard/dashboards/project/stacks/views.py
  70. 0
    8
      openstack_dashboard/enabled/_1610_orchestration_panel_group.py
  71. 0
    9
      openstack_dashboard/enabled/_1620_project_stacks_panel.py
  72. 0
    10
      openstack_dashboard/enabled/_1630_project_resource_types_panel.py
  73. 0
    10
      openstack_dashboard/enabled/_1640_project_template_versions_panel.py
  74. 0
    5
      openstack_dashboard/exceptions.py
  75. 0
    6
      openstack_dashboard/local/local_settings.py.example
  76. 0
    1
      openstack_dashboard/settings.py
  77. 0
    80
      openstack_dashboard/static/app/core/openstack-service-api/heat.service.js
  78. 0
    80
      openstack_dashboard/static/app/core/openstack-service-api/heat.service.spec.js
  79. 0
    69
      openstack_dashboard/test/api_tests/heat_rest_tests.py
  80. 0
    358
      openstack_dashboard/test/api_tests/heat_tests.py
  81. 0
    11
      openstack_dashboard/test/helpers.py
  82. 0
    2
      openstack_dashboard/test/integration_tests/config.py
  83. 0
    2
      openstack_dashboard/test/integration_tests/horizon.conf
  84. 0
    7
      openstack_dashboard/test/integration_tests/pages/navigation.py
  85. 0
    0
      openstack_dashboard/test/integration_tests/pages/project/orchestration/__init__.py
  86. 0
    99
      openstack_dashboard/test/integration_tests/pages/project/orchestration/stackspage.py
  87. 0
    73
      openstack_dashboard/test/integration_tests/tests/test_stacks.py
  88. 0
    4
      openstack_dashboard/test/test_data/exceptions.py
  89. 0
    617
      openstack_dashboard/test/test_data/heat_data.py
  90. 0
    8
      openstack_dashboard/test/test_data/keystone_data.py
  91. 0
    2
      openstack_dashboard/test/test_data/utils.py
  92. 11
    0
      releasenotes/notes/heat-panel-splitout-b609b157aa4bf29b.yaml
  93. 0
    1
      requirements.txt
  94. 1
    1
      tools/gate/integration/devstack_gate_rc

+ 0
- 5
doc/source/admin/manage-services.rst View File

@@ -30,8 +30,3 @@ As an administrative user, you can view information for OpenStack services.
* :guilabel:`Network Agents`:
Displays the network agents active within the cluster, such as L3 and
DHCP agents, and the status of each agent.

* :guilabel:`Orchestration Services`:
Displays information specific to the Orchestration service. Name,
engine id, host and topic are listed for each service, as well as its
activation status.

+ 0
- 1
doc/source/configuration/customizing.rst View File

@@ -116,7 +116,6 @@ You can also override existing methods with your own versions::

NO = lambda *x: False

tabs.HeatServiceTab.allowed = NO
tables.AssociateIP.allowed = NO
tables.SimpleAssociateIP.allowed = NO
tables.SimpleDisassociateIP.allowed = NO

+ 0
- 24
doc/source/configuration/settings.rst View File

@@ -804,7 +804,6 @@ Default:
'compute': 'nova_policy.json',
'volume': 'cinder_policy.json',
'image': 'glance_policy.json',
'orchestration': 'heat_policy.json',
'network': 'neutron_policy.json',
}

@@ -1125,29 +1124,6 @@ Default:
Used to customize features related to the image service, such as the list of
supported image formats.

Heat
----

OPENSTACK_HEAT_STACK
~~~~~~~~~~~~~~~~~~~~

.. versionadded:: 9.0.0(Mitaka)

Default:

.. code-block:: python

{
'enable_user_pass': True
}

A dictionary of settings to use with heat stacks. Currently, the only setting
available is "enable_user_pass", which can be used to disable the password
field while launching the stack. Currently HEAT API needs user password to
perform all the heat operations because in HEAT API trusts is not enabled by
default. So, this setting can be set as "False" in-case HEAT uses trusts by
default otherwise it needs to be set as "True".

Keystone
--------


+ 1
- 1
doc/source/contributor/quickstart.rst View File

@@ -83,7 +83,7 @@ To start the Horizon development server use the command below

.. note::

The default port for runserver is 8000 which is already consumed by
The default port for runserver is 8000 which might be already consumed by
heat-api-cfn in DevStack. If running in DevStack
``tox -e runserver -- localhost:9000`` will start the test server at
``http://localhost:9000``

+ 0
- 3
doc/source/contributor/ref/local_conf.rst View File

@@ -69,9 +69,6 @@ see https://docs.openstack.org/devstack/latest/
SWIFT_REPLICAS=1
SWIFT_DATA_DIR=$DEST/data/swift

# Enable Heat
enable_plugin heat https://git.openstack.org/openstack/heat

# Enable Neutron
enable_plugin neutron https://git.openstack.org/openstack/neutron


+ 0
- 1
doc/source/install/from-source.rst View File

@@ -22,7 +22,6 @@ System Requirements

* `cinder <https://docs.openstack.org/cinder/latest/>`_: Block Storage
* `glance <https://docs.openstack.org/glance/latest/>`_: Image Management
* `heat <https://docs.openstack.org/heat/latest/>`_: Orchestration
* `neutron <https://docs.openstack.org/neutron/latest/>`_: Networking
* `nova <https://docs.openstack.org/nova/latest/>`_: Compute
* `swift <https://docs.openstack.org/swift/latest/>`_: Object Storage

+ 0
- 1
doc/source/user/index.rst View File

@@ -18,7 +18,6 @@ sizes of server instances.
manage-containers.rst
manage-volumes.rst
manage-shares.rst
stacks.rst
databases.rst
manage-lbaasv2.rst
browser_support

+ 0
- 17
doc/source/user/log-in.rst View File

@@ -50,11 +50,6 @@ The dashboard is generally installed on the controller node.
(:ref:`dashboard-admin-tab`) and :guilabel:`Identity` tab
(:ref:`dashboard-identity-tab`) are displayed.

.. note::

Some tabs, such as :guilabel:`Orchestration` and :guilabel:`Firewalls`,
only appear on the dashboard if they are properly configured.

.. _dashboard-project-tab:

OpenStack dashboard — Project tab
@@ -143,15 +138,6 @@ Network tab

* :guilabel:`Firewall Rules`: Add and manage firewall rules.

Orchestration tab
-----------------

* :guilabel:`Stacks`: Use the REST API to orchestrate multiple composite
cloud applications.

* :guilabel:`Resource Types`: Show a list of all the supported resource
types for HOT templates.

Object Store tab
----------------

@@ -234,9 +220,6 @@ System tab

* :guilabel:`Network Agents`: View the network agents.

* :guilabel:`Orchestration Services`: View a list of all Orchestration
services.

* :guilabel:`Shares`: Use the following tabs to complete these tasks:

* :guilabel:`Shares`: View, create, manage, and delete shares.

+ 0
- 149
doc/source/user/stacks.rst View File

@@ -1,149 +0,0 @@
========================
Launch and manage stacks
========================

OpenStack Orchestration is a service that you can use to
orchestrate multiple composite cloud applications. This
service supports the use of both the Amazon Web Services (AWS)
CloudFormation template format through both a Query API that
is compatible with CloudFormation and the native OpenStack
Heat Orchestration Template (HOT) format through a REST API.

These flexible template languages enable application
developers to describe and automate the deployment of
infrastructure, services, and applications. The templates
enable creation of most OpenStack resource types, such as
instances, floating IP addresses, volumes, security groups,
and users. Once created, the resources are referred to as
stacks.

The template languages are described in the `Template Guide
<https://docs.openstack.org/heat/latest/template_guide/>`_.

Launch a stack
~~~~~~~~~~~~~~

#. Log in to the dashboard.
#. Select the appropriate project from the drop down menu at the top left.
#. On the :guilabel:`Project` tab, open the :guilabel:`Orchestration` tab and
click :guilabel:`Stacks` category.
#. Click :guilabel:`Launch Stack`.
#. In the :guilabel:`Select Template` dialog box, specify the
following values:

+---------------------------------------+-------------------------------+
| :guilabel:`Template Source` | Choose the source of the |
| | template from the list. |
+---------------------------------------+-------------------------------+
| :guilabel:`Template URL/File/Data` | Depending on the source that |
| | you select, enter the URL, |
| | browse to the file location, |
| | or directly include the |
| | template. |
+---------------------------------------+-------------------------------+
| :guilabel:`Environment Source` | Choose the source of the |
| | environment from the list. |
| | The environment files contain |
| | additional settings for the |
| | stack. |
+---------------------------------------+-------------------------------+
| :guilabel:`Environment File/Data` | Depending on the source that |
| | you select, browse to the |
| | file location, directly |
| | include the environment |
+---------------------------------------+-------------------------------+

#. Click :guilabel:`Next`.
#. In the :guilabel:`Launch Stack` dialog box, specify the
following values:

+---------------------------------+---------------------------------+
| :guilabel:`Stack Name` | Enter a name to identify |
| | the stack. |
+---------------------------------+---------------------------------+
| :guilabel:`Creation Timeout` | Specify the number of minutes |
| :guilabel:`(minutes)` | that can elapse before the |
| | launch of the stack times out. |
+---------------------------------+---------------------------------+
| :guilabel:`Rollback On Failure` | Select this check box if you |
| | want the service to roll back |
| | changes if the stack fails to |
| | launch. |
+---------------------------------+---------------------------------+
| :guilabel:`Password for user` | Specify the password that |
| :guilabel:`"demo"` | the default user uses when the |
| | stack is created. |
+---------------------------------+---------------------------------+
| :guilabel:`DBUsername` | Specify the name of the |
| | database user. |
+---------------------------------+---------------------------------+
| :guilabel:`LinuxDistribution` | Specify the Linux distribution |
| | that is used in the stack. |
+---------------------------------+---------------------------------+
| :guilabel:`DBRootPassword` | Specify the root password for |
| | the database. |
+---------------------------------+---------------------------------+
| :guilabel:`KeyName` | Specify the name of the key pair|
| | to use to log in to the stack. |
+---------------------------------+---------------------------------+
| :guilabel:`DBName` | Specify the name of the |
| | database. |
+---------------------------------+---------------------------------+
| :guilabel:`DBPassword` | Specify the password of the |
| | database. |
+---------------------------------+---------------------------------+
| :guilabel:`InstanceType` | Specify the flavor for the |
| | instance. |
+---------------------------------+---------------------------------+

#. Click :guilabel:`Launch` to create a stack. The :guilabel:`Stacks`
tab shows the stack.

After the stack is created, click on the stack name to see the
following details:

Topology
The topology of the stack.

Overview
The parameters and details of the stack.

Resources
The resources used by the stack.

Events
The events related to the stack.

Template
The template for the stack.

Manage a stack
~~~~~~~~~~~~~~

#. Log in to the dashboard.
#. Select the appropriate project from the drop down menu at the top left.
#. On the :guilabel:`Project` tab, open the :guilabel:`Orchestration` tab and
click :guilabel:`Stacks` category.
#. Select the stack that you want to update.
#. Click :guilabel:`Change Stack Template`.
#. In the :guilabel:`Select Template` dialog box, select the
new template source or environment source.
#. Click :guilabel:`Next`.

The :guilabel:`Update Stack Parameters` window appears.
#. Enter new values for any parameters that you want to update.
#. Click :guilabel:`Update`.

Delete a stack
~~~~~~~~~~~~~~

When you delete a stack, you cannot undo this action.

#. Log in to the dashboard.
#. Select the appropriate project from the drop down menu at the top left.
#. On the :guilabel:`Project` tab, open the :guilabel:`Orchestration` tab and
click :guilabel:`Stacks` category.
#. Select the stack that you want to delete.
#. Click :guilabel:`Delete Stack`.
#. In the confirmation dialog box, click :guilabel:`Delete Stack`
to confirm the deletion.

+ 0
- 2
openstack_dashboard/api/__init__.py View File

@@ -34,7 +34,6 @@ Keystone/Nova/Glance/Swift et. al.
from openstack_dashboard.api import base
from openstack_dashboard.api import cinder
from openstack_dashboard.api import glance
from openstack_dashboard.api import heat
from openstack_dashboard.api import keystone
from openstack_dashboard.api import network
from openstack_dashboard.api import neutron
@@ -46,7 +45,6 @@ __all__ = [
"base",
"cinder",
"glance",
"heat",
"keystone",
"network",
"neutron",

+ 0
- 265
openstack_dashboard/api/heat.py View File

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

import contextlib

from django.conf import settings
from heatclient import client as heat_client
from heatclient.common import template_format
from heatclient.common import template_utils
from heatclient.common import utils as heat_utils
from oslo_serialization import jsonutils
import six
from six.moves.urllib import request

from horizon import exceptions
from horizon.utils import functions as utils
from horizon.utils.memoized import memoized
from openstack_dashboard.api import base
from openstack_dashboard.contrib.developer.profiler import api as profiler


def format_parameters(params):
parameters = {}
for count, p in enumerate(params, 1):
parameters['Parameters.member.%d.ParameterKey' % count] = p
parameters['Parameters.member.%d.ParameterValue' % count] = params[p]
return parameters


@memoized
def heatclient(request, password=None):
api_version = "1"
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
endpoint = base.url_for(request, 'orchestration')
kwargs = {
'token': request.user.token.id,
'insecure': insecure,
'ca_file': cacert,
'username': request.user.username,
'password': password
# 'timeout': args.timeout,
# 'ca_file': args.ca_file,
# 'cert_file': args.cert_file,
# 'key_file': args.key_file,
}
client = heat_client.Client(api_version, endpoint, **kwargs)
client.format_parameters = format_parameters
return client


@profiler.trace
def stacks_list(request, marker=None, sort_dir='desc', sort_key='created_at',
paginate=False, filters=None):
limit = getattr(settings, 'API_RESULT_LIMIT', 1000)
page_size = utils.get_page_size(request)

if paginate:
request_size = page_size + 1
else:
request_size = limit

kwargs = {'sort_dir': sort_dir, 'sort_key': sort_key}
if marker:
kwargs['marker'] = marker

if filters:
kwargs.update(filters)
if 'status' in kwargs:
kwargs['status'] = kwargs['status'].replace(' ', '_').upper()

stacks_iter = heatclient(request).stacks.list(limit=request_size,
**kwargs)

has_prev_data = False
has_more_data = False
stacks = list(stacks_iter)

if paginate:
if len(stacks) > page_size:
stacks.pop()
has_more_data = True
if marker is not None:
has_prev_data = True
elif sort_dir == 'asc' and marker is not None:
has_more_data = True
elif marker is not None:
has_prev_data = True
return (stacks, has_more_data, has_prev_data)


def _ignore_if(key, value):
if key != 'get_file' and key != 'type':
return True
if not isinstance(value, six.string_types):
return True
if (key == 'type' and
not value.endswith(('.yaml', '.template'))):
return True
return False


@profiler.trace
def get_template_files(template_data=None, template_url=None):
if template_data:
tpl = template_data
elif template_url:
with contextlib.closing(request.urlopen(template_url)) as u:
tpl = u.read()
else:
return {}, None
if not tpl:
return {}, None
if isinstance(tpl, six.binary_type):
tpl = tpl.decode('utf-8')
template = template_format.parse(tpl)
files = {}
_get_file_contents(template, files)
return files, template


def _get_file_contents(from_data, files):
if not isinstance(from_data, (dict, list)):
return
if isinstance(from_data, dict):
recurse_data = from_data.values()
for key, value in from_data.items():
if _ignore_if(key, value):
continue
if not value.startswith(('http://', 'https://')):
raise exceptions.GetFileError(value, 'get_file')
if value not in files:
file_content = heat_utils.read_url_content(value)
if template_utils.is_template(file_content):
template = get_template_files(template_url=value)[1]
file_content = jsonutils.dumps(template)
files[value] = file_content
else:
recurse_data = from_data
for value in recurse_data:
_get_file_contents(value, files)


@profiler.trace
def stack_delete(request, stack_id):
return heatclient(request).stacks.delete(stack_id)


@profiler.trace
def stack_get(request, stack_id):
return heatclient(request).stacks.get(stack_id)


@profiler.trace
def template_get(request, stack_id):
return heatclient(request).stacks.template(stack_id)


@profiler.trace
def stack_create(request, password=None, **kwargs):
return heatclient(request, password).stacks.create(**kwargs)


@profiler.trace
def stack_preview(request, password=None, **kwargs):
return heatclient(request, password).stacks.preview(**kwargs)


@profiler.trace
def stack_update(request, stack_id, password=None, **kwargs):
return heatclient(request, password).stacks.update(stack_id, **kwargs)


@profiler.trace
def snapshot_create(request, stack_id):
return heatclient(request).stacks.snapshot(stack_id)


@profiler.trace
def snapshot_list(request, stack_id):
return heatclient(request).stacks.snapshot_list(stack_id)


@profiler.trace
def snapshot_show(request, stack_id, snapshot_id):
return heatclient(request).stacks.snapshot_show(stack_id, snapshot_id)


@profiler.trace
def snapshot_delete(request, stack_id, snapshot_id):
return heatclient(request).stacks.snapshot_delete(stack_id, snapshot_id)


@profiler.trace
def events_list(request, stack_name):
return heatclient(request).events.list(stack_name)


@profiler.trace
def resources_list(request, stack_name):
return heatclient(request).resources.list(stack_name)


@profiler.trace
def resource_get(request, stack_id, resource_name):
return heatclient(request).resources.get(stack_id, resource_name)


@profiler.trace
def resource_metadata_get(request, stack_id, resource_name):
return heatclient(request).resources.metadata(stack_id, resource_name)


@profiler.trace
def template_validate(request, **kwargs):
return heatclient(request).stacks.validate(**kwargs)


@profiler.trace
def action_check(request, stack_id):
return heatclient(request).actions.check(stack_id)


@profiler.trace
def action_suspend(request, stack_id):
return heatclient(request).actions.suspend(stack_id)


@profiler.trace
def action_resume(request, stack_id):
return heatclient(request).actions.resume(stack_id)


@profiler.trace
def resource_types_list(request, filters=None):
return heatclient(request).resource_types.list(filters=filters)


@profiler.trace
def resource_type_get(request, resource_type):
return heatclient(request).resource_types.get(resource_type)


@profiler.trace
def service_list(request):
return heatclient(request).services.list()


@profiler.trace
def template_version_list(request):
return heatclient(request).template_versions.list()


@profiler.trace
def template_function_list(request, template_version):
return heatclient(request).template_versions.get(template_version)

+ 0
- 2
openstack_dashboard/api/rest/__init__.py View File

@@ -24,7 +24,6 @@ in https://wiki.openstack.org/wiki/APIChangeGuidelines.
from openstack_dashboard.api.rest import cinder
from openstack_dashboard.api.rest import config
from openstack_dashboard.api.rest import glance
from openstack_dashboard.api.rest import heat
from openstack_dashboard.api.rest import keystone
from openstack_dashboard.api.rest import network
from openstack_dashboard.api.rest import neutron
@@ -37,7 +36,6 @@ __all__ = [
'cinder',
'config',
'glance',
'heat',
'keystone',
'network',
'neutron',

+ 0
- 51
openstack_dashboard/api/rest/heat.py View File

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

from django.views import generic

from openstack_dashboard import api
from openstack_dashboard.api.rest import urls
from openstack_dashboard.api.rest import utils as rest_utils


@urls.register
class Validate(generic.View):
"""API for validating a template"""
url_regex = r'heat/validate/$'

@rest_utils.ajax(data_required=True)
def post(self, request):
"""Validate a template

The following parameters may be passed in the POST
application/json object. The parameters are:
request:

:param template_url: The template to validate
"""
return api.heat.template_validate(request, **(request.DATA))


@urls.register
class Services(generic.View):
"""API for heat services."""
url_regex = r'heat/services/$'

@rest_utils.ajax()
def get(self, request):
"""Get a list of heat services."""
if api.base.is_service_enabled(request, 'orchestration'):
result = api.heat.service_list(request)
return {'items': [u.to_dict() for u in result]}
else:
raise rest_utils.AjaxError(501, '')

+ 0
- 92
openstack_dashboard/conf/heat_policy.json View File

@@ -1,92 +0,0 @@
{
"context_is_admin": "role:admin",
"deny_stack_user": "not role:heat_stack_user",
"deny_everybody": "!",

"cloudformation:ListStacks": "rule:deny_stack_user",
"cloudformation:CreateStack": "rule:deny_stack_user",
"cloudformation:DescribeStacks": "rule:deny_stack_user",
"cloudformation:DeleteStack": "rule:deny_stack_user",
"cloudformation:UpdateStack": "rule:deny_stack_user",
"cloudformation:CancelUpdateStack": "rule:deny_stack_user",
"cloudformation:DescribeStackEvents": "rule:deny_stack_user",
"cloudformation:ValidateTemplate": "rule:deny_stack_user",
"cloudformation:GetTemplate": "rule:deny_stack_user",
"cloudformation:EstimateTemplateCost": "rule:deny_stack_user",
"cloudformation:DescribeStackResource": "",
"cloudformation:DescribeStackResources": "rule:deny_stack_user",
"cloudformation:ListStackResources": "rule:deny_stack_user",

"cloudwatch:DeleteAlarms": "rule:deny_stack_user",
"cloudwatch:DescribeAlarmHistory": "rule:deny_stack_user",
"cloudwatch:DescribeAlarms": "rule:deny_stack_user",
"cloudwatch:DescribeAlarmsForMetric": "rule:deny_stack_user",
"cloudwatch:DisableAlarmActions": "rule:deny_stack_user",
"cloudwatch:EnableAlarmActions": "rule:deny_stack_user",
"cloudwatch:GetMetricStatistics": "rule:deny_stack_user",
"cloudwatch:ListMetrics": "rule:deny_stack_user",
"cloudwatch:PutMetricAlarm": "rule:deny_stack_user",
"cloudwatch:PutMetricData": "",
"cloudwatch:SetAlarmState": "rule:deny_stack_user",

"actions:action": "rule:deny_stack_user",
"build_info:build_info": "rule:deny_stack_user",
"events:index": "rule:deny_stack_user",
"events:show": "rule:deny_stack_user",
"resource:index": "rule:deny_stack_user",
"resource:metadata": "",
"resource:signal": "",
"resource:mark_unhealthy": "rule:deny_stack_user",
"resource:show": "rule:deny_stack_user",
"stacks:abandon": "rule:deny_stack_user",
"stacks:create": "rule:deny_stack_user",
"stacks:delete": "rule:deny_stack_user",
"stacks:detail": "rule:deny_stack_user",
"stacks:export": "rule:deny_stack_user",
"stacks:generate_template": "rule:deny_stack_user",
"stacks:global_index": "rule:deny_everybody",
"stacks:index": "rule:deny_stack_user",
"stacks:list_resource_types": "rule:deny_stack_user",
"stacks:list_template_versions": "rule:deny_stack_user",
"stacks:list_template_functions": "rule:deny_stack_user",
"stacks:lookup": "",
"stacks:preview": "rule:deny_stack_user",
"stacks:resource_schema": "rule:deny_stack_user",
"stacks:show": "rule:deny_stack_user",
"stacks:template": "rule:deny_stack_user",
"stacks:environment": "rule:deny_stack_user",
"stacks:update": "rule:deny_stack_user",
"stacks:update_patch": "rule:deny_stack_user",
"stacks:preview_update": "rule:deny_stack_user",
"stacks:preview_update_patch": "rule:deny_stack_user",
"stacks:validate_template": "rule:deny_stack_user",
"stacks:snapshot": "rule:deny_stack_user",
"stacks:show_snapshot": "rule:deny_stack_user",
"stacks:delete_snapshot": "rule:deny_stack_user",
"stacks:list_snapshots": "rule:deny_stack_user",
"stacks:restore_snapshot": "rule:deny_stack_user",
"stacks:list_outputs": "rule:deny_stack_user",
"stacks:show_output": "rule:deny_stack_user",

"software_configs:global_index": "rule:deny_everybody",
"software_configs:index": "rule:deny_stack_user",
"software_configs:create": "rule:deny_stack_user",
"software_configs:show": "rule:deny_stack_user",
"software_configs:delete": "rule:deny_stack_user",
"software_deployments:index": "rule:deny_stack_user",
"software_deployments:create": "rule:deny_stack_user",
"software_deployments:show": "rule:deny_stack_user",
"software_deployments:update": "rule:deny_stack_user",
"software_deployments:delete": "rule:deny_stack_user",
"software_deployments:metadata": "",

"service:index": "rule:context_is_admin",

"resource_types:OS::Nova::Flavor": "rule:context_is_admin",
"resource_types:OS::Cinder::EncryptedVolumeType": "rule:context_is_admin",
"resource_types:OS::Cinder::VolumeType": "rule:context_is_admin",
"resource_types:OS::Manila::ShareType": "rule:context_is_admin",
"resource_types:OS::Neutron::QoSPolicy": "rule:context_is_admin",
"resource_types:OS::Neutron::QoSBandwidthLimitRule": "rule:context_is_admin",
"resource_types:OS::Nova::HostAggregate": "rule:context_is_admin"
}

+ 1
- 2
openstack_dashboard/dashboards/admin/dashboard.py View File

@@ -28,8 +28,7 @@ class Admin(horizon.Dashboard):
('image', 'context_is_admin'),
('volume', 'context_is_admin'),
('compute', 'context_is_admin'),
('network', 'context_is_admin'),
('orchestration', 'context_is_admin'),)
('network', 'context_is_admin'),)
else:
permissions = (tuple(utils.get_admin_permissions()),)


+ 1
- 2
openstack_dashboard/dashboards/admin/info/panel.py View File

@@ -26,5 +26,4 @@ class Info(horizon.Panel):
slug = 'info'
policy_rules = (("compute", "context_is_admin"),
("volume", "context_is_admin"),
("network", "context_is_admin"),
("orchestration", "context_is_admin"),)
("network", "context_is_admin"),)

+ 0
- 42
openstack_dashboard/dashboards/admin/info/tables.py View File

@@ -236,45 +236,3 @@ class NetworkAgentsTable(tables.DataTable):
table_actions = (NetworkAgentsFilterAction, )
row_actions = (NetworkL3AgentRoutersLinkAction, )
multi_select = False


class HeatServiceFilterAction(tables.FilterAction):
filter_field = 'type'

def filter(self, table, services, filter_string):
q = filter_string.lower()

def comp(service):
attr = getattr(service, self.filter_field, '')
if attr is not None and q in attr.lower():
return True
return False

return filter(comp, services)


class HeatServiceTable(tables.DataTable):
hostname = tables.Column('hostname', verbose_name=_('Hostname'))
binary = tables.Column("binary", verbose_name=_('Name'))
engine_id = tables.Column('engine_id', verbose_name=_('Engine Id'))
host = tables.Column('host', verbose_name=_('Host'))
topic = tables.Column('topic', verbose_name=_('Topic'))
# For consistent with other tables in system info, set column name to
# 'state'
state = tables.Column('status', verbose_name=_('State'),
display_choices=SERVICE_STATE_DISPLAY_CHOICES)
updated_at = tables.Column('updated_at',
verbose_name=pgettext_lazy(
'Time since the last update',
u'Last Updated'),
filters=(utils_filters.parse_isotime,
filters.timesince))

def get_object_id(self, obj):
return "%s" % obj.engine_id

class Meta(object):
name = "heat_services"
verbose_name = _("Orchestration Services")
table_actions = (HeatServiceFilterAction,)
multi_select = False

+ 1
- 26
openstack_dashboard/dashboards/admin/info/tabs.py View File

@@ -18,7 +18,6 @@ from horizon import exceptions
from horizon import tabs
from openstack_dashboard.api import base
from openstack_dashboard.api import cinder
from openstack_dashboard.api import heat
from openstack_dashboard.api import neutron
from openstack_dashboard.api import nova
from openstack_dashboard.dashboards.admin.info import constants
@@ -118,32 +117,8 @@ class NetworkAgentsTab(tabs.TableTab):
return agents


class HeatServiceTab(tabs.TableTab):
table_classes = (tables.HeatServiceTable,)
name = tables.HeatServiceTable.Meta.verbose_name
slug = tables.HeatServiceTable.Meta.name
template_name = constants.INFO_DETAIL_TEMPLATE_NAME

def allowed(self, request):
try:
return base.is_service_enabled(request, 'orchestration')
except Exception:
exceptions.handle(request, _('Orchestration service is disabled.'))
return False

def get_heat_services_data(self):
try:
services = heat.service_list(self.tab_group.request)
except Exception:
msg = _('Unable to get Orchestration service list.')
exceptions.check_message(["Connection", "refused"], msg)
exceptions.handle(self.request, msg)
services = []
return services


class SystemInfoTabs(tabs.TabGroup):
slug = "system_info"
tabs = (ServicesTab, NovaServicesTab, CinderServicesTab,
NetworkAgentsTab, HeatServiceTab)
NetworkAgentsTab)
sticky = True

+ 1
- 16
openstack_dashboard/dashboards/admin/info/tests.py View File

@@ -29,7 +29,7 @@ class SystemInfoViewTests(test.BaseAdminViewTests):
api.nova: ('service_list',),
api.neutron: ('agent_list', 'is_extension_supported'),
api.cinder: ('service_list',),
api.heat: ('service_list',)})
})
def _test_base_index(self):
api.base.is_service_enabled(IsA(http.HttpRequest), IgnoreArg()) \
.MultipleTimes().AndReturn(True)
@@ -49,10 +49,6 @@ class SystemInfoViewTests(test.BaseAdminViewTests):
api.cinder.service_list(IsA(http.HttpRequest)).\
AndReturn(cinder_services)

heat_services = self.heat_services.list()
api.heat.service_list(IsA(http.HttpRequest)).\
AndReturn(heat_services)

self.mox.ReplayAll()

res = self.client.get(INDEX_URL)
@@ -88,14 +84,3 @@ class SystemInfoViewTests(test.BaseAdminViewTests):
)

self.mox.VerifyAll()

def test_heat_index(self):
res = self._test_base_index()
heat_services_tab = res.context['tab_group'].\
get_tab('heat_services')
self.assertQuerysetEqual(
heat_services_tab._tables['heat_services'].data,
[service.__repr__() for service in self.heat_services.list()]
)

self.mox.VerifyAll()

+ 0
- 0
openstack_dashboard/dashboards/project/stacks/__init__.py View File


+ 0
- 83
openstack_dashboard/dashboards/project/stacks/api.py View File

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

import json

from openstack_dashboard.api import heat

from openstack_dashboard.dashboards.project.stacks import mappings
from openstack_dashboard.dashboards.project.stacks import sro


class Stack(object):
pass


def d3_data(request, stack_id=''):
try:
stack = heat.stack_get(request, stack_id)
except Exception:
stack = Stack()
stack.id = stack_id
stack.stack_name = request.session.get('stack_name', '')
stack.stack_status = 'DELETE_COMPLETE'
stack.stack_status_reason = 'DELETE_COMPLETE'

try:
resources = heat.resources_list(request, stack.stack_name)
except Exception:
resources = []

d3_data = {"nodes": [], "stack": {}}
if stack:
stack_image = mappings.get_resource_image(stack.stack_status, 'stack')
stack_node = {
'stack_id': stack.id,
'name': stack.stack_name,
'status': stack.stack_status,
'image': stack_image,
'image_size': 60,
'image_x': -30,
'image_y': -30,
'text_x': 40,
'text_y': ".35em",
'in_progress': (stack.status == 'IN_PROGRESS'),
'info_box': sro.stack_info(stack, stack_image)
}
d3_data['stack'] = stack_node

if resources:
for resource in resources:
resource_image = mappings.get_resource_image(
resource.resource_status,
resource.resource_type)
resource_status = mappings.get_resource_status(
resource.resource_status)
if resource_status in ('IN_PROGRESS', 'INIT'):
in_progress = True
else:
in_progress = False
resource_node = {
'name': resource.resource_name,
'status': resource.resource_status,
'image': resource_image,
'required_by': resource.required_by,
'image_size': 50,
'image_x': -25,
'image_y': -25,
'text_x': 35,
'text_y': ".35em",
'in_progress': in_progress,
'info_box': sro.resource_info(resource)
}
d3_data['nodes'].append(resource_node)
return json.dumps(d3_data)

+ 0
- 488
openstack_dashboard/dashboards/project/stacks/forms.py View File

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

import json
import logging

import django
from django.conf import settings
from django.utils import html
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.debug import sensitive_variables

from oslo_utils import strutils
import six

from horizon import exceptions
from horizon import forms
from horizon import messages

from openstack_dashboard import api
from openstack_dashboard.dashboards.project.images \
import utils as image_utils
from openstack_dashboard.dashboards.project.instances \
import utils as instance_utils


LOG = logging.getLogger(__name__)


def create_upload_form_attributes(prefix, input_type, name):
"""Creates attribute dicts for the switchable upload form

:type prefix: str
:param prefix: prefix (environment, template) of field
:type input_type: str
:param input_type: field type (file, raw, url)
:type name: str
:param name: translated text label to display to user
:rtype: dict
:return: an attribute set to pass to form build
"""
attributes = {'class': 'switched', 'data-switch-on': prefix + 'source'}
attributes['data-' + prefix + 'source-' + input_type] = name
return attributes


class TemplateForm(forms.SelfHandlingForm):

class Meta(object):
name = _('Select Template')
help_text = _('Select a template to launch a stack.')

# TODO(jomara) - update URL choice for template & environment files
# w/ client side download when applicable
base_choices = [('file', _('File')),
('raw', _('Direct Input'))]
url_choice = [('url', _('URL'))]
attributes = {'class': 'switchable', 'data-slug': 'templatesource'}
template_source = forms.ChoiceField(label=_('Template Source'),
choices=base_choices + url_choice,
widget=forms.ThemableSelectWidget(
attrs=attributes))

attributes = create_upload_form_attributes(
'template',
'file',
_('Template File'))
template_upload = forms.FileField(
label=_('Template File'),
help_text=_('A local template to upload.'),
widget=forms.FileInput(attrs=attributes),
required=False)

attributes = create_upload_form_attributes(
'template',
'url',
_('Template URL'))
template_url = forms.URLField(
label=_('Template URL'),
help_text=_('An external (HTTP) URL to load the template from.'),
widget=forms.TextInput(attrs=attributes),
required=False)

attributes = create_upload_form_attributes(
'template',
'raw',
_('Template Data'))
template_data = forms.CharField(
label=_('Template Data'),
help_text=_('The raw contents of the template.'),
widget=forms.widgets.Textarea(attrs=attributes),
required=False)

attributes = {'data-slug': 'envsource', 'class': 'switchable'}
environment_source = forms.ChoiceField(
label=_('Environment Source'),
choices=base_choices,
widget=forms.ThemableSelectWidget(attrs=attributes),
required=False)

attributes = create_upload_form_attributes(
'env',
'file',
_('Environment File'))
environment_upload = forms.FileField(
label=_('Environment File'),
help_text=_('A local environment to upload.'),
widget=forms.FileInput(attrs=attributes),
required=False)

attributes = create_upload_form_attributes(
'env',
'raw',
_('Environment Data'))
environment_data = forms.CharField(
label=_('Environment Data'),
help_text=_('The raw contents of the environment file.'),
widget=forms.widgets.Textarea(attrs=attributes),
required=False)

if django.VERSION >= (1, 9):
# Note(Itxaka): On django>=1.9 Charfield has an strip option that
# we need to set to False as to not hit
# https://bugs.launchpad.net/python-heatclient/+bug/1546166
environment_data.strip = False
template_data.strip = False

def __init__(self, *args, **kwargs):
self.next_view = kwargs.pop('next_view')
super(TemplateForm, self).__init__(*args, **kwargs)

def clean(self):
cleaned = super(TemplateForm, self).clean()

files = self.request.FILES
self.clean_uploaded_files('template', _('template'), cleaned, files)
self.clean_uploaded_files('environment', _('environment'), cleaned,
files)

# Validate the template and get back the params.
kwargs = {}
if cleaned['environment_data']:
kwargs['environment'] = cleaned['environment_data']
try:
files, tpl =\
api.heat.get_template_files(cleaned.get('template_data'),
cleaned.get('template_url'))
kwargs['files'] = files
kwargs['template'] = tpl
validated = api.heat.template_validate(self.request, **kwargs)
cleaned['template_validate'] = validated
cleaned['template_validate']['files'] = files
cleaned['template_validate']['template'] = tpl
except Exception as e:
raise forms.ValidationError(six.text_type(e))

return cleaned

def clean_uploaded_files(self, prefix, field_label, cleaned, files):
"""Cleans Template & Environment data from form upload.

Does some of the crunchy bits for processing uploads vs raw
data depending on what the user specified. Identical process
for environment data & template data.

:type prefix: str
:param prefix: prefix (environment, template) of field
:type field_label: str
:param field_label: translated prefix str for messages
:type input_type: dict
:param prefix: existing cleaned fields from form
:rtype: dict
:return: cleaned dict including environment & template data
"""

upload_str = prefix + "_upload"
data_str = prefix + "_data"
url = cleaned.get(prefix + '_url')
data = cleaned.get(prefix + '_data')

has_upload = upload_str in files
# Uploaded file handler
if has_upload and not url:
log_template_name = files[upload_str].name
LOG.info('got upload %s', log_template_name)

tpl = files[upload_str].read()
if tpl.startswith('{'):
try:
json.loads(tpl)
except Exception as e:
msg = _('There was a problem parsing the'
' %(prefix)s: %(error)s')
msg = msg % {'prefix': prefix, 'error': six.text_type(e)}
raise forms.ValidationError(msg)
cleaned[data_str] = tpl

# URL handler
elif url and (has_upload or data):
msg = _('Please specify a %s using only one source method.')
msg = msg % field_label
raise forms.ValidationError(msg)

elif prefix == 'template':
# Check for raw template input - blank environment allowed
if not url and not data:
msg = _('You must specify a template via one of the '
'available sources.')
raise forms.ValidationError(msg)

def create_kwargs(self, data):
kwargs = {'parameters': data['template_validate'],
'environment_data': data['environment_data']}
if data.get('stack_id'):
kwargs['stack_id'] = data['stack_id']
return kwargs

def handle(self, request, data):
kwargs = self.create_kwargs(data)
# NOTE (gabriel): This is a bit of a hack, essentially rewriting this
# request so that we can chain it as an input to the next view...
# but hey, it totally works.
request.method = 'GET'

return self.next_view.as_view()(request, **kwargs)


class ChangeTemplateForm(TemplateForm):
class Meta(object):
name = _('Edit Template')
help_text = _('Select a new template to re-launch a stack.')
stack_id = forms.CharField(label=_('Stack ID'),
widget=forms.widgets.HiddenInput)
stack_name = forms.CharField(label=_('Stack Name'),
widget=forms.TextInput(attrs={'readonly':
'readonly'}))


class PreviewTemplateForm(TemplateForm):
class Meta(object):
name = _('Preview Template')
help_text = _('Select a new template to preview a stack.')


class CreateStackForm(forms.SelfHandlingForm):

param_prefix = '__param_'

class Meta(object):
name = _('Create Stack')

environment_data = forms.CharField(
widget=forms.widgets.HiddenInput,
required=False)
if django.VERSION >= (1, 9):
# Note(Itxaka): On django>=1.9 Charfield has an strip option that
# we need to set to False as to not hit
# https://bugs.launchpad.net/python-heatclient/+bug/1546166
environment_data.strip = False

parameters = forms.CharField(
widget=forms.widgets.HiddenInput)
stack_name = forms.RegexField(
max_length=255,
label=_('Stack Name'),
help_text=_('Name of the stack to create.'),
regex=r"^[a-zA-Z][a-zA-Z0-9_.-]*$",
error_messages={'invalid':
_('Name must start with a letter and may '
'only contain letters, numbers, underscores, '
'periods and hyphens.')})
timeout_mins = forms.IntegerField(
initial=60,
label=_('Creation Timeout (minutes)'),
help_text=_('Stack creation timeout in minutes.'))
enable_rollback = forms.BooleanField(
label=_('Rollback On Failure'),
help_text=_('Enable rollback on create/update failure.'),
required=False)

def __init__(self, *args, **kwargs):
parameters = kwargs.pop('parameters')
# special case: load template data from API, not passed in params
if kwargs.get('validate_me'):
parameters = kwargs.pop('validate_me')
super(CreateStackForm, self).__init__(*args, **kwargs)

if self._stack_password_enabled():
self.fields['password'] = forms.CharField(
label=_('Password for user "%s"') % self.request.user.username,
help_text=_('This is required for operations to be performed '
'throughout the lifecycle of the stack'),
widget=forms.PasswordInput())

self._build_parameter_fields(parameters)

def _stack_password_enabled(self):
stack_settings = getattr(settings, 'OPENSTACK_HEAT_STACK', {})
return stack_settings.get('enable_user_pass', True)

def _build_parameter_fields(self, template_validate):
self.help_text = template_validate['Description']

params = template_validate.get('Parameters', {})
if template_validate.get('ParameterGroups'):
params_in_order = []
for group in template_validate['ParameterGroups']:
for param in group.get('parameters', []):
if param in params:
params_in_order.append((param, params[param]))
else:
# no parameter groups, simply sorted to make the order fixed
params_in_order = sorted(params.items())
for param_key, param in params_in_order:
field = None
field_key = self.param_prefix + param_key
initial = param.get('Value',
param.get('DefaultValue',
param.get('Default')))
field_args = {
'initial': initial,
'label': param.get('Label', param_key),
'help_text': html.escape(param.get('Description', '')),
'required': initial is None,
}

param_type = param.get('Type', None)
hidden = strutils.bool_from_string(param.get('NoEcho', 'false'))
if 'CustomConstraint' in param:
choices = self._populate_custom_choices(
param['CustomConstraint'])
field_args['choices'] = choices
field = forms.ChoiceField(**field_args)

elif 'AllowedValues' in param:
choices = map(lambda x: (x, x), param['AllowedValues'])
field_args['choices'] = choices
field = forms.ChoiceField(**field_args)

elif param_type == 'Json' and 'Default' in param:
field_args['initial'] = json.dumps(param['Default'])
field = forms.CharField(**field_args)

elif param_type in ('CommaDelimitedList', 'String', 'Json'):
if 'MinLength' in param:
field_args['min_length'] = int(param['MinLength'])
field_args['required'] = field_args['min_length'] > 0
if 'MaxLength' in param:
field_args['max_length'] = int(param['MaxLength'])
if hidden:
field_args['widget'] = forms.PasswordInput(
render_value=True)
field = forms.CharField(**field_args)

elif param_type == 'Number':
if 'MinValue' in param:
field_args['min_value'] = int(param['MinValue'])
if 'MaxValue' in param:
field_args['max_value'] = int(param['MaxValue'])
field = forms.IntegerField(**field_args)

elif param_type == 'Boolean':
field_args['required'] = False
field = forms.BooleanField(**field_args)

if field:
self.fields[field_key] = field

@sensitive_variables('password')
def handle(self, request, data):
prefix_length = len(self.param_prefix)
params_list = [(k[prefix_length:], v) for (k, v) in data.items()
if k.startswith(self.param_prefix)]
fields = {
'stack_name': data.get('stack_name'),
'timeout_mins': data.get('timeout_mins'),
'disable_rollback': not(data.get('enable_rollback')),
'parameters': dict(params_list),
'files': json.loads(data.get('parameters')).get('files'),
'template': json.loads(data.get('parameters')).get('template')
}
if data.get('password'):
fields['password'] = data.get('password')

if data.get('environment_data'):
fields['environment'] = data.get('environment_data')

try:
api.heat.stack_create(self.request, **fields)
messages.info(request, _("Stack creation started."))
return True
except Exception:
exceptions.handle(request)

def _populate_custom_choices(self, custom_type):
if custom_type == 'neutron.network':
return instance_utils.network_field_data(self.request, True)
if custom_type == 'nova.keypair':
return instance_utils.keypair_field_data(self.request, True)
if custom_type == 'glance.image':
return image_utils.image_field_data(self.request, True)
if custom_type == 'nova.flavor':
return instance_utils.flavor_field_data(self.request, True)
return []


class EditStackForm(CreateStackForm):

class Meta(object):
name = _('Update Stack Parameters')

stack_id = forms.CharField(
label=_('Stack ID'),
widget=forms.widgets.HiddenInput)
stack_name = forms.CharField(
label=_('Stack Name'),
widget=forms.TextInput(attrs={'readonly': 'readonly'}))

@sensitive_variables('password')
def handle(self, request, data):
prefix_length = len(self.param_prefix)
params_list = [(k[prefix_length:], v) for (k, v) in data.items()
if k.startswith(self.param_prefix)]

stack_id = data.get('stack_id')
fields = {
'stack_name': data.get('stack_name'),
'timeout_mins': data.get('timeout_mins'),
'disable_rollback': not(data.get('enable_rollback')),
'parameters': dict(params_list),
'files': json.loads(data.get('parameters')).get('files'),
'template': json.loads(data.get('parameters')).get('template')
}
if data.get('password'):
fields['password'] = data.get('password')

if data.get('environment_data'):
fields['environment'] = data.get('environment_data')

try:
api.heat.stack_update(self.request, stack_id=stack_id, **fields)
messages.info(request, _("Stack update started."))
return True
except Exception:
exceptions.handle(request)


class PreviewStackForm(CreateStackForm):

class Meta(object):
name = _('Preview Stack Parameters')

def __init__(self, *args, **kwargs):
self.next_view = kwargs.pop('next_view')
super(CreateStackForm, self).__init__(*args, **kwargs)

def handle(self, request, data):
prefix_length = len(self.param_prefix)
params_list = [(k[prefix_length:], v) for (k, v) in data.items()
if k.startswith(self.param_prefix)]
fields = {
'stack_name': data.get('stack_name'),
'timeout_mins': data.get('timeout_mins'),
'disable_rollback': not(data.get('enable_rollback')),
'parameters': dict(params_list),
'files': json.loads(data.get('parameters')).get('files'),
'template': json.loads(data.get('parameters')).get('template')
}

if data.get('environment_data'):
fields['environment'] = data.get('environment_data')

try:
stack_preview = api.heat.stack_preview(self.request, **fields)
request.method = 'GET'
return self.next_view.as_view()(request,
stack_preview=stack_preview)
except Exception:
exceptions.handle(request)

+ 0
- 350
openstack_dashboard/dashboards/project/stacks/mappings.py View File

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

import json
import logging

from django.conf import settings
from django.core.urlresolvers import reverse
from django.template.defaultfilters import register
from django.utils import html
from django.utils import safestring
import six
import six.moves.urllib.parse as urlparse

from openstack_dashboard.api import swift

LOG = logging.getLogger(__name__)


resource_urls = {
"AWS::AutoScaling::AutoScalingGroup": {
'link': 'horizon:project:stacks:detail'},
"AWS::CloudFormation::Stack": {
'link': 'horizon:project:stacks:detail'},
"AWS::EC2::Instance": {
'link': 'horizon:project:instances:detail'},
"AWS::EC2::InternetGateway": {
'link': 'horizon:project:networks:ports:detail'},
"AWS::EC2::NetworkInterface": {
'link': 'horizon:project:networks:ports:detail'},
"AWS::EC2::RouteTable": {
'link': 'horizon:project:routers:detail'},
"AWS::EC2::SecurityGroup": {
'link': 'horizon:project:security_groups:index'},
"AWS::EC2::Subnet": {
'link': 'horizon:project:networks:subnets:detail'},
"AWS::EC2::Volume": {
'link': 'horizon:project:volumes:detail'},
"AWS::EC2::VPC": {
'link': 'horizon:project:networks:detail'},
"AWS::S3::Bucket": {
'link': 'horizon:project:containers:index'},
"OS::Cinder::Volume": {
'link': 'horizon:project:volumes:detail'},
"OS::Heat::AccessPolicy": {
'link': 'horizon:project:stacks:detail'},
"OS::Heat::AutoScalingGroup": {
'link': 'horizon:project:stacks:detail'},
"OS::Heat::CloudConfig": {
'link': 'horizon:project:stacks:detail'},
"OS::Neutron::Firewall": {
'link': 'horizon:project:firewalls:firewalldetails'},
"OS::Neutron::FirewallPolicy": {
'link': 'horizon:project:firewalls:policydetails'},
"OS::Neutron::FirewallRule": {
'link': 'horizon:project:firewalls:ruledetails'},
"OS::Heat::HARestarter": {
'link': 'horizon:project:stacks:detail'},
"OS::Heat::InstanceGroup": {
'link': 'horizon:project:stacks:detail'},
"OS::Heat::MultipartMime": {
'link': 'horizon:project:stacks:detail'},
"OS::Heat::ResourceGroup": {
'link': 'horizon:project:stacks:detail'},
"OS::Heat::SoftwareConfig": {
'link': 'horizon:project:stacks:detail'},
"OS::Heat::StructuredConfig": {
'link': 'horizon:project:stacks:detail'},
"OS::Heat::StructuredDeployment": {
'link': 'horizon:project:stacks:detail'},
"OS::Heat::Stack": {
'link': 'horizon:project:stacks:detail'},
"OS::Heat::WaitCondition": {
'link': 'horizon:project:stacks:detail'},
"OS::Heat::WaitConditionHandle": {
'link': 'horizon:project:stacks:detail'},
"OS::Neutron::IKEPolicy": {
'link': 'horizon:project:vpn:ikepolicydetails'},
"OS::Neutron::IPsecPolicy": {
'link': 'horizon:project:vpn:ipsecpolicydetails'},
"OS::Neutron::IPsecSiteConnection": {
'link': 'horizon:project:vpn:ipsecsiteconnectiondetails'},
"OS::Neutron::Net": {
'link': 'horizon:project:networks:detail'},
"OS::Neutron::Port": {
'link': 'horizon:project:networks:ports:detail'},
"OS::Neutron::Router": {
'link': 'horizon:project:routers:detail'},
"OS::Neutron::Subnet": {
'link': 'horizon:project:networks:subnets:detail'},
"OS::Neutron::VPNService": {
'link': 'horizon:project:vpn:vpnservicedetails'},
"OS::Nova::KeyPair": {
'link': 'horizon:project:key_pairs:index'},
"OS::Nova::Server": {
'link': 'horizon:project:instances:detail'},
"OS::Swift::Container": {
'link': 'horizon:project:containers:index',
'format_pattern': '%s' + swift.FOLDER_DELIMITER},
}


def resource_to_url(resource):
if (not resource or
not resource.physical_resource_id or
not hasattr(resource, 'resource_type')):
return None

mapping = resource_urls.get(resource.resource_type, {})
try:
if 'link' not in mapping:
return None
format_pattern = mapping.get('format_pattern') or '%s'
rid = format_pattern % resource.physical_resource_id
url = reverse(mapping['link'], args=(rid,))
except Exception as e:
LOG.exception(e)
return None
return url


@register.filter
def stack_output(output):
if not output:
return u''
if isinstance(output, six.string_types):
parts = urlparse.urlsplit(output)
if parts.netloc and parts.scheme in ('http', 'https'):
url = html.escape(output)
safe_link = u'<a href="%s" target="_blank">%s</a>' % (url, url)
return safestring.mark_safe(safe_link)
if isinstance(output, dict) or isinstance(output, list):
output = json.dumps(output, indent=2)
return safestring.mark_safe(u'<pre>%s</pre>' % html.escape(output))

static_url = getattr(settings, "STATIC_URL", "/static/")
resource_images = {
'LB_FAILED': static_url + 'dashboard/img/lb-red.svg',
'LB_DELETE': static_url + 'dashboard/img/lb-red.svg',
'LB_IN_PROGRESS': static_url + 'dashboard/img/lb-gray.gif',
'LB_INIT': static_url + 'dashboard/img/lb-gray.svg',
'LB_COMPLETE': static_url + 'dashboard/img/lb-green.svg',
'DB_FAILED': static_url + 'dashboard/img/db-red.svg',
'DB_DELETE': static_url + 'dashboard/img/db-red.svg',
'DB_IN_PROGRESS': static_url + 'dashboard/img/db-gray.gif',
'DB_INIT': static_url + 'dashboard/img/db-gray.svg',
'DB_COMPLETE': static_url + 'dashboard/img/db-green.svg',
'STACK_FAILED': static_url + 'dashboard/img/stack-red.svg',
'STACK_DELETE': static_url + 'dashboard/img/stack-red.svg',
'STACK_IN_PROGRESS': static_url + 'dashboard/img/stack-gray.gif',
'STACK_INIT': static_url + 'dashboard/img/stack-gray.svg',
'STACK_COMPLETE': static_url + 'dashboard/img/stack-green.svg',
'SERVER_FAILED': static_url + 'dashboard/img/server-red.svg',
'SERVER_DELETE': static_url + 'dashboard/img/server-red.svg',
'SERVER_IN_PROGRESS': static_url + 'dashboard/img/server-gray.gif',
'SERVER_INIT': static_url + 'dashboard/img/server-gray.svg',
'SERVER_COMPLETE': static_url + 'dashboard/img/server-green.svg',
'ALARM_FAILED': static_url + 'dashboard/img/alarm-red.svg',
'ALARM_DELETE': static_url + 'dashboard/img/alarm-red.svg',
'ALARM_IN_PROGRESS': static_url + 'dashboard/img/alarm-gray.gif',
'ALARM_INIT': static_url + 'dashboard/img/alarm-gray.svg',
'ALARM_COMPLETE': static_url + 'dashboard/img/alarm-green.svg',
'VOLUME_FAILED': static_url + 'dashboard/img/volume-red.svg',
'VOLUME_DELETE': static_url + 'dashboard/img/volume-red.svg',
'VOLUME_IN_PROGRESS': static_url + 'dashboard/img/volume-gray.gif',
'VOLUME_INIT': static_url + 'dashboard/img/volume-gray.svg',
'VOLUME_COMPLETE': static_url + 'dashboard/img/volume-green.svg',
'IMAGE_FAILED': static_url + 'dashboard/img/image-red.svg',
'IMAGE_DELETE': static_url + 'dashboard/img/image-red.svg',
'IMAGE_IN_PROGRESS': static_url + 'dashboard/img/image-gray.gif',
'IMAGE_INIT': static_url + 'dashboard/img/image-gray.svg',
'IMAGE_COMPLETE': static_url + 'dashboard/img/image-green.svg',
'WAIT_FAILED': static_url + 'dashboard/img/wait-red.svg',
'WAIT_DELETE': static_url + 'dashboard/img/wait-red.svg',
'WAIT_IN_PROGRESS': static_url + 'dashboard/img/wait-gray.gif',
'WAIT_INIT': static_url + 'dashboard/img/wait-gray.svg',
'WAIT_COMPLETE': static_url + 'dashboard/img/wait-green.svg',
'FIREWALL_FAILED': static_url + 'dashboard/img/firewall-red.svg',
'FIREWALL_DELETE': static_url + 'dashboard/img/firewall-red.svg',
'FIREWALL_IN_PROGRESS': static_url + 'dashboard/img/firewall-gray.gif',
'FIREWALL_INIT': static_url + 'dashboard/img/firewall-gray.svg',
'FIREWALL_COMPLETE': static_url + 'dashboard/img/firewall-green.svg',
'FLOATINGIP_FAILED': static_url + 'dashboard/img/floatingip-red.svg',
'FLOATINGIP_DELETE': static_url + 'dashboard/img/floatingip-red.svg',
'FLOATINGIP_IN_PROGRESS': static_url + 'dashboard/img/floatingip-gray.gif',
'FLOATINGIP_INIT': static_url + 'dashboard/img/floatingip-gray.svg',
'FLOATINGIP_COMPLETE': static_url + 'dashboard/img/floatingip-green.svg',
'ROUTER_FAILED': static_url + 'dashboard/img/router-red.svg',
'ROUTER_DELETE': static_url + 'dashboard/img/router-red.svg',
'ROUTER_IN_PROGRESS': static_url + 'dashboard/img/router-gray.gif',
'ROUTER_INIT': static_url + 'dashboard/img/router-gray.svg',
'ROUTER_COMPLETE': static_url + 'dashboard/img/router-green.svg',
'POLICY_FAILED': static_url + 'dashboard/img/policy-red.svg',
'POLICY_DELETE': static_url + 'dashboard/img/policy-red.svg',
'POLICY_IN_PROGRESS': static_url + 'dashboard/img/policy-gray.gif',
'POLICY_INIT': static_url + 'dashboard/img/policy-gray.svg',
'POLICY_COMPLETE': static_url + 'dashboard/img/policy-green.svg',
'CONFIG_FAILED': static_url + 'dashboard/img/config-red.svg',
'CONFIG_DELETE': static_url + 'dashboard/img/config-red.svg',
'CONFIG_IN_PROGRESS': static_url + 'dashboard/img/config-gray.gif',
'CONFIG_INIT': static_url + 'dashboard/img/config-gray.svg',
'CONFIG_COMPLETE': static_url + 'dashboard/img/config-green.svg',
'NETWORK_FAILED': static_url + 'dashboard/img/network-red.svg',
'NETWORK_DELETE': static_url + 'dashboard/img/network-red.svg',
'NETWORK_IN_PROGRESS': static_url + 'dashboard/img/network-gray.gif',
'NETWORK_INIT': static_url + 'dashboard/img/network-gray.svg',
'NETWORK_COMPLETE': static_url + 'dashboard/img/network-green.svg',
'PORT_FAILED': static_url + 'dashboard/img/port-red.svg',
'PORT_DELETE': static_url + 'dashboard/img/port-red.svg',
'PORT_IN_PROGRESS': static_url + 'dashboard/img/port-gray.gif',
'PORT_INIT': static_url + 'dashboard/img/port-gray.svg',
'PORT_COMPLETE': static_url + 'dashboard/img/port-green.svg',
'SECURITYGROUP_FAILED': static_url + 'dashboard/img/securitygroup-red.svg',
'SECURITYGROUP_DELETE': static_url + 'dashboard/img/securitygroup-red.svg',
'SECURITYGROUP_IN_PROGRESS':
static_url + 'dashboard/img/securitygroup-gray.gif',
'SECURITYGROUP_INIT': static_url + 'dashboard/img/securitygroup-gray.svg',
'SECURITYGROUP_COMPLETE':
static_url + 'dashboard/img/securitygroup-green.svg',
'VPN_FAILED': static_url + 'dashboard/img/vpn-red.svg',
'VPN_DELETE': static_url + 'dashboard/img/vpn-red.svg',
'VPN_IN_PROGRESS': static_url + 'dashboard/img/vpn-gray.gif',
'VPN_INIT': static_url + 'dashboard/img/vpn-gray.svg',
'VPN_COMPLETE': static_url + 'dashboard/img/vpn-green.svg',
'FLAVOR_FAILED': static_url + 'dashboard/img/flavor-red.svg',
'FLAVOR_DELETE': static_url + 'dashboard/img/flavor-red.svg',
'FLAVOR_IN_PROGRESS': static_url + 'dashboard/img/flavor-gray.gif',
'FLAVOR_INIT': static_url + 'dashboard/img/flavor-gray.svg',
'FLAVOR_COMPLETE': static_url + 'dashboard/img/flavor-green.svg',
'KEYPAIR_FAILED': static_url + 'dashboard/img/keypair-red.svg',
'KEYPAIR_DELETE': static_url + 'dashboard/img/keypair-red.svg',
'KEYPAIR_IN_PROGRESS': static_url + 'dashboard/img/keypair-gray.gif',
'KEYPAIR_INIT': static_url + 'dashboard/img/keypair-gray.svg',
'KEYPAIR_COMPLETE': static_url + 'dashboard/img/keypair-green.svg',
'UNKNOWN_FAILED': static_url + 'dashboard/img/unknown-red.svg',
'UNKNOWN_DELETE': static_url + 'dashboard/img/unknown-red.svg',
'UNKNOWN_IN_PROGRESS': static_url + 'dashboard/img/unknown-gray.gif',
'UNKNOWN_INIT': static_url + 'dashboard/img/unknown-gray.svg',
'UNKNOWN_COMPLETE': static_url + 'dashboard/img/unknown-green.svg',
}


resource_types = {
# LB
'LoadBalance': 'LB',
'HealthMonitor': 'LB',
'PoolMember': 'LB',
'Pool': 'LB',
# DB
'DBInstance': 'DB',
'Database': 'DB',
# SERVER
'Instance': 'SERVER',
'Server': 'SERVER',
# ALARM
'Alarm': 'ALARM',
'CombinationAlarm': 'ALARM',
'CWLiteAlarm': 'ALARM',
# VOLUME
'Volume': 'VOLUME',
'VolumeAttachment': 'VOLUME',
# STACK
'stack': 'STACK',
'AutoScalingGroup': 'STACK',
'InstanceGroup': 'STACK',
'ServerGroup': 'STACK',
'ResourceGroup': 'STACK',
# IMAGE
'Image': 'IMAGE',
# WAIT
'WaitCondition': 'WAIT',
'WaitConditionHandle': 'WAIT',
'UpdateWaitConditionHandle': 'WAIT',
# FIREWALL
'Firewall': 'FIREWALL',
'FirewallPolicy': 'FIREWALL',
'FirewallRule': 'FIREWALL',
# FLOATINGIP
'FloatingIP': 'FLOATINGIP',
'FloatingIPAssociation': 'FLOATINGIP',
# ROUTER
'Router': 'ROUTER',
'RouterGateway': 'ROUTER',
'RouterInterface': 'ROUTER',
# POLICY
'ScalingPolicy': 'POLICY',
# CONFIG
'CloudConfig': 'CONFIG',
'MultipartMime': 'CONFIG',
'SoftwareConfig': 'CONFIG',
'SoftwareDeployment': 'CONFIG',
'StructuredConfig': 'CONFIG',
'StructuredDeployment': 'CONFIG',
# NETWORK
'Net': 'NETWORK',
'Subnet': 'NETWORK',
'NetworkGateway': 'NETWORK',
'ProviderNet': 'NETWORK',
# PORT
'Port': 'PORT',
# SECURITYGROUP
'SecurityGroup': 'SECURITYGROUP',
# VPN
'VPNService': 'VPN',
# FLAVOR
'Flavor': 'FLAVOR',
# KEYPAIR
'KeyPair': 'KEYPAIR',
}


def get_resource_type(type):
for key, value in resource_types.items():
if key in type:
return value

return 'UNKNOWN'


def get_resource_status(status):
if ('IN_PROGRESS' in status):
return 'IN_PROGRESS'
elif ('FAILED' in status):
return 'FAILED'
elif ('DELETE' in status):
return 'DELETE'
elif ('INIT' in status):
return 'INIT'
else:
return 'COMPLETE'


def get_resource_image(status, type):
"""Sets the image url and in_progress action sw based on status."""
resource_type = get_resource_type(type)
resource_status = get_resource_status(status)
resource_state = resource_type + "_" + resource_status

for key in resource_images:
if key == resource_state:
return resource_images.get(key)

+ 0
- 21
openstack_dashboard/dashboards/project/stacks/panel.py View File

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

from django.utils.translation import ugettext_lazy as _

import horizon


class Stacks(horizon.Panel):
name = _("Stacks")
slug = "stacks"
permissions = ('openstack.services.orchestration',)

+ 0
- 0
openstack_dashboard/dashboards/project/stacks/resource_types/__init__.py View File


+ 0
- 23
openstack_dashboard/dashboards/project/stacks/resource_types/panel.py View File

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

from django.utils.translation import ugettext_lazy as _

import horizon


class ResourceTypes(horizon.Panel):
name = _("Resource Types")
slug = "stacks.resource_types"
permissions = ('openstack.services.orchestration',)
policy_rules = (("orchestration", "stacks:list_resource_types"),)

+ 0
- 36
openstack_dashboard/dashboards/project/stacks/resource_types/tables.py View File

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

from django.utils.translation import ugettext_lazy as _

from horizon import tables


class ResourceTypesFilterAction(tables.FilterAction):
filter_type = 'server'
filter_choices = (('name', _('Type ='), True, _("Case sensitive")),)


class ResourceTypesTable(tables.DataTable):
name = tables.Column("resource_type",
verbose_name=_("Type"),
link="horizon:project:stacks.resource_types:details",)

def get_object_id(self, resource):
return resource.resource_type

class Meta(object):
name = "resource_types"
verbose_name = _("Resource Types")
table_actions = (ResourceTypesFilterAction,)
multi_select = False

+ 0
- 32
openstack_dashboard/dashboards/project/stacks/resource_types/tabs.py View File

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

from django.utils.translation import ugettext_lazy as _

from horizon import tabs


class ResourceTypeOverviewTab(tabs.Tab):
name = _("Overview")
slug = "resource_type_overview"
template_name = "project/stacks.resource_types/_details.html"

def get_context_data(self, request):
return {"r_type": self.tab_group.kwargs['rt'],
"r_type_attributes": self.tab_group.kwargs['rt_attributes'],
"r_type_properties": self.tab_group.kwargs['rt_properties']}


class ResourceTypeDetailsTabs(tabs.TabGroup):
slug = "resource_type_details"
tabs = (ResourceTypeOverviewTab,)

+ 0
- 15
openstack_dashboard/dashboards/project/stacks/resource_types/templates/stacks.resource_types/_details.html View File

@@ -1,15 +0,0 @@
{% load i18n %}

<div class="detail">
<dl>
<dd>{{ r_type }}</dd>
</dl>

<h4>{% trans "Attributes" %}</h4>
<pre>{{ r_type_attributes }}
</pre>

<h4>{% trans "Properties" %}</h4>
<pre>{{ r_type_properties }}
</pre>
</div>

+ 0
- 52
openstack_dashboard/dashboards/project/stacks/resource_types/tests.py View File

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

from django.core.urlresolvers import reverse
from django import http

from mox3.mox import IsA

from openstack_dashboard import api
from openstack_dashboard.test import helpers as test


class ResourceTypesTests(test.TestCase):

@test.create_stubs({api.heat: ('resource_types_list',)})
def test_index(self):
filters = {}
api.heat.resource_types_list(
IsA(http.HttpRequest), filters=filters).AndReturn(
self.resource_types.list())
self.mox.ReplayAll()