Adds configuration support to associate firewall to routers

- Allows router selection during firewall create/update
- Exposes router association information in the firewall details page
- Extends relevant tests

Co-Authored-By: Abishek Subramanian <absubram@cisco.com>
Co-Authored-By: Lin Hua Cheng <os.lcheng@gmail.com>
Co-Authored-By: Akihiro Motoki <amotoki@gmail.com>
Change-Id: I602b7273c1a2caf9d80347689d09cbbe166d2048
Implements: blueprint fwaas-router-insertion
This commit is contained in:
Vishwanath Jayaraman 2015-03-20 02:49:07 -05:00
parent 7e40ab720b
commit fb9bcd31da
19 changed files with 801 additions and 17 deletions

View File

@ -2,6 +2,8 @@ horizon.firewalls = {
user_decided_length: false,
rules_selected: [],
rules_available: [],
routers_selected: [],
routers_available: [],
getConsoleLog: function(via_user_submit) {
var form_element = $("#tail_length"),
@ -140,9 +142,117 @@ horizon.firewalls = {
}).disableSelection();
},
/*
* Gets the html select element associated with a given
* router id for router_id.
**/
get_router_element: function(router_id) {
return $('li > label[for^="id_router_' + router_id + '"]');
},
/*
* Initializes an associative array of lists of the current
* routers.
**/
init_router_list: function() {
horizon.firewalls.routers_selected = [];
horizon.firewalls.routers_available = [];
$(this.get_router_element("")).each(function(){
var $this = $(this);
var $input = $this.children("input");
var router_property = {
name:$this.text().replace(/^\s+/,""),
id:$input.attr("id"),
value:$input.attr("value")
};
if($input.is(':checked')) {
horizon.firewalls.routers_selected.push(router_property);
} else {
horizon.firewalls.routers_available.push(router_property);
}
});
},
/*
* Generates the HTML structure for a router that will be displayed
* as a list item in the router list.
**/
generate_router_element: function(name, id, value) {
var $li = $('<li>');
$li.attr('name', value).html(name + '<em class="router_id">(' + value + ')</em><a href="#" class="btn btn-primary"></a>');
return $li;
},
/*
* Generates the HTML structure for the router List.
**/
generate_routerlist_html: function() {
var self = this;
var updateForm = function() {
var lists = $("#routerListId li").attr('data-index',100);
var active_routers = $("#selected_router > li").map(function(){
return $(this).attr("name");
});
$("#routerListId input:checkbox").removeAttr('checked');
active_routers.each(function(index, value){
$("#routerListId input:checkbox[value=" + value + "]")
.prop('checked', true)
.parents("li").attr('data-index',index);
});
$("#routerListId ul").html(
lists.sort(function(a,b){
if( $(a).data("index") < $(b).data("index")) { return -1; }
if( $(a).data("index") > $(b).data("index")) { return 1; }
return 0;
})
);
};
$("#routerListSortContainer").show();
$("#routerListIdContainer").hide();
self.init_router_list();
// Make sure we don't duplicate the routers in the list
$("#available_router").empty();
$.each(self.routers_available, function(index, value){
$("#available_router").append(self.generate_router_element(value.name, value.id, value.value));
});
// Make sure we don't duplicate the routers in the list
$("#selected_router").empty();
$.each(self.routers_selected, function(index, value){
$("#selected_router").append(self.generate_router_element(value.name, value.id, value.value));
});
$(".routerlist > li > a.btn").click(function(e){
var $this = $(this);
e.preventDefault();
e.stopPropagation();
if($this.parents("ul#available_router").length > 0) {
$this.parent().appendTo($("#selected_router"));
} else if ($this.parents("ul#selected_router").length > 0) {
$this.parent().appendTo($("#available_router"));
}
updateForm();
});
if ($("#routerListId > div.form-group.error").length > 0) {
var errortext = $("#routerListId > div.form-group.error").find("span.help-block").text();
$("#selected_router_h4").before($('<div class="dynamic-error">').html(errortext));
}
$(".routerlist").sortable({
connectWith: "ul.routerlist",
placeholder: "ui-state-highlight",
distance: 5,
start:function(e,info){
$("#selected_router").addClass("dragging");
},
stop:function(e,info){
$("#selected_router").removeClass("dragging");
updateForm();
}
}).disableSelection();
},
workflow_init: function(modal) {
// Initialise the drag and drop rule list
horizon.firewalls.generate_rulelist_html();
horizon.firewalls.generate_routerlist_html();
}
};

View File

