From a7834f5d6c32ee6c086187748dc2c5baa11b8fe7 Mon Sep 17 00:00:00 2001 From: Cole Robinson Date: Fri, 20 Sep 2013 20:40:07 -0400 Subject: [PATCH] Add Network class for parsing XML Convert virtManager/host.py, do some cleanups and modernization to the UI there, add tests. createnet.py hasn't really been touched yet, but still works because it was building the XML by hand anyways. --- tests/testdriver.xml | 2 +- tests/xmlparse-xml/network-multi-in.xml | 25 + tests/xmlparse-xml/network-multi-out.xml | 29 + tests/xmlparse.py | 50 ++ ui/vmm-host.ui | 831 ++++++++++------------- virtManager/createnet.py | 6 +- virtManager/host.py | 168 ++--- virtManager/network.py | 342 +++------- virtinst/__init__.py | 3 +- virtinst/network.py | 161 +++++ 10 files changed, 816 insertions(+), 801 deletions(-) create mode 100644 tests/xmlparse-xml/network-multi-in.xml create mode 100644 tests/xmlparse-xml/network-multi-out.xml create mode 100644 virtinst/network.py diff --git a/tests/testdriver.xml b/tests/testdriver.xml index cd70ad82..444391da 100644 --- a/tests/testdriver.xml +++ b/tests/testdriver.xml @@ -603,7 +603,7 @@ ipv4_prefix - 81ff0d90-c91e-6742-64da-4a736edb9a9b + 81ff0d90-c91e-6742-64da-4a736edb9122 diff --git a/tests/xmlparse-xml/network-multi-in.xml b/tests/xmlparse-xml/network-multi-in.xml new file mode 100644 index 00000000..a1e645fb --- /dev/null +++ b/tests/xmlparse-xml/network-multi-in.xml @@ -0,0 +1,25 @@ + + ipv6_multirange + 41b4afe4-87bb-8087-6724-5e208a2d483a + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/xmlparse-xml/network-multi-out.xml b/tests/xmlparse-xml/network-multi-out.xml new file mode 100644 index 00000000..51ac06a1 --- /dev/null +++ b/tests/xmlparse-xml/network-multi-out.xml @@ -0,0 +1,29 @@ + + new-foo + 41b4afe4-87bb-8087-6724-5e208a2d1111 + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/xmlparse.py b/tests/xmlparse.py index dd0e7d87..4cae880c 100644 --- a/tests/xmlparse.py +++ b/tests/xmlparse.py @@ -994,6 +994,56 @@ class XMLParseTest(unittest.TestCase): utils.diff_compare(vol.get_xml_config(), outfile) + ################### + # tests # + ################### + + def testNetMulti(self): + basename = "network-multi" + infile = "tests/xmlparse-xml/%s-in.xml" % basename + outfile = "tests/xmlparse-xml/%s-out.xml" % basename + net = virtinst.Network(conn, parsexml=file(infile).read()) + + check = self._make_checker(net) + check("name", "ipv6_multirange", "new-foo") + check("uuid", "41b4afe4-87bb-8087-6724-5e208a2d483a", + "41b4afe4-87bb-8087-6724-5e208a2d1111") + check("bridge", "virbr3", "virbr3new") + check("stp", True, False) + check("delay", 0, 2) + check("domain_name", "net7", "newdom") + check("ipv6", None, True) + check("macaddr", None, "52:54:00:69:eb:FF") + + check = self._make_checker(net.forward) + check("mode", "nat", "route") + check("dev", None, "eth22") + + self.assertEqual(len(net.ips), 4) + check = self._make_checker(net.ips[0]) + check("address", "192.168.7.1", "192.168.8.1") + check("netmask", "255.255.255.0", "255.255.254.0") + check("tftp", None, "/var/lib/tftproot") + check("bootp_file", None, "pxeboot.img") + check("bootp_server", None, "1.2.3.4") + + check = self._make_checker(net.ips[0].ranges[0]) + check("start", "192.168.7.128", "192.168.8.128") + check("end", "192.168.7.254", "192.168.8.254") + + check = self._make_checker(net.ips[0].hosts[1]) + check("macaddr", "52:54:00:69:eb:91", "52:54:00:69:eb:92") + check("name", "badbob", "newname") + check("ip", "192.168.7.3", "192.168.8.3") + + check = self._make_checker(net.ips[1]) + check("family", "ipv6", "ipv6") + check("prefix", 64, 63) + + net.add_route("192.168.8.0", "24", "192.168.8.10") + + utils.diff_compare(net.get_xml_config(), outfile) + utils.test_create(conn, net.get_xml_config(), "networkDefineXML") ############## diff --git a/ui/vmm-host.ui b/ui/vmm-host.ui index c5a9151b..40986847 100644 --- a/ui/vmm-host.ui +++ b/ui/vmm-host.ui @@ -1,7 +1,6 @@ - True @@ -330,6 +329,7 @@ True False True + 0.5 True @@ -496,6 +496,9 @@ True True False + + + @@ -510,147 +513,200 @@ True False - + True False - 10 + 6 - + True False - 0 + 12 - + True False - 0 - none + 5 + 6 - + + True + False + 3 + + + True + False + gtk-missing-image + + + False + True + 0 + + + + + True + False + 0 + Running + + + True + True + 1 + + + + + 1 + 2 + 1 + 1 + + + + + Some Label + True + True + False + True + 0 + True + + + + 1 + 3 + 1 + 1 + + + + + True + False + 0 + label + True + + + 1 + 1 + 1 + 1 + + + + + True + False + 0 + label + + + 1 + 4 + 1 + 1 + + + + + True + False + 0 + Device: + + + 0 + 1 + 1 + 1 + + + + + True + False + 0 + State: + + + 0 + 2 + 1 + 1 + + + + + True + False + 0 + A_utostart: + True + net-autostart + + + 0 + 3 + 1 + 1 + + + + + True + False + 0 + DNS Domain: + True + + + 0 + 4 + 1 + 1 + + + + + True + False + 0 + <b>Name</b> + True + True + + + 0 + 0 + 2 + 1 + + + + + False + True + 0 + + + + + True + True + + True False - 6 - 7 - 2 - 6 5 + 6 - - True - False - 0 - Name: - - - GTK_FILL - - - - - - True - False - 0 - Device: - - - 1 - 2 - GTK_FILL - - - - - - True - False - 0 - State: - - - 2 - 3 - GTK_FILL - - - - - - True - False - 3 - - - True - False - gtk-missing-image - - - False - True - 0 - - - - - True - False - 0 - Running - - - True - True - 1 - - - - - 1 - 2 - 2 - 3 - GTK_FILL - - - - - - True - False - 0 - A_utostart: - True - net-autostart - - - 3 - 4 - GTK_FILL - - - - - - Some Label - True - True - False - True - True - - - - 1 - 2 - 3 - 4 - GTK_FILL - - - - - + True False 0 @@ -659,12 +715,13 @@ 1 - 2 - + 0 + 1 + 1 - + True False 0 @@ -673,53 +730,23 @@ 1 - 2 1 - 2 - + 1 + 1 - - True - False - 0 - DNS Domain Name: - True - - - 4 - 5 - GTK_FILL - - - - - - True - False - 0 - IPv4 Forwarding: - - - 5 - 6 - GTK_FILL - - - - - + True False 0 + <network-addr> via <gateway-addr> 1 - 2 - 4 - 5 - + 3 + 1 + 1 @@ -728,7 +755,7 @@ False 3 - + True False gtk-missing-image @@ -740,7 +767,7 @@ - + True False 0 @@ -755,25 +782,137 @@ 1 - 2 - 5 - 6 - GTK_FILL - + 2 + 1 + 1 - + True False 0 - IPv6 Forwarding: + Network: - 6 - 7 - GTK_FILL - + 0 + 0 + 1 + 1 + + + + + True + False + 0 + DHCP range: + + + 0 + 1 + 1 + 1 + + + + + True + False + 0 + Forwarding: + + + 0 + 2 + 1 + 1 + + + + + True + False + 0 + Static Route: + + + 0 + 3 + 1 + 1 + + + + + + + True + False + <b>IPv4 configuration</b> + True + + + + + False + True + 1 + + + + + True + True + + + True + False + 5 + 6 + + + True + False + 0 + label + True + + + 1 + 0 + 1 + 1 + + + + + True + False + 0 + label + True + + + 1 + 1 + 1 + 1 + + + + + True + False + 0 + <network-addr> via <gateway-addr> + True + + + 1 + 3 + 1 + 1 @@ -782,7 +921,7 @@ False 3 - + True False gtk-missing-image @@ -794,7 +933,7 @@ - + True False 0 @@ -809,203 +948,11 @@ 1 - 2 - 6 - 7 - GTK_FILL - - - - - - - - True - False - 0 - <b>Basic details</b> - True - - - - - - - False - True - 0 - - - - - True - False - - - True - False - 0 - none - - - True - False - 6 - 4 - 2 - 6 - 5 - - - True - False - 0 - Network: - - - GTK_FILL - - - - - - True - False - 0 - DHCP start: - - - 1 - 2 - GTK_FILL - - - - - - True - False - 0 - DHCP end: - - 2 - 3 - GTK_FILL - + 1 + 1 - - - True - False - 0 - label - True - - - 1 - 2 - GTK_FILL - - - - - True - False - 0 - label - True - - - 1 - 2 - 1 - 2 - GTK_FILL - - - - - True - False - 0 - label - True - - - 1 - 2 - 2 - 3 - GTK_FILL - - - - - True - False - 0 - Static Route: - - - 3 - 4 - - - - - True - False - 0 - <network-addr> via <gateway-addr> - - - 1 - 2 - 3 - 4 - GTK_FILL - - - - - - - True - False - <b>IPv4 configuration</b> - True - - - - - - - False - True - 1 - - - - - True - False - - - True - False - 0 - none - - - True - False - 6 - 4 - 2 - 6 - 5 True @@ -1014,8 +961,10 @@ Network: - GTK_FILL - + 0 + 0 + 1 + 1 @@ -1023,103 +972,41 @@ True False 0 - DHCP start: + DHCP range: + 0 1 - 2 - GTK_FILL - + 1 + 1 - + True False 0 - DHCP end: + Forwarding: + 0 2 - 3 - GTK_FILL - + 1 + 1 - + True False 0 Static Route: + 0 3 - 4 - GTK_FILL - - - - - - True - False - 0 - label - True - - - 1 - 2 - GTK_FILL - - - - - True - False - 0 - label - True - - - 1 - 2 - 1 - 2 - GTK_FILL - - - - - True - False - 0 - label - True - - - 1 - 2 - 2 - 3 - GTK_FILL - - - - - True - False - 0 - <network-addr> via <gateway-addr> - True - - - 1 - 2 - 3 - 4 - GTK_FILL + 1 + 1 @@ -1133,24 +1020,13 @@ + + False + True + 2 + - - False - True - 2 - - - - - True - False - - - False - True - 3 - @@ -1374,6 +1250,9 @@ True True False + + + @@ -1555,6 +1434,7 @@ True False True + 0.5 True @@ -1653,6 +1533,9 @@ True True + + + @@ -1920,6 +1803,9 @@ True True False + + + @@ -2337,6 +2223,9 @@ True True + + + diff --git a/virtManager/createnet.py b/virtManager/createnet.py index 7364b2c0..8bd847eb 100644 --- a/virtManager/createnet.py +++ b/virtManager/createnet.py @@ -28,7 +28,8 @@ from gi.repository import Gtk from gi.repository import Gdk # pylint: enable=E0611 -from virtManager.network import vmmNetwork +from virtinst import Network + from virtManager.baseclass import vmmGObjectUI PAGE_INTRO = 0 @@ -751,9 +752,8 @@ class vmmCreateNetwork(vmmGObjectUI): self.widget("summary-routev4-network").hide() self.widget("summary-routev4-gateway").hide() - forward_txt = "" dev, mode = self.get_config_forwarding() - forward_txt = vmmNetwork.pretty_desc(mode, dev) + forward_txt = Network.pretty_forward_desc(mode, dev) self.widget("summary-ipv4-forwarding").set_text(forward_txt) ip = self.get_config_ip6() diff --git a/virtManager/host.py b/virtManager/host.py index 1e39fad0..8503959a 100644 --- a/virtManager/host.py +++ b/virtManager/host.py @@ -548,24 +548,78 @@ class vmmHost(vmmGObjectUI): self.widget("net-apply").set_sensitive(False) + def _populate_net_ipv4_state(self, net): + (netstr, + (dhcpstart, dhcpend), + (routeaddr, routevia)) = net.get_ipv4_network() + + self.widget("net-ipv4-expander").set_visible(bool(netstr)) + if not netstr: + return + + forward = net.get_ipv4_forward_mode() + self.widget("net-ipv4-forwarding-icon").set_from_stock( + forward and Gtk.STOCK_CONNECT or Gtk.STOCK_DISCONNECT, + Gtk.IconSize.MENU) + self.widget("net-ipv4-forwarding").set_text(net.pretty_forward_mode()) + + dhcpstr = _("Disabled") + if dhcpstart: + dhcpstr = dhcpstart + " - " + dhcpend + self.widget("net-ipv4-dhcp-range").set_text(dhcpstr) + self.widget("net-ipv4-network").set_text(netstr) + + uihelpers.set_grid_row_visible( + self.widget("net-ipv4-route"), bool(routevia)) + if routevia: + routevia = routeaddr + ", gateway=" + routevia + self.widget("net-ipv4-route").set_text(routevia or "") + + + def _populate_net_ipv6_state(self, net): + (netstr, + (dhcpstart, dhcpend), + (routeaddr, routevia)) = net.get_ipv6_network() + + self.widget("net-ipv6-expander").set_visible(bool(netstr)) + self.widget("net-ipv6-forwarding-icon").set_from_stock( + netstr and Gtk.STOCK_CONNECT or Gtk.STOCK_DISCONNECT, + Gtk.IconSize.MENU) + + if netstr: + prettymode = _("Routed network") + elif net.get_ipv6_enabled(): + prettymode = _("Isolated network, internal routing only") + else: + prettymode = _("Isolated network, routing disabled") + self.widget("net-ipv6-forwarding").set_text(prettymode) + + dhcpstr = _("Disabled") + if dhcpstart: + dhcpstr = dhcpstart + " - " + dhcpend + self.widget("net-ipv6-dhcp-range").set_text(dhcpstr) + self.widget("net-ipv6-network").set_text(netstr or "") + + uihelpers.set_grid_row_visible( + self.widget("net-ipv6-route"), bool(routevia)) + if routevia: + routevia = routeaddr + ", gateway=" + routevia + self.widget("net-ipv6-route").set_text(routevia or "") + def populate_net_state(self, net): active = net.is_active() self.widget("net-details").set_sensitive(True) - self.widget("net-name").set_text(net.get_name()) - dns_name = net.get_name_domain() - if dns_name: - self.widget("net-name-domain").set_text(dns_name) - else: - self.widget("net-name-domain").set_text("") + self.widget("net-name").set_markup( + "Network %s:" % net.get_name()) + self.widget("net-device").set_text(net.get_bridge_device() or "") + self.widget("net-name-domain").set_text(net.get_name_domain() or "") + uihelpers.set_grid_row_visible(self.widget("net-name-domain"), + bool(net.get_name_domain())) - dev = active and net.get_bridge_device() or "" state = active and _("Active") or _("Inactive") icon = (active and self.ICON_RUNNING or self.ICON_SHUTOFF) - - self.widget("net-device").set_text(dev) - self.widget("net-device").set_sensitive(active) self.widget("net-state").set_text(state) self.widget("net-state-icon").set_from_icon_name(icon, Gtk.IconSize.MENU) @@ -579,82 +633,14 @@ class vmmHost(vmmGObjectUI): self.widget("net-autostart").set_active(autostart) self.widget("net-autostart").set_label(autolabel) - ######### IPv4 ######### - result = net.get_ipv4_network() - network = result[0] - dhcp = result[1] - route = result[2] - if network: - self.widget("net-frame-ip4").show() - else: - self.widget("net-frame-ip4").hide() - self.widget("net-ip4-network").set_text(str(network)) - - start = dhcp and str(dhcp[0]) or _("Disabled") - end = dhcp and str(dhcp[1]) or _("Disabled") - self.widget("net-ip4-dhcp-start").set_text(start) - self.widget("net-ip4-dhcp-end").set_text(end) - if route and route[0] and route[1]: - routeVia = str(route[0]) + ", gateway=" + str(route[1]) - self.widget("net-ip4-route-label").show() - else: - routeVia = "" - self.widget("net-ip4-route-label").hide() - self.widget("net-ip4-route-via").set_text(routeVia) - - forward, ignore = net.get_ipv4_forward() - iconsize = Gtk.IconSize.MENU - icon = forward and Gtk.STOCK_CONNECT or Gtk.STOCK_DISCONNECT - self.widget("net-ip4-forwarding-icon").set_from_stock(icon, iconsize) - forward_str = net.pretty_forward_mode() - self.widget("net-ip4-forwarding").set_text(forward_str) - - ######### IPv6 ######### - result = net.get_ipv6_network() - network = result[0] - dhcp = result[1] - route = result[2] - if network: - self.widget("net-frame-ip6").show() - iconsize = Gtk.IconSize.MENU - icon = Gtk.STOCK_CONNECT - self.widget("net-ip6-forwarding-icon").set_from_stock(icon, iconsize) - self.widget("net-ip6-forwarding").set_text(_("Routed network")) - else: - self.widget("net-frame-ip6").hide() - iconsize = Gtk.IconSize.MENU - icon = Gtk.STOCK_DISCONNECT - self.widget("net-ip6-forwarding-icon").set_from_stock(icon, iconsize) - ipv6 = net.get_ipv6_routing() - if ipv6: - self.widget("net-ip6-forwarding").set_text( - _("Isolated network, internal and host routing only")) - elif ipv6 == 'yes': - self.widget("net-ip6-forwarding").set_text( - _("Isolated network, internal routing only")) - else: - self.widget("net-ip6-forwarding").set_text( - _("Isolated network, routing disabled")) - self.widget("net-ip6-network").set_text(str(network)) - - start = dhcp and str(dhcp[0]) or _("Disabled") - end = dhcp and str(dhcp[1]) or _("Disabled") - self.widget("net-ip6-dhcp-start").set_text(start) - self.widget("net-ip6-dhcp-end").set_text(end) - if route and route[0] and route[1]: - routeVia = str(route[0]) + ", gateway=" + str(route[1]) - self.widget("net-ip6-route-label").show() - else: - routeVia = "" - self.widget("net-ip6-route-label").hide() - self.widget("net-ip6-route-via").set_text(routeVia) + self._populate_net_ipv4_state(net) + self._populate_net_ipv6_state(net) def reset_net_state(self): self.widget("net-details").set_sensitive(False) self.widget("net-name").set_text("") self.widget("net-device").set_text("") - self.widget("net-device").set_sensitive(False) self.widget("net-state").set_text(_("Inactive")) self.widget("net-state-icon").set_from_icon_name(self.ICON_SHUTOFF, Gtk.IconSize.MENU) @@ -663,21 +649,17 @@ class vmmHost(vmmGObjectUI): self.widget("net-delete").set_sensitive(False) self.widget("net-autostart").set_label(_("Never")) self.widget("net-autostart").set_active(False) - self.widget("net-ip4-network").set_text("") - self.widget("net-ip4-dhcp-start").set_text("") - self.widget("net-ip4-dhcp-end").set_text("") - self.widget("net-ip4-route-label").hide() - self.widget("net-ip4-route-via").set_text("") - self.widget("net-ip4-forwarding-icon").set_from_stock( + self.widget("net-ipv4-network").set_text("") + self.widget("net-ipv4-dhcp-range").set_text("") + self.widget("net-ipv4-route").set_text("") + self.widget("net-ipv4-forwarding-icon").set_from_stock( Gtk.STOCK_DISCONNECT, Gtk.IconSize.MENU) - self.widget("net-ip4-forwarding").set_text( + self.widget("net-ipv4-forwarding").set_text( _("Isolated network")) - self.widget("net-ip6-network").set_text("") - self.widget("net-ip6-dhcp-start").set_text("") - self.widget("net-ip6-dhcp-end").set_text("") - self.widget("net-ip6-route-label").hide() - self.widget("net-ip6-route-via").set_text("") - self.widget("net-ip6-forwarding").set_text( + self.widget("net-ipv6-network").set_text("") + self.widget("net-ipv6-dhcp-range").set_text("") + self.widget("net-ipv6-route").set_text("") + self.widget("net-ipv6-forwarding").set_text( _("Isolated network")) self.widget("net-apply").set_sensitive(False) diff --git a/virtManager/network.py b/virtManager/network.py index c142b013..459b32c0 100644 --- a/virtManager/network.py +++ b/virtManager/network.py @@ -19,40 +19,28 @@ # import ipaddr -import libxml2 -from virtinst import util +from virtinst import Network from virtManager.libvirtobject import vmmLibvirtObject +def _make_addr_str(addrStr, prefix, netmaskStr): + if prefix: + return str(ipaddr.IPNetwork(str(addrStr) + "/" + + str(prefix)).masked()) + elif netmaskStr: + netmask = ipaddr.IPAddress(netmaskStr) + network = ipaddr.IPAddress(addrStr) + return str(ipaddr.IPNetwork(str(network) + "/" + + str(netmask)).masked()) + else: + return str(ipaddr.IPNetwork(str(addrStr))) + + class vmmNetwork(vmmLibvirtObject): - @staticmethod - def pretty_desc(forward, forwardDev): - if forward or forwardDev: - if not forward or forward == "nat": - if forwardDev: - desc = _("NAT to %s") % forwardDev - else: - desc = _("NAT") - elif forward == "route": - if forwardDev: - desc = _("Route to %s") % forwardDev - else: - desc = _("Routed network") - else: - if forwardDev: - desc = "%s to %s" % (forward, forwardDev) - else: - desc = "%s network" % forward.capitalize() - else: - desc = _("Isolated network, internal and host routing only") - - return desc - def __init__(self, conn, backend, key): - vmmLibvirtObject.__init__(self, conn, backend, key) - self._uuid = key + vmmLibvirtObject.__init__(self, conn, backend, key, parseclass=Network) self._active = True self._support_isactive = None @@ -60,7 +48,10 @@ class vmmNetwork(vmmLibvirtObject): self.tick() - # Required class methods + ########################## + # Required class methods # + ########################## + def get_name(self): return self._backend.name() def _XMLDesc(self, flags): @@ -68,6 +59,21 @@ class vmmNetwork(vmmLibvirtObject): def _define(self, xml): return self.conn.define_network(xml) + + ########### + # Actions # + ########### + + def _backend_get_active(self): + if self._support_isactive is None: + self._support_isactive = self.conn.check_net_support( + self._backend, + self.conn.SUPPORT_NET_ISACTIVE) + + if not self._support_isactive: + return True + return bool(self._backend.isActive()) + def _set_active(self, state): if state == self._active: return @@ -77,18 +83,6 @@ class vmmNetwork(vmmLibvirtObject): def is_active(self): return self._active - def get_label(self): - return self.get_name() - - def get_uuid(self): - return self._uuid - - def get_bridge_device(self): - try: - return self._backend.bridgeName() - except: - return "" - def _kick_conn(self): self.conn.schedule_priority_tick(pollnet=True) @@ -105,209 +99,93 @@ class vmmNetwork(vmmLibvirtObject): self._backend = None self._kick_conn() - def set_autostart(self, value): - self._backend.setAutostart(value) - def get_autostart(self): return self._backend.autostart() - - def _backend_get_active(self): - if self._support_isactive is None: - self._support_isactive = self.conn.check_net_support( - self._backend, - self.conn.SUPPORT_NET_ISACTIVE) - - if not self._support_isactive: - return True - return bool(self._backend.isActive()) + def set_autostart(self, value): + self._backend.setAutostart(value) def tick(self): self._set_active(self._backend_get_active()) - ######################## - # XML parsing routines # - ######################## - - def get_ipv4_static_route(self): - doc = None - ret = None - routeAddr = None - routeVia = None - xml = self.get_xml() - doc = libxml2.parseDoc(xml) - nodes = doc.xpathEval('//route') - for node in nodes: - family = node.xpathEval('string(./@family)') - if not family or family == 'ipv4': - addrStr = node.xpathEval('string(./@address)') - netmaskStr = node.xpathEval('string(./@netmask)') - gatewayStr = node.xpathEval('string(./@gateway)') - prefix = node.xpathEval('string(./@prefix)') - if prefix: - prefix = int(prefix) - routeAddr = str(ipaddr.IPNetwork(str(addrStr) + "/" + str(prefix)).masked()) - elif netmaskStr: - netmask = ipaddr.IPAddress(netmaskStr) - network = ipaddr.IPAddress(addrStr) - routeAddr = str(ipaddr.IPNetwork(str(network) + "/" + str(netmask)).masked()) - else: - routeAddr = str(ipaddr.IPNetwork(str(addrStr))) - routeVia = str(ipaddr.IPAddress(str(gatewayStr))) - break - - if doc: - doc.freeDoc() - if routeAddr and routeVia: - ret = [routeAddr, routeVia] - else: - ret = None - return ret - - def get_ipv4_network(self): - doc = None - ret = None - goodNode = None - dhcpstart = None - dhcpend = None - xml = self.get_xml() - doc = libxml2.parseDoc(xml) - nodes = doc.xpathEval('//ip') - for node in nodes: - family = node.xpathEval('string(./@family)') - if not family or family == 'ipv4': - dhcp = node.xpathEval('string(./dhcp)') - if dhcp: - dhcpstart = node.xpathEval('string(./dhcp/range[1]/@start)') - dhcpend = node.xpathEval('string(./dhcp/range[1]/@end)') - goodNode = node - break - - if goodNode is None: - for node in nodes: - family = node.xpathEval('string(./@family)') - if not family or family == 'ipv4': - goodNode = node - break - - if goodNode: - addrStr = goodNode.xpathEval('string(./@address)') - netmaskStr = goodNode.xpathEval('string(./@netmask)') - prefix = goodNode.xpathEval('string(./@prefix)') - if prefix: - prefix = int(prefix) - ret = str(ipaddr.IPNetwork(str(addrStr) + "/" + str(prefix)).masked()) - elif netmaskStr: - netmask = ipaddr.IPAddress(netmaskStr) - network = ipaddr.IPAddress(addrStr) - ret = str(ipaddr.IPNetwork(str(network) + "/" + str(netmask)).masked()) - else: - ret = str(ipaddr.IPNetwork(str(addrStr))) - if doc: - doc.freeDoc() - if dhcpstart and dhcpend: - dhcp = [str(ipaddr.IPAddress(dhcpstart)), str(ipaddr.IPAddress(dhcpend))] - else: - dhcp = None - route = self.get_ipv4_static_route() - return [ret, dhcp, route] - - def get_ipv6_static_route(self): - doc = None - ret = None - routeAddr = None - routeVia = None - xml = self.get_xml() - doc = libxml2.parseDoc(xml) - nodes = doc.xpathEval('//route') - for node in nodes: - family = node.xpathEval('string(./@family)') - if family and family == 'ipv6': - addrStr = node.xpathEval('string(./@address)') - prefix = node.xpathEval('string(./@prefix)') - gatewayStr = node.xpathEval('string(./@gateway)') - if prefix: - prefix = int(prefix) - routeAddr = str(ipaddr.IPNetwork(str(addrStr) + "/" + str(prefix)).masked()) - else: - routeAddr = str(ipaddr.IPNetwork(str(addrStr))) - routeVia = str(ipaddr.IPAddress(str(gatewayStr))) - break - - if doc: - doc.freeDoc() - if routeAddr and routeVia: - ret = [routeAddr, routeVia] - else: - ret = None - return ret - - def get_ipv6_network(self): - doc = None - ret = None - goodNode = None - dhcpstart = None - dhcpend = None - xml = self.get_xml() - doc = libxml2.parseDoc(xml) - nodes = doc.xpathEval('//ip') - for node in nodes: - family = node.xpathEval('string(./@family)') - if family and family == 'ipv6': - dhcp = node.xpathEval('string(./dhcp)') - if dhcp: - dhcpstart = node.xpathEval('string(./dhcp/range[1]/@start)') - dhcpend = node.xpathEval('string(./dhcp/range[1]/@end)') - goodNode = node - break - - if goodNode is None: - for node in nodes: - family = node.xpathEval('string(./@family)') - if family and family == 'ipv6': - goodNode = node - break - - if goodNode: - addrStr = goodNode.xpathEval('string(./@address)') - prefix = goodNode.xpathEval('string(./@prefix)') - if prefix: - prefix = int(prefix) - ret = str(ipaddr.IPNetwork(str(addrStr) + "/" + str(prefix)).masked()) - else: - ret = str(ipaddr.IPNetwork(str(addrStr))) - if doc: - doc.freeDoc() - if dhcpstart and dhcpend: - dhcp = [str(ipaddr.IPAddress(dhcpstart)), str(ipaddr.IPAddress(dhcpend))] - else: - dhcp = None - route = self.get_ipv6_static_route() - return [ret, dhcp, route] + ############### + # XML parsing # + ############### + def get_uuid(self): + return self._get_xmlobj().uuid + def get_bridge_device(self): + return self._get_xmlobj().bridge def get_name_domain(self): - xml = self.get_xml() - name_domain = util.xpath(xml, "/network/domain/@name") - return name_domain - - def get_ipv6_routing(self): - xml = self.get_xml() - ipv6_routing = util.xpath(xml, "/network/@ipv6") - return ipv6_routing - - def get_ipv4_forward(self): - xml = self.get_xml() - fw = util.xpath(xml, "/network/forward/@mode") - forwardDev = util.xpath(xml, "/network/forward/@dev") - return [fw, forwardDev] - + return self._get_xmlobj().domain_name + def get_ipv6_enabled(self): + return self._get_xmlobj().ipv6 + def get_ipv4_forward_mode(self): + return self._get_xmlobj().forward.mode def pretty_forward_mode(self): - forward, forwardDev = self.get_ipv4_forward() - return vmmNetwork.pretty_desc(forward, forwardDev) + return self._get_xmlobj().forward.pretty_desc() def can_pxe(self): - xml = self.get_xml() - forward = self.get_ipv4_forward()[0] + forward = self.get_ipv4_forward_mode() if forward and forward != "nat": return True - return bool(util.xpath(xml, "/network/ip/dhcp/bootp/@file")) + for ip in self._get_xmlobj().ips: + if ip.bootp_file: + return True + return False + + def _get_static_route(self, family): + xmlobj = self._get_xmlobj() + route = None + for r in xmlobj.routes: + if (r.family == family or (family == "ipv4" and not r.family)): + route = r + break + if not route: + return [None, None] + + routeAddr = _make_addr_str(route.address, route.prefix, route.netmask) + routeVia = str(ipaddr.IPAddress(str(route.gateway))) + + if not routeAddr or not routeVia: + return [None, None] + return [routeAddr, routeVia] + + def _get_network(self, family): + dhcpstart = None + dhcpend = None + + xmlobj = self._get_xmlobj() + ip = None + for i in xmlobj.ips: + if (i.family == family or + (family == "ipv4" and not i.family)): + if i.ranges: + ip = i + dhcpstart = i.ranges[0].start + dhcpend = i.ranges[0].end + break + + if not ip: + for i in xmlobj.ips: + if (i.family == family or + (family == "ipv4" and not i.family)): + ip = i + break + + ret = None + if ip: + ret = _make_addr_str(ip.address, ip.prefix, ip.netmask) + + dhcp = [None, None] + if dhcpstart and dhcpend: + dhcp = [str(ipaddr.IPAddress(dhcpstart)), + str(ipaddr.IPAddress(dhcpend))] + return [ret, dhcp] + + def get_ipv4_network(self): + ret = self._get_network("ipv4") + return ret + [self._get_static_route("ipv4")] + def get_ipv6_network(self): + ret = self._get_network("ipv6") + return ret + [self._get_static_route("ipv6")] diff --git a/virtinst/__init__.py b/virtinst/__init__.py index 6dad3e55..f48239a9 100644 --- a/virtinst/__init__.py +++ b/virtinst/__init__.py @@ -31,8 +31,9 @@ from virtinst.seclabel import Seclabel import virtinst.nodedev as NodeDeviceParser import virtinst.capabilities as CapabilitiesParser -from virtinst.storage import StoragePool, StorageVolume from virtinst.interface import Interface, InterfaceProtocol +from virtinst.network import Network +from virtinst.storage import StoragePool, StorageVolume from virtinst.device import VirtualDevice from virtinst.deviceinterface import VirtualNetworkInterface diff --git a/virtinst/network.py b/virtinst/network.py new file mode 100644 index 00000000..55f3fbbb --- /dev/null +++ b/virtinst/network.py @@ -0,0 +1,161 @@ +# +# Copyright 2013 Red Hat, Inc. +# Cole Robinson +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301 USA. +""" +Classes for building and installing libvirt XML +""" + +import libvirt + +from virtinst import util +from virtinst.xmlbuilder import XMLBuilder, XMLChildProperty, XMLProperty + + +class _NetworkDHCPRange(XMLBuilder): + _XML_ROOT_NAME = "range" + start = XMLProperty("./@start") + end = XMLProperty("./@end") + + +class _NetworkDHCPHost(XMLBuilder): + _XML_ROOT_NAME = "host" + macaddr = XMLProperty("./@mac") + name = XMLProperty("./@name") + ip = XMLProperty("./@ip") + + +class _NetworkIP(XMLBuilder): + _XML_ROOT_NAME = "ip" + + family = XMLProperty("./@family") + address = XMLProperty("./@address") + prefix = XMLProperty("./@prefix", is_int=True) + netmask = XMLProperty("./@netmask") + + tftp = XMLProperty("./tftp/@root") + bootp_file = XMLProperty("./dhcp/bootp/@file") + bootp_server = XMLProperty("./dhcp/bootp/@server") + + ranges = XMLChildProperty(_NetworkDHCPRange, relative_xpath="./dhcp") + hosts = XMLChildProperty(_NetworkDHCPHost, relative_xpath="./dhcp") + + +class _NetworkRoute(XMLBuilder): + _XML_ROOT_NAME = "route" + + family = XMLProperty("./@family") + address = XMLProperty("./@address") + prefix = XMLProperty("./@prefix", is_int=True) + gateway = XMLProperty("./@gateway") + + +class _NetworkForward(XMLBuilder): + _XML_ROOT_NAME = "forward" + + mode = XMLProperty("./@mode") + dev = XMLProperty("./@dev") + + def pretty_desc(self): + return Network.pretty_forward_desc(self.mode, self.dev) + + +class Network(XMLBuilder): + """ + Top level class for object XML + """ + @staticmethod + def pretty_forward_desc(mode, dev): + if mode or dev: + if not mode or mode == "nat": + if dev: + desc = _("NAT to %s") % dev + else: + desc = _("NAT") + elif mode == "route": + if dev: + desc = _("Route to %s") % dev + else: + desc = _("Routed network") + else: + if dev: + desc = "%s to %s" % (mode, dev) + else: + desc = "%s network" % mode.capitalize() + else: + desc = _("Isolated network, internal and host routing only") + + return desc + + def __init__(self, *args, **kwargs): + XMLBuilder.__init__(self, *args, **kwargs) + self._random_uuid = None + + + ###################### + # Validation helpers # + ###################### + + def _check_name_collision(self, name): + try: + self.conn.networkLookupByName(name) + except libvirt.libvirtError: + return + raise ValueError(_("Name '%s' already in use by another network." % + name)) + + def _get_default_uuid(self): + if self._random_uuid is None: + self._random_uuid = util.generate_uuid(self.conn) + return self._random_uuid + + + ################## + # XML properties # + ################## + + _XML_ROOT_NAME = "network" + _XML_PROP_ORDER = ["ipv6", "name", "uuid", "forward", + "bridge", "stp", "delay", "domain_name", + "macaddr", "ips", "routes"] + + ipv6 = XMLProperty("./@ipv6", is_yesno=True) + name = XMLProperty("./name", validate_cb=_check_name_collision) + uuid = XMLProperty("./uuid", + validate_cb=lambda s, v: util.validate_uuid(v), + default_cb=_get_default_uuid) + + # Not entirely correct, there can be multiple routes + forward = XMLChildProperty(_NetworkForward, is_single=True) + + domain_name = XMLProperty("./domain/@name") + + bridge = XMLProperty("./bridge/@name") + stp = XMLProperty("./bridge/@stp", is_onoff=True) + delay = XMLProperty("./bridge/@delay", is_int=True) + macaddr = XMLProperty("./mac/@address") + + ips = XMLChildProperty(_NetworkIP) + routes = XMLChildProperty(_NetworkRoute) + + def add_route(self, address, prefix, gateway): + route = _NetworkRoute(self.conn) + route.family = "ipv4" + route.address = address + route.prefix = prefix + route.gateway = gateway + self._add_child(route)