details: Add OS name view/edit, + oslist rework

This is just a big nasty commit.

Turn the OS inspection page into an always available page that
shows the libosinfo name from the domain metadata XML. Use oslist.py
and have it absorb more of the common behavior needed by create.py
and details.py. Add UI tests for it all
This commit is contained in:
Cole Robinson 2018-09-29 16:04:05 -04:00
parent 75c64151b1
commit b19f94299b
14 changed files with 476 additions and 312 deletions

View File

@ -58,7 +58,7 @@
<name>test-many-devices</name>
<metadata>
<libosinfo:libosinfo xmlns:libosinfo="http://libosinfo.org/xmlns/libvirt/domain/1.0">
<libosinfo:os id="http://fedoraproject.org/fedora/27"/>
<libosinfo:os id="http://fedoraproject.org/fedora/unknown"/>
</libosinfo:libosinfo>
</metadata>
<currentMemory>204800</currentMemory>
@ -550,14 +550,18 @@ Foo bar baz &amp; yeah boii &lt; &gt; yeahfoo
</domain>
<domain type='test'>
<name>test-alternate-devs</name>
<uuid>4a64cc71-19c4-2fd0-2323-00aa941ea3c3</uuid>
<description>Test alternate devices that can't be crammed in
test-many-devices, like an alternate RNG.
test-many-devices, like an alternate RNG, EOL OS ID, title field
</description>
<title>test alternate devs (title)</title>
<metadata>
<libosinfo:libosinfo xmlns:libosinfo="http://libosinfo.org/xmlns/libvirt/domain/1.0">
<libosinfo:os id="http://microsoft.com/msdos/6.22"/>
</libosinfo:libosinfo>
</metadata>
<memory>8388608</memory>
<currentMemory>2097152</currentMemory>
<vcpu>2</vcpu>
@ -578,6 +582,7 @@ test-many-devices, like an alternate RNG.
</devices>
</domain>
<domain type='test' xmlns:test='http://libvirt.org/schemas/domain/test/1.0'>
<test:runstate>5</test:runstate>
<name>test-clone-simple</name>

View File

@ -83,10 +83,12 @@ class Details(uiutils.UITestCase):
self._testRename(origname, "test-new-name")
def testDetailsEdits(self):
def testDetailsEditDomain1(self):
"""
Test overview, memory, cpu pages
"""
win = self._open_details_window(vmname="test-many-devices")
appl = win.find("config-apply", "push button")
hwlist = win.find("hw-list")
# Overview description
tab = self._select_hw(win, "Overview", "overview-tab")
@ -141,6 +143,33 @@ class Details(uiutils.UITestCase):
self.assertTrue(tab.find_fuzzy("Maximum", "spin").text == "32")
def testDetailsEditDomain2(self):
"""
Test boot and OS pages
"""
win = self._open_details_window(vmname="test-many-devices")
appl = win.find("config-apply", "push button")
self._stop_vm(win)
# OS edits
tab = self._select_hw(win, "OS information", "os-tab")
entry = tab.find("oslist-entry")
self.assertEqual(entry.text, "Fedora")
entry.click()
self.pressKey("Down")
popover = win.find("oslist-popover")
popover.find("include-eol").click()
entry.text = "fedora12"
popover.find_fuzzy("fedora12").bring_on_screen().click()
uiutils.check_in_loop(lambda: not popover.visible)
self.assertEqual(entry.text, "Fedora 12")
appl.click()
uiutils.check_in_loop(lambda: not appl.sensitive)
self.assertEqual(entry.text, "Fedora 12")
# Boot tweaks
def check_bootorder(c):
# Click the bootlist checkbox, which is hard to find in the tree
import dogtail.rawinput
@ -149,7 +178,6 @@ class Details(uiutils.UITestCase):
button = 1
dogtail.rawinput.click(x, y, button)
# Boot tweaks
tab = self._select_hw(win, "Boot Options", "boot-tab")
self._stop_vm(win)
tab.find_fuzzy("Start virtual machine on host", "check box").click()
@ -178,6 +206,16 @@ class Details(uiutils.UITestCase):
uiutils.check_in_loop(lambda: not appl.sensitive)
def testDetailsEditDiskNet(self):
"""
Test disk and network devices
"""
win = self._open_details_window(vmname="test-many-devices")
appl = win.find("config-apply", "push button")
hwlist = win.find("hw-list")
self._stop_vm(win)
# Disk options
tab = self._select_hw(win, "IDE Disk 1", "disk-tab")
tab.find("Shareable:", "check box").click()
@ -254,6 +292,15 @@ class Details(uiutils.UITestCase):
uiutils.check_in_loop(lambda: not appl.sensitive)
def testDetailsEditDevices(self):
"""
Test all other devices
"""
win = self._open_details_window(vmname="test-many-devices")
appl = win.find("config-apply", "push button")
self._stop_vm(win)
# Graphics
tab = self._select_hw(win, "Display VNC", "graphics-tab")
tab.find("Type:", "combo box").click_combo_entry()

View File

