[Ovirt-devel] [PATCH 1/2] Provides a new storage administration system to the managed node.

Joey Boggs jboggs at redhat.com
Wed Oct 28 01:13:42 UTC 2009


Darryl L. Pierce wrote:
> Users can now:
>  * Add a new storage pool.
>  * Delete a storage pool.
>  * Start and stop storage pools.
>  * Add a new storage volume.
>  * Delete a storage volume.
>  * List existing storage pools, with details.
>
> Signed-off-by: Darryl L. Pierce <dpierce at redhat.com>
> ---
>  Makefile.am                |   28 +++++--
>  nodeadmin/adddomain.py     |   15 +---
>  nodeadmin/addpool.py       |  182 ++++++++++++++++++++++++++++++++++++++++++++
>  nodeadmin/addvolume.py     |  160 ++++++++++++++++++++++++++++++++++++++
>  nodeadmin/configscreen.py  |   52 +++++++++++++
>  nodeadmin/createmeter.py   |   30 +++++++
>  nodeadmin/libvirtworker.py |   67 +++++++++++++++--
>  nodeadmin/listpools.py     |   63 +++++++++++++++
>  nodeadmin/mainmenu.py      |   24 ++++---
>  nodeadmin/poolconfig.py    |  137 +++++++++++++++++++++++++++++++++
>  nodeadmin/removepool.py    |   72 +++++++++++++++++
>  nodeadmin/removevolume.py  |   76 ++++++++++++++++++
>  nodeadmin/setup.py.in      |    9 ++-
>  nodeadmin/startpool.py     |   62 +++++++++++++++
>  nodeadmin/stoppool.py      |   62 +++++++++++++++
>  nodeadmin/storagemenu.py   |   63 +++++++++++++++
>  nodeadmin/utils.py         |   10 +++
>  nodeadmin/volumeconfig.py  |   76 ++++++++++++++++++
>  ovirt-node.spec.in         |    7 ++
>  19 files changed, 1155 insertions(+), 40 deletions(-)
>  create mode 100644 nodeadmin/addpool.py
>  create mode 100644 nodeadmin/addvolume.py
>  create mode 100644 nodeadmin/createmeter.py
>  create mode 100644 nodeadmin/listpools.py
>  create mode 100644 nodeadmin/poolconfig.py
>  create mode 100644 nodeadmin/removepool.py
>  create mode 100644 nodeadmin/removevolume.py
>  create mode 100644 nodeadmin/startpool.py
>  create mode 100644 nodeadmin/stoppool.py
>  create mode 100644 nodeadmin/storagemenu.py
>  create mode 100644 nodeadmin/volumeconfig.py
>
> diff --git a/Makefile.am b/Makefile.am
> index 3ce24c1..55ef277 100644
> --- a/Makefile.am
> +++ b/Makefile.am
> @@ -28,29 +28,39 @@ EXTRA_DIST =			\
>    images/syslinux-vesa-splash.jpg	\
>    nodeadmin/__init__.py         \
>    nodeadmin/adddomain.py        \
> +  nodeadmin/addpool.py          \
> +  nodeadmin/addvolume.py        \
>    nodeadmin/configscreen.py     \
> +  nodeadmin/createmeter.py      \
>    nodeadmin/createnetwork.py    \
>    nodeadmin/createuser.py       \
> +  nodeadmin/definenet.py        \
>    nodeadmin/destroynetwork.py   \
> +  nodeadmin/domainconfig.py     \
>    nodeadmin/halworker.py        \
>    nodeadmin/libvirtworker.py    \
> -  nodeadmin/userworker.py       \
> +  nodeadmin/listdomains.py      \
> +  nodeadmin/listnetworks.py     \
> +  nodeadmin/listpools.py        \
>    nodeadmin/mainmenu.py         \
>    nodeadmin/menuscreen.py       \
> +  nodeadmin/networkconfig.py    \
>    nodeadmin/netmenu.py          \
> +  nodeadmin/nodeadmin.py        \
>    nodeadmin/nodemenu.py         \
>    nodeadmin/removedomain.py     \
> -  nodeadmin/undefinenetwork.py  \
> +  nodeadmin/removepool.py       \
> +  nodeadmin/removevolume.py     \
> +  nodeadmin/setup.py            \
>    nodeadmin/startdomain.py      \
> +  nodeadmin/startpool.py        \
>    nodeadmin/stopdomain.py       \
> -  nodeadmin/definenet.py        \
> -  nodeadmin/domainconfig.py     \
> -  nodeadmin/networkconfig.py    \
> -  nodeadmin/listdomains.py      \
> -  nodeadmin/listnetworks.py     \
> -  nodeadmin/nodeadmin.py        \
> -  nodeadmin/setup.py            \
> +  nodeadmin/stoppool.py         \
> +  nodeadmin/storagemenu.py      \
> +  nodeadmin/undefinenetwork.py  \
> +  nodeadmin/userworker.py       \
>    nodeadmin/utils.py            \
> +  nodeadmin/volumeconfig.py     \
>    scripts/collectd.conf.in	\
>    scripts/ovirt			\
>    scripts/ovirt-awake		\
> diff --git a/nodeadmin/adddomain.py b/nodeadmin/adddomain.py
> index 70a2011..bb06a62 100755
> --- a/nodeadmin/adddomain.py
> +++ b/nodeadmin/adddomain.py
> @@ -20,11 +20,10 @@
>  
>  from snack import *
>  import os
> +from createmeter  import CreateMeter
>  from domainconfig import DomainConfig
>  from configscreen import ConfigScreen
> -import urlgrabber.progress as progress
>  import utils
> -import logging
>  
>  from virtinst import *
>  
> @@ -51,16 +50,6 @@ OS_VARIANT="os.variant"
>  MEMORY="memory"
>  CPUS="cpus"
>  
> -class DummyMeter(progress.BaseMeter):
> -    def _do_start(self, now = None):
> -        logging.info("Starting...")
> -
> -    def _do_end(self, amount_read, now = None):
> -        logging.info("Ending: read=%d" % amount_read)
> -
> -    def _do_update(self, amount_read, now = None):
> -        logging.info("Update: read=%d" % amount_read)
> -
>  class DomainConfigScreen(ConfigScreen):
>      def __init__(self):
>          ConfigScreen.__init__(self, "Create A New Virtual Machine")
> @@ -212,7 +201,7 @@ class DomainConfigScreen(ConfigScreen):
>              self.__config.set_virt_type(self.__virt_types.getSelection())
>              self.__config.set_architecture(self.__architectures.getSelection())
>          elif page == CONFIRM_PAGE:
> -            self.get_libvirt().define_domain(self.__config, DummyMeter())
> +            self.get_libvirt().define_domain(self.__config, CreateMeter())
>              self.set_finished()
>  
>      def get_back_page(self, page):
> diff --git a/nodeadmin/addpool.py b/nodeadmin/addpool.py
> new file mode 100644
> index 0000000..389be52
> --- /dev/null
> +++ b/nodeadmin/addpool.py
> @@ -0,0 +1,182 @@
> +# addstorage.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
> +
> +from configscreen import *
> +from poolconfig import PoolConfig
> +from virtinst import Storage
> +
> +POOL_NAME_PAGE    = 1
> +POOL_DETAILS_PAGE = 2
> +CONFIRM_PAGE      = 3
> +
> +class AddStoragePoolConfigScreen(ConfigScreen):
> +    def __init__(self):
> +        ConfigScreen.__init__(self, "Add A Storage Pool")
> +        self.__config = PoolConfig(self.get_libvirt())
> +
> +    def get_elements_for_page(self, screen, page):
> +        if   page is POOL_NAME_PAGE:    return self.get_pool_name_page(screen)
> +        elif page is POOL_DETAILS_PAGE: return self.get_pool_details_page(screen)
> +        elif page is CONFIRM_PAGE:      return self.get_confirm_page(screen)
> +
> +    def page_has_next(self, page):
> +        return page < CONFIRM_PAGE
> +
> +    def page_has_back(self, page):
> +        return page > POOL_NAME_PAGE
> +
> +    def page_has_finish(self, page):
> +        return page is CONFIRM_PAGE
> +
> +    def validate_input(self, page, errors):
> +        if   page is POOL_NAME_PAGE:
> +            if utils.string_is_not_blank(self.__name.value()):
> +                if self.get_libvirt().storage_pool_exists(self.__name.value()):
> +                    errors.append("Name '%s' already in use by another pool." % self.__name.value())
> +                else:
> +                    return True
> +            else:
> +                errors.append("Storage object name must be a string between 0 and 50 characters.")
> +        elif page is POOL_DETAILS_PAGE:
> +            result = True
> +            if self.__config.needs_target_path():
> +                if utils.string_is_not_blank(self.__target_path.value()):
> +                    if self.__target_path.value()[0:1] is not '/':
> +                        errors.append("'%s' is not an absolute path." % self.__target_path.value())
> +                        result = False
> +                else:
> +                    errors.append("You must enter a target path.")
> +                    result = False
> +            if self.__config.needs_format():
> +                if self.__formats.getSelection() is None:
> +                    errors.append("You must select a pool format.")
> +                    result = False
> +            if self.__config.needs_hostname():
> +                if utils.string_is_not_blank(self.__hostname.value()):
> +                    errors.append("You must enter a hostname.")
> +                    result = False
> +            if self.__config.needs_source_path():
> +                if utils.string_is_not_blank(self.__source_path.value()):
> +                    if self.__source_path.value()[0:1] is not '/':
> +                        errors.append("'%s' is not an absolute path." % self.__source_path.value())
> +                        result = False
> +                else:
> +                    errors.append("you  must enter a source path.")
> +                    result = False
> +            return result
> +        elif page is CONFIRM_PAGE: return True
> +        return False
> +
> +    def process_input(self, page):
> +        if page is POOL_NAME_PAGE:
> +            self.__config.set_name(self.__name.value())
> +            self.__config.set_type(self.__type.getSelection())
> +            #self._reset_flags(self.__type.current())
> +        elif page is POOL_DETAILS_PAGE:
> +            if self.__config.needs_target_path():
> +                self.__config.set_target_path(self.__target_path.value())
> +            if self.__config.needs_format():
> +                self.__config.set_format(self.__formats.getSelection())
> +            if self.__config.needs_hostname():
> +                self.__config.set_hostname(self.__hostname.value())
> +            if self.__config.needs_source_path():
> +                self.__config.set_source_path(self.__source_path.value())
> +            if self.__config.needs_build_pool():
> +                self.__config.set_build_pool(self.__build_pool.value())
> +        elif page is CONFIRM_PAGE:
> +            self.get_libvirt().define_storage_pool(self.__config.get_name(), config = self.__config)
> +            self.get_libvirt().create_storage_pool(self.__config.get_name())
> +            self.set_finished()
> +
> +    def get_pool_name_page(self, screen):
> +        self.__name = Entry(50, self.__config.get_name())
> +        pooltypes = []
> +        for pooltype in Storage.StoragePool.get_pool_types():
> +            pooltypes.append(["%s: %s" % (pooltype, Storage.StoragePool.get_pool_type_desc(pooltype)),
> +                              pooltype,
> +                              self.__config.get_type() is pooltype])
> +        self.__type = RadioBar(screen, pooltypes)
> +        grid = Grid(2, 2)
> +        grid.setField(Label("Name:"), 0, 0, anchorRight = 1)
> +        grid.setField(self.__name, 1, 0, anchorLeft = 1)
> +        grid.setField(Label("Type:"), 0, 1, anchorRight = 1, anchorTop = 1)
> +        grid.setField(self.__type, 1, 1, anchorLeft = 1)
> +        return [Label("Add Storage Pool"),
> +                grid]
> +
> +    def get_pool_details_page(self, screen):
> +        rows = 0
> +        if self.__config.needs_target_path():
> +            self.__target_path = Entry(50, self.__config.get_target_path())
> +            rows += 1
> +        if self.__config.needs_format():
> +            formats = []
> +            for format in self.__config.get_formats():
> +                formats.append([format, format, format is self.__config.get_format()])
> +            self.__formats = RadioBar(screen, formats)
> +            rows += 1
> +        if self.__config.needs_hostname():
> +            self.__hostname = Entry(50, self.__config.get_hostname())
> +            rows += 1
> +        if self.__config.needs_source_path():
> +            self.__source_path = Entry(50, self.__config.get_source_path())
> +            rows += 1
> +        if self.__config.needs_build_pool():
> +            self.__build_pool = Checkbox("Build Pool", self.__config.get_build_pool())
> +            rows += 1
> +        grid = Grid(2, rows)
> +        currentrow = 0
> +        if self.__config.needs_target_path():
> +            grid.setField(Label("Target Path:"), 0, currentrow, anchorRight = 1)
> +            grid.setField(self.__target_path, 1, currentrow, anchorLeft = 1)
> +            currentrow += 1
> +        if self.__config.needs_format():
> +            grid.setField(Label("Format:"), 0, currentrow, anchorRight = 1, anchorTop = 1)
> +            grid.setField(self.__formats, 1, currentrow, anchorLeft = 1)
> +            currentrow += 1
> +        if self.__config.needs_hostname():
> +            grid.setField(Label("Host Name:"), 0, currentrow, anchorRight = 1)
> +            grid.setField(self.__hostname, 1, currentrow, anchorRight = 1)
> +            currentrow += 1
> +        if self.__config.needs_source_path():
> +            grid.setField(Label("Source Path:"), 0, currentrow, anchorRight = 1)
> +            grid.setField(self.__source_path, 1, currentrow, anchorLeft = 1)
> +            currentrow += 1
> +        if self.__config.needs_build_pool():
> +            grid.setField(Label(" "), 0, currentrow, anchorRight = 1)
> +            grid.setField(self.__build_pool, 1, currentrow, anchorLeft = 1)
> +            currentrow += 1
> +        return [Label("Specify a storage location to be later split into virtual machine storage"),
> +                grid]
> +
> +    def get_confirm_page(self, screen):
> +        grid = Grid(2, 2)
> +        grid.setField(Label("Name:"), 0, 0, anchorRight = 1)
> +        grid.setField(Label(self.__config.get_name()), 1, 0, anchorLeft = 1)
> +        grid.setField(Label("Target Path:"), 0, 1, anchorRight = 1)
> +        grid.setField(Label(self.__config.get_target_path()), 1, 1, anchorLeft = 1)
> +        return [Label("Confirm Pool Details"),
> +                grid]
> +
> +def AddStoragePool():
> +    screen = AddStoragePoolConfigScreen()
> +    screen.start()
> diff --git a/nodeadmin/addvolume.py b/nodeadmin/addvolume.py
> new file mode 100644
> index 0000000..82c014c
> --- /dev/null
> +++ b/nodeadmin/addvolume.py
> @@ -0,0 +1,160 @@
> +# addvolume.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 createmeter import CreateMeter
> +from configscreen import *
> +from volumeconfig import StorageVolumeConfig
> +from utils import *
> +
> +SELECT_POOL_PAGE   = 1
> +VOLUME_NAME_PAGE   = 2
> +VOLUME_FORMAT_PAGE = 3
> +MAX_CAPACITY_PAGE  = 4
> +CONFIRM_PAGE       = 5
> +
> +class AddVolumeConfigScreen(StorageListConfigScreen):
> +    def __init__(self):
> +        StorageListConfigScreen.__init__(self, "Add A New Storage Volume")
> +        self.__config = StorageVolumeConfig()
> +
> +    def get_elements_for_page(self, screen, page):
> +        if   page is SELECT_POOL_PAGE:   return self.get_storage_pool_list_page(screen)
> +        elif page is VOLUME_NAME_PAGE:   return self.get_volume_name_page(screen)
> +        elif page is VOLUME_FORMAT_PAGE: return self.get_volume_format_page(screen)
> +        elif page is MAX_CAPACITY_PAGE:  return self.get_max_capacity_page(screen)
> +        elif page is CONFIRM_PAGE:       return self.get_confirm_page(screen)
> +
> +    def page_has_next(self, page):
> +        if page is SELECT_POOL_PAGE:
> +            return self.has_selectable_pools()
> +        else:
> +            if page < CONFIRM_PAGE: return True
> +        return False
> +
> +    def page_has_back(self, page):
> +        if page > SELECT_POOL_PAGE: return True
> +        return False
> +
> +    def page_has_finish(self, page):
> +        return page is CONFIRM_PAGE
> +
> +    def validate_input(self, page, errors):
> +        if page is SELECT_POOL_PAGE:
> +            if self.get_selected_pool() is not None:
> +                return True
> +            else:
> +                errors.append("You must select a storage pool.")
> +        elif page is VOLUME_NAME_PAGE:
> +            if string_is_not_blank(self.__name.value()):
> +                return True
> +            else:
> +                errors.append("Storage object name can only contain alphanumeric, '_', '.', or '-' characters.")
> +        elif page is VOLUME_FORMAT_PAGE:
> +            if self.__formats.current() is not None:
> +                return True
> +            else:
> +                errors.append("You must select a volume format.")
> +        elif page is MAX_CAPACITY_PAGE:
> +            if string_is_not_blank(self.__capacity.value()):
> +                if string_is_not_blank(self.__allocation.value()):
> +                    capacity = int(self.__capacity.value())
> +                    allocation = int(self.__allocation.value())
> +                    if capacity > 0:
> +                        if capacity <= self.__config.get_pool().info()[3] / 1024**2:
> +                            if allocation >= 0:
> +                                if allocation <= capacity:
> +                                    return True
> +                                else:
> +                                    errors.append("Allocation cannot exceed the maximum capacity.")
> +                            else:
> +                                errors.append("The allocation must be greater than or equal to 0.")
> +                        else:
> +                            errors.append("The maximum capacity cannot exceed the storage pool size.")
> +                    else:
> +                        errors.append("The capacity must be greater than zero.")
> +                else:
> +                    errors.append("An allocation value must be entered.")
> +            else:
> +                errors.append("A maximum volume capacity must be entered.")
> +        elif page is CONFIRM_PAGE: return True
> +        return False
> +
> +    def process_input(self, page):
> +        if page is SELECT_POOL_PAGE:
> +            self.__config.set_pool(self.get_libvirt().get_storage_pool(self.get_selected_pool()))
> +        elif page is VOLUME_NAME_PAGE:
> +            self.__config.set_name(self.__name.value())
> +        elif page is VOLUME_FORMAT_PAGE:
> +            self.__config.set_format(self.__formats.current())
> +        elif page is MAX_CAPACITY_PAGE:
> +            self.__config.set_max_capacity(int(self.__capacity.value()))
> +            self.__config.set_allocation(int(self.__allocation.value()))
> +        elif page is CONFIRM_PAGE:
> +            self.get_libvirt().define_storage_volume(self.__config, CreateMeter())
> +            self.set_finished()
> +
> +    def get_volume_name_page(self, screen):
> +        self.__name = Entry(50, self.__config.get_name())
> +        grid = Grid(2, 1)
> +        grid.setField(Label("Name:"), 0, 0, anchorRight = 1)
> +        grid.setField(self.__name, 1, 0, anchorLeft = 1)
> +        return [Label("New Storage Volume"),
> +                grid,
> +                Label("Name of the volume to create. File extension may be appended.")]
> +
> +    def get_volume_format_page(self, screen):
> +        self.__formats = Listbox(0)
> +        for format in self.__config.get_formats_for_pool():
> +            self.__formats.append(format, format)
> +        grid = Grid(1, 1)
> +        grid.setField(self.__formats, 0, 0)
> +        return [Label("Select The Volume Format"),
> +                grid]
> +
> +    def get_max_capacity_page(self, screen):
> +        self.__capacity = Entry(6, str(self.__config.get_max_capacity()))
> +        self.__allocation = Entry(6, str(self.__config.get_allocation()))
> +        grid = Grid(2, 2)
> +        grid.setField(Label("Max. Capacity (MB):"), 0, 0, anchorRight = 1)
> +        grid.setField(self.__capacity, 1, 0, anchorLeft = 1)
> +        grid.setField(Label("Allocation (MB):"), 0, 1, anchorRight = 1)
> +        grid.setField(self.__allocation, 1, 1, anchorLeft = 1)
> +        return [Label("Storage Volume Quota"),
> +                Label("%s's available space: %0.2f GB" % (self.__config.get_pool().name(),
> +                                                          self.__config.get_pool().info()[3] / 1024.0**3)),
> +                grid]
> +
> +    def get_confirm_page(self, screen):
> +        grid = Grid(2, 5)
> +        grid.setField(Label("Volume Name:"), 0, 0, anchorRight = 1)
> +        grid.setField(Label("%s (%s)" % (self.__config.get_name(), self.__config.get_pool().name())), 1, 0, anchorLeft = 1)
> +        grid.setField(Label("Format:"), 0, 1, anchorRight = 1)
> +        grid.setField(Label(self.__config.get_format()), 1, 1, anchorLeft = 1)
> +        grid.setField(Label("Max. Capacity:"), 0, 2, anchorRight = 1)
> +        grid.setField(Label("%0.2f GB" % (self.__config.get_max_capacity() / 1024.0)), 1, 2, anchorLeft = 1)
> +        grid.setField(Label("Allocation:"), 0, 3, anchorRight = 1)
> +        grid.setField(Label("%0.2f GB" % (self.__config.get_allocation() / 1024.0)), 1, 3, anchorLeft = 1)
> +        return [Label("Ready To Allocation New Storage Volume"),
> +                grid]
> +
> +def AddStorageVolume():
> +    screen = AddVolumeConfigScreen()
> +    screen.start()
> diff --git a/nodeadmin/configscreen.py b/nodeadmin/configscreen.py
> index f214aea..7654697 100644
> --- a/nodeadmin/configscreen.py
> +++ b/nodeadmin/configscreen.py
> @@ -179,3 +179,55 @@ class NetworkListConfigScreen(ConfigScreen):
>  
>      def has_selectable_networks(self):
>          return self.__has_networks
> +
> +class StorageListConfigScreen(ConfigScreen):
> +    '''Provides a base class for any configuration screen that deals with storage pool lists.'''
> +
> +    def __init__(self, title):
> +        ConfigScreen.__init__(self, title)
> +
> +    def get_storage_pool_list_page(self, screen, defined=True, created=True):
> +        pools = self.get_libvirt().list_storage_pools(defined=defined, created=created)
> +        if len(pools) > 0:
> +            self.__has_pools = True
> +            self.__pools_list = Listbox(0)
> +            for pool in pools:
> +                self.__pools_list.append(pool, pool)
> +            result = self.__pools_list
> +        else:
> +            self.__has_pools = False
> +            result = Label("There are no storage pools available.")
> +        grid = Grid(1, 1)
> +        grid.setField(result, 0, 0)
> +        return [Label("Storage Pool List"),
> +                grid]
> +
> +    def get_selected_pool(self):
> +        return self.__pools_list.current()
> +
> +    def has_selectable_pools(self):
> +        return self.__has_pools
> +
> +    def get_storage_volume_list_page(self, screen):
> +        '''Requires that self.__pools_list have a selected element.'''
> +        pool = self.get_libvirt().get_storage_pool(self.get_selected_pool())
> +        if len(pool.listVolumes()) > 0:
> +            self.__has_volumes = True
> +            self.__volumes_list = Listbox(0)
> +            for volname in pool.listVolumes():
> +                volume = pool.storageVolLookupByName(volname)
> +                self.__volumes_list.append("%s (%0.2f GB)" % (volume.name(), volume.info()[2] / 1024**3), volume.name())
> +            result = self.__volumes_list
> +        else:
> +            self.__has_volumes = False
> +            result = Label("There are no storage volumes available.")
> +        grid = Grid(1, 1)
> +        grid.setField(result, 0, 0)
> +        return [Label("Storage Volume List"),
> +                grid]
> +
> +    def get_selected_volume(self):
> +        return self.__volumes_list.current()
> +
> +    def has_selectable_volumes(self):
> +        return self.__has_volumes
> diff --git a/nodeadmin/createmeter.py b/nodeadmin/createmeter.py
> new file mode 100644
> index 0000000..521e7d8
> --- /dev/null
> +++ b/nodeadmin/createmeter.py
> @@ -0,0 +1,30 @@
> +# createmeter.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.
> +
> +import urlgrabber.progress as progress
> +import logging
> +
> +class CreateMeter(progress.BaseMeter):
> +    def _do_start(self, now = None):
> +        logging.info("Starting...")
> +
> +    def _do_end(self, amount_read, now = None):
> +        logging.info("Ending: read=%d" % amount_read)
> +
> +    def _do_update(self, amount_read, now = None):
> +        logging.info("Update: read=%d" % amount_read)
> diff --git a/nodeadmin/libvirtworker.py b/nodeadmin/libvirtworker.py
> index ba07605..b2acabe 100644
> --- a/nodeadmin/libvirtworker.py
> +++ b/nodeadmin/libvirtworker.py
> @@ -35,6 +35,10 @@ class LibvirtWorker:
>          self.__net.setup(self.__conn)
>          (self.__new_guest, self.__new_domain) = virtinst.CapabilitiesParser.guest_lookup(conn = self.__conn)
>  
> +    def get_connection(self):
> +        '''Returns the underlying connection.'''
> +        return self.__conn
> +
>      def list_domains(self, defined = True, started = True):
>          '''Lists all domains.'''
>          result = []
> @@ -134,9 +138,12 @@ class LibvirtWorker:
>          network = self.get_network(name)
>          network.undefine()
>  
> -    def list_storage_pools(self):
> +    def list_storage_pools(self, defined=True, created=True):
>          '''Returns the list of all defined storage pools.'''
> -        return self.__conn.listStoragePools()
> +        pools = []
> +        if defined: pools.extend(self.__conn.listDefinedStoragePools())
> +        if created: pools.extend(self.__conn.listStoragePools())
> +        return pools
>  
>      def storage_pool_exists(self, name):
>          '''Returns whether a storage pool exists.'''
> @@ -144,16 +151,62 @@ class LibvirtWorker:
>          if name in pools: return True
>          return False
>  
> -    def define_storage_pool(self, name):
> +    def create_storage_pool(self, name):
> +        '''Starts the named storage pool if it is not currently started.'''
> +        if name not in self.list_storage_pools(defined = False):
> +            pool = self.get_storage_pool(name)
> +            pool.create(0)
> +
> +    def destroy_storage_pool(self, name):
> +        '''Stops the specified storage pool.'''
> +        if name in self.list_storage_pools(defined = False):
> +            pool = self.get_storage_pool(name)
> +            pool.destroy()
> +
> +    def define_storage_pool(self, name, config = None, meter = None):
>          '''Defines a storage pool with the given name.'''
> -        try:
> +        if config is None:
>              pool = virtinst.Storage.DirectoryPool(conn=self.__conn,
>                                                    name=name,
>                                                    target_path=DEFAULT_POOL_TARGET_PATH)
> -            newpool = pool.install(build=True, create=True)
> +            newpool = pool.install(build=True, create=True, meter=meter)
>              newpool.setAutostart(True)
> -        except Exception, error:
> -            raise RuntimeError("Could not create pool: %s - %s", str(error))
> +        else:
> +            pool = config.get_pool()
> +            pool.target_path = config.get_target_path()
> +            if config.needs_hostname():
> +                pool.host = config.get_hostname()
> +            if config.needs_source_path():
> +                pool.source_path = config.get_source_path()
> +            if config.needs_format():
> +                pool.format = config.get_format()
> +            pool.conn = self.__conn
> +            pool.get_xml_config()
> +            newpool = pool.install(meter=meter,
> +                                   build=True, # config.get_build_pool(),
> +                                   create=True)
> +            newpool.setAutostart(True)
> +
> +    def undefine_storage_pool(self, name):
> +        '''Undefines the specified storage pool.'''
> +        pool = self.get_storage_pool(name)
> +        pool.undefine()
> +
> +    def get_storage_pool(self, name):
> +        '''Returns the storage pool with the specified name.'''
> +        return self.__conn.storagePoolLookupByName(name)
> +
> +    def define_storage_volume(self, config, meter):
> +        '''Defines a new storage volume.'''
> +        self.create_storage_pool(config.get_pool().name())
> +        volume = config.create_volume()
> +        volume.install(meter = meter)
> +
> +    def remove_storage_volume(self, poolname, volumename):
> +        '''Removes the specified storage volume.'''
> +        pool = self.get_storage_pool(poolname)
> +        volume = pool.storageVolLookupByName(volumename)
> +        volume.delete(0)
>  
>      def list_bridges(self):
>          '''Lists all defined and active bridges.'''
> diff --git a/nodeadmin/listpools.py b/nodeadmin/listpools.py
> new file mode 100644
> index 0000000..686c42d
> --- /dev/null
> +++ b/nodeadmin/listpools.py
> @@ -0,0 +1,63 @@
> +# listpools.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
> +DETAILS_PAGE = 2
> +
> +class ListStoragePoolsConfigScreen(StorageListConfigScreen):
> +    def __init__(self):
> +        StorageListConfigScreen.__init__(self, "List Storage Pools")
> +
> +    def get_elements_for_page(self, screen, page):
> +        if   page is LIST_PAGE:    return self.get_storage_pool_list_page(screen)
> +        elif page is DETAILS_PAGE: return self.get_pool_details_page(screen)
> +
> +    def page_has_next(self, page):
> +        if page is LIST_PAGE and self.has_selectable_pools():
> +                return True
> +        return False
> +
> +    def page_has_back(self, page):
> +        if page is DETAILS_PAGE: return True
> +        return False
> +
> +    def get_pool_details_page(self, screen):
> +        pool = self.get_libvirt().get_storage_pool(self.get_selected_pool())
> +        volumes = Listbox(0);
> +        for name in pool.listVolumes():
> +            volume = pool.storageVolLookupByName(name)
> +            volumes.append("%s (%0.1f G)" % (name, volume.info()[1] / 1024**3), name)
> +        grid = Grid(2, 3)
> +        grid.setField(Label("Name:"), 0, 0, anchorRight = 1)
> +        grid.setField(Label(pool.name()), 1, 0, anchorLeft = 1)
> +        grid.setField(Label("Volumes:"), 0, 1, anchorRight = 1)
> +        grid.setField(volumes, 1, 1, anchorLeft = 1)
> +        grid.setField(Label("Autostart:"), 0, 2, anchorRight = 1)
> +        label = "No"
> +        if pool.autostart(): label = "Yes"
> +        grid.setField(Label(label), 1, 2, anchorLeft = 1)
> +        return [Label("Details For Storage Pool: %s" % self.get_selected_pool()),
> +                grid]
> +
> +def ListStoragePools():
> +    screen = ListStoragePoolsConfigScreen()
> +    screen.start()
> diff --git a/nodeadmin/mainmenu.py b/nodeadmin/mainmenu.py
> index 73501fa..52d9298 100755
> --- a/nodeadmin/mainmenu.py
> +++ b/nodeadmin/mainmenu.py
> @@ -19,28 +19,32 @@
>  from snack import *
>  import traceback
>  
> -from menuscreen     import MenuScreen
> -from nodemenu       import NodeMenu
> -from netmenu        import NetworkMenu
> +from menuscreen  import MenuScreen
> +from nodemenu    import NodeMenu
> +from netmenu     import NetworkMenu
> +from storagemenu import StoragePoolMenu
>  
>  import utils
>  import logging
>  
>  NODE_MENU    = 1
>  NETWORK_MENU = 2
> -EXIT_CONSOLE = 99
> +STORAGE_MENU = 3
> +EXIT_CONSOLE = 4
>  
>  class MainMenuScreen(MenuScreen):
>      def __init__(self):
>          MenuScreen.__init__(self, "Main Menu")
>  
>      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()
> +        return (("Node Administration",         NODE_MENU),
> +                ("Network Administration",      NETWORK_MENU),
> +                ("Storage Pool Administration", STORAGE_MENU))
> +
> +    def handle_selection(self, item):
> +        if   item is NODE_MENU:    NodeMenu()
> +        elif item is NETWORK_MENU: NetworkMenu()
> +        elif item is STORAGE_MENU: StoragePoolMenu()
>  
>  def MainMenu():
>      screen = MainMenuScreen()
> diff --git a/nodeadmin/poolconfig.py b/nodeadmin/poolconfig.py
> new file mode 100644
> index 0000000..06af722
> --- /dev/null
> +++ b/nodeadmin/poolconfig.py
> @@ -0,0 +1,137 @@
> +# poolconfig.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 virtinst import Storage
> +
> +ROOT_TARGET_PATH="/var/lib/libvirt/images/%s"
> +
> +class PoolConfig:
> +    def __init__(self, libvirt):
> +        self.__libvirt = libvirt
> +        self.__name = ""
> +        self.set_type(None)
> +        self.__format = None
> +        self.__hostname = ""
> +        self.__target_path = ""
> +        self.__source_path = ""
> +        self.__build_pool  = False
> +
> +    def get_pool(self):
> +        return self.__pool
> +
> +    def set_name(self, name):
> +        self.__name = name
> +
> +    def get_name(self):
> +        return self.__name
> +
> +    def set_type(self, pooltype):
> +        self.__type = pooltype
> +        self.__needs_target_path = False
> +        self.__needs_format      = False
> +        self.__needs_hostname    = False
> +        self.__needs_source_path = False
> +        self.__needs_build_pool  = False
> +        if pooltype is not None:
> +            if   pooltype is Storage.StoragePool.TYPE_DIR:
> +                self.__needs_target_path = True
> +                self.__target_path = ROOT_TARGET_PATH % self.__name
> +                self.__build_pool = True
> +            elif pooltype is Storage.StoragePool.TYPE_DISK:
> +                self.__needs_target_path = True
> +                self.__needs_format      = True
> +                self.__needs_source_path = True
> +                self.__needs_build_pool  = True
> +            elif pooltype is Storage.StoragePool.TYPE_FS:
> +                self.__needs_target_path = True
> +                self.__needs_format      = True
> +                self.__needs_source_path = True
> +                self.__build_pool  = True
> +            elif pooltype is Storage.StoragePool.TYPE_ISCSI:
> +                self.__needs_target_path = True
> +                self.__needs_hostname    = True
> +                self.__needs_source_path = True
> +                self.__build_pool  = False
> +            elif pooltype is Storage.StoragePool.TYPE_LOGICAL:
> +                self.__needs_target_path = True
> +                self.__needs_source_path = True
> +                self.__needs_build_pool  = True
> +            elif pooltype is Storage.StoragePool.TYPE_NETFS:
> +                self.__needs_target_path = True
> +                self.__needs_format      = True
> +                self.__needs_hostname    = True
> +                self.__needs_source_path = True
> +                self.__build_pool  = True
> +            # create pool
> +            pool_class = Storage.StoragePool.get_pool_class(self.__type)
> +            self.__pool = pool_class(name = self.__name,
> +                                     conn = self.__libvirt.get_connection())
> +            if self.__needs_format:
> +                self.__format = self.__pool.formats[0]
> +        else:
> +            self.__type = Storage.StoragePool.get_pool_types()[0]
> +
> +    def get_type(self):
> +        return self.__type
> +
> +    def needs_target_path(self):
> +        return self.__needs_target_path
> +
> +    def needs_format(self):
> +        return self.__needs_format
> +
> +    def needs_hostname(self):
> +        return self.__needs_hostname
> +
> +    def needs_source_path(self):
> +        return self.__needs_source_path
> +
> +    def needs_build_pool(self):
> +        return self.__needs_build_pool
> +    def set_target_path(self, path):
> +        self.__target_path = path
> +
> +    def get_target_path(self):
> +        return self.__target_path
> +
> +    def get_formats(self):
> +        return self.__pool.formats
> +
> +    def set_format(self, format):
> +        self.__format = format
> +
> +    def get_format(self):
> +        return self.__format
> +
> +    def set_hostname(self, hostname):
> +        self.__hostname = hostname
> +
> +    def get_hostname(self):
> +        return self.__hostname
> +
> +    def set_source_path(self, source_path):
> +        self.__source_path = source_path
> +
> +    def get_source_path(self):
> +        return self.__source_path
> +
> +    def set_build_pool(self, build_pool):
> +        self.__build_pool = build_pool
> +
> +    def  get_build_pool(self):
> +        return self.__build_pool
> diff --git a/nodeadmin/removepool.py b/nodeadmin/removepool.py
> new file mode 100644
> index 0000000..7a7f46d
> --- /dev/null
> +++ b/nodeadmin/removepool.py
> @@ -0,0 +1,72 @@
> +#!/usr/bin/env python
> +#
> +# removepool.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_POOLS_PAGE    = 1
> +CONFIRM_PAGE       = 2
> +
> +class RemoveStoragePoolConfigScreen(StorageListConfigScreen):
> +    def __init__(self):
> +        StorageListConfigScreen.__init__(self, "Remove A Storage Pool")
> +
> +    def get_elements_for_page(self, screen, page):
> +        if   page is LIST_POOLS_PAGE: return self.get_storage_pool_list_page(screen)
> +        elif page is CONFIRM_PAGE:    return self.get_confirm_page(screen)
> +
> +    def page_has_next(self, page):
> +        return page is LIST_POOLS_PAGE and self.has_selectable_pools()
> +
> +    def page_has_back(self, page):
> +        return False
> +
> +    def page_has_finish(self, page):
> +        return page is CONFIRM_PAGE
> +
> +    def validate_input(self, page, errors):
> +        if page is LIST_POOLS_PAGE:
> +            if self.get_selected_pool() is not None:
> +                return True
> +            else:
> +                errors.append("Please select a storage pool to be removed.")
> +        elif page is CONFIRM_PAGE:
> +            if self.__confirm.value():
> +                return True
> +            else:
> +                errors.append("You must confirm removing a storage pool.")
> +        return False
> +
> +    def process_input(self, page):
> +        if page is CONFIRM_PAGE:
> +            self.get_libvirt().destroy_storage_pool(self.get_selected_pool())
> +            self.get_libvirt().undefine_storage_pool(self.get_selected_pool())
> +            self.set_finished()
> +
> +    def get_confirm_page(self, screen):
> +        self.__confirm = Checkbox("Check here to confirm deleting pool: %s" % self.get_selected_pool())
> +        grid = Grid(1, 1)
> +        grid.setField(self.__confirm, 0, 0)
> +        return [Label("Remove Selected Storage Pool"),
> +                grid]
> +
> +def RemoveStoragePool():
> +    screen = RemoveStoragePoolConfigScreen()
> +    screen.start()
> diff --git a/nodeadmin/removevolume.py b/nodeadmin/removevolume.py
> new file mode 100644
> index 0000000..5ad3058
> --- /dev/null
> +++ b/nodeadmin/removevolume.py
> @@ -0,0 +1,76 @@
> +# removevolume.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 createmeter import CreateMeter
> +from configscreen import *
> +from volumeconfig import StorageVolumeConfig
> +from utils import *
> +
> +SELECT_POOL_PAGE   = 1
> +SELECT_VOLUME_PAGE = 2
> +CONFIRM_PAGE       = 3
> +
> +class RemoveVolumeConfigScreen(StorageListConfigScreen):
> +    def __init__(self):
> +        StorageListConfigScreen.__init__(self, "Add A New Storage Volume")
> +        self.__config = StorageVolumeConfig()
> +
> +    def get_elements_for_page(self, screen, page):
> +        if   page is SELECT_POOL_PAGE:   return self.get_storage_pool_list_page(screen)
> +        elif page is SELECT_VOLUME_PAGE: return self.get_storage_volume_list_page(screen)
> +        elif page is CONFIRM_PAGE:       return self.get_confirm_page(screen)
> +
> +    def page_has_next(self, page):
> +        if   page is SELECT_POOL_PAGE:   return self.has_selectable_pools()
> +        elif page is SELECT_VOLUME_PAGE: return self.has_selectable_volumes()
> +        return False
> +
> +    def validate_input(self, page, errors):
> +        if   page is SELECT_POOL_PAGE:   return self.get_selected_pool() is not None
> +        elif page is SELECT_VOLUME_PAGE: return self.get_selected_volume() is not None
> +        elif page is CONFIRM_PAGE:
> +            if self.__confirm.value():
> +                return True
> +            else:
> +                errors.append("You must confirm deleting a storage volume.")
> +        return False
> +
> +    def process_input(self, page):
> +        if page is CONFIRM_PAGE:
> +            self.get_libvirt().remove_storage_volume(self.get_selected_pool(), self.get_selected_volume())
> +            self.set_finished()
> +
> +    def page_has_back(self, page):
> +        return page > SELECT_POOL_PAGE
> +
> +    def page_has_finish(self, page):
> +        return page is CONFIRM_PAGE
> +
> +    def get_confirm_page(self, screen):
> +        self.__confirm = Checkbox("Check here to confirm deleting volume: %s" % self.get_selected_volume())
> +        grid = Grid(1, 1)
> +        grid.setField(self.__confirm, 0, 0)
> +        return [Label("Remove Selected Storage Volume"),
> +                grid]
> +
> +def RemoveStorageVolume():
> +    screen = RemoveVolumeConfigScreen()
> +    screen.start()
> diff --git a/nodeadmin/setup.py.in b/nodeadmin/setup.py.in
> index 1e6e028..17bfe93 100644
> --- a/nodeadmin/setup.py.in
> +++ b/nodeadmin/setup.py.in
> @@ -35,5 +35,12 @@ setup(name = "nodeadmin",
>              'createnet   = nodeadmin.createnetwork:CreateNetwork',
>              'destroynet  = nodeadmin.destroynetwork:DestroyNetwork',
>              'undefinenet = nodeadmin.undefinenetwork:UndefineNetwork',
> -            'listnets    = nodeadmin.listnetworks:ListNetworks']
> +            'listnets    = nodeadmin.listnetworks:ListNetworks',
> +            'addpool     = nodeadmin.addpool:AddStoragePool',
> +            'rmpool      = nodeadmin.removepool:RemoveStoragePool',
> +            'startpool   = nodeadmin.startpool:StartStoragePool',
> +            'stoppool    = nodeadmin.stoppool:StopStoragePool',
> +            'addvolume   = nodeadmin.addvolume:AddStorageVolume',
> +            'rmvolume    = nodeadmin.removevolume:RemoveStorageVolume',
> +            'listpools   = nodeadmin.listpools:ListPools']
>          })
> diff --git a/nodeadmin/startpool.py b/nodeadmin/startpool.py
> new file mode 100644
> index 0000000..8a84512
> --- /dev/null
> +++ b/nodeadmin/startpool.py
> @@ -0,0 +1,62 @@
> +#!/usr/bin/env python
> +#
> +# startpool.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_POOLS_PAGE    = 1
> +FINAL_PAGE         = 2
> +
> +class StartStoragePoolConfigScreen(StorageListConfigScreen):
> +    def __init__(self):
> +        StorageListConfigScreen.__init__(self, "Start A Storage Pool")
> +
> +    def get_elements_for_page(self, screen, page):
> +        if   page is LIST_POOLS_PAGE: return self.get_storage_pool_list_page(screen, created = False)
> +        elif page is FINAL_PAGE:      return self.get_final_page(screen)
> +
> +    def page_has_next(self, page):
> +        return page is LIST_POOLS_PAGE and self.has_selectable_pools()
> +
> +    def page_has_back(self, page):
> +        return False
> +
> +    def page_has_finish(self, page):
> +        return page is FINAL_PAGE
> +
> +    def validate_input(self, page, errors):
> +        if page is LIST_POOLS_PAGE:
> +            if self.get_selected_pool() is not None:
> +                return True
> +            else:
> +                errors.append("Please select a storage pool to be started.")
> +        return False
> +
> +    def process_input(self, page):
> +        if page is LIST_POOLS_PAGE:
> +            self.get_libvirt().create_storage_pool(self.get_selected_pool())
> +            self.set_finished()
> +
> +    def get_final_page(self, screen):
> +        return [Label("Storage pool started: %s" % self.get_selected_pool())]
> +
> +def StartStoragePool():
> +    screen = StartStoragePoolConfigScreen()
> +    screen.start()
> diff --git a/nodeadmin/stoppool.py b/nodeadmin/stoppool.py
> new file mode 100644
> index 0000000..0522b95
> --- /dev/null
> +++ b/nodeadmin/stoppool.py
> @@ -0,0 +1,62 @@
> +#!/usr/bin/env python
> +#
> +# stoppool.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_POOLS_PAGE    = 1
> +FINAL_PAGE         = 2
> +
> +class StopStoragePoolConfigScreen(StorageListConfigScreen):
> +    def __init__(self):
> +        StorageListConfigScreen.__init__(self, "Stop A Storage Pool")
> +
> +    def get_elements_for_page(self, screen, page):
> +        if   page is LIST_POOLS_PAGE: return self.get_storage_pool_list_page(screen, defined = False)
> +        elif page is FINAL_PAGE:      return self.get_final_page(screen)
> +
> +    def page_has_next(self, page):
> +        return page is LIST_POOLS_PAGE and self.has_selectable_pools()
> +
> +    def page_has_back(self, page):
> +        return False
> +
> +    def page_has_finish(self, page):
> +        return page is FINAL_PAGE
> +
> +    def validate_input(self, page, errors):
> +        if page is LIST_POOLS_PAGE:
> +            if self.get_selected_pool() is not None:
> +                return True
> +            else:
> +                errors.append("Please select a storage pool to be stopped.")
> +        return False
> +
> +    def process_input(self, page):
> +        if page is LIST_POOLS_PAGE:
> +            self.get_libvirt().destroy_storage_pool(self.get_selected_pool())
> +            self.set_finished()
> +
> +    def get_final_page(self, screen):
> +        return [Label("Storage pool stopped: %s" % self.get_selected_pool())]
> +
> +def StopStoragePool():
> +    screen = StopStoragePoolConfigScreen()
> +    screen.start()
> diff --git a/nodeadmin/storagemenu.py b/nodeadmin/storagemenu.py
> new file mode 100644
> index 0000000..0b56dae
> --- /dev/null
> +++ b/nodeadmin/storagemenu.py
> @@ -0,0 +1,63 @@
> +# storagemenu.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 addpool       import AddStoragePool
> +from startpool     import StartStoragePool
> +from stoppool      import StopStoragePool
> +from removepool    import RemoveStoragePool
> +from addvolume     import AddStorageVolume
> +from removevolume  import RemoveStorageVolume
> +from listpools     import ListStoragePools
> +
> +ADD_POOL      = 1
> +START_POOL    = 2
> +STOP_POOL     = 3
> +REMOVE_POOL   = 4
> +ADD_VOLUME    = 5
> +REMOVE_VOLUME = 6
> +LIST_POOLS    = 7
> +
> +class StoragePoolMenuScreen(MenuScreen):
> +    def __init__(self):
> +        MenuScreen.__init__(self, "Storage Pool Administration")
> +
> +    def get_menu_items(self):
> +        return (("Add A Storage Pool",      ADD_POOL),
> +                ("Start A Storage Pool",    START_POOL),
> +                ("Stop A Storage Pool",     STOP_POOL),
> +                ("Remove A Storage Pool",   REMOVE_POOL),
> +                ("Add A Storage Volume",    ADD_VOLUME),
> +                ("Remove A Storage Volume", REMOVE_VOLUME),
> +                ("List Storage Pools",      LIST_POOLS))
> +
> +    def handle_selection(self, item):
> +        if   item is ADD_POOL:      AddStoragePool()
> +        elif item is START_POOL:    StartStoragePool()
> +        elif item is STOP_POOL:     StopStoragePool()
> +        elif item is REMOVE_POOL:   RemoveStoragePool()
> +        elif item is ADD_VOLUME:    AddStorageVolume()
> +        elif item is REMOVE_VOLUME: RemoveStorageVolume()
> +        elif item is LIST_POOLS:    ListStoragePools()
> +
> +def StoragePoolMenu():
> +    screen = StoragePoolMenuScreen()
> +    screen.start()
> diff --git a/nodeadmin/utils.py b/nodeadmin/utils.py
> index 55a838c..28ccb8b 100644
> --- a/nodeadmin/utils.py
> +++ b/nodeadmin/utils.py
> @@ -17,9 +17,19 @@
>  # also available at http://www.gnu.org/copyleft/gpl.html.
>  
>  import logging
> +import re
>  
>  logging.basicConfig(level=logging.DEBUG,
>                      format='%(asctime)s %(levelname)-8s %(message)s',
>                      datefmt='%a, %d %b %Y %H:%M:%S',
>                      filename='/var/log/ovirt-nodeadmin.log',
>                      filemode='w')
> +
> +def string_is_not_blank(value):
> +    if len(value) > 0: return True
> +    return False
> +
> +def string_has_no_spaces(value):
> +    if re.match("^[a-zA-Z0-9_]*$", value):
> +        return True
> +    return False
> diff --git a/nodeadmin/volumeconfig.py b/nodeadmin/volumeconfig.py
> new file mode 100644
> index 0000000..7741391
> --- /dev/null
> +++ b/nodeadmin/volumeconfig.py
> @@ -0,0 +1,76 @@
> +# volumeconfig.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.
> +
> +import virtinst
> +from virtinst import Storage
> +
> +class StorageVolumeConfig:
> +    def __init__(self):
> +        self.__pool = None
> +        self.__name = ""
> +        self.__formats = None
> +        self.__format = None
> +        self.__max_capacity = 10000
> +        self.__allocation = 0
> +
> +    def set_pool(self, pool):
> +        self.__pool = pool
> +        self.__formats = None
> +        self.__pool_type = virtinst.util.get_xml_path(self.__pool.XMLDesc(0), '/pool/@type')
> +        self.__volume_class = Storage.StoragePool.get_volume_for_pool(self.__pool_type)
> +
> +    def get_pool(self):
> +        return self.__pool
> +
> +    def create_volume(self):
> +        volume = self.__volume_class(name       = self.__name + ".img",
> +                                     allocation = self.__allocation * 1024**2,
> +                                     capacity   = self.__max_capacity * 1024**2,
> +                                     pool       = self.__pool)
> +        volume.pool = self.__pool
> +        volume.format = self.__format
> +        return volume
> +
> +    def set_name(self, name):
> +        self.__name = name
> +
> +    def get_name(self):
> +        return self.__name
> +
> +    def get_formats_for_pool(self):
> +        if self.__formats is None:
> +            self.__formats = self.__volume_class.formats
> +        return self.__formats
> +
> +    def set_format(self, format):
> +        self.__format = format
> +
> +    def get_format(self):
> +        return self.__format
> +
> +    def set_max_capacity(self, capacity):
> +        self.__max_capacity = capacity
> +
> +    def get_max_capacity(self):
> +        return self.__max_capacity
> +
> +    def set_allocation(self, allocation):
> +        self.__allocation = allocation
> +
> +    def get_allocation(self):
> +        return self.__allocation
> diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in
> index d23a4ef..6509fa0 100644
> --- a/ovirt-node.spec.in
> +++ b/ovirt-node.spec.in
> @@ -369,11 +369,18 @@ fi
>  %{_initrddir}/ovirt-functions
>  %defattr(-,root,root,0644)
>  %{_bindir}/nodeadmin
> +%{_bindir}/addpool
>  %{_bindir}/addvm
> +%{_bindir}/addvolume
>  %{_bindir}/startvm
>  %{_bindir}/stopvm
>  %{_bindir}/rmvm
> +%{_bindir}/listpools
>  %{_bindir}/listvms
> +%{_bindir}/rmpool
> +%{_bindir}/rmvolume
> +%{_bindir}/startpool
> +%{_bindir}/stoppool
>  %{_bindir}/definenet
>  %{_bindir}/createnet
>  %{_bindir}/destroynet
>   
Patches applied fine this go around, heres my findings so far

Works fine: fs

Volume format options are blank for:  lvm, disk

- Other errors
directory/iscsi/nfs - can't verify fqdn/ip may be best to not verify 
this and leave it up to the user, couldn't get past this point to verify 
volume creation

For iscsi also get an error that source path isn't absolute
- The virt-manager implementation has these as the input and I verified 
I was able to connect using virt-manager
   - targetpath  /dev/disk/by-path "default"
   - hostname - accepts ip/fqdn
   - sourcepath == "targetname"

The documentation surrounding the storage setup even for virt-manager is 
almost nonexistent, we may need to make sure we give examples at a 
minimum somewhere.





More information about the ovirt-devel mailing list