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

Darryl L. Pierce dpierce at redhat.com
Wed Oct 21 19:50:47 UTC 2009


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                |   31 +++++---
 nodeadmin/addpool.py       |  182 ++++++++++++++++++++++++++++++++++++++++++++
 nodeadmin/addvolume.py     |  160 ++++++++++++++++++++++++++++++++++++++
 nodeadmin/configscreen.py  |   52 +++++++++++++
 nodeadmin/createmeter.py   |   30 +++++++
 nodeadmin/definedomain.py  |   15 +---
 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         |   49 ++++++++----
 19 files changed, 1184 insertions(+), 56 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 abb7c33..14b8ccb 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -27,30 +27,41 @@ EXTRA_DIST =			\
   images/grub-splash.xpm.gz	\
   images/syslinux-vesa-splash.jpg	\
   nodeadmin/__init__.py         \
+  nodeadmin/addpool.py          \
+  nodeadmin/addvolume.py        \
   nodeadmin/configscreen.py     \
+  nodeadmin/createdomain.py     \
+  nodeadmin/createmeter.py      \
   nodeadmin/createnetwork.py    \
   nodeadmin/createuser.py       \
+  nodeadmin/definedomain.py     \
+  nodeadmin/definenet.py        \
   nodeadmin/destroydomain.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/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/nodemenu.py         \
+  nodeadmin/poolconfig.py       \
+  nodeadmin/removepool.py       \
+  nodeadmin/removevolume.py     \
   nodeadmin/setup.py            \
+  nodeadmin/startpool.py        \
+  nodeadmin/stoppool.py         \
+  nodeadmin/storagemenu.py      \
+  nodeadmin/undefinedomain.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/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/definedomain.py b/nodeadmin/definedomain.py
index 3fffca2..791c278 100755
--- a/nodeadmin/definedomain.py
+++ b/nodeadmin/definedomain.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/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 3635810..8a7fc7c 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 2a6b7b6..c392123 100644
--- a/ovirt-node.spec.in
+++ b/ovirt-node.spec.in
@@ -182,23 +182,35 @@ cd -
 %{__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/definedomain.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 -m0755 nodeadmin/listdomains.py %{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0644 nodeadmin/nodemenu.py %{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0755 nodeadmin/undefinedomain.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/definenet.py %{buildroot}%{python_sitelib}/nodeadmin
 %{__install} -p -m0755 nodeadmin/destroynetwork.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/undefinenetwork.py %{buildroot}%{python_sitelib}/nodeadmin
 
+%{__install} -p -m0755 nodeadmin/addpool.py %{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0755 nodeadmin/addvolume.py %{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0755 nodeadmin/listpools.py %{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0644 nodeadmin/poolconfig.py %{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0755 nodeadmin/removepool.py %{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0755 nodeadmin/removevolume.py %{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0755 nodeadmin/startpool.py %{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0755 nodeadmin/stoppool.py %{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0755 nodeadmin/storagemenu.py %{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0644 nodeadmin/volumeconfig.py %{buildroot}%{python_sitelib}/nodeadmin
+
 %{__install} -p -m0755 nodeadmin/createuser.py %{buildroot}%{python_sitelib}/nodeadmin
 
+%{__install} -p -m0644 nodeadmin/createmeter.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
@@ -368,18 +380,25 @@ fi
 %{_sbindir}/ovirt-awake
 %{_initrddir}/ovirt-functions
 %defattr(-,root,root,0644)
-%{_bindir}/nodeadmin
-%{_bindir}/definedom
+%{_bindir}/addpool
+%{_bindir}/addvolume
 %{_bindir}/createdom
-%{_bindir}/destroydom
-%{_bindir}/undefinedom
-%{_bindir}/listdoms
-%{_bindir}/definenet
 %{_bindir}/createnet
+%{_bindir}/createuser
+%{_bindir}/definedom
+%{_bindir}/definenet
+%{_bindir}/destroydom
 %{_bindir}/destroynet
-%{_bindir}/undefinenet
+%{_bindir}/listdoms
 %{_bindir}/listnets
-%{_bindir}/createuser
+%{_bindir}/listpools
+%{_bindir}/nodeadmin
+%{_bindir}/rmpool
+%{_bindir}/rmvolume
+%{_bindir}/startpool
+%{_bindir}/stoppool
+%{_bindir}/undefinedom
+%{_bindir}/undefinenet
 %{_sysconfdir}/collectd.conf.in
 %{python_sitelib}/nodeadmin
 %{python_sitelib}/nodeadmin- at VERSION@-py2.6.egg-info
-- 
1.6.2.5




More information about the ovirt-devel mailing list