@ -16,6 +16,8 @@ from __future__ import absolute_import
from django.utils.datastructures import SortedDict
from horizon.utils import memoized
from openstack_dashboard.api import neutron
neutronclient = neutron.neutronclient
@ -42,6 +44,11 @@ class Policy(neutron.NeutronAPIDictWrapper):
class Firewall(neutron.NeutronAPIDictWrapper):
"""Wrapper for neutron firewall."""
def __init__(self, apiresource):
apiresource['admin_state'] = \
'UP' if apiresource['admin_state_up'] else 'DOWN'
super(Firewall, self).__init__(apiresource)
def get_dict(self):
firewall_dict = self._apidict
firewall_dict['firewall_id'] = firewall_dict['id']
@ -283,3 +290,18 @@ def firewall_update(request, firewall_id, **kwargs):
firewall = neutronclient(request).update_firewall(
firewall_id, body).get('firewall')
return Firewall(firewall)
@memoized.memoized
def firewall_unassociated_routers_list(request, tenant_id):
all_routers = neutron.router_list(request, tenant_id=tenant_id)
tenant_firewalls = firewall_list_for_tenant(request, tenant_id=tenant_id)
firewall_router_ids = [rid
for fw in tenant_firewalls
for rid in getattr(fw, 'router_ids', [])]
available_routers = [r for r in all_routers
if r.id not in firewall_router_ids]
available_routers = sorted(available_routers,
key=lambda router: router.name_or_id)
return available_routers

View File

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import abc
import logging
from django.core.urlresolvers import reverse
@ -287,3 +288,94 @@ class RemoveRuleFromPolicy(forms.SelfHandlingForm):
LOG.error(msg)
redirect = reverse(self.failure_url)
exceptions.handle(request, msg, redirect=redirect)
class RouterInsertionFormBase(forms.SelfHandlingForm):
def __init__(self, request, *args, **kwargs):
super(RouterInsertionFormBase, self).__init__(request, *args, **kwargs)
try:
router_choices = self.get_router_choices(request, kwargs)
self.fields['router_ids'].choices = router_choices
except Exception as e:
msg = self.init_failure_msg % {'name': self.initial['name'],
'reason': e}
LOG.error(msg)
redirect = reverse(self.failure_url)
exceptions.handle(request, msg, redirect=redirect)
@abc.abstractmethod
def get_router_choices(self, request, kwargs):
"""Return a list of selectable routers."""
@abc.abstractmethod
def get_new_router_ids(self, context):
"""Return a new list of router IDs associated with the firewall."""
def handle(self, request, context):
firewall_id = self.initial['firewall_id']
firewall_name_or_id = self.initial['name'] or firewall_id
try:
body = {'router_ids': self.get_new_router_ids(context)}
firewall = api.fwaas.firewall_update(request, firewall_id, **body)
msg = self.success_msg % {'firewall': firewall_name_or_id}
LOG.debug(msg)
messages.success(request, msg)
return firewall
except Exception as e:
msg = self.failure_msg % {'name': firewall_name_or_id, 'reason': e}
LOG.error(msg)
redirect = reverse(self.failure_url)
exceptions.handle(request, msg, redirect=redirect)
class AddRouterToFirewall(RouterInsertionFormBase):
router_ids = forms.MultipleChoiceField(
label=_("Add Routers"),
required=False,
widget=forms.CheckboxSelectMultiple(),
help_text=_("Add selected router(s) to the firewall."))
failure_url = 'horizon:project:firewalls:index'
success_msg = _('Router(s) was/were successfully added to firewall '
'%(firewall)s.')
failure_msg = _('Failed to add router(s) to firewall %(name)s: %(reason)s')
init_failure_msg = _('Failed to retrieve available routers: %(reason)s')
def get_router_choices(self, request, kwargs):
tenant_id = self.request.user.tenant_id
routers_list = api.fwaas.firewall_unassociated_routers_list(
request, tenant_id)
return [(r.id, r.name_or_id) for r in routers_list]
def get_new_router_ids(self, context):
existing_router_ids = self.initial['router_ids']
add_router_ids = context['router_ids']
return add_router_ids + existing_router_ids
class RemoveRouterFromFirewall(RouterInsertionFormBase):
router_ids = forms.MultipleChoiceField(
label=_("Remove Routers"),
required=False,
widget=forms.CheckboxSelectMultiple(),
help_text=_("Unselect the router(s) to be removed from firewall."))
failure_url = 'horizon:project:firewalls:index'
success_msg = _('Router(s) was successfully removed from firewall '
'%(firewall)s.')
failure_msg = _('Failed to remove router(s) from firewall %(name)s: '
'%(reason)s')
init_failure_msg = _('Failed to retrieve current routers in firewall '
'%(name)s: %(reason)s')
def get_router_choices(self, request, kwargs):
tenant_id = self.request.user.tenant_id
all_routers = api.neutron.router_list(request, tenant_id=tenant_id)
current_routers = [r for r in all_routers
if r['id'] in kwargs['initial']['router_ids']]
return [(r.id, r.name_or_id) for r in current_routers]
def get_new_router_ids(self, context):
# context[router_ids] is router IDs to be kept.
return context['router_ids']

