summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris Lumens <clumens@redhat.com>2009-11-17 13:43:27 -0500
committerChris Lumens <clumens@redhat.com>2009-12-11 16:16:19 -0500
commit92cb83bec3dcd1b2590538b10af58332ab017522 (patch)
tree44eaddd8b5c1504e5294f9262630b3504e290075
parent9874f5a6e41a7fc1b55684a2d3eee52f72bc9bc5 (diff)
downloadanaconda-92cb83bec3dcd1b2590538b10af58332ab017522.tar.gz
anaconda-92cb83bec3dcd1b2590538b10af58332ab017522.tar.xz
anaconda-92cb83bec3dcd1b2590538b10af58332ab017522.zip
Add a dialog to configure advanced storage devices.
This brings back the old behavior of having a dialog that can prompt for unusual storage devices that require manual intervention, like FCOE and iSCSI. After the dialog is run, we have to put and new devices into the UI. However, udev isn't going to provide just a list of newly appeared devices so we have to maintain a list of what was around previously, and compare the current device list to that. This promises to be slow but there's not a better option.
-rw-r--r--iw/advanced_storage.py255
-rw-r--r--iw/filter_gui.py114
-rw-r--r--ui/filter.glade4
3 files changed, 349 insertions, 24 deletions
diff --git a/iw/advanced_storage.py b/iw/advanced_storage.py
new file mode 100644
index 000000000..22ddff7fa
--- /dev/null
+++ b/iw/advanced_storage.py
@@ -0,0 +1,255 @@
+#
+# Copyright (C) 2009 Red Hat, Inc. All rights reserved.
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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, see <http://www.gnu.org/licenses/>.
+#
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+# UI methods for supporting adding advanced storage devices.
+import gobject
+import gtk
+import gtk.glade
+import gui
+import iutil
+import network
+import storage.fcoe
+import storage.iscsi
+
+def addFcoeDrive(anaconda):
+ (dxml, dialog) = gui.getGladeWidget("fcoe-config.glade", "fcoeDialog")
+ combo = dxml.get_widget("fcoeNicCombo")
+ dcb_cb = dxml.get_widget("dcbCheckbutton")
+
+ # Populate the combo
+ cell = gtk.CellRendererText()
+ combo.pack_start(cell, True)
+ combo.set_attributes(cell, text = 0)
+ cell.set_property("wrap-width", 525)
+ combo.set_size_request(480, -1)
+ store = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
+ combo.set_model(store)
+
+ netdevs = anaconda.id.network.available()
+ keys = netdevs.keys()
+ keys.sort()
+ selected_interface = None
+ for dev in keys:
+ # Skip NICs which are connected (iow in use for a net install)
+ if dev in network.getActiveNetDevs():
+ continue
+
+ i = store.append(None)
+ desc = netdevs[dev].get("DESC")
+ if desc:
+ desc = "%s - %s" %(dev, desc)
+ else:
+ desc = "%s" %(dev,)
+
+ mac = netdevs[dev].get("HWADDR")
+ if mac:
+ desc = "%s - %s" %(desc, mac)
+
+ if selected_interface is None:
+ selected_interface = i
+
+ store[i] = (desc, dev)
+
+ if selected_interface:
+ combo.set_active_iter(selected_interface)
+ else:
+ combo.set_active(0)
+
+ # Show the dialog
+ gui.addFrame(dialog)
+ dialog.show_all()
+ sg = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
+ sg.add_widget(dxml.get_widget("fcoeNicCombo"))
+
+ while True:
+ rc = dialog.run()
+
+ if rc in [gtk.RESPONSE_CANCEL, gtk.RESPONSE_DELETE_EVENT]:
+ break
+
+ iter = combo.get_active_iter()
+ if iter is None:
+ anaconda.intf.messageWindow(_("Error"),
+ _("You must select a NIC to use."),
+ type="warning", custom_icon="error")
+ continue
+
+ try:
+ anaconda.id.storage.fcoe.addSan(store.get_value(iter, 1),
+ dcb=dcb_cb.get_active(),
+ intf=anaconda.intf)
+ except IOError as e:
+ anaconda.intf.messageWindow(_("Error"), str(e))
+ rc = gtk.RESPONSE_CANCEL
+
+ break
+
+ dialog.destroy()
+ return rc
+
+def addIscsiDrive(anaconda):
+ if not network.hasActiveNetDev():
+ net = NetworkConfigurator(anaconda.id.network)
+ ret = net.run()
+ net.destroy()
+ if ret != gtk.RESPONSE_OK:
+ return ret
+
+ (dxml, dialog) = gui.getGladeWidget("iscsi-config.glade", "iscsiDialog")
+ gui.addFrame(dialog)
+ dialog.show_all()
+ sg = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
+ for w in ["iscsiAddrEntry", "iscsiInitiatorEntry", "userEntry",
+ "passEntry", "userinEntry", "passinEntry"]:
+ sg.add_widget(dxml.get_widget(w))
+
+ # get the initiator name if it exists and don't allow changing
+ # once set
+ e = dxml.get_widget("iscsiInitiatorEntry")
+ e.set_text(anaconda.id.storage.iscsi.initiator)
+ if anaconda.id.storage.iscsi.initiatorSet:
+ e.set_sensitive(False)
+
+ while True:
+ rc = dialog.run()
+ if rc in [gtk.RESPONSE_CANCEL, gtk.RESPONSE_DELETE_EVENT]:
+ break
+
+ initiator = e.get_text().strip()
+ if len(initiator) == 0:
+ anaconda.intf.messageWindow(_("Invalid Initiator Name"),
+ _("You must provide an initiator name."))
+ continue
+
+ anaconda.id.storage.iscsi.initiator = initiator
+
+ target = dxml.get_widget("iscsiAddrEntry").get_text().strip()
+ user = dxml.get_widget("userEntry").get_text().strip()
+ pw = dxml.get_widget("passEntry").get_text().strip()
+ user_in = dxml.get_widget("userinEntry").get_text().strip()
+ pw_in = dxml.get_widget("passinEntry").get_text().strip()
+
+ try:
+ count = len(target.split(":"))
+ idx = target.rfind("]:")
+ # Check for IPV6 [IPV6-ip]:port
+ if idx != -1:
+ ip = target[1:idx]
+ port = target[idx+2:]
+ # Check for IPV4 aaa.bbb.ccc.ddd:port
+ elif count == 2:
+ idx = target.rfind(":")
+ ip = target[:idx]
+ port = target[idx+1:]
+ else:
+ ip = target
+ port = "3260"
+
+ network.sanityCheckIPString(ip)
+ except (network.IPMissing, network.IPError) as msg:
+ anaconda.intf.messageWindow(_("Error with Data"), msg)
+ continue
+
+ try:
+ anaconda.id.storage.iscsi.addTarget(ip, port, user, pw,
+ user_in, pw_in,
+ anaconda.intf)
+ except ValueError as e:
+ anaconda.intf.messageWindow(_("Error"), str(e))
+ continue
+ except IOError as e:
+ anaconda.intf.messageWindow(_("Error"), str(e))
+ rc = gtk.RESPONSE_CANCEL
+
+ break
+
+ dialog.destroy()
+ return rc
+
+def addZfcpDrive(anaconda):
+ (dxml, dialog) = gui.getGladeWidget("zfcp-config.glade", "zfcpDialog")
+ gui.addFrame(dialog)
+ dialog.show_all()
+ sg = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
+ for w in ["devnumEntry", "wwpnEntry", "fcplunEntry"]:
+ sg.add_widget(dxml.get_widget(w))
+
+ while True:
+ rc = dialog.run()
+ if rc != gtk.RESPONSE_APPLY:
+ break
+
+ devnum = dxml.get_widget("devnumEntry").get_text().strip()
+ wwpn = dxml.get_widget("wwpnEntry").get_text().strip()
+ fcplun = dxml.get_widget("fcplunEntry").get_text().strip()
+
+ try:
+ anaconda.storage.zfcp.addFCP(devnum, wwpn, fcplun)
+ except ValueError as e:
+ anaconda.intf.messageWindow(_("Error"), str(e))
+ continue
+
+ break
+
+ dialog.destroy()
+ return rc
+
+def addDrive(anaconda):
+ (dxml, dialog) = gui.getGladeWidget("adddrive.glade", "addDriveDialog")
+ gui.addFrame(dialog)
+ dialog.show_all()
+ if not iutil.isS390():
+ dxml.get_widget("zfcpRadio").hide()
+ dxml.get_widget("zfcpRadio").set_group(None)
+
+ if not storage.iscsi.has_iscsi():
+ dxml.get_widget("iscsiRadio").set_sensitive(False)
+ dxml.get_widget("iscsiRadio").set_active(False)
+
+ if not storage.fcoe.has_fcoe():
+ dxml.get_widget("fcoeRadio").set_sensitive(False)
+ dxml.get_widget("fcoeRadio").set_active(False)
+
+ #figure out what advanced devices we have available and set sensible default
+ group = dxml.get_widget("iscsiRadio").get_group()
+ for button in group:
+ if button is not None and button.get_property("sensitive"):
+ button.set_active(True)
+ break
+
+ rc = dialog.run()
+ dialog.hide()
+
+ if rc in [gtk.RESPONSE_CANCEL, gtk.RESPONSE_DELETE_EVENT]:
+ return False
+
+ if dxml.get_widget("iscsiRadio").get_active() and storage.iscsi.has_iscsi():
+ rc = addIscsiDrive(anaconda)
+ elif dxml.get_widget("fcoeRadio").get_active() and storage.fcoe.has_fcoe():
+ rc = addFcoeDrive(anaconda)
+ elif dxml.get_widget("zfcpRadio") is not None and dxml.get_widget("zfcpRadio").get_active():
+ rc = addZfcpDrive(anaconda)
+
+ dialog.destroy()
+
+ if rc in [gtk.RESPONSE_CANCEL, gtk.RESPONSE_DELETE_EVENT]:
+ return False
+ else:
+ return True
diff --git a/iw/filter_gui.py b/iw/filter_gui.py
index 0a26ac40b..ac1bdb8f3 100644
--- a/iw/filter_gui.py
+++ b/iw/filter_gui.py
@@ -19,6 +19,7 @@
#
import block
+import collections
import gtk, gobject
import gtk.glade
import gui
@@ -45,6 +46,40 @@ PORT_COL = 11
TARGET_COL = 12
LUN_COL = 13
+# This is kind of a magic class that is used for populating the device store.
+# It mostly acts like a list except for some funny behavior on adding/getting.
+# You must add udev dicts to this list, but when you go to examine the list
+# (by pulling items out, checking membership, etc.) you are comparing based
+# on names.
+#
+# The only reason to have this is to prevent needing two lists in a variety
+# of places throughout FilterWindow.
+class NameCache(collections.MutableSequence):
+ def __init__(self, iterable):
+ self._lst = list(iterable)
+
+ def __contains__(self, item):
+ return item["name"] in iter(self)
+
+ def __delitem__(self, index):
+ return self._lst.__delitem__(index)
+
+ def __getitem__(self, index):
+ return self._lst.__getitem__(index)["name"]
+
+ def __iter__(self):
+ for d in self._lst:
+ yield d["name"]
+
+ def __len__(self):
+ return len(self._lst)
+
+ def __setitem__(self, index, value):
+ return self._lst.__setitem__(index, value)
+
+ def insert(self, index, value):
+ return self._lst.insert(index, value)
+
# These are global because they need to be accessible across all Callback
# objects as the same values, and from the AdvancedFilterWindow object to add
# and remove devices when populating scrolled windows.
@@ -65,7 +100,7 @@ def isRAID(info):
def isMultipath(info):
return udev_device_is_multipath_member(info)
-def isOther(info)
+def isOther(info):
return udev_device_is_iscsi(info) or udev_device_is_fcoe(info)
class Callbacks(object):
@@ -341,16 +376,27 @@ class FilterWindow(InstallWindow):
self.anaconda.id.storage.exclusiveDisks.extend(list(selected))
- def _addTuple(self, tuple):
- global totalDevices, totalSize
+ def _add_advanced_clicked(self, button):
+ from advanced_storage import addDrive
+
+ if not addDrive(self.anaconda):
+ return
+
+ udev_trigger(subsystem="block")
+ new_disks = filter(udev_device_is_disk, udev_get_block_devices())
+ (new_singlepaths, new_mpaths, new_partitions) = identifyMultipaths(new_disks)
+ (new_raids, new_nonraids) = self.split_list(lambda d: isRAID(d) and not isCCISS(d),
+ new_singlepaths)
+
+ nonraids = filter(lambda d: d not in self._cachedDevices, new_nonraids)
+ mpaths = filter(lambda d: d not in self._cachedMPaths, new_mpaths)
+ raids = filter(lambda d: d not in self._cachedRaidDevices, new_raids)
- self.store.append(None, tuple)
- totalDevices += 1
- totalSize += tuple[0]["XXX_SIZE"]
+ self.populate(nonraids, mpaths, raids)
- for pg in self.pages:
- if pg.cb.isMember(tuple[0]):
- pg.cb.addToUI(tuple)
+ self._cachedDevices.extend(nonraids)
+ self._cachedMPaths.extend(mpaths)
+ self._cachedRaidDevices.extend(raids)
def _makeBasic(self):
np = NotebookPage(self.store, "basic", self.xml, Callbacks(self.xml))
@@ -379,7 +425,7 @@ class FilterWindow(InstallWindow):
np.ds.addColumn(_("Paths"), PATHS_COL)
return np
- def _makeOther(self)
+ def _makeOther(self):
np = NotebookPage(self.store, "other", self.xml, OtherCallbacks(self.xml))
np.ds.addColumn(_("WWID"), WWID_COL)
@@ -414,8 +460,10 @@ class FilterWindow(InstallWindow):
(self.xml, self.vbox) = gui.getGladeWidget("filter.glade", "vbox")
self.buttonBox = self.xml.get_widget("buttonBox")
self.notebook = self.xml.get_widget("notebook")
+ self.addAdvanced = self.xml.get_widget("addAdvancedButton")
self.notebook.connect("switch-page", self._page_switched)
+ self.addAdvanced.connect("clicked", self._add_advanced_clicked)
self.pages = []
@@ -449,16 +497,40 @@ class FilterWindow(InstallWindow):
self._makeSearch()]
udev_trigger(subsystem="block")
- all_devices = filter(udev_device_is_disk, udev_get_block_devices())
- (all_devices, mpaths, partitions) = identifyMultipaths(all_devices)
+ disks = filter(udev_device_is_disk, udev_get_block_devices())
+ (singlepaths, mpaths, partitions) = identifyMultipaths(disks)
# The device list could be really long, so we really only want to
# iterate over it the bare minimum of times. Dividing this list up
# now means fewer elements to iterate over later.
- (raid_devices, devices) = self.partition_list(lambda d: isRAID(d) and not isCCISS(d),
- all_devices)
+ (raids, nonraids) = self.split_list(lambda d: isRAID(d) and not isCCISS(d),
+ singlepaths)
+
+ self.populate(nonraids, mpaths, raids)
+
+ # If the "Add Advanced" button is ever clicked, we need to have a list
+ # of what devices previously existed so we know what's new. Then we
+ # can just add the new devices to the UI. This is going to be slow,
+ # but the user has to click a button to get to the slow part.
+ self._cachedDevices = NameCache(singlepaths)
+ self._cachedMPaths = NameCache(mpaths)
+ self._cachedRaidDevices = NameCache(raids)
+
+ return self.vbox
- for d in devices:
+ def populate(self, nonraids, mpaths, raids):
+ def _addTuple(tuple):
+ global totalDevices, totalSize
+
+ self.store.append(None, tuple)
+ totalDevices += 1
+ totalSize += tuple[0]["XXX_SIZE"]
+
+ for pg in self.pages:
+ if pg.cb.isMember(tuple[0]):
+ pg.cb.addToUI(tuple)
+
+ for d in nonraids:
partedDevice = parted.Device(path="/dev/" + udev_device_get_name(d))
d["XXX_SIZE"] = int(partedDevice.getSize())
@@ -467,7 +539,7 @@ class FilterWindow(InstallWindow):
udev_device_get_vendor(d), udev_device_get_bus(d),
udev_device_get_serial(d), udev_device_get_wwid(d),
"", "", "", "")
- self._addTuple(tuple)
+ _addTuple(tuple)
for rs in block.getRaidSets():
rs.activate(mknod=True, mkparts=False)
@@ -478,7 +550,7 @@ class FilterWindow(InstallWindow):
fstype = ""
members = map(lambda m: m.get_devpath(), list(rs.get_members()))
- for d in raid_devices:
+ for d in raids:
if udev_device_get_name(d) in members:
fstype = udev_device_get_format(d)
break
@@ -491,7 +563,7 @@ class FilterWindow(InstallWindow):
tuple = (data, True, False, rs.name, partedDevice.model,
str(size) + " MB", "", "", "", "", "", "", "", "")
- self._addTuple(tuple)
+ _addTuple(tuple)
rs.deactivate()
@@ -511,11 +583,9 @@ class FilterWindow(InstallWindow):
udev_device_get_serial(mpath[0]),
udev_device_get_wwid(mpath[0]),
paths, "", "", "")
- self._addTuple(tuple)
-
- return self.vbox
+ _addTuple(tuple)
- def partition_list(self, pred, lst):
+ def split_list(self, pred, lst):
pos = []
neg = []
diff --git a/ui/filter.glade b/ui/filter.glade
index a8894184e..505b1aa8b 100644
--- a/ui/filter.glade
+++ b/ui/filter.glade
@@ -1264,7 +1264,7 @@ Target WWID</property>
<property name="spacing">0</property>
<child>
- <widget class="GtkButton" id="addISCSIButton">
+ <widget class="GtkButton" id="addAdvancedButton">
<property name="visible">True</property>
<property name="can_default">True</property>
<property name="can_focus">True</property>
@@ -1309,7 +1309,7 @@ Target WWID</property>
<child>
<widget class="GtkLabel" id="label9">
<property name="visible">True</property>
- <property name="label" translatable="yes">Add iSCSI Target</property>
+ <property name="label" translatable="yes">Add Advanced Target</property>
<property name="use_underline">True</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>