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