View File

@ -12,15 +12,21 @@
# License for the specific language governing permissions and limitations
# under the License.
import logging
from django.core.urlresolvers import reverse
from django.template import defaultfilters as filters
from django.utils.translation import pgettext_lazy
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import exceptions
from horizon import tables
from openstack_dashboard import api
from openstack_dashboard import policy
LOG = logging.getLogger(__name__)
class AddRuleLink(tables.LinkAction):
name = "addrule"
@ -187,11 +193,59 @@ class RemoveRuleFromPolicyLink(policy.PolicyTargetMixin,
return base_url
class AddRouterToFirewallLink(policy.PolicyTargetMixin,
tables.LinkAction):
name = "addrouter"
verbose_name = _("Add Router")
classes = ("ajax-modal", "btn-update",)
policy_rules = (("network", "get_firewall"),
("network", "add_router"),)
def get_link_url(self, firewall):
base_url = reverse("horizon:project:firewalls:addrouter",
kwargs={'firewall_id': firewall.id})
return base_url
def allowed(self, request, firewall):
if not api.neutron.is_extension_supported(request,
'fwaasrouterinsertion'):
return False
tenant_id = firewall['tenant_id']
available_routers = api.fwaas.firewall_unassociated_routers_list(
request, tenant_id)
return bool(available_routers)
class RemoveRouterFromFirewallLink(policy.PolicyTargetMixin,
tables.LinkAction):
name = "removerouter"
verbose_name = _("Remove Router")
classes = ("ajax-modal", "btn-update",)
policy_rules = (("network", "get_firewall"),
("network", "remove_router"),)
def get_link_url(self, firewall):
base_url = reverse("horizon:project:firewalls:removerouter",
kwargs={'firewall_id': firewall.id})
return base_url
def allowed(self, request, firewall):
if not api.neutron.is_extension_supported(request,
'fwaasrouterinsertion'):
return False
return bool(firewall['router_ids'])
def get_rules_name(datum):
return ', '.join([rule.name or rule.id[:13]
for rule in datum.rules])
def get_routers_name(firewall):
if firewall.routers:
return ', '.join(router['name'] for router in firewall.routers)
def get_policy_name(datum):
if datum.policy:
return datum.policy.name or datum.policy.id
@ -276,18 +330,43 @@ class FirewallsTable(tables.DataTable):
("Inactive", pgettext_lazy("Current status of a Firewall",
u"Inactive")),
)
ADMIN_STATE_DISPLAY_CHOICES = (
("UP", pgettext_lazy("Admin state of a Firewall", u"UP")),
("DOWN", pgettext_lazy("Admin state of a Firewall", u"DOWN")),
)
name = tables.Column("name_or_id",
verbose_name=_("Name"),
link="horizon:project:firewalls:firewalldetails")
firewall_policy_id = tables.Column(get_policy_name,
link=get_policy_link,
verbose_name=_("Policy"))
router_ids = tables.Column(get_routers_name,
verbose_name=_("Associated Routers"))
status = tables.Column("status",
verbose_name=_("Status"),
display_choices=STATUS_DISPLAY_CHOICES)
admin_state = tables.Column("admin_state",
verbose_name=_("Admin State"),
display_choices=ADMIN_STATE_DISPLAY_CHOICES)
class Meta(object):
name = "firewallstable"
verbose_name = _("Firewalls")
table_actions = (AddFirewallLink, DeleteFirewallLink)
row_actions = (UpdateFirewallLink, DeleteFirewallLink)
row_actions = (UpdateFirewallLink, DeleteFirewallLink,
AddRouterToFirewallLink, RemoveRouterFromFirewallLink)
def __init__(self, request, data=None, needs_form_wrapper=None, **kwargs):
super(FirewallsTable, self).__init__(
request, data=data,
needs_form_wrapper=needs_form_wrapper, **kwargs)
try:
if not api.neutron.is_extension_supported(request,
'fwaasrouterinsertion'):
del self.columns['router_ids']
except Exception as e:
msg = _('Failed to verify extension support %(reason)s') % {
'reason': e}
LOG.error(msg)
exceptions.handle(request, msg)

View File

