1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2024-10-31 19:44:16 +00:00

instance network revamped. Qos config for instance net is added

This commit is contained in:
catborise 2019-11-20 08:37:59 +03:00
parent 718388ffef
commit ddd3dd5f65
4 changed files with 314 additions and 90 deletions

View file

@ -851,53 +851,139 @@
</p>
<div class="col-xs-12 col-sm-12">
<form method="post" role="form">{% csrf_token %}
{% for network in networks %}
{% if forloop.first %}
<p><strong>{% trans "Network Devices" %}</strong></p>
{% endif %}
<div class="panel panel-default">
<div class="panel-heading">
<label>eth{{ forloop.counter0 }}({{ network.target|default:"no target" }})</label>
<button class="btn btn-sm pull-right btn-danger" value="{{ network.mac }}" name="delete_network" title="{% trans "Delete Device" %}" onclick="return confirm('{% trans "Are you sure?" %}')">{% trans "Delete" %}</button>
</div>
<div class="panel-body">
<div class="form-group form-inline">
<label class="col-sm-2 col-sm-offset-1 control-label">{% trans "MAC" %} </label>
<input class="form-control" type="text" value="{{ network.mac }}" readonly/>
<label class="control-label"><em>to</em></label>
<input class="form-control" type="text" name="net-mac-{{ forloop.counter0 }}" value="{{ network.mac }}"/>
</div>
<div class="form-group form-inline">
<label class="col-sm-2 col-sm-offset-1 control-label">{% trans "NIC" %} </label>
<input class="form-control" type="text" value="{{ network.nic }}" readonly/>
<label class="control-label"><em>to</em></label>
<select class="form-control" name="net-source-{{ forloop.counter0 }}">
{% for c_net in networks_host %}
<option value="net:{{ c_net }}" {% ifequal c_net network.nic %} selected {% endifequal %}>{% trans 'Network' %} {{ c_net }}</option>
{% endfor %}
{% for c_iface in interfaces_host %}
<option value="iface:{{ c_iface }}" {% ifequal c_iface network.nic %} selected {% endifequal %}>{% trans 'Interface' %} {{ c_iface }}</option>
{% endfor %}
</select>
</div>
<div class="form-group form-inline">
<label class="col-sm-2 col-sm-offset-1">{% trans "Filter" %} </label>
<input class="form-control" type="text" value="{{ network.filterref }}" readonly/>
<label class="control-label"><em>to</em></label>
<select class="form-control" name="net-nwfilter-{{ forloop.counter0 }}">
<option value="">{% trans "None" %}</option>
{% for c_filters in nwfilters_host %}
<option value="{{ c_filters }}" {% ifequal c_filters network.filterref %} selected {% endifequal %}>{{ c_filters }}</option>
{% endfor %}
</select>
</div>
<button class="btn btn-sm btn-primary btn-block" name="change_network" title="{% trans "Apply Network Changes" %}">{% trans "Apply" %}</button>
</div>
</div>
{% endfor %}
</form>
<p><strong>{% trans "Network Devices" %}</strong></p>
<table class="table table-hover">
<thead>
<tr>
<th>{% trans 'Name' %}</th>
<th>{% trans 'MAC' %}</th>
<th>{% trans 'NIC' %}</th>
<th>{% trans 'Filter' %}</th>
<th>{% trans 'Actions' %}</th>
</tr>
</thead>
<tbody>
{% for network in networks %}
<tr>
<td class="col-sm-2"><label>eth{{ forloop.counter0 }}({{ network.target|default:"no target" }})</label></td>
<td><input class="form-control" type="text" value="{{ network.mac }}" readonly/></td>
<td><input class="form-control" type="text" value="{{ network.nic }}" readonly/></td>
<td><input class="form-control" type="text" value="{{ network.filterref }}" readonly/></td>
<td class="col-sm-2">
<form class="form-horizontal" method="post" name="set_qos{{ forloop.counter0 }}" role="form">{% csrf_token %}
<button data-target="#editInstanceNetwork{{ forloop.counter0 }}" type="button" class="btn btn-sm btn-primary"
title="Edit NIC" data-toggle="modal">
<span class="glyphicon glyphicon-edit" aria-hidden="true"></span>
</button>
<div class="modal fade" id="editInstanceNetwork{{ forloop.counter0 }}" role="dialog" aria-labelledby="editInstanceNetworkLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">{% trans "Edit Instance Network" %}</h4>
</div>
<div class="modal-body">
<div class="form-group form-inline">
<label class="col-sm-2 control-label">{% trans "MAC" %} </label>
<input class="form-control" type="text" value="{{ network.mac }}" readonly/>
<label class="control-label"><em>to</em></label>
<input class="form-control" type="text" name="net-mac-{{ forloop.counter0 }}" value="{{ network.mac }}"/>
</div>
<div class="form-group form-inline">
<label class="col-sm-2 control-label">{% trans "NIC" %} </label>
<input class="form-control" type="text" value="{{ network.nic }}" readonly/>
<label class="control-label"><em>to</em></label>
<select class="form-control" name="net-source-{{ forloop.counter0 }}">
{% for c_net in networks_host %}
<option value="net:{{ c_net }}" {% ifequal c_net network.nic %} selected {% endifequal %}>{% trans 'Network' %} {{ c_net }}</option>
{% endfor %}
{% for c_iface in interfaces_host %}
<option value="iface:{{ c_iface }}" {% ifequal c_iface network.nic %} selected {% endifequal %}>{% trans 'Interface' %} {{ c_iface }}</option>
{% endfor %}
</select>
</div>
<div class="form-group form-inline">
<label class="col-sm-2 control-label">{% trans "Filter" %} </label>
<input class="form-control" type="text" value="{{ network.filterref }}" readonly/>
<label class="control-label"><em>to</em></label>
<select class="form-control" name="net-nwfilter-{{ forloop.counter0 }}">
<option value="">{% trans "None" %}</option>
{% for c_filters in nwfilters_host %}
<option value="{{ c_filters }}" {% ifequal c_filters network.filterref %} selected {% endifequal %}>{{ c_filters }}</option>
{% endfor %}
</select>
</div>
<button class="btn btn-sm btn-primary btn-block" name="change_network" title="{% trans "Apply Network Changes" %}">{% trans "Apply" %}</button>
</div>
</div>
</div>
</div>
<button class="btn btn-sm btn-danger" value="{{ network.mac }}" name="delete_network" title="{% trans "Delete Device" %}"
onclick="return confirm('{% trans "Are you sure?" %}')">
<i class="glyphicon glyphicon-trash"></i>
</button>
{% include 'add_network_qos.html' with id=forloop.counter0 %}
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if qos %}
<div class="col-xs-10 col-sm-10">
<p><strong>{% trans "Qos Configuration" %}</strong></p>
</div>
<div class="col-xs-12 col-sm-12">
<table class="table table-hover">
<thead>
<tr>
<th>{% trans "Direction" %}</th>
<th>{% trans "Average" %}</th>
<th>{% trans "Peak" %}</th>
<th>{% trans "Burst" %}</th>
<th>{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for q, attrs in qos.items %}
{% for att in attrs %}
<form method="post" role="form">{% csrf_token %}
<tr>
<td><label class="control-label">{{ q }} {{ att.direction | capfirst }}</label></td>
<td><input id="qos_average" class="form-control" name="qos_average"
value="{{ att.average|default:'' }}"/></td>
<td><input id="qos_peak" class="form-control" name="qos_peak"
value="{{ att.peak|default:'' }}"/></td>
<td><input id="qos_burst" class="form-control" name="qos_burst"
value="{{ att.burst|default:'' }}"/></td>
<td class="col-sm-2">
<input name="qos_direction" value="{{ att.direction }}" hidden/>
<input name="net-mac" value="{{ q }}" hidden/>
<button type="submit" class="btn btn-sm btn-primary"
name="set_qos" data-toggle="modal"
title="Edit Qos" onclick="return confirm('{% trans "Are you sure?" %}')">
<i class="glyphicon glyphicon-save"></i>
</button>
<button type="submit" class="btn btn-sm btn-danger"
name="unset_qos"
title="Delete Qos" onclick="return confirm('{% trans "Are you sure?" %}')">
<i class="glyphicon glyphicon-trash"></i>
</button>
</td>
</tr>
</form>
{% endfor %}
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<div class="clearfix"></div>
</div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="migrate">

