This version of the patch includes feedback from jboggs at redhat.com, including fixes to the BuildRequires and Requires in the spec file.
Darryl L. Pierce
2009-Aug-31 15:50 UTC
[Ovirt-devel] [PATCH node] Introduces the node-admin toolset.
Defines a primary entry point that displays a menu of options that user can use to administer the managed node. It leverages the code from virtinst-python to do the heavy lifting and just provides a front end for collecting user data. Created a new configuration class to be used for all configuration screens. The user can select to create a node. They are then walked through the steps to create a domain similar to virt-manager. The user can create a defined domain. The user can destroy a created domain. The user can undefine a defined domain. The user can list all domains on the system. The user can create a new user account. When the RPM is created, a separate entry point is created for each node admin command. This allows the commands to be invoked individually as well as from the main menu. Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- .gitignore | 1 + Makefile.am | 14 ++ admin/__init__.py | 20 ++ admin/configscreen.py | 150 +++++++++++++++ admin/createdomain.py | 68 +++++++ admin/createuser.py | 106 +++++++++++ admin/definedomain.py | 470 +++++++++++++++++++++++++++++++++++++++++++++++ admin/destroydomain.py | 66 +++++++ admin/domainconfig.py | 217 ++++++++++++++++++++++ admin/halworker.py | 37 ++++ admin/libvirtworker.py | 248 +++++++++++++++++++++++++ admin/listdomains.py | 68 +++++++ admin/mainmenu.py | 71 +++++++ admin/nodeadmin.py | 29 +++ admin/setup.py.in | 34 ++++ admin/undefinedomain.py | 83 +++++++++ admin/userworker.py | 38 ++++ configure.ac | 1 + ovirt-node.spec.in | 36 ++++ 19 files changed, 1757 insertions(+), 0 deletions(-) create mode 100644 admin/__init__.py create mode 100644 admin/configscreen.py create mode 100755 admin/createdomain.py create mode 100755 admin/createuser.py create mode 100755 admin/definedomain.py create mode 100755 admin/destroydomain.py create mode 100644 admin/domainconfig.py create mode 100644 admin/halworker.py create mode 100644 admin/libvirtworker.py create mode 100755 admin/listdomains.py create mode 100755 admin/mainmenu.py create mode 100755 admin/nodeadmin.py create mode 100644 admin/setup.py.in create mode 100755 admin/undefinedomain.py create mode 100644 admin/userworker.py diff --git a/.gitignore b/.gitignore index 26a0210..19b15d1 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ missing stamp-h1 ovirt-node*.gz ovirt-node.spec +*.pyc diff --git a/Makefile.am b/Makefile.am index 419cdf1..5cb0c67 100644 --- a/Makefile.am +++ b/Makefile.am @@ -26,6 +26,20 @@ EXTRA_DIST = \ ovirt-node-selinux.fc \ images/grub-splash.xpm.gz \ images/syslinux-vesa-splash.jpg \ + admin/configscreen.py \ + admin/createuser.py \ + admin/destroydomain.py \ + admin/halworker.py \ + admin/libvirtworker.py \ + admin/userworker.py \ + admin/mainmenu.py \ + admin/undefinedomain.py \ + admin/createdomain.py \ + admin/definedomain.py \ + admin/domainconfig.py \ + admin/listdomains.py \ + admin/nodeadmin.py \ + admin/setup.py \ scripts/collectd.conf.in \ scripts/ovirt \ scripts/ovirt-awake \ diff --git a/admin/__init__.py b/admin/__init__.py new file mode 100644 index 0000000..1f3c72c --- /dev/null +++ b/admin/__init__.py @@ -0,0 +1,20 @@ +# __init__.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 nodeadmin import NodeAdmin + diff --git a/admin/configscreen.py b/admin/configscreen.py new file mode 100644 index 0000000..0282eee --- /dev/null +++ b/admin/configscreen.py @@ -0,0 +1,150 @@ +# configscreen.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 halworker import HALWorker +from libvirtworker import LibvirtWorker +import traceback + +BACK_BUTTON = "back" +NEXT_BUTTON = "next" +CANCEL_BUTTON = "cancel" +FINISH_BUTTON = "finish" + +class ConfigScreen: + '''Enables the creation of navigable, multi-paged configuration screens.''' + + def __init__(self, title): + self.__title = title + self.__current_page = 1 + self.__finished = False + self.__hal = HALWorker() + self.__libvirt = LibvirtWorker() + + def get_hal(self): + return self.__hal + + def get_libvirt(self): + return self.__libvirt + + def set_finished(self): + self.__finished = True + + def get_elements_for_page(self, screen, page): + return [] + + def page_has_next(self, page): + return False + + def page_has_finish(self, page): + return False + + def get_back_page(self, page): + if page > 1: return page - 1 + return page + + def go_back(self): + self.__current_page = self.get_back_page(self.__current_page) + + def get_next_page(self, page): + return page + 1 + + def go_next(self): + self.__current_page = self.get_next_page(self.__current_page) + + def validate_input(self, page, errors): + return True + + def process_input(self, page): + return + + def start(self): + active = True + while active and (self.__finished == False): + screen = SnackScreen() + gridform = GridForm(screen, self.__title, 1, 4) + elements = self.get_elements_for_page(screen, self.__current_page) + current_element = 0 + for element in elements: + gridform.add(element, 0, current_element) + current_element += 1 + # create the navigation buttons + buttons = [] + if self.__current_page > 1: buttons.append(["Back", BACK_BUTTON, "F11"]) + if self.page_has_next(self.__current_page): buttons.append(["Next", NEXT_BUTTON, "F12"]) + if self.page_has_finish(self.__current_page): buttons.append(["Finish", FINISH_BUTTON, "F10"]) + buttons.append(["Cancel", CANCEL_BUTTON, "ESC"]) + buttonbar = ButtonBar(screen, buttons) + gridform.add(buttonbar, 0, current_element, growx = 1) + current_element += 1 + try: + result = gridform.runOnce() + pressed = buttonbar.buttonPressed(result) + if pressed == BACK_BUTTON: + self.go_back() + elif pressed == NEXT_BUTTON or pressed == FINISH_BUTTON: + errors = [] + if self.validate_input(self.__current_page, errors): + self.process_input(self.__current_page) + self.go_next() + else: + error_text = "" + for error in errors: + error_text += "%s\n" % error + ButtonChoiceWindow(screen, + "There Were Errors", + error_text, + buttons = ["OK"]) + elif pressed == CANCEL_BUTTON: + active = False + except Exception, error: + ButtonChoiceWindow(screen, + "An Exception Has Occurred", + str(error) + "\n" + traceback.format_exc(), + buttons = ["OK"]) + screen.popWindow() + screen.finish() + +class DomainListConfigScreen(ConfigScreen): + '''Provides a base class for all config screens that require a domain list.''' + + def __init__(self, title): + ConfigScreen.__init__(self, title) + + def get_domain_list_page(self, screen, defined=True, created=True): + domains = self.get_libvirt().list_domains(defined, created) + result = None + + if len(domains) > 0: + self.__has_domains = True + self.__domain_list = Listbox(0) + for name in self.get_libvirt().list_domains(defined, created): + self.__domain_list.append(name, name) + result = [self.__domain_list] + else: + self.__has_domains = False + grid = Grid(1, 1) + grid.setField(Label("There are no domains available."), 0, 0) + result = [grid] + return result + + def get_selected_domain(self): + return self.__domain_list.current() + + def has_selectable_domains(self): + return self.__has_domains diff --git a/admin/createdomain.py b/admin/createdomain.py new file mode 100755 index 0000000..b73a09e --- /dev/null +++ b/admin/createdomain.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# +# createdomain.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 * + +class CreateDomainConfigScreen(DomainListConfigScreen): + LIST_PAGE = 1 + CREATE_PAGE = 2 + + def __init__(self): + DomainListConfigScreen.__init__(self, "Create A Domain") + + def get_elements_for_page(self, screen, page): + if page is self.LIST_PAGE: + return self.get_domain_list_page(screen, created = False) + elif page is self.CREATE_PAGE: + return self.get_create_domain_page(screen) + + def page_has_next(self, page): + if page is self.LIST_PAGE: return self.has_selectable_domains() + return False + + def page_has_back(self, page): + if page is self.CREATE_PAGE: return True + return False + + def validate_input(self, page, errors): + if page is self.LIST_PAGE: + if self.get_selected_domain() is not None: + domain = self.get_selected_domain() + try: + self.get_libvirt().create_domain(domain) + return True + except Exception, error: + errors.append("There was an error creating the domain: %s" % domain) + errors.append(str(error)) + else: + errors.append("You must first select a domain to create.") + + def process_input(self, page): + print "foo" + + def get_create_domain_page(self, screen): + grid = Grid(1, 1) + grid.setField(Label("%s was successfully created." % self.get_selected_domain()), 0, 0) + return [grid] + +def CreateDomain(): + screen = CreateDomainConfigScreen() + screen.start() diff --git a/admin/createuser.py b/admin/createuser.py new file mode 100755 index 0000000..dbc4626 --- /dev/null +++ b/admin/createuser.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +# +# createuser.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 ConfigScreen +from userworker import UserWorker + +import libuser + +DETAILS_PAGE = 1 +CONFIRM_PAGE = 2 + +class CreateUserConfigScreen(ConfigScreen): + def __init__(self): + ConfigScreen.__init__(self, "Create A User Account") + self.__username = None + self.__useradmin = libuser.admin() + self.__user_worker = UserWorker() + + def get_elements_for_page(self, screen, page): + if page is DETAILS_PAGE: return self.get_details_page(screen) + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) + + def validate_input(self, page, errors): + if page is DETAILS_PAGE: + if len(self.__username.value()) > 0: + name = self.__username.value() + if self.__useradmin.lookupUserByName(name) is None: + if len(self.__password.value()) > 0: + if self.__password.value() == self.__confirm.value(): + return True + else: + errors.append("Passwords do not match.") + else: + errors.append("You must enter a password.") + else: + errors.append("User %s already exists." % name) + else: + errors.append("You must enter a username.") + self.__confirm.value() + return False + + def process_input(self, page): + if page is CONFIRM_PAGE: + self.__user_worker.create_user(self.__username.value(), + self.__password.value(), + "wheel" if self.__adminuser.value() else None) + self.set_finished() + + def page_has_next(self, page): + return (page is DETAILS_PAGE) + + def page_has_back(self, page): + return (page is CONFIRM_PAGE) + + def page_has_finish(self, page): + return (page is CONFIRM_PAGE) + + def get_details_page(self, screen): + if self.__username is None: + self.__username = Entry(50, "") + self.__password = Entry(50, "", password = 1) + self.__confirm = Entry(50, "", password = 1) + self.__adminuser = Checkbox("This user is an administrator", False) + grid = Grid(2, 4) + grid.setField(Label("Username:"), 0, 0, anchorRight = 1) + grid.setField(self.__username, 1, 0, anchorLeft = 1) + grid.setField(Label("Password:"), 0, 1, anchorRight = 1) + grid.setField(self.__password, 1, 1, anchorLeft = 1) + grid.setField(Label("Confirm password:"), 0, 2, anchorRight = 1) + grid.setField(self.__confirm, 1, 2, anchorLeft = 1) + grid.setField(Label(" "), 0, 3) + grid.setField(self.__adminuser, 1, 3, anchorLeft = 1) + return [Label("Enter The User Details"), + grid] + + def get_confirm_page(self, screen): + grid = Grid(1, 2) + grid.setField(Label("Username: %s" % self.__username.value()), 0, 0) + admin_label = "is not" + if self.__adminuser.value(): + admin_label = "is" + grid.setField(Label("This user %s an administrator." % admin_label), 0, 1) + return [Label("Create this user account?"), + grid] + +def CreateUser(): + screen = CreateUserConfigScreen() + screen.start() diff --git a/admin/definedomain.py b/admin/definedomain.py new file mode 100755 index 0000000..ea4c986 --- /dev/null +++ b/admin/definedomain.py @@ -0,0 +1,470 @@ +#!/usr/bin/env python +# +# definedomain.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 os +from domainconfig import DomainConfig +from configscreen import ConfigScreen +import urlgrabber.progress as progress + +from virtinst import * + +VM_DETAILS_PAGE = 1 +LOCAL_INSTALL_PAGE = 2 +SELECT_CDROM_PAGE = 3 +SELECT_ISO_PAGE = 4 +NETWORK_INSTALL_PAGE = 10 +OS_TYPE_PAGE = 11 +OS_VARIANT_PAGE = 12 +RAM_CPU_PAGE = 13 +ENABLE_STORAGE_PAGE = 14 +LOCAL_STORAGE_PAGE = 15 +MANAGED_STORAGE_PAGE = 16 +BRIDGE_PAGE = 17 +VIRT_DETAILS_PAGE = 18 +CONFIRM_PAGE = 19 + +LOCATION="location" +KICKSTART="kickstart" +KERNELOPTS="kernel.options" +OS_TYPE="os.type" +OS_VARIANT="os.variant" +MEMORY="memory" +CPUS="cpus" + +class DummyMeter(progress.BaseMeter): + + def _do_start(self, now = None): + print "Starting..." + + def _do_end(self, amount_read, now = None): + print "Ending: read=%d" % amount_read + + def _do_update(self, amount_read, now = None): + print "Update: read=%d" % amount_read + +class DomainConfigScreen(ConfigScreen): + + def __init__(self): + ConfigScreen.__init__(self, "Create A New Virtual Machine") + self.__config = DomainConfig() + self.__config.set_architecture(self.get_libvirt().get_default_architecture()) + self.__config.set_virt_type(self.get_libvirt().get_default_virt_type()) + + def get_elements_for_page(self, screen, page): + if page == VM_DETAILS_PAGE: return self.get_vm_details_page(screen) + elif page == LOCAL_INSTALL_PAGE: return self.get_local_install_page(screen) + elif page == SELECT_CDROM_PAGE: return self.get_select_cdrom_page(screen) + elif page == SELECT_ISO_PAGE: return self.get_select_iso_page(screen) + elif page == NETWORK_INSTALL_PAGE: return self.get_network_install_page(screen) + elif page == OS_TYPE_PAGE: return self.get_os_type_page(screen) + elif page == OS_VARIANT_PAGE: return self.get_os_variant_page(screen) + elif page == RAM_CPU_PAGE: return self.get_ram_and_cpu_page(screen) + elif page == ENABLE_STORAGE_PAGE: return self.get_enable_storage_page(screen) + elif page == LOCAL_STORAGE_PAGE: return self.get_local_storage_page(screen) + elif page == MANAGED_STORAGE_PAGE: return self.get_managed_storage_page(screen) + elif page == BRIDGE_PAGE: return self.get_bridge_page(screen) + elif page == VIRT_DETAILS_PAGE: return self.get_virt_details_page(screen) + elif page == CONFIRM_PAGE: return self.get_confirm_page(screen) + return [] + + def validate_input(self, page, errors): + if page == VM_DETAILS_PAGE: + if len(self.__guest_name.value()) > 0: + if self.get_libvirt().domain_exists(self.__guest_name.value()): + errors.append("Guest name '%s' is already in use." % self.__guest_name.value()) + else: + return True + else: + errors.append("Guest name must be a string between 0 and 50 characters.") + elif page == LOCAL_INSTALL_PAGE: + if self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_CDROM: + return True + elif self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_ISO: + return True + elif page == SELECT_CDROM_PAGE: + if self.__install_media.getSelection() != None: + if len(self.get_hal().list_installable_volumes()) == 0: + errors.append("No installable media is available.") + else: + return True + else: + errors.append("You must select an install media.") + elif page == SELECT_ISO_PAGE: + if len(self.__iso_path.value()) > 0: + if os.path.exists(self.__iso_path.value()): + if os.path.isfile(self.__iso_path.value()): + return True + else: + errors.append("%s is not a file." % self.__iso_path.value()) + else: + errors.append("No such install media exists:") + errors.append(self.__iso_path.value()) + else: + errors.append("An install media selection is required.") + elif page == NETWORK_INSTALL_PAGE: + if len(self.__install_url.value()) > 0: + return True + else: + errors.append("An install tree is required.") + elif page == OS_TYPE_PAGE: return True + elif page == OS_VARIANT_PAGE: return True + elif page == RAM_CPU_PAGE: + if (len(self.__memory.value()) > 0 and len(self.__cpus.value()) > 0) \ + and (int(self.__memory.value()) > 0 and int(self.__cpus.value()) > 0): + return True + else: + if len(self.__memory.value()) == 0: + errors.append("A value must be entered for memory.") + elif int(self.__memory.value()) <= 0: + errors.append("A positive integer value must be entered for memory.") + if len(self.__cpus.value()) == 0: + errors.append("A value must be entered for CPUs.") + elif int(self.__cpus.value()) <= 0: + errors.append("A positive integer value must be entered for memory.") + elif page == ENABLE_STORAGE_PAGE: return True + elif page == LOCAL_STORAGE_PAGE: + if len(self.__storage_size.value()) > 0: + if float(self.__storage_size.value()) > 0: + return True + else: + errors.append("A positive value must be entered for the storage size.") + else: + errors.append("A value must be entered for the storage size.") + elif page == MANAGED_STORAGE_PAGE: + if self.__existing_storage.getSelection() is not None: + return True + else: + errors.append("Please select a storage volume.") + elif page == BRIDGE_PAGE: + if self.__network_bridges.getSelection() != None: + if len(self.__mac_address.value()) > 0: + # TODO: regex check the format + return True + else: + errors.append("MAC address must be supplied.") + else: + errors.append("A network bridge must be selected.") + elif page == VIRT_DETAILS_PAGE: + if self.__virt_types.getSelection() != None and self.__architectures.getSelection() != None: + return True + if self.__virt_types.getSelection() is None: + errors.append("Please select a virtualization type.") + if self.__architectures.getSelection() is None: + errors.append("Please selection an architecture.") + elif page == CONFIRM_PAGE: return True + return False + + def process_input(self, page): + if page == VM_DETAILS_PAGE: + self.__config.set_guest_name(self.__guest_name.value()) + self.__config.set_install_type(self.__install_type.getSelection()) + elif page == LOCAL_INSTALL_PAGE: + self.__config.set_use_cdrom_source(self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_CDROM) + elif page == SELECT_CDROM_PAGE: + self.__config.set_install_media(self.__install_media.getSelection()) + elif page == SELECT_ISO_PAGE: + self.__config.set_iso_path(self.__iso_path.value()) + elif page == NETWORK_INSTALL_PAGE: + self.__config.set_install_url(self.__install_url.value()) + self.__config.set_kickstart_url(self.__kickstart_url.value()) + self.__config.set_kernel_options(self.__kernel_options.value()) + elif page == OS_TYPE_PAGE: + self.__config.set_os_type(self.__os_types.getSelection()) + elif page == OS_VARIANT_PAGE: + self.__config.set_os_variant(self.__os_variants.getSelection()) + elif page == RAM_CPU_PAGE: + self.__config.set_memory(int(self.__memory.value())) + self.__config.set_cpus(int(self.__cpus.value())) + elif page == ENABLE_STORAGE_PAGE: + self.__config.set_enable_storage(self.__enable_storage.value()) + if self.__storage_type.getSelection() == DomainConfig.NEW_STORAGE: + self.__config.set_use_local_storage(True) + elif self.__storage_type.getSelection() == Node.STORAGE_TYPE_EXISTING: + self.__config.set_use_local_storage(False) + elif page == LOCAL_STORAGE_PAGE: + self.__config.set_storage_size(float(self.__storage_size.value())) + self.__config.set_allocate_storage(self.__allocate_storage.value()) + elif page == MANAGED_STORAGE_PAGE: + self.__config.set_use_local_storage(False) + self.__config.set_existing_storage(self.__existing_storage.getSelection()) + self.__config.set_storage_size(self.get_libvirt().get_storage_size(self.__existing_storage.getSelection())) + elif page == BRIDGE_PAGE: + self.__config.set_network_bridge(self.__network_bridges.getSelection()) + elif page == VIRT_DETAILS_PAGE: + 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.set_finished() + + def get_back_page(self, page): + result = page + if page == OS_TYPE_PAGE: + install_type = self.__config.get_install_type() + if install_type == DomainConfig.LOCAL_INSTALL: + if self.__config.get_use_cdrom_source(): + result = SELECT_CDROM_PAGE + else: + result = SELECT_ISO_PAGE + elif install_type == DomainConfig.NETWORK_INSTALL: + result = NETWORK_INSTALL_PAGE + elif install_type == DomainConfig.PXE_INSTALL: + result = VM_DETAILS_PAGE + elif page == LOCAL_STORAGE_PAGE or page == MANAGED_STORAGE_PAGE: + result = ENABLE_STORAGE_PAGE + elif page == NETWORK_INSTALL_PAGE: + result = VM_DETAILS_PAGE + elif page == SELECT_CDROM_PAGE or page == SELECT_ISO_PAGE: + result = LOCAL_INSTALL_PAGE + elif page == BRIDGE_PAGE: + if self.__config.get_use_local_storage(): + result = LOCAL_STORAGE_PAGE + else: + result = MANAGED_STORAGE_PAGE + else: + if page > 1: result = page - 1 + return result + + def get_next_page(self, page): + result = page + if page == VM_DETAILS_PAGE: + install_type = self.__config.get_install_type() + if install_type == DomainConfig.LOCAL_INSTALL: + result = LOCAL_INSTALL_PAGE + elif install_type == DomainConfig.NETWORK_INSTALL: + result = NETWORK_INSTALL_PAGE + elif install_type == DomainConfig.PXE_INSTALL: + result = OS_TYPE_PAGE + elif page == LOCAL_INSTALL_PAGE: + if self.__config.get_use_cdrom_source(): + result = SELECT_CDROM_PAGE + else: + result = SELECT_ISO_PAGE + elif page == SELECT_CDROM_PAGE or page == SELECT_ISO_PAGE: + result = OS_TYPE_PAGE + elif page == NETWORK_INSTALL_PAGE: + result = OS_TYPE_PAGE + elif page == ENABLE_STORAGE_PAGE: + result = BRIDGE_PAGE + if self.__config.get_enable_storage(): + if self.__config.get_use_local_storage(): + result = LOCAL_STORAGE_PAGE + else: + result = MANAGED_STORAGE_PAGE + elif page == LOCAL_STORAGE_PAGE or page == MANAGED_STORAGE_PAGE: + result = BRIDGE_PAGE + else: + result = page + 1 + return result + + def page_has_finish(self, page): + if page == CONFIRM_PAGE: return True + return False + + def page_has_next(self, page): + if page < CONFIRM_PAGE: + return True + + def get_vm_details_page(self, screen): + self.__guest_name = Entry(50, self.__config.get_guest_name()) + self.__install_type = RadioBar(screen, (("Local install media (ISO image or CDROM)", + DomainConfig.LOCAL_INSTALL, + self.__config.is_install_type(DomainConfig.LOCAL_INSTALL)), + ("Network Install (HTTP, FTP, or NFS)", + DomainConfig.NETWORK_INSTALL, + self.__config.is_install_type(DomainConfig.NETWORK_INSTALL)), + ("Network Boot (PXE)", + DomainConfig.PXE_INSTALL, + self.__config.is_install_type(DomainConfig.PXE_INSTALL)))) + grid = Grid(2,3) + grid.setField(Label("Name:"), 0, 0, anchorRight = 1) + grid.setField(self.__guest_name, 1, 0, anchorLeft = 1) + grid.setField(Label("Choose how you would like to install the operating system"), 1, 1, + anchorLeft = 1, anchorTop = 1) + grid.setField(self.__install_type, 1, 2, anchorLeft = 1) + return [Label("Enter your machine details"), + grid] + + def get_local_install_page(self, screen): + self.__install_source = RadioBar(screen, (("Use CDROM or DVD", + DomainConfig.INSTALL_SOURCE_CDROM, + self.__config.get_use_cdrom_source()), + ("Use ISO image", + DomainConfig.INSTALL_SOURCE_ISO, + self.__config.get_use_cdrom_source() is False))) + grid = Grid(1,1) + grid.setField(self.__install_source, 0, 0, anchorLeft = 1) + return [Label("Locate your install media"), + grid] + + def get_select_cdrom_page(self, screen): + drives = [] + media = self.get_hal().list_installable_volumes() + for drive in media.keys(): + drives.append([media[drive], drive, self.__config.is_install_media(drive)]) + self.__install_media = RadioBar(screen, (drives)) + grid = Grid(1, 1) + grid.setField(self.__install_media, 0, 0) + return [Label("Select the install media"), + grid] + + def get_select_iso_page(self, screen): + self.__iso_path = Entry(50, self.__config.get_iso_path()) + grid = Grid(1, 2) + grid.setField(Label("Enter ISO path:"), 0, 0, anchorLeft = 1) + grid.setField(self.__iso_path, 0, 1, anchorLeft = 1) + return [Label("Enter the full path to an install ISO"), + grid] + + def get_network_install_page(self, screen): + self.__install_url = Entry(50, self.__config.get_install_url()) + self.__kickstart_url = Entry(50, self.__config.get_kickstart_url()) + self.__kernel_options = Entry(50, self.__config.get_kernel_options()) + grid = Grid(2,3) + grid.setField(Label("URL:"), 0, 0, anchorRight = 1) + grid.setField(self.__install_url, 1, 0, anchorLeft = 1) + grid.setField(Label("Kickstart URL:"), 0, 1, anchorRight = 1) + grid.setField(self.__kickstart_url, 1, 1, anchorLeft = 1) + grid.setField(Label("Kernel Options:"), 0, 2, anchorRight = 1) + grid.setField(self.__kernel_options, 1, 2, anchorLeft = 1) + return [Label("Provide the operating system URL"), + grid] + + def get_os_type_page(self, screen): + types = [] + for type in Guest.list_os_types(): + types.append([Guest.get_os_type_label(type), type, self.__config.is_os_type(type)]) + self.__os_types = RadioBar(screen, types) + grid = Grid(1, 1) + grid.setField(self.__os_types, 0, 0, anchorLeft = 1) + return [Label("Choose the operating system type"), + grid] + + def get_os_variant_page(self, screen): + variants = [] + type = self.__config.get_os_type() + for variant in Guest.list_os_variants(type): + variants.append([Guest.get_os_variant_label(type, variant), variant, self.__config.is_os_variant(variant)]) + self.__os_variants = RadioBar(screen, variants) + grid = Grid(1, 1) + grid.setField(self.__os_variants, 0, 0, anchorLeft = 1) + return [Label("Choose the operating system version"), + grid] + + def get_ram_and_cpu_page(self, screen): + self.__memory = Entry(10, str(self.__config.get_memory())) + self.__cpus = Entry(10, str(self.__config.get_cpus())) + grid = Grid(2,2) + grid.setField(Label("Memory (RAM):"), 0, 0, anchorRight = 1) + grid.setField(self.__memory, 1, 0, anchorLeft = 1) + grid.setField(Label("CPUs:"), 0, 1, anchorRight = 1) + grid.setField(self.__cpus, 1, 1, anchorLeft = 1) + return [Label("Choose memory and CPU settings"), + grid] + + def get_enable_storage_page(self, screen): + self.__enable_storage = Checkbox("Enable storage for this virtual machine", self.__config.get_enable_storage()) + self.__storage_type = RadioBar(screen,((["Create a disk image on the computer's hard disk", + DomainConfig.NEW_STORAGE, + self.__config.get_use_local_storage()]), + (["Select managed or other existing storage", + DomainConfig.EXISTING_STORAGE, + self.__config.get_use_local_storage() is False]))) + grid = Grid(1,2) + grid.setField(self.__enable_storage, 0, 0, anchorLeft = 1) + grid.setField(self.__storage_type, 0, 1, anchorLeft = 1) + return [Label("Configure storage"), + grid] + + def get_local_storage_page(self, screen): + self.__storage_size = Entry(6, str(self.__config.get_storage_size())) + self.__allocate_storage = Checkbox("Allocate entire disk now", self.__config.get_allocate_storage()) + grid = Grid(2, 2) + grid.setField(self.__allocate_storage, 0, 0, growx = 1, anchorLeft = 1) + grid.setField(Label("Storage size (GB):"), 0, 1, anchorLeft = 1) + grid.setField(self.__storage_size, 1, 1) + return [Label("Configure local storage"), + grid] + + def get_managed_storage_page(self, screen): + volumes = [] + for volume in self.get_libvirt().list_storage_volumes(): + volumes.append(["%s (%d GB)" % (volume.name(), volume.info()[1] / (1024 ** 3)), + volume.name(), + self.__config.is_existing_storage(volume.name())]) + self.__existing_storage = RadioBar(screen, (volumes)) + grid = Grid(2, 1) + grid.setField(Label("Existing storage:"), 0, 0) + grid.setField(self.__existing_storage, 1, 0) + return [Label("Configure managed storage"), + grid] + + def get_bridge_page(self, screen): + bridges = [] + for bridge in self.get_libvirt().list_bridges(): + bridges.append(["Virtual network '%s'" % bridge.name(), bridge.name(), self.__config.get_network_bridge() == bridge.name()]) + self.__network_bridges = RadioBar(screen, (bridges)) + if self.__config.get_mac_address() == None: + self.__config.set_mac_address(self.get_libvirt().generate_mac_address()) + self.__mac_address = Entry(20, self.__config.get_mac_address()) + grid = Grid(1, 1) + grid.setField(self.__network_bridges, 0, 0) + return [Label("Select an existing bridge"), + grid] + + def get_virt_details_page(self, screen): + virt_types = [] + for type in self.get_libvirt().list_virt_types(): + virt_types.append([type, type, self.__config.is_virt_type(type)]) + self.__virt_types = RadioBar(screen, (virt_types)) + archs = [] + for arch in self.get_libvirt().list_architectures(): + archs.append([arch, arch, self.__config.is_architecture(arch)]) + self.__architectures = RadioBar(screen, (archs)) + grid = Grid(2, 2) + grid.setField(Label("Virt Type:"), 0, 0, anchorRight = 1, anchorTop = 1) + grid.setField(self.__virt_types, 1, 0, anchorLeft = 1) + grid.setField(Label("Architecture:"), 0, 1, anchorRight = 1, anchorTop = 1) + grid.setField(self.__architectures, 1, 1, anchorLeft = 1) + return [Label("Configure virtualization details"), + grid] + + def get_confirm_page(self, screen): + grid = Grid(2, 6) + grid.setField(Label("OS:"), 0, 0, anchorRight = 1) + grid.setField(Label(Guest.get_os_variant_label(self.__config.get_os_type(), + self.__config.get_os_variant())), 1, 0, anchorLeft = 1) + grid.setField(Label("Install:"), 0, 1, anchorRight = 1) + grid.setField(Label(self.__config.get_install_type_text()), 1, 1, anchorLeft = 1) + grid.setField(Label("Memory:"), 0, 2, anchorRight = 1) + grid.setField(Label("%s MB" % self.__config.get_memory()), 1, 2, anchorLeft = 1) + grid.setField(Label("CPUs:"), 0, 3, anchorRight = 1) + grid.setField(Label("%d" % self.__config.get_cpus()), 1, 3, anchorLeft = 1) + grid.setField(Label("Storage:"), 0, 4, anchorRight = 1) + grid.setField(Label(self.__config.get_existing_storage()), 1, 4, anchorLeft = 1) + grid.setField(Label("Network:"), 0, 5, anchorRight = 1) + grid.setField(Label(self.__config.get_network_bridge()), 1, 5, anchorLeft = 1) + return [Label("Ready to begin installation of %s" % self.__config.get_guest_name()), + grid] + +def DefineDomain(): + screen = DomainConfigScreen() + screen.start() diff --git a/admin/destroydomain.py b/admin/destroydomain.py new file mode 100755 index 0000000..350c32e --- /dev/null +++ b/admin/destroydomain.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# +# destroydomain.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 * + +class DestroyDomainConfigScreen(DomainListConfigScreen): + LIST_PAGE = 1 + DESTROY_PAGE = 2 + + def __init__(self): + DomainListConfigScreen.__init__(self, "Destroy A Domain") + + def get_elements_for_page(self, screen, page): + if page is self.LIST_PAGE: + return self.get_domain_list_page(screen, defined = False) + elif page is self.DESTROY_PAGE: + return self.get_destroy_page(screen) + + def page_has_next(self, page): + if page is self.LIST_PAGE: return self.has_selectable_domains() + return False + + def page_has_back(self, page): + if page is self.DESTROY_PAGE: return True + return False + + def validate_input(self, page, errors): + if page is self.LIST_PAGE: + if self.get_selected_domain() is not None: + domain = self.get_selected_domain() + try: + self.get_libvirt().destroy_domain(domain) + return True + except Exception, error: + errors.append("There was an error destroy the domain: %s" % domain) + errors.append(str(error)) + else: + errors.append("You must first select a domain to destroy.") + return False + + def get_destroy_page(self, screen): + grid = Grid(1, 1) + grid.setField(Label("%s was successfully destroyed." % self.get_selected_domain()), 0, 0) + return [grid] + +def DestroyDomain(): + screen = DestroyDomainConfigScreen() + screen.start() diff --git a/admin/domainconfig.py b/admin/domainconfig.py new file mode 100644 index 0000000..ef39fe0 --- /dev/null +++ b/admin/domainconfig.py @@ -0,0 +1,217 @@ +# domainconfig.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 Guest + +class DomainConfig: + LOCAL_INSTALL = "local" + NETWORK_INSTALL = "network" + PXE_INSTALL = "pxe" + INSTALL_TYPE_TEXT = {LOCAL_INSTALL : "Local CDROM/ISO", + NETWORK_INSTALL : "URL INstall Tree", + PXE_INSTALL : "PXE Install"} + + INSTALL_SOURCE_CDROM = "cdrom" + INSTALL_SOURCE_ISO = "iso" + + NEW_STORAGE = "new" + EXISTING_STORAGE = "existing" + + def __init__(self): + self.__guest_name = "" + self.__install_type = DomainConfig.LOCAL_INSTALL + self.__use_cdrom_source = True + self.__install_location = "" + self.__install_media = "" + self.__iso_path = "" + self.__install_url = "" + self.__kickstart_url = "" + self.__kernel_options = "" + self.__os_type = "other" + self.__os_variant = None + self.__memory = 512 + self.__cpus = 1 + self.__enable_storage = True + self.__use_local_storage = True + self.__storage_size = 8.0 + self.__allocate_storage = True + self.__existing_storage = "" + self.__network_bridge = None + self.__mac_address = None + self.__virt_type = None + self.__architecture = None + + def set_guest_name(self, name): + self.__guest_name = name + + def get_guest_name(self): + return self.__guest_name + + def set_install_type(self, type): + self.__install_type = type + + def get_install_type(self): + return self.__install_type + + def get_install_type_text(self): + return DomainConfig.INSTALL_TYPE_TEXT[self.get_install_type()] + + def is_install_type(self, type): + return self.__install_type == type + + def set_install_location(self, location): + self.__install_location = location + + def set_use_cdrom_source(self, use): + self.__use_cdrom_source = use + + def get_use_cdrom_source(self): + return self.__use_cdrom_source + + def get_install_location(self): + return self.__install_location + + def is_install_location(self, location): + return self.__install_location == location + + def set_install_media(self, media): + self.__install_media = media + + def get_install_media(self): + return self.__install_media + + def is_install_media(self, media): + return self.__install_media == media + + def set_iso_path(self, path): + self.__iso_path = path + + def get_iso_path(self): + return self.__iso_path + + def set_install_url(self, url): + self.__install_url = url + + def get_install_url(self): + return self.__install_url + + def set_kickstart_url(self, url): + self.__kickstart_url = url + + def get_kickstart_url(self): + return self.__kickstart_url + + def set_kernel_options(self, options): + self.__kernel_options = options + + def get_kernel_options(self): + return self.__kernel_options + + def set_os_type(self, type): + self.__os_type = type + self.__os_variant = Guest.list_os_variants(type)[0] + + def get_os_type(self): + return self.__os_type + + def is_os_type(self, type): + return self.__os_type == type + + def set_os_variant(self, variant): + self.__os_variant = variant + + def get_os_variant(self): + return self.__os_variant + + def is_os_variant(self, variant): + return self.__os_variant == variant + + def set_memory(self, memory): + self.__memory = int(memory) + + def get_memory(self): + return self.__memory + + def set_cpus(self, cpus): + self.__cpus = cpus + + def get_cpus(self): + return self.__cpus + + def set_enable_storage(self, enable): + self.__enable_storage = enable + + def get_enable_storage(self): + return self.__enable_storage + + def set_use_local_storage(self, use): + self.__use_local_storage = use + + def get_use_local_storage(self): + return self.__use_local_storage + + def set_storage_size(self, size): + self.__storage_size = size + + def get_storage_size(self): + return self.__storage_size + + def set_allocate_storage(self, allocate): + self.__allocate_storage = allocate + + def get_allocate_storage(self): + return self.__allocate_storage + + def set_existing_storage(self, storage): + self.__existing_storage = storage + + def get_existing_storage(self): + return self.__existing_storage + + def is_existing_storage(self, storage): + return self.__existing_storage == storage + + def set_network_bridge(self, bridge): + self.__network_bridge = bridge + + def get_network_bridge(self): + return self.__network_bridge + + def set_mac_address(self, address): + self.__mac_address = address + + def get_mac_address(self): + return self.__mac_address + + def set_virt_type(self, type): + self.__virt_type = type + + def get_virt_type(self): + return self.__virt_type + + def is_virt_type(self, type): + return self.__virt_type == type + + def set_architecture(self, architecture): + self.__architecture = architecture + + def get_architecture(self): + return self.__architecture + + def is_architecture(self, architecture): + return self.__architecture == architecture diff --git a/admin/halworker.py b/admin/halworker.py new file mode 100644 index 0000000..448c22d --- /dev/null +++ b/admin/halworker.py @@ -0,0 +1,37 @@ +# halworker.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 dbus +import virtinst + +class HALWorker: + '''Provides utilities for working with HAL to get hardware information.''' + def __init__(self): + self.__bus = dbus.SystemBus() + hobj = self.__bus.get_object("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager") + self.__conn = dbus.Interface(hobj, "org.freedesktop.Hal.Manager") + + def list_installable_volumes(self): + result = {} + for udi in self.__conn.FindDeviceByCapability("volume"): + device = self.__bus.get_object("org.freedesktop.Hal", udi) + info = dbus.Interface(device, "org.freedesktop.Hal.Device") + if info.GetProperty("volume.is_disc"): + if info.GetProperty("volume.disc.has_data"): + result[str(info.GetProperty("block.device"))] = info.GetProperty("volume.label") + return result diff --git a/admin/libvirtworker.py b/admin/libvirtworker.py new file mode 100644 index 0000000..054d09e --- /dev/null +++ b/admin/libvirtworker.py @@ -0,0 +1,248 @@ +# libvirtworker.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 dbus +import libvirt +import os +import virtinst + +from domainconfig import DomainConfig + +class LibvirtWorker: + '''Provides utilities for interfacing with libvirt.''' + def __init__(self, url = "qemu:///system"): + self.__conn = libvirt.open(url) + self.__capabilities = virtinst.CapabilitiesParser.parse(self.__conn.getCapabilities()) + self.__net = virtinst.VirtualNetworkInterface(conn = self.__conn) + self.__net.setup(self.__conn) + (self.__new_guest, self.__new_domain) = virtinst.CapabilitiesParser.guest_lookup(conn = self.__conn) + + def list_domains(self, defined = True, started = True): + '''Lists all domains.''' + result = [] + if defined: + result.extend(self.__conn.listDefinedDomains()) + if started: + for id in self.__conn.listDomainsID(): + result.append(self.__conn.lookupByID(id).name()) + return result + + def get_domain(self, name): + '''Returns the specified domain.''' + result = self.__conn.lookupByName(name) + if result is None: raise Exception("No such domain exists: %s" % name) + + return result + + def domain_exists(self, name): + '''Returns whether a domain with the specified node exists.''' + domains = self.list_domains() + if name in domains: return True + + + return False + + def create_domain(self, name): + '''Creates the specified domain.''' + domain = self.get_domain(name) + domain.create() + + def destroy_domain(self, name): + '''Destroys the specified domain.''' + domain = self.get_domain(name) + domain.destroy() + + def undefine_domain(self, name): + '''Undefines the specified domain.''' + domain = self.get_domain(name) + domain.undefine() + + def list_bridges(self): + '''Lists all defined and active bridges.''' + bridges = self.__conn.listNetworks() + bridges.extend(self.__conn.listDefinedNetworks()) + result = [] + for name in bridges: + bridge = self.__conn.networkLookupByName(name) + result.append(bridge) + return result + + def generate_mac_address(self): + return self.__net.macaddr + + def list_storage_volumes(self): + '''Lists all defined storage volumes.''' + pools = self.__conn.listStoragePools() + pools.extend(self.__conn.listDefinedStoragePools()) + result = [] + for name in pools: + pool = self.__conn.storagePoolLookupByName(name) + for volname in pool.listVolumes(): + volume = self.__conn.storageVolLookupByPath("/var/lib/libvirt/images/%s" % volname) + result.append(volume) + return result + + def get_storage_size(self, name): + '''Returns the size of the specified storage volume.''' + volume = self.__conn.storageVolLookupByPath("/var/lib/libvirt/images/%s" % name) + return volume.info()[1] / (1024.0 ** 3) + + def get_virt_types(self): + result = [] + for guest in self.__capabilities.guests: + guest_type = guest.os_type + for domain in guest.domains: + domain_type = domain.hypervisor_type + label = domain_type + + if domain_type is "kvm" and guest_type is "xen": label = "xenner" + elif domain_type is "xen": + if guest_type is "xen": + label = "xen (paravirt)" + elif guest_type is "kvm": + label = "xen (fullvirt)" + elif domain_type is "test": + if guest_type is "xen": + label = "test (xen)" + elif guest_type is "hvm": + label = "test (hvm)" + + for row in result: + if row[0] == label: + label = None + break + if label is None: continue + + result.append([label, domain_type, guest_type]) + return result + + def list_virt_types(self): + virt_types = self.get_virt_types() + result = [] + for type in virt_types: + result.append(type[0]) + return result + + def get_default_architecture(self): + '''Returns a default hypervisor type for new domains.''' + return self.__new_guest.arch + + def get_hypervisor(self, virt_type): + virt_types = self.get_virt_types() + for type in virt_types: + if type[0] is virt_type: return type[1] + return None + + def get_default_virt_type(self): + '''Returns the default virtualization type for new domains.''' + return self.__new_domain.hypervisor_type + + def get_os_type(self, virt_type): + virt_types = self.get_virt_types() + for type in virt_types: + if type[0] is virt_type: return type[2] + return None + + def list_architectures(self): + result = [] + for guest in self.__capabilities.guests: + for domain in guest.domains: + label = guest.arch + for row in result: + if row == label: + label = None + break + if label is None: continue + + result.append(label) + return result + + def define_domain(self, config, meter): + location = extra = kickstart = None + + if config.get_install_type() == DomainConfig.LOCAL_INSTALL: + if config.get_use_cdrom_source(): + iclass = virtinst.DistroInstaller + location = config.get_install_media() + else: + iclass = virtinst.LiveCDInstaller + location = config.get_is_path() + elif config.get_install_type() == DomainConfig.NETWORK_INSTALL: + iclass = virtinst.DistroInstaller + location = config.get_install_url() + extra = config.get_kernel_options() + kickstart = config.get_kickstart_url() + elif config.get_install_type() == DomainConfig.PXE_INSTALL: + iclass = virtinst.PXEInstaller + + installer = iclass(conn = self.__conn, + type = self.get_hypervisor(config.get_virt_type()), + os_type = self.get_os_type(config.get_virt_type())) + self.__guest = installer.guest_from_installer() + self.__guest.name = config.get_guest_name() + self.__guest.vcpus = config.get_cpus() + self.__guest.memory = config.get_memory() + self.__guest.maxmemory = config.get_memory() + + self.__guest.installer.location = location + if config.get_use_cdrom_source(): self.__guest.installer.cdrom = True + extraargs = "" + if extra: extraargs += extra + if kickstart: extraargs += " ks=%s" % kickstart + if extraargs: self.__guest.installer.extraarags = extraargs + + self.__guest.uuid = virtinst.util.uuidToString(virtinst.util.randomUUID()) + + if config.get_os_type() != "generic": self.__guest.os_type = config.get_os_type() + if config.get_os_variant() != "generic": self.__guest.os_variant = config.get_os_variant() + + self.__guest._graphics_dev = virtinst.VirtualGraphics(type = virtinst.VirtualGraphics.TYPE_VNC) + self.__guest.sound_devs = [] + self.__guest.sound_devs.append(virtinst.VirtualAudio(model = "es1370")) + + self._setup_nics(config) + self._setup_disks(config) + + self.__guest.conn = self.__conn + self.__domain = self.__guest.start_install(False, meter = meter) + + def _setup_nics(self, config): + self.__guest.nics = [] + nic = virtinst.VirtualNetworkInterface(type = virtinst.VirtualNetworkInterface.TYPE_VIRTUAL, + bridge = config.get_network_bridge(), + network = config.get_network_bridge(), + macaddr = config.get_mac_address()) + self.__guest.nics.append(nic) + + def _setup_disks(self, config): + self.__guest.disks = [] + if config.get_enable_storage(): + path = None + if config.get_use_local_storage(): + pool = self.__conn.storagePoolLookupByName("default") + path = virtinst.Storage.StorageVolume.find_free_name(config.get_guest_name(), + pool_object = pool, + suffix = ".img") + path = os.path.join("/var/lib/libvirt/images/", path) + + if path is not None: + storage= virtinst.VirtualDisk(conn = self.__conn, + path = path, + size = config.get_storage_size()) + self.__guest.disks.append(storage) + self.__guest.conn = self.__conn diff --git a/admin/listdomains.py b/admin/listdomains.py new file mode 100755 index 0000000..1b51ee2 --- /dev/null +++ b/admin/listdomains.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# +# listdomains.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 libvirtworker import LibvirtWorker +from configscreen import * + +class ListDomainsConfigScreen(DomainListConfigScreen): + LIST_PAGE = 1 + DETAIL_PAGE = 2 + + def __init__(self): + DomainListConfigScreen.__init__(self, 'List Domains') + + def page_has_next(self, page): + return (page == self.LIST_PAGE) + + def page_has_back(self, page): + return (page == self.DETAIL_PAGE) + + def validate_input(self, page, errors): + if page == self.LIST_PAGE: + if self.get_selected_domain() is None: + errors.append("Please select a domain to view.") + else: + return True + + def get_elements_for_page(self, screen, page): + if page == self.LIST_PAGE: + return self.get_domain_list_page(screen) + elif page == self.DETAIL_PAGE: + return self.get_detail_page_elements(screen) + + def get_detail_page_elements(self, screen): + domain = self.get_libvirt().get_domain(self.get_selected_domain()) + grid = Grid(2, 5) + grid.setField(Label("Name: "), 0, 0, anchorRight = 1) + grid.setField(Label(domain.name()), 1, 0, anchorLeft = 1) + grid.setField(Label("UUID: "), 0, 1, anchorRight = 1) + grid.setField(Label(domain.UUIDString()), 1, 1, anchorLeft = 1) + grid.setField(Label("OS Type: "), 0, 2, anchorRight = 1) + grid.setField(Label(domain.OSType()), 1, 2, anchorLeft = 1) + grid.setField(Label("Max. Memory: "), 0, 3, anchorRight = 1) + grid.setField(Label(str(domain.maxMemory())), 1, 3, anchorLeft = 1) + grid.setField(Label("Max. VCPUs: "), 0, 4, anchorRight = 1) + grid.setField(Label(str(domain.maxVcpus())), 1, 4, anchorLeft = 1) + return [grid] + +def ListDomains(): + screen = ListDomainsConfigScreen() + screen.start() diff --git a/admin/mainmenu.py b/admin/mainmenu.py new file mode 100755 index 0000000..2808dad --- /dev/null +++ b/admin/mainmenu.py @@ -0,0 +1,71 @@ +# mainmenu.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 configscreen import ConfigScreen +from definedomain import DefineDomain +from createdomain import CreateDomain +from destroydomain import DestroyDomain +from undefinedomain import UndefineDomain +from listdomains import ListDomains +from createuser import CreateUser + +DEFINE_DOMAIN = 1 +CREATE_DOMAIN = 2 +DESTROY_DOMAIN = 3 +UNDEFINE_DOMAIN = 4 +LIST_DOMAINS = 5 +CREATE_USER = 6 +EXIT_CONSOLE = 99 + +def MainMenu(): + finished = False + while finished == False: + screen = SnackScreen() + menu = Listbox(height = 0, width = 0, returnExit = 1) + menu.append("Define A Domain", DEFINE_DOMAIN) + menu.append("Create A Domain", CREATE_DOMAIN) + menu.append("Destroy A Domain", DESTROY_DOMAIN) + menu.append("Undefine A Domain", UNDEFINE_DOMAIN) + menu.append("List All Domains", LIST_DOMAINS) + menu.append("Create A User", CREATE_USER) + menu.append("Exit Administration", EXIT_CONSOLE) + gridform = GridForm(screen, "Node Administration Console", 1, 4) + gridform.add(menu, 0, 0) + result = gridform.run(); + screen.popWindow() + screen.finish() + + try: + if result.current() == DEFINE_DOMAIN: DefineDomain() + elif result.current() == CREATE_DOMAIN: CreateDomain() + elif result.current() == DESTROY_DOMAIN: DestroyDomain() + elif result.current() == UNDEFINE_DOMAIN: UndefineDomain() + elif result.current() == LIST_DOMAINS: ListDomains() + elif result.current() == CREATE_USER: CreateUser() + elif result.current() == EXIT_CONSOLE: finished = True + except Exception, error: + screen = SnackScreen() + ButtonChoiceWindow(screen, + "An Exception Has Occurred", + str(error) + "\n" + traceback.format_exc(), + buttons = ["OK"]) + screen.popWindow() + screen.finish() + finished = True diff --git a/admin/nodeadmin.py b/admin/nodeadmin.py new file mode 100755 index 0000000..864a4c0 --- /dev/null +++ b/admin/nodeadmin.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# +# node-admin - 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 sys + +from mainmenu import MainMenu + +def NodeAdmin(): + MainMenu() + +if __name__ == "__main__": + sys.exit(NodeAdmin()) diff --git a/admin/setup.py.in b/admin/setup.py.in new file mode 100644 index 0000000..ac9981a --- /dev/null +++ b/admin/setup.py.in @@ -0,0 +1,34 @@ +# setup.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 setuptools import setup, find_packages + +setup(name = "nodeadmin", + version = "@VERSION@", + package_dir = {'nodeadmin': 'admin'}, + packages = find_packages('.'), + entry_points = { + 'console_scripts': [ + 'nodeadmin = nodeadmin.nodeadmin:NodeAdmin', + 'definedom = nodeadmin.definedomain:DefineDomain', + 'createdom = nodeadmin.createdomain:CreateDomain', + 'destroydom = nodeadmin.destroydomain:DestroyDomain', + 'undefinedom = nodeadmin.undefinedomain:UndefineDomain', + 'createuser = nodeadmin.createuser:CreateUser', + 'listdoms = nodeadmin.listdomains:ListDomains'] + }) diff --git a/admin/undefinedomain.py b/admin/undefinedomain.py new file mode 100755 index 0000000..2620540 --- /dev/null +++ b/admin/undefinedomain.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# +# undefinedomain.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 * + +class UndefineDomainConfigScreen(DomainListConfigScreen): + LIST_PAGE = 1 + CONFIRM_PAGE = 2 + UNDEFINE_PAGE = 3 + + def __init__(self): + DomainListConfigScreen.__init__(self, "Undefine A Domain") + + def get_elements_for_page(self, screen, page): + if page is self.LIST_PAGE: return self.get_domain_list_page(screen) + elif page is self.CONFIRM_PAGE: return self.get_confirm_page(screen) + elif page is self.UNDEFINE_PAGE: return self.get_undefine_page(screen) + + def page_has_next(self, page): + if page is self.LIST_PAGE: return self.has_selectable_domains() + elif page is self.CONFIRM_PAGE: return True + return False + + def page_has_back(self, page): + if page is self.CONFIRM_PAGE: return True + elif page is self.UNDEFINE_PAGE: return True + return False + + def get_back_page(self, page): + if page is self.CONFIRM_PAGE: return self.LIST_PAGE + elif page is self.UNDEFINE_PAGE: return self.LIST_PAGE + + def validate_input(self, page, errors): + if page is self.LIST_PAGE: + if self.get_selected_domain() is not None: + return True + else: + errors.append("You must first select a domain.") + elif page is self.CONFIRM_PAGE: + if self.__confirm_undefine.value(): + domain = self.get_selected_domain() + try: + self.get_libvirt().undefine_domain(domain) + return True + except Exception, error: + errors.append("Failed to undefine %s." % domain) + errors.append(str(error)) + else: + errors.append("You must confirm undefining the domain to proceed.") + return False + + def get_confirm_page(self, screen): + self.__confirm_undefine = Checkbox("Check here to confirm undefining %s." % self.get_selected_domain(), 0) + grid = Grid(1, 1) + grid.setField(self.__confirm_undefine, 0, 0) + return [grid] + + def get_undefine_page(self, screen): + grid = Grid(1, 1) + grid.setField(Label("%s has been undefined." % self.get_selected_domain()), 0, 0) + return [grid] + +def UndefineDomain(): + screen = UndefineDomainConfigScreen() + screen.start() diff --git a/admin/userworker.py b/admin/userworker.py new file mode 100644 index 0000000..167197b --- /dev/null +++ b/admin/userworker.py @@ -0,0 +1,38 @@ +# userworker.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 libuser + +class UserWorker: + '''Provides APIs for creating, modifying and deleting user accounts.''' + def __init__(self): + self.__admin = libuser.admin() + + def create_user(self, username, password, other_group): + '''Creates a new user account with the provides username, + password. The user is also added to the optional group + if one is specified.''' + user = self.__admin.initUser(username) + user.set('pw_passwd', password) + self.__admin.addUser(user) + if other_group is not None: + group = self.__admin.lookupGroupByName(other_group) + if group is None: raise Exception("Invalid group specified: %s" % other_group) + user.add('pw_gid', group.get('pw_gid')[0]) + self.__admin.modifyUser(user) + diff --git a/configure.ac b/configure.ac index d965a82..e778b10 100644 --- a/configure.ac +++ b/configure.ac @@ -8,6 +8,7 @@ test x"$ac_ct_CC:$CFLAGS" = 'xgcc:-g -O2' \ && CFLAGS="$CFLAGS -Wshadow -Wall -Werror" AC_CONFIG_FILES([Makefile + admin/setup.py gptsync/Makefile ovirt-node.spec ]) diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in index 12815c9..f9649ed 100644 --- a/ovirt-node.spec.in +++ b/ovirt-node.spec.in @@ -1,5 +1,7 @@ %define product_family oVirt Node %define beta Beta +%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} + Summary: The oVirt Node daemons/scripts Name: ovirt-node @@ -21,6 +23,8 @@ Requires(post): /sbin/chkconfig Requires(preun): /sbin/chkconfig BuildRequires: libvirt-devel >= 0.5.1 BuildRequires: dbus-devel hal-devel +BuildRequires: python-devel +BuildRequires: setuptool Requires: libvirt >= 0.6.3 Requires: augeas >= 0.3.5 Requires: libvirt-qpid >= 0.2.14-3 @@ -44,6 +48,10 @@ Requires: nc Requires: grub Requires: /usr/sbin/crond Requires: anyterm +Requires: newt-python +Requires: libuser-python +Requires: dbus-python + ExclusiveArch: %{ix86} x86_64 %define app_root %{_datadir}/%{name} @@ -144,6 +152,7 @@ cd - %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/cron.d %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/cron.hourly %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/logrotate.d +%{__install} -d -m0755 %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0755 scripts/ovirt-awake %{buildroot}%{_sbindir} %{__install} -p -m0755 scripts/ovirt-config-boot %{buildroot}%{_sbindir} @@ -164,6 +173,21 @@ cd - %{__install} -p -m0755 scripts/persist %{buildroot}%{_sbindir} %{__install} -p -m0755 scripts/unpersist %{buildroot}%{_sbindir} +# %{__install} -p -m0644 admin/__init__.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 admin/configscreen.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 admin/createdomain.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 admin/createuser.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 admin/definedomain.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 admin/destroydomain.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 admin/domainconfig.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 admin/halworker.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 admin/libvirtworker.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 admin/userworker.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 admin/listdomains.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 admin/mainmenu.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 admin/nodeadmin.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 admin/undefinedomain.py %{buildroot}%{python_sitelib}/nodeadmin + # gptsync %{__install} -p -m0755 gptsync/gptsync %{buildroot}%{_sbindir} %{__install} -p -m0755 gptsync/showpart %{buildroot}%{_sbindir} @@ -182,6 +206,10 @@ cd - %{__install} -p -m0644 logrotate/ovirt-logrotate %{buildroot}%{_sysconfdir}/cron.d %{__install} -p -m0644 logrotate/ovirt-logrotate.conf %{buildroot}%{_sysconfdir}/logrotate.d +# install the admin tools +python admin/setup.py install --root %{buildroot} +rm -rf %{buildroot}%{python_sitelib}/nodeadmin- at VERSION@* + echo "oVirt Node release %{version}-%{release}" > %{buildroot}%{_sysconfdir}/ovirt-release mkdir -p %{buildroot}/%{_sysconfdir}/default touch %{buildroot}/%{_sysconfdir}/default/ovirt @@ -325,7 +353,15 @@ fi %{_sbindir}/ovirt-awake %{_initrddir}/ovirt-functions %defattr(-,root,root,0644) +%{_bindir}/nodeadmin +%{_bindir}/definedom +%{_bindir}/createdom +%{_bindir}/destroydom +%{_bindir}/undefinedom +%{_bindir}/listdoms +%{_bindir}/createuser %{_sysconfdir}/collectd.conf.in +%{python_sitelib}/nodeadmin %config %attr(0644,root,root) %{_sysconfdir}/ovirt-release %config %attr(0644,root,root) %{_sysconfdir}/default/ovirt -- 1.6.2.5