[Ovirt-devel] [PATCH node] Introduces the virtual network administration functions.

Mike Burns mburns at redhat.com
Tue Sep 29 21:12:58 UTC 2009


On Wed, Sep 23, 2009 at 05:12:46PM -0400, Darryl L. Pierce wrote:
> 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 <dpierce at redhat.com>
> ---
>  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 <dpierce at redhat.com>
> +#
> +# 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 <dpierce at redhat.com>
> +#
> +# 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)

If possible we might want to expand the size of these fields 1 char.  If you have a max width IP address, the first char gets cut off from the display.  Functionally, this is no impact, but it was confusing at first glance.


> +        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)

On my vm, this label was too wide for the screen.  I'm not sure if this is only my system, but we might want to split into multiple lines.

> +        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 <dpierce at redhat.com>
> +#
> +# 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 <dpierce at redhat.com>
> +#
> +# 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 <dpierce at redhat.com>
> +#
> +# 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 <dpierce at redhat.com>
> +#
> +# 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 <dpierce at redhat.com>
> +#
> +# 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 <dpierce at redhat.com>
> +#
> +# 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 <dpierce at redhat.com>
> +#
> +# 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())]
> +

This label is awkward.  Something like "Network %s has been undefined." seems better to me.

> +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
> Ovirt-devel at redhat.com
> https://www.redhat.com/mailman/listinfo/ovirt-devel
> 


This seems pretty good.  There are a couple of nitpicks inline.  The only major thing I found is that we need to add /var/lib/dnsmasq to the r/o filesystem overrides.  If we don't we can't create or destroy networks.  

I tested defining and undefining networks, but without above entry to rwtab, i can't create/destroy networks.  I'll try rebuilding it with entry in rwtab, but wanted to get comments out first.

Oh, side note, I'm not an expert at python by any stretch, so most of the testing was done using black box techniques.

Mike




More information about the ovirt-devel mailing list