@ -49,8 +49,28 @@ class NewVM(uiutils.UITestCase):
# Create default PXE VM
newvm.find_fuzzy("PXE", "radio").click()
newvm.find_fuzzy("Forward", "button").click()
newvm.find("oslist-entry").text = "generic"
newvm.find("oslist-popover").find_fuzzy("generic").click()
osentry = newvm.find("oslist-entry")
uiutils.check_in_loop(lambda: not osentry.text)
# Make sure we throw an error if no OS selected
newvm.find_fuzzy("Forward", "button").click()
alert = self.app.root.find("vmm dialog", "alert")
alert.find("You must select", "label")
alert.find("OK", "push button").click()
# Test activating the osentry to grab the popover selection
osentry.click()
osentry.typeText("generic")
newvm.find("oslist-popover")
osentry.click()
self.pressKey("Enter")
uiutils.check_in_loop(lambda: osentry.text == "Generic default")
# Verify back+forward still keeps Generic selected
newvm.find_fuzzy("Back", "button").click()
newvm.find_fuzzy("Forward", "button").click()
uiutils.check_in_loop(lambda: "Generic" in osentry.text)
newvm.find_fuzzy("Forward", "button").click()
newvm.find_fuzzy("Forward", "button").click()
newvm.find_fuzzy("Forward", "button").click()
@ -88,15 +108,15 @@ class NewVM(uiutils.UITestCase):
browser.find_fuzzy("iso-vol", "table cell").click()
browser.find_fuzzy("Choose Volume", "button").click()
label = newvm.find("oslist-entry")
osentry = newvm.find("oslist-entry")
uiutils.check_in_loop(lambda: browser.showing is False)
uiutils.check_in_loop(lambda: label.text == "None detected")
uiutils.check_in_loop(lambda: osentry.text == "None detected")
# Change distro to win8
newvm.find_fuzzy("Automatically detect", "check").click()
label.text = "windows 8"
osentry.text = "windows 8"
popover = newvm.find("oslist-popover")
popover.find_fuzzy("Include end of life").click()
popover.find_fuzzy("include-eol").click()
popover.find_fuzzy(r"\(win8\)").click()
newvm.find_fuzzy("Forward", "button").click()
@ -141,15 +161,40 @@ class NewVM(uiutils.UITestCase):
newvm.find_fuzzy("Network Install", "radio").click()
newvm.find_fuzzy("Forward", "button").click()
osentry = newvm.find("oslist-entry")
uiutils.check_in_loop(lambda: osentry.text.startswith("Waiting"))
newvm.find("URL", "text").text = (
"https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/14/Fedora/x86_64/os/")
url = "https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/10/Fedora/x86_64/os/"
oslabel = "Fedora 10"
newvm.find("URL", "text").text = url
version = newvm.find("oslist-entry")
uiutils.check_in_loop(
lambda: version.text == "Fedora 14",
timeout=10)
uiutils.check_in_loop(lambda: osentry.text == oslabel, timeout=10)
# Move forward, then back, ensure OS stays selected
newvm.find_fuzzy("Forward", "button").click()
newvm.find_fuzzy("Back", "button").click()
uiutils.check_in_loop(lambda: osentry.text == oslabel)
# Disable autodetect, make sure OS still selected
newvm.find_fuzzy("Automatically detect", "check").click()
uiutils.check_in_loop(lambda: osentry.text == oslabel)
newvm.find_fuzzy("Forward", "button").click()
newvm.find_fuzzy("Back", "button").click()
# Ensure the EOL field was selected
osentry.click()
self.pressKey("Down")
popover = newvm.find("oslist-popover")
uiutils.check_in_loop(lambda: popover.showing)
self.assertTrue(newvm.find("include-eol", "check").isChecked)
# Re-enable autodetect, check for detecting text
newvm.find_fuzzy("Automatically detect", "check").click()
uiutils.check_in_loop(lambda: not popover.showing)
uiutils.check_in_loop(lambda: "Detecting" in osentry.text)
uiutils.check_in_loop(lambda: osentry.text == oslabel, timeout=10)
# Progress the install
newvm.find_fuzzy("Forward", "button").click()
newvm.find_fuzzy("Forward", "button").click()
newvm.find_fuzzy("Forward", "button").click()
@ -159,7 +204,7 @@ class NewVM(uiutils.UITestCase):
"Creating Virtual Machine", "frame")
uiutils.check_in_loop(lambda: not progress.showing, timeout=120)
self.app.root.find_fuzzy("fedora14 on", "frame")
self.app.root.find_fuzzy("fedora10 on", "frame")
self.assertFalse(newvm.showing)

View File

@ -269,6 +269,7 @@ class VMMDogtailNode(dogtail.tree.Node):
raise RuntimeError("Could not bring widget on screen")
return self
#########################
# Widget search helpers #
#########################

View File

@ -116,7 +116,10 @@ class XMLParseTest(unittest.TestCase):
check("on_lockfailure", "poweroff", "restart")
check = self._make_checker(guest._metadata.libosinfo) # pylint: disable=protected-access
check("os_id", "http://fedoraproject.org/fedora/17", "frib")
check("os_id", "http://fedoraproject.org/fedora/17")
guest.set_os_name("generic")
check("os_id", None, "frib")
self.assertEqual(guest.osinfo.name, "generic")
check = self._make_checker(guest.clock)
check("offset", "utc", "localtime")

View File