@ -75,6 +75,16 @@ class FirewallsTab(tabs.TableTab):
tenant_id = self.request.user.tenant_id
request = self.tab_group.request
firewalls = api.fwaas.firewall_list_for_tenant(request, tenant_id)
if api.neutron.is_extension_supported(request,
'fwaasrouterinsertion'):
routers = api.neutron.router_list(request, tenant_id=tenant_id)
for fw in firewalls:
router_list = [r for r in routers
if r['id'] in fw['router_ids']]
fw.get_dict()['routers'] = router_list
except Exception:
firewalls = []
exceptions.handle(self.tab_group.request,
@ -127,11 +137,21 @@ class FirewallDetailsTab(tabs.Tab):
fid = self.tab_group.kwargs['firewall_id']
try:
firewall = api.fwaas.firewall_get(request, fid)
body = {'firewall': firewall}
if api.neutron.is_extension_supported(request,
'fwaasrouterinsertion'):
tenant_id = self.request.user.tenant_id
tenant_routers = api.neutron.router_list(request,
tenant_id=tenant_id)
router_ids = firewall.get_dict()['router_ids']
routers = [r for r in tenant_routers
if r['id'] in router_ids]
body['routers'] = routers
except Exception:
exceptions.handle(request,
_('Unable to retrieve firewall details.'),
redirect=self.failure_url)
return {'firewall': firewall}
return body
class FirewallTabs(tabs.TabGroup):

View File

@ -0,0 +1,8 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
<h3>{% trans "Description:" %}</h3>
<p>{% trans "Choose the router(s) you want to add." %}</p>
{% endblock %}

View File

@ -27,5 +27,17 @@
<dt>{% trans "Admin State Up" %}</dt>
<dd>{{ firewall.admin_state_up|yesno|capfirst }}</dd>
<dt>{% trans "Routers" %}</dt>
<dd>
{% if routers %}
{% for router in routers %}
{% url 'horizon:project:routers:detail' router.id as router_url %}
<a href="{{ router_url }}">{{ router.name|default:router.id}}</a><br>
{% endfor %}
{% else %}
{% trans "-" %}
{% endif %}
</dd>
</dl>
</div>

View File

@ -0,0 +1,7 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
<h3>{% trans "Description:" %}</h3>
<p>{% trans "Unselect the routers you want to disassociate from the firewall." %}</p>
{% endblock %}

View File

@ -0,0 +1,3 @@
{% load i18n %}
<p>{% blocktrans %}Choose router(s) from Available Routers to Selected Routers by push button or drag and drop. {% endblocktrans %}</p>

View File

@ -0,0 +1,45 @@
{% load i18n %}
<noscript><h3>{{ step }}</h3></noscript>
<table class="table-fixed" id="routerListSortContainer">
<tbody>
<tr>
<td class="actions">
<h4 id="selected_router_h4">{% trans "Selected Routers" %}</h4>
<ul id="selected_router" class="routerlist">
</ul>
<h4>{% trans "Available Routers" %}</h4>
<ul id="available_router" class="routerlist">
</ul>
</td>
<td class="help_text">
{% include "project/firewalls/_update_router_help.html" %}
</td>
</tr>
</tbody>
</table>
<table class="table-fixed" id="routerListIdContainer">
<tbody>
<tr>
<td class="actions">
<div id="routerListId">
{% include "horizon/common/_form_fields.html" %}
</div>
</td>
<td class="help_text">
{{ step.get_help_text }}
</td>
</tr>
</tbody>
</table>
<script>
if (typeof $ !== 'undefined') {
horizon.firewalls.workflow_init($(".workflow"));
} else {
addHorizonLoadEvent(function() {
horizon.firewalls.workflow_init($(".workflow"));
});
}
</script>

View File

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Add Router to Firewall" %}{% endblock %}
{% block main %}
{% include 'project/firewalls/_add_router_to_firewall.html' %}
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Remove Router from Firewall" %}{% endblock %}
{% block main %}
{% include 'project/firewalls/_remove_router_from_firewall.html' %}
{% endblock %}

View File

@ -49,10 +49,21 @@ class FirewallTests(test.TestCase):
INSERTRULE_PATH = 'horizon:%s:firewalls:insertrule' % DASHBOARD
REMOVERULE_PATH = 'horizon:%s:firewalls:removerule' % DASHBOARD
def set_up_expect(self):
ADDROUTER_PATH = 'horizon:%s:firewalls:addrouter' % DASHBOARD
REMOVEROUTER_PATH = 'horizon:%s:firewalls:removerouter' % DASHBOARD
def set_up_expect(self, fwaas_router_extension=True):
# retrieve rules
tenant_id = self.tenant.id
api.neutron.is_extension_supported(
IsA(http.HttpRequest), 'fwaasrouterinsertion'
).AndReturn(fwaas_router_extension)
api.neutron.is_extension_supported(
IsA(http.HttpRequest), 'fwaasrouterinsertion'
).MultipleTimes().AndReturn(fwaas_router_extension)
api.fwaas.rule_list_for_tenant(
IsA(http.HttpRequest),
tenant_id).AndReturn(self.fw_rules.list())
@ -67,9 +78,24 @@ class FirewallTests(test.TestCase):
api.fwaas.firewall_list_for_tenant(
IsA(http.HttpRequest), tenant_id).AndReturn(firewalls)
routers = self.routers.list()
api.neutron.router_list(
IsA(http.HttpRequest), tenant_id=tenant_id).AndReturn(routers)
api.neutron.router_list(
IsA(http.HttpRequest), tenant_id=tenant_id). \
MultipleTimes().AndReturn(routers)
api.fwaas.firewall_list_for_tenant(
IsA(http.HttpRequest), tenant_id='1'). \
MultipleTimes().AndReturn(firewalls)
def set_up_expect_with_exception(self):
tenant_id = self.tenant.id
api.neutron.is_extension_supported(
IsA(http.HttpRequest), 'fwaasrouterinsertion').AndReturn(True)
api.fwaas.rule_list_for_tenant(
IsA(http.HttpRequest),
tenant_id).AndRaise(self.exceptions.neutron)
@ -82,7 +108,9 @@ class FirewallTests(test.TestCase):
@test.create_stubs({api.fwaas: ('firewall_list_for_tenant',
'policy_list_for_tenant',
'rule_list_for_tenant')}, )
'rule_list_for_tenant'),
api.neutron: ('is_extension_supported',
'router_list',), })
def test_index_firewalls(self):
self.set_up_expect()
@ -98,9 +126,14 @@ class FirewallTests(test.TestCase):
self.assertEqual(len(res.context['table'].data),
len(self.firewalls.list()))
# TODO(absubram): Change test_index_firewalls for with and without
# router extensions.
@test.create_stubs({api.fwaas: ('firewall_list_for_tenant',
'policy_list_for_tenant',
'rule_list_for_tenant')}, )
'rule_list_for_tenant'),
api.neutron: ('is_extension_supported',
'router_list',), })
def test_index_policies(self):
self.set_up_expect()
@ -119,7 +152,9 @@ class FirewallTests(test.TestCase):
@test.create_stubs({api.fwaas: ('firewall_list_for_tenant',
'policy_list_for_tenant',
'rule_list_for_tenant')}, )
'rule_list_for_tenant'),
api.neutron: ('is_extension_supported',
'router_list',), })
def test_index_rules(self):
self.set_up_expect()
@ -138,7 +173,8 @@ class FirewallTests(test.TestCase):
@test.create_stubs({api.fwaas: ('firewall_list_for_tenant',
'policy_list_for_tenant',
'rule_list_for_tenant')}, )
'rule_list_for_tenant'),
api.neutron: ('is_extension_supported',), })
def test_index_exception_firewalls(self):
self.set_up_expect_with_exception()
@ -157,7 +193,8 @@ class FirewallTests(test.TestCase):
@test.create_stubs({api.fwaas: ('firewall_list_for_tenant',
'policy_list_for_tenant',
'rule_list_for_tenant')}, )
'rule_list_for_tenant'),
api.neutron: ('is_extension_supported',), })
def test_index_exception_policies(self):
self.set_up_expect_with_exception()
@ -177,7 +214,8 @@ class FirewallTests(test.TestCase):
@test.create_stubs({api.fwaas: ('firewall_list_for_tenant',
'policy_list_for_tenant',
'rule_list_for_tenant')}, )
'rule_list_for_tenant'),
api.neutron: ('is_extension_supported',), })
def test_index_exception_rules(self):
self.set_up_expect_with_exception()
@ -301,18 +339,31 @@ class FirewallTests(test.TestCase):
self.assertFormErrors(res, 1)
@test.create_stubs({api.fwaas: ('firewall_create',
'policy_list_for_tenant'), })
def test_add_firewall_post(self):
def _test_add_firewall_post(self, router_extension=False):
firewall = self.firewalls.first()
policies = self.fw_policies.list()
tenant_id = self.tenant.id
if router_extension:
routers = self.routers.list()
firewalls = self.firewalls.list()
form_data = {'name': firewall.name,
'description': firewall.description,
'firewall_policy_id': firewall.firewall_policy_id,
'shared': firewall.shared,
'admin_state_up': firewall.admin_state_up
}
if router_extension:
form_data['router_ids'] = firewall.router_ids
api.neutron.router_list(
IsA(http.HttpRequest), tenant_id=tenant_id).AndReturn(routers)
api.fwaas.firewall_list_for_tenant(
IsA(http.HttpRequest),
tenant_id=tenant_id).AndReturn(firewalls)
api.neutron.is_extension_supported(
IsA(http.HttpRequest),
'fwaasrouterinsertion').AndReturn(router_extension)
api.fwaas.policy_list_for_tenant(
IsA(http.HttpRequest), tenant_id).AndReturn(policies)
api.fwaas.firewall_create(
@ -326,7 +377,25 @@ class FirewallTests(test.TestCase):
self.assertRedirectsNoFollow(res, str(self.INDEX_URL))
@test.create_stubs({api.fwaas: ('firewall_create',
'policy_list_for_tenant'), })
'policy_list_for_tenant',),
api.neutron: ('is_extension_supported',), })
def test_add_firewall_post(self):
self._test_add_firewall_post()
# @test.create_stubs({api.fwaas: ('firewall_create',
# 'policy_list_for_tenant',
# 'firewall_list_for_tenant',),
# api.neutron: ('is_extension_supported',
# 'router_list'), })
# def test_add_firewall_post_with_router_extension(self):
# self._test_add_firewall_post(router_extension=True)
# TODO(absubram): Fix test_add_firewall_post_with_router_extension
# It currently fails because views.py is not
# initializing the AddRouter workflow?
@test.create_stubs({api.fwaas: ('firewall_create',
'policy_list_for_tenant',),
api.neutron: ('is_extension_supported',), })
def test_add_firewall_post_with_error(self):
firewall = self.firewalls.first()
policies = self.fw_policies.list()
@ -337,6 +406,9 @@ class FirewallTests(test.TestCase):
'shared': firewall.shared,
'admin_state_up': firewall.admin_state_up
}
api.neutron.is_extension_supported(
IsA(http.HttpRequest),
'fwaasrouterinsertion').AndReturn(False)
api.fwaas.policy_list_for_tenant(
IsA(http.HttpRequest), tenant_id).AndReturn(policies)
@ -618,10 +690,75 @@ class FirewallTests(test.TestCase):
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, str(self.INDEX_URL))
@test.create_stubs({api.fwaas: ('firewall_get',
'firewall_list_for_tenant',
'firewall_update',
'firewall_unassociated_routers_list')})
def test_firewall_add_router(self):
tenant_id = self.tenant.id
firewall = self.firewalls.first()
routers = self.routers.list()
existing_router_ids = firewall.router_ids
add_router_ids = [routers[1].id]
form_data = {'router_ids': add_router_ids}
post_data = {'router_ids': add_router_ids + existing_router_ids}
api.fwaas.firewall_get(
IsA(http.HttpRequest), firewall.id).AndReturn(firewall)
api.fwaas.firewall_unassociated_routers_list(
IsA(http.HttpRequest), tenant_id).AndReturn(routers)
firewall.router_ids = [add_router_ids, existing_router_ids]
api.fwaas.firewall_update(
IsA(http.HttpRequest),
firewall.id, **post_data).AndReturn(firewall)
self.mox.ReplayAll()
res = self.client.post(
reverse(self.ADDROUTER_PATH, args=(firewall.id,)), form_data)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, str(self.INDEX_URL))
@test.create_stubs({api.fwaas: ('firewall_get',
'firewall_update',
'firewall_unassociated_routers_list'),
api.neutron: ('router_list',), })
def test_firewall_remove_router(self):
firewall = self.firewalls.first()
tenant_id = self.tenant.id
routers = self.routers.list()
existing_router_ids = firewall.router_ids
form_data = {'router_ids': existing_router_ids}
api.fwaas.firewall_get(
IsA(http.HttpRequest), firewall.id).AndReturn(firewall)
api.neutron.router_list(
IsA(http.HttpRequest), tenant_id=tenant_id).AndReturn(routers)
firewall.router_ids = []
api.fwaas.firewall_update(
IsA(http.HttpRequest),
firewall.id, **form_data).AndReturn(firewall)
self.mox.ReplayAll()
res = self.client.post(
reverse(self.REMOVEROUTER_PATH, args=(firewall.id,)), form_data)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, str(self.INDEX_URL))
@test.create_stubs({api.fwaas: ('firewall_list_for_tenant',
'policy_list_for_tenant',
'rule_list_for_tenant',
'rule_delete')})
'rule_delete'),
api.neutron: ('is_extension_supported',
'router_list',), })
def test_delete_rule(self):
self.set_up_expect()
rule = self.fw_rules.first()
@ -636,7 +773,9 @@ class FirewallTests(test.TestCase):
@test.create_stubs({api.fwaas: ('firewall_list_for_tenant',
'policy_list_for_tenant',
'rule_list_for_tenant',
'policy_delete')})
'policy_delete'),
api.neutron: ('is_extension_supported',
'router_list',), })
def test_delete_policy(self):
self.set_up_expect()
policy = self.fw_policies.first()
@ -651,7 +790,9 @@ class FirewallTests(test.TestCase):
@test.create_stubs({api.fwaas: ('firewall_list_for_tenant',
'policy_list_for_tenant',
'rule_list_for_tenant',
'firewall_delete')})
'firewall_delete'),
api.neutron: ('is_extension_supported',
'router_list',), })
def test_delete_firewall(self):
self.set_up_expect()
fwl = self.firewalls.first()

