KGRKJGETMRETU895U-589TY5MIGM5JGB5SDFESFREWTGR54TY
Server : Apache/2.2.17 (Unix) mod_ssl/2.2.17 OpenSSL/0.9.8e-fips-rhel5 DAV/2 PHP/5.2.17
System : Linux localhost 2.6.18-419.el5 #1 SMP Fri Feb 24 22:47:42 UTC 2017 x86_64
User : nobody ( 99)
PHP Version : 5.2.17
Disable Function : NONE
Directory :  /proc/22697/root/usr/share/system-config-lvm/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : //proc/22697/root/usr/share/system-config-lvm/InputController.py
"""This class represents the primary controller interface
   for the LVM UI application.
"""


import string
import os
import re
import stat
import os.path
import gobject
import locale
from lvm_model import lvm_model
from CommandHandler import CommandHandler
from lvmui_constants import *
from CommandError import CommandError
import Fstab
import Filesystem
from Segment import STRIPED_SEGMENT_ID
from ExtentBlock import ExtentBlock
from WaitMsg import WaitMsg

import PhysicalVolume

from execute import execWithCapture, execWithCaptureErrorStatus, execWithCaptureStatus
from utilities import follow_links_to_target


import gettext
_ = gettext.gettext

### gettext first, then import gtk (exception prints gettext "_") ###
try:
    import gtk
    import gtk.glade
except RuntimeError, e:
    print _("""
  Unable to initialize graphical environment. Most likely cause of failure
  is that the tool was not run using a graphical environment. Please either
  start your graphical user interface or set your DISPLAY variable.
                                                                                
  Caught exception: %s
""") % e
    sys.exit(-1)
                                                                                
import gnome
import gnome.ui

SIZE_COL = TYPE_COL
VOL_TYPE_COL = 3

UNALLOC_VOL = 0
UNINIT_VOL = 1

###TRANSLATOR: The string below is seen when adding a new Physical
###Volume to an existing Volume Group.
ADD_PV_TO_VG_LABEL=_("Select a Volume Group to add %s to:")

MEGA_MULTIPLIER = 1000000.0
GIGA_MULTIPLIER = 1000000000.0
KILO_MULTIPLIER = 1000.0

DEFAULT_STRIPE_SIZE_IDX = 4

MAX_PHYSICAL_VOLS = 256
MAX_LOGICAL_VOLS = 256
DEFAULT_EXTENT_SIZE = 4
DEFAULT_EXTENT_SIZE_MEG_IDX = 1
DEFAULT_EXTENT_SIZE_KILO_IDX = 2

NO_FILESYSTEM_FS = 0

ACCEPTABLE_STRIPE_SIZES = [4,8,16,32,64,128,256,512]

ACCEPTABLE_EXTENT_SIZES = ["2","4","8","16","32","64","128","256","512","1024"]

###TRANSLATOR: The two strings below refer to the name and type of
###available disk entities on the system. There are two types --
###The first is an 'unallocated physical volume' which is a disk or
###partition that has been initialized for use with LVM, by writing
###a special label onto the first block of the partition. The other type
###is an 'uninitialized entity', which is an available disk or partition
###that is NOT yet initialized to be used with LVM. Hope this helps give
###some context.
ENTITY_NAME=_("Name")
ENTITY_SIZE=_("Size")
ENTITY_TYPE=_("Entity Type")

UNALLOCATED_PV=_("Unallocated Physical Volume")
UNINIT_DE=_("Uninitialized Disk Entity") 
ADD_VG_LABEL=_("Select disk entities to add to the %s Volume Group:")

CANT_STRIPE_MESSAGE=_("A Volume Group must be made up of two or more Physical Volumes to support striping. This Volume Group does not meet that requirement.")

NON_UNIQUE_NAME=_("A Logical Volume with the name %s already exists in this Volume Group. Please choose a unique name.")

NON_UNIQUE_VG_NAME=_("A Volume Group with the name %s already exists. Please choose a unique name.")

MUST_PROVIDE_NAME=_("A Name must be provided for the new Logical Volume")

MUST_PROVIDE_VG_NAME=_("A Name must be provided for the new Volume Group")

BAD_MNT_POINT=_("The specified mount point, %s, does not exist. Do you wish to create it?")

BAD_MNT_CREATION=_("The creation of mount point %s unexpectedly failed.")

NOT_IMPLEMENTED=_("This capability is not yet implemented in this version")

EXCEEDED_MAX_LVS=_("The number of Logical Volumes in this Volume Group has reached its maximum limit.")

EXCEEDED_MAX_PVS=_("The number of Physical Volumes in this Volume Group has reached its maximum limit.")

EXCEEDING_MAX_PVS=_("At most %s Physical Volumes can be added to this Volume Group before the limit is reached.")

NOT_ENOUGH_SPACE_FOR_NEW_LV=_("Volume Group %s does not have enough space for new Logical Volumes. A possible solution would be to add an additional Physical Volume to the Volume Group.")

ALREADY_A_SNAPSHOT=_("A snapshot of a snapshot is not supported.")
CANNOT_SNAPSHOT_A_MIRROR=_("A snapshot of a mirrored Logical Volume is not supported.")

CANNOT_REMOVE_UNDER_SNAPSHOT=_("Logical volume %s has snapshot %s currently associated with it. Please remove the snapshot first.")
CANNOT_REMOVE_UNDER_SNAPSHOTS=_("Logical volume %s has snapshots: %s currently associated with it. Please remove snapshots first.")

TYPE_CONVERSION_ERROR=_("Undefined type conversion error in model factory. Unable to complete task.")

MOUNTED_WARNING=_("BIG WARNING: Logical Volume %s has an %s file system on it and is currently mounted on %s. Are you absolutely certain that you wish to discard the data on this mounted filesystem?")

UNMOUNT_PROMPT=_("Logical Volume %s is currently mounted on %s. In order to complete request, it has to be unmounted. Are you sure you want it unmounted?")



###TRANSLATOR: An extent below is an abstract unit of storage. The size
###of an extent is user-definable.
REMAINING_SPACE_VGNAME=_("Unused space on %s")
REMAINING_SPACE_MEGABYTES=_("%s megabytes")
REMAINING_SPACE_KILOBYTES=_("%s kilobytes")
REMAINING_SPACE_GIGABYTES=_("%s gigabytes")
REMAINING_SPACE_EXTENTS=_("%s extents")

REMAINING_SPACE_VG=_("Remaining free space in Volume Group:\n")
REMAINING_SPACE_AFTER=_("Remaining space for this Volume:\n")

EXTENTS=_("Extents")
GIGABYTES=_("Gigabytes")
MEGABYTES=_("Megabytes")
KILOBYTES=_("Kilobytes")

NUMBERS_ONLY=_("The %s should only contain number values")
NUMBERS_ONLY_MAX_PVS=_("The Maximum Physical Volumes field should contain only integer values between 1 and 256")
NUMBERS_ONLY_MAX_LVS=_("The Maximum Logical Volumes field should contain only integer values between 1 and 256")

CONFIRM_PVREMOVE=_("Are you quite certain that you wish to remove %s from Logical Volume Management?")

SOLO_PV_IN_VG=_("The Physical Volume named %s, that you wish to remove, has data from active Logical Volume(s) mapped to its extents. Because it is the only Physical Volume in the Volume Group, there is no place to move the data to. Recommended action is either to add a new Physical Volume before removing this one, or else remove the Logical Volumes that are associated with this Physical Volume.") 
CONFIRM_PV_VG_REMOVE=_("Are you quite certain that you wish to remove %s from the %s Volume Group?")
CONFIRM_VG_REMOVE=_("Removing Physical Volume %s from the Volume Group %s will leave the Volume group empty, and it will be removed as well. Do you wish to proceed?")
NOT_ENOUGH_SPACE_VG=_("Volume Group %s does not have enough space to move the data stored on %s. A possible solution would be to add an additional Physical Volume to the Volume Group.")
NO_DM_MIRROR=_("The dm-mirror module is either not loaded in your kernel, or your kernel does not support the dm-mirror target. If it is supported, try running \"modprobe dm-mirror\". Otherwise, operations that require moving data on Physical Extents are unavailable.")
NO_DM_SNAPSHOT=_("The dm-snapshot module is either not loaded in your kernel, or your kernel does not support the dm-snapshot target. If it is supported, try running \"modprobe dm-snapshot\". Otherwise, creation of snapshots is unavailable.")

CONFIRM_LV_REMOVE=_("Are you quite certain that you wish to remove logical volume %s?")
CONFIRM_LV_REMOVE_FILESYSTEM=_("Logical volume %s contains %s filesystem. All data on it will be lost! Are you quite certain that you wish to remove logical volume %s?")
CONFIRM_LV_REMOVE_MOUNTED=_("Logical volume %s contains data from directory %s. All data in it will be lost! Are you quite certain that you wish to remove logical volume %s?")


