[Ovirt-devel] [PATCH 1/2] Users can now work with remote libvirt hosts.

Darryl L. Pierce dpierce at redhat.com
Tue Dec 8 21:13:53 UTC 2009


The user can:
 * select a remote machine
 * add a remote machine
 * remove a remote machine

Signed-off-by: Darryl L. Pierce <dpierce at redhat.com>
---
 ...rs-can-now-work-with-remote-libvirt-hosts.patch |  628 ++++++++++++++++++++
 ...rs-to-migrate-virtual-machines-between-ho.patch |  253 ++++++++
 Makefile.am                                        |    5 +
 nodeadmin/addhost.py                               |  129 ++++
 nodeadmin/changehost.py                            |   58 ++
 nodeadmin/configscreen.py                          |   36 ++-
 nodeadmin/definenet.py                             |    1 +
 nodeadmin/hostconnect.py                           |   29 +
 nodeadmin/hostmenu.py                              |   46 ++
 nodeadmin/libvirtworker.py                         |   53 ++-
 nodeadmin/mainmenu.py                              |   24 +-
 nodeadmin/removehost.py                            |   66 ++
 ovirt-node.spec.in                                 |    5 +
 13 files changed, 1320 insertions(+), 13 deletions(-)
 create mode 100644 0001-Users-can-now-work-with-remote-libvirt-hosts.patch
 create mode 100644 0002-Enables-users-to-migrate-virtual-machines-between-ho.patch
 create mode 100644 nodeadmin/addhost.py
 create mode 100644 nodeadmin/changehost.py
 create mode 100644 nodeadmin/hostconnect.py
 create mode 100644 nodeadmin/hostmenu.py
 create mode 100644 nodeadmin/removehost.py