View File

@ -39,5 +39,9 @@ urlpatterns = patterns(
views.RuleDetailsView.as_view(), name='ruledetails'),
url(r'^policy/(?P<policy_id>[^/]+)/$',
views.PolicyDetailsView.as_view(), name='policydetails'),
url(r'^addrouter/(?P<firewall_id>[^/]+)/$',
views.AddRouterToFirewallView.as_view(), name='addrouter'),
url(r'^removerouter/(?P<firewall_id>[^/]+)/$',
views.RemoveRouterFromFirewallView.as_view(), name='removerouter'),
url(r'^firewall/(?P<firewall_id>[^/]+)/$',
views.FirewallDetailsView.as_view(), name='firewalldetails'))

View File

@ -33,7 +33,9 @@ from openstack_dashboard.dashboards.project.firewalls \
from openstack_dashboard.dashboards.project.firewalls \
import workflows as fw_workflows
AddRouterToFirewall = fw_forms.AddRouterToFirewall
InsertRuleToPolicy = fw_forms.InsertRuleToPolicy
RemoveRouterFromFirewall = fw_forms.RemoveRouterFromFirewall
RemoveRuleFromPolicy = fw_forms.RemoveRuleFromPolicy
UpdateFirewall = fw_forms.UpdateFirewall
UpdatePolicy = fw_forms.UpdatePolicy
@ -104,6 +106,13 @@ class AddFirewallView(workflows.WorkflowView):
template_name = "project/firewalls/addfirewall.html"
page_title = _("Add New Firewall")
def get_workflow(self):
if api.neutron.is_extension_supported(self.request,
'fwaasrouterinsertion'):
AddFirewall.register(fw_workflows.SelectRoutersStep)
workflow = super(AddFirewallView, self).get_workflow()
return workflow
class FireWallDetailTabs(tabs.TabView):
template_name = 'project/firewalls/details_tabs.html'
@ -318,3 +327,53 @@ class RemoveRuleFromPolicyView(forms.ModalFormView):
initial = policy.get_dict()
initial['policy_id'] = initial['id']
return initial
class RouterCommonView(forms.ModalFormView):
form_id = "update_firewall_form"
context_object_name = 'firewall'
submit_label = _("Save Changes")
success_url = reverse_lazy("horizon:project:firewalls:index")
def get_context_data(self, **kwargs):
context = super(RouterCommonView,
self).get_context_data(**kwargs)
context["firewall_id"] = self.kwargs['firewall_id']
args = (self.kwargs['firewall_id'],)
context['submit_url'] = reverse(self.submit_url, args=args)
obj = self._get_object()
if obj:
context['name'] = obj.name_or_id
return context
@memoized.memoized_method
def _get_object(self, *args, **kwargs):
firewall_id = self.kwargs['firewall_id']
try:
firewall = api.fwaas.firewall_get(self.request, firewall_id)
return firewall
except Exception:
redirect = self.success_url
msg = _('Unable to retrieve firewall details.')
exceptions.handle(self.request, msg, redirect=redirect)
def get_initial(self):
firewall = self._get_object()
initial = firewall.get_dict()
return initial
class AddRouterToFirewallView(RouterCommonView):
form_class = AddRouterToFirewall
modal_header = _("Add Router to Firewall")
template_name = "project/firewalls/add_router_to_firewall.html"
submit_url = "horizon:project:firewalls:addrouter"
page_title = _("Add Router to Firewall")
class RemoveRouterFromFirewallView(RouterCommonView):
form_class = RemoveRouterFromFirewall
modal_header = _("Remove Router from Firewall")
template_name = "project/firewalls/remove_router_from_firewall.html"
submit_url = "horizon:project:firewalls:removerouter"
page_title = _("Remove Router from Firewall")