###########################################################
class InputController:
  def __init__(self, reset_tree_model, treeview, model_factory, glade_xml):
    self.reset_tree_model = reset_tree_model
    self.treeview = treeview
    self.model_factory = model_factory
    self.glade_xml = glade_xml
    
    self.command_handler = CommandHandler()
    self.section_list = list()
    self.section_type = UNSELECTABLE_TYPE
    
    self.setup_dialogs()
    
    # check if pvmove is in progress
    if self.model_factory.pvmove_in_progress():
        self.command_handler.complete_pvmove()
  
  def setup_dialogs(self):
    self.init_entity_button = self.glade_xml.get_widget('uninit_button')
    self.init_entity_button.connect("clicked", self.on_init_entity)
    
    self.setup_new_vg_form()
    #self.setup_pv_rm_migrate()
    #self.setup_pv_rm()
    
    ###################
    ##This form adds an unallocated PV to a VG
    self.add_pv_to_vg_dlg = self.glade_xml.get_widget('add_pv_to_vg_form')
    self.add_pv_to_vg_dlg.connect("delete_event",self.add_pv_to_vg_delete_event)
    self.add_pv_to_vg_button = self.glade_xml.get_widget('add_pv_to_vg_button')
    self.add_pv_to_vg_button.connect("clicked",self.on_add_pv_to_vg)
    self.add_pv_to_vg_treeview = self.glade_xml.get_widget('add_pv_to_vg_treeview')
    self.ok_add_pv_to_vg_button = self.glade_xml.get_widget('ok_add_pv_to_vg_button')
    self.ok_add_pv_to_vg_button.connect("clicked",self.on_ok_add_pv_to_vg)
    self.cancel_add_pv_to_vg_button = self.glade_xml.get_widget('cancel_add_pv_to_vg_button')
    self.cancel_add_pv_to_vg_button.connect("clicked",self.on_cancel_add_pv_to_vg)
    self.add_pv_to_vg_label = self.glade_xml.get_widget('add_pv_to_vg_label')
    model = gtk.ListStore (gobject.TYPE_STRING,
                           gobject.TYPE_STRING)
    self.add_pv_to_vg_treeview.set_model(model)
    renderer1 = gtk.CellRendererText()
    column1 = gtk.TreeViewColumn("Volume Groups",renderer1, text=0)
    self.add_pv_to_vg_treeview.append_column(column1)
    renderer2 = gtk.CellRendererText()
    column2 = gtk.TreeViewColumn("Size",renderer2, text=1)
    self.add_pv_to_vg_treeview.append_column(column2)
    
    # new lv button
    self.new_lv_button = self.glade_xml.get_widget('new_lv_button')
    self.new_lv_button.connect("clicked",self.on_new_lv)
    
    self.setup_extend_vg_form()
    self.setup_misc_widgets()
    
  ##################
  ##This form adds a new VG
  def setup_new_vg_form(self):
    self.new_vg_dlg = self.glade_xml.get_widget('new_vg_form')
    self.new_vg_dlg.connect("delete_event",self.new_vg_delete_event)
    self.new_vg_button = self.glade_xml.get_widget('new_vg_button')
    self.new_vg_button.connect("clicked", self.on_new_vg)
    self.ok_new_vg_button = self.glade_xml.get_widget('ok_new_vg_button')
    self.ok_new_vg_button.connect("clicked",self.ok_new_vg)
    self.cancel_new_vg_button = self.glade_xml.get_widget('cancel_new_vg_button')
    self.cancel_new_vg_button.connect("clicked", self.cancel_new_vg)
    
    ##Buttons and fields...
    self.new_vg_name = self.glade_xml.get_widget('new_vg_name')
    self.new_vg_max_pvs = self.glade_xml.get_widget('new_vg_max_pvs')
    self.new_vg_max_lvs = self.glade_xml.get_widget('new_vg_max_lvs')
    self.new_vg_extent_size = self.glade_xml.get_widget('new_vg_extent_size')
    self.new_vg_radio_meg = self.glade_xml.get_widget('radiobutton1')
    self.new_vg_radio_meg.connect('clicked', self.change_new_vg_radio)
    self.new_vg_radio_kilo = self.glade_xml.get_widget('radiobutton2')
    self.new_vg_clustered = self.glade_xml.get_widget('clustered_butt')

  def on_new_vg(self, button):
    self.prep_new_vg_dlg()
    self.new_vg_dlg.show()
                                                                                
  def cancel_new_vg(self, button):
    self.new_vg_dlg.hide()

  def ok_new_vg(self, button):
    Name_request = ""
    max_physical_volumes = 256
    max_logical_volumes = 256
    phys_extent_size = 8
    phys_extent_units_meg = True
    autobackup = True
    resizable = True
    
    selection = self.treeview.get_selection()
    model,iter = selection.get_selected()
    pv = model.get_value(iter, OBJ_COL)
    
    proposed_name = self.new_vg_name.get_text().strip()
    if proposed_name == "":
        self.errorMessage(MUST_PROVIDE_VG_NAME)
        return
    
    #Now check for unique name
    vg_list = self.model_factory.get_VGs()
    for vg in vg_list:
        if vg.get_name() == proposed_name:
            self.new_vg_name.select_region(0, (-1))
            self.errorMessage(NON_UNIQUE_VG_NAME % proposed_name)
            return
    Name_request = proposed_name
    
    max_pvs_field = self.new_vg_max_pvs.get_text()
    if max_pvs_field.isalnum() == False:
        self.errorMessage(NUMBERS_ONLY_MAX_PVS)
        self.new_vg_max_pvs.set_text(str(MAX_PHYSICAL_VOLS))
        return
    else:
        max_pvs = int(max_pvs_field)
        if (max_pvs < 1) or (max_pvs > MAX_PHYSICAL_VOLS):
            self.errorMessage(NUMBERS_ONLY_MAX_PVS)
            self.new_vg_max_pvs.set_text(str(MAX_PHYSICAL_VOLS))
            return
        max_physical_volumes = max_pvs
    
    max_lvs_field = self.new_vg_max_lvs.get_text()
    if max_lvs_field.isalnum() == False:
        self.errorMessage(NUMBERS_ONLY_MAX_LVS)
        self.new_vg_max_lvs.set_text(str(MAX_LOGICAL_VOLS))
        return
    else:
        max_lvs = int(max_lvs_field)
        if (max_lvs < 1) or (max_lvs > MAX_LOGICAL_VOLS):
            self.errorMessage(NUMBERS_ONLY_MAX_LVS)
            self.new_vg_max_lvs.set_text(str(MAX_LOGICAL_VOLS))
            return
        max_logical_volumes = max_lvs
    
    extent_idx = self.new_vg_extent_size.get_history()
    phys_extent_units_meg =  self.new_vg_radio_meg.get_active()
    
    clustered = self.new_vg_clustered.get_active()
    if clustered:
        msg = _("In order for Volume Group to be safely used in clustered environment, lvm2-cluster rpm has to be installed, `lvmconf --enable-cluster` has to be executed and clvmd service has to be running")
        self.infoMessage(msg)
    
    try:
        self.command_handler.create_new_vg(Name_request,
                                           str(max_physical_volumes),
                                           str(max_logical_volumes),
                                           ACCEPTABLE_EXTENT_SIZES[extent_idx],
                                           phys_extent_units_meg,
                                           pv.get_path(),
                                           clustered)
    except CommandError, e:
        self.errorMessage(e.getMessage())
    
    self.new_vg_dlg.hide()
    
    apply(self.reset_tree_model, [Name_request])
  
  def prep_new_vg_dlg(self):
      self.new_vg_name.set_text("")
      self.new_vg_max_pvs.set_text(str(MAX_PHYSICAL_VOLS))
      self.new_vg_max_lvs.set_text(str(MAX_LOGICAL_VOLS))
      self.new_vg_radio_meg.set_active(True)
      self.new_vg_extent_size.set_history(DEFAULT_EXTENT_SIZE_MEG_IDX)
      self.new_vg_clustered.set_active(False)

  def change_new_vg_radio(self, button):
      menu = self.new_vg_extent_size.get_menu()
      items = menu.get_children()
      #We don't want to offer the 2 and 4 options for kilo's - min size is 8k
      if self.new_vg_radio_meg.get_active() == True:
          items[0].set_sensitive(True)
          items[1].set_sensitive(True)
          self.new_vg_extent_size.set_history(DEFAULT_EXTENT_SIZE_MEG_IDX)
      else:
          items[0].set_sensitive(False)
          items[1].set_sensitive(False)
          self.new_vg_extent_size.set_history(DEFAULT_EXTENT_SIZE_KILO_IDX)
  
  def on_pv_rm(self, button):
      self.remove_pv()
  
  def remove_pv(self, pv=None):
    mapped_lvs = True
    solo_pv = False
    reset_tree = False
    if pv == None:
        reset_tree = True #This says that tree reset will not be handled by caller
        selection = self.treeview.get_selection()
        model, iter = selection.get_selected()
        pv = model.get_value(iter, OBJ_COL)
    vg = pv.get_vg()
    
    # first check if all extents can be migrated
    for extent in pv.get_extent_blocks():
        extents_lv = extent.get_lv()
        if extents_lv.is_used():
            error_message = None
            if extents_lv.is_mirror_log:
                error_message = _("Physical Volume %s contains extents belonging to a mirror log of Logical Volume %s. Mirrored Logical Volumes are not yet migratable, so %s is not removable.")
                error_message = error_message % (pv.get_path(), extents_lv.get_name(), pv.get_path())
            elif extents_lv.is_mirror_image:
                error_message = _("Physical Volume %s contains extents belonging to a mirror image of Logical Volume %s. Mirrored Logical Volumes are not yet migratable, so %s is not removable.")
                error_message = error_message % (pv.get_path(), extents_lv.get_name(), pv.get_path())
            elif extents_lv.is_snapshot():
                error_message = _("Physical Volume %s contains extents belonging to %s, a snapshot of %s. Snapshots are not yet migratable, so %s is not removable.")
                error_message = error_message % (pv.get_path(), extents_lv.get_name(), extents_lv.get_snapshot_info()[0].get_name(), pv.get_path())
            elif extents_lv.has_snapshots():
                snapshots = extents_lv.get_snapshots()
                if len(snapshots) == 1:
                    error_message = _("Physical Volume %s contains extents belonging to %s, the origin of snapshot %s. Snapshot origins are not yet migratable, so %s is not removable.")
                else:
                    error_message = _("Physical Volume %s contains extents belonging to %s, the origin of snapshots %s. Snapshot origins are not yet migratable, so %s is not removable.")
                snapshots_string = snapshots[0].get_name()
                for snap in snapshots[1:]:
                    snapshot_string = snapshot_string + ', ' + snap.get_name()
                error_message = error_message % (pv.get_path(), extents_lv.get_name(), snapshots_string, pv.get_path())
            if error_message != None:
                self.errorMessage(error_message)
                return False
    
    #The following cases must be considered in this method:
    #1) a PV is to be removed that has extents mapped to an LV:
    #  1a) if there are other PVs, call pvmove on the PV to migrate the 
    #      data to other  PVs in the VG
    #      i) If there is sufficient room, pvmove the extents, then vgreduce
    #      ii) If there is not room, inform the user to add more storage and
    #           try again later
    #  1b) If there are not other PVs, state that either more PVs must
    #      be added so that the in use extents can be migrated, or else
    #      present a list of LVs that must be removed in order to 
    #      remove the PV
    #2) a PV is to be removed that has NO LVs mapped to its extents:
    #  2a) If there are more than one PV in the VG, just vgreduce away the PV
    #  2b) If the PV is the only one, then vgremove the VG
    #
    
    total, alloc, free = pv.get_extent_total_used_free()
    pv_list = vg.get_pvs().values()
    if len(pv_list) <= 1: #This PV is the only one in the VG
        solo_pv = True
    else:
        solo_pv = False
    
    extent_list = pv.get_extent_blocks()[:] # copy
    if len(extent_list) == 1: #There should always be at least one extent seg
        #We now know either the entire PV is used by one LV, or else it is
        #an unutilized PV. If the latter, we can just vgreduce it away 
        #if (seg_name == FREE) or (seg_name == UNUSED):
        if extent_list[0].get_lv().is_used():
            mapped_lvs = True
        else:
            mapped_lvs = False
    else:
        mapped_lvs = True
    
    #Cases:
    if mapped_lvs == False:
        if solo_pv:
            #call vgremove
            retval = self.warningMessage(CONFIRM_VG_REMOVE % (pv.get_path(),vg.get_name()))
            if (retval == gtk.RESPONSE_NO):
                return False
            try:
                self.command_handler.remove_vg(vg.get_name())
            except CommandError, e:
                self.errorMessage(e.getMessage())
                return False
        else: #solo_pv is False, more than one PV...
            retval = self.warningMessage(CONFIRM_PV_VG_REMOVE % (pv.get_path(),vg.get_name()))
            if (retval == gtk.RESPONSE_NO):
                return False
            try:
                self.command_handler.reduce_vg(vg.get_name(), pv.get_path())
            except CommandError, e:
                self.errorMessage(e.getMessage())
                return False
    else:
        #Two cases here: if solo_pv, bail, else check for size needed
        if solo_pv:
            self.errorMessage(SOLO_PV_IN_VG % pv.get_path())
            return False
        else: #There are additional PVs. We need to check space 
            ext_total, ext_used, ext_free = vg.get_extent_total_used_free()
            actual_free_exts = ext_free - free
            if alloc <= actual_free_exts:
                if self.command_handler.is_dm_mirror_loaded() == False:
                    self.errorMessage(NO_DM_MIRROR)
                    return False
                retval = self.warningMessage(CONFIRM_PV_VG_REMOVE % (pv.get_path(),vg.get_name()))
                if (retval == gtk.RESPONSE_NO):
                    return False
                
                # remove unused from extent_list
                for ext in extent_list[:]:
                    if ext.get_lv().is_used() == False:
                        extent_list.remove(ext)
                dlg = self.migrate_exts_dlg(True, pv, extent_list)
                if dlg == None:
                    return False
                exts_structs = []
                for ext in extent_list:
                    exts_structs.append(ext.get_start_size())
                try:
                    self.command_handler.move_pv(pv.get_path(), exts_structs, dlg.get_data())
                except CommandError, e:
                    self.errorMessage(e.getMessage())
                    return True
                try:
                    self.command_handler.reduce_vg(vg.get_name(), pv.get_path())
                except CommandError, e:
                    self.errorMessage(e.getMessage())
                    return True
                
            else:
                self.errorMessage(NOT_ENOUGH_SPACE_VG % (vg.get_name(),pv.get_path()))
                return False
    
    if reset_tree == True:
        apply(self.reset_tree_model, [vg.get_name()])
    
    return True
  
  def on_lv_rm(self, button):
    self.remove_lv()
  
  def remove_lv(self, lv=None):
    reset_tree = False
    if lv == None:
        reset_tree = True
        selection = self.treeview.get_selection()
        model, iter = selection.get_selected()
        lv = model.get_value(iter, OBJ_COL)
    
    if lv.has_snapshots():
        snapshots = lv.get_snapshots()
        if len(snapshots) == 1:
            self.errorMessage(CANNOT_REMOVE_UNDER_SNAPSHOT % (lv.get_name(), snapshots[0].get_name()))
        else:
            snaps_str = snapshots[0].get_name()
            for snap in snapshots[1:]:
                snaps_str = snaps_str + ', ' + snap.get_name()
            self.errorMessage(CANNOT_REMOVE_UNDER_SNAPSHOTS % (lv.get_name(), snaps_str))
        return False
    
    mountpoint = self.model_factory.getMountPoint(lv.get_path())
    fs = Filesystem.get_fs(lv.get_path())
    if fs.name == Filesystem.get_filesystems()[0].name:
        fs = None
    fstab_mountpoint = Fstab.get_mountpoint(lv.get_path())
    
    # prompt for confirmation
    message = None
    if mountpoint == None:
        if fs == None:
            message = CONFIRM_LV_REMOVE % lv.get_name()
        else:
            message = CONFIRM_LV_REMOVE_FILESYSTEM % (lv.get_name(), fs.name, lv.get_name())
    else:
        message = CONFIRM_LV_REMOVE_MOUNTED % (lv.get_name(), mountpoint, lv.get_name())
    retval = self.warningMessage(message)
    if retval == gtk.RESPONSE_NO:
        return False
    
    # unmount and remove from fstab
    if mountpoint != None:
        try:
            self.command_handler.unmount(mountpoint)
        except CommandError, e:
            self.errorMessage(e.getMessage())
            return False
    if fstab_mountpoint != None:
        Fstab.remove(lv.get_path())
    
    # finally remove lv
    try:
        self.command_handler.remove_lv(lv.get_path())
    except CommandError, e:
        self.errorMessage(e.getMessage())
        return False
    
    if reset_tree:
        apply(self.reset_tree_model, [lv.get_vg().get_name()])
    
    return True
  
  def on_rm_select_lvs(self, button):
    if self.section_list == None:
        return
    #check if list > 0
    lvs_to_remove = self.section_list[:]
    if len(lvs_to_remove) == 0:
        return
    vg = lvs_to_remove[0].get_vg()
    # check if all operations could be completed
    for lv in lvs_to_remove:
        if lv.has_snapshots():
            for snap in lv.get_snapshots():
                if snap not in lvs_to_remove:
                    self.errorMessage(UNABLE_TO_PROCESS_REQUEST + '\n' + _("Logical Volume \"%s\" has snapshots that are not selected for removal. They must be removed as well.") % lv.get_name())
                    return
    # remove snapshots first
    reload_lvm = False
    reset_tree_model = False
    for lv in lvs_to_remove[:]:
        if lv.is_snapshot():
            lvs_to_remove.remove(lv)
            if self.remove_lv(lv):
                # success
                reload_lvm = True
            else:
                # remove_lv failure
                origin = lv.get_snapshot_info()[0]
                if origin in lvs_to_remove:
                    msg = _("\"%s\", an origin of snapshot \"%s\", has been deleted from removal list.")
                    msg = msg % (origin.get_name(), lv.get_name())
                    self.simpleInfoMessage(msg)
                    lvs_to_remove.remove(origin)
    
    if reload_lvm:
        self.model_factory.reload()
        vg = self.model_factory.get_VG(vg.get_name())
        reset_tree_model = True
    # remove other lvs
    for lv in lvs_to_remove:
        if self.remove_lv(vg.get_lvs()[lv.get_name()]):
            reset_tree_model = True
    
    if reset_tree_model:
        self.clear_highlighted_sections()
        apply(self.reset_tree_model, [vg.get_name()])
  
  def on_rm_select_pvs(self, button):
      if self.section_list == None:
          return
      #need to check if list > 0
      if len(self.section_list) == 0:
          return
      # check if all operations could be completed
      for pv in self.section_list:
          for extent in pv.get_extent_blocks():
              extents_lv = extent.get_lv()
              if extents_lv.is_used():
                  error_message = None
                  if extents_lv.is_mirror_log or extents_lv.is_mirror_image:
                      error_message = _("Physical Volume \"%s\" contains extents belonging to a mirror. Mirrors are not migratable, so %s is not removable.")
                      error_message = error_message % (pv.get_path(), pv.get_path())
                  elif extents_lv.is_snapshot() or extents_lv.has_snapshots():
                      error_message = _("Physical Volume \"%s\" contains extents belonging to a snapshot or a snapshot's origin. Snapshots are not migratable, so %s is not removable.")
                      error_message = error_message % (pv.get_path(), pv.get_path())
                  if error_message != None:
                      self.errorMessage(UNABLE_TO_PROCESS_REQUEST + '\n' + error_message)
                      return
    
      # do the job
      reset_tree_model = False
      for pv in self.section_list:
          pvpath = pv.get_path()
          vgname = pv.get_vg().get_name()
          pv_to_remove = self.model_factory.get_VG(vgname).get_pvs()[pvpath]
          if self.remove_pv(pv_to_remove):
              # remove_pv migrates extents -> need to reload lvm data
              self.model_factory.reload()
              reset_tree_model = True
      
      selection = self.treeview.get_selection()
      model,iter = selection.get_selected()
      vg = model.get_value(iter, OBJ_COL)
      
      if reset_tree_model:
          self.clear_highlighted_sections()
          apply(self.reset_tree_model, [vg.get_name()])
  
  def on_new_lv(self, button):
      main_selection = self.treeview.get_selection()
      main_model, main_iter = main_selection.get_selected()
      main_path = main_model.get_path(main_iter)
      vg = main_model.get_value(main_iter, OBJ_COL)
      if len(vg.get_lvs().values()) == vg.get_max_lvs():
          self.errorMessage(EXCEEDED_MAX_LVS)
          return
      
      total_exts, used_exts, free_exts = vg.get_extent_total_used_free()
      if free_exts == 0:
          self.errorMessage(NOT_ENOUGH_SPACE_FOR_NEW_LV % vg.get_name())
          return
      
      dlg = LV_edit_props(None, vg, self.model_factory, self.command_handler)
      if dlg.run() == False:
          return
      
      apply(self.reset_tree_model,[vg.get_name()])
  
  def on_init_entity(self, button):
      selection = self.treeview.get_selection()
      model,iter = selection.get_selected()
      pv = model.get_value(iter, OBJ_COL)
      if self.initialize_entity(pv) == None:
          return
      apply(self.reset_tree_model, ['', '', pv.get_path()])
  
  def on_init_entity_from_menu(self, obj, dlg=None):
      if dlg == None:
          dlg = self.glade_xml.get_widget("init_block_device_dlg")
      label = self.glade_xml.get_widget("init_block_device_dlg_path")
      label.select_region(0, (-1))
      label.grab_focus()
      rc = dlg.run()
      dlg.hide()
      if rc == gtk.RESPONSE_APPLY:
          path = label.get_text().strip()
          target = follow_links_to_target(path)
          if target == None:
              self.errorMessage(_("The path you specified does not exist."))
              self.on_init_entity_from_menu(None, dlg)
              return
          else:
              o = execWithCapture('/bin/ls', ['/bin/ls', '-l', target])
              output = o.strip()
              if output[0] != 'b':
                  self.errorMessage(_("The path you specified is not a Block Device."))
                  self.on_init_entity_from_menu(None, dlg)
                  return
          pv = PhysicalVolume.PhysicalVolume(path, None, None, 0, 0, False, 0, 0)
          pv.set_path(path)
          self.glade_xml.get_widget("init_block_device_dlg_path").set_text('')
          if self.initialize_entity(pv) == None:
              self.glade_xml.get_widget("init_block_device_dlg_path").set_text(path)
              self.on_init_entity_from_menu(None, dlg)
          else:
              apply(self.reset_tree_model, ['', '', pv.get_path()])
      else:
          self.glade_xml.get_widget("init_block_device_dlg_path").set_text('')
  
  def initialize_entity(self, pv):
      path = pv.get_path()
      mountPoint = self.model_factory.getMountPoint(path)
      doFormat = False
      message = ''
      if mountPoint == None:
          fs = Filesystem.get_fs(path)
          if fs.name == Filesystem.get_filesystems()[0].name:
              fs = None
          if fs == None:
              if pv.needsFormat():
                  if pv.wholeDevice():
                      message = INIT_ENTITY % path
                  else:
                      # disabled until fdisk_wrapper gets into reliable shape
                      # doFormat = True
                      # message = INIT_ENTITY_FREE_SPACE % (pv.get_volume_size_string(), path)
                      return None
              else:
                  message = INIT_ENTITY % path
          else:
              message = INIT_ENTITY_FILESYSTEM % (path, fs.name, path)
      else:
          message = INIT_ENTITY_MOUNTED % (path, mountPoint, path)
      rc = self.warningMessage(message)
      if (rc == gtk.RESPONSE_NO):
          return None
      
      if mountPoint != None:
          try:
              self.command_handler.unmount(mountPoint)
          except CommandError, e:
              self.errorMessage(e.getMessage())
              return None
      
      if pv.needsFormat() and pv.wholeDevice():
          dialog = self.glade_xml.get_widget('whole_device_format_choice')
          label = self.glade_xml.get_widget('whole_device_format_choice_label')
          label.set_text(INIT_ENTITY_DEVICE_CHOICE % path) 
          rc = dialog.run()
          dialog.hide()
          if rc == gtk.RESPONSE_YES:
              doFormat = True
          elif rc == gtk.RESPONSE_NO:
              doFormat = False
          else:
              return None
      
      try:
          if doFormat:
              # format
              devpath = path
              path = self.model_factory.partition_UV(pv)
              # tell kernel to reread new partition table
              if self.command_handler.reread_partition_table(devpath) == False:
                  message = RESTART_COMPUTER % pv.getDevnames()[0]
                  self.errorMessage(message)
                  self.errorMessage(_("Initialization of %s failed") % pv.getDevnames()[0])
                  return None
              
          self.command_handler.initialize_entity(path)
      except CommandError, e:
          self.errorMessage(e.getMessage())
          return None
      return path
  
  def on_add_pv_to_vg(self, button):
      model = self.add_pv_to_vg_treeview.get_model()
      if model != None:
          model.clear()
      
      vg_list = self.model_factory.get_VGs()
      if len(vg_list) > 0:
          for vg in vg_list:
              iter = model.append()
              model.set(iter,
                        NAME_COL, vg.get_name(),
                        SIZE_COL, vg.get_size_total_used_free_string()[0])
      
      selection = self.treeview.get_selection()
      main_model, iter_val = selection.get_selected()
      pv = main_model.get_value(iter_val, OBJ_COL)
      label_string = ADD_PV_TO_VG_LABEL % pv.get_path()
      self.add_pv_to_vg_label.set_text(label_string)
      self.add_pv_to_vg_treeview.set_model(model)
      self.add_pv_to_vg_dlg.show()
  
  def add_pv_to_vg_delete_event(self, *args):
      self.add_pv_to_vg_dlg.hide()
      return True
  
  def on_ok_add_pv_to_vg(self, button):
      selection = self.treeview.get_selection()
      main_model, iter_val = selection.get_selected()
      pv = main_model.get_value(iter_val, OBJ_COL)
      
      selection = self.add_pv_to_vg_treeview.get_selection()
      model, iter = selection.get_selected()
      vgname = model.get_value(iter, NAME_COL)
      
      vg = self.model_factory.get_VG(vgname)
      
      #Check if this VG allows an Additional PV
      if vg.get_max_pvs() == len(vg.get_pvs().values()):
          self.errorMessage(EXCEEDED_MAX_PVS)
          self.add_pv_to_vg_dlg.hide()
          return
      
      try:
          self.command_handler.add_unalloc_to_vg(pv.get_path(), vgname)
      except CommandError, e:
          self.errorMessage(e.getMessage())
          return
      
      args = list()
      args.append(pv.get_path())
      apply(self.reset_tree_model, [vg.get_name()])
      
      self.add_pv_to_vg_dlg.hide()
  
  def on_cancel_add_pv_to_vg(self,button):
      self.add_pv_to_vg_dlg.hide()
  
  
  def setup_extend_vg_form(self):
      self.on_extend_vg_button = self.glade_xml.get_widget('on_extend_vg_button')
      self.on_extend_vg_button.connect("clicked",self.on_extend_vg)
      self.extend_vg_form = self.glade_xml.get_widget('extend_vg_form')
      self.extend_vg_form.connect("delete_event",self.extend_vg_delete_event)
      self.extend_vg_tree = self.glade_xml.get_widget('extend_vg_tree')
      self.extend_vg_label = self.glade_xml.get_widget('extend_vg_label')
      self.glade_xml.get_widget('on_ok_extend_vg').connect('clicked', self.on_ok_extend_vg)
      self.glade_xml.get_widget('on_cancel_extend_vg').connect('clicked',self.on_cancel_extend_vg)
      #set up columns for tree
      model = gtk.ListStore (gobject.TYPE_STRING,
                             gobject.TYPE_STRING,
                             gobject.TYPE_STRING,
                             gobject.TYPE_INT,
                             gobject.TYPE_PYOBJECT)
      
      self.extend_vg_tree.set_model(model)
      renderer1 = gtk.CellRendererText()
      column1 = gtk.TreeViewColumn(ENTITY_NAME,renderer1, text=0)
      self.extend_vg_tree.append_column(column1)
      renderer2 = gtk.CellRendererText()
      column2 = gtk.TreeViewColumn(ENTITY_SIZE,renderer2, text=1)
      self.extend_vg_tree.append_column(column2)
      renderer3 = gtk.CellRendererText()
      column3 = gtk.TreeViewColumn(ENTITY_TYPE,renderer3, markup=2)
      self.extend_vg_tree.append_column(column3)
      # set up multiselection
      self.extend_vg_tree.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
  
  def on_extend_vg(self, button):
      main_selection = self.treeview.get_selection()
      main_model,main_iter = main_selection.get_selected()
      main_path = main_model.get_path(main_iter)
      vg = main_model.get_value(main_iter, OBJ_COL)
      if vg.get_max_pvs() == len(vg.get_pvs().values()):
          self.errorMessage(EXCEEDED_MAX_PVS)
          return
      
      self.rebuild_extend_vg_tree()
      self.extend_vg_form.show()
  
  def on_ok_extend_vg(self, button):
      selection = self.extend_vg_tree.get_selection()
      if selection == None:
          self.extend_vg_form.hide() #cancel opp if OK clicked w/o selection
      
      #Now get name of VG to be extended...
      main_selection = self.treeview.get_selection()
      main_model,main_iter = main_selection.get_selected()
      main_path = main_model.get_path(main_iter)
      vg = main_model.get_value(main_iter, OBJ_COL)
      
      # handle selections
      model, treepathlist = selection.get_selected_rows()
      
      # check if pvs can be added to vg
      max_addable_pvs = vg.get_max_pvs() - len(vg.get_pvs().values())
      if max_addable_pvs < len(treepathlist):
          self.errorMessage(EXCEEDING_MAX_PVS % max_addable_pvs)
          return
      
      reset_tree_model = False
      for treepath in treepathlist:
          iter = model.get_iter(treepath)
          entity_path = model.get_value(iter, NAME_COL)
          entity_type = model.get_value(iter, VOL_TYPE_COL)
          if entity_type == UNINIT_VOL:  #First, initialize if necessary
              entity = model.get_value(iter, OBJ_COL)
              entity_path = self.initialize_entity(entity)
              if entity_path == None:
                  continue
          try:
              self.command_handler.add_unalloc_to_vg(entity_path, vg.get_name())
          except CommandError, e:
              self.errorMessage(e.getMessage())
              continue
          reset_tree_model = True
      
      self.extend_vg_form.hide()
      if reset_tree_model:
          apply(self.reset_tree_model, [vg.get_name()])
  
  def on_cancel_extend_vg(self, button):
      self.extend_vg_form.hide()
  
  def extend_vg_delete_event(self, *args):
      self.extend_vg_form.hide()
      return True
  
  def rebuild_extend_vg_tree(self):
    uv_string = "<span foreground=\"#ED1C2A\"><b>" + UNALLOCATED_PV + "</b></span>"
    iv_string = "<span foreground=\"#BBBBBB\"><b>" + UNINIT_DE + "</b></span>"
    model = self.extend_vg_tree.get_model()
    if model != None:
        model.clear()
    
    unallocated_vols = self.model_factory.query_unallocated()
    for vol in unallocated_vols:
        iter = model.append()
        model.set(iter,
                  NAME_COL, vol.get_path(),
                  SIZE_COL, vol.get_size_total_string(),
                  PATH_COL, uv_string,
                  VOL_TYPE_COL, UNALLOC_VOL,
                  OBJ_COL, vol)
    
    uninitialized_list = self.model_factory.query_uninitialized()
    for item in uninitialized_list:
        if item.initializable:
            iter = model.append()
            model.set(iter,
                      NAME_COL, item.get_path(),
                      SIZE_COL, item.get_size_total_string(),
                      PATH_COL, iv_string,
                      VOL_TYPE_COL,UNINIT_VOL,
                      OBJ_COL, item)
    
    selection = self.treeview.get_selection()
    main_model, iter_val = selection.get_selected()
    vg = main_model.get_value(iter_val, OBJ_COL)
    self.extend_vg_label.set_text(ADD_VG_LABEL % vg.get_name())
  
  def new_vg_delete_event(self, *args):
    self.new_vg_dlg.hide()
    return True
  
  def setup_misc_widgets(self):
      self.remove_unalloc_pv = self.glade_xml.get_widget('remove_unalloc_pv')
      self.remove_unalloc_pv.connect("clicked",self.on_remove_unalloc_pv)
      self.on_pv_rm_button = self.glade_xml.get_widget('on_pv_rm_button')
      self.on_pv_rm_button.connect("clicked",self.on_pv_rm)
      self.on_lv_rm_button = self.glade_xml.get_widget('on_lv_rm_button')
      self.on_lv_rm_button.connect("clicked",self.on_lv_rm)
      self.on_rm_select_lvs_button = self.glade_xml.get_widget('on_rm_select_lvs')
      self.on_rm_select_lvs_button.connect("clicked",self.on_rm_select_lvs)
      self.on_rm_select_pvs_button = self.glade_xml.get_widget('on_rm_select_pvs')
      self.on_rm_select_pvs_button.connect("clicked",self.on_rm_select_pvs)
      self.migrate_exts_button = self.glade_xml.get_widget('button27')
      self.migrate_exts_button.connect("clicked",self.on_migrate_exts)
      self.edit_lv_button = self.glade_xml.get_widget('button35')
      self.edit_lv_button.connect("clicked",self.on_edit_lv)
      self.create_snapshot_button = self.glade_xml.get_widget('create_snapshot_button')
      self.create_snapshot_button.connect("clicked",self.on_create_snapshot)
      
      # misc events
      self.glade_xml.get_widget("initialize_block_device1").connect('activate', self.on_init_entity_from_menu)
      
  
  def on_remove_unalloc_pv(self, button):
      selection = self.treeview.get_selection()
      model, iter = selection.get_selected()
      pv = model.get_value(iter, OBJ_COL)
      retval = self.warningMessage(CONFIRM_PVREMOVE % pv.get_path())
      if (retval == gtk.RESPONSE_NO):
          return
      else:
          try:
              self.command_handler.remove_pv(pv.get_path())
          except CommandError, e:
              self.errorMessage(e.getMessage())
              return
          apply(self.reset_tree_model, ['', '', pv.get_path()])
  
  def on_migrate_exts(self, button):
      selection = self.treeview.get_selection()
      model, iter = selection.get_selected()
      pv = model.get_value(iter, OBJ_COL)
      
      # get selected extents
      if self.section_list == None:
          self.simpleInfoMessage(_("Please select some extents first"))
          return
      if len(self.section_list) == 0:
          self.simpleInfoMessage(_("Please select some extents first"))
          return
      extents_from = self.section_list[:]
      
      # dialog
      dlg = self.migrate_exts_dlg(False, pv, extents_from)
      if dlg == None:
          return
      exts_from_structs = []
      for ext in extents_from:
          exts_from_structs.append(ext.get_start_size())
      try:
          self.command_handler.move_pv(pv.get_path(), exts_from_structs, dlg.get_data())
      except CommandError, e:
          self.errorMessage(e.getMessage())
      apply(self.reset_tree_model, [pv.get_vg().get_name()])
      return
  
  # removal - whether this is a migration or a removal operation
  def migrate_exts_dlg(self, removal, pv, exts):
      vg = pv.get_vg()
      needed_extents = 0
      for ext in exts:
          needed_extents = needed_extents + ext.get_start_size()[1]
      
      free_extents = 0
      pvs = []
      for p in vg.get_pvs().values():
          if pv != p:
              p_free_exts = p.get_extent_total_used_free()[2]
              if p_free_exts >= needed_extents:
                  pvs.append(p)
              free_extents = free_extents + p_free_exts
      if needed_extents > free_extents:
          self.errorMessage(_("There are not enough free extents to perform the necessary migration. Adding more physical volumes would solve the problem."))
          return None
      lvs = {}
      for ext in exts:
          lv = ext.get_lv()
          lvs[lv] = lv
      dlg = MigrateDialog(not removal, pvs, lvs.values())
      if not dlg.run():
          return None
      return dlg
  
  def on_edit_lv(self, button):
      selection = self.treeview.get_selection()
      model, iter = selection.get_selected()
      lv = model.get_value(iter, OBJ_COL)
      vg = lv.get_vg()
      dlg = LV_edit_props(lv, vg, self.model_factory, self.command_handler)
      if dlg.run() == False:
          return
      
      apply(self.reset_tree_model, [vg.get_name()])
  
  def on_create_snapshot(self, button):
      selection = self.treeview.get_selection()
      model, iter = selection.get_selected()
      lv = model.get_value(iter, OBJ_COL)
      vg = lv.get_vg()
      if vg.get_max_lvs() == len(vg.get_lvs().values()):
          self.errorMessage(EXCEEDED_MAX_LVS)
          return
      
      # checks
      if lv.is_snapshot():
          self.errorMessage(ALREADY_A_SNAPSHOT)
          return
      if lv.is_mirrored():
          self.errorMessage(CANNOT_SNAPSHOT_A_MIRROR)
          return
      t_exts, u_exts, f_exts = vg.get_extent_total_used_free()
      if f_exts == 0:
          self.errorMessage(NOT_ENOUGH_SPACE_FOR_NEW_LV % vg.get_name())
          return
      if self.command_handler.is_dm_snapshot_loaded() == False:
          self.errorMessage(NO_DM_SNAPSHOT)
          return
      
      dlg = LV_edit_props(lv, vg, self.model_factory, self.command_handler, True)
      if dlg.run() == False:
          return
      
      apply(self.reset_tree_model, [vg.get_name()])
      
      
      
  #######################################################
  ###Convenience Dialogs
  
  def warningMessage(self, message):
      dlg = gtk.MessageDialog(None, 0,
                              gtk.MESSAGE_WARNING,
                              gtk.BUTTONS_YES_NO,
                              message)
      dlg.show_all()
      rc = dlg.run()
      dlg.destroy()
      if (rc == gtk.RESPONSE_NO):
          return gtk.RESPONSE_NO
      elif (rc == gtk.RESPONSE_DELETE_EVENT):
          return gtk.RESPONSE_NO
      elif (rc == gtk.RESPONSE_CLOSE):
          return gtk.RESPONSE_NO
      elif (rc == gtk.RESPONSE_CANCEL):
          return gtk.RESPONSE_NO
      else:
          return rc
  
  def errorMessage(self, message):
      dlg = gtk.MessageDialog(None, 0,
                              gtk.MESSAGE_ERROR,
                              gtk.BUTTONS_OK,
                              message)
      dlg.show_all()
      rc = dlg.run()
      dlg.destroy()
      return rc
  
  def infoMessage(self, message):
      dlg = gtk.MessageDialog(None, 0,
                              gtk.MESSAGE_INFO,
                              gtk.BUTTONS_OK,
                              message)
      dlg.show_all()
      rc = dlg.run()
      dlg.destroy()
      return rc
  
  def simpleInfoMessage(self, message):
      dlg = gtk.MessageDialog(None, 0,
                              gtk.MESSAGE_INFO,
                              gtk.BUTTONS_OK,
                              message)
      dlg.show_all()
      rc = dlg.run()
      dlg.destroy()
      if (rc == gtk.RESPONSE_NO):
          return gtk.RESPONSE_NO
      elif (rc == gtk.RESPONSE_DELETE_EVENT):
          return gtk.RESPONSE_NO
      elif (rc == gtk.RESPONSE_CLOSE):
          return gtk.RESPONSE_NO
      elif (rc == gtk.RESPONSE_CANCEL):
          return gtk.RESPONSE_NO
      else:
          return rc
  
  def register_highlighted_sections(self, section_type, section_list):
      self.section_type = section_type
      self.section_list = section_list
  
  def clear_highlighted_sections(self):
      self.section_type = UNSELECTABLE_TYPE
      self.section_list = None
      
    