diff --git a/0001-Users-can-now-work-with-remote-libvirt-hosts.patch b/0001-Users-can-now-work-with-remote-libvirt-hosts.patch
new file mode 100644
index 0000000..a6c2342
--- /dev/null
+++ b/0001-Users-can-now-work-with-remote-libvirt-hosts.patch
@@ -0,0 +1,628 @@
+From f4bd14953ef3ff1335b6980563ebbf64cb97153a Mon Sep 17 00:00:00 2001
+From: Darryl L. Pierce <dpierce at redhat.com>
+Date: Wed, 28 Oct 2009 16:29:53 -0400
+Subject: [PATCH 1/2] Users can now work with remote libvirt hosts.
+
+The user can:
+ * select a remote machine
+ * add a remote machine
+ * remove a remote machine
+---
+ Makefile.am                |    5 ++
+ nodeadmin/addhost.py       |  129 ++++++++++++++++++++++++++++++++++++++++++++
+ nodeadmin/changehost.py    |   58 ++++++++++++++++++++
+ nodeadmin/configscreen.py  |   36 ++++++++++++-
+ nodeadmin/definenet.py     |    1 +
+ nodeadmin/hostconnect.py   |   29 ++++++++++
+ nodeadmin/hostmenu.py      |   46 ++++++++++++++++
+ nodeadmin/libvirtworker.py |   53 +++++++++++++++++-
+ nodeadmin/mainmenu.py      |   14 +++--
+ nodeadmin/removehost.py    |   66 ++++++++++++++++++++++
+ ovirt-node.spec.in         |    5 ++
+ 11 files changed, 434 insertions(+), 8 deletions(-)
+ create mode 100644 nodeadmin/addhost.py
+ create mode 100644 nodeadmin/changehost.py
+ create mode 100644 nodeadmin/hostconnect.py
+ create mode 100644 nodeadmin/hostmenu.py
+ create mode 100644 nodeadmin/removehost.py
+
+diff --git a/Makefile.am b/Makefile.am
+index b3929de..1671405 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -28,11 +28,15 @@ EXTRA_DIST =			\
+   images/syslinux-vesa-splash.jpg	\
+   nodeadmin/__init__.py         \
+   nodeadmin/adddomain.py        \
++  nodeadmin/addhost.py          \
++  nodeadmin/changehost.py       \
+   nodeadmin/configscreen.py     \
+   nodeadmin/createnetwork.py    \
+   nodeadmin/createuser.py       \
+   nodeadmin/destroynetwork.py   \
+   nodeadmin/halworker.py        \
++  nodeadmin/hostconnect.py      \
++  nodeadmin/hostmenu.py         \
+   nodeadmin/libvirtworker.py    \
+   nodeadmin/userworker.py       \
+   nodeadmin/mainmenu.py         \
+@@ -40,6 +44,7 @@ EXTRA_DIST =			\
+   nodeadmin/netmenu.py          \
+   nodeadmin/nodemenu.py         \
+   nodeadmin/removedomain.py     \
++  nodeadmin/removehost.py       \
+   nodeadmin/undefinenetwork.py  \
+   nodeadmin/startdomain.py      \
+   nodeadmin/stopdomain.py       \
+diff --git a/nodeadmin/addhost.py b/nodeadmin/addhost.py
+new file mode 100644
+index 0000000..ef35b7d
+--- /dev/null
++++ b/nodeadmin/addhost.py
+@@ -0,0 +1,129 @@
++# addhost.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 *
++
++DETAILS_PAGE = 1
++CONFIRM_PAGE = 2
++
++HYPERVISOR_XEN      = "xen"
++HYPERVISOR_KVM      = "kvm"
++
++HYPERVISORS = {HYPERVISOR_XEN : "Xen",
++               HYPERVISOR_KVM : "QEMU/KVM"}
++
++CONNECTION_LOCAL    = "local"
++CONNECTION_KERBEROS = "kerberos"
++CONNECTION_SSL      = "ssl"
++CONNECTION_SSH      = "ssh"
++
++CONNECTIONS = {CONNECTION_LOCAL    : "Local",
++               CONNECTION_KERBEROS : "Remote Password or Kerberos",
++               CONNECTION_SSL      : "Remote SSL/TLS with x509 certificate",
++               CONNECTION_SSH      : "Remote tunnel over SSH"}
++
++class AddHostConfigScreen(ConfigScreen):
++    def __init__(self):
++        ConfigScreen.__init__(self, "Add A Remote Host")
++        self.__configured = False
++
++    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 page_has_next(self, page):
++        return page < CONFIRM_PAGE
++
++    def page_has_back(self, page):
++        return page > DETAILS_PAGE
++
++    def page_has_finish(self, page):
++        return page is CONFIRM_PAGE
++
++    def validate_input(self, page, errors):
++        if page is DETAILS_PAGE:
++            if len(self.__hostname.value()) > 0:
++                return True
++            else:
++                errors.append("You must enter a remote hostname.")
++        elif page is CONFIRM_PAGE: return True
++        return False
++
++    def process_input(self, page):
++        if page is CONFIRM_PAGE:
++            hv       = self.__hypervisor.getSelection()
++            conn     = self.__connection.getSelection()
++            hostname = self.__hostname.value()
++
++            if   hv is HYPERVISOR_XEN:
++                if   conn is CONNECTION_LOCAL:    url = "xen:///"
++                elif conn is CONNECTION_KERBEROS: url = "xen+tcp:///" + hostname + "/"
++                elif conn is CONNECTION_SSL:      url = "xen+tls:///" + hostname + "/"
++                elif conn is CONNECTION_SSH:      url = "xen+ssh:///" + hostname + "/"
++            elif hv is HYPERVISOR_KVM:
++                if   conn is CONNECTION_LOCAL:    url = "qemu:///system"
++                elif conn is CONNECTION_KERBEROS: url = "qemu+tcp://" + hostname + "/system"
++                elif conn is CONNECTION_SSL:      url = "qemu+tls://" + hostname + "/system"
++                elif conn is CONNECTION_SSH:      url = "qemu+ssh://" + hostname + "/system"
++
++            self.get_virt_manager_config().add_connection(url)
++            self.set_finished()
++
++    def get_details_page(self, screen):
++        if not self.__configured:
++            self.__hypervisor = RadioBar(screen, ((HYPERVISORS[HYPERVISOR_XEN], HYPERVISOR_XEN, True),
++                                                  (HYPERVISORS[HYPERVISOR_KVM], HYPERVISOR_KVM, False)))
++            self.__connection = RadioBar(screen, ((CONNECTIONS[CONNECTION_LOCAL],    CONNECTION_LOCAL, True),
++                                                  (CONNECTIONS[CONNECTION_KERBEROS], CONNECTION_KERBEROS, False),
++                                                  (CONNECTIONS[CONNECTION_SSL],      CONNECTION_SSL, False),
++                                                  (CONNECTIONS[CONNECTION_SSH],      CONNECTION_SSH, False)))
++            self.__hostname = Entry(50, "")
++            self.__autoconnect = Checkbox("Autoconnect on Startup")
++            self.__configured = True
++        grid = Grid(2, 4)
++        grid.setField(Label("Hypervisor:"), 0, 0, anchorRight = 1, anchorTop = 1)
++        grid.setField(self.__hypervisor, 1, 0, anchorLeft = 1)
++        grid.setField(Label("Connection:"), 0, 1, anchorRight = 1, anchorTop = 1)
++        grid.setField(self.__connection, 1, 1, anchorLeft = 1)
++        grid.setField(Label("Hostname:"), 0, 2, anchorRight = 1)
++        grid.setField(self.__hostname, 1, 2, anchorLeft = 1)
++        grid.setField(Label(""), 0, 3, anchorRight = 1)
++        grid.setField(self.__autoconnect, 1, 3, anchorLeft = 1)
++        return [Label("Add Connection"),
++                grid]
++
++    def get_confirm_page(self, screen):
++        grid = Grid(2, 4)
++        grid.setField(Label("Hypervisor:"), 0, 0, anchorRight = 1)
++        grid.setField(Label(HYPERVISORS[self.__hypervisor.getSelection()]), 1, 0, anchorLeft = 1)
++        grid.setField(Label("Connection:"), 0, 1, anchorRight = 1)
++        grid.setField(Label(CONNECTIONS[self.__connection.getSelection()]), 1, 1, anchorLeft = 1)
++        grid.setField(Label("Hostname:"), 0, 2, anchorRight = 1)
++        grid.setField(Label(self.__hostname.value()), 1, 2, anchorLeft = 1)
++        grid.setField(Label("Autoconnect on Startup:"), 0, 3, anchorRight = 1)
++        label = "Yes"
++        if not self.__autoconnect.value(): label = "No"
++        grid.setField(Label(label), 1, 3, anchorLeft = 1)
++        return [Label("Confirm Connection"),
++                grid]
++
++def AddHost():
++    screen = AddHostConfigScreen()
++    screen.start()
+diff --git a/nodeadmin/changehost.py b/nodeadmin/changehost.py
+new file mode 100644
+index 0000000..23e6854
+--- /dev/null
++++ b/nodeadmin/changehost.py
+@@ -0,0 +1,58 @@
++# changehost.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 logging
++import libvirtworker
++from configscreen import *
++
++CONNECTION_LIST_PAGE = 1
++CONNECTED_PAGE       = 2
++
++class ChangeHostConfigScreen(HostListConfigScreen):
++    def __init__(self):
++        HostListConfigScreen.__init__(self, "Change Host")
++
++    def get_elements_for_page(self, screen, page):
++        if   page is CONNECTION_LIST_PAGE: return self.get_connection_list_page(screen)
++        elif page is CONNECTED_PAGE:       return self.get_connected_page(screen)
++
++    def process_input(self, page):
++        if   page is CONNECTION_LIST_PAGE:
++            logging.info("Changing libvirt connection to %s" % self.get_selected_connection())
++            libvirtworker.set_default_url(self.get_selected_connection())
++            self.get_libvirt().open_connection(self.get_selected_connection())
++        elif page is CONNECTED_PAGE: self.set_finished()
++
++    def page_has_next(self, page):
++        if page is CONNECTION_LIST_PAGE: return self.has_selectable_connections()
++        return False
++
++    def page_has_back(self, page):
++        return page > CONNECTION_LIST_PAGE
++
++    def page_has_finish(self, page):
++        return page is CONNECTED_PAGE
++
++    def get_connected_page(self, screen):
++        return [Label("Connected to %s" % self.get_selected_connection())]
++
++def ChangeHost():
++    screen = ChangeHostConfigScreen()
++    screen.start()
+diff --git a/nodeadmin/configscreen.py b/nodeadmin/configscreen.py
+index f214aea..98e0338 100644
+--- a/nodeadmin/configscreen.py
++++ b/nodeadmin/configscreen.py
+@@ -18,7 +18,7 @@
+
+ from snack import *
+ from halworker import HALWorker
+-from libvirtworker import LibvirtWorker
++from libvirtworker import *
+ import traceback
+
+ BACK_BUTTON   = "back"
+@@ -35,6 +35,7 @@ class ConfigScreen:
+         self.__finished = False
+         self.__hal = HALWorker()
+         self.__libvirt = LibvirtWorker()
++        self.__vm_config = VirtManagerConfig()
+
+     def get_hal(self):
+         return self.__hal
+@@ -42,6 +43,9 @@ class ConfigScreen:
+     def get_libvirt(self):
+         return self.__libvirt
+
++    def get_virt_manager_config(self):
++        return self.__vm_config
++
+     def set_finished(self):
+         self.__finished = True
+
+@@ -179,3 +183,33 @@ class NetworkListConfigScreen(ConfigScreen):
+
+     def has_selectable_networks(self):
+         return self.__has_networks
++
++class HostListConfigScreen(ConfigScreen):
++    '''Provides a base class for working with lists of libvirt hosts.'''
++
++    def __init__(self, title):
++        ConfigScreen.__init__(self, title)
++
++    def get_connection_list_page(self, screen):
++        connections = self.get_virt_manager_config().get_connection_list()
++        result = None
++
++        if len(connections) > 0:
++            self.__has_connections = True
++            self.__connection_list = Listbox(0)
++            for connection in connections:
++                self.__connection_list.append(connection, connection)
++            result = self.__connection_list
++        else:
++            self.__has_connections = False
++            result = Label("There are no defined connections.")
++        grid = Grid(1, 1)
++        grid.setField(result, 0, 0)
++        return [Label("Host List"),
++                grid]
++
++    def get_selected_connection(self):
++        return self.__connection_list.current()
++
++    def has_selectable_connections(self):
++        return self.__has_connections
+diff --git a/nodeadmin/definenet.py b/nodeadmin/definenet.py
+index 4aa37d5..6dff18f 100644
+--- a/nodeadmin/definenet.py
++++ b/nodeadmin/definenet.py
+@@ -20,6 +20,7 @@ from snack import *
+ from IPy import IP
+ import traceback
+ import logging
++import re
+
+ from configscreen  import ConfigScreen
+ from networkconfig import NetworkConfig
+diff --git a/nodeadmin/hostconnect.py b/nodeadmin/hostconnect.py
+new file mode 100644
+index 0000000..a1be569
+--- /dev/null
++++ b/nodeadmin/hostconnect.py
+@@ -0,0 +1,29 @@
++# hostconnect.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 HostConnectConfigScreen(ConfigScreen):
++    def __init__(self):
++        ConfigScree
++
++def HostConnect():
++    screen = HostConnectConfigScreen()
++    screen.start()
+diff --git a/nodeadmin/hostmenu.py b/nodeadmin/hostmenu.py
+new file mode 100644
+index 0000000..4054d6b
+--- /dev/null
++++ b/nodeadmin/hostmenu.py
+@@ -0,0 +1,46 @@
++# hostmenu.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 menuscreen import MenuScreen
++from changehost import ChangeHost
++from addhost    import AddHost
++from removehost import RemoveHost
++
++SELECT_HOST = 1
++ADD_HOST    = 2
++REMOVE_HOST = 3
++
++class HostMenuScreen(MenuScreen):
++    def __init__(self):
++        MenuScreen.__init__(self, "Host Menu Screen")
++
++    def get_menu_items(self):
++        return (("Select A Host", SELECT_HOST),
++                ("Add A Host",    ADD_HOST),
++                ("Remove A Host", REMOVE_HOST))
++
++    def handle_selection(self, item):
++        if   item is SELECT_HOST: ChangeHost()
++        elif item is ADD_HOST:    AddHost()
++        elif item is REMOVE_HOST: RemoveHost()
++
++def HostMenu():
++    screen = HostMenuScreen()
++    screen.start()
+diff --git a/nodeadmin/libvirtworker.py b/nodeadmin/libvirtworker.py
+index ba07605..2998486 100644
+--- a/nodeadmin/libvirtworker.py
++++ b/nodeadmin/libvirtworker.py
+@@ -21,20 +21,69 @@ import libvirt
+ import os
+ import virtinst
+ import utils
++import logging
+
+ from domainconfig import DomainConfig
+
+ DEFAULT_POOL_TARGET_PATH="/var/lib/libvirt/images"
++DEFAULT_URL="qemu:///system"
++
++default_url = DEFAULT_URL
++
++def set_default_url(url):
++    logging.info("Changing DEFAULT_URL to %s" % url)
++    global default_url
++
++    default_url = url
++
++def get_default_url():
++    logging.info("Returning default URL of %s" % default_url)
++    return default_url
++
++class VirtManagerConfig:
++    def __init__(self, filename = "/etc/remote-libvirt.conf"):
++        self.__filename = filename
++
++    def get_connection_list(self):
++        result = []
++        if os.path.exists(self.__filename):
++            input = file(self.__filename, "r")
++            for entry in input: result.append(entry[0:-1])
++        return result
++
++    def add_connection(self, connection):
++        connections = self.get_connection_list()
++        if connections.count(connection) is 0:
++            connections.append(connection)
++            self._save_connections(connections)
++
++    def remove_connection(self, connection):
++        connections = self.get_connection_list()
++        if connections.count(connection) > 0:
++            connections.remove(connection)
++            self._save_connections(connections)
++
++    def _save_connections(self, connections):
++        output = file(self.__filename, "w")
++        for entry in connections:
++            print >> output, entry
++        output.close
+
+ class LibvirtWorker:
+     '''Provides utilities for interfacing with libvirt.'''
+-    def __init__(self, url = "qemu:///system"):
+-        self.__conn = libvirt.open(url)
++    def __init__(self, url = None):
++        if url is None: url = get_default_url()
++        logging.info("Connecting to libvirt: %s" % url)
++        self.open_connection(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 open_connection(self, url):
++        '''Lets the user change the url for the connection.'''
++        self.__conn = libvirt.open(url)
++
+     def list_domains(self, defined = True, started = True):
+         '''Lists all domains.'''
+         result = []
+diff --git a/nodeadmin/mainmenu.py b/nodeadmin/mainmenu.py
+index 73501fa..944ffeb 100755
+--- a/nodeadmin/mainmenu.py
++++ b/nodeadmin/mainmenu.py
+@@ -19,15 +19,17 @@
+ from snack import *
+ import traceback
+
+-from menuscreen     import MenuScreen
+-from nodemenu       import NodeMenu
+-from netmenu        import NetworkMenu
++from menuscreen import MenuScreen
++from nodemenu   import NodeMenu
++from netmenu    import NetworkMenu
++from hostmenu   import HostMenu
+
+ import utils
+ import logging
+
+ NODE_MENU    = 1
+ NETWORK_MENU = 2
++HOST_MENU    = 3
+ EXIT_CONSOLE = 99
+
+ class MainMenuScreen(MenuScreen):
+@@ -35,12 +37,14 @@ class MainMenuScreen(MenuScreen):
+         MenuScreen.__init__(self, "Main Menu")
+
+     def get_menu_items(self):
+-        return (("Node Administration", NODE_MENU),
+-                ("Network Administration", NETWORK_MENU))
++        return (("Node Administration",    NODE_MENU),
++                ("Network Administration", NETWORK_MENU),
++                ("Host Administration",    HOST_MENU))
+
+     def handle_selection(self, page):
+         if   page is NODE_MENU:    NodeMenu()
+         elif page is NETWORK_MENU: NetworkMenu()
++        elif page is HOST_MENU:    HostMenu()
+
+ def MainMenu():
+     screen = MainMenuScreen()
+diff --git a/nodeadmin/removehost.py b/nodeadmin/removehost.py
+new file mode 100644
+index 0000000..cf3c46c
+--- /dev/null
++++ b/nodeadmin/removehost.py
+@@ -0,0 +1,66 @@
++# removehost.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 *
++
++SELECT_HOST_PAGE    = 1
++CONFIRM_REMOVE_PAGE = 2
++
++class RemoveHostConfigScreen(HostListConfigScreen):
++    def __init__(self):
++        HostListConfigScreen.__init__(self, "Remove Host Connection")
++
++    def get_elements_for_page(self, screen, page):
++        if   page is SELECT_HOST_PAGE:    return self.get_connection_list_page(screen)
++        elif page is CONFIRM_REMOVE_PAGE: return self.get_confirm_remove_page(screen)
++
++    def page_has_next(self, page):
++        return page is SELECT_HOST_PAGE and self.has_selectable_connections()
++
++    def page_has_back(self, page):
++        return page is CONFIRM_REMOVE_PAGE
++
++    def page_has_finish(self, page):
++        return page is CONFIRM_REMOVE_PAGE
++
++    def validate_input(self, page, errors):
++        if   page is SELECT_HOST_PAGE: return True
++        elif page is CONFIRM_REMOVE_PAGE:
++            if self.__confirm.value():
++                return True
++            else:
++                errors.append("You must confirm removing the connection.")
++        return False
++
++    def process_input(self, page):
++        if page is CONFIRM_REMOVE_PAGE:
++            self.get_virt_manager_config().remove_connection(self.get_selected_connection())
++            self.set_finished()
++
++    def get_confirm_remove_page(self, screen):
++        self.__confirm = Checkbox("Remove this connection: %s" % self.get_selected_connection(), 0)
++        grid = Grid(1, 1)
++        grid.setField(self.__confirm, 0, 0)
++        return [Label("Remove Host Connection"),
++                grid]
++
++def RemoveHost():
++    screen = RemoveHostConfigScreen()
++    screen.start()
+diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in
+index 56e3aad..23ca2bf 100644
+--- a/ovirt-node.spec.in
++++ b/ovirt-node.spec.in
+@@ -198,6 +198,11 @@ cd -
+ %{__install} -p -m0755 nodeadmin/destroynetwork.py %{buildroot}%{python_sitelib}/nodeadmin
+ %{__install} -p -m0755 nodeadmin/undefinenetwork.py %{buildroot}%{python_sitelib}/nodeadmin
+
++%{__install} -p -m0755 nodeadmin/addhost.py %{buildroot}%{python_sitelib}/nodeadmin
++%{__install} -p -m0644 nodeadmin/changehost.py %{buildroot}%{python_sitelib}/nodeadmin
++%{__install} -p -m0755 nodeadmin/hostmenu.py %{buildroot}%{python_sitelib}/nodeadmin
++%{__install} -p -m0755 nodeadmin/removehost.py %{buildroot}%{python_sitelib}/nodeadmin
++
+ %{__install} -p -m0755 nodeadmin/createuser.py %{buildroot}%{python_sitelib}/nodeadmin
+
+ %{__install} -p -m0644 nodeadmin/halworker.py %{buildroot}%{python_sitelib}/nodeadmin
+--
+1.6.5.2
+
diff --git a/0002-Enables-users-to-migrate-virtual-machines-between-ho.patch b/0002-Enables-users-to-migrate-virtual-machines-between-ho.patch
new file mode 100644
index 0000000..a56ce52
--- /dev/null
+++ b/0002-Enables-users-to-migrate-virtual-machines-between-ho.patch
@@ -0,0 +1,253 @@
+From c565f28b25bd6b77b6a61ce92c2c70248f08130d Mon Sep 17 00:00:00 2001
+From: Darryl L. Pierce <dpierce at redhat.com>
+Date: Wed, 11 Nov 2009 10:51:23 -0500
+Subject: [PATCH 2/2] Enables users to migrate virtual machines between hosts.
+
+Users select a virtual machine on their current libvirt host. They then
+select a target machine, which must have been previously configured as a
+connection. They confirm the migration and then it runs.
+---
+ Makefile.am                |    1 +
+ nodeadmin/addhost.py       |   10 ++++-
+ nodeadmin/libvirtworker.py |    6 +++
+ nodeadmin/migratedomain.py |   81 ++++++++++++++++++++++++++++++++++++++++++++
+ nodeadmin/nodemenu.py      |   28 +++++++++------
+ nodeadmin/setup.py.in      |    1 +
+ ovirt-node.spec.in         |    2 +
+ 7 files changed, 115 insertions(+), 14 deletions(-)
+ create mode 100644 nodeadmin/migratedomain.py
+
+diff --git a/Makefile.am b/Makefile.am
+index 1671405..f557ea2 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -41,6 +41,7 @@ EXTRA_DIST =			\
+   nodeadmin/userworker.py       \
+   nodeadmin/mainmenu.py         \
+   nodeadmin/menuscreen.py       \
++  nodeadmin/migratedomain.py    \
+   nodeadmin/netmenu.py          \
+   nodeadmin/nodemenu.py         \
+   nodeadmin/removedomain.py     \
+diff --git a/nodeadmin/addhost.py b/nodeadmin/addhost.py
+index ef35b7d..ebcb4ea 100644
+--- a/nodeadmin/addhost.py
++++ b/nodeadmin/addhost.py
+@@ -59,7 +59,9 @@ class AddHostConfigScreen(ConfigScreen):
+
+     def validate_input(self, page, errors):
+         if page is DETAILS_PAGE:
+-            if len(self.__hostname.value()) > 0:
++            if self.__connection.getSelection() is CONNECTION_LOCAL:
++                return True
++            elif len(self.__hostname.value()) > 0:
+                 return True
+             else:
+                 errors.append("You must enter a remote hostname.")
+@@ -115,8 +117,12 @@ class AddHostConfigScreen(ConfigScreen):
+         grid.setField(Label(HYPERVISORS[self.__hypervisor.getSelection()]), 1, 0, anchorLeft = 1)
+         grid.setField(Label("Connection:"), 0, 1, anchorRight = 1)
+         grid.setField(Label(CONNECTIONS[self.__connection.getSelection()]), 1, 1, anchorLeft = 1)
++        if self.__connection.getSelection() is not CONNECTION_LOCAL:
++            hostname = self.__hostname.value()
++        else:
++            hostname = "local"
+         grid.setField(Label("Hostname:"), 0, 2, anchorRight = 1)
+-        grid.setField(Label(self.__hostname.value()), 1, 2, anchorLeft = 1)
++        grid.setField(Label(hostname), 1, 2, anchorLeft = 1)
+         grid.setField(Label("Autoconnect on Startup:"), 0, 3, anchorRight = 1)
+         label = "Yes"
+         if not self.__autoconnect.value(): label = "No"
+diff --git a/nodeadmin/libvirtworker.py b/nodeadmin/libvirtworker.py
+index 2998486..878b01c 100644
+--- a/nodeadmin/libvirtworker.py
++++ b/nodeadmin/libvirtworker.py
+@@ -122,6 +122,12 @@ class LibvirtWorker:
+         domain = self.get_domain(name)
+         domain.undefine()
+
++    def migrate_domain(self, name, target):
++        '''Migrates the specified domain to the target machine.'''
++        target_conn = libvirt.open(target)
++        virtmachine = self.get_domain(name)
++        virtmachine.migrate(target_conn, libvirt.VIR_MIGRATE_LIVE, None, None, 0)
++
+     def list_networks(self, defined = True, started = True):
+         '''Lists all networks.'''
+         result = []
+diff --git a/nodeadmin/migratedomain.py b/nodeadmin/migratedomain.py
+new file mode 100644
+index 0000000..8c8c268
+--- /dev/null
++++ b/nodeadmin/migratedomain.py
+@@ -0,0 +1,81 @@
++#!/usr/bin/env python
++#
++# migratedomain.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 *
++
++LIST_DOMAINS  = 1
++SELECT_TARGET = 2
++CONFIRM_PAGE  = 3
++
++class MigrateDomainConfigScreen(DomainListConfigScreen):
++    def __init__(self):
++        DomainListConfigScreen.__init__(self, "Migrate Virtual Machine")
++        self.__configured = False
++
++    def get_elements_for_page(self, screen, page):
++        if   page is LIST_DOMAINS:  return self.get_domain_list_page(screen)
++        elif page is SELECT_TARGET: return self.get_target_page(screen)
++        elif page is CONFIRM_PAGE:  return self.get_confirm_page(screen)
++
++    def page_has_next(self, page):
++        if   page is LIST_DOMAINS: return self.has_selectable_domains()
++        else: return page < CONFIRM_PAGE
++
++    def page_has_back(self, page):
++        return page < CONFIRM_PAGE
++
++    def page_has_finish(self, page):
++        return page is CONFIRM_PAGE
++
++    def validate_input(self, page, errors):
++        if   page is LIST_DOMAINS: return self.get_selected_domain() is not None
++        elif page is SELECT_TARGET:
++            if self.__targets.current() is None:
++                errors.append("Please enter a target hostname or IP address.")
++                return False
++        elif page is CONFIRM_PAGE:
++            if not self.__confirm.value():
++                errors.append("You must confirm migrating this virtual machine to proceed.")
++                return False
++        return True
++
++    def process_input(self, page):
++        if page is CONFIRM_PAGE:
++            self.get_libvirt().migrate_domain(self.get_selected_domain(), self.__targets.current())
++            self.set_finished()
++
++    def get_target_page(self, screen):
++        self.__targets = Listbox(0)
++        for connection in self.get_virt_manager_config().get_connection_list():
++            self.__targets.append(connection, connection)
++        return [Label("Select A Target Host"),
++                self.__targets]
++
++    def get_confirm_page(self, screen):
++        self.__confirm = Checkbox("Confirm migrating this virtual machine.")
++        grid = Grid(1, 1)
++        grid.setField(self.__confirm, 0, 0)
++        return [grid]
++
++def MigrateDomain():
++    screen = MigrateDomainConfigScreen()
++    screen.start()
+diff --git a/nodeadmin/nodemenu.py b/nodeadmin/nodemenu.py
+index 16be89c..f213e09 100755
+--- a/nodeadmin/nodemenu.py
++++ b/nodeadmin/nodemenu.py
+@@ -26,17 +26,19 @@ from startdomain    import StartDomain
+ from stopdomain     import StopDomain
+ from removedomain   import RemoveDomain
+ from listdomains    import ListDomains
++from migratedomain  import MigrateDomain
+ from createuser     import CreateUser
+
+ import utils
+ import logging
+
+-ADD_DOMAIN    = 1
+-START_DOMAIN = 2
+-STOP_DOMAIN   = 3
+-REMOVE_DOMAIN = 4
+-LIST_DOMAINS  = 5
+-CREATE_USER   = 6
++ADD_DOMAIN     = 1
++START_DOMAIN   = 2
++STOP_DOMAIN    = 3
++REMOVE_DOMAIN  = 4
++LIST_DOMAINS   = 5
++MIGRATE_DOMAIN = 6
++CREATE_USER    = 7
+
+ class NodeMenuScreen(MenuScreen):
+     def __init__(self):
+@@ -48,15 +50,17 @@ class NodeMenuScreen(MenuScreen):
+                 ("Stop A Virtual Machine",    STOP_DOMAIN),
+                 ("Remove A Virtual Machine",  REMOVE_DOMAIN),
+                 ("List All Virtual Machines", LIST_DOMAINS),
++                ("Migrate Virtual Machine",   MIGRATE_DOMAIN),
+                 ("Create A User",             CREATE_USER))
+
+     def handle_selection(self, item):
+-            if   item is ADD_DOMAIN:    AddDomain()
+-            elif item is START_DOMAIN:  StartDomain()
+-            elif item is STOP_DOMAIN:   StopDomain()
+-            elif item is REMOVE_DOMAIN: RemoveDomain()
+-            elif item is LIST_DOMAINS:  ListDomains()
+-            elif item is CREATE_USER:   CreateUser()
++            if   item is ADD_DOMAIN:     AddDomain()
++            elif item is START_DOMAIN:   StartDomain()
++            elif item is STOP_DOMAIN:    StopDomain()
++            elif item is REMOVE_DOMAIN:  RemoveDomain()
++            elif item is LIST_DOMAINS:   ListDomains()
++            elif item is MIGRATE_DOMAIN: MigrateDomain()
++            elif item is CREATE_USER:    CreateUser()
+
+ def NodeMenu():
+     screen = NodeMenuScreen()
+diff --git a/nodeadmin/setup.py.in b/nodeadmin/setup.py.in
+index 1e6e028..8b17487 100644
+--- a/nodeadmin/setup.py.in
++++ b/nodeadmin/setup.py.in
+@@ -29,6 +29,7 @@ setup(name = "nodeadmin",
+             'startvm     = nodeadmin.startdomain:StartDomain',
+             'stopvm      = nodeadmin.stopdomain:StopDomain',
+             'rmvm        = nodeadmin.removedomain:RemoveDomain',
++            'migratevm   = nodeadmin.migratedomain:MigradeDomain',
+             'createuser  = nodeadmin.createuser:CreateUser',
+             'listvms     = nodeadmin.listdomains:ListDomains',
+             'definenet   = nodeadmin.definenet:DefineNetwork',
+diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in
+index 23ca2bf..f46bd2b 100644
+--- a/ovirt-node.spec.in
++++ b/ovirt-node.spec.in
+@@ -187,6 +187,7 @@ cd -
+ %{__install} -p -m0755 nodeadmin/adddomain.py %{buildroot}%{python_sitelib}/nodeadmin
+ %{__install} -p -m0644 nodeadmin/domainconfig.py %{buildroot}%{python_sitelib}/nodeadmin
+ %{__install} -p -m0755 nodeadmin/listdomains.py %{buildroot}%{python_sitelib}/nodeadmin
++%{__install} -p -m0755 nodeadmin/migratedomain.py %{buildroot}%{python_sitelib}/nodeadmin
+ %{__install} -p -m0755 nodeadmin/removedomain.py %{buildroot}%{python_sitelib}/nodeadmin
+ %{__install} -p -m0755 nodeadmin/startdomain.py %{buildroot}%{python_sitelib}/nodeadmin
+ %{__install} -p -m0755 nodeadmin/stopdomain.py %{buildroot}%{python_sitelib}/nodeadmin
+@@ -380,6 +381,7 @@ fi
+ %{_bindir}/startvm
+ %{_bindir}/stopvm
+ %{_bindir}/rmvm
++%{_bindir}/migratevm
+ %{_bindir}/listvms
+ %{_bindir}/definenet
+ %{_bindir}/createnet
+--
+1.6.5.2
+
diff --git a/Makefile.am b/Makefile.am
index de3bd18..e673aa4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -28,8 +28,10 @@ EXTRA_DIST =			\
   images/syslinux-vesa-splash.jpg	\
   nodeadmin/__init__.py         \
   nodeadmin/adddomain.py        \
+  nodeadmin/addhost.py          \
   nodeadmin/addpool.py          \
   nodeadmin/addvolume.py        \
+  nodeadmin/changehost.py       \
   nodeadmin/configscreen.py     \
   nodeadmin/createmeter.py      \
   nodeadmin/createnetwork.py    \
@@ -38,6 +40,8 @@ EXTRA_DIST =			\
   nodeadmin/destroynetwork.py   \
   nodeadmin/domainconfig.py     \
   nodeadmin/halworker.py        \
+  nodeadmin/hostconnect.py      \
+  nodeadmin/hostmenu.py         \
   nodeadmin/libvirtworker.py    \
   nodeadmin/listdomains.py      \
   nodeadmin/listnetworks.py     \
@@ -50,6 +54,7 @@ EXTRA_DIST =			\
   nodeadmin/nodemenu.py         \
   nodeadmin/poolconfig.py       \
   nodeadmin/removedomain.py     \
+  nodeadmin/removehost.py       \
   nodeadmin/removepool.py       \
   nodeadmin/removevolume.py     \
   nodeadmin/startdomain.py      \
diff --git a/nodeadmin/addhost.py b/nodeadmin/addhost.py
new file mode 100644
index 0000000..ef35b7d
--- /dev/null
+++ b/nodeadmin/addhost.py
@@ -0,0 +1,129 @@
+# addhost.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 *
+
+DETAILS_PAGE = 1
+CONFIRM_PAGE = 2
+
+HYPERVISOR_XEN      = "xen"
+HYPERVISOR_KVM      = "kvm"
+
+HYPERVISORS = {HYPERVISOR_XEN : "Xen",
+               HYPERVISOR_KVM : "QEMU/KVM"}
+
+CONNECTION_LOCAL    = "local"
+CONNECTION_KERBEROS = "kerberos"
+CONNECTION_SSL      = "ssl"
+CONNECTION_SSH      = "ssh"
+
+CONNECTIONS = {CONNECTION_LOCAL    : "Local",
+               CONNECTION_KERBEROS : "Remote Password or Kerberos",
+               CONNECTION_SSL      : "Remote SSL/TLS with x509 certificate",
+               CONNECTION_SSH      : "Remote tunnel over SSH"}
+
+class AddHostConfigScreen(ConfigScreen):
+    def __init__(self):
+        ConfigScreen.__init__(self, "Add A Remote Host")
+        self.__configured = False
+
+    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 page_has_next(self, page):
+        return page < CONFIRM_PAGE
+
+    def page_has_back(self, page):
+        return page > DETAILS_PAGE
+
+    def page_has_finish(self, page):
+        return page is CONFIRM_PAGE
+
+    def validate_input(self, page, errors):
+        if page is DETAILS_PAGE:
+            if len(self.__hostname.value()) > 0:
+                return True
+            else:
+                errors.append("You must enter a remote hostname.")
+        elif page is CONFIRM_PAGE: return True
+        return False
+
+    def process_input(self, page):
+        if page is CONFIRM_PAGE:
+            hv       = self.__hypervisor.getSelection()
+            conn     = self.__connection.getSelection()
+            hostname = self.__hostname.value()
+
+            if   hv is HYPERVISOR_XEN:
+                if   conn is CONNECTION_LOCAL:    url = "xen:///"
+                elif conn is CONNECTION_KERBEROS: url = "xen+tcp:///" + hostname + "/"
+                elif conn is CONNECTION_SSL:      url = "xen+tls:///" + hostname + "/"
+                elif conn is CONNECTION_SSH:      url = "xen+ssh:///" + hostname + "/"
+            elif hv is HYPERVISOR_KVM:
+                if   conn is CONNECTION_LOCAL:    url = "qemu:///system"
+                elif conn is CONNECTION_KERBEROS: url = "qemu+tcp://" + hostname + "/system"
+                elif conn is CONNECTION_SSL:      url = "qemu+tls://" + hostname + "/system"
+                elif conn is CONNECTION_SSH:      url = "qemu+ssh://" + hostname + "/system"
+
+            self.get_virt_manager_config().add_connection(url)
+            self.set_finished()
+
+    def get_details_page(self, screen):
+        if not self.__configured:
+            self.__hypervisor = RadioBar(screen, ((HYPERVISORS[HYPERVISOR_XEN], HYPERVISOR_XEN, True),
+                                                  (HYPERVISORS[HYPERVISOR_KVM], HYPERVISOR_KVM, False)))
+            self.__connection = RadioBar(screen, ((CONNECTIONS[CONNECTION_LOCAL],    CONNECTION_LOCAL, True),
+                                                  (CONNECTIONS[CONNECTION_KERBEROS], CONNECTION_KERBEROS, False),
+                                                  (CONNECTIONS[CONNECTION_SSL],      CONNECTION_SSL, False),
+                                                  (CONNECTIONS[CONNECTION_SSH],      CONNECTION_SSH, False)))
+            self.__hostname = Entry(50, "")
+            self.__autoconnect = Checkbox("Autoconnect on Startup")
+            self.__configured = True
+        grid = Grid(2, 4)
+        grid.setField(Label("Hypervisor:"), 0, 0, anchorRight = 1, anchorTop = 1)
+        grid.setField(self.__hypervisor, 1, 0, anchorLeft = 1)
+        grid.setField(Label("Connection:"), 0, 1, anchorRight = 1, anchorTop = 1)
+        grid.setField(self.__connection, 1, 1, anchorLeft = 1)
+        grid.setField(Label("Hostname:"), 0, 2, anchorRight = 1)
+        grid.setField(self.__hostname, 1, 2, anchorLeft = 1)
+        grid.setField(Label(""), 0, 3, anchorRight = 1)
+        grid.setField(self.__autoconnect, 1, 3, anchorLeft = 1)
+        return [Label("Add Connection"),
+                grid]
+
+    def get_confirm_page(self, screen):
+        grid = Grid(2, 4)
+        grid.setField(Label("Hypervisor:"), 0, 0, anchorRight = 1)
+        grid.setField(Label(HYPERVISORS[self.__hypervisor.getSelection()]), 1, 0, anchorLeft = 1)
+        grid.setField(Label("Connection:"), 0, 1, anchorRight = 1)
+        grid.setField(Label(CONNECTIONS[self.__connection.getSelection()]), 1, 1, anchorLeft = 1)
+        grid.setField(Label("Hostname:"), 0, 2, anchorRight = 1)
+        grid.setField(Label(self.__hostname.value()), 1, 2, anchorLeft = 1)
+        grid.setField(Label("Autoconnect on Startup:"), 0, 3, anchorRight = 1)
+        label = "Yes"
+        if not self.__autoconnect.value(): label = "No"
+        grid.setField(Label(label), 1, 3, anchorLeft = 1)
+        return [Label("Confirm Connection"),
+                grid]
+
+def AddHost():
+    screen = AddHostConfigScreen()
+    screen.start()
diff --git a/nodeadmin/changehost.py b/nodeadmin/changehost.py
new file mode 100644
index 0000000..23e6854
--- /dev/null
+++ b/nodeadmin/changehost.py
@@ -0,0 +1,58 @@
+# changehost.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 logging
+import libvirtworker
+from configscreen import *
+
+CONNECTION_LIST_PAGE = 1
+CONNECTED_PAGE       = 2
+
+class ChangeHostConfigScreen(HostListConfigScreen):
+    def __init__(self):
+        HostListConfigScreen.__init__(self, "Change Host")
+
+    def get_elements_for_page(self, screen, page):
+        if   page is CONNECTION_LIST_PAGE: return self.get_connection_list_page(screen)
+        elif page is CONNECTED_PAGE:       return self.get_connected_page(screen)
+
+    def process_input(self, page):
+        if   page is CONNECTION_LIST_PAGE:
+            logging.info("Changing libvirt connection to %s" % self.get_selected_connection())
+            libvirtworker.set_default_url(self.get_selected_connection())
+            self.get_libvirt().open_connection(self.get_selected_connection())
+        elif page is CONNECTED_PAGE: self.set_finished()
+
+    def page_has_next(self, page):
+        if page is CONNECTION_LIST_PAGE: return self.has_selectable_connections()
+        return False
+
+    def page_has_back(self, page):
+        return page > CONNECTION_LIST_PAGE
+
+    def page_has_finish(self, page):
+        return page is CONNECTED_PAGE
+
+    def get_connected_page(self, screen):
+        return [Label("Connected to %s" % self.get_selected_connection())]
+
+def ChangeHost():
+    screen = ChangeHostConfigScreen()
+    screen.start()
diff --git a/nodeadmin/configscreen.py b/nodeadmin/configscreen.py
index 7654697..02ab5b4 100644
--- a/nodeadmin/configscreen.py
+++ b/nodeadmin/configscreen.py
@@ -18,7 +18,7 @@
 
 from snack import *
 from halworker import HALWorker
-from libvirtworker import LibvirtWorker
+from libvirtworker import *
 import traceback
 
 BACK_BUTTON   = "back"
@@ -35,6 +35,7 @@ class ConfigScreen:
         self.__finished = False
         self.__hal = HALWorker()
         self.__libvirt = LibvirtWorker()
+        self.__vm_config = VirtManagerConfig()
 
     def get_hal(self):
         return self.__hal
@@ -42,6 +43,9 @@ class ConfigScreen:
     def get_libvirt(self):
         return self.__libvirt
 
+    def get_virt_manager_config(self):
+        return self.__vm_config
+
     def set_finished(self):
         self.__finished = True
 
@@ -231,3 +235,33 @@ class StorageListConfigScreen(ConfigScreen):
 
     def has_selectable_volumes(self):
         return self.__has_volumes
+
+class HostListConfigScreen(ConfigScreen):
+    '''Provides a base class for working with lists of libvirt hosts.'''
+
+    def __init__(self, title):
+        ConfigScreen.__init__(self, title)
+
+    def get_connection_list_page(self, screen):
+        connections = self.get_virt_manager_config().get_connection_list()
+        result = None
+
+        if len(connections) > 0:
+            self.__has_connections = True
+            self.__connection_list = Listbox(0)
+            for connection in connections:
+                self.__connection_list.append(connection, connection)
+            result = self.__connection_list
+        else:
+            self.__has_connections = False
+            result = Label("There are no defined connections.")
+        grid = Grid(1, 1)
+        grid.setField(result, 0, 0)
+        return [Label("Host List"),
+                grid]
+
+    def get_selected_connection(self):
+        return self.__connection_list.current()
+
+    def has_selectable_connections(self):
+        return self.__has_connections
diff --git a/nodeadmin/definenet.py b/nodeadmin/definenet.py
index 4aa37d5..6dff18f 100644
--- a/nodeadmin/definenet.py
+++ b/nodeadmin/definenet.py
@@ -20,6 +20,7 @@ from snack import *
 from IPy import IP
 import traceback
 import logging
+import re
 
 from configscreen  import ConfigScreen
 from networkconfig import NetworkConfig
diff --git a/nodeadmin/hostconnect.py b/nodeadmin/hostconnect.py
new file mode 100644
index 0000000..a1be569
--- /dev/null
+++ b/nodeadmin/hostconnect.py
@@ -0,0 +1,29 @@
+# hostconnect.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 HostConnectConfigScreen(ConfigScreen):
+    def __init__(self):
+        ConfigScree
+
+def HostConnect():
+    screen = HostConnectConfigScreen()
+    screen.start()
diff --git a/nodeadmin/hostmenu.py b/nodeadmin/hostmenu.py
new file mode 100644
index 0000000..4054d6b
--- /dev/null
+++ b/nodeadmin/hostmenu.py
@@ -0,0 +1,46 @@
+# hostmenu.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 menuscreen import MenuScreen
+from changehost import ChangeHost
+from addhost    import AddHost
+from removehost import RemoveHost
+
+SELECT_HOST = 1
+ADD_HOST    = 2
+REMOVE_HOST = 3
+
+class HostMenuScreen(MenuScreen):
+    def __init__(self):
+        MenuScreen.__init__(self, "Host Menu Screen")
+
+    def get_menu_items(self):
+        return (("Select A Host", SELECT_HOST),
+                ("Add A Host",    ADD_HOST),
+                ("Remove A Host", REMOVE_HOST))
+
+    def handle_selection(self, item):
+        if   item is SELECT_HOST: ChangeHost()
+        elif item is ADD_HOST:    AddHost()
+        elif item is REMOVE_HOST: RemoveHost()
+
+def HostMenu():
+    screen = HostMenuScreen()
+    screen.start()
diff --git a/nodeadmin/libvirtworker.py b/nodeadmin/libvirtworker.py
index f31266c..15d1c58 100644
--- a/nodeadmin/libvirtworker.py
+++ b/nodeadmin/libvirtworker.py
@@ -21,15 +21,60 @@ import libvirt
 import os
 import virtinst
 import utils
+import logging
 
 from domainconfig import DomainConfig
 
 DEFAULT_POOL_TARGET_PATH="/var/lib/libvirt/images"
+DEFAULT_URL="qemu:///system"
+
+default_url = DEFAULT_URL
+
+def set_default_url(url):
+    logging.info("Changing DEFAULT_URL to %s" % url)
+    global default_url
+
+    default_url = url
+
+def get_default_url():
+    logging.info("Returning default URL of %s" % default_url)
+    return default_url
+
+class VirtManagerConfig:
+    def __init__(self, filename = "/etc/remote-libvirt.conf"):
+        self.__filename = filename
+
+    def get_connection_list(self):
+        result = []
+        if os.path.exists(self.__filename):
+            input = file(self.__filename, "r")
+            for entry in input: result.append(entry[0:-1])
+        return result
+
+    def add_connection(self, connection):
+        connections = self.get_connection_list()
+        if connections.count(connection) is 0:
+            connections.append(connection)
+            self._save_connections(connections)
+
+    def remove_connection(self, connection):
+        connections = self.get_connection_list()
+        if connections.count(connection) > 0:
+            connections.remove(connection)
+            self._save_connections(connections)
+
+    def _save_connections(self, connections):
+        output = file(self.__filename, "w")
+        for entry in connections:
+            print >> output, entry
+        output.close
 
 class LibvirtWorker:
     '''Provides utilities for interfacing with libvirt.'''
-    def __init__(self, url = "qemu:///system"):
-        self.__conn = libvirt.open(url)
+    def __init__(self, url = None):
+        if url is None: url = get_default_url()
+        logging.info("Connecting to libvirt: %s" % url)
+        self.open_connection(url)
         self.__capabilities = virtinst.CapabilitiesParser.parse(self.__conn.getCapabilities())
         self.__net = virtinst.VirtualNetworkInterface(conn = self.__conn)
         self.__net.setup(self.__conn)
@@ -39,6 +84,10 @@ class LibvirtWorker:
         '''Returns the underlying connection.'''
         return self.__conn
 
+    def open_connection(self, url):
+        '''Lets the user change the url for the connection.'''
+        self.__conn = libvirt.open(url)
+
     def list_domains(self, defined = True, started = True):
         '''Lists all domains.'''
         result = []
diff --git a/nodeadmin/mainmenu.py b/nodeadmin/mainmenu.py
index 52d9298..9c435fd 100755
--- a/nodeadmin/mainmenu.py
+++ b/nodeadmin/mainmenu.py
@@ -23,14 +23,16 @@ from menuscreen  import MenuScreen
 from nodemenu    import NodeMenu
 from netmenu     import NetworkMenu
 from storagemenu import StoragePoolMenu
+from hostmenu    import HostMenu
 
 import utils
 import logging
 
-NODE_MENU    = 1
-NETWORK_MENU = 2
-STORAGE_MENU = 3
-EXIT_CONSOLE = 4
+NODE_MENU    =  1
+NETWORK_MENU =  2
+STORAGE_MENU =  3
+HOST_MENU    =  4
+EXIT_CONSOLE = 99
 
 class MainMenuScreen(MenuScreen):
     def __init__(self):
@@ -39,12 +41,14 @@ class MainMenuScreen(MenuScreen):
     def get_menu_items(self):
         return (("Node Administration",         NODE_MENU),
                 ("Network Administration",      NETWORK_MENU),
-                ("Storage Pool Administration", STORAGE_MENU))
-
-    def handle_selection(self, item):
-        if   item is NODE_MENU:    NodeMenu()
-        elif item is NETWORK_MENU: NetworkMenu()
-        elif item is STORAGE_MENU: StoragePoolMenu()
+                ("Storage Pool Administration", STORAGE_MENU),
+                ("Host Administration",         HOST_MENU))
+
+    def handle_selection(self, page):
+        if   page is NODE_MENU:    NodeMenu()
+        elif page is NETWORK_MENU: NetworkMenu()
+        elif page is STORAGE_MENU: StoragePoolMenu()
+        elif page is HOST_MENU:    HostMenu()
 
 def MainMenu():
     screen = MainMenuScreen()
