Moved the node administration elements to a separate submenu.

Created a new network administration menu.

 * users can define a network
 * users can create a network
 * users can destroy a network
 * users can undefine a network
 * users can list existing networks, including details

Each new command is also available as a separate command line executable
as well.

Signed-off-by: Darryl L. Pierce <[email protected]>
---
 Makefile.am                  |    9 ++
 nodeadmin/configscreen.py    |   35 ++++++-
 nodeadmin/createdomain.py    |    3 -
 nodeadmin/createnetwork.py   |   52 +++++++++
 nodeadmin/definenet.py       |  255 ++++++++++++++++++++++++++++++++++++++++++
 nodeadmin/destroynetwork.py  |   56 +++++++++
 nodeadmin/halworker.py       |    8 ++
 nodeadmin/libvirtworker.py   |   61 ++++++++++
 nodeadmin/listnetworks.py    |   55 +++++++++
 nodeadmin/mainmenu.py        |   71 ++++--------
 nodeadmin/menuscreen.py      |   57 ++++++++++
 nodeadmin/netmenu.py         |   58 ++++++++++
 nodeadmin/networkconfig.py   |   99 ++++++++++++++++
 nodeadmin/nodemenu.py        |   63 +++++++++++
 nodeadmin/setup.py.in        |    7 +-
 nodeadmin/undefinenetwork.py |   88 +++++++++++++++
 ovirt-node.spec.in           |   33 +++++-
 17 files changed, 948 insertions(+), 62 deletions(-)
 create mode 100644 nodeadmin/createnetwork.py
 create mode 100644 nodeadmin/definenet.py
 create mode 100644 nodeadmin/destroynetwork.py
 create mode 100644 nodeadmin/listnetworks.py
 create mode 100644 nodeadmin/menuscreen.py
 create mode 100755 nodeadmin/netmenu.py
 create mode 100644 nodeadmin/networkconfig.py
 create mode 100755 nodeadmin/nodemenu.py
 create mode 100644 nodeadmin/undefinenetwork.py

diff --git a/Makefile.am b/Makefile.am
index 2d195c0..abb7c33 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -28,17 +28,26 @@ EXTRA_DIST =                        \
   images/syslinux-vesa-splash.jpg      \
   nodeadmin/__init__.py         \
   nodeadmin/configscreen.py     \
+  nodeadmin/createnetwork.py    \
   nodeadmin/createuser.py       \
   nodeadmin/destroydomain.py    \
+  nodeadmin/destroynetwork.py   \
   nodeadmin/halworker.py        \
   nodeadmin/libvirtworker.py    \
   nodeadmin/userworker.py       \
   nodeadmin/mainmenu.py         \
+  nodeadmin/menuscreen.py       \
+  nodeadmin/netmenu.py          \
+  nodeadmin/nodemenu.py         \
   nodeadmin/undefinedomain.py   \
+  nodeadmin/undefinenetwork.py  \
   nodeadmin/createdomain.py     \
   nodeadmin/definedomain.py     \
+  nodeadmin/definenet.py        \
   nodeadmin/domainconfig.py     \
+  nodeadmin/networkconfig.py    \
   nodeadmin/listdomains.py      \
+  nodeadmin/listnetworks.py     \
   nodeadmin/nodeadmin.py        \
   nodeadmin/setup.py            \
   nodeadmin/utils.py            \
diff --git a/nodeadmin/configscreen.py b/nodeadmin/configscreen.py
index 0282eee..f214aea 100644
--- a/nodeadmin/configscreen.py
+++ b/nodeadmin/configscreen.py
@@ -77,8 +77,9 @@ class ConfigScreen:
         active = True
         while active and (self.__finished == False):
             screen = SnackScreen()
-            gridform = GridForm(screen, self.__title, 1, 4)
             elements = self.get_elements_for_page(screen, self.__current_page)
+            # TODO: need to set the form height to the number of elements on 
the page
+            gridform = GridForm(screen, self.__title, 1, 10)
             current_element = 0
             for element in elements:
                 gridform.add(element, 0, current_element)
@@ -133,7 +134,7 @@ class DomainListConfigScreen(ConfigScreen):
         if len(domains) > 0:
             self.__has_domains = True
             self.__domain_list = Listbox(0)
-            for name in self.get_libvirt().list_domains(defined, created):
+            for name in domains:
                 self.__domain_list.append(name, name)
             result = [self.__domain_list]
         else:
@@ -148,3 +149,33 @@ class DomainListConfigScreen(ConfigScreen):
 
     def has_selectable_domains(self):
         return self.__has_domains
+
+class NetworkListConfigScreen(ConfigScreen):
+    '''Provides a base class for all config screens that require a network 
list.'''
+
+    def __init__(self, title):
+        ConfigScreen.__init__(self, title)
+
+    def get_network_list_page(self, screen, defined=True, created=True):
+        networks = self.get_libvirt().list_networks(defined, created)
+        result = None
+
+        if len(networks) > 0:
+            self.__has_networks = True
+            self.__network_list = Listbox(0)
+            for name in networks:
+                self.__network_list.append(name, name)
+            result = self.__network_list
+        else:
+            self.__has_networks = False
+            result = Label("There are no networks available.")
+        grid = Grid(1, 1)
+        grid.setField(result, 0, 0)
+        return [Label("Network List"),
+                grid]
+
+    def get_selected_network(self):
+        return self.__network_list.current()
+
+    def has_selectable_networks(self):
+        return self.__has_networks
diff --git a/nodeadmin/createdomain.py b/nodeadmin/createdomain.py
index b73a09e..6f10b44 100755
--- a/nodeadmin/createdomain.py
+++ b/nodeadmin/createdomain.py
@@ -55,9 +55,6 @@ class CreateDomainConfigScreen(DomainListConfigScreen):
             else:
                 errors.append("You must first select a domain to create.")
 
