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">&times;</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">&times;</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">&times;</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">&times;</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