[Ovirt-devel] [PATCH 1/2] Provides a new storage administration system to the managed node.
Joey Boggs
jboggs at redhat.com
Mon Nov 9 19:54:50 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 | 183 ++++++++++++++++++++++++++++++++++++++++++++
> nodeadmin/addvolume.py | 177 ++++++++++++++++++++++++++++++++++++++++++
> nodeadmin/configscreen.py | 52 +++++++++++++
> nodeadmin/createmeter.py | 30 +++++++
> nodeadmin/libvirtworker.py | 67 ++++++++++++++--
> nodeadmin/listpools.py | 63 +++++++++++++++
> nodeadmin/mainmenu.py | 24 ++++---
> nodeadmin/poolconfig.py | 143 ++++++++++++++++++++++++++++++++++
> 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 | 83 ++++++++++++++++++++
> ovirt-node.spec.in | 7 ++
> 19 files changed, 1186 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..9fa1e7d
> --- /dev/null
> +++ b/nodeadmin/addpool.py
> @@ -0,0 +1,183 @@
> +# 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 not 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.__config.source_must_be_absolute():
> + 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..63cc54e
> --- /dev/null
> +++ b/nodeadmin/addvolume.py
> @@ -0,0 +1,177 @@
> +# 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 get_next_page(self, page):
> + if page is VOLUME_NAME_PAGE:
> + if self.__config.needs_format():
> + return VOLUME_FORMAT_PAGE
> + else:
> + return MAX_CAPACITY_PAGE
> + return StorageListConfigScreen.get_next_page(self, page)
> +
> + def get_back_page(self, page):
> + if page is MAX_CAPACITY_PAGE:
> + if self.__config.needs_format():
> + return VOLUME_FORMAT_PATH
> + else:
> + return VOLUME_NAME_PAGE
> + return StorageListConfigScreen.get_back_page(self, 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)
> + if self.__config.needs_format():
> + 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..6ece6c7
> --- /dev/null
> +++ b/nodeadmin/poolconfig.py
> @@ -0,0 +1,143 @@
> +# 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 source_must_be_absolute(self):
> + if self.__type is Storage.StoragePool.TYPE_ISCSI:
> + return False
> + return True
> +
> + 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..86ada74
> --- /dev/null
> +++ b/nodeadmin/volumeconfig.py
> @@ -0,0 +1,83 @@
> +# 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
> + if self.needs_format():
> + volume.format = self.__format
> + return volume
> +
> + def set_name(self, name):
> + self.__name = name
> +
> + def get_name(self):
> + return self.__name
> +
> + def needs_format(self):
> + if self.__pool.__dict__.keys().count("get_formats_for_pool") > 0:
> + return self.__pool.get_formats_for_pool() is not 0
> + else:
> + return False
> +
> + 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
>
ACK pending below comments:
For lvm -It creates a pool fine, when creating a volume its missing the
volume group name when running lvcreate
+-----+ An Exception Has Occurred +-----+
Couldn't create storage volume '1.img': 'internal error '/sbin/lvcreate
--name 1.img -L 1024K /tmp/lvm' exited with non-zero status 3 and signal 0
Volume group name expected (no slash) Run `lvcreate --help' for more
information.
iscsi creates a pool fine but when you reach creating a volume it tells
you it's no implemented, any way to block this option for even being
available or alert the user ahead of time?
All the other options create pools/volumes fine
More information about the ovirt-devel
mailing list