View file

@ -272,6 +272,7 @@ def instance(request, compute_id, vname):
title = conn.get_title()
description = conn.get_description()
networks = conn.get_net_device()
qos = conn.get_all_qos()
disks = conn.get_disk_devices()
media = conn.get_media_devices()
if len(media) != 0:
@ -482,8 +483,9 @@ def instance(request, compute_id, vname):
addlogmsg(request.user.username, instance.name, msg)
return HttpResponseRedirect(request.get_full_path() + '#resize')
if 'resizevm_mem' in request.POST and (
request.user.is_superuser or request.user.is_staff or userinstance.is_change):
if 'resizevm_mem' in request.POST and (request.user.is_superuser or
request.user.is_staff or
userinstance.is_change):
new_memory = request.POST.get('memory', '')
new_memory_custom = request.POST.get('memory_custom', '')
if new_memory_custom:
@ -699,7 +701,8 @@ def instance(request, compute_id, vname):
msg = _("Set boot order")
if not conn.get_status() == 5:
messages.success(request, _("Boot menu changes applied. But it will be activated after shutdown"))
messages.success(request, _("Boot menu changes applied. " +
"But it will be activated after shutdown"))
else:
messages.success(request, _("Boot order changed successfully."))
addlogmsg(request.user.username, instance.name, msg)
@ -727,7 +730,8 @@ def instance(request, compute_id, vname):
error_messages.append(msg)
if not error_messages:
if not conn.set_console_passwd(passwd):
msg = _("Error setting console password. You should check that your instance have an graphic device.")
msg = _("Error setting console password. " +
"You should check that your instance have an graphic device.")
error_messages.append(msg)
else:
msg = _("Set VNC password")
@ -808,6 +812,40 @@ def instance(request, compute_id, vname):
addlogmsg(request.user.username, instance.name, msg)
return HttpResponseRedirect(request.get_full_path() + '#network')
if 'set_qos' in request.POST:
qos_dir = request.POST.get('qos_direction', '')
average = request.POST.get('qos_average') or 0
peak = request.POST.get('qos_peak') or 0
burst = request.POST.get('qos_burst') or 0
keys = request.POST.keys()
mac_key = [key for key in keys if 'mac' in key]
if mac_key: mac = request.POST.get(mac_key[0])
try:
conn.set_qos(mac, qos_dir, average, peak, burst)
if conn.get_status() == 5:
messages.success(request, "{} Qos is set".format(qos_dir.capitalize()))
else:
messages.success(request,
"{} Qos is set. Network XML is changed.".format(qos_dir.capitalize()) +
"Stop and start network to activate new config")
except libvirtError as le:
messages.error(request, le.message)
return HttpResponseRedirect(request.get_full_path() + '#network')
if 'unset_qos' in request.POST:
qos_dir = request.POST.get('qos_direction', '')
mac = request.POST.get('net-mac')
conn.unset_qos(mac, qos_dir)
if conn.get_status() == 5:
messages.success(request, "{} Qos is deleted".format(qos_dir.capitalize()))
else:
messages.success(request,
"{} Qos is deleted. Network XML is changed. ".format(qos_dir.capitalize()) +
"Stop and start network to activate new config.")
return HttpResponseRedirect(request.get_full_path() + '#network')
if 'add_owner' in request.POST:
user_id = int(request.POST.get('user_id', ''))