@ -2024,18 +2024,11 @@ connections is not yet supported.&lt;/small&gt;</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkSearchEntry" id="install-os-name">
<object class="GtkAlignment" id="install-os-align">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">edit-find-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="primary_icon_sensitive">False</property>
<signal name="search-changed" handler="on_install_os_name_search_changed" swapped="no"/>
<signal name="stop-search" handler="on_install_os_name_stop_search" swapped="no"/>
<child internal-child="accessible">
<object class="AtkObject" id="install-os-name-atkobject">
<property name="AtkObject::accessible-name" translatable="yes">oslist-entry</property>
</object>
<property name="can_focus">False</property>
<child>
<placeholder/>
</child>
</object>
<packing>
@ -2088,10 +2081,11 @@ connections is not yet supported.&lt;/small&gt;</property>
</object>
</child>
<child type="label">
<object class="GtkLabel">
<object class="GtkLabel" id="os-label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Operating system distribution:</property>
<property name="label" translatable="yes">C_hoose the operating system you are installing:</property>
<property name="use_underline">True</property>
</object>
</child>
</object>

View File

@ -1389,89 +1389,30 @@
<property name="row_spacing">6</property>
<property name="column_spacing">6</property>
<child>
<object class="GtkLabel" id="label72">
<object class="GtkAlignment" id="details-os-align">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Product name:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="inspection-hostname">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label">foo</property>
<property name="selectable">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label71">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Hostname:</property>
<property name="hexpand">True</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Operating system:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="inspection-product-name">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label">foo</property>
<property name="selectable">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="inspection-type">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">foo</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="label70">
<object class="GtkLabel" id="details-os-label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;b&gt;Operating System&lt;/b&gt;</property>
<property name="label" translatable="yes">&lt;b&gt;Operating Sys_tem&lt;/b&gt;</property>
<property name="use_markup">True</property>
<property name="use_underline">True</property>
</object>
</child>
</object>
@ -1531,7 +1472,7 @@
<property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkGrid" id="details-overview-error-box">
<object class="GtkGrid" id="details-inspection-error-box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
@ -1597,6 +1538,11 @@
<property name="position">2</property>
</packing>
</child>
<child internal-child="accessible">
<object class="AtkObject" id="box14-atkobject">
<property name="AtkObject::accessible-name" translatable="yes">os-tab</property>
</object>
</child>
</object>
<packing>
<property name="position">1</property>
@ -1606,7 +1552,7 @@
<object class="GtkLabel" id="label14">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">inspect</property>
<property name="label">osinfo</property>
</object>
<packing>
<property name="position">1</property>

View File

@ -2,10 +2,27 @@
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.14"/>
<object class="GtkSearchEntry" id="os-name">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">edit-find-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="primary_icon_sensitive">False</property>
<signal name="activate" handler="on_os_name_activate" swapped="no"/>
<signal name="key-press-event" handler="on_os_name_key_press_event" swapped="no"/>
<signal name="search-changed" handler="on_os_name_search_changed" swapped="no"/>
<signal name="stop-search" handler="on_os_name_stop_search" swapped="no"/>
<child internal-child="accessible">
<object class="AtkObject" id="os-name-atkobject">
<property name="AtkObject::accessible-name" translatable="yes">oslist-entry</property>
</object>
</child>
</object>
<object class="GtkPopover" id="vmm-oslist">
<property name="width_request">400</property>
<property name="height_request">300</property>
<property name="can_focus">False</property>
<property name="modal">False</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
@ -25,6 +42,7 @@
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<property name="stock">gtk-info</property>
<property name="icon_size">3</property>
</object>
@ -35,10 +53,10 @@
</packing>
</child>
<child>
<object class="GtkLabel">
<object class="GtkLabel" id="eol-warn">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Can't find the operating system you are looking for ?
<property name="label">Can't find the operating system you are looking for?
Try selecting the next most recent version displayed,
or use the "Generic" entry.</property>
</object>
@ -56,7 +74,7 @@ or use the "Generic" entry.</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<object class="GtkScrolledWindow" id="os-scroll">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="vscrollbar_policy">always</property>
@ -70,9 +88,15 @@ or use the "Generic" entry.</property>
<property name="hover_selection">True</property>
<property name="enable_grid_lines">horizontal</property>
<property name="activate_on_single_click">True</property>
<signal name="row-activated" handler="on_os_list_row_activated" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
<child internal-child="accessible">
<object class="AtkObject" id="os-list-atkobject">
<property name="AtkObject::accessible-name" translatable="yes">os-list</property>
</object>
</child>
</object>
</child>
</object>
@ -91,6 +115,11 @@ or use the "Generic" entry.</property>
<property name="halign">start</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_include_eol_toggled" swapped="no"/>
<child internal-child="accessible">
<object class="AtkObject" id="include-eol-atkobject">
<property name="AtkObject::accessible-name" translatable="yes">include-eol</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>

View File

