Merge "Add RBAC policies feature to horizon dashboard"
This commit is contained in:
commit
188fd6dc19
@ -1671,6 +1671,7 @@ Default:
|
||||
'enable_ha_router': False,
|
||||
'enable_ipv6': True,
|
||||
'enable_quotas': False,
|
||||
'enable_rbac_policy': True,
|
||||
'enable_router': True,
|
||||
'extra_provider_types': {},
|
||||
'physical_networks': [],
|
||||
@ -1775,6 +1776,16 @@ Enable support for Neutron quotas feature. To make this feature work
|
||||
appropriately, you need to use Neutron plugins with quotas extension support
|
||||
and quota_driver should be DbQuotaDriver (default config).
|
||||
|
||||
enable_rbac_policy
|
||||
##################
|
||||
|
||||
.. versionadded:: 15.0.0(Stein)
|
||||
|
||||
Default: ``True``
|
||||
|
||||
Set this to True to enable RBAC Policies panel that provide the ability for
|
||||
users to use RBAC function. This option only affects when Neutron is enabled.
|
||||
|
||||
enable_router
|
||||
#############
|
||||
|
||||
|
@ -1992,3 +1992,59 @@ def list_availability_zones(request, resource=None, state=None):
|
||||
az_list = [az for az in az_list if az['state'] == state]
|
||||
|
||||
return sorted(az_list, key=lambda zone: zone['name'])
|
||||
|
||||
|
||||
class RBACPolicy(NeutronAPIDictWrapper):
|
||||
"""Wrapper for neutron RBAC Policy."""
|
||||
|
||||
|
||||
def rbac_policy_create(request, **kwargs):
|
||||
"""Create a RBAC Policy.
|
||||
|
||||
:param request: request context
|
||||
:param target_tenant: target tenant of the policy
|
||||
:param tenant_id: owner tenant of the policy(Not recommended)
|
||||
:param object_type: network or qos_policy
|
||||
:param object_id: object id of policy
|
||||
:param action: access_as_shared or access_as_external
|
||||
:return: RBACPolicy object
|
||||
"""
|
||||
body = {'rbac_policy': kwargs}
|
||||
rbac_policy = neutronclient(request).create_rbac_policy(
|
||||
body=body).get('rbac_policy')
|
||||
return RBACPolicy(rbac_policy)
|
||||
|
||||
|
||||
def rbac_policy_list(request, **kwargs):
|
||||
"""List of RBAC Policies."""
|
||||
policies = neutronclient(request).list_rbac_policies(
|
||||
**kwargs).get('rbac_policies')
|
||||
return [RBACPolicy(p) for p in policies]
|
||||
|
||||
|
||||
def rbac_policy_update(request, policy_id, **kwargs):
|
||||
"""Update a RBAC Policy.
|
||||
|
||||
:param request: request context
|
||||
:param policy_id: target policy id
|
||||
:param target_tenant: target tenant of the policy
|
||||
:return: RBACPolicy object
|
||||
"""
|
||||
body = {'rbac_policy': kwargs}
|
||||
rbac_policy = neutronclient(request).update_rbac_policy(
|
||||
policy_id, body=body).get('rbac_policy')
|
||||
return RBACPolicy(rbac_policy)
|
||||
|
||||
|
||||
@profiler.trace
|
||||
def rbac_policy_get(request, policy_id, **kwargs):
|
||||
"""Get RBAC policy for a given policy id."""
|
||||
policy = neutronclient(request).show_rbac_policy(
|
||||
policy_id, **kwargs).get('rbac_policy')
|
||||
return RBACPolicy(policy)
|
||||
|
||||
|
||||
@profiler.trace
|
||||
def rbac_policy_delete(request, policy_id):
|
||||
"""Delete RBAC policy for a given policy id."""
|
||||
neutronclient(request).delete_rbac_policy(policy_id)
|
||||
|
170
openstack_dashboard/dashboards/admin/rbac_policies/forms.py
Normal file
170
openstack_dashboard/dashboards/admin/rbac_policies/forms.py
Normal file
@ -0,0 +1,170 @@
|
||||
# Copyright 2019 vmware, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import logging
|
||||
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
from openstack_dashboard import api
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Predefined provider types.
|
||||
ACTIONS = [
|
||||
{
|
||||
'name': 'access_as_shared',
|
||||
'value': _('Access as Shared')
|
||||
},
|
||||
{
|
||||
'name': 'access_as_external',
|
||||
'value': _('Access as External')
|
||||
}
|
||||
]
|
||||
|
||||
# Predefined provider object types.
|
||||
OBJECT_TYPES = [
|
||||
{
|
||||
'name': 'network',
|
||||
'value': _('Network')
|
||||
}
|
||||
]
|
||||
|
||||
QOS_POLICY_TYPE = {
|
||||
'name': 'qos_policy',
|
||||
'value': _('QoS Policy')
|
||||
}
|
||||
|
||||
|
||||
class CreatePolicyForm(forms.SelfHandlingForm):
|
||||
target_tenant = forms.ThemableChoiceField(label=_("Target Project"))
|
||||
object_type = forms.ThemableChoiceField(
|
||||
label=_("Object Type"),
|
||||
widget=forms.ThemableSelectWidget(
|
||||
attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'object_type'
|
||||
}))
|
||||
network_id = forms.ThemableChoiceField(
|
||||
label=_("Network"),
|
||||
widget=forms.ThemableSelectWidget(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'object_type',
|
||||
}),
|
||||
required=False)
|
||||
qos_policy_id = forms.ThemableChoiceField(
|
||||
label=_("QoS Policy"),
|
||||
widget=forms.ThemableSelectWidget(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'object_type',
|
||||
}),
|
||||
required=False)
|
||||
action = forms.ThemableChoiceField(label=_("Action"))
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(CreatePolicyForm, self).__init__(request, *args, **kwargs)
|
||||
tenant_choices = [('', _("Select a project"))]
|
||||
tenants, has_more = api.keystone.tenant_list(request)
|
||||
tenant_choices.append(("*", "*"))
|
||||
for tenant in tenants:
|
||||
tenant_choices.append((tenant.id, tenant.name))
|
||||
self.fields['target_tenant'].choices = tenant_choices
|
||||
action_choices = [('', _("Select an action"))]
|
||||
for action in ACTIONS:
|
||||
action_choices.append((action['name'],
|
||||
action['value']))
|
||||
self.fields['action'].choices = action_choices
|
||||
network_choices = []
|
||||
networks = api.neutron.network_list(request)
|
||||
for network in networks:
|
||||
network_choices.append((network.id, network.name))
|
||||
self.fields['network_id'].choices = network_choices
|
||||
|
||||
# If enable QoS Policy
|
||||
if api.neutron.is_extension_supported(request, extension_alias='qos'):
|
||||
qos_policies = api.neutron.policy_list(request)
|
||||
qos_choices = [(qos_policy['id'], qos_policy['name'])
|
||||
for qos_policy in qos_policies]
|
||||
self.fields['qos_policy_id'].choices = qos_choices
|
||||
if QOS_POLICY_TYPE not in OBJECT_TYPES:
|
||||
OBJECT_TYPES.append(QOS_POLICY_TYPE)
|
||||
|
||||
object_type_choices = [('', _("Select an object type"))]
|
||||
for object_type in OBJECT_TYPES:
|
||||
object_type_choices.append((object_type['name'],
|
||||
object_type['value']))
|
||||
self.fields['object_type'].choices = object_type_choices
|
||||
|
||||
# Register object types which required
|
||||
self.fields['network_id'].widget.attrs.update(
|
||||
{'data-object_type-network': _('Network')})
|
||||
self.fields['qos_policy_id'].widget.attrs.update(
|
||||
{'data-object_type-qos_policy': _('QoS Policy')})
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
params = {
|
||||
'target_tenant': data['target_tenant'],
|
||||
'action': data['action'],
|
||||
'object_type': data['object_type'],
|
||||
}
|
||||
if data['object_type'] == 'network':
|
||||
params['object_id'] = data['network_id']
|
||||
elif data['object_type'] == 'qos_policy':
|
||||
params['object_id'] = data['qos_policy_id']
|
||||
|
||||
rbac_policy = api.neutron.rbac_policy_create(request, **params)
|
||||
msg = _('RBAC Policy was successfully created.')
|
||||
messages.success(request, msg)
|
||||
return rbac_policy
|
||||
except Exception:
|
||||
redirect = reverse('horizon:admin:rbac_policies:index')
|
||||
msg = _('Failed to create a rbac policy.')
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
return False
|
||||
|
||||
|
||||
class UpdatePolicyForm(forms.SelfHandlingForm):
|
||||
target_tenant = forms.ThemableChoiceField(label=_("Target Project"))
|
||||
failure_url = 'horizon:admin:rbac_policies:index'
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(UpdatePolicyForm, self).__init__(request, *args, **kwargs)
|
||||
tenant_choices = [('', _("Select a project"))]
|
||||
tenants, has_more = api.keystone.tenant_list(request)
|
||||
for tenant in tenants:
|
||||
tenant_choices.append((tenant.id, tenant.name))
|
||||
self.fields['target_tenant'].choices = tenant_choices
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
params = {'target_tenant': data['target_tenant']}
|
||||
rbac_policy = api.neutron.rbac_policy_update(
|
||||
request, self.initial['rbac_policy_id'], **params)
|
||||
msg = _('RBAC Policy %s was successfully updated.') \
|
||||
% self.initial['rbac_policy_id']
|
||||
messages.success(request, msg)
|
||||
return rbac_policy
|
||||
except Exception as e:
|
||||
LOG.info('Failed to update rbac policy %(id)s: %(exc)s',
|
||||
{'id': self.initial['rbac_policy_id'], 'exc': e})
|
||||
msg = _('Failed to update rbac policy %s') \
|
||||
% self.initial['rbac_policy_id']
|
||||
redirect = reverse(self.failure_url)
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
44
openstack_dashboard/dashboards/admin/rbac_policies/panel.py
Normal file
44
openstack_dashboard/dashboards/admin/rbac_policies/panel.py
Normal file
@ -0,0 +1,44 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import horizon
|
||||
|
||||
from openstack_dashboard.api import neutron
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RBACPolicies(horizon.Panel):
|
||||
name = _("RBAC Policies")
|
||||
slug = "rbac_policies"
|
||||
permissions = ('openstack.services.network',)
|
||||
policy_rules = (("network", "context_is_admin"),)
|
||||
|
||||
def allowed(self, context):
|
||||
request = context['request']
|
||||
network_config = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {})
|
||||
try:
|
||||
return (
|
||||
network_config.get('enable_rbac_policy', True) and
|
||||
neutron.is_extension_supported(request,
|
||||
extension_alias='rbac-policies')
|
||||
)
|
||||
except Exception:
|
||||
LOG.error("Call to list enabled services failed. This is likely "
|
||||
"due to a problem communicating with the Neutron "
|
||||
"endpoint. RBAC Policies panel will not be displayed.")
|
||||
return False
|
82
openstack_dashboard/dashboards/admin/rbac_policies/tables.py
Normal file
82
openstack_dashboard/dashboards/admin/rbac_policies/tables.py
Normal file
@ -0,0 +1,82 @@
|
||||
# Copyright 2019 vmware, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from horizon import tables
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard import policy
|
||||
|
||||
|
||||
class CreateRBACPolicy(policy.PolicyTargetMixin, tables.LinkAction):
|
||||
name = "create"
|
||||
verbose_name = _("Create RBAC Policy")
|
||||
url = "horizon:admin:rbac_policies:create"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "plus"
|
||||
policy_rules = (("network", "create_rbac_policy"),)
|
||||
|
||||
|
||||
class DeleteRBACPolicy(policy.PolicyTargetMixin, tables.DeleteAction):
|
||||
help_text = _("Deleted RBAC policy is not recoverable.")
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete RBAC Policy",
|
||||
u"Delete RBAC Policies",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Deleted RBAC Policy",
|
||||
u"Deleted RBAC Policies",
|
||||
count
|
||||
)
|
||||
|
||||
policy_rules = (("network", "delete_rbac_policy"),)
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
api.neutron.rbac_policy_delete(request, obj_id)
|
||||
|
||||
|
||||
class UpdateRBACPolicy(policy.PolicyTargetMixin, tables.LinkAction):
|
||||
name = "update"
|
||||
verbose_name = _("Edit Policy")
|
||||
url = "horizon:admin:rbac_policies:update"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "pencil"
|
||||
policy_rules = (("network", "update_rbac_policy"),)
|
||||
|
||||
|
||||
class RBACPoliciesTable(tables.DataTable):
|
||||
tenant = tables.Column("tenant_name", verbose_name=_("Project"))
|
||||
id = tables.WrappingColumn('id',
|
||||
verbose_name=_('ID'),
|
||||
link="horizon:admin:rbac_policies:detail")
|
||||
object_type = tables.WrappingColumn('object_type',
|
||||
verbose_name=_('Object Type'))
|
||||
object_name = tables.Column("object_name", verbose_name=_("Object"))
|
||||
target_tenant = tables.Column("target_tenant_name",
|
||||
verbose_name=_("Target Project"))
|
||||
|
||||
class Meta(object):
|
||||
name = "rbac policies"
|
||||
verbose_name = _("RBAC Policies")
|
||||
table_actions = (CreateRBACPolicy, DeleteRBACPolicy,)
|
||||
row_actions = (UpdateRBACPolicy, DeleteRBACPolicy)
|
57
openstack_dashboard/dashboards/admin/rbac_policies/tabs.py
Normal file
57
openstack_dashboard/dashboards/admin/rbac_policies/tabs.py
Normal file
@ -0,0 +1,57 @@
|
||||
# Copyright 2019 vmware, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 exceptions
|
||||
from horizon import tabs
|
||||
from horizon.utils import memoized
|
||||
|
||||
from openstack_dashboard import api
|
||||
|
||||
|
||||
class OverviewTab(tabs.Tab):
|
||||
name = _("Overview")
|
||||
slug = "overview"
|
||||
template_name = "admin/rbac_policies/_detail_overview.html"
|
||||
preload = False
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data(self):
|
||||
rbac_policy = {}
|
||||
rbac_policy_id = None
|
||||
try:
|
||||
rbac_policy_id = self.tab_group.kwargs['rbac_policy_id']
|
||||
rbac_policy = api.neutron.rbac_policy_get(self.request,
|
||||
rbac_policy_id)
|
||||
|
||||
except Exception:
|
||||
msg = _('Unable to retrieve details for rbac_policy "%s".') \
|
||||
% (rbac_policy_id)
|
||||
exceptions.handle(self.request, msg)
|
||||
return rbac_policy
|
||||
|
||||
def get_context_data(self, request, **kwargs):
|
||||
context = super(OverviewTab, self).get_context_data(request, **kwargs)
|
||||
rbac_policy = self._get_data()
|
||||
|
||||
context["rbac_policy"] = rbac_policy
|
||||
return context
|
||||
|
||||
|
||||
class RBACDetailsTabs(tabs.DetailTabsGroup):
|
||||
slug = "rbac_policy_tabs"
|
||||
tabs = (OverviewTab, )
|
||||
sticky = True
|
@ -0,0 +1,7 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% trans "From here you can create a rbac policy." %}</p>
|
||||
{% endblock %}
|
@ -0,0 +1,18 @@
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
<div class="info detail">
|
||||
<dl class="dl-horizontal">
|
||||
<dt title="{% trans 'ID' %}">{% trans "ID" %}</dt>
|
||||
<dd>{{ rbac_policy.id|default:_("None") }}</dd>
|
||||
<dt title="{% trans 'Project ID' %}">{% trans "Project ID" %}</dt>
|
||||
<dd>{{ rbac_policy.project_id|default:_("-") }}</dd>
|
||||
<dt title="{% trans 'Object Type' %}">{% trans "Object Type" %}</dt>
|
||||
<dd>{{ rbac_policy.object_type|default:_("Unknown") }}</dd>
|
||||
<dt title="{% trans 'Object ID' %}">{% trans "Object ID" %}</dt>
|
||||
<dd>{{ rbac_policy.object_id|default:_("Unknown") }}</dd>
|
||||
<dt title="{% trans 'Action' %}">{% trans "Action" %}</dt>
|
||||
<dd>{{ rbac_policy.action|default:_("Unknown") }}</dd>
|
||||
<dt title="{% trans 'Target Tenant' %}">{% trans "Target Tenant" %}</dt>
|
||||
<dd>{{ rbac_policy.target_tenant|default:_("None") }}</dd>
|
||||
</dl>
|
||||
</div>
|
@ -0,0 +1,7 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% trans "You may update the editable properties of the RBAC policy here." %}</p>
|
||||
{% endblock %}
|
@ -0,0 +1,7 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create a RBAC Policy" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'admin/rbac_policies/_create.html' %}
|
||||
{% endblock %}
|
@ -0,0 +1,20 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "RBAC Policy Details" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_detail_header.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<hr>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{{ tab_group.render }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,7 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Update RBAC Policy" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/rbac_policies/_update.html' %}
|
||||
{% endblock %}
|
122
openstack_dashboard/dashboards/admin/rbac_policies/tests.py
Normal file
122
openstack_dashboard/dashboards/admin/rbac_policies/tests.py
Normal file
@ -0,0 +1,122 @@
|
||||
# Copyright 2019 vmware, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from django.urls import reverse
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.test import helpers as test
|
||||
|
||||
INDEX_TEMPLATE = 'horizon/common/_data_table_view.html'
|
||||
INDEX_URL = reverse('horizon:admin:rbac_policies:index')
|
||||
|
||||
|
||||
class RBACPolicyTests(test.BaseAdminViewTests):
|
||||
|
||||
@test.create_mocks({api.neutron: ('rbac_policy_list',
|
||||
'network_list',
|
||||
'policy_list',
|
||||
'is_extension_supported',),
|
||||
api.keystone: ('tenant_list',)})
|
||||
def test_index(self):
|
||||
tenants = self.tenants.list()
|
||||
|
||||
self.mock_tenant_list.return_value = [tenants, False]
|
||||
self.mock_network_list.return_value = self.networks.list()
|
||||
self.mock_policy_list.return_value = self.qos_policies.list()
|
||||
self.mock_rbac_policy_list.return_value = self.rbac_policies.list()
|
||||
self.mock_is_extension_supported.return_value = True
|
||||
|
||||
res = self.client.get(INDEX_URL)
|
||||
self.assertTemplateUsed(res, INDEX_TEMPLATE)
|
||||
rbac_policies = res.context['table'].data
|
||||
self.assertItemsEqual(rbac_policies, self.rbac_policies.list())
|
||||
self.mock_network_list.assert_called_once_with(test.IsHttpRequest())
|
||||
self.mock_policy_list.assert_called_once_with(test.IsHttpRequest())
|
||||
self.mock_tenant_list.assert_called_once_with(test.IsHttpRequest())
|
||||
self.mock_is_extension_supported.assert_called_once_with(
|
||||
test.IsHttpRequest(), extension_alias='qos')
|
||||
self.mock_rbac_policy_list.assert_called_once_with(
|
||||
test.IsHttpRequest())
|
||||
|
||||
@test.create_mocks({api.neutron: ('network_list',
|
||||
'rbac_policy_create',
|
||||
'is_extension_supported',),
|
||||
api.keystone: ('tenant_list',)})
|
||||
def test_rbac_create_post_with_network_type(self):
|
||||
network = self.networks.first()
|
||||
tenants = self.tenants.list()
|
||||
rbac_policy = self.rbac_policies.first()
|
||||
|
||||
self.mock_tenant_list.return_value = [tenants, False]
|
||||
self.mock_network_list.return_value = self.networks.list()
|
||||
self.mock_is_extension_supported.return_value = False
|
||||
self.mock_rbac_policy_create.return_value = rbac_policy
|
||||
|
||||
form_data = {'target_tenant': rbac_policy.target_tenant,
|
||||
'action': 'access_as_external',
|
||||
'object_type': 'network',
|
||||
'network_id': network.id}
|
||||
url = reverse('horizon:admin:rbac_policies:create')
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
self.mock_tenant_list.assert_called_once_with(test.IsHttpRequest())
|
||||
self.mock_network_list.assert_called_once_with(test.IsHttpRequest())
|
||||
self.mock_is_extension_supported.assert_called_once_with(
|
||||
test.IsHttpRequest(), extension_alias='qos')
|
||||
params = {'target_tenant': rbac_policy.target_tenant,
|
||||
'action': 'access_as_external',
|
||||
'object_type': 'network',
|
||||
'object_id': network.id}
|
||||
self.mock_rbac_policy_create.assert_called_once_with(
|
||||
test.IsHttpRequest(), **params)
|
||||
|
||||
@test.create_mocks({api.neutron: ('network_list',
|
||||
'policy_list',
|
||||
'rbac_policy_create',
|
||||
'is_extension_supported',),
|
||||
api.keystone: ('tenant_list',)})
|
||||
def test_rbac_create_post_with_qos_policy_type(self):
|
||||
qos_policy = self.qos_policies.first()
|
||||
tenants = self.tenants.list()
|
||||
rbac_policy = self.rbac_policies.filter(object_type="qos_policy")[0]
|
||||
|
||||
self.mock_tenant_list.return_value = [tenants, False]
|
||||
self.mock_network_list.return_value = self.networks.list()
|
||||
self.mock_policy_list.return_value = self.qos_policies.list()
|
||||
self.mock_is_extension_supported.return_value = True
|
||||
self.mock_rbac_policy_create.return_value = rbac_policy
|
||||
|
||||
form_data = {'target_tenant': rbac_policy.target_tenant,
|
||||
'action': 'access_as_shared',
|
||||
'object_type': 'qos_policy',
|
||||
'qos_policy_id': qos_policy.id}
|
||||
url = reverse('horizon:admin:rbac_policies:create')
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
self.mock_tenant_list.assert_called_once_with(test.IsHttpRequest())
|
||||
self.mock_network_list.assert_called_once_with(test.IsHttpRequest())
|
||||
self.mock_policy_list.assert_called_once_with(test.IsHttpRequest())
|
||||
self.mock_is_extension_supported.assert_called_once_with(
|
||||
test.IsHttpRequest(), extension_alias='qos')
|
||||
params = {'target_tenant': rbac_policy.target_tenant,
|
||||
'action': 'access_as_shared',
|
||||
'object_type': 'qos_policy',
|
||||
'object_id': qos_policy.id}
|
||||
self.mock_rbac_policy_create.assert_called_once_with(
|
||||
test.IsHttpRequest(), **params)
|
30
openstack_dashboard/dashboards/admin/rbac_policies/urls.py
Normal file
30
openstack_dashboard/dashboards/admin/rbac_policies/urls.py
Normal file
@ -0,0 +1,30 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from openstack_dashboard.dashboards.admin.rbac_policies import views
|
||||
|
||||
|
||||
RBAC_POLICY_URL = r'^(?P<rbac_policy_id>[^/]+)/%s$'
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
url(r'^create/$', views.CreateView.as_view(), name='create'),
|
||||
url(RBAC_POLICY_URL % '$',
|
||||
views.DetailView.as_view(),
|
||||
name='detail'),
|
||||
url(RBAC_POLICY_URL % 'update',
|
||||
views.UpdateView.as_view(),
|
||||
name='update'),
|
||||
]
|
144
openstack_dashboard/dashboards/admin/rbac_policies/views.py
Normal file
144
openstack_dashboard/dashboards/admin/rbac_policies/views.py
Normal file
@ -0,0 +1,144 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.urls import reverse
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
from horizon import tables
|
||||
from horizon import tabs
|
||||
from horizon.utils import memoized
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.admin.rbac_policies \
|
||||
import forms as rbac_policy_forms
|
||||
from openstack_dashboard.dashboards.admin.rbac_policies \
|
||||
import tables as rbac_policy_tables
|
||||
from openstack_dashboard.dashboards.admin.rbac_policies \
|
||||
import tabs as rbac_policy_tabs
|
||||
|
||||
|
||||
class IndexView(tables.DataTableView):
|
||||
table_class = rbac_policy_tables.RBACPoliciesTable
|
||||
page_title = _("RBAC Policies")
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_tenants(self):
|
||||
try:
|
||||
tenants, has_more = api.keystone.tenant_list(self.request)
|
||||
except Exception:
|
||||
tenants = []
|
||||
msg = _("Unable to retrieve information about the "
|
||||
"policies' projects.")
|
||||
exceptions.handle(self.request, msg)
|
||||
|
||||
tenant_dict = OrderedDict([(t.id, t.name) for t in tenants])
|
||||
return tenant_dict
|
||||
|
||||
def _get_networks(self):
|
||||
try:
|
||||
networks = api.neutron.network_list(self.request)
|
||||
except Exception:
|
||||
networks = []
|
||||
msg = _("Unable to retrieve information about the "
|
||||
"policies' networks.")
|
||||
exceptions.handle(self.request, msg)
|
||||
return dict([(n.id, n.name) for n in networks])
|
||||
|
||||
def _get_qos_policies(self):
|
||||
qos_policies = []
|
||||
try:
|
||||
if api.neutron.is_extension_supported(self.request,
|
||||
extension_alias='qos'):
|
||||
qos_policies = api.neutron.policy_list(self.request)
|
||||
except Exception:
|
||||
msg = _("Unable to retrieve information about the "
|
||||
"policies' qos policies.")
|
||||
exceptions.handle(self.request, msg)
|
||||
return dict([(q.id, q.name) for q in qos_policies])
|
||||
|
||||
def get_data(self):
|
||||
try:
|
||||
rbac_policies = api.neutron.rbac_policy_list(self.request)
|
||||
except Exception:
|
||||
rbac_policies = []
|
||||
messages.error(self.request,
|
||||
_("Unable to retrieve RBAC policies."))
|
||||
if rbac_policies:
|
||||
tenant_dict = self._get_tenants()
|
||||
network_dict = self._get_networks()
|
||||
qos_policy_dict = self._get_qos_policies()
|
||||
for p in rbac_policies:
|
||||
# Set tenant name and object name
|
||||
p.tenant_name = tenant_dict.get(p.tenant_id, p.tenant_id)
|
||||
p.target_tenant_name = tenant_dict.get(p.target_tenant,
|
||||
p.target_tenant)
|
||||
if p.object_type == "network":
|
||||
p.object_name = network_dict.get(p.object_id, p.object_id)
|
||||
elif p.object_type == "qos_policy":
|
||||
p.object_name = qos_policy_dict.get(p.object_id,
|
||||
p.object_id)
|
||||
return rbac_policies
|
||||
|
||||
|
||||
class CreateView(forms.ModalFormView):
|
||||
template_name = 'admin/rbac_policies/create.html'
|
||||
form_id = "create_rbac_policy_form"
|
||||
form_class = rbac_policy_forms.CreatePolicyForm
|
||||
submit_label = _("Create RBAC Policy")
|
||||
submit_url = reverse_lazy("horizon:admin:rbac_policies:create")
|
||||
success_url = reverse_lazy("horizon:admin:rbac_policies:index")
|
||||
page_title = _("Create A RBAC Policy")
|
||||
|
||||
|
||||
class UpdateView(forms.ModalFormView):
|
||||
context_object_name = 'rbac_policies'
|
||||
template_name = 'admin/rbac_policies/update.html'
|
||||
form_class = rbac_policy_forms.UpdatePolicyForm
|
||||
form_id = "update_rbac_policy_form"
|
||||
submit_label = _("Save Changes")
|
||||
submit_url = 'horizon:admin:rbac_policies:update'
|
||||
success_url = reverse_lazy('horizon:admin:rbac_policies:index')
|
||||
page_title = _("Update RBAC Policy")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UpdateView, self).get_context_data(**kwargs)
|
||||
args = (self.kwargs['rbac_policy_id'],)
|
||||
context["rbac_policy_id"] = self.kwargs['rbac_policy_id']
|
||||
context["submit_url"] = reverse(self.submit_url, args=args)
|
||||
return context
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_object(self, *args, **kwargs):
|
||||
rbac_policy_id = self.kwargs['rbac_policy_id']
|
||||
try:
|
||||
return api.neutron.rbac_policy_get(self.request, rbac_policy_id)
|
||||
except Exception:
|
||||
redirect = self.success_url
|
||||
msg = _('Unable to retrieve rbac policy details.')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
def get_initial(self):
|
||||
rbac_policy = self._get_object()
|
||||
return {'rbac_policy_id': rbac_policy['id'],
|
||||
'target_tenant': rbac_policy['target_tenant']}
|
||||
|
||||
|
||||
class DetailView(tabs.TabView):
|
||||
tab_group_class = rbac_policy_tabs.RBACDetailsTabs
|
||||
template_name = 'horizon/common/_detail.html'
|
||||
page_title = "{{ rbac_policy.id }}"
|
@ -0,0 +1,10 @@
|
||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||
PANEL = 'rbac_policies'
|
||||
# The slug of the dashboard the PANEL associated with. Required.
|
||||
PANEL_DASHBOARD = 'admin'
|
||||
# The slug of the panel group the PANEL is associated with.
|
||||
PANEL_GROUP = 'network'
|
||||
|
||||
# Python panel class of the PANEL to be added.
|
||||
ADD_PANEL = ('openstack_dashboard.dashboards.admin.'
|
||||
'rbac_policies.panel.RBACPolicies')
|
@ -295,6 +295,11 @@ TEST_GLOBAL_MOCKS_ON_PANELS = {
|
||||
'.network_qos.panel.NetworkQoS.can_access'),
|
||||
'return_value': True,
|
||||
},
|
||||
'rbac_policies': {
|
||||
'method': ('openstack_dashboard.dashboards.admin'
|
||||
'.rbac_policies.panel.RBACPolicies.can_access'),
|
||||
'return_value': True,
|
||||
},
|
||||
'server_groups': {
|
||||
'method': ('openstack_dashboard.dashboards.project'
|
||||
'.server_groups.panel.ServerGroups.can_access'),
|
||||
|
@ -45,6 +45,7 @@ def data(TEST):
|
||||
TEST.neutron_quota_usages = utils.TestDataContainer()
|
||||
TEST.ip_availability = utils.TestDataContainer()
|
||||
TEST.qos_policies = utils.TestDataContainer()
|
||||
TEST.rbac_policies = utils.TestDataContainer()
|
||||
TEST.tp_ports = utils.TestDataContainer()
|
||||
TEST.neutron_availability_zones = utils.TestDataContainer()
|
||||
|
||||
@ -66,6 +67,7 @@ def data(TEST):
|
||||
TEST.api_monitors = utils.TestDataContainer()
|
||||
TEST.api_extensions = utils.TestDataContainer()
|
||||
TEST.api_ip_availability = utils.TestDataContainer()
|
||||
TEST.api_rbac_policies = utils.TestDataContainer()
|
||||
TEST.api_qos_policies = utils.TestDataContainer()
|
||||
TEST.api_tp_trunks = utils.TestDataContainer()
|
||||
TEST.api_tp_ports = utils.TestDataContainer()
|
||||
@ -773,6 +775,26 @@ def data(TEST):
|
||||
TEST.api_qos_policies.add(policy_dict1)
|
||||
TEST.qos_policies.add(neutron.QoSPolicy(policy_dict1))
|
||||
|
||||
# rbac policies
|
||||
rbac_policy_dict = {"project_id": "1",
|
||||
"object_type": "network",
|
||||
"id": "7f27e61a-9863-448a-a769-eb922fdef3f8",
|
||||
"object_id": "82288d84-e0a5-42ac-95be-e6af08727e42",
|
||||
"target_tenant": "2",
|
||||
"action": "access_as_external",
|
||||
"tenant_id": "1"}
|
||||
TEST.api_rbac_policies.add(rbac_policy_dict)
|
||||
TEST.rbac_policies.add(neutron.RBACPolicy(rbac_policy_dict))
|
||||
rbac_policy_dict1 = {"project_id": "1",
|
||||
"object_type": "qos_policy",
|
||||
"id": "7f27e61a-9863-448a-a769-eb922fdef3f8",
|
||||
"object_id": "a21dcd22-7189-cccc-aa32-22adafaf16a7",
|
||||
"target_tenant": "2",
|
||||
"action": "access_as_shared",
|
||||
"tenant_id": "1"}
|
||||
TEST.api_rbac_policies.add(rbac_policy_dict1)
|
||||
TEST.rbac_policies.add(neutron.RBACPolicy(rbac_policy_dict1))
|
||||
|
||||
# TRUNKPORT
|
||||
#
|
||||
# The test setup was created by the following command sequence:
|
||||
|
@ -0,0 +1,11 @@
|
||||
---
|
||||
features:
|
||||
- >
|
||||
[`blueprint neutron-rbac-policies <https://blueprints.launchpad.net/horizon/+spec/rbac-policies>`_]
|
||||
This blueprint adds RBAC policies panel to the Admin Network group.
|
||||
This panel will be enabled by default when the RBAC extension is
|
||||
enabled. Remove this panel by setting "'enable_rbac_policy': False"
|
||||
in 'local_settings.py'. RBAC policy supports the control of two
|
||||
resources: networks and qos policies, because qos policies is
|
||||
an extension function of neutron, need to enable this extension
|
||||
if wants to use it.
|
Loading…
x
Reference in New Issue
Block a user