View file

@ -1,11 +1,11 @@
{% load i18n %}
{% if request.user.is_superuser %}
<a href="#AddQos" type="button" class="btn btn-sm btn-success pull-right" data-toggle="modal" title="add qos">
<a href="#AddQos{{ id }}" type="button" class="btn btn-sm btn-success pull-right" data-toggle="modal" title="add qos">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
</a>
<!-- Modal pool -->
<div class="modal fade" id="AddQos" tabindex="-1" role="dialog" aria-labelledby="AddQosLabel"
<div class="modal fade" id="AddQos{{ id }}" tabindex="-1" role="dialog" aria-labelledby="AddQosLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
@ -14,7 +14,6 @@
<h4 class="modal-title">{% trans "Add Inbound Qos for Network" %}</h4>
</div>
<div class="modal-body">
<form class="form-horizontal" method="post" name="set_qos" role="form">{% csrf_token %}
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Direction" %}:</label>
<div class="col-sm-6">
@ -27,32 +26,30 @@
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Average" %}:</label>
<div class="col-sm-6">
<input class="form-control" name="qos_inbound_average" placeholder="kilobytes"
<input class="form-control" name="qos_average" placeholder="kilobytes"
required pattern="[0-9]+"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Peak" %}:</label>
<div class="col-sm-6">
<input class="form-control" name="qos_inbound_peak" placeholder="kilobytes"
required pattern="[0-9]+"/>
<input class="form-control" name="qos_peak" placeholder="kilobytes"
pattern="[0-9]+"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Burst" %}:</label>
<div class="col-sm-6">
<input class="form-control" name="qos_inbound_burst" placeholder="kilobytes"
required pattern="[0-9]+"/>
<input class="form-control" name="qos_burst" placeholder="kilobytes"
pattern="[0-9]+"/>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans 'Close' %}</button>
<button type="submit" class="btn btn-primary" name="set_qos">{% trans 'Save' %}</button>
</div>
</form>
</div>
</div> <!-- /.modal-dialog -->
</div> <!-- /.modal -->