@ -28,8 +28,8 @@ from .domain import vmmDomainVirtinst
from .engine import vmmEngine
from .mediacombo import vmmMediaCombo
from .netlist import vmmNetworkList
from .storagebrowse import vmmStorageBrowser
from .oslist import vmmOSList
from .storagebrowse import vmmStorageBrowser
# Number of seconds to wait for media detection
DETECT_TIMEOUT = 20
@ -125,12 +125,10 @@ class vmmCreate(vmmGObjectUI):
self._guest = None
self._failed_guest = None
self._os = None
# Distro detection state variables
self._detect_os_in_progress = False
self._os_already_detected_for_media = False
self._show_all_os_was_selected = False
self._customize_window = None
@ -172,7 +170,7 @@ class vmmCreate(vmmGObjectUI):
"on_install_oscontainer_browse_clicked": self._browse_oscontainer,
"on_install_container_source_toggle": self._container_source_toggle,
"on_install_detect_os_toggled": self._toggle_detect_os,
"on_install_detect_os_toggled": self._detect_os_toggled_cb,
"on_kernel_browse_clicked": self._browse_kernel,
"on_initrd_browse_clicked": self._browse_initrd,
@ -181,9 +179,6 @@ class vmmCreate(vmmGObjectUI):
"on_enable_storage_toggled": self._toggle_enable_storage,
"on_create_vm_name_changed": self._name_changed,
"on_install_os_name_search_changed": self._os_name_search_changed,
"on_install_os_name_stop_search": self._os_name_stop_search,
})
self.bind_escape_key_close()
@ -326,16 +321,10 @@ class vmmCreate(vmmGObjectUI):
lst.set_model(model)
uiutil.init_combo_text_column(lst, 0)
# OS distro list
self._os_list = vmmOSList()
self._os_list.connect("os-selected", self._os_name_selected)
def _os_name_selected(self, ignore, osobj):
name = self.widget("install-os-name")
self._os = osobj
self._os_list.hide()
if self._os is not None:
name.set_text(self._os.label)
self.widget("install-os-align").add(self._os_list.search_entry)
self.widget("os-label").set_mnemonic_widget(self._os_list.search_entry)
def _reset_state(self, urihint=None):
"""
@ -344,7 +333,6 @@ class vmmCreate(vmmGObjectUI):
"""
self._failed_guest = None
self._guest = None
self._show_all_os_was_selected = False
self.reset_finish_cursor()
self.widget("create-pages").set_current_page(PAGE_NAME)
@ -365,7 +353,8 @@ class vmmCreate(vmmGObjectUI):
# Everything from this point forward should be connection independent
# Distro/Variant
self._toggle_detect_os(self.widget("install-detect-os"))
self._os_list.reset_state()
self._os_already_detected_for_media = False
def _populate_media_model(media_model, urls):
media_model.clear()
@ -937,32 +926,6 @@ class vmmCreate(vmmGObjectUI):
return activeconn
#############################################
# Helpers for populating OS type/variant UI #
#############################################
def _set_distro_selection(self, variant):
"""
Update the UI with the distro that was detected from the detection
thread.
"""
if not self._is_os_detect_active():
# If the user changed the OS detect checkbox in the meantime,
# don't update the UI
return
name = self.widget("install-os-name")
if variant:
self._os = virtinst.OSDB.lookup_os(variant)
else:
self._os = None
if self._os is None:
name.set_text(_("None detected"))
else:
name.set_text(self._os.label)
###############################
# Misc UI populating routines #
###############################
@ -1012,7 +975,7 @@ class vmmCreate(vmmGObjectUI):
elif instmethod == INSTALL_PAGE_VZ_TEMPLATE:
install = _("Virtuozzo container")
self.widget("summary-os").set_text(self._os and self._os.label or _("Unknown"))
self.widget("summary-os").set_text(self._guest.osinfo.label)
self.widget("summary-install").set_text(install)
self.widget("summary-mem").set_text(mem)
self.widget("summary-cpu").set_text(cpu)
@ -1251,34 +1214,17 @@ class vmmCreate(vmmGObjectUI):
def _cdrom_changed(self, src):
self._detectable_media_widget_changed(src)
def _toggle_detect_os(self, src):
def _detect_os_toggled_cb(self, src):
if not src.is_visible():
return
# We are only here if the user explicitly changed detection UI
dodetect = src.get_active()
self.widget("install-os-name").set_sensitive(not dodetect)
self.widget("install-os-name").set_text("")
self._os = None
self._change_os_detect(not dodetect)
if dodetect:
self._os_already_detected_for_media = False
self._start_detect_os_if_needed()
def _os_name_search_changed(self, src):
searchname = src.get_text().strip()
if self._os is None:
if src.get_sensitive() and searchname != "":
self._os_list.filter_name(searchname)
self._os_list.show(src)
else:
self._os_list.hide()
else:
if self._os.label != searchname:
self._os = None
self._os_list.hide()
def _os_name_stop_search(self, src):
src.set_text("")
self._os_list.hide()
def _local_media_toggled(self, src):
usecdrom = src.get_active()
self.widget("install-cdrom-align").set_sensitive(usecdrom)
@ -1442,43 +1388,34 @@ class vmmCreate(vmmGObjectUI):
self.widget("header-pagenum").set_markup(page_lbl)
def _change_os_detect(self, sensitive):
self._os_list.set_sensitive(sensitive)
if not sensitive and not self._os_list.get_selected_os():
self._os_list.search_entry.set_text(
_("Waiting for install media / source"))
def _set_install_page(self):
instnotebook = self.widget("install-method-pages")
detectbox = self.widget("install-detect-os-box")
detect = self.widget("install-detect-os")
osbox = self.widget("install-os-distro-box")
name = self.widget("install-os-name")
instpage = self._get_config_install_page()
# Setting OS value for a container guest doesn't really matter
# at the moment
iscontainer = instpage in [INSTALL_PAGE_CONTAINER_APP,
INSTALL_PAGE_CONTAINER_OS]
osbox.set_visible(iscontainer)
# Setting OS value for container doesn't matter presently
self.widget("install-os-distro-box").set_visible(
not self._is_container_install())
enabledetect = (instpage == INSTALL_PAGE_ISO and
self.conn and
not self.conn.is_remote() or
self._get_config_install_page() == INSTALL_PAGE_URL)
enabledetect = False
if instpage == INSTALL_PAGE_URL:
enabledetect = True
elif instpage == INSTALL_PAGE_ISO and not self.conn.is_remote():
enabledetect = True
detectbox.set_visible(enabledetect)
autodetect = detectbox.get_visible() and detect.get_active()
name.set_sensitive(not autodetect)
if enabledetect:
self._os = None
else:
if self._os is None:
name.set_text("")
self.widget("install-detect-os-box").set_visible(enabledetect)
dodetect = (enabledetect and
self.widget("install-detect-os").get_active())
self._change_os_detect(not dodetect)
if instpage == INSTALL_PAGE_PXE:
# Hide the install notebook for pxe, since there isn't anything
# to ask for
instnotebook.hide()
else:
instnotebook.show()
instnotebook.set_current_page(instpage)
# PXE installs have nothing to ask for
self.widget("install-method-pages").set_visible(
instpage != INSTALL_PAGE_PXE)
self.widget("install-method-pages").set_current_page(instpage)
def _back_clicked(self, src_ignore):
notebook = self.widget("create-pages")
@ -1524,15 +1461,7 @@ class vmmCreate(vmmGObjectUI):
def _page_changed(self, ignore1, ignore2, pagenum):
if pagenum == PAGE_INSTALL:
self.widget("install-os-distro-box").set_visible(
not self._is_container_install())
# Kick off distro detection when we switch to the install
# page, to detect the default selected media
self._start_detect_os_if_needed(check_install_page=False)
elif pagenum == PAGE_FINISH:
if pagenum == PAGE_FINISH:
try:
self._populate_summary()
except Exception as e:
@ -1643,9 +1572,11 @@ class vmmCreate(vmmGObjectUI):
init = None
fs = None
template = None
osobj = self._os_list.get_selected_os()
if not self._is_container_install() and self._os is None:
return self.err.val_err(_("Please specify a valid OS variant."))
if not self._is_container_install() and not osobj:
return self.err.val_err(_("You must select an OS.")
+ "\n\n" + self._os_list.eol_text)
if instmethod == INSTALL_PAGE_ISO:
instclass = virtinst.DistroInstaller
@ -1743,7 +1674,7 @@ class vmmCreate(vmmGObjectUI):
try:
# Overwrite the guest
installer = instclass(self.conn.get_backend())
variant = self._os and self._os.name or None
variant = osobj and osobj.name or None
self._guest = self._build_guest(variant)
if not self._guest:
return False
@ -1801,7 +1732,7 @@ class vmmCreate(vmmGObjectUI):
self._guest.os.kernel_args = kargs
try:
name = self._generate_default_name(self._os)
name = self._generate_default_name(self._guest.osinfo)
self.widget("create-vm-name").set_text(name)
self._guest.validate_name(self._guest.conn, name)
self._guest.name = name
@ -1825,11 +1756,9 @@ class vmmCreate(vmmGObjectUI):
self._addstorage.check_path_search(
self, self.conn, path)
res = None
if self._os is not None:
res = self._os.get_recommended_resources(self._guest)
logging.debug("Recommended resources for os=%s: %s",
self._os.label, res)
res = self._guest.osinfo.get_recommended_resources(self._guest)
logging.debug("Recommended resources for os=%s: %s",
self._guest.osinfo.name, res)
# Change the default values suggested to the user.
ram_size = DEFAULT_MEM
@ -1987,8 +1916,7 @@ class vmmCreate(vmmGObjectUI):
# Distro detection handling #
#############################
def _start_detect_os_if_needed(self,
forward_after_finish=False, check_install_page=True):
def _start_detect_os_if_needed(self, forward_after_finish=False):
"""
Will kick off the OS detection thread if all conditions are met,
like we actually have media to detect, detection isn't already
@ -2002,12 +1930,9 @@ class vmmCreate(vmmGObjectUI):
if self._detect_os_in_progress:
return
if check_install_page and not is_install_page:
if not is_install_page:
return
if not media:
name = self.widget("install-os-name")
if not name.get_sensitive():
name.set_text(_("Waiting for install media / source"))
return
if not self._is_os_detect_active():
return
@ -2052,6 +1977,7 @@ class vmmCreate(vmmGObjectUI):
detectThread.setDaemon(True)
detectThread.start()
self._os_list.search_entry.set_text(_("Detecting..."))
spin = self.widget("install-detect-os-spinner")
spin.start()
@ -2103,7 +2029,17 @@ class vmmCreate(vmmGObjectUI):
self.widget("create-forward").set_sensitive(True)
self._os_already_detected_for_media = True
self._detect_os_in_progress = False
self._set_distro_selection(distro)
if not self._is_os_detect_active():
# If the user changed the OS detect checkbox in the meantime,
# don't update the UI
return
if distro:
self._os_list.select_os(virtinst.OSDB.lookup_os(distro))
else:
self._os_list.reset_state()
self._os_list.search_entry.set_text(_("None detected"))
if forward_after_finish:
self.idle_add(self._forward_clicked, ())

View File

@ -17,16 +17,17 @@ from virtinst import util
from . import vmmenu
from . import uiutil
from .baseclass import vmmGObjectUI
from .addhardware import vmmAddHardware
from .baseclass import vmmGObjectUI
from .choosecd import vmmChooseCD
from .engine import vmmEngine
from .fsdetails import vmmFSDetails
from .gfxdetails import vmmGraphicsDetails
from .graphwidgets import Sparkline
from .netlist import vmmNetworkList
from .oslist import vmmOSList
from .snapshots import vmmSnapshotPage
from .storagebrowse import vmmStorageBrowser
from .graphwidgets import Sparkline
# Parameters that can be edited in the details window
@ -37,6 +38,8 @@ from .graphwidgets import Sparkline
EDIT_DESC,
EDIT_IDMAP,
EDIT_OS_NAME,
EDIT_VCPUS,
EDIT_MAXVCPUS,
EDIT_CPU,
@ -95,7 +98,7 @@ from .graphwidgets import Sparkline
EDIT_FS,
EDIT_HOSTDEV_ROMBAR) = range(1, 53)
EDIT_HOSTDEV_ROMBAR) = range(1, 54)
# Columns in hw list model
@ -107,7 +110,7 @@ from .graphwidgets import Sparkline
# Types for the hw list model: numbers specify what order they will be listed
(HW_LIST_TYPE_GENERAL,
HW_LIST_TYPE_INSPECTION,
HW_LIST_TYPE_OS,
HW_LIST_TYPE_STATS,
HW_LIST_TYPE_CPU,
HW_LIST_TYPE_MEMORY,
@ -452,6 +455,7 @@ class vmmDetails(vmmGObjectUI):
self._addhwmenuitems = None
self._shutdownmenu = None
self._vmmenu = None
self._os_list = None
self.init_menus()
self.init_details()
@ -607,7 +611,8 @@ class vmmDetails(vmmGObjectUI):
# Deliberately keep all this after signal connection
self.vm.connect("state-changed", self.refresh_vm_state)
self.vm.connect("resources-sampled", self.refresh_resources)
self.vm.connect("inspection-changed", lambda *x: self.refresh_inspection_page())
self.vm.connect("inspection-changed",
lambda *x: self.refresh_os_page())
self.populate_hw_list()
@ -927,7 +932,13 @@ class vmmDetails(vmmGObjectUI):
uiutil.set_grid_row_visible(
self.widget("overview-chipset-title"), show_chipset)
# Inspection page
# OS/Inspection page
self._os_list = vmmOSList()
self.widget("details-os-align").add(self._os_list.search_entry)
self.widget("details-os-label").set_mnemonic_widget(
self._os_list.search_entry)
self._os_list.connect("os-selected", self._os_list_name_selected_cb)
apps_list = self.widget("inspection-apps")
apps_model = Gtk.ListStore(str, str, str)
apps_list.set_model(apps_model)
@ -1196,8 +1207,8 @@ class vmmDetails(vmmGObjectUI):
if pagetype == HW_LIST_TYPE_GENERAL:
self.refresh_overview_page()
elif pagetype == HW_LIST_TYPE_INSPECTION:
self.refresh_inspection_page()
elif pagetype == HW_LIST_TYPE_OS:
self.refresh_os_page()
elif pagetype == HW_LIST_TYPE_STATS:
self.refresh_stats_page()
elif pagetype == HW_LIST_TYPE_CPU:
@ -1577,6 +1588,9 @@ class vmmDetails(vmmGObjectUI):
if inspection:
inspection.vm_refresh(self.vm)
def _os_list_name_selected_cb(self, src, osobj):
self.enable_apply(EDIT_OS_NAME)
##############################
# Details/Hardware listeners #
@ -1891,6 +1905,8 @@ class vmmDetails(vmmGObjectUI):
try:
if pagetype is HW_LIST_TYPE_GENERAL:
ret = self.config_overview_apply()
elif pagetype is HW_LIST_TYPE_OS:
ret = self.config_os_apply()
elif pagetype is HW_LIST_TYPE_CPU:
ret = self.config_vcpus_apply()
elif pagetype is HW_LIST_TYPE_MEMORY:
@ -1994,6 +2010,16 @@ class vmmDetails(vmmGObjectUI):
kwargs, self.vm, self.err,
hotplug_args=hotplug_args)
def config_os_apply(self):
kwargs = {}
if self.edited(EDIT_OS_NAME):
osobj = self._os_list.get_selected_os()
kwargs["os_name"] = osobj and osobj.name or "generic"
return vmmAddHardware.change_config_helper(self.vm.define_os,
kwargs, self.vm, self.err)
def config_vcpus_apply(self):
kwargs = {}
hotplug_args = {}
@ -2448,7 +2474,9 @@ class vmmDetails(vmmGObjectUI):
IdMap_proper = getattr(IdMap, name.replace("-", "_"))
self.widget(name).set_value(int(IdMap_proper))
def refresh_inspection_page(self):
def refresh_os_page(self):
self._os_list.select_os(self.vm.xmlobj.osinfo)
inspection_supported = self.config.inspection_supported()
uiutil.set_grid_row_visible(self.widget("details-overview-error"),
bool(self.vm.inspection.errorstr))
@ -2457,25 +2485,12 @@ class vmmDetails(vmmGObjectUI):
self.vm.inspection.errorstr)
inspection_supported = False
self.widget("details-inspection-os").set_visible(inspection_supported)
self.widget("details-inspection-apps").set_visible(inspection_supported)
self.widget("details-inspection-refresh").set_visible(
inspection_supported)
if not inspection_supported:
return
# Operating System (ie. inspection data)
hostname = self.vm.inspection.hostname
if not hostname:
hostname = _("unknown")
self.widget("inspection-hostname").set_text(hostname)
os_type = self.vm.inspection.os_type
if not os_type:
os_type = "unknown"
self.widget("inspection-type").set_text(_label_for_os_type(os_type))
product_name = self.vm.inspection.product_name
if not product_name:
product_name = _("unknown")
self.widget("inspection-product-name").set_text(product_name)
# Applications (also inspection data)
apps = self.vm.inspection.applications or []
apps_list = self.widget("inspection-apps")
@ -3073,10 +3088,8 @@ class vmmDetails(vmmGObjectUI):
page_id, title])
add_hw_list_option(_("Overview"), HW_LIST_TYPE_GENERAL, "computer")
add_hw_list_option(_("OS information"), HW_LIST_TYPE_OS, "computer")
if not self.is_customize_dialog:
if self.config.inspection_supported():
add_hw_list_option(_("OS information"),
HW_LIST_TYPE_INSPECTION, "computer")
add_hw_list_option(_("Performance"), HW_LIST_TYPE_STATS,
"utilities-system-monitor")
add_hw_list_option(_("CPUs"), HW_LIST_TYPE_CPU, "device_cpu")

View File

@ -612,6 +612,14 @@ class vmmDomain(vmmLibvirtObject):
self._redefine_xmlobj(guest)
def define_os(self, os_name=_SENTINEL):
guest = self._make_xmlobj_to_define()
if os_name != _SENTINEL:
guest.set_os_name(os_name)
self._redefine_xmlobj(guest)
def define_boot(self, boot_order=_SENTINEL, boot_menu=_SENTINEL,
kernel=_SENTINEL, initrd=_SENTINEL, dtb=_SENTINEL,
kernel_args=_SENTINEL, init=_SENTINEL, initargs=_SENTINEL):

View File

@ -3,7 +3,7 @@
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
from gi.repository import Gtk
from gi.repository import Gdk, Gtk
import virtinst
@ -21,16 +21,32 @@ class vmmOSList(vmmGObjectUI):
self._filter_name = None
self._filter_eol = True
self._selected_os = None
self.search_entry = self.widget("os-name")
self.search_entry.set_placeholder_text(_("Type to start searching..."))
self.eol_text = self.widget("eol-warn").get_text()
self.builder.connect_signals({
"on_include_eol_toggled": self._eol_toggled,
"on_include_eol_toggled": self._eol_toggled_cb,
"on_os_name_activate": self._entry_activate_cb,
"on_os_name_key_press_event": self._key_press_cb,
"on_os_name_search_changed": self._search_changed_cb,
"on_os_name_stop_search": self._stop_search_cb,
"on_os_list_row_activated": self._os_selected_cb,
})
self._init_state()
def _init_state(self):
def _cleanup(self):
pass
self.topwin.set_modal(False)
###########
# UI init #
###########
def _init_state(self):
os_list = self.widget("os-list")
# (os object, label)
@ -41,10 +57,10 @@ class vmmOSList(vmmGObjectUI):
for os in all_os:
os_list_model.append([os, "%s (%s)" % (os.label, os.name)])
self._os_list_model = Gtk.TreeModelFilter(child_model=os_list_model)
self._os_list_model.set_visible_func(self._filter_os)
model_filter = Gtk.TreeModelFilter(child_model=os_list_model)
model_filter.set_visible_func(self._filter_os_cb)
os_list.set_model(self._os_list_model)
os_list.set_model(model_filter)
nameCol = Gtk.TreeViewColumn(_("Name"))
nameCol.set_spacing(6)
@ -54,50 +70,159 @@ class vmmOSList(vmmGObjectUI):
nameCol.add_attribute(text, 'text', 1)
os_list.append_column(nameCol)
os_list.connect("row_activated", self._os_selected_cb)
def _eol_toggled(self, src):
###################
# Private helpers #
###################
def _set_default_selection(self):
os_list = self.widget("os-list")
sel = os_list.get_selection()
if not self.topwin.get_visible():
return
if not len(os_list.get_model()):
return
sel.select_iter(os_list.get_model()[0].iter)
def _refilter(self):
os_list = self.widget("os-list")
sel = os_list.get_selection()
sel.unselect_all()
os_list.get_model().refilter()
self._set_default_selection()
def _filter_by_name(self, partial_name):
self._filter_name = partial_name.lower()
self._refilter()
def _clear_filter(self):
self._filter_by_name("")
self.widget("os-scroll").get_vadjustment().set_value(0)
def _sync_os_selection(self):
model, titer = self.widget("os-list").get_selection().get_selected()
self._selected_os = None
if titer:
self._selected_os = model[titer][0]
self.search_entry.set_text(self._selected_os.label)
self.emit("os-selected", self._selected_os)
def _show_popover(self):
self.topwin.set_relative_to(self.search_entry)
self.topwin.popup()
self._set_default_selection()
################
# UI Callbacks #
################
def _entry_activate_cb(self, src):
os_list = self.widget("os-list")
sel = os_list.get_selection()
model, rows = sel.get_selected_rows()
if rows:
self.select_os(model[rows[0]][0])
def _key_press_cb(self, src, event):
if Gdk.keyval_name(event.keyval) != "Down":
return
self._show_popover()
self.widget("os-list").grab_focus()
def _eol_toggled_cb(self, src):
self._filter_eol = not src.get_active()
self._refilter()
def _os_selected_cb(self, tree_view, path, column):
model, titer = tree_view.get_selection().get_selected()
if titer is None:
self.emit("os-selected", None)
else:
self.emit("os-selected", model[titer][0])
def _search_changed_cb(self, src):
"""
Called text in search_entry is changed
"""
searchname = src.get_text().strip()
selected_label = None
if self._selected_os:
selected_label = self._selected_os.label
if (not src.get_sensitive() or
selected_label == searchname):
self.topwin.popdown()
self._clear_filter()
return
self._filter_by_name(searchname)
self._show_popover()
def _stop_search_cb(self, src):
"""
Called when the search window is closed, like with Escape key
"""
if self._selected_os:
self.search_entry.set_text(self._selected_os.label)
else:
self.search_entry.set_text("")
def _os_selected_cb(self, src, path, column):
self._sync_os_selection()
def _filter_os_cb(self, model, titer, ignore1):
osobj = model.get(titer, 0)[0]
if osobj.is_generic():
return True
def _filter_os(self, model, titer, ignore1):
os = model.get(titer, 0)[0]
if self._filter_eol:
if os.eol:
if osobj.eol:
return False
if self._filter_name is not None and self._filter_name != "":
label = os.label.lower()
name = os.name.lower()
label = osobj.label.lower()
name = osobj.name.lower()
if (label.find(self._filter_name) == -1 and
name.find(self._filter_name) == -1):
return False
return True
def _refilter(self):
###############
# Public APIs #
###############
def reset_state(self):
self._selected_os = None
self.search_entry.set_text("")
self._clear_filter()
self._sync_os_selection()
def select_os(self, vmosobj):
self._clear_filter()
os_list = self.widget("os-list")
sel = os_list.get_selection()
sel.unselect_all()
self._os_list_model.refilter()
if vmosobj.eol and not self.widget("include-eol").get_active():
self.widget("include-eol").set_active(True)
def filter_name(self, partial_name):
self._filter_name = partial_name.lower()
self._refilter()
for row in os_list.get_model():
osobj = row[0]
if osobj.name != vmosobj.name:
continue
def show(self, parent):
self.topwin.set_relative_to(parent)
self.topwin.popup()
os_list.get_selection().select_iter(row.iter)
self._sync_os_selection()
return
def hide(self):
self.topwin.popdown()
def get_selected_os(self):
return self._selected_os
def _cleanup(self):
pass
def set_sensitive(self, sensitive):
if sensitive == self.search_entry.get_sensitive():
return
if not sensitive:
self.search_entry.set_sensitive(False)
self.reset_state()
else:
if self._selected_os:
self.select_os(self._selected_os)
else:
self.reset_state()
self.search_entry.set_sensitive(True)

View File

@ -264,8 +264,7 @@ class Guest(XMLBuilder):
logging.debug("Setting Guest os_name=%s", name)
self.__osinfo = obj
if self.__osinfo.full_id:
self._metadata.libosinfo.os_id = self.__osinfo.full_id
self._metadata.libosinfo.os_id = self.__osinfo.full_id
def _supports_virtio(self, os_support):
if not self.conn.is_qemu():

View File

@ -20,6 +20,31 @@ from gi.repository import Libosinfo as libosinfo
# Sorting helpers #
###################
def _sortby(osobj):
"""
Combines distro+version to make a more sort friendly string. Examples
fedora25 -> fedora-0025000000000000
ubuntu17.04 -> ubuntu-0017000400000000
win2k8r2 -> win-0006000100000000
"""
if osobj.is_generic():
# Sort generic at the end of the list
return "zzzzzz-000000000000"
version = osobj.version
try:
t = version.split(".")
t = t[:min(4, len(t))] + [0] * (4 - min(4, len(t)))
new_version = ""
for n in t:
new_version = new_version + ("%.4i" % int(n))
version = new_version
except Exception:
pass
return "%s-%s" % (osobj.distro, version)
def _sort(tosort):
sortby_mappings = {}
distro_mappings = {}
@ -28,13 +53,15 @@ def _sort(tosort):
for key, osinfo in tosort.items():
# Libosinfo has some duplicate version numbers here, so append .1
# if there's a collision
sortby = osinfo.sortby
sortby = _sortby(osinfo)
while sortby_mappings.get(sortby):
sortby = sortby + ".1"
sortby_mappings[sortby] = key
# Group by distro first, so debian is clumped together, fedora, etc.
distro = osinfo.distro
if osinfo.is_generic():
distro = "zzzzzz"
if distro not in distro_mappings:
distro_mappings[distro] = []
distro_mappings[distro].append(sortby)
@ -217,12 +244,12 @@ class _OsVariant(object):
self.full_id = self._os and self._os.get_id() or None
self.name = self._os and self._os.get_short_id() or "generic"
self.label = self._os and self._os.get_name() or "Generic"
self.label = self._os and self._os.get_name() or "Generic default"
self.codename = self._os and self._os.get_codename() or ""
self.distro = self._os and self._os.get_distro() or ""
self.version = self._os and self._os.get_version() or None
self.eol = self._get_eol()
self.sortby = self._get_sortby()
def __repr__(self):
return "<%s name=%s>" % (self.__class__.__name__, self.name)
@ -308,28 +335,14 @@ class _OsVariant(object):
return now > rel5
return False
def _get_sortby(self):
if not self._os:
return "1"
version = self._os.get_version()
try:
t = version.split(".")
t = t[:min(4, len(t))] + [0] * (4 - min(4, len(t)))
new_version = ""
for n in t:
new_version = new_version + ("%.4i" % int(n))
version = new_version
except Exception:
pass
return "%s-%s" % (self.distro, version)
###############
# Public APIs #
###############
def is_generic(self):
return self._os is None
def is_windows(self):
return self._family in ['win9x', 'winnt', 'win16']