class MigrateDialog:
    
    def __init__(self, migrate, pvs, lvs):
        gladepath = 'migrate_extents.glade'
        if not os.path.exists(gladepath):
            gladepath = "%s/%s" % (INSTALLDIR, gladepath)
        gtk.glade.bindtextdomain(PROGNAME)
        self.glade_xml = gtk.glade.XML (gladepath, domain=PROGNAME)
        
        # fill out lv selection combobox
        self.lv_combo = gtk.combo_box_new_text()
        self.glade_xml.get_widget('lv_selection_container').pack_end(self.lv_combo)
        self.lv_combo.show()
        self.lv_combo.set_sensitive(False)
        for lv in lvs:
            self.lv_combo.append_text(lv.get_name())
        model = self.lv_combo.get_model()
        iter = model.get_iter_first()
        self.lv_combo.set_active_iter(iter)
        
        # fill out pv selection combobox
        pv_selection_container = self.glade_xml.get_widget('pv_selection_container')
        self.pv_combo = gtk.combo_box_new_text()
        pv_selection_container.pack_end(self.pv_combo)
        self.pv_combo.show()
        self.pv_combo.set_sensitive(False)
        if len(pvs) != 0:
            for p in pvs:
                self.pv_combo.append_text(p.get_path())
            model = self.pv_combo.get_model()
            iter = model.get_iter_first()
            self.pv_combo.set_active_iter(iter)
        else:
            pv_selection_container.hide()
        
        self.dlg = self.glade_xml.get_widget('dialog1')
        msg_label = self.glade_xml.get_widget('msg_label')
        self.dlg.set_title(_("Migrate extents"))
        if migrate:
            msg_label.hide()
        else:
            # remove
            self.glade_xml.get_widget('lv_selection_container').hide()
        
        # events
        self.glade_xml.get_widget('choose_pv_radio').connect('clicked', self.on_choose_pv_radio)
        self.glade_xml.get_widget('choose_lv_check').connect('clicked', self.on_choose_lv_check)
        
    
    def on_choose_pv_radio(self, obj1):
        if self.glade_xml.get_widget('choose_pv_radio').get_active():
            self.pv_combo.set_sensitive(True)
        else:
            self.pv_combo.set_sensitive(False)
    
    def on_choose_lv_check(self, obj1):
        if self.glade_xml.get_widget('choose_lv_check').get_active():
            self.lv_combo.set_sensitive(True)
        else:
            self.lv_combo.set_sensitive(False)
    
    def run(self):
        rc = self.dlg.run()
        self.dlg.hide()
        return rc == gtk.RESPONSE_OK
    
    # return [pv to migrate to, policy (0 - inherit, 1 - normal, 2 - contiguous, 3 - anywhere), lv to migrate from]
    def get_data(self):
        ret = []
        
        # migrate extents to
        if self.glade_xml.get_widget('choose_pv_radio').get_active() == True:
            iter = self.pv_combo.get_active_iter()
            ret.append(self.pv_combo.get_model().get_value(iter, 0))
        else:
            ret.append(None)
        
        if self.glade_xml.get_widget('radiobutton4').get_active():
            ret.append(0)
        elif self.glade_xml.get_widget('radiobutton5').get_active():
            ret.append(1)
        elif self.glade_xml.get_widget('radiobutton6').get_active():
            ret.append(2)
        else:
            ret.append(3)
        
        # lv to migrate from
        if self.glade_xml.get_widget('choose_lv_check').get_active():
            iter = self.lv_combo.get_active_iter()
            ret.append(self.lv_combo.get_model().get_value(iter, 0))
        else:
            ret.append(None)    
        
        return ret



