Allow group member to create with fixed IP

Change-Id: I0f00bc39a07eeb524c036c99b79b99bade480dcd
Implements: blueprint fixed-ips-for-pt
This commit is contained in:
ank 2016-05-11 17:43:16 +05:30
parent f0bedbde6b
commit a924945dce
5 changed files with 367 additions and 27 deletions

View File

@ -1,31 +1,70 @@
{% load i18n %}
<noscript><h3>{{ step }}</h3></noscript>
<table class="table-fixed" id="networkListSortContainer">
<style type="text/css">
#subnets_table {
width: 100%
}
#subnets_table td {
border: 1px solid #466E85;
text-align: center;
padding: 3px;
}
</style>
<table class="table-fixed" id="groupListSortContainer" style="width:100%">
<tbody>
<tr>
<td class="actions">
<label id="selected_network_label">{% trans "Selected groups" %}</label>
<div class="form-group">
<label id="selected_group_label">{% trans "Selected Groups" %}</label>
<span class="help-icon" data-toggle="tooltip" data-placement="top"
title=""
data-original-title="Select one or more Groups from the Available Groups. To select, click on Group or drag and drop to the Selected Groups area. You may also change the order of Groups by dragging and dropping within the Selected Groups (ordering determines the order of NICs). You may optionally assign an IP address for a Group by clicking on the Group in the Selected Groups.">
<span class="fa fa-question-circle"></span>
</span>
</div>
<ul id="selected_network" class="networklist">
</ul>
<label>{% trans "Available groups" %}</label>
<ul id="available_network" class="networklist">
<label>{% trans "Available Groups" %}</label>
<ul id="available_group" class="networklist">
</ul>
</td>
<td class="help_text">
<p>{% 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 %}</p>
<td class="help_text" style="padding-left:20px">
<div id="fixed_ip_div" style="display:none">
{% trans "Subnet(s) available for Group '" %}<label id="group"></label>{% trans "'" %}
<table id='subnets_table'>
<thead>
<tr>
<td>CIDR</td>
<td>Pool Start</td>
<td>Pool End</td>
</tr>
</thead>
<tbody></tbody>
</table>
<div class="form-group">
<label class="control-label required" for="fixed_ip">Assign IP</label>
<span class="help-icon" data-toggle="tooltip" data-placement="top"
title=""
data-original-title="Choose IP address in Subnets CIDR within allocated pool start and end range (refer to table above).">
<span class="fa fa-question-circle"></span>
</span>
</div>
<input id="fixed_ip" type="text"/>
<input id="set_ip_button" type="button" value="Add"/>
<input id="remove" type="button" value="Remove"/>
<p id="errors" style="color:red" ></p>
</div>
</td>
</tr>
</tbody>
</table>
<table class="table-fixed" id="networkListIdContainer">
<table class="table-fixed" id="groupListIdContainer">
<tbody>
<tr>
<td class="actions">
<div id="networkListId">
<div id="groupListId">
{% include "horizon/common/_form_fields.html" %}
</div>
</td>
@ -36,13 +75,7 @@
</tbody>
</table>
<script>
if (typeof $ !== 'undefined') {
horizon.instances.workflow_init($(".workflow"));
} else {
addHorizonLoadEvent(function() {
horizon.instances.workflow_init($(".workflow"));
});
}
</script>
<script src='{{ STATIC_URL }}dashboard/js/member.js' type='text/javascript'></script>
<script>
member.groups_init();
</script>

View File

@ -73,4 +73,7 @@ urlpatterns = patterns('',
'(?P<ext_policy_target_id>[^/]+)/$',
views.ExtRemoveConsumedPRSView.as_view(),
name='ext_remove_consumed_prs'),
url(r'/check_ip_availability',
views.check_ip_availability,
name='check_ip_availability'),
)

View File

@ -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')

View File

@ -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':

View File

@ -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>');
$li.attr('name', value).html(name + '<strong></strong><a href="#" class="btn btn-primary"></a>');
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($('<div class="dynamic-error">').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 = '<tr><td>'+subnet_details[0]+'</td><td>'+
subnet_details[1] +'</td><td>' + subnet_details[2]+'</td>';
$('#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()
});
}
};