View File

@ -168,6 +168,51 @@ class SelectRulesStep(workflows.Step):
return context
class SelectRoutersAction(workflows.Action):
router = forms.MultipleChoiceField(
label=_("Routers"),
required=False,
widget=forms.CheckboxSelectMultiple(),
help_text=_("Create a firewall with selected routers."))
class Meta(object):
name = _("Routers")
permissions = ('openstack.services.network',)
help_text = _("Select routers for your firewall.")
def populate_router_choices(self, request, context):
try:
tenant_id = self.request.user.tenant_id
routers_list = api.fwaas.firewall_unassociated_routers_list(
request, tenant_id)
except Exception as e:
routers_list = []
exceptions.handle(request,
_('Unable to retrieve routers (%(error)s).') % {
'error': str(e)})
routers_list = [(router.id, router.name_or_id)
for router in routers_list]
return routers_list
class SelectRoutersStep(workflows.Step):
action_class = SelectRoutersAction
template_name = "project/firewalls/_update_routers.html"
contributes = ("router_ids", "all_routers_selected",
"Select No Routers")
def contribute(self, data, context):
if data:
routers = self.workflow.request.POST.getlist("router")
if routers:
routers = [r for r in routers if r != '']
context['router_ids'] = routers
else:
context['router_ids'] = []
return context
class AddPolicyAction(workflows.Action):
name = forms.CharField(max_length=80,
label=_("Name"))
@ -293,7 +338,7 @@ class AddFirewall(workflows.Workflow):
# involve more complex configuration over time. Hence,
# a workflow instead of a single form is used for
# firewall_rule add to be ready for future extension.
default_steps = (AddFirewallStep,)
default_steps = (AddFirewallStep, )
def format_status_message(self, message):
return message % self.context.get('name')