class LV_edit_props:
    
    # set lv to None if new lv is to be created
    def __init__(self, lv, vg, model_factory, command_handler, snapshot=False):
        self.snapshot = snapshot
        if lv == None:
            self.new = True
            self.snapshot = False
        else:
            if self.snapshot:
                self.new = True
            else:
                self.new = False
        self.lv = lv
        self.vg = vg
        self.model_factory = model_factory
        self.command_handler = command_handler
        
        # available filesystems
        self.filesystems = dict()
        fss = Filesystem.get_filesystems()
        self.fs_none = fss[0]
        for fs in fss:
            self.filesystems[fs.name] = fs
        if self.new:
            if self.snapshot:
                self.fs = Filesystem.get_fs(self.lv.get_path())
                self.filesystems[self.fs.name] = self.fs    
            else:
                self.fs = self.fs_none
            self.mount_point = ''
            self.mount = False
            self.mount_at_reboot = False
        else:
            self.fs = Filesystem.get_fs(lv.get_path())
            if self.fs.name == self.fs_none.name:
                self.fs = self.fs_none
            else:
                self.filesystems.pop(self.fs_none.name)
            self.filesystems[self.fs.name] = self.fs
            self.mount_point = self.model_factory.getMountPoint(lv.get_path())
            self.mountpoint_at_reboot = Fstab.get_mountpoint(lv.get_path().strip())
            if self.mount_point == None:
                if self.mountpoint_at_reboot == None:
                    self.mount_point = ''
                else:
                    self.mount_point = self.mountpoint_at_reboot
                self.mount = False
            else:
                self.mount = True
            self.mount_at_reboot = (self.mountpoint_at_reboot != None)
        for fs_name in self.filesystems:
            self.filesystems[fs_name].set_clustered(vg.clustered())
        
        gladepath = 'lv_edit_props.glade'
        if not os.path.exists(gladepath):
            gladepath = "%s/%s" % (INSTALLDIR, gladepath)
        gtk.glade.bindtextdomain(PROGNAME)
        self.glade_xml = gtk.glade.XML (gladepath, domain=PROGNAME)
        self.dlg = self.glade_xml.get_widget('dialog1')
        
        self.size_units_combo = gtk.combo_box_new_text()
        self.glade_xml.get_widget('size_units_container').pack_end(self.size_units_combo)
        self.size_units_combo.show()
        
        self.filesys_combo = gtk.combo_box_new_text()
        self.glade_xml.get_widget('filesys_container').pack_start(self.filesys_combo)
        self.filesys_combo.show()
        self.fs_config_button = gtk.Button(_("Options"))
        self.glade_xml.get_widget('filesys_container').pack_end(self.fs_config_button)
        #self.fs_config_button.show()
        self.fs_config_button.hide()
        
    
    def run(self):
        need_reload = False
        
        self.setup_dlg()
        while True:
            rc = self.dlg.run()
            if rc == gtk.RESPONSE_REJECT:
                self.setup_dlg()
                continue
            elif rc == gtk.RESPONSE_OK:
                try:
                    if self.apply() == True:
                        need_reload = True
                        break
                except CommandError, e:
                    self.errorMessage(e.getMessage())
                    need_reload = True
                    break
            else:
                break
        self.dlg.hide()
        
        return need_reload
    
    def setup_dlg(self):
        # title
        if self.new:
            if self.snapshot:
                self.dlg.set_title(_("Create A Snapshot of %s") % self.lv.get_name())
            else:
                self.dlg.set_title(_("Create New Logical Volume"))
        else:
            if self.lv.is_snapshot():
                message = _("Edit %s, a Snapshot of %s")
                self.dlg.set_title(message % (self.lv.get_name(), self.lv.get_snapshot_info()[0].get_name()))
            else:
                self.dlg.set_title(_("Edit Logical Volume"))
        
        # lv name
        self.name_entry = self.glade_xml.get_widget('lv_name')
        if self.new:
            self.name_entry.set_text('')
        else:
            self.name_entry.set_text(self.lv.get_name())
        
        # revert button
        if self.new:
            self.glade_xml.get_widget('revert_button').hide()
        else:
            self.glade_xml.get_widget('revert_button').show()
        
        # lv properties
        # TODO: use ACCEPTABLE_STRIPE_SIZES
        stripe_size_combo = self.glade_xml.get_widget('stripe_size')
        model = stripe_size_combo.get_model()
        iter = model.get_iter_first()
        stripe_size_combo.set_active_iter(iter)
        if self.new:
            if self.snapshot:
                self.glade_xml.get_widget('lv_properties_frame').hide()
            else:
                self.glade_xml.get_widget('stripes_container').set_sensitive(False)
                stripe_size_combo = self.glade_xml.get_widget('stripe_size')
                model = stripe_size_combo.get_model()
                iter = model.get_iter_first()
                stripe_size_combo.set_active_iter(iter)
                max_stripes = len(self.vg.get_pvs())
                if max_stripes > 8:
                    max_stripes = 8
                self.glade_xml.get_widget('stripes_num').set_range(2, max_stripes)
                self.glade_xml.get_widget('stripes_num').set_update_policy(gtk.UPDATE_IF_VALID)
        else:
            if self.lv.is_snapshot():
                self.glade_xml.get_widget('lv_properties_frame').hide()
            else:
                self.glade_xml.get_widget('linear').hide()
                self.glade_xml.get_widget('striped').hide()
                self.glade_xml.get_widget('stripes_container').hide()
        
        # filesystem
        self.glade_xml.get_widget('filesys_container').remove(self.filesys_combo)
        self.filesys_combo = gtk.combo_box_new_text()
        self.glade_xml.get_widget('filesys_container').pack_start(self.filesys_combo)
        self.filesys_combo.show()
        self.filesys_combo.append_text(self.fs.name)
        for filesys in self.filesystems:
            if (self.fs.name != filesys) and self.filesystems[filesys].creatable:
                self.filesys_combo.append_text(filesys)
        model = self.filesys_combo.get_model()
        iter = model.get_iter_first()
        self.filesys_combo.set_active_iter(iter)
        self.filesys_show_hide()
        if self.snapshot:
            self.glade_xml.get_widget('filesys_container').set_sensitive(False)
        elif not self.new:
            if self.lv.is_snapshot():
                self.glade_xml.get_widget('filesys_container').set_sensitive(False)
        self.mountpoint_entry = self.glade_xml.get_widget('mount_point')
        if self.new:
            self.mountpoint_entry.set_text('')
        else:
            self.mountpoint_entry.set_text(self.mount_point)
        self.glade_xml.get_widget('mount').set_active(self.mount)
        self.glade_xml.get_widget('mount_at_reboot').set_active(self.mount_at_reboot)
        self.on_mount_changed(None)
        
        # size
        self.size_scale = self.glade_xml.get_widget('size_scale')
        self.size_entry = self.glade_xml.get_widget('size_entry')
        self.glade_xml.get_widget('size_units_container').remove(self.size_units_combo)
        self.size_units_combo = gtk.combo_box_new_text()
        self.glade_xml.get_widget('size_units_container').pack_end(self.size_units_combo)
        self.size_units_combo.show()
        for unit in [EXTENTS, GIGABYTES, MEGABYTES, KILOBYTES]:
            self.size_units_combo.append_text(unit)
        model = self.size_units_combo.get_model()

        # set active item in combo box 
        iter = model.get_iter_first()
        active_iter = iter
        while (iter != None):
            if model.get_value(iter, 0) == GIGABYTES:
                active_iter = iter
            iter = model.iter_next(iter)
        
        self.size_units_combo.set_active_iter(active_iter)
        self.extent_size = self.vg.get_extent_size()
        self.size_lower = 1
        if self.new:
            self.size = 0
        else:
            self.size = self.lv.get_extent_total_used_free()[0]
        self.size_upper = self.vg.get_extent_total_used_free()[2] + self.size
        self.set_size_new(self.size)
        self.update_size_limits()
        self.change_size_units()
        
        # mirroring
        if self.new:
            self.mirror_to_diff_hds = None # prompt for option
            self.glade_xml.get_widget('enable_mirroring').set_active(False)
        else:
            already_mirrored = self.lv.is_mirrored()
            if already_mirrored:
                self.mirror_to_diff_hds = False # mirror not resizable => don't care for now
            else:
                self.mirror_to_diff_hds = None # prompt for option
            self.glade_xml.get_widget('enable_mirroring').set_active(already_mirrored)
        self.mirror_to_diff_hds = False
        if MIRRORING_UI_SUPPORT == False:
            if self.new:
                self.glade_xml.get_widget('enable_mirroring').hide()
            else:
                self.glade_xml.get_widget('lv_properties_frame').hide()
        
        # set up mirror limits
        self.on_enable_mirroring(None)
        
        # events
        self.fs_config_button.connect('clicked', self.on_fs_config)
        self.filesys_combo.connect('changed', self.on_fs_change)
        self.size_units_combo.connect('changed', self.on_units_change)
        self.size_scale.connect('adjust-bounds', self.on_size_change_scale)
        self.size_entry.connect('focus-out-event', self.on_size_change_entry)
        self.glade_xml.get_widget('linear').connect('clicked', self.on_linear_changed)
        self.glade_xml.get_widget('enable_mirroring').connect('clicked', self.on_enable_mirroring)
        self.glade_xml.get_widget('striped').connect('clicked', self.on_striped_changed)
        self.glade_xml.get_widget('mount').connect('clicked', self.on_mount_changed)
        self.glade_xml.get_widget('mount_at_reboot').connect('clicked', self.on_mount_changed)
        self.glade_xml.get_widget('use_remaining_button').connect('clicked', self.on_use_remaining)
        
    
    def on_linear_changed(self, obj):
        if self.glade_xml.get_widget('linear').get_active() == False:
            self.glade_xml.get_widget('enable_mirroring').set_active(False)
            self.glade_xml.get_widget('enable_mirroring').set_sensitive(False)
            return
        else:
            self.glade_xml.get_widget('stripes_container').set_sensitive(False)
            self.glade_xml.get_widget('enable_mirroring').set_sensitive(True)
    def on_striped_changed(self, obj):
        if self.glade_xml.get_widget('striped').get_active() == False:
            return
        pv_list = self.vg.get_pvs()
        if len(pv_list) < 2:  #striping is not an option
            self.errorMessage(CANT_STRIPE_MESSAGE)
            self.glade_xml.get_widget('linear').set_active(True)
            return
        else:
            self.glade_xml.get_widget('stripes_container').set_sensitive(True)
    def on_enable_mirroring(self, obj):
        if self.glade_xml.get_widget('enable_mirroring').get_active() == False:
            self.update_size_limits()
            return
        # is mirroring supported by lvm version in use?
        if self.model_factory.is_mirroring_supported() == False:
            self.errorMessage(_("Underlying Logical Volume Management does not support mirroring"))
            self.glade_xml.get_widget('enable_mirroring').set_active(False)
            self.update_size_limits()
            return
        # check if lv is striped - no mirroring
        if not self.new:
            if self.lv.is_striped():
                self.errorMessage(_("Striped Logical Volumes cannot be mirrored."))
                self.glade_xml.get_widget('enable_mirroring').set_active(False)
                self.update_size_limits()
                return
        # check if lv is origin - no mirroring
        if not self.new:
            if self.lv.has_snapshots() and not self.lv.is_mirrored():
                self.errorMessage(_("Logical Volumes with associated snapshots cannot be mirrored yet."))
                self.glade_xml.get_widget('enable_mirroring').set_active(False)
                self.update_size_limits()
                return
        
        # mirror images placement: diff HDs or anywhere
        if self.mirror_to_diff_hds == None: # prompt
            rc = self.questionMessage(_("The primary purpose of mirroring is to protect data in the case of hard drive failure. Do you want to place mirror images onto different hard drives?"))
            if rc == gtk.RESPONSE_YES:
                self.mirror_to_diff_hds = True
            else:
                self.mirror_to_diff_hds = False
        
        max_mirror_size = self.__get_max_mirror_data(self.vg)[0]
        if max_mirror_size == 0:
            if self.mirror_to_diff_hds:
                self.errorMessage(_("Less than 3 hard drives are available with free space. Disabling mirroring."))
                self.glade_xml.get_widget('enable_mirroring').set_active(False)
                self.update_size_limits()
                return
            else:
                self.errorMessage(_("There must be free space on at least three Physical Volumes to enable mirroring"))
                self.glade_xml.get_widget('enable_mirroring').set_active(False)
                self.update_size_limits()
                return
        
        if self.size_new > max_mirror_size:
            if self.new:
                self.update_size_limits(max_mirror_size)
                self.infoMessage(_("The size of the Logical Volume has been adjusted to the maximum available size for mirrors."))
                self.size_entry.select_region(0, (-1))
                self.size_entry.grab_focus()
            else:
                if self.lv.is_mirrored() == False:
                    message = _("There is not enough free space to add mirroring. Reduce size of Logical Volume to at most %s, or add Physical Volumes.")
                    iter = self.size_units_combo.get_active_iter()
                    units = self.size_units_combo.get_model().get_value(iter, 0)
                    reduce_to_string = str(self.__get_num(max_mirror_size)) + ' ' + units
                    self.errorMessage(message % reduce_to_string)
                    self.glade_xml.get_widget('enable_mirroring').set_active(False)
                    self.size_entry.select_region(0, (-1))
                    self.size_entry.grab_focus()
                else:
                    self.update_size_limits()
        else:
            self.update_size_limits(max_mirror_size)
    
    def __get_max_mirror_data(self, vg):
        # copy pvs into list
        free_list = []
        for pv in vg.get_pvs().values():
            free_extents = pv.get_extent_total_used_free()[2]
            # add extents of current LV
            if not self.new:
                if self.lv.is_mirrored():
                    lvs_to_match = self.lv.get_segments()[0].get_images()
                else:
                    lvs_to_match = [self.lv]
                for ext in pv.get_extent_blocks():
                    if ext.get_lv() in lvs_to_match:
                        free_extents = free_extents + ext.get_start_size()[1]
            if free_extents != 0:
                free_list.append((free_extents, pv))
        
        if self.mirror_to_diff_hds:
            ## place mirror onto different hds ##
            # group pvs into hd groups
            devices = {}
            for t in free_list:
                pv = t[1]
                pv_free = t[0]
                device_name_in_list = None
                for devname in pv.getDevnames():
                    if devname in devices.keys():
                        device_name_in_list = devname
                if device_name_in_list == None:
                    if len(pv.getDevnames()) == 0:
                        # no known devnmaes
                        devices[pv.get_path()] = [pv_free, [[pv_free, pv]]]
                    else:
                        # not in the list
                        devices[pv.getDevnames()[0]] = [pv_free, [[pv_free, pv]]]
                else:
                    devices[device_name_in_list][0] = devices[device_name_in_list][0] + pv_free
                    devices[device_name_in_list][1].append([pv_free, pv])
            free_list = devices.values()
            if len(devices.keys()) < 3:
                return 0, [], [], []
            # sort free_list
            for i in range(len(free_list) - 1, 0, -1):
                for j in range(0, i):
                    if free_list[j][0] < free_list[j + 1][0]:
                        tmp = free_list[j + 1]
                        free_list[j + 1] = free_list[j]
                        free_list[j] = tmp
            # sort within free_list
            for t in free_list:
                sort_me = t[1]
                for i in range(len(sort_me) - 1, 0, -1):
                    for j in range(0, i):
                        if sort_me[j][0] < sort_me[j + 1][0]:
                            tmp = sort_me[j + 1]
                            sort_me[j + 1] = sort_me[j]
                            sort_me[j] = tmp
            # create list of largest partitions
            largest_list = []
            for t in free_list:
                t_largest_size = t[1][0][0]
                t_largest_pv = t[1][0][1]
                largest_list.append([t_largest_size, t_largest_pv])
            # sort largest list
            for i in range(len(largest_list) - 1, 0, -1):
                for j in range(0, i):
                    if largest_list[j][0] < largest_list[j + 1][0]:
                        tmp = largest_list[j + 1]
                        largest_list[j + 1] = largest_list[j]
                        largest_list[j] = tmp
            return largest_list[1][0], [largest_list[0][1]], [largest_list[1][1]], [largest_list.pop()[1]]
        else:
            ## place mirror anywhere, even on the same hd :( ##
            if len(free_list) < 3:
                return 0, [], [], []
            # sort
            for i in range(len(free_list) - 1, 0, -1):
                for j in range(0, i):
                    if free_list[j][0] < free_list[j + 1][0]:
                        tmp = free_list[j + 1]
                        free_list[j + 1] = free_list[j]
                        free_list[j] = tmp
            # remove smallest one for log
            log = free_list.pop()[1]
            
            # place pvs into buckets of similar size
            buck1, s1 = [free_list[0][1]], free_list[0][0]
            buck2, s2 = [free_list[1][1]], free_list[1][0]
            for t in free_list[2:]:
                if s1 < s2:
                    s1 = s1 + t[0]
                    buck1.append(t[1])
                else:
                    s2 = s2 + t[0]
                    buck2.append(t[1])
            
            max_m_size = 0
            if s1 < s2:
                max_m_size = s1
            else:
                max_m_size = s2
            
            return max_m_size, buck1, buck2, [log]
        
    def on_mount_changed(self, obj):
        m1 = self.glade_xml.get_widget('mount').get_active()
        m2 = self.glade_xml.get_widget('mount_at_reboot').get_active()
        if m1 or m2:
            self.mountpoint_entry.set_sensitive(True)
        else:
            self.mountpoint_entry.set_sensitive(False)
    
    def on_fs_config(self, button):
        pass
    
    def on_fs_change(self, obj):
        self.filesys_show_hide()
        # go thru on_enable_mirroring() to get to update_size_limits,
        # that in turn disables resizing if fs doesn't support that
        self.on_enable_mirroring(None)
    
    def filesys_show_hide(self):
        iter = self.filesys_combo.get_active_iter()
        filesys = self.filesystems[self.filesys_combo.get_model().get_value(iter, 0).decode("utf-8")]
        
        if filesys.editable:
            self.fs_config_button.set_sensitive(True)
        else:
            self.fs_config_button.set_sensitive(False)
        
        if filesys.mountable:
            self.glade_xml.get_widget('mountpoint_container').set_sensitive(True)
            self.glade_xml.get_widget('mount_container').set_sensitive(True)
        else:
            self.glade_xml.get_widget('mount').set_active(False)
            self.glade_xml.get_widget('mount_at_reboot').set_active(False)
            self.glade_xml.get_widget('mountpoint_container').set_sensitive(False)
            self.glade_xml.get_widget('mount_container').set_sensitive(False)
        
    
    def update_size_limits(self, upper=None):
        iter = self.filesys_combo.get_active_iter()
        filesys = self.filesystems[self.filesys_combo.get_model().get_value(iter, 0).decode("utf-8")]
        fs_resizable = (filesys.extendable_online or filesys.extendable_offline or filesys.reducible_online or filesys.reducible_offline)
        
        if not self.new:
            if fs_resizable:
                self.glade_xml.get_widget('fs_not_resizable').hide()
            else:
                self.glade_xml.get_widget('fs_not_resizable').show()
            
            if self.lv.has_snapshots():
                self.glade_xml.get_widget('origin_not_resizable').show()
                self.glade_xml.get_widget('free_space_label').hide()
                self.size_scale.set_sensitive(False)
                self.size_entry.set_sensitive(False)
                self.glade_xml.get_widget('use_remaining_button').set_sensitive(False)
                self.glade_xml.get_widget('remaining_space_label').hide()
                return
            elif self.lv.is_mirrored():
                if self.glade_xml.get_widget('enable_mirroring').get_active():
                    self.glade_xml.get_widget('mirror_not_resizable').show()
                    self.glade_xml.get_widget('free_space_label').hide()
                    self.size_scale.set_sensitive(False)
                    self.size_entry.set_sensitive(False)
                    self.glade_xml.get_widget('use_remaining_button').set_sensitive(False)
                    self.glade_xml.get_widget('remaining_space_label').hide()
                    self.set_size_new(self.size)
                    return
                else:
                    self.glade_xml.get_widget('mirror_not_resizable').hide()
                    self.glade_xml.get_widget('free_space_label').show()
                    self.size_scale.set_sensitive(True)
                    self.size_entry.set_sensitive(True)
                    self.glade_xml.get_widget('use_remaining_button').set_sensitive(True)
                    self.glade_xml.get_widget('remaining_space_label').show()
        
        self.size_lower = 1
        if upper == None:
            self.size_upper = self.vg.get_extent_total_used_free()[2] + self.size
        else:
            self.size_upper = upper
        
        as_new = self.new
        fs_change = not (filesys == self.fs)
        if fs_change:
            as_new = True
        
        if as_new:
            self.glade_xml.get_widget('fs_not_resizable').hide()
            self.glade_xml.get_widget('free_space_label').show()
            self.size_scale.set_sensitive(True)
            self.size_entry.set_sensitive(True)
            self.glade_xml.get_widget('use_remaining_button').set_sensitive(True)
            self.glade_xml.get_widget('remaining_space_label').show()
        else:
            if not (filesys.extendable_online or filesys.extendable_offline):
                self.size_upper = self.size
            if not (filesys.reducible_online or filesys.reducible_offline):
                self.size_lower = self.size
            
            if fs_resizable:
                self.glade_xml.get_widget('fs_not_resizable').hide()
                self.glade_xml.get_widget('free_space_label').show()
                self.size_scale.set_sensitive(True)
                self.size_entry.set_sensitive(True)
                self.glade_xml.get_widget('use_remaining_button').set_sensitive(True)
                self.glade_xml.get_widget('remaining_space_label').show()
            else:
                self.glade_xml.get_widget('fs_not_resizable').show()
                self.glade_xml.get_widget('free_space_label').hide()
                self.size_scale.set_sensitive(False)
                self.size_entry.set_sensitive(False)
                self.glade_xml.get_widget('use_remaining_button').set_sensitive(False)
                self.glade_xml.get_widget('remaining_space_label').hide()
                
                # set old size value
                self.set_size_new(self.size)
                
        if self.size_lower < self.size_upper:
            self.glade_xml.get_widget('size_scale_container').set_sensitive(True)
        else:
            self.glade_xml.get_widget('size_scale_container').set_sensitive(False)
        
        # update values to be within limits
        self.change_size_units()
        self.set_size_new(self.size_new)
    
    def on_units_change(self, obj):
        self.change_size_units()
    
    def change_size_units(self):
        lower = self.__get_num(self.size_lower)
        upper = self.__get_num(self.size_upper)
        
        size_beg_label = self.glade_xml.get_widget('size_beg')
        size_beg_label.set_text(str(lower))
        size_end_label = self.glade_xml.get_widget('size_end')
        size_end_label.set_text(str(upper))
        
        if self.size_lower < self.size_upper:
            self.size_scale.set_range(lower, upper)
        
        self.set_size_new(self.size_new)
    
    def update_remaining_space_label(self):
        iter = self.size_units_combo.get_active_iter()
        units = self.size_units_combo.get_model().get_value(iter, 0)
        rem = self.size_upper - self.size_new
        rem_vg = self.vg.get_extent_total_used_free()[2]
        if self.glade_xml.get_widget('enable_mirroring').get_active():
            mirror_log_size = 1
            rem_vg = rem_vg + (self.size - self.size_new) * 2 - mirror_log_size
        else:
            rem_vg = rem_vg - self.size_new + self.size
        string_vg = REMAINING_SPACE_VG + str(self.__get_num(rem_vg)) + ' ' + units
        self.glade_xml.get_widget('free_space_label').set_text(string_vg)
        string = REMAINING_SPACE_AFTER + str(self.__get_num(rem)) + ' ' + units
        self.glade_xml.get_widget('remaining_space_label').set_text(string)
    
    def on_use_remaining(self, obj):
        self.set_size_new(self.size_upper)
    
    def on_size_change_scale(self, obj1, obj2):
        size = self.size_scale.get_value()
        self.set_size_new(self.__get_extents(size))
    
    def on_size_change_entry(self, obj1, obj2):
        size_text = self.size_entry.get_text()
        size_float = 0.0
        try:  ##In case gibberish is entered into the size field...
            size_float = float(size_text)
        except ValueError, e:
            self.size_entry.set_text(str(self.__get_num(self.size_new)))
            return False
        self.set_size_new(self.__get_extents(size_float))
        return False
    def set_size_new(self, exts):
        size = exts
        if size > self.size_upper:
            size = self.size_upper
        elif size < self.size_lower:
            size = self.size_lower
        self.size_new = size
        size_units = self.__get_num(size)
        self.size_entry.set_text(str(size_units))
        self.size_scale.set_value(size_units)
        self.update_remaining_space_label()
    def __get_extents(self, num):
        iter = self.size_units_combo.get_active_iter()
        units = self.size_units_combo.get_model().get_value(iter, 0)
        if units == EXTENTS:
            return int(num)
        elif units == GIGABYTES:
            num = int(num * 1024 * 1024 * 1024 / self.extent_size)
        elif units == MEGABYTES:
            num = int(num * 1024 * 1024 / self.extent_size)
        elif units == KILOBYTES:
            num = int(num * 1024 / self.extent_size)
        if num < 1:
            num = 1
        return num
    def __get_num(self, extents):
        iter = self.size_units_combo.get_active_iter()
        units = self.size_units_combo.get_model().get_value(iter, 0)
        if units == EXTENTS:
            return int(extents)
        elif units == GIGABYTES:
            val = extents * self.extent_size / 1024.0 / 1024.0 / 1024.0
        elif units == MEGABYTES:
            val = extents * self.extent_size / 1024.0 / 1024.0
        elif units == KILOBYTES:
            val = extents * self.extent_size / 1024.0
        string = '%.2f' % float(val)
        return float(string)
    
    def apply(self):
        name_new = self.name_entry.get_text().strip()
        size_new = int(self.size_new) # in extents
        
        iter = self.filesys_combo.get_active_iter()
        filesys_new = self.filesystems[self.filesys_combo.get_model().get_value(iter, 0).decode("utf-8")]
        
        if filesys_new.mountable:
            mount_new = self.glade_xml.get_widget('mount').get_active()
            mount_at_reboot_new = self.glade_xml.get_widget('mount_at_reboot').get_active()
            mountpoint_new = self.mountpoint_entry.get_text().strip()
        else:
            mount_new = False
            mount_at_reboot_new = False 
            mountpoint_new = ''
        
        mirrored_new = self.glade_xml.get_widget('enable_mirroring').get_active()
        striped = self.glade_xml.get_widget('striped').get_active()
        stripe_size_combo = self.glade_xml.get_widget('stripe_size')
        iter = stripe_size_combo.get_active_iter()
        stripe_size = int(stripe_size_combo.get_model().get_value(iter, 0))
        stripes_num = int(self.glade_xml.get_widget('stripes_num').get_value_as_int())
        
        # TODO
        fs_options_changed = False
        
        
        # validation Ladder
        # name
        if name_new == '':
            self.errorMessage(MUST_PROVIDE_NAME)
            return False
        # illegal characters
        invalid_lvname_message = ''
        if re.match('snapshot', name_new) or re.match('pvmove', name_new):
            invalid_lvname_message = _("Names beginning with \"snapshot\" or \"pvmove\" are reserved keywords.")
        elif re.search('_mlog', name_new) or re.search('_mimage', name_new):
            invalid_lvname_message = _("Names containing \"_mlog\" or \"_mimage\" are reserved keywords.")
        elif name_new[0] == '-':
            invalid_lvname_message = _("Names beginning with a \"-\" are invalid")
        elif name_new == '.' or name_new == '..':
            invalid_lvname_message = _("Name can be neither \".\" nor \"..\"")
        else:
            for t in name_new:
                if t in string.ascii_letters + string.digits + '._-+':
                    continue
                elif t in string.whitespace:
                    invalid_lvname_message = _("Whitespaces are not allowed in Logical Volume names")
                    break
                else:
                    invalid_lvname_message = _("Invalid character \"%s\" in Logical Volume name") % t
                    break
        if invalid_lvname_message != '':
            self.errorMessage(invalid_lvname_message)
            self.name_entry.select_region(0, (-1))
            self.name_entry.grab_focus()
            return False
        # Name must be unique for this VG
        for lv in self.vg.get_lvs().values():
            if lv.get_name() == name_new:
                if not self.new:
                    if self.lv.get_name() == name_new:
                        continue
                self.name_entry.select_region(0, (-1))
                self.name_entry.grab_focus()
                self.errorMessage(NON_UNIQUE_NAME % name_new)
                return False
        # check mountpoint
        if mount_new or mount_at_reboot_new:
            if mountpoint_new == '':
                self.errorMessage(_("Please specify mount point"))
                return False
            # create folder if it doesn't exist
            if os.path.exists(mountpoint_new) == False:  ###stat mnt point
                rc = self.questionMessage(BAD_MNT_POINT % mountpoint_new)
                if (rc == gtk.RESPONSE_YES):  #create mount point
                    try:
                        os.mkdir(mountpoint_new)
                    except OSError, e:
                        self.errorMessage(BAD_MNT_CREATION % mountpoint_new)
                        self.mountpoint_entry.set_text('')
                        return False
                else:
                    self.mountpoint_entry.select_region(0, (-1))
                    return False
        
        # action
        if self.new:
            ### new LV ###
            
            # create LV
            new_lv_command_set = {}
            new_lv_command_set[NEW_LV_NAME_ARG] = name_new
            new_lv_command_set[NEW_LV_VGNAME_ARG] = self.vg.get_name()
            new_lv_command_set[NEW_LV_UNIT_ARG] = EXTENT_IDX
            new_lv_command_set[NEW_LV_SIZE_ARG] = size_new
            new_lv_command_set[NEW_LV_IS_STRIPED_ARG] = striped
            new_lv_command_set[NEW_LV_MIRRORING] = mirrored_new
            if striped == True:
                new_lv_command_set[NEW_LV_STRIPE_SIZE_ARG] = stripe_size
                new_lv_command_set[NEW_LV_NUM_STRIPES_ARG] = stripes_num
            new_lv_command_set[NEW_LV_SNAPSHOT] = self.snapshot
            if self.snapshot:
                new_lv_command_set[NEW_LV_SNAPSHOT_ORIGIN] = self.lv.get_path()
            pvs_to_create_at = []
            if mirrored_new:
                size, b1, b2, l1 = self.__get_max_mirror_data(self.vg)
                pvs_to_create_at = b1[:]
                for pv in b2:
                    pvs_to_create_at.append(pv)
                for pv in l1:
                    pvs_to_create_at.append(pv)
            self.command_handler.new_lv(new_lv_command_set, pvs_to_create_at)
            
            lv_path = self.model_factory.get_logical_volume_path(name_new, self.vg.get_name())
            
            # make filesystem
            if not self.snapshot:
                try:
                    filesys_new.create(lv_path)
                except CommandError, e:
                    self.command_handler.remove_lv(lv_path)
                    raise e
            
            # mount
            if mount_new:
                self.command_handler.mount(lv_path, mountpoint_new, filesys_new.fsname)
            if mount_at_reboot_new:
                Fstab.add(None, lv_path, mountpoint_new, filesys_new.fsname)
        else:
            ### edit LV ###
            
            rename = name_new != self.lv.get_name()
            filesys_change = (filesys_new != self.fs)
            ext2_to_ext3 = (filesys_new.name == Filesystem.ext3().name) and (self.fs.name == Filesystem.ext2().name)
            if ext2_to_ext3:
                retval = self.questionMessage(_("Do you want to upgrade ext2 to ext3 preserving data on Logical Volume?"))
                if (retval == gtk.RESPONSE_NO):
                    ext2_to_ext3 = False
            
            snapshot = None
            if self.lv.is_snapshot():
                snapshot = self.lv.get_snapshot_info()[0]
            
            resize = (size_new != self.size)
            extend = (size_new > self.size)
            reduce = (size_new < self.size)
            
            # remove mirror if not needed anymore
            if self.lv.is_mirrored() and not mirrored_new:
                self.command_handler.remove_mirroring(self.lv.get_path())
            
            # DEBUGING: check if resizing is posible
            #if extend:
            #    if self.command_handler.extend_lv(self.lv.get_path(), size_new, True) == False:
            #        retval = self.infoMessage(_("fixme: resizing not possible"))
            #        return False
            #elif reduce:
            #    if self.command_handler.reduce_lv(self.lv.get_path(), size_new, True) == False:
            #        retval = self.infoMessage(_("fixme: resizing not possible"))
            #        return False
            
            mounted = self.mount
            unmount = False
            unmount_prompt = True
            if rename or filesys_change or mount_new == False:
                unmount = True
            if resize and self.lv.is_mirrored():
                unmount = True

            lv_open = False
            if len(self.lv.get_attr()) >= 6 and self.lv.get_attr()[5] == "o":
              lv_open = True

            if lv_open and mounted == False:
              ## LV is probably exported to another computer(s)
              retval = self.errorMessage(_("Logical volume is not mounted but is in use. Please close all applications using this device (eg iscsi)"))
              return False

            if filesys_change and self.fs.name!=self.fs_none.name and not ext2_to_ext3:
                retval = self.warningMessage(_("Changing the filesystem will destroy all data on the Logical Volume! Are you sure you want to proceed?"))
                if (retval == gtk.RESPONSE_NO):
                    return False
                unmount_prompt = False
            else:
                if not snapshot:
                    if extend and mounted and (not self.fs.extendable_online):
                        unmount = True
                    if reduce and mounted and (not self.fs.reducible_online):
                        unmount = True
            
            # unmount if needed
            if unmount and mounted:
                if unmount_prompt:
                    retval = self.warningMessage(UNMOUNT_PROMPT % (self.lv.get_path(), self.mount_point))
                    if (retval == gtk.RESPONSE_NO):
                        return False
                self.command_handler.unmount(self.mount_point)
                mounted = False
            
            # rename
            if rename:
                self.command_handler.rename_lv(self.vg.get_name(), self.lv.get_name(), name_new)
            lv_path = self.model_factory.get_logical_volume_path(name_new, self.vg.get_name())
            lv_path_old = self.lv.get_path()
            
            # resize lv
            if resize:
                if (filesys_change and not ext2_to_ext3) or snapshot:
                    # resize LV only
                    if size_new > self.size:
                        self.command_handler.extend_lv(lv_path, size_new)
                    else:
                        self.command_handler.reduce_lv(lv_path, size_new)
                else:
                    # resize lv and filesystem
                    if size_new > self.size:
                        # resize LV first
                        self.command_handler.extend_lv(lv_path, size_new)
                        # resize FS
                        try:
                            if mounted:
                                if self.fs.extendable_online:
                                    self.fs.extend_online(lv_path)
                                else:
                                    self.command_handler.unmount(self.mount_point)
                                    mounted = False
                                    self.fs.extend_offline(lv_path)
                            else:
                                if self.fs.extendable_offline:
                                    self.fs.extend_offline(lv_path)
                                else:
                                    # mount temporarily
                                    tmp_mountpoint = '/tmp/tmp_mountpoint'
                                    while os.access(tmp_mountpoint, os.F_OK):
                                        tmp_mountpoint = tmp_mountpoint + '1'
                                    os.mkdir(tmp_mountpoint)
                                    self.command_handler.mount(lv_path, tmp_mountpoint, Filesystem.get_fs(lv_path).fsname)
                                    self.fs.extend_online(lv_path)
                                    self.command_handler.unmount(tmp_mountpoint)
                                    os.rmdir(tmp_mountpoint)
                        except:
                            # revert LV size
                            self.command_handler.reduce_lv(lv_path, self.size)
                            raise
                    else:
                        # resize FS first
                        new_size_bytes = size_new * self.extent_size
                        if mounted:
                            if self.fs.reducible_online:
                                self.fs.reduce_online(lv_path, new_size_bytes)
                            else:
                                self.command_handler.unmount(self.mount_point)
                                mounted = False
                                self.fs.reduce_offline(lv_path, new_size_bytes)
                        else:
                            if self.fs.reducible_offline:
                                self.fs.reduce_offline(lv_path, new_size_bytes)
                            else:
                                # mount temporarily
                                tmp_mountpoint = '/tmp/tmp_mountpoint'
                                while os.access(tmp_mountpoint, os.F_OK):
                                    tmp_mountpoint = tmp_mountpoint + '1'
                                os.mkdir(tmp_mountpoint)
                                self.command_handler.mount(lv_path, tmp_mountpoint)
                                self.fs.reduce_online(lv_path, new_size_bytes)
                                self.command_handler.unmount(tmp_mountpoint)
                                os.rmdir(tmp_mountpoint)
                        # resize LV
                        self.command_handler.reduce_lv(lv_path, size_new)
            
            
            # add mirror if needed
            if not self.lv.is_mirrored() and mirrored_new:
                # first reload lvm_data so that resizing info is known
                self.model_factory.reload()
                self.lv = self.model_factory.get_VG(self.lv.get_vg().get_name()).get_lvs()[name_new]
                # make room for mirror (free some pvs of main image's extents)
                pvlist_from_make_room = self.__make_room_for_mirror(self.lv)
                if pvlist_from_make_room == None:
                    # migration not performed, continue process with no mirroring
                    self.infoMessage(_("Mirror not created. Completing remaining tasks."))
                else:
                    # create mirror
                    self.infoMessage(_('Underlaying LVM doesn\'t support addition of mirrors to existing Logical Volumes. Completing remaining tasks.'))
                    #self.command_handler.add_mirroring(self.lv.get_path(), pvlist_from_make_room)
                    pass
            
            # fs options
            if fs_options_changed and not filesys_change:
                self.fs.change_options(lv_path)
            
            # change FS
            if filesys_change:
                if ext2_to_ext3:
                    self.fs.upgrade(lv_path)
                else:
                    filesys_new.create(lv_path)
            
            # mount
            fsname = self.fs.fsname
            if filesys_change:
                fsname = filesys_new.fsname
            if mount_new and not mounted:
                self.command_handler.mount(lv_path, mountpoint_new, fsname)
            fstab_op = Fstab.get_mount_options(lv_path_old)
            if mount_at_reboot_new:
                # Add new entry; if mount_at_reboot was not set before
                # then all fstab_op[] will be None, so default options
                # should be set
                if (fstab_op[Fstab.OPTIONS] != None):
                    Fstab.add(lv_path_old, lv_path, mountpoint_new, fsname, fstab_op[Fstab.OPTIONS], fstab_op[Fstab.DUMP], fstab_op[Fstab.FSCK])
                else:
                    Fstab.add(lv_path_old, lv_path, mountpoint_new, fsname)
            else:
              Fstab.remove(lv_path_old)
                
        return True
    
    # return list of pvs to use for mirror, or None on failure
    def __make_room_for_mirror(self, lv):
        t, bucket1, bucket2, logs = self.__get_max_mirror_data(lv.get_vg())
        return_pvlist = bucket1[:]
        for pv in bucket2:
            return_pvlist.append(pv)
        for pv in logs:
            return_pvlist.append(pv)
        
        structs = self.__get_structs_for_ext_migration(lv)
        if len(structs) == 0:
            # nothing to be migrated
            return return_pvlist
        
        # extents need moving :(
        string = ''
        for struct in structs:
            string = string + '\n' + struct[0].get_path()
            string = string + ':' + str(struct[1]) + '-' + str(struct[1] + struct[2] - 1)
            string = string + '   -> ' + struct[3].get_path()
        rc = self.questionMessage(_("In order to add mirroring, some extents need to be migrated.") + '\n' + string + '\n' + _("Do you want to migrate specified extents?"))
        if rc == gtk.RESPONSE_YES:
            for struct in structs:
                pv_from = struct[0]
                ext_start = struct[1]
                size = struct[2]
                pv_to = struct[3]
                self.command_handler.move_pv(pv_from.get_path(), 
                                             [(ext_start, size)],
                                             [pv_to.get_path(), None, lv.get_path()])
            return return_pvlist
        else:
            return None
    # return [[pv_from, ext_start, size, pv_to], ...]
    def __get_structs_for_ext_migration(self, lv):
        t, bucket1, bucket2, logs = self.__get_max_mirror_data(lv.get_vg())
        
        # pick bucket to move lv to
        if self.__get_extent_count_in_bucket(lv, bucket1) < self.__get_extent_count_in_bucket(lv, bucket2):
            bucket_to = bucket2
        else:
            bucket_to = bucket1
        bucket_from = []
        for pv in lv.get_vg().get_pvs().values():
            if pv not in bucket_to:
                bucket_from.append(pv)
        
        structs = []
        bucket_to_i = 0
        pv_to = bucket_to[bucket_to_i]
        free_exts = pv_to.get_extent_total_used_free()[2]
        for pv_from in bucket_from:
            for ext_block in pv_from.get_extent_blocks():
                if ext_block.get_lv() != lv:
                    continue
                block_start, block_size = ext_block.get_start_size()
                while block_size != 0:
                    if block_size >= free_exts:
                        structs.append([pv_from, block_start, free_exts, pv_to])
                        block_start = block_start + free_exts
                        block_size = block_size - free_exts
                        # get next pv_to from bucket_to
                        bucket_to_i = bucket_to_i + 1
                        if bucket_to_i == len(bucket_to):
                            # should be done
                            return structs
                        pv_to = bucket_to[bucket_to_i]
                        free_exts = pv_to.get_extent_total_used_free()[2]
                    else:
                        structs.append([pv_from, block_start, block_size, pv_to])
                        block_start = block_start + block_size
                        block_size = block_size - block_size
                        free_exts = free_exts - block_size
        return structs
    
    def __get_extent_count_in_bucket(self, lv, bucket_pvs):
        ext_count = 0
        for pv in bucket_pvs:
            for ext_block in pv.get_extent_blocks():
                if ext_block.get_lv() == lv:
                    ext_count = ext_count + ext_block.get_start_size()[1]
        return ext_count
    
    def errorMessage(self, message):
        dlg = gtk.MessageDialog(None, 0,
                                gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
                                message)
        dlg.show_all()
        rc = dlg.run()
        dlg.destroy()
        return rc
    
    def infoMessage(self, message):
        dlg = gtk.MessageDialog(None, 0,
                                gtk.MESSAGE_INFO, gtk.BUTTONS_OK,
                                message)
        dlg.show_all()
        rc = dlg.run()
        dlg.destroy()
        return rc
    
    def questionMessage(self, message):
        dlg = gtk.MessageDialog(None, 0,
                                gtk.MESSAGE_INFO, gtk.BUTTONS_YES_NO,
                                message)
        dlg.show_all()
        rc = dlg.run()
        dlg.destroy()
        if (rc == gtk.RESPONSE_NO):
            return gtk.RESPONSE_NO
        elif (rc == gtk.RESPONSE_DELETE_EVENT):
            return gtk.RESPONSE_NO
        elif (rc == gtk.RESPONSE_CLOSE):
            return gtk.RESPONSE_NO
        elif (rc == gtk.RESPONSE_CANCEL):
            return gtk.RESPONSE_NO
        else:
            return rc
    
    def warningMessage(self, message):
        dlg = gtk.MessageDialog(None, 0,
                                gtk.MESSAGE_WARNING, gtk.BUTTONS_YES_NO,
                                message)
        dlg.show_all()
        rc = dlg.run()
        dlg.destroy()
        if (rc == gtk.RESPONSE_NO):
            return gtk.RESPONSE_NO
        elif (rc == gtk.RESPONSE_DELETE_EVENT):
            return gtk.RESPONSE_NO
        elif (rc == gtk.RESPONSE_CLOSE):
            return gtk.RESPONSE_NO
        elif (rc == gtk.RESPONSE_CANCEL):
            return gtk.RESPONSE_NO
        else:
            return rc

        

Anon7 - 2021