Darryl L. Pierce
2009-Jul-31 20:03 UTC
[Ovirt-devel] RFC: This patch is not being submitted for ACK...
...just looking for some feedback on the direction I'm going. The code won't get all the way to the define stage since I'm in the middle of retrofitting it to use virtinst instead of a home spun node definition.
Darryl L. Pierce
2009-Jul-31 20:03 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 VM similar to virt-manager. The user can list all domains on the system. Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- Makefile.am | 12 ++ node_admin/configscreen.py | 95 +++++++++++++++ node_admin/createuser.py | 22 ++++ node_admin/definevm.py | 284 ++++++++++++++++++++++++++++++++++++++++++++ node_admin/listvms.py | 67 +++++++++++ node_admin/mainmenu.py | 64 ++++++++++ node_admin/node-admin.py | 29 +++++ node_admin/node.py | 210 ++++++++++++++++++++++++++++++++ node_admin/nodeconfig.py | 123 +++++++++++++++++++ node_admin/startvm.py | 22 ++++ node_admin/stopvm.py | 22 ++++ node_admin/undefinevm.py | 22 ++++ ovirt-node.spec.in | 1 + 13 files changed, 973 insertions(+), 0 deletions(-) create mode 100755 node_admin/__init__.py create mode 100644 node_admin/configscreen.py create mode 100755 node_admin/createuser.py create mode 100755 node_admin/definevm.py create mode 100755 node_admin/listvms.py create mode 100755 node_admin/mainmenu.py create mode 100755 node_admin/node-admin.py create mode 100755 node_admin/node.py create mode 100644 node_admin/nodeconfig.py create mode 100755 node_admin/startvm.py create mode 100755 node_admin/stopvm.py create mode 100755 node_admin/undefinevm.py diff --git a/Makefile.am b/Makefile.am index 0374f07..5b758b7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -26,6 +26,18 @@ EXTRA_DIST = \ ovirt-node-selinux.fc \ images/grub-splash.xpm.gz \ images/syslinux-vesa-splash.jpg \ + node_admin/configscreen.py \ + node_admin/createuser.py \ + node_admin/definevm.py \ + node_admin/__init__.py \ + node_admin/listvms.py \ + node_admin/mainmenu.py \ + node_admin/node-admin.py \ + node_admin/node.py \ + node_admin/startvm.py \ + node_admin/stopvm.py \ + node_admin/undefinevm.py \ + node_admin/utils.py \ scripts/collectd \ scripts/collectd.conf.in \ scripts/ovirt \ diff --git a/node_admin/__init__.py b/node_admin/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/node_admin/configscreen.py b/node_admin/configscreen.py new file mode 100644 index 0000000..48aacd1 --- /dev/null +++ b/node_admin/configscreen.py @@ -0,0 +1,95 @@ +# 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 * + +class ConfigScreen: + '''Enables the creation of navigable, multi-paged configuration screens.''' + + __BACK_BUTTON = "back" + __NEXT_BUTTON = "next" + __CANCEL_BUTTON = "cancel" + __FINISH_BUTTON = "finish" + + def __init__(self, title): + self.__title = title + self.__current_page = 1 + self.__finished = False + + 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): + 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", self.__BACK_BUTTON]) + if self.page_has_next(self.__current_page): buttons.append(["Next", self.__NEXT_BUTTON, "F12"]) + if self.page_has_finish(self.__current_page): buttons.append(["Finish", self.__FINISH_BUTTON]) + buttons.append(["Cancel", self.__CANCEL_BUTTON]) + buttonbar = ButtonBar(screen, buttons) + gridform.add(buttonbar, 0, current_element, growx = 1) + current_element += 1 + result = gridform.runOnce() + screen.popWindow() + screen.finish() + pressed = buttonbar.buttonPressed(result) + if pressed == self.__BACK_BUTTON: + self.go_back() + elif pressed == self.__NEXT_BUTTON or pressed == self.__FINISH_BUTTON: + if self.validate_input(self.__current_page): + self.process_input(self.__current_page) + self.go_next() + elif pressed == self.__CANCEL_BUTTON: + active = False diff --git a/node_admin/createuser.py b/node_admin/createuser.py new file mode 100755 index 0000000..e624570 --- /dev/null +++ b/node_admin/createuser.py @@ -0,0 +1,22 @@ +# definevm.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 * + +def createUser(): + print "Create user!" diff --git a/node_admin/definevm.py b/node_admin/definevm.py new file mode 100755 index 0000000..d79f4ce --- /dev/null +++ b/node_admin/definevm.py @@ -0,0 +1,284 @@ +# definevm.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 node import * +from nodeutils import * +import os +from nodeconfig import NodeConfig +from configscreen import ConfigScreen + +from virtinst import Guest, DistroInstaller, LiveCDInstaller, PXEInstaller + +VM_DETAILS_PAGE = 1 +LOCAL_INSTALL_PAGE = 2 +NETWORK_INSTALL_PAGE = 3 +OS_TYPE_PAGE = 10 +OS_VARIANT_PAGE = 11 +RAM_CPU_PAGE = 12 +LOCAL_STORAGE_PAGE = 13 +BRIDGE_PAGE = 14 +CONFIRM_PAGE = 15 + +LOCATION="location" +KICKSTART="kickstart" +KERNELOPTS="kernel.options" +OS_TYPE="os.type" +OS_VARIANT="os.variant" +MEMORY="memory" +CPUS="cpus" + +class VirtualMachineConfigScreen(ConfigScreen): + + def __init__(self): + ConfigScreen.__init__(self, "Create A New Virtual Machine") + self.__config = NodeConfig() + + 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 == 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 == LOCAL_STORAGE_PAGE: + return self.get_local_storage_page(screen) + elif page == BRIDGE_PAGE: + return self.get_bridge_page(screen) + elif page == CONFIRM_PAGE: + return self.get_confirm_page(screen) + return [] + + def validate_input(self, page): + if page == VM_DETAILS_PAGE: + if len(self.__guest_name.value()) > 0: + return True + elif page == LOCAL_INSTALL_PAGE: + if len(self.__install_source.value()) > 0 and os.path.exists(self.__install_source.value()): + return True + elif page == NETWORK_INSTALL_PAGE: + if len(self.__install_url.value()) > 0: return True + 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 + elif page == LOCAL_STORAGE_PAGE: + if self.__storage_type.getSelection() == Node.STORAGE_TYPE_NEW: + if len(self.__storage_size.value()) > 0: + if float(self.__storage_size.value()) > 0: return True + elif self.__storage_type.getSelection() == Node.STORAGE_TYPE_EXISTING: + if os.path.exists(self.__existing_storage.value()): return true + elif page == BRIDGE_PAGE: return True + 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_install_location(self.__install_source.value()) + elif page == NETWORK_INSTALL_PAGE: + self.__config[LOCATION] = self.__install_url.value() + self.__config[KICKSTART] = self.__kickstart_url.value() + self.__config[KERNELOPTS] = 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 == LOCAL_STORAGE_PAGE: + self.__config.set_enable_storage(self.__enable_storage.value()) + if self.__config.set_enable_storage: + if self.__storage_type.getSelection() == NodeConfig.NEW_STORAGE: + self.__config.set_use_local_storage(true) + self.__config.set_storage_size(float(self.__storage_size.value())) + self.__guest.set_allocate_storage(self.__allocate_storage.value()) + elif self.__storage_type.getSelection() == Node.STORAGE_TYPE_EXISTING: + self.__config.set_use_local_storage(false) + self.__guest.set_existing_storage(self.__existing_storage.value()) + elif page == CONFIRM_PAGE: + # save the VM + define_domain(self.__guest) + 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 == NodeConfig.LOCAL_INSTALL: + result = LOCAL_INSTALL_PAGE + elif install_type == NodeConfig.NETWORK_INSTALL: + result = NETWORK_INSTALL_PAGE + elif install_type == NodeConfig.PXE_INSTALL: + result = VM_DETAILS_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 == NodeConfig.LOCAL_INSTALL: + result = LOCAL_INSTALL_PAGE + elif install_type == NodeConfig.NETWORK_INSTALL: + result = NETWORK_INSTALL_PAGE + elif install_type == NodeConfig.PXE_INSTALL: + result = OS_TYPE_PAGE + elif page == LOCAL_INSTALL_PAGE or page == NETWORK_INSTALL_PAGE: + result = OS_TYPE_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)", + NodeConfig.LOCAL_INSTALL, + self.__config.is_install_type(NodeConfig.LOCAL_INSTALL)), + ("Network Install (HTTP, FTP, or NFS)", + NodeConfig.NETWORK_INSTALL, + self.__config.is_install_type(NodeConfig.NETWORK_INSTALL)), + ("Network Boot (PXE)", + NodeConfig.PXE_INSTALL, + self.__config.is_install_type(NodeConfig.PXE_INSTALL)))) + grid = Grid(2,4) + grid.setField(Label("Enter your machine details"), 0, 0, growx = 1) + grid.setField(Label("Name:"), 0, 1, anchorLeft = 1) + grid.setField(self.__guest_name, 1, 1) + grid.setField(Label("Choose how you would like to install the operating system"), 0, 2, growx = 1) + grid.setField(self.__install_type, 0, 3) + return [grid] + + def get_local_install_page(self, screen): + self.__install_source = Entry(50, self.__config.get_install_location()) + grid = Grid(2,1) + grid.setField(Label("Install source:"), 0, 0) + grid.setField(self.__install_source, 1, 0) + return [grid] + + def get_network_install_page(self, screen): + self.__install_url = Entry(50, _get_field_str(self.__guest.get_location())) + self.__kickstart_url = Entry(50, self.__guest.get_kickstart_url()) + self.__kernel_options = Entry(50, self.__guest.get_kernel_options()) + grid = Grid(2,4) + grid.setField(Label("Provide the operating system URL"), 0, 0, growx = 1) + grid.setField(Label("URL:"), 0, 1) + grid.setField(self.__install_url, 1, 1) + grid.setField(Label("Kickstart URL:"), 0, 2) + grid.setField(self.__kickstart_url, 1, 2) + grid.setField(Label("Kernel Options:"), 0, 3) + grid.setField(self.__kernel_options, 1, 3) + return [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(2, 1) + grid.setField(Label("OS Type:"), 0, 0) + grid.setField(self.__os_types, 1, 0) + return [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(2, 1) + grid.setField(Label("OS Variant:"), 0, 0) + grid.setField(self.__os_variants, 1, 0) + return [grid] + + def get_ram_and_cpu_page(self, screen): + self.__memory = Entry(4, str(self.__config.get_memory())) + self.__cpus = Entry(3, str(self.__config.get_cpus())) + grid = Grid(2,3) + grid.setField(Label("Choose memory and CPU settings"), 0, 0, growx = 1) + grid.setField(Label("Memory (RAM):"), 0, 1) + grid.setField(self.__memory, 1, 1) + grid.setField(Label("CPUs:"), 0, 2) + grid.setField(self.__cpus, 1, 2) + return [grid] + + def get_local_storage_page(self, screen): + self.__enable_storage = Checkbox("Enable storage for this virtual machine", self.__config.get_enable_storage()) + storage_types = [] + for option in Node.STORAGE_TYPES.keys(): + storage_types.append([Node.STORAGE_TYPES.get(option), option, self.__config.get_storage_type() == option]) + self.__storage_type = RadioBar(screen,storage_types, + ((["Create a disk image on the computer's hard disk", + NodeConfig.NEW_STORAGE, + self.__config.get_use_local_storage()]), + (["Select managed or other existing storage", + NodeConfig.EXISTING_STORAGE, + self.__config.get_use_local_storage() is False]))) + self.__storage_size = Entry(6, str(self.__config.get_storage_size())) + self.__allocate_storage = Checkbox("Allocate entire disk now", self.__config.get_allocate_storage()) + self.__existing_storage = Entry(60, self.__config.get_existing_storage()) + grid = Grid(2,5) + grid.setField(self.__enable_storage, 0, 0, growx = 1, anchorLeft = 1) + grid.setField(self.__storage_type, 1, 1, growx = 1) + grid.setField(self.__allocate_storage, 0, 2, growx = 1, anchorLeft = 1) + grid.setField(Label("Storage size (GB):"), 0, 3, anchorLeft = 1) + grid.setField(self.__storage_size, 1, 3) + grid.setField(Label("Existing storage:"), 0, 4) + grid.setField(self.__existing_storage, 1, 4) + return [grid] + + def get_bridge_page(self, screen): + return [] + + def get_confirm_page(self, screen): + grid = Grid(2, 6) + grid.setField(Label("Ready to begin installation of %s" % self.__guest.get_name()), 0, 0, growx = 1) + grid.setField(Label("OS:"), 0, 1) + grid.setField(Label(self.__guest.get_os_type()), 1, 1) + grid.setField(Label("Install:"), 0, 2) + grid.setField(Label(self.__guest.get_boot_type_text()), 1, 2) + grid.setField(Label("Memory:"), 0, 3) + grid.setField(Label("%s MB" % self.__guest.get_memory()), 1, 3) + grid.setField(Label("CPUs:"), 0, 4) + grid.setField(Label("%d" % self.__guest.get_cpus()), 1, 4) + grid.setField(Label("Storage:"), 0, 5) + grid.setField(Label(self.__guest.get_existing_storage()), 1, 5) + return [grid] + +def defineVM(): + vmconfig = VirtualMachineConfigScreen() + vmconfig.start() diff --git a/node_admin/listvms.py b/node_admin/listvms.py new file mode 100755 index 0000000..ff3e42e --- /dev/null +++ b/node_admin/listvms.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# definevm.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 configscreen import ConfigScreen +from snack import * +from nodeutils import * + +class ListDomainsConfigScreen(ConfigScreen): + __LIST_PAGE = 1 + __DETAIL_PAGE = 2 + + def __init__(self): + ConfigScreen.__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 get_elements_for_page(self, screen, page): + if page == self.__LIST_PAGE: + return self.get_list_page_elements() + elif page == self.__DETAIL_PAGE: + return self.get_detail_page_elements() + + def get_list_page_elements(self): + domains = get_domain_list() + self.__domain_list = Listbox(0) + for name in domains.keys(): + self.__domain_list.append(name, name) + return [self.__domain_list] + + def get_detail_page_elements(self): + domain = get_domain(self.__domain_list.current()) + 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 listVMs(): + screen = ListDomainsConfigScreen() + screen.start() diff --git a/node_admin/mainmenu.py b/node_admin/mainmenu.py new file mode 100755 index 0000000..6b3d27e --- /dev/null +++ b/node_admin/mainmenu.py @@ -0,0 +1,64 @@ +# 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 * +from definevm import defineVM +from listvms import listVMs + +DEFINE_VM = 1 +START_VM = 2 +STOP_VM = 3 +UNDEFINE_VM = 4 +LIST_VMS = 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 new VM", DEFINE_VM) + menu.append("Start a VM", START_VM) + menu.append("Stop a running VM", STOP_VM) + menu.append("Undefine a VM", UNDEFINE_VM) + menu.append("List all VMs", LIST_VMS) + menu.append("Create a user", CREATE_USER) + menu.append("Exit Node Administration", EXIT_CONSOLE) + gridform = GridForm(screen, "Node Administration Console", 1, 4) + gridform.add(menu, 0, 0) + result = gridform.run(); + screen.popWindow() + screen.finish() + + if result.current() == DEFINE_VM: + defineVM() + elif result.current() == START_VM: + startVM() + elif result.current() == STOP_VM: + stopVM() + elif result.current() == UNDEFINE_VM: + destroyVM() + elif result.current() == LIST_VMS: + listVMs() + elif result.current() == CREATE_USER: + createUser() + elif result.current() == EXIT_CONSOLE: + finished = True + else: + print "Nope.\n" diff --git a/node_admin/node-admin.py b/node_admin/node-admin.py new file mode 100755 index 0000000..5061362 --- /dev/null +++ b/node_admin/node-admin.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 * + +def main(): + mainMenu() + +if __name__ == "__main__": + sys.exit(main()) diff --git a/node_admin/node.py b/node_admin/node.py new file mode 100755 index 0000000..d566f87 --- /dev/null +++ b/node_admin/node.py @@ -0,0 +1,210 @@ +# node.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. + +class Node: + BOOT_TYPE_LOCAL = "local" + BOOT_TYPE_NETWORK = "network" + BOOT_TYPE_PXE = "pxe" + BOOT_TYPES = {BOOT_TYPE_LOCAL : "Local install media (ISO image or CDROM)", + BOOT_TYPE_NETWORK : "Network Install (HTTP, FTP, or NFS)", + BOOT_TYPE_PXE : "Network Boot (PXE)"} + OS_TYPES = {"generic" : "Generic", + "linux" : "Linux", + "other" : "Other", + "solaris" : "Solaris", + "unix" : "UNIX", + "windows" : "Windows"} + OS_VERSIONS = ["Generic"] + STORAGE_TYPE_NEW = "new" + STORAGE_TYPE_EXISTING = "existing" + STORAGE_TYPES = {STORAGE_TYPE_NEW : "Create a disk image on the computer's hard disk", + STORAGE_TYPE_EXISTING : "Select managed or other existing storage"} + + def __init__(self): + self.__name = "" + self.__boot_type = Node.BOOT_TYPE_PXE + self.__install_source = "" + self.__install_url = "" + self.__kickstart_url = "" + self.__kernel_options = "" + self.__os_type = "generic" + self.__os_version = "generic" + self.__memory = 512 + self.__cpus = 1 + self.__storage_size = 8.0 + self.__allocate_storage = True + self.__storage_type = Node.STORAGE_TYPE_NEW + self.__existing_storage = "" + + def set_name(self, name): + self.__name = name + + def get_name(self): + return self.__name + + def set_boot_type(self, boot_type): + if Node.BOOT_TYPES.has_key(boot_type): + self.__boot_type = boot_type + else: + raise ValueError("Invalid boot type: %s" % boot_type) + + def get_boot_type(self): + return self.__boot_type + + def get_boot_device(self): + if self.__boot_type == Node.BOOT_TYPE_PXE: + return "network" + else: + return "disk" + + def get_boot_type_text(self): + return Node.BOOT_TYPES.get(self.__boot_type) + + def set_install_source(self, install_source): + if os.path.exist(install_source): + self.__install_source = install_source + else: + raise Error("Non-existent install source: %s" % install_source) + + def get_install_source(self): + return self.__install_source + + 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, os_type): + if Node.OS_TYPES.has_key(os_type): + self.__os_type = os_type + else: + raise ValueError("Invalid OS type: %s" % os_type) + + def get_os_type(self): + return self.__os_type + + def set_os_version(self, os_version): + try: + if Node.OS_VERSIONS.index(os_version) >= 0: self.__os_version = os_version + except ValueError, error: + raise error + + def get_os_version(self): + return self.__os_version + + def set_memory(self, memory): + if int(memory) > 0: + self.__memory = memory + else: + raise ValueError("Invalid memory value: %d" % int(memory)) + + def get_memory(self): + return self.__memory + + def set_cpus(self, cpus): + if int(cpus): + self.__cpus = cpus + else: + raise ValueError("Invalid number of cpus: %d" % int(cpus)) + + def get_cpus(self): + return self.__cpus + + def has_local_storage(self): + if self.__storage_size > 0: return True + return False + + def get_local_storage(self): + return "/var/lib/libvirt/images/%s.img" % self.get_name() + + def set_storage_size(self, storage_size): + if float(storage_size) > 0.0: + self.__storage_size = storage_size + else: + raise ValueError("Invalid storage size: %f" % float(storage_size)) + + def get_storage_size(self): + return self.__storage_size + + def set_allocate_storage(self, allocate_storage): + self.__allocate_storage = allocate_storage + + def get_allocate_storage(self): + return self.__allocate_storage + + def set_storage_type(self, storage_type): + if Node.STORAGE_TYPES.has_key(storage_type): + self.__storage_type = storage_type + else: + raise ValueError("Invalid storage type: %s" % storage_type) + + def get_storage_type(self): + return self.__storage_type + + def set_existing_storage(self, existing_storage): + if os.path.exist(existing_storage): + self.__existing_storage = existing_storage + else: + raise ValueError("No such file: %s" % existing_storage) + + def get_existing_storage(self): + return self.__existing_storage + + def get_xml(self): + result = "<domain type='kvm'>\n" + result += "<name>%s</name>\n" % self.get_name() + result += "<memory>%d</memory>\n" % (self.get_memory() * 1024 * 1024) + result += "<vcpu>%d</vcpu>\n" % self.get_cpus() + result += "<os>\n" + result += "<type arch='%s' machine='%s'>%s</type>\n" % ('x86_64', 'pc', 'hvm') + result += "<boot dev='%s' />\n" % self.get_boot_device() + result += "</os>\n" + result += "<features>\n" + result += "<acpi />\n" + result += "<pae />\n" + result += "</features>\n" + result += "<clock offset='utc' />\n" + result += "<on_poweroff>destroy</on_poweroff>\n" + result += "<on_reboot>restart</on_reboot>\n" + result += "<on_crash>restart</on_crash>\n" + result += "<devices>\n" + if self.has_local_storage(): + result += "<disk type='file' device='disk'>\n" + result += "<source file='%s' />\n" % self.get_local_storage() + result += "<target dev='vda' bus='virtio' />\n" + result += "</disk>\n" + result += "<serial type='pty' />\n" + result += "<console type='pty' />\n" + result += "<graphics type='vnc' port='-1' autoport='yes' keyman='en-us' />\n" + result += "</devices>\n" + result += "</domain>\n" + + return result diff --git a/node_admin/nodeconfig.py b/node_admin/nodeconfig.py new file mode 100644 index 0000000..1e1106d --- /dev/null +++ b/node_admin/nodeconfig.py @@ -0,0 +1,123 @@ +# nodeconfig.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 NodeConfig: + LOCAL_INSTALL = "local" + NETWORK_INSTALL = "network" + PXE_INSTALL = "pxe" + + NEW_STORAGE = "new" + EXISTING_STORAGE = "existing" + + def __init__(self): + self.__guest_name = "" + self.__install_type = NodeConfig.LOCAL_INSTALL + self.__install_location = "" + self.__os_type = Guest.list_os_types()[0] + self.__os_variant = Guest.list_os_variants(self.__os_type)[0] + 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 = "" + + 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 is_install_type(self, type): + return self.__install_type == type + + def set_install_location(self, location): + self.__install_location = location + + def get_install_location(self): + return self.__install_location + + 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 diff --git a/node_admin/startvm.py b/node_admin/startvm.py new file mode 100755 index 0000000..00b4cd2 --- /dev/null +++ b/node_admin/startvm.py @@ -0,0 +1,22 @@ +# definevm.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 * + +def startVM(): + print "Start VM!" diff --git a/node_admin/stopvm.py b/node_admin/stopvm.py new file mode 100755 index 0000000..656dfdc --- /dev/null +++ b/node_admin/stopvm.py @@ -0,0 +1,22 @@ +# definevm.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 * + +def stopVM(): + print "Stop VM!" diff --git a/node_admin/undefinevm.py b/node_admin/undefinevm.py new file mode 100755 index 0000000..7217985 --- /dev/null +++ b/node_admin/undefinevm.py @@ -0,0 +1,22 @@ +# definevm.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 * + +def defineVM(): + print "Undefine VM!" diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in index 3138011..4fded95 100644 --- a/ovirt-node.spec.in +++ b/ovirt-node.spec.in @@ -43,6 +43,7 @@ Requires: nc Requires: grub Requires: /usr/sbin/crond Requires: anyterm +Requires: newt-python ExclusiveArch: %{ix86} x86_64 %define app_root %{_datadir}/%{name} -- 1.6.2.5
Darryl L. Pierce
2009-Aug-28 15:19 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 | 13 ++ 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 | 73 ++++++++ admin/nodeadmin.py | 29 +++ admin/setup.py.in | 34 ++++ admin/undefinedomain.py | 83 +++++++++ admin/userworker.py | 38 ++++ configure.ac | 1 + ovirt-node.spec.in | 33 ++++ 19 files changed, 1755 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..3329644 100644 --- a/Makefile.am +++ b/Makefile.am @@ -26,6 +26,19 @@ 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/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..3ff6559 --- /dev/null +++ b/admin/mainmenu.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python +# +# 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..e84afb8 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,7 @@ Requires(post): /sbin/chkconfig Requires(preun): /sbin/chkconfig BuildRequires: libvirt-devel >= 0.5.1 BuildRequires: dbus-devel hal-devel +BuildRequires: python-devel Requires: libvirt >= 0.6.3 Requires: augeas >= 0.3.5 Requires: libvirt-qpid >= 0.2.14-3 @@ -44,6 +47,9 @@ Requires: nc Requires: grub Requires: /usr/sbin/crond Requires: anyterm +Requires: newt-python +Requires: libuser-python + ExclusiveArch: %{ix86} x86_64 %define app_root %{_datadir}/%{name} @@ -144,6 +150,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 +171,20 @@ 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 -m0755 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 -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 +203,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 +350,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