diff --git a/nodeadmin/removehost.py b/nodeadmin/removehost.py
new file mode 100644
index 0000000..cf3c46c
--- /dev/null
+++ b/nodeadmin/removehost.py
@@ -0,0 +1,66 @@
+# removehost.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 *
+
+SELECT_HOST_PAGE    = 1
+CONFIRM_REMOVE_PAGE = 2
+
+class RemoveHostConfigScreen(HostListConfigScreen):
+    def __init__(self):
+        HostListConfigScreen.__init__(self, "Remove Host Connection")
+
+    def get_elements_for_page(self, screen, page):
+        if   page is SELECT_HOST_PAGE:    return self.get_connection_list_page(screen)
+        elif page is CONFIRM_REMOVE_PAGE: return self.get_confirm_remove_page(screen)
+
+    def page_has_next(self, page):
+        return page is SELECT_HOST_PAGE and self.has_selectable_connections()
+
+    def page_has_back(self, page):
+        return page is CONFIRM_REMOVE_PAGE
+
+    def page_has_finish(self, page):
+        return page is CONFIRM_REMOVE_PAGE
+
+    def validate_input(self, page, errors):
+        if   page is SELECT_HOST_PAGE: return True
+        elif page is CONFIRM_REMOVE_PAGE:
+            if self.__confirm.value():
+                return True
+            else:
+                errors.append("You must confirm removing the connection.")
+        return False
+
+    def process_input(self, page):
+        if page is CONFIRM_REMOVE_PAGE:
+            self.get_virt_manager_config().remove_connection(self.get_selected_connection())
+            self.set_finished()
+
+    def get_confirm_remove_page(self, screen):
+        self.__confirm = Checkbox("Remove this connection: %s" % self.get_selected_connection(), 0)
+        grid = Grid(1, 1)
+        grid.setField(self.__confirm, 0, 0)
+        return [Label("Remove Host Connection"),
+                grid]
+
+def RemoveHost():
+    screen = RemoveHostConfigScreen()
+    screen.start()
diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in
index 325fecc..f056f3f 100644
--- a/ovirt-node.spec.in
+++ b/ovirt-node.spec.in
@@ -198,6 +198,11 @@ cd -
 %{__install} -p -m0755 nodeadmin/destroynetwork.py %{buildroot}%{python_sitelib}/nodeadmin
 %{__install} -p -m0755 nodeadmin/undefinenetwork.py %{buildroot}%{python_sitelib}/nodeadmin
 
+%{__install} -p -m0755 nodeadmin/addhost.py %{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0644 nodeadmin/changehost.py %{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0755 nodeadmin/hostmenu.py %{buildroot}%{python_sitelib}/nodeadmin
+%{__install} -p -m0755 nodeadmin/removehost.py %{buildroot}%{python_sitelib}/nodeadmin
+
 %{__install} -p -m0755 nodeadmin/createuser.py %{buildroot}%{python_sitelib}/nodeadmin
 
 %{__install} -p -m0644 nodeadmin/halworker.py %{buildroot}%{python_sitelib}/nodeadmin
-- 
1.6.5.2




More information about the ovirt-devel mailing list