View File

@ -2,9 +2,15 @@
@import "/horizon/lib/bootstrap_datepicker/datepicker3.css";
@import "/horizon/lib/rickshaw.css";
// Custom Theme Variables
@import "/custom/variables";
// Horizon Variables
@import "variables";
// Horizon Mixins
@import "mixins";
// Vendor Components
@import "/bootstrap/scss/bootstrap";
@import "/horizon/lib/font-awesome/scss/font-awesome.scss";
@ -19,6 +25,7 @@
@import "components/network_topology";
@import "/angular/styles";
@import "/horizon/lib/magic_search/magic_search.scss";
/* new clearfix */
.clearfix:after {
@ -1768,6 +1775,116 @@ label.log-length {
}
/* Styling for draged firewall router object */
#routerListSortContainer {
display: none;
}
.routerlist {
padding: 6px;
background: #eee;
border: 1px solid $border-color;
min-height: 2em;
width: auto !important;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
li {
width: 226px;
list-style-type: none;
margin: 6px auto;
padding: 3px;
background: $body-bg;
border: 1px solid $border-color;
line-height: 18px;
border-radius: 3px;
cursor: move;
padding-left: 23px;
background: $body-bg url(../img/drag.png) no-repeat 11px 50%;
em {
font-size: 0.5em;
line-height: 1em;
color:#999;
font-style: normal;
margin-left: 0.8em;
}
i {
margin-right: 5px;
vertical-align: middle;
}
a.btn {
@include box-sizing(border-box);
font-size: 11px;
line-height: 12px;
padding: 2px 5px 3px;
margin-right: 1px;
width: 18px;
text-align: center;
//position: absolute;
right:5px;
vertical-align: middle;
float: right;
&:before {
content: "+";
}
}
}
li.ui-sortable-helper {
background-color: #def;
}
li.ui-state-highlight {
border: 1px dotted $border-color;
background: #efefef;
height: 0.5em;
}
li:after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
}
#selected_router {
margin-bottom: 1.5em;
counter-reset:v1 0;
background: #edf9ff;
border:1px solid $border-color;
li {
position: relative;
a.btn {
&:before {
content: "-";
}
}
}
li:before {
content:"router:"counter(v1);
counter-increment:v1;
display: inline-block;
margin-right: 5px;
background: $gray;
color:$body-bg;
font-size: 90%;
padding: 0px 4px;
vertical-align: middle;
border-radius: 2px;
position: absolute;
left: -2em;
}
&.dragging {
li:before {
content:"router:";
background-color:rgba(102,102,102,0.5);
padding-right: 10px;
}
li.ui-state-highlight:before {
content:"";
background:transparent;
}
}
}
/**** Sort Indicator ****/
.tablesorter thead tr th
{
@ -1836,3 +1953,4 @@ textarea.key_text {
right: 0px !important;
}
}

View File

@ -346,6 +346,9 @@ class FwaasApiTests(test.APITestCase):
self.assertEqual(exp_firewall.firewall_policy_id, ret_val.policy.id)
self.assertEqual(exp_firewall.policy.name, ret_val.policy.name)
# TODO(absubram) : Add API tests for firewall_create with routers,
# add router to firewal and remove router from fw.
@test.create_stubs({neutronclient: ('list_firewalls',
'list_firewall_policies')})
def test_firewall_list(self):

View File

@ -967,6 +967,7 @@ def data(TEST):
'firewall_policy_id':
'abcdef-c3eb-4fee-9763-12de3338041e',
'name': 'firewall1',
'router_ids': [TEST.routers.first().id],
'description': 'firewall description',
'status': 'PENDING_CREATE',
'shared': True,
@ -975,6 +976,7 @@ def data(TEST):
fw1 = fwaas.Firewall(copy.deepcopy(fw1_dict))
fw1._apidict['policy'] = policy1
fw1._apidict['routers'] = [TEST.routers.first()]
TEST.firewalls.add(fw1)
# 2nd firewall (no name)