This patch provides the ability to create a "dir" type storage pool, and to add and remove volumes for existing pools.
Darryl L. Pierce
2009-Oct-12 13:11 UTC
[Ovirt-devel] [PATCH node] Provides a new storage administration system to the managed node.
Users can now: * Add a new storage pool. * 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 | 29 +++++--- nodeadmin/addpool.py | 129 +++++++++++++++++++++++++++++++++++ nodeadmin/addvolume.py | 160 ++++++++++++++++++++++++++++++++++++++++++++ nodeadmin/configscreen.py | 52 ++++++++++++++ nodeadmin/createmeter.py | 30 ++++++++ nodeadmin/definedomain.py | 15 +---- nodeadmin/libvirtworker.py | 41 ++++++++++-- nodeadmin/listpools.py | 63 +++++++++++++++++ nodeadmin/mainmenu.py | 24 ++++--- nodeadmin/poolconfig.py | 43 ++++++++++++ nodeadmin/removevolume.py | 76 +++++++++++++++++++++ nodeadmin/setup.py.in | 7 ++- nodeadmin/startpool.py | 62 +++++++++++++++++ nodeadmin/storagemenu.py | 59 ++++++++++++++++ nodeadmin/utils.py | 10 +++ nodeadmin/volumeconfig.py | 76 +++++++++++++++++++++ ovirt-node.spec.in | 45 ++++++++---- 17 files changed, 866 insertions(+), 55 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/removevolume.py create mode 100644 nodeadmin/startpool.py create mode 100644 nodeadmin/storagemenu.py create mode 100644 nodeadmin/volumeconfig.py diff --git a/Makefile.am b/Makefile.am index abb7c33..85383d7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -27,30 +27,39 @@ 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/removevolume.py \ nodeadmin/setup.py \ + nodeadmin/startpool.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..9497e1c --- /dev/null +++ b/nodeadmin/addpool.py @@ -0,0 +1,129 @@ +# 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 +DIR_TYPE_DETAILS = 2 +CONFIRM_PAGE = 99 + +class AddStoragePoolConfigScreen(ConfigScreen): + def __init__(self): + ConfigScreen.__init__(self, "Add A Storage Pool") + self.__config = PoolConfig() + + def get_elements_for_page(self, screen, page): + if page is POOL_NAME_PAGE: return self.get_pool_name_page(screen) + elif page is DIR_TYPE_DETAILS: return self.get_dir_type_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 get_next_page(self, page): + if page is POOL_NAME_PAGE: + if self.__config.get_type() is "dir": return DIR_TYPE_DETAILS + raise Exception("self.__config.get_type() = %s" % self.__config.get_type()) + elif page is DIR_TYPE_DETAILS: return CONFIRM_PAGE + + def get_back_page(self, page): + if page is DIR_TYPE_DETAILS: return POOL_NAME_PAGE + if page is CONFIRM_PAGE: + if self.__config.get_type() is "dir": return DIR_TYPE_DETAILS + + def validate_input(self, page, errors): + if page is POOL_NAME_PAGE: + if utils.string_is_not_blank(self.__name.value()): + if not self.get_libvirt().storage_pool_exists(self.__name.value()): + return True + else: + errors.append("Name '%s' already in use by another pool." % self.__name.value()) + else: + errors.append("Storage object name must be a string between 0 and 50 characters.") + elif page is DIR_TYPE_DETAILS: + if utils.string_is_not_blank(self.__target_path.value()): + if self.__target_path.value()[0:1] is '/': + return True + else: + errors.append("'%s' is not an absolute path." % self.__target_path.value()) + else: + errors.append("You must enter a target path.") + 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.current()) + elif page is DIR_TYPE_DETAILS: + self.__config.set_target_path(self.__target_path.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()) + self.__type = Listbox(0) + for type in Storage.StoragePool.get_pool_types(): + self.__type.append(Storage.StoragePool.get_pool_type_desc(type), type) + if self.__config.get_type() is not None: + self.__type.setCurrent(self.__config.get_type()) + 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_dir_type_page(self, screen): + self.__target_path = Entry(50, self.__config.get_target_path()) + grid = Grid(2, 1) + grid.setField(Label("Target Path:"), 0, 0, anchorRight = 1) + grid.setField(self.__target_path, 1, 0, anchorLeft = 1) + return self.get_location_page(grid) + + def get_location_page(self, grid): + 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 6a6612c..067f9e8 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..01ad689 100644 --- a/nodeadmin/libvirtworker.py +++ b/nodeadmin/libvirtworker.py @@ -134,9 +134,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 +147,42 @@ 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 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.setAutostart(True) - except Exception, error: - raise RuntimeError("Could not create pool: %s - %s", str(error)) + else: + pool_class = virtinst.Storage.StoragePool.get_pool_class(config.get_type()) + pool = pool_class(name = name, conn = self.__conn) + pool.target_path = config.get_target_path() + poolobj = pool.install(meter = meter, build = True) + poolobj.setAutostart(True) + + 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..4f8b4c4 --- /dev/null +++ b/nodeadmin/poolconfig.py @@ -0,0 +1,43 @@ +# 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. + +ROOT_TARGET_PATH="/var/lib/libvirt/images/%s" + +class PoolConfig: + def __init__(self): + self.__name = "" + self.__type = None + + def set_name(self, name): + self.__name = name + + def get_name(self): + return self.__name + + def set_type(self, type): + self.__type = type + if type is "dir": self.__target_path = ROOT_TARGET_PATH % self.__name + + def get_type(self): + return self.__type + + def set_target_path(self, path): + self.__target_path = path + + def get_target_path(self): + return self.__target_path 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..c7af07e 100644 --- a/nodeadmin/setup.py.in +++ b/nodeadmin/setup.py.in @@ -35,5 +35,10 @@ 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', + 'startpool = nodeadmin.startpool:StartStoragePool', + 'addvolume = nodeadmin.addvolume:AddStorageVolume', + 'listpools = nodeadmin.listpools:ListPools', + 'rmvolume = nodeadmin.removevolume:RemoveStorageVolume'] }) 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/storagemenu.py b/nodeadmin/storagemenu.py new file mode 100644 index 0000000..a50ed78 --- /dev/null +++ b/nodeadmin/storagemenu.py @@ -0,0 +1,59 @@ +# 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 addvolume import AddStorageVolume +from removevolume import RemoveStorageVolume +from listpools import ListStoragePools + +ADD_POOL = 1 +ADD_VOLUME = 2 +REMOVE_VOLUME = 3 +START_POOL = 4 +STOP_POOL = 5 +UNADD_POOL = 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), + ("Add A Storage Volume", ADD_VOLUME), + ("Remove A Storage Volume", REMOVE_VOLUME), + ("Unadd A Storage Pool", UNADD_POOL), + ("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 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..c52eb02 100644 --- a/ovirt-node.spec.in +++ b/ovirt-node.spec.in @@ -182,23 +182,33 @@ 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/removevolume.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/startpool.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 +378,23 @@ 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}/rmvolume +%{_bindir}/startpool +%{_bindir}/undefinedom +%{_bindir}/undefinenet %{_sysconfdir}/collectd.conf.in %{python_sitelib}/nodeadmin %{python_sitelib}/nodeadmin- at VERSION@-py2.6.egg-info -- 1.6.2.5