diff --git a/gbpui/panels/policytargets/templates/policytargets/_update_groups.html b/gbpui/panels/policytargets/templates/policytargets/_update_groups.html index cb21648..23a7ec6 100644 --- a/gbpui/panels/policytargets/templates/policytargets/_update_groups.html +++ b/gbpui/panels/policytargets/templates/policytargets/_update_groups.html @@ -1,31 +1,70 @@ {% load i18n %} - + +
-
- +
+ + + + +
- -
    + +
-

{% blocktrans %}Choose group from Available groups to Selected - groups by push button or drag and drop, you may change group order - by drag and drop as well. {% endblocktrans %}

+
+
- +
@@ -36,13 +75,7 @@
-
+
{% include "horizon/common/_form_fields.html" %}
- - + + diff --git a/gbpui/panels/policytargets/urls.py b/gbpui/panels/policytargets/urls.py index 21cf3ac..cf25422 100644 --- a/gbpui/panels/policytargets/urls.py +++ b/gbpui/panels/policytargets/urls.py @@ -73,4 +73,7 @@ urlpatterns = patterns('', '(?P[^/]+)/$', views.ExtRemoveConsumedPRSView.as_view(), name='ext_remove_consumed_prs'), + url(r'/check_ip_availability', + views.check_ip_availability, + name='check_ip_availability'), ) diff --git a/gbpui/panels/policytargets/views.py b/gbpui/panels/policytargets/views.py index 8cd3093..92a241d 100644 --- a/gbpui/panels/policytargets/views.py +++ b/gbpui/panels/policytargets/views.py @@ -10,9 +10,11 @@ # License for the specific language governing permissions and limitations # under the License. +import json import re from django.core.urlresolvers import reverse_lazy +from django.http import HttpResponse # noqa from django.utils.translation import ugettext_lazy as _ from horizon import exceptions @@ -28,6 +30,11 @@ import forms as policy_target_forms import tabs as policy_target_tabs import workflows as policy_target_workflows +from openstack_dashboard import api + +from netaddr import IPAddress +from netaddr import IPNetwork + PTGTabs = policy_target_tabs.PTGTabs PTGDetailsTabs = policy_target_tabs.PTGDetailsTabs @@ -285,3 +292,28 @@ class RemoveConsumedPRSView(forms.ModalFormView): def get_initial(self): return self.kwargs + + +def check_ip_availability(request): + fixed_ip = request.GET.get('fixed_ip') + response = {'error': 'IP address is not within the allocated pool range'} + subnets = request.GET.get('subnets') + subnets = subnets.split(";") + for subnet in subnets: + subnet_details = subnet.split(",") + try: + if IPAddress(fixed_ip) in IPNetwork(subnet_details[0]): + if IPAddress(fixed_ip) >= IPAddress(subnet_details[1]) and \ + IPAddress(fixed_ip) <= IPAddress(subnet_details[2]): + fixed_ips = "ip_address=" + fixed_ip + ports = api.neutron.port_list(request, fixed_ips=fixed_ips) + if ports: + response = {"inuse": False, + "error": "IP address already in use"} + else: + response = {"inuse": True} + break + except Exception: + response = {'error': 'Unable to check IP availability'} + json_string = json.dumps(response, ensure_ascii=False) + return HttpResponse(json_string, content_type='text/json') diff --git a/gbpui/panels/policytargets/workflows.py b/gbpui/panels/policytargets/workflows.py index 6a9e234..0a9f95c 100644 --- a/gbpui/panels/policytargets/workflows.py +++ b/gbpui/panels/policytargets/workflows.py @@ -34,6 +34,10 @@ from openstack_dashboard.dashboards.project.instances.workflows \ from gbpui import client from gbpui import fields +from netaddr import IPAddress +from netaddr import IPNetwork + + LOG = logging.getLogger(__name__) POLICY_RULE_SET_URL = "horizon:project:application_policy:addpolicy_rule_set" @@ -389,7 +393,7 @@ class SetAccessControls(workflows.Step): class SetGroupAction(workflows.Action): # To reuse horizon instance launch code related to Networking, # form filed must be 'network' only - network = forms.MultipleChoiceField(label=_("Groups"), + network = fields.CustomMultiChoiceField(label=_("Groups"), widget=forms.CheckboxSelectMultiple(), error_messages={ 'required': _( @@ -403,7 +407,26 @@ class SetGroupAction(workflows.Action): def __init__(self, request, *args, **kwargs): super(SetGroupAction, self).__init__(request, *args, **kwargs) policy_targetid = self.request.path.split("/")[-2] - self.fields['network'].initial = [policy_targetid] + ptg = client.policy_target_get(request, policy_targetid) + subnet_dedails = None + for subnet_id in ptg.subnets: + try: + subnet = api.neutron.subnet_get(request, subnet_id) + if subnet_dedails is None: + subnet_dedails = subnet['cidr'] + else: + subnet_dedails = ";" + subnet['cidr'] + allocation_pools = subnet['allocation_pools'] + if allocation_pools: + start = allocation_pools[0]['start'] + end = allocation_pools[0]['end'] + subnet_dedails = subnet_dedails + "," + start + subnet_dedails = subnet_dedails + "," + end + except Exception as e: + LOG.error(str(e)) + pass + initial_value = policy_targetid + ":" + subnet_dedails + self.fields['network'].initial = [initial_value] class Meta(object): name = _("Groups") @@ -416,6 +439,24 @@ class SetGroupAction(workflows.Action): tenant_id=request.user.tenant_id) for pt in pts: pt.set_id_as_name_if_empty() + subnet_dedails = None + for subnet_id in pt.subnets: + try: + subnet = api.neutron.subnet_get(request, subnet_id) + if subnet_dedails is None: + subnet_dedails = subnet['cidr'] + else: + subnet_dedails = ";" + subnet['cidr'] + allocation_pools = subnet['allocation_pools'] + if allocation_pools: + start = allocation_pools[0]['start'] + end = allocation_pools[0]['end'] + subnet_dedails = subnet_dedails + "," + start + subnet_dedails = subnet_dedails + "," + end + except Exception as e: + LOG.error(str(e)) + pass + pt.id = pt.id + ":" + subnet_dedails pt_list.append((pt.id, pt.name)) return sorted(pt_list, key=lambda obj: obj[1]) except Exception: @@ -534,9 +575,22 @@ class LaunchInstance(workflows.Workflow): instance_name = context['name'] + str(count) nics = [] for ptg_id in context['group_id']: - ep = client.pt_create( - request, policy_target_group_id=ptg_id, - name=instance_name[:41] + "_gbpui") + values = ptg_id.split(":") + ptg_id = values[0] + args = {'policy_target_group_id': ptg_id, + 'name': instance_name[:41] + "_gbpui"} + if len(values) == 3: + ptg = client.policy_target_get(request, ptg_id) + fixed_ip = values[2] + for subnet_id in ptg.subnets: + subnet = api.neutron.subnet_get(request, subnet_id) + if IPAddress(fixed_ip) in \ + IPNetwork(subnet['cidr']): + args['fixed_ips'] = [ + {'subnet_id': subnet['id'], + 'ip_address': fixed_ip}] + break + ep = client.pt_create(request, **args) nics.append({'port-id': ep.port_id}) api.nova.server_create(request, instance_name, @@ -555,10 +609,10 @@ class LaunchInstance(workflows.Workflow): config_drive=context.get('config_drive')) count += 1 return True - except Exception: + except Exception as e: error = _("Unable to launch member %(count)s with name %(name)s") msg = error % {'count': count, 'name': instance_name} - LOG.error(msg) + LOG.error(str(e)) u = "horizon:project:policytargets:policy_targetdetails" policy_target_id = self.request.path.split("/")[-2] redirect = reverse(u, kwargs={'policy_target_id': diff --git a/gbpui/static/dashboard/js/member.js b/gbpui/static/dashboard/js/member.js new file mode 100644 index 0000000..69cf942 --- /dev/null +++ b/gbpui/static/dashboard/js/member.js @@ -0,0 +1,218 @@ +member = { + user_decided_length: false, + user_volume_size: false, + groups_selected: [], + groups_available: [], + + /* + * Gets the html select element associated with a given + * group id for group_id. + **/ + get_group_element: function(group_id) { + return $('li > label[for^="id_network_' + group_id + '"]'); + }, + + /* + * Initializes an associative array of lists of the current + * groups. + **/ + init_group_list: function () { + member.groups_selected = []; + member.groups_available = []; + $(this.get_group_element("")).each(function () { + var $this = $(this); + var $input = $this.children("input"); + var name = horizon.string.escapeHtml($this.text().replace(/^\s+/, "")); + var group_property = { + "name": name, + "id": $input.attr("id"), + "value": $input.attr("value") + }; + if ($input.is(":checked")) { + member.groups_selected.push(group_property); + } else { + member.groups_available.push(group_property); + } + }); + }, + + /* + * Generates the HTML structure for a group that will be displayed + * as a list item in the group list. + **/ + generate_group_element: function(name, id, value) { + var $li = $('
  • '); + $li.attr('name', value).html(name + ''); + return $li; + }, + + /* + * Generates the HTML structure for the group List. + **/ + generate_grouplist_html: function() { + var self = this; + var available_group = $("#available_group"); + var selected_group = $("#selected_network"); + var reset_unselected_group_fixed_ip = function(){ + ptg = $("#fixed_ip").attr("data-ptg") + if($("ul#available_group li[name^='"+ ptg +"']").length > 0){ + $("ul#available_group li[name^='"+ ptg +"']").css("background-color", ""); + $("#fixed_ip_div").hide() + } + }; + var updateForm = function() { + var groupListId = $("#groupListId"); + var lists = groupListId.find("li").attr('data-index',100); + var active_groups = $("#selected_network > li").map(function(){ + return $(this).attr("name"); + }); + groupListId.find("input:checkbox").removeAttr('checked'); + active_groups.each(function(index, value){ + groupListId.find("input:checkbox[value^='" + value + "']") + .prop('checked', true) + .parents("li").attr('data-index',index); + }); + groupListId.find("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; + }) + ); + reset_unselected_group_fixed_ip() + }; + + $("#groupListSortContainer").show(); + $("#groupListIdContainer").hide(); + self.init_group_list(); + // Make sure we don't duplicate the groups in the list + available_group.empty(); + $.each(self.groups_available, function(index, value){ + available_group.append(self.generate_group_element(value.name, value.id, value.value)); + }); + // Make sure we don't duplicate the groups in the list + selected_group.empty(); + $.each(self.groups_selected, function(index, value){ + selected_group.append(self.generate_group_element(value.name, value.id, value.value)); + }); + + $(".networklist > li > a.btn").click(function(e){ + var $this = $(this); + e.preventDefault(); + e.stopPropagation(); + if($this.parents("ul#available_group").length > 0) { + $this.parent().appendTo(selected_group); + } else if ($this.parents("ul#selected_network").length > 0) { + $this.parent().appendTo(available_group); + } + updateForm(); + }); + if ($("#groupListId > div.form-group.error").length > 0) { + var errortext = $("#groupListId > div.form-group.error span.help-block").text(); + $("#selected_group_label").before($('
    ').html(errortext)); + } + $(".networklist").sortable({ + connectWith: "ul.networklist", + placeholder: "ui-state-highlight", + distance: 5, + start:function(e,info){ + selected_group.addClass("dragging"); + }, + stop:function(e,info){ + selected_group.removeClass("dragging"); + updateForm(); + } + }).disableSelection(); + }, + + allow_fixed_ip: function(selected_group){ + fixed_ip = "" + $("#fixed_ip").val("") + $("#errors").text("") + $("#fixed_ip_div").show() + ptg = $(selected_group).attr('name') + $(selected_group).siblings().css( "background-color", ""); + $(selected_group).css('background-color', '#e6e6e6'); + selected_element = $(".multiple-checkbox #id_network li input[value^='"+ ptg +"']"); + value = selected_element.val(); + values = value.split(':'); + group = $(selected_group).text() + group = group.split("(") + $("#group").text(group[0]) + subnets = values[1].split(";") + $('#subnets_table tbody').empty(); + for (index=0; index < subnets.length; index++){ + subnet_details = subnets[index].split(",") + row = ''+subnet_details[0]+''+ + subnet_details[1] +'' + subnet_details[2]+''; + $('#subnets_table tbody').append(row); + } + $("#fixed_ip").attr("data-ptg", values[0]); + $("#fixed_ip").attr("data-subnet", values[1]) + if (values.length == 3) + $("#fixed_ip").val(values[2]); + }, + associate_fixed_ip: function(){ + ptg = $("#fixed_ip").attr("data-ptg") + subnet = $("#fixed_ip").attr("data-subnet") + fixed_ip = $("#fixed_ip").val() + if (!fixed_ip || 0 === fixed_ip.length ){ + $("#errors").text("Enter valid IP address") + return + } + $.ajax({ + url: 'check_ip_availability', + data: "fixed_ip="+fixed_ip+"&subnets="+subnet, + method: 'get', + success: function(response) { + if(response.inuse){ + horizon.alert('success', "IP address '" + fixed_ip +"' is available.") + value = ptg + ":" + subnet + ":" + fixed_ip + selected_element = $(".multiple-checkbox #id_network li input[value^='"+ ptg +"']"); + selected_element.val(value) + if(fixed_ip){ + $("#selected_network li[name^='"+ ptg +"'] strong").text(" ( "+fixed_ip + " )") + } + else{ + $("#selected_network li[name^='"+ ptg +"'] strong").text("") + } + $("ul#selected_network li[name^='"+ ptg +"']").css("background-color", ""); + $("#fixed_ip_div").hide() + } + else{ + $("#errors").text(response.error) + } + }, + error: function(response) { + $("#errors").text(response) + } + }); + }, + disassociate_fixed_ip: function(){ + ptg = $("#fixed_ip").attr("data-ptg") + subnet = $("#fixed_ip").attr("data-subnet") + value = ptg + ":" + subnet + selected_element = $(".multiple-checkbox #id_network li input[value^='"+ ptg +"']"); + selected_element.val(value) + $("#selected_network li[name^='"+ ptg +"'] strong").text("") + $("#fixed_ip_div").hide() + $("ul#selected_network li").css("background-color", ""); + }, + groups_init: function() { + // Initialise the drag and drop group list + member.generate_grouplist_html(); + + // allocate fixed ip + $(document).on('click', "ul#selected_network li", function(){ + member.allow_fixed_ip(this); + }); + + $("#set_ip_button").click(function(){ + member.associate_fixed_ip(); + }); + + $("#remove").click(function(){ + member.disassociate_fixed_ip() + }); + } +};