From 79abcd460bdde7e6de4e441b615fd19bda04f566 Mon Sep 17 00:00:00 2001 From: catborise <catborise@yahoo.com> Date: Fri, 15 Nov 2019 11:29:16 +0300 Subject: [PATCH 1/4] Add Qos functions and Edit network with xml function --- networks/templates/add_inbound_qos.html | 41 +++++++++++++++++ networks/templates/add_outbound_qos.html | 40 +++++++++++++++++ vrtManager/network.py | 57 ++++++++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 networks/templates/add_inbound_qos.html create mode 100644 networks/templates/add_outbound_qos.html diff --git a/networks/templates/add_inbound_qos.html b/networks/templates/add_inbound_qos.html new file mode 100644 index 0000000..135b1c8 --- /dev/null +++ b/networks/templates/add_inbound_qos.html @@ -0,0 +1,41 @@ +{% load i18n %} +{% if request.user.is_superuser %} + <a href="#AddInboundQos" type="button" class="btn btn-success pull-right" data-toggle="modal"> + <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> + </a> + + <!-- Modal pool --> + <div class="modal fade" id="AddInboundQos" tabindex="-1" role="dialog" aria-labelledby="AddInboundQosLabel" 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">×</button> + <h4 class="modal-title">{% trans "Add Inbound Qos for Network" %}</h4> + </div> + <form class="form-horizontal" method="post" action="" role="form" novalidate>{% csrf_token %} + <div class="modal-body"> + <div class="form-group col-sm-4 "> + <label for="qos_inbound_av">{% trans "Average" %}:</label> + <input id="qos_inbound_av" class="form-control" name="qos_inbound_average" value="{{ att.average }}"/> + </div> + <div class="form-group col-sm-4"> + <label for="qos_inbound_peak">{% trans "Peak" %}:</label> + <input id="qos_inbound_peak" class="form-control" name="qos_inbound_peak" value="{{ att.peak }}"/> + </div> + <div class="form-group col-sm-4"> + <label for="qos_inbound_burst">{% trans "Burst" %}:</label> + <input id="qos_inbound_burst" class="form-control" name="qos_inbound_burst" value="{{ att.burst }}"/></p> + </div> + + </div> <!-- /.modal-content --> + <div class="modal-footer"> + <input name="qos_direction" value="inbound" hidden/> + <div class="col-sm-6"> + <button class="btn btn-primary btn-block" name="set_qos">{% trans 'Save' %}</button> + </div> + </div> + </form> + </div> + </div> <!-- /.modal-dialog --> + </div> <!-- /.modal --> +{% endif %} \ No newline at end of file diff --git a/networks/templates/add_outbound_qos.html b/networks/templates/add_outbound_qos.html new file mode 100644 index 0000000..4adf19d --- /dev/null +++ b/networks/templates/add_outbound_qos.html @@ -0,0 +1,40 @@ +{% load i18n %} +{% if request.user.is_superuser %} + <a href="#AddInboundQos" type="button" class="btn btn-success pull-right" data-toggle="modal"> + <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> + </a> + + <!-- Modal pool --> + <div class="modal fade" id="AddInboundQos" tabindex="-1" role="dialog" aria-labelledby="AddInboundQosLabel" 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">×</button> + <h4 class="modal-title">{% trans "Add Inbound Qos for Network" %}</h4> + </div> + <form class="form-horizontal" method="post" action="" role="form" novalidate>{% csrf_token %} + <div class="modal-body"> + <div class="form-group col-sm-4 "> + <label for="qos_inbound_av">{% trans "Average" %}:</label> + <input id="qos_inbound_av" class="form-control" name="qos_inbound_average" value="{{ att.average }}"/> + </div> + <div class="form-group col-sm-4"> + <label for="qos_inbound_peak">{% trans "Peak" %}:</label> + <input id="qos_inbound_peak" class="form-control" name="qos_inbound_peak" value="{{ att.peak }}"/> + </div> + <div class="form-group col-sm-4"> + <label for="qos_inbound_burst">{% trans "Burst" %}:</label> + <input id="qos_inbound_burst" class="form-control" name="qos_inbound_burst" value="{{ att.burst }}"/></p> + </div> + </div> <!-- /.modal-content --> + <div class="modal-footer"> + <input name="qos_direction" value="inbound" hidden/> + <div class="col-sm-6"> + <button class="btn btn-primary btn-block" name="set_qos">{% trans 'Save' %}</button> + </div> + </div> + </form> + </div> + </div> <!-- /.modal-dialog --> + </div> <!-- /.modal --> +{% endif %} \ No newline at end of file diff --git a/vrtManager/network.py b/vrtManager/network.py index ebe54af..87333da 100644 --- a/vrtManager/network.py +++ b/vrtManager/network.py @@ -290,3 +290,60 @@ class wvmNetwork(wvmConnect): parent_index, VIR_NETWORK_UPDATE_AFFECT_LIVE | VIR_NETWORK_UPDATE_AFFECT_CONFIG) + def get_qos(self): + qos_values = dict() + tree = etree.fromstring(self._XMLDesc(0)) + qos = tree.xpath("/network/bandwidth") + if qos: + qos = qos[0] + + in_qos = qos.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') + qos_values['inbound'] = {'average': in_av, 'peak': in_peak, 'burst': in_burst} + + out_qos = qos.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') + qos_values['outbound'] = {'average': out_av, 'peak': out_peak, 'burst': out_burst} + return qos_values + + def set_qos(self, 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)) + + band = tree.xpath("/network/bandwidth") + if len(band) == 0: + xml = "<bandwidth>" + xml + "</bandwidth>" + tree.append(etree.fromstring(xml)) + else: + direct = band[0].find(direction) + if direct is not None: + parent = direct.getparent() + parent.remove(direct) + parent.append(etree.fromstring(xml)) + else: + band[0].append(etree.fromstring(xml)) + new_xml = etree.tostring(tree) + self.wvm.networkDefineXML(new_xml) + + def unset_qos(self, direction): + tree = etree.fromstring(self._XMLDesc(0)) + for direct in tree.xpath("/network/bandwidth/{}".format(direction)): + parent = direct.getparent() + parent.remove(direct) + + self.wvm.networkDefineXML(etree.tostring(tree)) + + def edit_network(self, new_xml): + self.wvm.networkDefineXML(new_xml) From d7b350a591be6d30774ee3decf2c4ccf3cba46a1 Mon Sep 17 00:00:00 2001 From: catborise <catborise@yahoo.com> Date: Fri, 15 Nov 2019 11:35:22 +0300 Subject: [PATCH 2/4] networks/view.py Add Qos Functions. Edit with XML corrections --- networks/views.py | 44 +++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/networks/views.py b/networks/views.py index 4d507b3..38bbd09 100644 --- a/networks/views.py +++ b/networks/views.py @@ -97,6 +97,7 @@ def network(request, compute_id, pool): autostart = conn.get_autostart() net_mac = conn.get_network_mac() net_forward = conn.get_network_forward() + qos = conn.get_qos() dhcp_range_start = ipv4_dhcp_range_end = dict() ip_networks = conn.get_ip_networks() @@ -187,20 +188,37 @@ def network(request, compute_id, pool): if 'edit_network' in request.POST: edit_xml = request.POST.get('edit_xml', '') if edit_xml: - try: - new_conn = wvmNetworks(compute.hostname, - compute.login, - compute.password, - compute.type) - new_conn.define_network(edit_xml) - if conn.is_active(): - messages.success(request, _("Network XML is changed. Stop and start network to activate new config.")) - else: - messages.success(request, _("Network XML is changed.")) - return HttpResponseRedirect(request.get_full_path()) - except libvirtError as lib_err: - error_messages.append(lib_err.message) + conn.edit_network(edit_xml) + if conn.is_active(): + messages.success(request, _("Network XML is changed. \\" + "Stop and start network to activate new config.")) + else: + messages.success(request, _("Network XML is changed.")) + return HttpResponseRedirect(request.get_full_path()) + if 'set_qos' in request.POST: + qos_dir = request.POST.get('qos_direction', '') + average = request.POST.get('qos_{}_average'.format(qos_dir), '') + peak = request.POST.get('qos_{}_peak'.format(qos_dir), '') + burst = request.POST.get('qos_{}_burst'.format(qos_dir), '') + + conn.set_qos(qos_dir, average, peak, burst) + if conn.is_active(): + messages.success(request, "{} Qos is set. Network XML is changed.".format(qos_dir.capitalize()) + + "Stop and start network to activate new config") + else: + messages.success(request, "{} Qos is set".format(qos_dir.capitalize())) + return HttpResponseRedirect(request.get_full_path()) + if 'unset_qos' in request.POST: + qos_dir = request.POST.get('qos_direction', '') + conn.unset_qos(qos_dir) + + if conn.is_active(): + messages.success(request, "{} Qos is deleted. Network XML is changed. ".format(qos_dir.capitalize()) + + "Stop and start network to activate new config.") + else: + messages.success(request, "{} Qos is deleted".format(qos_dir.capitalize())) + return HttpResponseRedirect(request.get_full_path()) conn.close() return render(request, 'network.html', locals()) From f93fed94375a4c6cd1f2718b2aecedbdfa260558 Mon Sep 17 00:00:00 2001 From: catborise <catborise@yahoo.com> Date: Fri, 15 Nov 2019 11:44:48 +0300 Subject: [PATCH 3/4] network.html Add Qos Details --- networks/templates/add_inbound_qos.html | 54 ++++++++++++---------- networks/templates/add_outbound_qos.html | 55 +++++++++++++---------- networks/templates/network.html | 57 +++++++++++++++++++++++- 3 files changed, 118 insertions(+), 48 deletions(-) diff --git a/networks/templates/add_inbound_qos.html b/networks/templates/add_inbound_qos.html index 135b1c8..53b29e5 100644 --- a/networks/templates/add_inbound_qos.html +++ b/networks/templates/add_inbound_qos.html @@ -1,40 +1,48 @@ {% load i18n %} {% if request.user.is_superuser %} - <a href="#AddInboundQos" type="button" class="btn btn-success pull-right" data-toggle="modal"> - <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> + <a href="#AddInboundQos" type="button" class="btn btn-success pull-right" data-toggle="modal" title="add inbound qos"> + <span class="glyphicon glyphicon-arrow-down" aria-hidden="true"></span> </a> <!-- Modal pool --> - <div class="modal fade" id="AddInboundQos" tabindex="-1" role="dialog" aria-labelledby="AddInboundQosLabel" aria-hidden="true"> + <div class="modal fade" id="AddInboundQos" tabindex="-1" role="dialog" aria-labelledby="AddInboundQosLabel" + 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">×</button> <h4 class="modal-title">{% trans "Add Inbound Qos for Network" %}</h4> </div> - <form class="form-horizontal" method="post" action="" role="form" novalidate>{% csrf_token %} - <div class="modal-body"> - <div class="form-group col-sm-4 "> - <label for="qos_inbound_av">{% trans "Average" %}:</label> - <input id="qos_inbound_av" class="form-control" name="qos_inbound_average" value="{{ att.average }}"/> - </div> - <div class="form-group col-sm-4"> - <label for="qos_inbound_peak">{% trans "Peak" %}:</label> - <input id="qos_inbound_peak" class="form-control" name="qos_inbound_peak" value="{{ att.peak }}"/> - </div> - <div class="form-group col-sm-4"> - <label for="qos_inbound_burst">{% trans "Burst" %}:</label> - <input id="qos_inbound_burst" class="form-control" name="qos_inbound_burst" value="{{ att.burst }}"/></p> - </div> - - </div> <!-- /.modal-content --> - <div class="modal-footer"> - <input name="qos_direction" value="inbound" hidden/> + + <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 "Average" %}:</label> <div class="col-sm-6"> - <button class="btn btn-primary btn-block" name="set_qos">{% trans 'Save' %}</button> + <input class="form-control" name="qos_inbound_average" required pattern="[0-9]+"/> </div> </div> - </form> + <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" + required 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" required pattern="[0-9]+"/> + </div> + </div> + <input name="qos_direction" value="inbound" hidden/> + </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 --> diff --git a/networks/templates/add_outbound_qos.html b/networks/templates/add_outbound_qos.html index 4adf19d..0e11640 100644 --- a/networks/templates/add_outbound_qos.html +++ b/networks/templates/add_outbound_qos.html @@ -1,39 +1,48 @@ {% load i18n %} {% if request.user.is_superuser %} - <a href="#AddInboundQos" type="button" class="btn btn-success pull-right" data-toggle="modal"> - <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> + <a href="#AddOutboundQos" type="button" class="btn btn-success pull-right" data-toggle="modal" title="add outbound qos"> + <span class="glyphicon glyphicon-arrow-up" aria-hidden="true"></span> </a> <!-- Modal pool --> - <div class="modal fade" id="AddInboundQos" tabindex="-1" role="dialog" aria-labelledby="AddInboundQosLabel" aria-hidden="true"> + <div class="modal fade" id="AddOutboundQos" tabindex="-1" role="dialog" aria-labelledby="AddOutboundQosLabel" + 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">×</button> - <h4 class="modal-title">{% trans "Add Inbound Qos for Network" %}</h4> + <h4 class="modal-title">{% trans "Add Outbound Qos for Network" %}</h4> </div> - <form class="form-horizontal" method="post" action="" role="form" novalidate>{% csrf_token %} - <div class="modal-body"> - <div class="form-group col-sm-4 "> - <label for="qos_inbound_av">{% trans "Average" %}:</label> - <input id="qos_inbound_av" class="form-control" name="qos_inbound_average" value="{{ att.average }}"/> - </div> - <div class="form-group col-sm-4"> - <label for="qos_inbound_peak">{% trans "Peak" %}:</label> - <input id="qos_inbound_peak" class="form-control" name="qos_inbound_peak" value="{{ att.peak }}"/> - </div> - <div class="form-group col-sm-4"> - <label for="qos_inbound_burst">{% trans "Burst" %}:</label> - <input id="qos_inbound_burst" class="form-control" name="qos_inbound_burst" value="{{ att.burst }}"/></p> - </div> - </div> <!-- /.modal-content --> - <div class="modal-footer"> - <input name="qos_direction" value="inbound" hidden/> + + <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 "Average" %}:</label> <div class="col-sm-6"> - <button class="btn btn-primary btn-block" name="set_qos">{% trans 'Save' %}</button> + <input class="form-control" name="qos_outbound_average" required pattern="[0-9]+"/> </div> </div> - </form> + <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_outbound_peak" + required 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_outbound_burst" required pattern="[0-9]+"/> + </div> + </div> + <input name="qos_direction" value="outbound" hidden/> + </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 --> diff --git a/networks/templates/network.html b/networks/templates/network.html index 0114a08..0f1c832 100644 --- a/networks/templates/network.html +++ b/networks/templates/network.html @@ -97,7 +97,7 @@ </div> </div> - <div class="row"> + <div class="row"> <h3 class="page-header">{% trans "IPv4 Configuration" %}</h3> </div> <div class="row"> @@ -172,7 +172,6 @@ </div> <div id="collapseTwo" class="panel-collapse collapse"> <div class="panel-body"> - <div class="input-append form-inline pull-right"> <div class="form-group"> <input type="text" class="form-control" id="filter_input"> @@ -339,6 +338,60 @@ </div> </div> {% endif %} + + {% ifequal state 0 %} + {% include 'add_outbound_qos.html' %} + {% include 'add_inbound_qos.html' %} + {% endifequal %} + + <div class="row"> + <h3 class="page-header">{% trans "Qos Configuration" %} + </h3> + </div> + + <div class="row"> + <div class="col-sm-12"> + <table class="table table-hover"> + <thead> + <tr> + <th style="text-align: center">{% trans "Direction" %}</th> + <th style="text-align: center">{% trans "Average" %}</th> + <th style="text-align: center">{% trans "Peak" %}</th> + <th style="text-align: center">{% trans "Burst" %}</th> + <th style="text-align: center">{% trans "Actions" %}</th> + </tr> + </thead> + <tbody> + {% for q, att in qos.items %} + <form method="post" role="form">{% csrf_token %} + <tr> + <td><label class="control-label">{{ q | capfirst }}</label></td> + <td><input id="qos_{{ q }}_av" class="form-control" name="qos_{{ q }}_average" + value="{{ att.average }}"/></td> + <td><input id="qos_{{ q }}_peak" class="form-control" name="qos_{{ q }}_peak" + value="{{ att.peak }}"/></td> + <td><input id="qos_{{ q }}_burst" class="form-control" name="qos_{{ q }}_burst" + value="{{ att.burst }}"/></td> + <td> + <input name="qos_direction" value="{{ q }}" hidden/> + <button type="submit" class="btn btn-sm btn-primary" + name="set_qos" + 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 %} + </tbody> + </table> + </div> + </div> {% endblock %} {% block script %} <script> From 69bc58d94f62a693a02c3a74129a213f9358d920 Mon Sep 17 00:00:00 2001 From: catborise <catborise@gmail.com> Date: Fri, 15 Nov 2019 16:35:02 +0300 Subject: [PATCH 4/4] Update README.md Add features, add iproute-tc package to centos req --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dcea63a..b63caad 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,19 @@ ## Features - +* QEMU/KVM Hypervisor Management +* QEMU/KVM Instance Management - Create, Delete, Update +* Hypervisor & Instance web based stats +* Manage Multiple QEMU/KVM Hypervisor +* Manage Hypervisor Datastore pools +* Manage Hypervisor Networks +* Instance Console Access with Browsers +* Libvirt API based web management UI +* User Based Authorization and Authentication * User can add SSH public key to root in Instance (Tested only Ubuntu) * User can change root password in Instance (Tested only Ubuntu) * Supports cloud-init datasource interface + ### Warning!!! @@ -67,7 +76,7 @@ wget -O - https://clck.ru/9V9fH | sudo sh ### Install WebVirtCloud panel (CentOS) ```bash -sudo yum -y install python-virtualenv python-devel libvirt-devel glibc gcc nginx supervisor python-lxml git python-libguestfs +sudo yum -y install python-virtualenv python-devel libvirt-devel glibc gcc nginx supervisor python-lxml git python-libguestfs iproute-tc ``` #### Creating directories and cloning repo