-    def process_input(self, page):
-        print "foo"
-
     def get_create_domain_page(self, screen):
         grid = Grid(1, 1)
         grid.setField(Label("%s was successfully created." % 
self.get_selected_domain()), 0, 0)
diff --git a/nodeadmin/createnetwork.py b/nodeadmin/createnetwork.py
new file mode 100644
index 0000000..6b40bb6
--- /dev/null
+++ b/nodeadmin/createnetwork.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+#
+# createnetwork.py - Copyright (C) 2009 Red Hat, Inc.
+# Written by Darryl L. Pierce <[email protected]>
+#
+# 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; version 2 of the License.
+#
+# 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.  A copy of the GNU General Public License is
+# also available at http://www.gnu.org/copyleft/gpl.html.
+
+from snack import *
+from configscreen import *
+
+LIST_PAGE   = 1
+CREATE_PAGE = 2
+
+class CreateNetworkConfigScreen(NetworkListConfigScreen):
+    def __init__(self):
+        NetworkListConfigScreen.__init__(self, "Create A Network")
+
+    def get_elements_for_page(self, screen, page):
+        if   page is LIST_PAGE:   return self.get_network_list_page(screen, 
created = False)
+        elif page is CREATE_PAGE: return self.get_create_network_page(screen)
+
+    def page_has_next(self, page):
+        if page is LIST_PAGE: return self.has_selectable_networks()
+
+    def page_has_back(self, page):
+        return (page is CREATE_PAGE)
+
+    def validate_input(self, page, errors):
+        if page is LIST_PAGE:
+            self.get_libvirt().create_network(self.get_selected_network())
+            return True
+
+    def get_create_network_page(self, screen):
+        return [Label("Network Started"),
+                Label("%s was successfully started." % 
self.get_selected_network())]
+
+def CreateNetwork():
+    screen = CreateNetworkConfigScreen()
+    screen.start()
diff --git a/nodeadmin/definenet.py b/nodeadmin/definenet.py
new file mode 100644
index 0000000..b76fe27
--- /dev/null
+++ b/nodeadmin/definenet.py
@@ -0,0 +1,255 @@
+# definenet.py - Copyright (C) 2009 Red Hat, Inc.
+# Written by Darryl L. Pierce <[email protected]>
+#
+# 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; version 2 of the License.
+#
+# 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.  A copy of the GNU General Public License is
+# also available at http://www.gnu.org/copyleft/gpl.html.
+
+from snack import *
+from IPy import IP
+import traceback
+import re
+import logging
+
+from configscreen  import ConfigScreen
+from networkconfig import NetworkConfig
+
+NETWORK_NAME_PAGE            = 1
+IPV4_ADDRESS_PAGE            = 2
+PUBLIC_NETWORK_ALERT_PAGE    = 3
+NETWORK_DETAILS_PAGE         = 4
+DHCP_RANGE_PAGE              = 5
+NETWORK_TYPE_PAGE            = 6
+SELECT_PHYSICAL_NETWORK_PAGE = 7
+SUMMARY_PAGE                 = 8
+
+class DefineNetworkConfigScreen(ConfigScreen):
+    def __init__(self):
+        ConfigScreen.__init__(self, "Create A Virtual Network Interface")
+        self.__config = NetworkConfig()
+
+    def get_elements_for_page(self, screen, page):
+        if   page is NETWORK_NAME_PAGE:            return 
self.get_network_name_page(screen)
+        elif page is IPV4_ADDRESS_PAGE:            return 
self.get_ipv4_address_page(screen)
+        elif page is PUBLIC_NETWORK_ALERT_PAGE:    return 
self.get_public_network_alert_page(screen)
+        elif page is NETWORK_DETAILS_PAGE:         return 
self.get_network_details_page(screen)
+        elif page is DHCP_RANGE_PAGE:              return 
self.get_dhcp_range_page(screen)
+        elif page is NETWORK_TYPE_PAGE:            return 
self.get_network_type_page(screen)
+        elif page is SELECT_PHYSICAL_NETWORK_PAGE: return 
self.get_select_physical_network_page(screen)
+        elif page is SUMMARY_PAGE:                 return 
self.get_summary_page(screen)
+
+    def validate_input(self, page, errors):
+        if page is NETWORK_NAME_PAGE:
+            if len(self.__name.value()) > 0:
+                if re.match("^[a-zA-Z0-9_]*$", self.__name.value()):
+                    return True
+                else:
+                    errors.append("The network name can only contain letters, 
numbers and the underscore, and no spaces.")
+            else:
+                errors.append("Network name must be non-blank and less than 50 
characters")
+        elif page is IPV4_ADDRESS_PAGE:
+            if len(self.__ipv4_address.value()) > 0:
+                try:
+                    self.__config.set_ipv4_address(self.__ipv4_address.value())
+                    return True
+                except Exception, error:
+                    errors.append("The network address could not be 
understood: %s" % str(error))
+            else:
+                errors.append("Network must be entered in the format 
1.2.3.4/8")
+        elif page is PUBLIC_NETWORK_ALERT_PAGE: return True
+        elif page is NETWORK_DETAILS_PAGE: return True
+        elif page is DHCP_RANGE_PAGE:
+            try:
+                if len(self.__start_address.value()) > 0 and 
len(self.__end_address.value()) > 0:
+                    start = IP(self.__start_address.value(), )
+                    end   = IP(self.__end_address.value())
+                    if not self.__config.is_bad_address(start) and not 
self.__config.is_bad_address(end):
+                        return True
+                    else:
+                        errors.append("Start and/or end address are outside of 
the choosen network.")
+                else:
+                    errors.append("Start and end address must be non-blank.")
+            except Exception, error:
+                logging.error(str(error))
+                errors.append("The start and/or end addresses could not be 
understood.")
+        elif page is NETWORK_TYPE_PAGE: return True
+        elif page is SELECT_PHYSICAL_NETWORK_PAGE: return True
+        elif page is SUMMARY_PAGE: return True
+        return False
+
+    def process_input(self, page):
+        if page is NETWORK_NAME_PAGE:
+            self.__config.set_name(self.__name.value())
+        elif page is DHCP_RANGE_PAGE:
+            self.__config.set_ipv4_start_address(self.__start_address.value())
+            self.__config.set_ipv4_end_address(self.__end_address.value())
+        elif page is NETWORK_TYPE_PAGE:
+            self.__config.set_isolated_network(self.__isolated_network.value())
+        elif page is SELECT_PHYSICAL_NETWORK_PAGE:
+            
self.__config.set_physical_device(self.__physical_devices.getSelection())
+        elif page is SUMMARY_PAGE:
+            self.get_libvirt().define_network(self.__config)
+            self.set_finished()
+
+    def get_next_page(self, page):
+        if page is IPV4_ADDRESS_PAGE:
+            if self.__config.is_public_ipv4_network():
+                return PUBLIC_NETWORK_ALERT_PAGE
+            else:
+                return NETWORK_DETAILS_PAGE
+        if page is NETWORK_TYPE_PAGE:
+            if self.__config.is_isolated_network():
+                return SUMMARY_PAGE
+            else:
+                return SELECT_PHYSICAL_NETWORK_PAGE
+        return ConfigScreen.get_next_page(self, page)
+
+    def get_back_page(self, page):
+        if page is NETWORK_DETAILS_PAGE:
+            return IPV4_ADDRESS_PAGE
+        if page is SUMMARY_PAGE:
+            if self.__config.is_isolated_network():
+                return NETWORK_TYPE_PAGE
+            else:
+                return SELECT_PHYSICAL_NETWORK_PAGE
+        return ConfigScreen.get_back_page(self, page)
+
+    def page_has_finish(self, page):
+        if page is SUMMARY_PAGE: return True
+        return False
+
+    def page_has_next(self, page):
+        if page < SUMMARY_PAGE: return True
+
+    def page_has_back(self, page):
+        if page > NETWORK_NAME_PAGE: return True
+        return False
+
+    def get_network_name_page(self, screen):
+        self.__name = Entry(50, self.__config.get_name())
+        grid = Grid(2, 1)
+        grid.setField(Label("Network Name:"), 0, 0)
+        grid.setField(self.__name, 1, 0)
+        return [Label("Please choose a name for your virtual network"),
+                grid]
+
+    def get_ipv4_address_page(self, screen):
+        self.__ipv4_address = Entry(18, self.__config.get_ipv4_address())
+        grid = Grid(2, 1)
+        grid.setField(Label("Network:"), 0, 0, anchorRight = 1)
+        grid.setField(self.__ipv4_address, 1, 0, anchorLeft = 1)
+        return [Label("You will need to choose an IPv4 address space for the 
virtual network:"),
+                grid,
+                Label("HINT: The network should be chosen from"),
+                Label("one of the IPv4 private address ranges;"),
+                Label("e.g., 10.0.0.0/8, 172.168.0.0/12, 192.168.0.0/16")]
+
+    def get_network_details_page(self, screen):
+        grid = Grid(2, 6)
+        grid.setField(Label("Network:"), 0, 0, anchorRight = 1)
+        grid.setField(Label(self.__config.get_ipv4_address()), 1, 0, 
anchorLeft = 1)
+        grid.setField(Label("Netmask:"), 0, 1, anchorRight = 1)
+        grid.setField(Label(self.__config.get_ipv4_netmask()), 1, 1, 
anchorLeft = 1)
+        grid.setField(Label("Broadcast:"), 0, 2, anchorRight = 1)
+        grid.setField(Label(self.__config.get_ipv4_broadcast()), 1, 2, 
anchorLeft = 1)
+        grid.setField(Label("Gateway:"), 0, 3, anchorRight = 1)
+        grid.setField(Label(self.__config.get_ipv4_gateway()), 1, 3, 
anchorLeft = 1)
+        grid.setField(Label("Size:"), 0, 4, anchorRight = 1)
+        grid.setField(Label("%d addresses" % 
self.__config.get_ipv4_max_addresses()), 1, 4, anchorLeft = 1)
+        grid.setField(Label("Type:"), 0, 5, anchorRight = 1)
+        grid.setField(Label(self.__config.get_ipv4_network_type()), 1, 5, 
anchorLeft = 1)
+        return [Label("Network Details"),
+                grid]
+
+    def get_public_network_alert_page(self, screen):
+        grid = Grid(1, 2)
+        grid.setField(Label("The network should normally use a private IPv4 
address."), 0, 0, anchorLeft = 1)
+        grid.setField(Label("Use this non-private address anyway?"), 0, 1, 
anchorLeft = 1)
+        return [Label("Check Network Address"),
+                grid]
+
+    def get_dhcp_range_page(self, screen):
+        self.__start_address = Entry(15, 
self.__config.get_ipv4_start_address())
+        self.__end_address   = Entry(15, self.__config.get_ipv4_end_address())
+        grid = Grid(2,2)
+        grid.setField(Label("Start:"), 0, 0, anchorRight = 1)
+        grid.setField(self.__start_address, 1, 0, anchorLeft = 1)
+        grid.setField(Label("End:"), 0, 1, anchorRight = 1)
+        grid.setField(self.__end_address, 1, 1, anchorLeft = 1)
+        return [Label("Selecting The DHCP Range"),
+                grid,
+                Label("TIP: Unless you wish to reserve some addresses to allow 
static network"),
+                Label("configuration in virtual machines, these paraemters can 
be left with"),
+                Label("their default values.")]
+
+    def get_network_type_page(self, screen):
+        self.__isolated_network = Checkbox("Isolated virtual network",
+                                           self.__config.is_isolated_network())
+        grid = Grid(1, 2)
+        grid.setField(Label("Please indicate whether this virtual network 
should be connected to the physical network."), 0, 0)
+        grid.setField(self.__isolated_network, 0, 1)
+        return [Label("Connecting To Physical Network"),
+                grid]
+
+    def get_select_physical_network_page(self, screen):
+        devices = []
+        devices.append(["NAT to any physical device", "", 
self.__config.get_physical_device() == ""])
+        for device in self.get_hal().list_network_devices():
+            devices.append(["NAT to physical device %s" % device, device, 
self.__config.get_physical_device() == device])
+        self.__physical_devices = RadioBar(screen, (devices))
+        grid = Grid(1, 2)
+        grid.setField(Label("Forward to physical network:"), 0, 0)
+        grid.setField(self.__physical_devices, 0, 1)
+        return [Label("Connecting To Physical Network"),
+                grid]
+
+    def get_summary_page(self, screen):
+        grid1 = Grid(2, 1)
+        grid1.setField(Label("Network name:"), 0, 0, anchorRight = 1)
+        grid1.setField(Label(self.__config.get_name()), 1, 0, anchorLeft = 1)
+
+        grid2 = Grid(2, 3)
+        grid2.setField(Label("Network:"), 0, 0, anchorRight = 1)
+        grid2.setField(Label(self.__config.get_ipv4_address()), 1, 0, 
anchorLeft = 1)
+        grid2.setField(Label("Gateway:"), 0, 1, anchorRight = 1)
+        grid2.setField(Label(self.__config.get_ipv4_gateway()), 1, 1, 
anchorLeft = 1)
+        grid2.setField(Label("Netmask:"), 0, 2, anchorRight = 1)
+        grid2.setField(Label(self.__config.get_ipv4_netmask()), 1, 2, 
anchorLeft = 1)
+
+        grid3 = Grid(2, 2)
+        grid3.setField(Label("Start address:"), 0, 0, anchorRight = 1)
+        grid3.setField(Label(self.__config.get_ipv4_start_address()), 1, 0, 
anchorLeft = 1)
+        grid3.setField(Label("End address:"), 0, 1, anchorRight = 1)
+        grid3.setField(Label(self.__config.get_ipv4_end_address()), 1, 1, 
anchorLeft = 1)
+
+        grid4 = Grid(2, 1)
+        grid4.setField(Label("Connectivity:"), 0, 0, anchorRight = 1)
+        if self.__config.is_isolated_network():
+            grid4.setField(Label("Isolated virtual network"), 1, 0, anchorLeft 
= 1)
+        else:
+            grid4.setField(Label("NAT to %s" % 
self.__config.get_physical_device_text()), 1, 0, anchorLeft = 1)
+
+        return [Label("Ready To Create Network"),
+                Label("Summary"),
+                grid1,
+                Label("IPv4 Network"),
+                grid2,
+                Label("DHCP"),
+                grid3,
+                Label("Forwarding"),
+                grid4]
+
+def DefineNetwork():
+    screen = DefineNetworkConfigScreen()
+    screen.start()
diff --git a/nodeadmin/destroynetwork.py b/nodeadmin/destroynetwork.py
new file mode 100644
index 0000000..5fb3e82
--- /dev/null
+++ b/nodeadmin/destroynetwork.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+#
+# destroynetwork.py - Copyright (C) 2009 Red Hat, Inc.
+# Written by Darryl L. Pierce <[email protected]>
+#
+# 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; version 2 of the License.
+#
+# 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.  A copy of the GNU General Public License is
+# also available at http://www.gnu.org/copyleft/gpl.html.
+
+from snack import *
+from configscreen import *
+
+LIST_PAGE    = 1
+DESTROY_PAGE = 2
+
+class DestroyNetworkConfigScreen(NetworkListConfigScreen):
+    def __init__(self):
+        NetworkListConfigScreen.__init__(self, "Destroy A Network")
+
+    def get_elements_for_page(self, screen, page):
+        if   page is LIST_PAGE:    return self.get_network_list_page(screen, 
defined = False)
+        elif page is DESTROY_PAGE: return self.get_destroy_network_page(screen)
+
+    def page_has_next(self, page):
+        if page is LIST_PAGE: return self.has_selectable_networks()
+        return False
+
+    def page_has_back(self, page):
+        if page is DESTROY_PAGE: return True
+        return False
+
+    def validate_input(self, page, errors):
+        if page is LIST_PAGE:
+            network = self.get_selected_network()
+            self.get_libvirt().destroy_network(network)
+            return True
+        return False
+
+    def get_destroy_network_page(self, screen):
+        return [Label("Network Destroyed"),
+                Label("%s has been destroyed." % self.get_selected_network())]
+
+def DestroyNetwork():
+    screen = DestroyNetworkConfigScreen()
+    screen.start()
diff --git a/nodeadmin/halworker.py b/nodeadmin/halworker.py
index 448c22d..7aa9a04 100644
--- a/nodeadmin/halworker.py
+++ b/nodeadmin/halworker.py
@@ -35,3 +35,11 @@ class HALWorker:
                 if info.GetProperty("volume.disc.has_data"):
                     result[str(info.GetProperty("block.device"))] = 
info.GetProperty("volume.label")
         return result
+
+    def list_network_devices(self):
+        result = []
+        for udi in self.__conn.FindDeviceByCapability("net"):
+            device = self.__bus.get_object("org.freedesktop.Hal", udi)
+            info = dbus.Interface(device, "org.freedesktop.Hal.Device")
+            result.append(info.GetProperty("net.interface"))
+        return result
diff --git a/nodeadmin/libvirtworker.py b/nodeadmin/libvirtworker.py
index adaea16..ba07605 100644
--- a/nodeadmin/libvirtworker.py
+++ b/nodeadmin/libvirtworker.py
@@ -73,6 +73,67 @@ class LibvirtWorker:
         domain = self.get_domain(name)
         domain.undefine()
 
+    def list_networks(self, defined = True, started = True):
+        '''Lists all networks.'''
+        result = []
+        if defined: result.extend(self.__conn.listDefinedNetworks())
+        if started: result.extend(self.__conn.listNetworks())
+        return result
+
+    def get_network(self, name):
+        '''Returns the specified network.'''
+        result = self.__conn.networkLookupByName(name)
+        if result is None: raise Exception("No such network exists: %s" % name)
+
+        return result
+
+    def network_exists(self, name):
+        '''Returns if a network with the given name already exists.'''
+        networks = self.list_networks()
+        if name in networks: return True
+        return False
+
+    def define_network(self, config):
+        '''Defines a new network.'''
+        # since there's no other way currently, we'll have to use XML
+        name = config.get_name()
+        ip = config.get_ipv4_address_raw()
+        start = config.get_ipv4_start_address()
+        end = config.get_ipv4_end_address()
+        fw = config.get_physical_device()
+
+        xml = "<network>" + \
+              "  <name>%s</name>\n" % name
+        if not config.is_public_ipv4_network():
+            if fw is not "":
+                xml += "  <forward dev='%s'/>\n" % fw[1]
+            else:
+                xml += "  <forward/>\n"
+
+        xml += "  <ip address='%s' netmask='%s'>\n" % (str(ip[1]), 
str(ip.netmask()))
+        xml += "    <dhcp>\n"
+        xml += "      <range start='%s' end='%s'/>\n" % (str(start), str(end))
+        xml += "    </dhcp>\n"
+        xml += "  </ip>\n"
+        xml += "</network>\n"
+
+        self.__conn.networkDefineXML(xml)
+
+    def create_network(self, name):
+        '''Creates a defined network.'''
+        network = self.get_network(name)
+        network.create()
+
+    def destroy_network(self, name):
+        '''Destroys the specified network.'''
+        network = self.get_network(name)
+        network.destroy()
+
+    def undefine_network(self, name):
+        '''Undefines the specified network.'''
+        network = self.get_network(name)
+        network.undefine()
+
     def list_storage_pools(self):
         '''Returns the list of all defined storage pools.'''
         return self.__conn.listStoragePools()
diff --git a/nodeadmin/listnetworks.py b/nodeadmin/listnetworks.py
new file mode 100644
index 0000000..a53f898
--- /dev/null
+++ b/nodeadmin/listnetworks.py
@@ -0,0 +1,55 @@
+# listnetworks.py - Copyright (C) 2009 Red Hat, Inc.
+# Written by Darryl L. Pierce <[email protected]>
+#
+# 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; version 2 of the License.
+#
+# 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.  A copy of the GNU General Public License is
+# also available at http://www.gnu.org/copyleft/gpl.html.
+
+from configscreen import *
+
+LIST_PAGE    = 1
+DETAILS_PAGE = 2
+
+class ListNetworksConfigScreen(NetworkListConfigScreen):
+    def __init__(self):
+        NetworkListConfigScreen.__init__(self, "List Networks")
+
+    def page_has_next(self, page):
+        return (page is LIST_PAGE) and self.has_selectable_networks()
+
+    def page_has_back(self, page):
+        return (page is DETAILS_PAGE)
+
+    def get_elements_for_page(self, screen, page):
+        if   page is LIST_PAGE:    return self.get_network_list_page(screen)
+        elif page is DETAILS_PAGE: return self.get_network_details_page(screen)
+
+    def get_network_details_page(self, screen):
+        network = self.get_libvirt().get_network(self.get_selected_network())
+        grid = Grid(2, 3)
+        grid.setField(Label("Name:"), 0, 0, anchorRight = 1)
+        grid.setField(Label(network.name()), 1, 0, anchorLeft = 1)
+        grid.setField(Label("Autostart:"), 0, 1, anchorRight = 1)
+        label = "No"
+        if network.autostart(): label = "Yes"
+        grid.setField(Label(label), 1, 1, anchorLeft = 1)
+        if network.bridgeName() is not "":
+            grid.setField(Label("Bridge:"), 0, 2, anchorRight = 1)
+            grid.setField(Label(network.bridgeName()), 1, 2, anchorLeft = 1)
+        return [Label("Network Interface Details"),
+                grid]
+
+def ListNetworks():
+    screen = ListNetworksConfigScreen()
+    screen.start()
diff --git a/nodeadmin/mainmenu.py b/nodeadmin/mainmenu.py
index 497ad57..73501fa 100755
--- a/nodeadmin/mainmenu.py
+++ b/nodeadmin/mainmenu.py
@@ -18,57 +18,30 @@
 
 from snack import *
 import traceback
-from configscreen   import ConfigScreen
-from definedomain   import DefineDomain
-from createdomain   import CreateDomain
-from destroydomain  import DestroyDomain
-from undefinedomain import UndefineDomain
-from listdomains    import ListDomains
-from createuser     import CreateUser
+
+from menuscreen     import MenuScreen
+from nodemenu       import NodeMenu
+from netmenu        import NetworkMenu
+
 import utils
 import logging
 
-DEFINE_DOMAIN    = 1
-CREATE_DOMAIN    = 2
-DESTROY_DOMAIN   = 3
-UNDEFINE_DOMAIN  = 4
-LIST_DOMAINS     = 5
-CREATE_USER      = 6
-EXIT_CONSOLE     = 99
+NODE_MENU    = 1
+NETWORK_MENU = 2
+EXIT_CONSOLE = 99
 
-def MainMenu():
-    finished = False
-    while finished == False:
-        screen = SnackScreen()
-        menu = Listbox(height = 0, width = 0, returnExit = 1)
-        menu.append("Define A Domain",     DEFINE_DOMAIN)
-        menu.append("Create A Domain",     CREATE_DOMAIN)
-        menu.append("Destroy A Domain",    DESTROY_DOMAIN)
-        menu.append("Undefine A Domain",   UNDEFINE_DOMAIN)
-        menu.append("List All Domains",    LIST_DOMAINS)
-        menu.append("Create A User",       CREATE_USER)
-        menu.append("Exit Administration", EXIT_CONSOLE)
-        gridform = GridForm(screen, "Node Administration Console", 1, 4)
-        gridform.add(menu, 0, 0)
-        result = gridform.run();
-        screen.popWindow()
-        screen.finish()
+class MainMenuScreen(MenuScreen):
+    def __init__(self):
+        MenuScreen.__init__(self, "Main Menu")
 
-        try:
-            if   result.current() == DEFINE_DOMAIN:   DefineDomain()
-            elif result.current() == CREATE_DOMAIN:   CreateDomain()
-            elif result.current() == DESTROY_DOMAIN:  DestroyDomain()
-            elif result.current() == UNDEFINE_DOMAIN: UndefineDomain()
-            elif result.current() == LIST_DOMAINS:    ListDomains()
-            elif result.current() == CREATE_USER:     CreateUser()
-            elif result.current() == EXIT_CONSOLE:    finished = True
-        except Exception, error:
-            screen = SnackScreen()
-            logging.info("An exception occurred: %s" % str(error))
-            ButtonChoiceWindow(screen,
-                               "An Exception Has Occurred",
-                               str(error) + "\n" + traceback.format_exc(),
-                               buttons = ["OK"])
-            screen.popWindow()
-            screen.finish()
-            finished = True
+    def get_menu_items(self):
+        return (("Node Administration", NODE_MENU),
+                ("Network Administration", NETWORK_MENU))
+
+    def handle_selection(self, page):
+        if   page is NODE_MENU:    NodeMenu()
+        elif page is NETWORK_MENU: NetworkMenu()
+
+def MainMenu():
+    screen = MainMenuScreen()
+    screen.start()
diff --git a/nodeadmin/menuscreen.py b/nodeadmin/menuscreen.py
new file mode 100644
index 0000000..1700e8c
--- /dev/null
+++ b/nodeadmin/menuscreen.py
@@ -0,0 +1,57 @@
+# mainmenu.py - Copyright (C) 2009 Red Hat, Inc.
+# Written by Darryl L. Pierce <[email protected]>
+#
+# 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; version 2 of the License.
+#
+# 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.  A copy of the GNU General Public License is
+# also available at http://www.gnu.org/copyleft/gpl.html.
+
+from snack import *
+import traceback
+
+import utils
+import logging
+
+EXIT_MENU = 99
+
+class MenuScreen:
+    def __init__(self, title):
+        self.__title = title
+
+    def start(self):
+        finished = False
+        while finished == False:
+            screen = SnackScreen()
+            menu = Listbox(height = 0, width = 0, returnExit = 1)
+            for menu_item in self.get_menu_items():
+                menu.append(menu_item[0], menu_item[1])
+            menu.append("Exit Menu", EXIT_MENU)
+            gridform = GridForm(screen, self.__title, 1, 4)
+            gridform.add(menu, 0, 0)
+            result = gridform.run();
+            screen.popWindow()
+            screen.finish()
+
+            try:
+                if result.current() == EXIT_MENU: finished = True
+                else: self.handle_selection(result.current())
+            except Exception, error:
+                screen = SnackScreen()
+                logging.info("An exception occurred: %s" % str(error))
+                ButtonChoiceWindow(screen,
+                                   "An Exception Has Occurred",
+                                   str(error) + "\n" + traceback.format_exc(),
+                                   buttons = ["OK"])
+                screen.popWindow()
+                screen.finish()
+                finished = True
diff --git a/nodeadmin/netmenu.py b/nodeadmin/netmenu.py
new file mode 100755
index 0000000..88e159f
--- /dev/null
+++ b/nodeadmin/netmenu.py
@@ -0,0 +1,58 @@
+# mainmenu.py - Copyright (C) 2009 Red Hat, Inc.
+# Written by Darryl L. Pierce <[email protected]>
+#
+# 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; version 2 of the License.
+#
+# 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.  A copy of the GNU General Public License is
+# also available at http://www.gnu.org/copyleft/gpl.html.
+
+from snack import *
+import traceback
+
+from menuscreen      import MenuScreen
+from definenet       import DefineNetwork
+from createnetwork   import CreateNetwork
+from destroynetwork  import DestroyNetwork
+from undefinenetwork import UndefineNetwork
+from listnetworks    import ListNetworks
+
+import utils
+import logging
+
+DEFINE_NETWORK   = 1
+CREATE_NETWORK   = 2
+DESTROY_NETWORK  = 3
+UNDEFINE_NETWORK = 4
+LIST_NETWORKS    = 5
+
+class NetworkMenuScreen(MenuScreen):
+    def __init__(self):
+        MenuScreen.__init__(self, "Network Administration")
+
+    def get_menu_items(self):
+        return (("Define A Network",   DEFINE_NETWORK),
+                ("Create A Network",   CREATE_NETWORK),
+                ("Destroy A Network",  DESTROY_NETWORK),
+                ("Undefine A Network", UNDEFINE_NETWORK),
+                ("List Networks",      LIST_NETWORKS))
+
+    def handle_selection(self, item):
+        if   item is DEFINE_NETWORK:   DefineNetwork()
+        elif item is CREATE_NETWORK:   CreateNetwork()
+        elif item is DESTROY_NETWORK:  DestroyNetwork()
+        elif item is UNDEFINE_NETWORK: UndefineNetwork()
+        elif item is LIST_NETWORKS:    ListNetworks()
+
+def NetworkMenu():
+    screen = NetworkMenuScreen()
+    screen.start()
diff --git a/nodeadmin/networkconfig.py b/nodeadmin/networkconfig.py
new file mode 100644
index 0000000..57d184d
--- /dev/null
+++ b/nodeadmin/networkconfig.py
@@ -0,0 +1,99 @@
+# networkconfig.py - Copyright (C) 2009 Red Hat, Inc.
+# Written by Darryl L. Pierce <[email protected]>
+#
+# 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; version 2 of the License.
+#
+# 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.  A copy of the GNU General Public License is
+# also available at http://www.gnu.org/copyleft/gpl.html.
+
+from IPy import IP
+import logging
+
+class NetworkConfig:
+    def __init__(self):
+        self.__name = ""
+        self.set_ipv4_address("192.168.100.0/24")
+        self.__isolated_network = True
+        self.__physical_device = ""
+
+    def set_name(self, name):
+        self.__name = name
+
+    def get_name(self):
+        return self.__name
+
+    def set_ipv4_address(self, address):
+        self.__ipv4_address = IP(address)
+        start = int(self.__ipv4_address.len() / 2)
+        end   = self.__ipv4_address.len() - 2
+        self.__ipv4_start = str(self.__ipv4_address[start])
+        self.__ipv4_end   = str(self.__ipv4_address[end])
+
+    def get_ipv4_address(self):
+        return self.__ipv4_address.strNormal()
+
+    def get_ipv4_address_raw(self):
+        return self.__ipv4_address
+
+    def get_ipv4_netmask(self):
+        return self.__ipv4_address.netmask().strNormal()
+
+    def get_ipv4_broadcast(self):
+        return self.__ipv4_address.broadcast().strNormal()
+
+    def get_ipv4_gateway(self):
+        return str(self.__ipv4_address[1])
+
+    def get_ipv4_max_addresses(self):
+        return self.__ipv4_address.len()
+
+    def get_ipv4_network_type(self):
+        return self.__ipv4_address.iptype()
+
+    def is_public_ipv4_network(self):
+        if self.__ipv4_address.iptype() is "PUBLIC":
+            return True
+        return False
+
+    def set_ipv4_start_address(self, address):
+        self.__ipv4_start = address
+
+    def get_ipv4_start_address(self):
+        return self.__ipv4_start
+
+    def set_ipv4_end_address(self, address):
+        self.__ipv4_end = address
+
+    def get_ipv4_end_address(self):
+        return self.__ipv4_end
+
+    def is_bad_address(self, address):
+        return not self.__ipv4_address.overlaps(address)
+
+    def set_isolated_network(self, isolated):
+        self.__isolated_network = isolated
+
+    def is_isolated_network(self):
+        return self.__isolated_network
+
+    def set_physical_device(self, device):
+        self.__physical_device = device
+
+    def get_physical_device(self):
+        return self.__physical_device
+
+    def get_physical_device_text(self):
+        if self.__physical_device == "":
+            return "any physical device"
+        else:
+            return "physical device %s" % self.__physical_device
diff --git a/nodeadmin/nodemenu.py b/nodeadmin/nodemenu.py
new file mode 100755
index 0000000..9e339ff
--- /dev/null
+++ b/nodeadmin/nodemenu.py
@@ -0,0 +1,63 @@
+# mainmenu.py - Copyright (C) 2009 Red Hat, Inc.
+# Written by Darryl L. Pierce <[email protected]>
+#
+# 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; version 2 of the License.
+#
+# 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.  A copy of the GNU General Public License is
+# also available at http://www.gnu.org/copyleft/gpl.html.
+
+from snack import *
+import traceback
+
+from menuscreen     import MenuScreen
+from configscreen   import ConfigScreen
+from definedomain   import DefineDomain
+from createdomain   import CreateDomain
+from destroydomain  import DestroyDomain
+from undefinedomain import UndefineDomain
+from listdomains    import ListDomains
+from createuser     import CreateUser
+
+import utils
+import logging
+
+DEFINE_DOMAIN    = 1
+CREATE_DOMAIN    = 2
+DESTROY_DOMAIN   = 3
+UNDEFINE_DOMAIN  = 4
+LIST_DOMAINS     = 5
+CREATE_USER      = 6
+
+class NodeMenuScreen(MenuScreen):
+    def __init__(self):
+        MenuScreen.__init__(self, "Node Administration")
+
+    def get_menu_items(self):
+        return (("Define A Domain",        DEFINE_DOMAIN),
+                ("Create A Domain",        CREATE_DOMAIN),
+                ("Destroy A Domain",       DESTROY_DOMAIN),
+                ("Undefine A Domain",      UNDEFINE_DOMAIN),
+                ("List All Domains",       LIST_DOMAINS),
+                ("Create A User",          CREATE_USER))
+
+    def handle_selection(self, item):
+            if   item is DEFINE_DOMAIN:   DefineDomain()
+            elif item is CREATE_DOMAIN:   CreateDomain()
+            elif item is DESTROY_DOMAIN:  DestroyDomain()
+            elif item is UNDEFINE_DOMAIN: UndefineDomain()
+            elif item is LIST_DOMAINS:    ListDomains()
+            elif item is CREATE_USER:     CreateUser()
+
+def NodeMenu():
+    screen = NodeMenuScreen()
+    screen.start()
diff --git a/nodeadmin/setup.py.in b/nodeadmin/setup.py.in
index f51a34c..3635810 100644
--- a/nodeadmin/setup.py.in
+++ b/nodeadmin/setup.py.in
@@ -30,5 +30,10 @@ setup(name = "nodeadmin",
             'destroydom  = nodeadmin.destroydomain:DestroyDomain',
             'undefinedom = nodeadmin.undefinedomain:UndefineDomain',
             'createuser  = nodeadmin.createuser:CreateUser',
-            'listdoms    = nodeadmin.listdomains:ListDomains']
+            'listdoms    = nodeadmin.listdomains:ListDomains',
+            'definenet   = nodeadmin.definenet:DefineNetwork',
+            'createnet   = nodeadmin.createnetwork:CreateNetwork',
+            'destroynet  = nodeadmin.destroynetwork:DestroyNetwork',
+            'undefinenet = nodeadmin.undefinenetwork:UndefineNetwork',
+            'listnets    = nodeadmin.listnetworks:ListNetworks']
         })
diff --git a/nodeadmin/undefinenetwork.py b/nodeadmin/undefinenetwork.py
new file mode 100644
index 0000000..f71bd20
--- /dev/null
+++ b/nodeadmin/undefinenetwork.py
@@ -0,0 +1,88 @@
+#!/usr/bin/env python
+#
+# undefinenetwork.py - Copyright (C) 2009 Red Hat, Inc.
+# Written by Darryl L. Pierce <[email protected]>
+#
+# 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; version 2 of the License.
+#
+# 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.  A copy of the GNU General Public License is
+# also available at http://www.gnu.org/copyleft/gpl.html.
+
+from snack import *
+from configscreen import *
+
+LIST_PAGE     = 1
+CONFIRM_PAGE  = 2
+UNDEFINE_PAGE = 3
+
+class UndefineNetworkConfigScreen(NetworkListConfigScreen):
+    def __init__(self):
+        NetworkListConfigScreen.__init__(self, "Undefine A Network")
+
+    def get_elements_for_page(self, screen, page):
+        if   page is LIST_PAGE:     return self.get_network_list_page(screen, 
created = False)
+        elif page is CONFIRM_PAGE:  return self.get_confirm_page(screen)
+        elif page is UNDEFINE_PAGE: return 
self.get_undefine_network_page(screen)
+
+    def process_input(self, page, errors):
+        if page is LIST_PAGE:
+            network = self.get_selected_network()
+            self.get_libvirt().undefine_network(network)
+            return True
+
+    def page_has_next(self, page):
+        if page is LIST_PAGE:    return self.has_selectable_networks()
+        if page is CONFIRM_PAGE: return True
+        return False
+
+    def page_has_back(self, page):
+        if page is CONFIRM_PAGE: return True
+        if page is UNDEFINE_PAGE: return True
+        return False
+
+    def get_back_page(self, page):
+        if   page is CONFIRM_PAGE: return LIST_PAGE
+        elif page is UNDEFINE_PAGE: return LIST_PAGE
+
+    def validate_input(self, page, errors):
+        if   page is LIST_PAGE: return True
+        elif page is CONFIRM_PAGE:
+            if self.__confirm_undefine.value():
+                return True
+            else:
+                errors.append("You must confirm undefining %s." % 
self.get_selected_network())
+        elif page is UNDEFINE_PAGE: return True
+        return False
+
+    def process_input(self, page):
+        if   page is LIST_PAGE:     return True
+        elif page is CONFIRM_PAGE:
+            network = self.get_selected_network()
+            self.get_libvirt().undefine_network(network)
+            return True
+        elif page is UNDEFINE_PAGE: return True
+        return False
+
+    def get_confirm_page(self, screen):
+        self.__confirm_undefine = Checkbox("Check here to confirm undefining 
%s." % self.get_selected_network(), 0)
+        grid = Grid(1, 1)
+        grid.setField(self.__confirm_undefine, 0, 0)
+        return [grid]
+
+    def get_undefine_network_page(self, screen):
+        return [Label("Network Is Undefined"),
+                Label("%s has been undefined." % self.get_selected_network())]
+
+def UndefineNetwork():
+    screen = UndefineNetworkConfigScreen()
+    screen.start()
diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in
index ee1942b..2a6b7b6 100644
--- a/ovirt-node.spec.in
+++ b/ovirt-node.spec.in
@@ -51,6 +51,7 @@ Requires:       anyterm
 Requires:       newt-python
 Requires:       libuser-python
 Requires:       dbus-python
+Requires:       python-IPy
 
 ExclusiveArch:  %{ix86} x86_64
 
@@ -175,19 +176,32 @@ cd -
 
 %{__install} -p -m0644 nodeadmin/__init__.py 
%{buildroot}%{python_sitelib}/nodeadmin
 %{__install} -p -m0644 nodeadmin/configscreen.py 
%{buildroot}%{python_sitelib}/nodeadmin
-%{__install} -p -m0755 nodeadmin/createdomain.py 
%{buildroot}%{python_sitelib}/nodeadmin
-%{__install} -p -m0755 nodeadmin/createuser.py 
%{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0644 nodeadmin/menuscreen.py 
%{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0755 nodeadmin/utils.py 
%{buildroot}%{python_sitelib}/nodeadmin
+
+%{__install} -p -m0755 nodeadmin/nodeadmin.py 
%{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0644 nodeadmin/mainmenu.py 
%{buildroot}%{python_sitelib}/nodeadmin
+
+%{__install} -p -m0644 nodeadmin/nodemenu.py 
%{buildroot}%{python_sitelib}/nodeadmin
 %{__install} -p -m0755 nodeadmin/definedomain.py 
%{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0755 nodeadmin/createdomain.py 
%{buildroot}%{python_sitelib}/nodeadmin
 %{__install} -p -m0755 nodeadmin/destroydomain.py 
%{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0755 nodeadmin/undefinedomain.py 
%{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0755 nodeadmin/listdomains.py 
%{buildroot}%{python_sitelib}/nodeadmin
 %{__install} -p -m0644 nodeadmin/domainconfig.py 
%{buildroot}%{python_sitelib}/nodeadmin
+
+%{__install} -p -m0644 nodeadmin/netmenu.py 
%{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0644 nodeadmin/networkconfig.py 
%{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0755 nodeadmin/definenet.py 
%{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0755 nodeadmin/createnetwork.py 
%{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0755 nodeadmin/destroynetwork.py 
%{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0755 nodeadmin/undefinenetwork.py 
%{buildroot}%{python_sitelib}/nodeadmin
+
+%{__install} -p -m0755 nodeadmin/createuser.py 
%{buildroot}%{python_sitelib}/nodeadmin
+
 %{__install} -p -m0644 nodeadmin/halworker.py 
%{buildroot}%{python_sitelib}/nodeadmin
 %{__install} -p -m0644 nodeadmin/libvirtworker.py 
%{buildroot}%{python_sitelib}/nodeadmin
 %{__install} -p -m0644 nodeadmin/userworker.py 
%{buildroot}%{python_sitelib}/nodeadmin
-%{__install} -p -m0755 nodeadmin/listdomains.py 
%{buildroot}%{python_sitelib}/nodeadmin
-%{__install} -p -m0644 nodeadmin/mainmenu.py 
%{buildroot}%{python_sitelib}/nodeadmin
-%{__install} -p -m0755 nodeadmin/nodeadmin.py 
%{buildroot}%{python_sitelib}/nodeadmin
-%{__install} -p -m0755 nodeadmin/undefinedomain.py 
%{buildroot}%{python_sitelib}/nodeadmin
-%{__install} -p -m0755 nodeadmin/utils.py 
%{buildroot}%{python_sitelib}/nodeadmin
 
 # gptsync
 %{__install} -p -m0755 gptsync/gptsync %{buildroot}%{_sbindir}
@@ -360,6 +374,11 @@ fi
 %{_bindir}/destroydom
 %{_bindir}/undefinedom
 %{_bindir}/listdoms
+%{_bindir}/definenet
+%{_bindir}/createnet
+%{_bindir}/destroynet
+%{_bindir}/undefinenet
+%{_bindir}/listnets
 %{_bindir}/createuser
 %{_sysconfdir}/collectd.conf.in
 %{python_sitelib}/nodeadmin
-- 
1.6.2.5

_______________________________________________
Ovirt-devel mailing list
[email protected]
https://www.redhat.com/mailman/listinfo/ovirt-devel

Reply via email to