View file

@ -7,6 +7,7 @@ except:
from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_MIGRATE_LIVE
from vrtManager import util
from xml.etree import ElementTree
from lxml import etree
from datetime import datetime
from vrtManager.connection import wvmConnect
from vrtManager.storage import wvmStorage
@ -157,6 +158,12 @@ class wvmInstance(wvmConnect):
return self.wvm.defineXML(xml)
def get_status(self):
"""
VIR_DOMAIN_NOSTATE = 0
VIR_DOMAIN_RUNNING = 1
VIR_DOMAIN_PAUSED = 3
VIR_DOMAIN_SHUTOFF = 5
"""
return self.instance.info()[0]
def get_autostart(self):
@ -229,17 +236,38 @@ class wvmInstance(wvmConnect):
def networks(ctx):
result = []
inbound = outbound = []
for net in ctx.xpath('/domain/devices/interface'):
mac_host = net.xpath('mac/@address')[0]
network_host = net.xpath('source/@network|source/@bridge|source/@dev')[0]
target_host = '' if not net.xpath('target/@dev') else net.xpath('target/@dev')[0]
filterref_host = '' if not net.xpath('filterref/@filter') else net.xpath('filterref/@filter')[0]
mac_inst = net.xpath('mac/@address')[0]
nic_inst = net.xpath('source/@network|source/@bridge|source/@dev')[0]
target_inst = '' if not net.xpath('target/@dev') else net.xpath('target/@dev')[0]
filterref_inst = '' if not net.xpath('filterref/@filter') else net.xpath('filterref/@filter')[0]
if net.xpath('bandwidth/inbound'):
in_attr = net.xpath('bandwidth/inbound')[0]
in_av = in_attr.get('average')
in_peak = in_attr.get('peak')
in_burst = in_attr.get('burst')
inbound = {'average': in_av, 'peak': in_peak, 'burst': in_burst}
if net.xpath('bandwidth/outbound'):
out_attr = net.xpath('bandwidth/outbound')[0]
out_av = out_attr.get('average')
out_peak = out_attr.get('peak')
out_burst = out_attr.get('burst')
outbound = {'average': out_av, 'peak': out_peak, 'burst': out_burst}
try:
net = self.get_network(network_host)
ip = get_mac_ipaddr(net, mac_host)
net = self.get_network(nic_inst)
ip = get_mac_ipaddr(net, mac_inst)
except libvirtError:
ip = None
result.append({'mac': mac_host, 'nic': network_host, 'target': target_host, 'ip': ip, 'filterref': filterref_host})
result.append({'mac': mac_inst,
'nic': nic_inst,
'target': target_inst,
'ip': ip,
'filterref': filterref_inst,
'inbound': inbound,
'outbound': outbound,
})
return result
return util.get_xml_path(self._XMLDesc(0), func=networks)
@ -680,8 +708,7 @@ class wvmInstance(wvmConnect):
def get_console_port(self, console_type=None):
if console_type is None:
console_type = self.get_console_type()
port = util.get_xml_path(self._XMLDesc(0),
"/domain/devices/graphics[@type='%s']/@port" % console_type)
port = util.get_xml_path(self._XMLDesc(0), "/domain/devices/graphics[@type='%s']/@port" % console_type)
return port
def get_console_websocket_port(self):
@ -691,8 +718,7 @@ class wvmInstance(wvmConnect):
return websocket_port
def get_console_passwd(self):
return util.get_xml_path(self._XMLDesc(VIR_DOMAIN_XML_SECURE),
"/domain/devices/graphics/@passwd")
return util.get_xml_path(self._XMLDesc(VIR_DOMAIN_XML_SECURE), "/domain/devices/graphics/@passwd")
def set_console_passwd(self, passwd):
xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE)
@ -735,8 +761,7 @@ class wvmInstance(wvmConnect):
self._defineXML(newxml)
def get_console_keymap(self):
return util.get_xml_path(self._XMLDesc(VIR_DOMAIN_XML_SECURE),
"/domain/devices/graphics/@keymap") or ''
return util.get_xml_path(self._XMLDesc(VIR_DOMAIN_XML_SECURE), "/domain/devices/graphics/@keymap") or ''
def resize(self, cur_memory, memory, cur_vcpu, vcpu, disks=[]):
"""
@ -1007,25 +1032,34 @@ class wvmInstance(wvmConnect):
bridge_name = net.bridgeName()
return bridge_name
def add_network(self, mac_address, source, source_type='net', interface_type='bridge', model='virtio', nwfilter=None):
def add_network(self, mac_address, source, source_type='net', model='virtio', nwfilter=None):
bridge_name = self.get_bridge_name(source, source_type)
xml_interface = """
<interface type='%s'>
<mac address='%s'/>
<source bridge='%s'/>
<model type='%s'/>
""" % (interface_type, mac_address, bridge_name, model)
forward_mode = self.get_network_forward(source)
if forward_mode in ['nat', 'isolated', 'routed']:
interface_type = 'network'
else:
interface_type = 'bridge'
xml_iface = """
<interface type='%s'>
<mac address='%s'/>""" % (interface_type, mac_address)
if interface_type == 'network':
xml_iface += """<source network='%s'/>""" % source
else:
xml_iface += """<source bridge='%s'/>""" % bridge_name
xml_iface += """<model type='%s'/>""" % model
if nwfilter:
xml_interface += """
xml_iface += """
<filterref filter='%s'/>
""" % nwfilter
xml_interface += """</interface>"""
xml_iface += """</interface>"""
if self.get_status() == 1:
self.instance.attachDeviceFlags(xml_interface, VIR_DOMAIN_AFFECT_LIVE)
self.instance.attachDeviceFlags(xml_interface, VIR_DOMAIN_AFFECT_CONFIG)
self.instance.attachDeviceFlags(xml_iface, VIR_DOMAIN_AFFECT_LIVE)
self.instance.attachDeviceFlags(xml_iface, VIR_DOMAIN_AFFECT_CONFIG)
if self.get_status() == 5:
self.instance.attachDeviceFlags(xml_interface, VIR_DOMAIN_AFFECT_CONFIG)
self.instance.attachDeviceFlags(xml_iface, VIR_DOMAIN_AFFECT_CONFIG)
def delete_network(self, mac_address):
tree = ElementTree.fromstring(self._XMLDesc(0))
@ -1044,10 +1078,11 @@ class wvmInstance(wvmConnect):
xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE)
tree = ElementTree.fromstring(xml)
for num, interface in enumerate(tree.findall('devices/interface')):
net_source = network_data['net-source-' + str(num)]
net_source_type = network_data['net-source-' + str(num) + '-type']
net_mac = network_data['net-mac-' + str(num)]
net_filter = network_data['net-nwfilter-' + str(num)]
net_mac = network_data.get('net-mac-' + str(num))
if net_mac is None: continue
net_source = network_data.get('net-source-' + str(num))
net_source_type = network_data.get('net-source-' + str(num) + '-type')
net_filter = network_data.get('net-nwfilter-' + str(num))
bridge_name = self.get_bridge_name(net_source, net_source_type)
if interface.get('type') == 'bridge':
source = interface.find('mac')
@ -1103,10 +1138,78 @@ class wvmInstance(wvmConnect):
tree = ElementTree.fromstring(xml)
self._set_options(tree, options)
new_xml = ElementTree.tostring(tree)
self._defineXML(new_xml)
def set_memory(self, size, flags=0):
self.instance.setMemoryFlags(size, flags)
def get_all_qos(self):
qos_values = dict()
tree = etree.fromstring(self._XMLDesc(0))
qos = tree.xpath("/domain/devices/interface")
for q in qos:
bound_list = list()
mac = q.xpath('mac/@address')
band = q.find('bandwidth')
if band is not None:
in_qos = band.find('inbound')
if in_qos is not None:
in_av = in_qos.get('average')
in_peak = in_qos.get('peak')
in_burst = in_qos.get('burst')
in_floor = in_qos.get('floor')
bound_list.append({'direction': 'inbound', 'average': in_av, 'peak': in_peak, 'floor': in_floor, 'burst': in_burst})
out_qos = band.find('outbound')
if out_qos is not None:
out_av = out_qos.get('average')
out_peak = out_qos.get('peak')
out_burst = out_qos.get('burst')
bound_list.append({'direction': 'outbound', 'average': out_av, 'peak': out_peak, 'burst': out_burst})
qos_values[mac[0]] = bound_list
return qos_values
def set_qos(self, mac, direction, average, peak, burst):
if direction == "inbound":
xml = "<inbound average='{}' peak='{}' burst='{}'/>".format(average, peak, burst)
elif direction == "outbound":
xml = "<outbound average='{}' peak='{}' burst='{}'/>".format(average, peak, burst)
else:
raise Exception('Direction must be inbound or outbound')
tree = etree.fromstring(self._XMLDesc(0))
macs = tree.xpath("/domain/devices/interface/mac")
for cur_mac in macs:
if cur_mac.get("address") == mac:
interface = cur_mac.getparent()
band = interface.find('bandwidth')
if band is None:
xml = "<bandwidth>" + xml + "</bandwidth>"
interface.append(etree.fromstring(xml))
else:
direct = band.find(direction)
if direct is not None:
parent = direct.getparent()
parent.remove(direct)
parent.append(etree.fromstring(xml))
else:
band.append(etree.fromstring(xml))
new_xml = etree.tostring(tree)
self.wvm.defineXML(new_xml)
def unset_qos(self, mac, direction):
tree = etree.fromstring(self._XMLDesc(0))
for direct in tree.xpath("/domain/devices/interface/bandwidth/{}".format(direction)):
band_el = direct.getparent()
interface_el = band_el.getparent() # parent bandwidth,it parent is interface
parent_mac = interface_el.xpath('mac/@address')
if parent_mac[0] == mac:
band_el.remove(direct)
self.wvm.defineXML(etree.tostring(tree))