From 38ae62d093811bde4bd8848deb124e3d607dcfcb Mon Sep 17 00:00:00 2001 From: catborise <catborise@yahoo.com> Date: Wed, 30 Oct 2019 11:02:38 +0300 Subject: [PATCH 1/5] define network with XML is related with network object. It is converted --- networks/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/networks/views.py b/networks/views.py index 71a0b9a..0bc9c1d 100644 --- a/networks/views.py +++ b/networks/views.py @@ -161,7 +161,7 @@ def network(request, compute_id, pool): compute.login, compute.password, compute.type) - conn.define_network(edit_xml) + 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: From 568ff92449495b7dcec77e8f6724cf8bb3712480 Mon Sep 17 00:00:00 2001 From: catborise <catborise@yahoo.com> Date: Wed, 30 Oct 2019 11:05:00 +0300 Subject: [PATCH 2/5] libvirt does not have the connection close reason contants anymore. pep8 conventions apply --- vrtManager/connection.py | 44 +++++++++++++++++----------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/vrtManager/connection.py b/vrtManager/connection.py index 278232c..062ee91 100644 --- a/vrtManager/connection.py +++ b/vrtManager/connection.py @@ -124,16 +124,7 @@ class wvmConnection(object): # on server shutdown libvirt module gets freed before the close callbacks are called # so we just check here if it is still present if libvirt is not None: - if (reason == libvirt.VIR_CONNECT_CLOSE_REASON_ERROR): - self.last_error = 'connection closed: Misc I/O error' - elif (reason == libvirt.VIR_CONNECT_CLOSE_REASON_EOF): - self.last_error = 'connection closed: End-of-file from server' - elif (reason == libvirt.VIR_CONNECT_CLOSE_REASON_KEEPALIVE): - self.last_error = 'connection closed: Keepalive timer triggered' - elif (reason == libvirt.VIR_CONNECT_CLOSE_REASON_CLIENT): - self.last_error = 'connection closed: Client requested it' - else: - self.last_error = 'connection closed: Unknown error' + self.last_error = reason # prevent other threads from using the connection (in the future) self.connection = None @@ -255,11 +246,11 @@ class wvmConnectionManager(object): """ self._connections_lock.acquireRead() try: - if (host in self._connections): + if host in self._connections: connections = self._connections[host] for connection in connections: - if (connection.login == login and connection.passwd == passwd and connection.type == conn): + if connection.login == login and connection.passwd == passwd and connection.type == conn: return connection finally: self._connections_lock.release() @@ -278,13 +269,13 @@ class wvmConnectionManager(object): connection = self._search_connection(host, login, passwd, conn) - if (connection is None): + if connection is None: self._connections_lock.acquireWrite() try: # we have to search for the connection again after aquireing the write lock # as the thread previously holding the write lock may have already added our connection connection = self._search_connection(host, login, passwd, conn) - if (connection is None): + if connection is None: # create a new connection if a matching connection does not already exist connection = wvmConnection(host, login, passwd, conn) @@ -317,7 +308,7 @@ class wvmConnectionManager(object): socket_host.settimeout(1) if conn_type == CONN_SSH: if ':' in hostname: - LIBVIRT_HOST, PORT = (hostname).split(":") + LIBVIRT_HOST, PORT = hostname.split(":") PORT = int(PORT) else: PORT = SSH_PORT @@ -332,6 +323,7 @@ class wvmConnectionManager(object): except Exception as err: return err + connection_manager = wvmConnectionManager( settings.LIBVIRT_KEEPALIVE_INTERVAL if hasattr(settings, 'LIBVIRT_KEEPALIVE_INTERVAL') else 5, settings.LIBVIRT_KEEPALIVE_COUNT if hasattr(settings, 'LIBVIRT_KEEPALIVE_COUNT') else 5 @@ -368,7 +360,7 @@ class wvmConnect(object): minor = ver / 1000 ver = ver % 1000 release = ver - return "%s.%s.%s" % (major,minor,release) + return "%s.%s.%s" % (major, minor, release) def get_lib_version(self): ver = self.wvm.getLibVersion() @@ -432,7 +424,7 @@ class wvmConnect(object): for arch in ctx.xpath('/capabilities/guest/arch'): domain_types = arch.xpath('domain/@type') arch_name = arch.xpath('@name')[0] - result[arch_name]= domain_types + result[arch_name] = domain_types return result return util.get_xml_path(self.get_cap_xml(), func=hypervisors) @@ -446,7 +438,7 @@ class wvmConnect(object): for arch in ctx.xpath('/capabilities/guest/arch'): emulator = arch.xpath('emulator') arch_name = arch.xpath('@name')[0] - result[arch_name]= emulator + result[arch_name] = emulator return result return util.get_xml_path(self.get_cap_xml(), func=emulators) @@ -460,8 +452,8 @@ class wvmConnect(object): def get_bus_list(ctx): result = [] for disk_enum in ctx.xpath('/domainCapabilities/devices/disk/enum'): - if disk_enum.xpath("@name")[0] == "bus": - for values in disk_enum: result.append(values.text) + if disk_enum.xpath("@name")[0] == "bus": + for values in disk_enum: result.append(values.text) return result # return [ 'ide', 'scsi', 'usb', 'virtio' ] @@ -482,11 +474,11 @@ class wvmConnect(object): def get_image_formats(self): """Get available image formats""" - return [ 'raw', 'qcow', 'qcow2' ] + return ['raw', 'qcow', 'qcow2'] def get_file_extensions(self): """Get available image filename extensions""" - return [ 'img', 'qcow', 'qcow2' ] + return ['img', 'qcow', 'qcow2'] def get_video(self): """ Get available graphics video types """ @@ -496,7 +488,7 @@ class wvmConnect(object): if video_enum.xpath("@name")[0] == "modelType": for values in video_enum: result.append(values.text) return result - return util.get_xml_path(self.get_dom_cap_xml(),func=get_video_list) + return util.get_xml_path(self.get_dom_cap_xml(), func=get_video_list) def get_iface(self, name): return self.wvm.interfaceLookupByName(name) @@ -560,6 +552,7 @@ class wvmConnect(object): def get_host_instances(self, raw_mem_size=False): vname = {} + def get_info(doc): mem = util.get_xpath(doc, "/domain/currentMemory") mem = int(mem) / 1024 @@ -574,7 +567,7 @@ class wvmConnect(object): title = title if title else '' description = util.get_xpath(doc, "/domain/description") description = description if description else '' - return (mem, vcpu, title, description) + return mem, vcpu, title, description for name in self.get_instances(): dom = self.get_instance(name) xml = dom.XMLDesc(0) @@ -592,6 +585,7 @@ class wvmConnect(object): def get_user_instances(self, name): dom = self.get_instance(name) xml = dom.XMLDesc(0) + def get_info(ctx): mem = util.get_xpath(ctx, "/domain/currentMemory") mem = int(mem) / 1024 @@ -604,7 +598,7 @@ class wvmConnect(object): title = title if title else '' description = util.get_xpath(ctx, "/domain/description") description = description if description else '' - return (mem, vcpu, title, description) + return mem, vcpu, title, description (mem, vcpu, title, description) = util.get_xml_path(xml, func=get_info) return { 'name': dom.name(), From f3f4f0afe801d4bcdb44466e5d83ed4dbb1ae29b Mon Sep 17 00:00:00 2001 From: catborise <catborise@yahoo.com> Date: Wed, 30 Oct 2019 11:05:50 +0300 Subject: [PATCH 3/5] Fix typos. Code Inspection for pep8 conventions --- instances/views.py | 10 +++++++--- interfaces/views.py | 1 + nwfilters/views.py | 31 +++++++++++++++---------------- secrets/views.py | 1 + vrtManager/IPy.py | 16 ++++++++-------- vrtManager/instance.py | 8 +++++--- 6 files changed, 37 insertions(+), 30 deletions(-) diff --git a/instances/views.py b/instances/views.py index 1a1c5c0..474bfd2 100644 --- a/instances/views.py +++ b/instances/views.py @@ -73,6 +73,7 @@ def allinstances(request): def instances(request, compute_id): """ :param request: + :param compute_id :return: """ all_host_vms = {} @@ -224,9 +225,9 @@ def instance(request, compute_id, vname): def get_network_tuple(network_source_str): network_source_pack = network_source_str.split(":", 1) if len(network_source_pack) > 1: - return (network_source_pack[1], network_source_pack[0]) + return network_source_pack[1], network_source_pack[0] else: - return (network_source_pack[0], 'net') + return network_source_pack[0], 'net' def migrate_instance(new_compute, instance, live=False, unsafe=False, xml_del=False, offline=False): status = connection_manager.host_is_up(new_compute.type, new_compute.hostname) @@ -1104,10 +1105,13 @@ def instances_actions(request): return HttpResponseRedirect(request.get_full_path()) return HttpResponseRedirect(request.get_full_path()) + @login_required def inst_graph(request, compute_id, vname): """ :param request: + :param compute_id: + :param vname: :return: """ json_blk = [] @@ -1298,7 +1302,7 @@ def delete_instance(instance, delete_disk=False): conn.delete() instance.delete() - print("Instance {} on compute {} sucessfully deleted".format(instance_name, compute.hostname)) + print("Instance {} on compute {} successfully deleted".format(instance_name, compute.hostname)) except libvirtError as lib_err: print("Error removing instance {} on compute {}".format(instance_name, compute.hostname)) diff --git a/interfaces/views.py b/interfaces/views.py index f71289c..acbf440 100644 --- a/interfaces/views.py +++ b/interfaces/views.py @@ -61,6 +61,7 @@ def interfaces(request, compute_id): def interface(request, compute_id, iface): """ :param request: + :param compute_id: :param iface: :return: """ diff --git a/nwfilters/views.py b/nwfilters/views.py index c0dd8d8..6f2529b 100644 --- a/nwfilters/views.py +++ b/nwfilters/views.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.shortcuts import render from django.http import HttpResponseRedirect from django.shortcuts import render, get_object_or_404 from django.utils.translation import ugettext_lazy as _ @@ -19,6 +18,7 @@ from logs.views import addlogmsg def nwfilters(request, compute_id): """ :param request: + :param compute_id: :return: """ @@ -31,9 +31,9 @@ def nwfilters(request, compute_id): try: conn = wvmNWFilters(compute.hostname, - compute.login, - compute.password, - compute.type) + compute.login, + compute.password, + compute.type) if request.method == 'POST': if 'create_nwfilter' in request.POST: @@ -63,7 +63,7 @@ def nwfilters(request, compute_id): addlogmsg(request.user.username, compute.hostname, lib_err.message) if 'del_nwfilter' in request.POST: - name = request.POST.get('nwfiltername','') + name = request.POST.get('nwfiltername', '') msg = _("Deleting NWFilter: %s" % name) in_use = False nwfilter = conn.get_nwfilter(name) @@ -71,7 +71,6 @@ def nwfilters(request, compute_id): is_conn = wvmInstances(compute.hostname, compute.login, compute.password, compute.type) instances = is_conn.get_instances() for inst in instances: - # if in_use: break i_conn = wvmInstance(compute.hostname, compute.login, compute.password, compute.type, inst) dom_filterrefs = i_conn.get_filterrefs() @@ -90,10 +89,10 @@ def nwfilters(request, compute_id): if 'cln_nwfilter' in request.POST: - name = request.POST.get('nwfiltername','') + name = request.POST.get('nwfiltername', '') cln_name = request.POST.get('cln_name', name + '-clone') - conn.clone_nwfilter(name,cln_name) + conn.clone_nwfilter(name, cln_name) msg = _("Cloning NWFilter %s as %s" % (name, cln_name)) addlogmsg(request.user.username, compute.hostname, msg) @@ -122,14 +121,14 @@ def nwfilter(request, compute_id, nwfltr): try: nwfilter = wvmNWFilter(compute.hostname, - compute.login, - compute.password, - compute.type, - nwfltr) + compute.login, + compute.password, + compute.type, + nwfltr) conn = wvmNWFilters(compute.hostname, - compute.login, - compute.password, - compute.type) + compute.login, + compute.password, + compute.type) for nwf in conn.get_nwfilters(): nwfilters_all.append(conn.get_nwfilter_info(nwf)) @@ -208,4 +207,4 @@ def nwfilter(request, compute_id, nwfltr): except Exception as error_msg: error_messages.append(error_msg) - return render(request, 'nwfilter.html', locals()) \ No newline at end of file + return render(request, 'nwfilter.html', locals()) diff --git a/secrets/views.py b/secrets/views.py index 90e5e36..efc0a09 100644 --- a/secrets/views.py +++ b/secrets/views.py @@ -12,6 +12,7 @@ from libvirt import libvirtError def secrets(request, compute_id): """ :param request: + :param compute_id: :return: """ diff --git a/vrtManager/IPy.py b/vrtManager/IPy.py index 6dbdf6b..b157f3d 100644 --- a/vrtManager/IPy.py +++ b/vrtManager/IPy.py @@ -318,9 +318,9 @@ class IPint(object): (self._ipversion == 6 and self._prefixlen == 128): if self.NoPrefixForSingleIp: want = 0 - if want == None: + if want is None: want = self.WantPrefixLen - if want == None: + if want is None: want = 1 if want: if want == 2: @@ -354,7 +354,7 @@ class IPint(object): """ bits = _ipVersionToLen(self._ipversion) - if self.WantPrefixLen == None and wantprefixlen == None: + if self.WantPrefixLen is None and wantprefixlen is None: wantprefixlen = 0 ret = _intToBin(self.ip) return '0' * (bits - len(ret)) + ret + self._printPrefix(wantprefixlen) @@ -370,7 +370,7 @@ class IPint(object): 'ffff:ffff:ffff:ffff:ffff:f:f:fffc/127' """ - if self.WantPrefixLen == None and wantprefixlen == None: + if self.WantPrefixLen is None and wantprefixlen is None: wantprefixlen = 1 if self._ipversion == 4: @@ -413,7 +413,7 @@ class IPint(object): 2001:658:22a:cafe:200:0:0:1 """ - if self.WantPrefixLen == None and wantprefixlen == None: + if self.WantPrefixLen is None and wantprefixlen is None: wantprefixlen = 1 if self._ipversion == 4: @@ -434,7 +434,7 @@ class IPint(object): 2001:0658:022a:cafe:0200:0000:0000:0001 """ - if self.WantPrefixLen == None and wantprefixlen == None: + if self.WantPrefixLen is None and wantprefixlen is None: wantprefixlen = 1 return intToIp(self.ip, self._ipversion) + self._printPrefix(wantprefixlen) @@ -448,7 +448,7 @@ class IPint(object): 0x20010658022acafe0200000000000001 """ - if self.WantPrefixLen == None and wantprefixlen == None: + if self.WantPrefixLen is None and wantprefixlen is None: wantprefixlen = 0 x = '0x%x' % self.ip @@ -463,7 +463,7 @@ class IPint(object): 42540616829182469433547762482097946625 """ - if self.WantPrefixLen == None and wantprefixlen == None: + if self.WantPrefixLen is None and wantprefixlen is None: wantprefixlen = 0 x = '%d' % self.ip diff --git a/vrtManager/instance.py b/vrtManager/instance.py index eb4147f..c929160 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -247,7 +247,7 @@ class wvmInstance(wvmConnect): def get_disk_devices(self): def disks(doc): result = [] - dev = volume = storage = src_file = None + dev = volume = storage = src_file = bus = None disk_format = used_size = disk_size = disk_cache = None for disk in doc.xpath('/domain/devices/disk'): @@ -288,7 +288,7 @@ class wvmInstance(wvmConnect): def get_media_devices(self): def disks(doc): result = [] - dev = volume = storage = None + dev = volume = storage = bus = None src_file = None for media in doc.xpath('/domain/devices/disk'): device = media.xpath('@device')[0] @@ -341,6 +341,7 @@ class wvmInstance(wvmConnect): def get_bootorder(self): boot_order = {} + type = target = None tree = ElementTree.fromstring(self._XMLDesc(0)) os = tree.find('os') boot = os.findall('boot') @@ -363,7 +364,7 @@ class wvmInstance(wvmConnect): devices = tree.find('devices') for dev in devices: - dev_target = dev_type = dev_device = dev_alias = None + dev_target = None boot_dev = dev.find('boot') if boot_dev is not None: idx = boot_dev.get('order') @@ -439,6 +440,7 @@ class wvmInstance(wvmConnect): disk.insert(2, src_media) return True + vol = None storages = self.get_storages(only_actives=True) for storage in storages: stg = self.get_storage(storage) From 0738ec7ec48710c34bd1a525b270ee2153dcaa07 Mon Sep 17 00:00:00 2001 From: catborise <catborise@yahoo.com> Date: Mon, 4 Nov 2019 12:03:13 +0300 Subject: [PATCH 4/5] adds bridge slave list to details of interface --- interfaces/templates/interface.html | 34 ++++++++++++++++++++++++++--- interfaces/views.py | 1 + vrtManager/interface.py | 30 +++++++++++++++++++++++-- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/interfaces/templates/interface.html b/interfaces/templates/interface.html index 1693d81..2ac89d9 100644 --- a/interfaces/templates/interface.html +++ b/interfaces/templates/interface.html @@ -38,12 +38,12 @@ <div class="row"> <div class="col-xs-6 col-sm-4"> <p>{% trans "Interface" %}:</p> - <p>{% trans "IPv4" %}: ({% ifequal ipv4 None %}{% trans 'None' %}{% else %}{{ ipv4_type }}{% endifequal %})</p> - <p>{% trans "IPv6" %}: ({% ifequal ipv6 None %}{% trans 'None' %}{% else %}{{ ipv6_type }}{% endifequal %})</p> + <p>{% trans "IPv4" %} ({% ifequal ipv4 None %}{% trans 'None' %}{% else %}{{ ipv4_type }}{% endifequal %}):</p> + <p>{% trans "IPv6" %} ({% ifequal ipv6 None %}{% trans 'None' %}{% else %}{{ ipv6_type }}{% endifequal %}):</p> <p>{% trans "MAC Adress" %}:</p> <p>{% trans "Interface Type" %}:</p> {% ifequal itype 'bridge' %} - <p>{% trans "Bridge device" %}</p> + <p>{% trans "Bridge Device" %}:</p> {% endifequal %} <p>{% trans "Boot Mode" %}:</p> <p>{% trans "State" %}:</p> @@ -69,5 +69,33 @@ </form> </p> </div> + <div class="col-sm-12"> + {% ifequal itype 'bridge' %} + <table class="table table-bordered"> + <caption>{% trans 'Slaves' %}</caption> + <thead> + <tr class="active"> + <td>{% trans 'MAC' %}</td> + <td>{% trans 'Name' %}</td> + <td>{% trans 'Type' %}</td> + <td>{% trans 'Speed' %}</td> + <td>{% trans 'State' %}</td> + </tr> + </thead> + <tbody> + {% for iface in slave_ifaces %} + <tr> + <td>{{ iface.mac }}</td> + <td>{{ iface.name }}</td> + <td>{{ iface.type }}</td> + <td>{{ iface.speed }}</td> + <td>{{ iface.state }}</td> + </tr> + {% endfor %} + </tbody> + </table> + {% endifequal %} + </div> </div> + {% endblock %} \ No newline at end of file diff --git a/interfaces/views.py b/interfaces/views.py index acbf440..a747120 100644 --- a/interfaces/views.py +++ b/interfaces/views.py @@ -88,6 +88,7 @@ def interface(request, compute_id, iface): ipv6 = conn.get_ipv6() ipv6_type = conn.get_ipv6_type() bridge = conn.get_bridge() + slave_ifaces = conn.get_bridge_slave_ifaces() if request.method == 'POST': if 'stop' in request.POST: diff --git a/vrtManager/interface.py b/vrtManager/interface.py index c91aaf9..ceaea18 100644 --- a/vrtManager/interface.py +++ b/vrtManager/interface.py @@ -1,5 +1,6 @@ from vrtManager.connection import wvmConnect from vrtManager import util +from xml.etree import ElementTree from libvirt import VIR_INTERFACE_XML_INACTIVE @@ -119,9 +120,34 @@ class wvmInterface(wvmConnect): return int_ipv6_ip + '/' + int_ipv6_mask def get_bridge(self): + bridge = None if self.get_type() == 'bridge': - xml = self._XMLDesc() - return util.get_xml_path(xml, "/interface/bridge/interface/@name") + bridge = util.get_xml_path(self._XMLDesc(), "/interface/bridge/interface/@name") + for iface in self.get_bridge_slave_ifaces(): + if iface.get('state') == 'up' and iface.get('speed') is not 'unknown': + bridge = iface.get('name') + return bridge + return bridge + else: + return None + + def get_bridge_slave_ifaces(self): + ifaces = list() + if self.get_type() == 'bridge': + tree = ElementTree.fromstring(self._XMLDesc()) + for iface in tree.findall("./bridge/"): + address = state = speed = None + name = iface.get('name') + type = iface.get('type') + link = iface.find('link') + if link is not None: + state = link.get('state') + speed = link.get('speed') + mac = iface.find('mac') + if mac is not None: + address = mac.get('address') + ifaces.append({'name': name, 'type': type, 'state': state, 'speed': speed, 'mac': address}) + return ifaces else: return None From c5a96b7662e21ca21fc058e4809e6a6875b9087d Mon Sep 17 00:00:00 2001 From: catborise <catborise@yahoo.com> Date: Thu, 7 Nov 2019 10:33:36 +0300 Subject: [PATCH 5/5] Add IPv6 support. --- ...ss.html => modify_ipv4_fixed_address.html} | 21 +- .../templates/modify_ipv6_fixed_address.html | 53 +++++ networks/templates/network.html | 204 +++++++++++++++--- networks/views.py | 47 ++-- vrtManager/network.py | 194 ++++++++++------- 5 files changed, 391 insertions(+), 128 deletions(-) rename networks/templates/{modify_fixed_address.html => modify_ipv4_fixed_address.html} (85%) create mode 100644 networks/templates/modify_ipv6_fixed_address.html diff --git a/networks/templates/modify_fixed_address.html b/networks/templates/modify_ipv4_fixed_address.html similarity index 85% rename from networks/templates/modify_fixed_address.html rename to networks/templates/modify_ipv4_fixed_address.html index fd1c938..7c1addb 100644 --- a/networks/templates/modify_fixed_address.html +++ b/networks/templates/modify_ipv4_fixed_address.html @@ -1,16 +1,16 @@ {% load i18n %} {% if request.user.is_superuser %} - <a href="#AddFixedNet" type="button" class="btn btn-success pull-right" data-toggle="modal"> + <a href="#AddFixedNet4" 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="AddFixedNet" tabindex="-1" role="dialog" aria-labelledby="AddFixedNetLabel" aria-hidden="true"> + <div class="modal fade" id="AddFixedNet4" tabindex="-1" role="dialog" aria-labelledby="AddFixedNet4Label" 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 Fixed Address" %}</h4> + <h4 class="modal-title">{% trans "Add IPv4 Fixed Address" %}</h4> </div> <form method="post" action="" role="form">{% csrf_token %} <div class="modal-body"> @@ -21,10 +21,16 @@ <input type="text" readonly class="form-control" name="subnet" value="{{ ipv4_network }}" required pattern="[0-9\/\.]+"> </div> </div> + <div class="form-group"> + <label class="col-sm-4 control-label">{% trans "MAC" %}</label> + <div class="col-sm-6"> + <input type="text" class="form-control" name="mac" required pattern="[0-9\/\:]+"> + </div> + </div> <div class="form-group"> <label class="col-sm-4 control-label">{% trans "Name" %}</label> <div class="col-sm-6"> - <input type="text" class="form-control" name="name" required pattern="[a-zA-Z0-9_]+"> + <input type="text" class="form-control" name="name" pattern="[a-zA-Z0-9_]+"> </div> </div> <div class="form-group"> @@ -33,15 +39,10 @@ <input type="text" class="form-control" name="address" required pattern="[0-9\/\.]+"> </div> </div> - <div class="form-group"> - <label class="col-sm-4 control-label">{% trans "MAC" %}</label> - <div class="col-sm-6"> - <input type="text" class="form-control" name="mac" required pattern="[0-9\/\:]+"> - </div> - </div> </div> </div> <div class="modal-footer"> + <input name="family" value="ipv4" hidden/> <button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Close" %}</button> <button type="submit" class="btn btn-primary" name="modify_fixed_address">{% trans "Create" %}</button> </div> diff --git a/networks/templates/modify_ipv6_fixed_address.html b/networks/templates/modify_ipv6_fixed_address.html new file mode 100644 index 0000000..fcc7eff --- /dev/null +++ b/networks/templates/modify_ipv6_fixed_address.html @@ -0,0 +1,53 @@ +{% load i18n %} +{% if request.user.is_superuser %} + <a href="#AddFixedNet6" 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="AddFixedNet6" tabindex="-1" role="dialog" aria-labelledby="AddFixedNet6Label" 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 IPV6 Fixed Address" %}</h4> + </div> + <form method="post" action="" role="form">{% csrf_token %} + <div class="modal-body"> + <div class="row"> + <div class="form-group"> + <label class="col-sm-4 control-label">{% trans "Subnet Pool" %}</label> + <div class="col-sm-6"> + <input type="text" readonly class="form-control" name="subnet" value="{{ ipv6_network }}" required pattern="[0-9\/\.]+"> + </div> + </div> + <div class="form-group"> + <label class="col-sm-4 control-label">{% trans "ID" %}</label> + <div class="col-sm-6"> + <input type="text" class="form-control" name="id" required pattern="[0-9a-dA-D\/\:]+"> + </div> + </div> + <div class="form-group"> + <label class="col-sm-4 control-label">{% trans "Name" %}</label> + <div class="col-sm-6"> + <input type="text" class="form-control" name="name" pattern="[a-zA-Z0-9_]+"> + </div> + </div> + <div class="form-group"> + <label class="col-sm-4 control-label">{% trans "Address" %}</label> + <div class="col-sm-6"> + <input type="text" class="form-control" name="address" required pattern="[a-dA-D0-9\/\:]+"> + </div> + </div> + </div> + </div> + <div class="modal-footer"> + <input name="family" value="ipv6" hidden/> + <button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Close" %}</button> + <button type="submit" class="btn btn-primary" name="modify_fixed_address">{% trans "Create" %}</button> + </div> + </form> + </div> <!-- /.modal-content --> + </div> <!-- /.modal-dialog --> + </div> <!-- /.modal --> +{% endif %} \ No newline at end of file diff --git a/networks/templates/network.html b/networks/templates/network.html index 46c79ab..c5d8cd5 100644 --- a/networks/templates/network.html +++ b/networks/templates/network.html @@ -68,6 +68,7 @@ </p> </div> </div> + <div class="row"> <h3 class="page-header"></h3> </div> @@ -93,31 +94,32 @@ </div> </div> </div> - <div class="row"> + + <div class="row"> <h3 class="page-header">{% trans "IPv4 Configuration" %}</h3> </div> <div class="row"> <div class="col-xs-6 col-sm-4"> - <p>{% trans "IPv4 Forwarding:" %}</p> - <p>{% trans "Network:" %}</p> - <p>{% trans "DHCP:" %}</p> + <p>{% trans "IPv4 Forwarding" %}:</p> + <p>{% trans "Network" %}:</p> {% if ipv4_dhcp_range_start and ipv4_dhcp_range_end %} - <p>{% trans "Start:" %}</p> - <p>{% trans "End:" %}</p> + <p>{% trans "DHCP" %}:</p> + <p>{% trans "Start" %}:</p> + <p>{% trans "End" %}:</p> {% endif %} </div> <div class="col-xs-6 col-sm-4"> <p> - {% ifequal ipv4_forward.0 'nat' %} + {% ifequal net_forward.0 'nat' %} {% trans "NAT" %} {% endifequal %} - {% ifequal ipv4_forward.0 'route' %} + {% ifequal net_forward.0 'route' %} {% trans "ROUTE" %} {% endifequal %} - {% ifequal ipv4_forward.0 'bridge' %} + {% ifequal net_forward.0 'bridge' %} {% trans "BRIDGE" %} {% endifequal %} - {% if not ipv4_forward.0 %} + {% if not net_forward.0 %} {% trans "ISOLATE" %} {% endif %} </p> @@ -137,6 +139,7 @@ {% else %} <p><input name="range_start" value="{{ ipv4_dhcp_range_start }}"/></p> <p><input name="range_end" value="{{ ipv4_dhcp_range_end }}"/></p> + <input hidden name="family" value="ipv4"/> <div class="col-xs-10 col-sm-8"> <input type="submit" class="btn btn-primary btn-block" value="Apply" name="modify_dhcp_range" @@ -147,15 +150,15 @@ {% endif %} </div> </div> - {% ifequal ipv4_forward.0 'nat' %} + {% if ipv4_dhcp_range_start and ipv4_dhcp_range_end %} {% if state %} - {% include 'modify_fixed_address.html' %} + {% include 'modify_ipv4_fixed_address.html' %} {% endif %} <div class="row"> - <h3 class="page-header">{% trans "Fixed Address" %}</h3> + <h3 class="page-header">{% trans "IPv4 Fixed Address" %}</h3> </div> - {% endifequal %} - {% if fixed_address %} + {% endif %} + {% if ipv4_fixed_address %} <div class="row"> <div class="col-xs-12"> <div class="panel-group" id="accordion"> @@ -175,7 +178,7 @@ <input type="button" class="btn btn-default" id="filter_button" value="Filter"> <button type="button" class="btn btn-default" id="filter_clear">{% trans 'Clear' %}</button> </div> - <table class="table table-hover"> + <table id="ipv4_table" class="table table-hover"> <thead> <tr> <th style="text-align: center">{% trans "MAC" %}</th> @@ -185,14 +188,132 @@ </tr> </thead> <tbody style="text-align: center"> - {% for fix in fixed_address %} + {% for fix4 in ipv4_fixed_address %} <tr> <form method="post" role="form">{% csrf_token %} - <td><label class="form-control" disabled="true">{{ fix.mac }}</label></td> - <td><input class="form-control" value="{{ fix.ip }}" name="address" /></td> - <td><input class="form-control" value="{{ fix.name }}" name="name" /></td> + <td><input class="form-control" value="{{ fix4.mac }}" name="mac" readonly/></td> + <td><input class="form-control" value="{{ fix4.ip }}" name="address" /></td> + <td><input class="form-control" value="{{ fix4.name }}" name="name" /></td> <td> - <input hidden name="mac" value="{{ fix.mac }}"/> + <input hidden name="family" value="ipv4"/> + <button type="submit" class="btn btn-sm btn-primary" + name="modify_fixed_address" + title="Edit entry" onclick="return confirm('{% trans "Are you sure?" %}')"> + <i class="glyphicon glyphicon-save"></i> + </button> + <button type="submit" class="btn btn-sm btn-danger" + name="delete_fixed_address" + title="Delete entry" onclick="return confirm('{% trans "Are you sure?" %}')"> + <i class="glyphicon glyphicon-trash"></i> + </button> + </td> + </form> + </tr> + {% endfor %} + </tbody> + </table> + </div> + </div> + </div> + </div> + </div> + </div> + {% endif %} + + <div class="row"> + <h3 class="page-header">{% trans "IPv6 Configuration" %}</h3> + </div> + <div class="row"> + <div class="col-xs-6 col-sm-4"> + <p>{% trans "IPv6 Forwarding" %}:</p> + <p>{% trans "Network" %}:</p> + {% if ipv6_dhcp_range_start and ipv6_dhcp_range_end %} + <p>{% trans "DHCP" %}:</p> + <p>{% trans "Start" %}:</p> + <p>{% trans "End" %}:</p> + {% endif %} + </div> + <div class="col-xs-6 col-sm-4"> + <p> + {% if not net_forward.0 %} + {% trans "ISOLATE" %} + {% else %} + {% trans "ROUTE" %} + {% endif %} + </p> + <p>{{ ipv6_network }}</p> + <p> + {% if ipv6_dhcp_range_start and ipv6_dhcp_range_end %} + <span class="text-success">{% trans "ON" %}</span> + {% else %} + <span class="text-danger">{% trans "OFF" %}</span> + {% endif %} + </p> + {% if ipv6_dhcp_range_start and ipv6_dhcp_range_end %} + <form method="post" role="form">{% csrf_token %} + {% if state %} + <p>{{ ipv6_dhcp_range_start }}</p> + <p>{{ ipv6_dhcp_range_end }}</p> + {% else %} + <p><input name="range_start" value="{{ ipv6_dhcp_range_start }}"/></p> + <p><input name="range_end" value="{{ ipv6_dhcp_range_end }}"/></p> + <input hidden name="family" value="ipv6"/> + <div class="col-xs-10 col-sm-8"> + <input type="submit" class="btn btn-primary btn-block" value="Apply" + name="modify_dhcp_range" + title="Edit DHCP Range" onclick="return confirm('{% trans "Are you sure?" %}')"/> + </div> + {% endif %} + </form> + {% endif %} + </div> + </div> + {% if ipv6_dhcp_range_start and ipv6_dhcp_range_end %} + {% if state %} + {% include 'modify_ipv6_fixed_address.html' %} + {% endif %} + <div class="row"> + <h3 class="page-header">{% trans "IPv6 Fixed Address" %}</h3> + </div> + {% endif %} + {% if ipv6_fixed_address %} + <div class="row"> + <div class="col-xs-12"> + <div class="panel-group" id="accordion"> + <div class="panel panel-default"> + <div class="panel-heading"> + <a data-toggle="collapse" data-parent="#accordion" href="#collapseIPv6"> + {% trans 'Show' %} + </a> + </div> + <div id="collapseIPv6" 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_ipv6"> + </div> + <input type="button" class="btn btn-default" id="filter_button_ipv6" value="Filter"> + <button type="button" class="btn btn-default" id="filter_clear_ipv6">{% trans 'Clear' %}</button> + </div> + <table id="ipv6_table" class="table table-hover"> + <thead> + <tr> + <th style="text-align: center">{% trans "ID" %}</th> + <th style="text-align: center">{% trans "Address" %}</th> + <th style="text-align: center">{% trans "Name" %}</th> + <th style="text-align: center">{% trans "Action" %}</th> + </tr> + </thead> + <tbody style="text-align: center"> + {% for fix6 in ipv6_fixed_address %} + <tr> + <form method="post" role="form">{% csrf_token %} + <td><input class="form-control" value="{{ fix6.id }}" name="id" readonly/></td> + <td><input class="form-control" value="{{ fix6.ip }}" name="address" /></td> + <td><input class="form-control" value="{{ fix6.name }}" name="name" /></td> + <td> + <input hidden name="family" value="ipv6"/> <button type="submit" class="btn btn-sm btn-primary" name="modify_fixed_address" title="Edit entry" onclick="return confirm('{% trans "Are you sure?" %}')"> @@ -229,15 +350,17 @@ // add event button labeled "filter" $('#filter_button').click(function (event) { // get value - var filter_val = $('#filter_input').val(); + let filter_val = $('#filter_input').val(); if (filter_val == '') { // show all - $('tbody tr').show(); + $('#ipv4_table tbody tr').show(); } else { - // show only matches - $('tbody tr:Contains(\'' + filter_val + '\')').show(); // hide non-matching items - $('tbody tr:not(:Contains(\'' + filter_val + '\'))').hide(); + let row_not_contains4 = $('#ipv4_table tbody tr input:not([value*=\'' + filter_val + '\'])'); + row_not_contains4.closest('tr').hide(); + // show only matches + let row_contains4 = $('#ipv4_table tbody tr input[value*=\'' + filter_val + '\']'); + row_contains4.closest('tr').show(); } }); // add event button labeled "clear" @@ -252,6 +375,35 @@ $('#filter_button').click(); } }); + + // add event button labeled "filter" + $('#filter_button_ipv6').click(function (event) { + // get value + let filter_val = $('#filter_input_ipv6').val(); + if (filter_val == '') { + // show all + $('#ipv6_table tbody tr').show(); + } else { + // hide non-matching items + let row_not_contains6 = $('#ipv6_table tbody tr input:not([value*=\'' + filter_val + '\'])'); + row_not_contains6.closest('tr').hide(); + // show only matches + let row_contains6 = $('#ipv6_table tbody tr input[value*=\'' + filter_val + '\']'); + row_contains6.closest('tr').show(); + } + }); + // add event button labeled "clear" + $('#filter_clear_ipv6').click(function (event) { + $('#filter_input_ipv6').val(''); + $('#filter_button_ipv6').click(); + }); + + // trigger filter when enter key pressed + $('#filter_input_ipv6').keyup(function (event) { + if (event.keyCode == 13) { + $('#filter_button_ipv6').click(); + } + }); }); </script> <script src="{% static "js/ace.js" %}"></script> diff --git a/networks/views.py b/networks/views.py index 0bc9c1d..427248a 100644 --- a/networks/views.py +++ b/networks/views.py @@ -86,11 +86,24 @@ def network(request, compute_id, pool): state = conn.is_active() device = conn.get_bridge_device() autostart = conn.get_autostart() - ipv4_forward = conn.get_ipv4_forward() - ipv4_dhcp_range_start = conn.get_ipv4_dhcp_range_start() - ipv4_dhcp_range_end = conn.get_ipv4_dhcp_range_end() - ipv4_network = conn.get_ipv4_network() - fixed_address = conn.get_mac_ipaddr() + net_forward = conn.get_network_forward() + dhcp_range_start = ipv4_dhcp_range_end = dict() + + ip_networks = conn.get_ip_networks() + for family, ip_network in ip_networks.items(): + if family == "ipv4": + ipv4_dhcp_range_start = conn.get_dhcp_range_start(family) + ipv4_dhcp_range_end = conn.get_dhcp_range_end(family) + ipv4_network = ip_network + ipv4_fixed_address = conn.get_dhcp_host_addr(family) + elif family == "ipv6": + ipv6_dhcp_range_start = conn.get_dhcp_range_start(family) + ipv6_dhcp_range_end = conn.get_dhcp_range_end(family) + ipv6_network = ip_network + ipv6_fixed_address = conn.get_dhcp_host_addr(family) + else: + raise Exception("Unknown Network Family") + xml = conn._XMLDesc(0) except libvirtError as lib_err: error_messages.append(lib_err) @@ -130,26 +143,34 @@ def network(request, compute_id, pool): if 'modify_fixed_address' in request.POST: name = request.POST.get('name', '') address = request.POST.get('address', '') - mac = request.POST.get('mac', '') + family = request.POST.get('family', 'ipv4') + + if family == 'ipv4': + mac_duid = request.POST.get('mac', '') + if family == 'ipv6': + mac_duid = request.POST.get('id', '') + try: - ret_val = conn.modify_fixed_address(name, address, mac) - messages.success(request, "Fixed Address Operation Completed.") + ret_val = conn.modify_fixed_address(name, address, mac_duid, family) + messages.success(request, "{} Fixed Address Operation Completed.".format(family)) return HttpResponseRedirect(request.get_full_path()) except libvirtError as lib_err: error_messages.append(lib_err.message) except ValueError as val_err: error_messages.append(val_err.message) if 'delete_fixed_address' in request.POST: - mac = request.POST.get('mac', '') - conn.delete_fixed_address(mac) - messages.success(request, "Fixed Address is Deleted.") + ip = request.POST.get('address', '') + family = request.POST.get('family', 'ipv4') + conn.delete_fixed_address(ip, family) + messages.success(request, "{} Fixed Address is Deleted.".format(family)) return HttpResponseRedirect(request.get_full_path()) if 'modify_dhcp_range' in request.POST: range_start = request.POST.get('range_start', '') range_end = request.POST.get('range_end', '') + family = request.POST.get('family', 'ipv4') try: - conn.modify_dhcp_range(range_start, range_end) - messages.success(request, "DHCP Range is Changed.") + conn.modify_dhcp_range(range_start, range_end, family) + messages.success(request, "{} DHCP Range is Changed.".format(family)) return HttpResponseRedirect(request.get_full_path()) except libvirtError as lib_err: error_messages.append(lib_err.message) diff --git a/vrtManager/network.py b/vrtManager/network.py index 971cc66..ded82c3 100644 --- a/vrtManager/network.py +++ b/vrtManager/network.py @@ -1,7 +1,7 @@ from vrtManager import util from vrtManager.IPy import IP from vrtManager.connection import wvmConnect -from xml.etree import ElementTree +from lxml import etree from libvirt import VIR_NETWORK_SECTION_IP_DHCP_HOST from libvirt import VIR_NETWORK_UPDATE_COMMAND_ADD_LAST, VIR_NETWORK_UPDATE_COMMAND_DELETE, VIR_NETWORK_UPDATE_COMMAND_MODIFY from libvirt import VIR_NETWORK_UPDATE_AFFECT_LIVE, VIR_NETWORK_UPDATE_AFFECT_CONFIG @@ -77,6 +77,7 @@ class wvmNetwork(wvmConnect): def __init__(self, host, login, passwd, conn, net): wvmConnect.__init__(self, host, login, passwd, conn) self.net = self.get_network(net) + self.parent_count = len(self.get_ip_networks()) def get_name(self): return self.net.name() @@ -111,128 +112,163 @@ class wvmNetwork(wvmConnect): def delete(self): self.net.undefine() - def update(self, command, section, parentIndex, xml, flags=0): + def update(self, command, section, xml, parentIndex, flags=0): return self.net.update(command, section, parentIndex, xml, flags) - def get_ipv4_network(self): + def get_ip_networks(self): + ip_networks = dict() xml = self._XMLDesc(0) if util.get_xml_path(xml, "/network/ip") is None: return None - addrStr = util.get_xml_path(xml, "/network/ip/@address") - netmaskStr = util.get_xml_path(xml, "/network/ip/@netmask") - prefix = util.get_xml_path(xml, "/network/ip/@prefix") + tree = etree.fromstring(xml) + ips = tree.findall('.ip') + for ip in ips: + address_str = ip.get('address') + netmask_str = ip.get('netmask') + prefix = ip.get('prefix') + family = ip.get('family', 'ipv4') + base = 32 if family == 'ipv4' else 128 + if prefix: + prefix = int(prefix) + binstr = ((prefix * "1") + ((base - prefix) * "0")) + netmask_str = str(IP(int(binstr, base=2))) - if prefix: - prefix = int(prefix) - binstr = ((prefix * "1") + ((32 - prefix) * "0")) - netmaskStr = str(IP(int(binstr, base=2))) + if netmask_str: + netmask = IP(netmask_str) + gateway = IP(address_str) + network = IP(gateway.int() & netmask.int()) + netmask_str = netmask_str if family == 'ipv4' else str(prefix) + ret = IP(str(network) + "/" + netmask_str) + else: + ret = IP(str(address_str)) + ip_networks[family] = ret + return ip_networks - if netmaskStr: - netmask = IP(netmaskStr) - gateway = IP(addrStr) - network = IP(gateway.int() & netmask.int()) - ret = IP(str(network) + "/" + netmaskStr) - else: - ret = IP(str(addrStr)) - - return ret - - def get_ipv4_forward(self): + def get_network_forward(self): xml = self._XMLDesc(0) fw = util.get_xml_path(xml, "/network/forward/@mode") - forwardDev = util.get_xml_path(xml, "/network/forward/@dev") - return [fw, forwardDev] + forward_dev = util.get_xml_path(xml, "/network/forward/@dev") + return [fw, forward_dev] - def get_ipv4_dhcp_range(self): + def get_dhcp_range(self, family='ipv4'): xml = self._XMLDesc(0) - dhcpstart = util.get_xml_path(xml, "/network/ip/dhcp/range[1]/@start") - dhcpend = util.get_xml_path(xml, "/network/ip/dhcp/range[1]/@end") + if family == 'ipv4': + dhcpstart = util.get_xml_path(xml, "/network/ip[not(@family='ipv6')]/dhcp/range[1]/@start") + dhcpend = util.get_xml_path(xml, "/network/ip[not(@family='ipv6')]/dhcp/range[1]/@end") + if family == 'ipv6': + dhcpstart = util.get_xml_path(xml, "/network/ip[@family='ipv6']/dhcp/range[1]/@start") + dhcpend = util.get_xml_path(xml, "/network/ip[@family='ipv6']/dhcp/range[1]/@end") + if not dhcpstart or not dhcpend: return None return [IP(dhcpstart), IP(dhcpend)] - def get_ipv4_dhcp_range_start(self): - dhcp = self.get_ipv4_dhcp_range() + def get_dhcp_range_start(self, family='ipv4'): + dhcp = self.get_dhcp_range(family) if not dhcp: return None - return dhcp[0] - def get_ipv4_dhcp_range_end(self): - dhcp = self.get_ipv4_dhcp_range() + def get_dhcp_range_end(self, family='ipv4'): + dhcp = self.get_dhcp_range(family) if not dhcp: return None - return dhcp[1] def can_pxe(self): xml = self._XMLDesc(0) - forward = self.get_ipv4_forward()[0] + forward = self.get_network_forward()[0] if forward and forward != "nat": return True return bool(util.get_xml_path(xml, "/network/ip/dhcp/bootp/@file")) - def get_mac_ipaddr(self): - def network(doc): - result = [] - for net in doc.xpath('/network/ip/dhcp/host'): - ip = net.xpath('@ip')[0] - mac = net.xpath('@mac')[0] - name = net.xpath('@name') - name = name[0] if name else "" + def get_dhcp_host_addr(self, family='ipv4'): + result = list() + tree = etree.fromstring(self._XMLDesc(0)) - result.append({'ip': ip, 'mac': mac, 'name': name}) - return result + for ipdhcp in tree.findall("./ip"): + if family == 'ipv4': + if ipdhcp.get('family') is None: + hosts = ipdhcp.findall('./dhcp/host') + for host in hosts: + ip = host.get('ip') + mac = host.get('mac') + name = host.get('name','') + result.append({'ip': ip, 'mac': mac, 'name': name}) + return result + else: + continue + if family == 'ipv6': + hosts = tree.xpath("./ip[@family='ipv6']/dhcp/host") + for host in hosts: + ip = host.get('ip') + id = host.get('id') + name = host.get('name','') + result.append({'ip': ip, 'id': id, 'name': name}) + return result - return util.get_xml_path(self._XMLDesc(0), func=network) - - def modify_fixed_address(self, name, address, mac): - util.validate_macaddr(mac) - if name: - new_xml = '<host mac="{}" name="{}" ip="{}"/>'.format(mac, name, IP(address)) - else: - new_xml = '<host mac="{}" ip="{}"/>'.format(mac, IP(address)) - new_host_xml = ElementTree.fromstring(new_xml) + def modify_dhcp_range(self, range_start, range_end, family='ipv4'): + if not self.is_active(): + tree = etree.fromstring(self._XMLDesc(0)) + if family == 'ipv4': + range = tree.xpath("./ip[not(@family='ipv6')]/dhcp/range") + if family == 'ipv6': + range = tree.xpath("./ip[@family='ipv6']/dhcp/range") + range[0].set('start', range_start) + range[0].set('end', range_end) + self.wvm.networkDefineXML(etree.tostring(tree)) - tree = ElementTree.fromstring(self._XMLDesc(0)) - hosts = tree.findall("./ip/dhcp/host") + def delete_fixed_address(self, ip, family='ipv4'): + tree = etree.fromstring(self._XMLDesc(0)) + if family == 'ipv4': + hosts = tree.xpath("/network/ip[not(@family='ipv6')]/dhcp/host") + parent_index = self.parent_count - 2 + if family == 'ipv6': + hosts = tree.xpath("/network/ip[@family='ipv6']/dhcp/host") + parent_index = self.parent_count - 1 + for h in hosts: + if h.get('ip') == ip: + if family == 'ipv4': + new_xml = '<host mac="{}" name="{}" ip="{}"/>'.format(h.get('mac'), h.get('name'), ip) + if family == 'ipv6': + new_xml = '<host id="{}" name="{}" ip="{}"/>'.format(h.get('id'), h.get('name'), ip) + + self.update(VIR_NETWORK_UPDATE_COMMAND_DELETE, VIR_NETWORK_SECTION_IP_DHCP_HOST, + new_xml, + parent_index, + VIR_NETWORK_UPDATE_AFFECT_LIVE | VIR_NETWORK_UPDATE_AFFECT_CONFIG) + break + + def modify_fixed_address(self, name, address, mac_duid, family='ipv4'): + tree = etree.fromstring(self._XMLDesc(0)) + if family == 'ipv4': + new_xml = '<host mac="{}" {} ip="{}"/>'.format(mac_duid, 'name="' + name + '"' if name else '', IP(address)) + hosts = tree.xpath("./ip[not(@family='ipv6')]/dhcp/host") + compare_var = 'mac' + parent_index = self.parent_count - 2 + if family == 'ipv6': + new_xml = '<host id="{}" {} ip="{}"/>'.format(mac_duid, 'name="' + name + '"' if name else '', IP(address)) + hosts = tree.xpath("./ip[@family='ipv6']/dhcp/host") + compare_var = 'id' + parent_index = self.parent_count - 1 + new_host_xml = etree.fromstring(new_xml) host = None for h in hosts: - if h.get('mac') == mac: + if h.get(compare_var) == mac_duid: host = h break if host is None: - self.update(VIR_NETWORK_UPDATE_COMMAND_ADD_LAST, VIR_NETWORK_SECTION_IP_DHCP_HOST, -1, new_xml, + self.update(VIR_NETWORK_UPDATE_COMMAND_ADD_LAST, VIR_NETWORK_SECTION_IP_DHCP_HOST, new_xml, + parent_index, VIR_NETWORK_UPDATE_AFFECT_LIVE | VIR_NETWORK_UPDATE_AFFECT_CONFIG) else: # change the host if host.get('name') == new_host_xml.get('name') and host.get('ip') == new_host_xml.get('ip'): return False else: - self.update(VIR_NETWORK_UPDATE_COMMAND_MODIFY, VIR_NETWORK_SECTION_IP_DHCP_HOST, -1, new_xml, + self.update(VIR_NETWORK_UPDATE_COMMAND_MODIFY, VIR_NETWORK_SECTION_IP_DHCP_HOST, new_xml, + parent_index, VIR_NETWORK_UPDATE_AFFECT_LIVE | VIR_NETWORK_UPDATE_AFFECT_CONFIG) - def delete_fixed_address(self, mac): - util.validate_macaddr(mac) - tree = ElementTree.fromstring(self._XMLDesc(0)) - hosts = tree.findall("./ip/dhcp/host") - - for h in hosts: - if h.get('mac') == mac: - new_xml = '<host mac="{}" name="{}" ip="{}"/>'.format(mac, h.get('name'), h.get('ip')) - self.update(VIR_NETWORK_UPDATE_COMMAND_DELETE, VIR_NETWORK_SECTION_IP_DHCP_HOST, -1, new_xml, - VIR_NETWORK_UPDATE_AFFECT_LIVE | VIR_NETWORK_UPDATE_AFFECT_CONFIG) - break - - def modify_dhcp_range(self, range_start, range_end): - if not self.is_active(): - new_range = '<range start="{}" end="{}"/>'.format(range_start, range_end) - tree = ElementTree.fromstring(self._XMLDesc(0)) - dhcp = tree.find("./ip/dhcp") - old_range = dhcp.find('range') - dhcp.remove(old_range) - dhcp.append(ElementTree.fromstring(new_range)) - - self.wvm.networkDefineXML(ElementTree.tostring(tree))