1# Copyright (C) 2008, 2013, 2014 Red Hat, Inc. 2# Copyright (C) 2008 Cole Robinson <crobinso@redhat.com> 3# 4# This work is licensed under the GNU GPLv2 or later. 5# See the COPYING file in the top-level directory. 6 7from gi.repository import Gtk 8 9from virtinst import log 10from virtinst import StorageVolume 11 12from .lib import uiutil 13from .asyncjob import vmmAsyncJob 14from .baseclass import vmmGObjectUI 15from .xmleditor import vmmXMLEditor 16 17 18class vmmCreateVolume(vmmGObjectUI): 19 __gsignals__ = { 20 "vol-created": (vmmGObjectUI.RUN_FIRST, None, [object, object]), 21 } 22 23 def __init__(self, conn, parent_pool): 24 vmmGObjectUI.__init__(self, "createvol.ui", "vmm-create-vol") 25 self.conn = conn 26 27 self._parent_pool = parent_pool 28 self._name_hint = None 29 self._storage_browser = None 30 31 self._xmleditor = vmmXMLEditor(self.builder, self.topwin, 32 self.widget("details-box-align"), 33 self.widget("details-box")) 34 self._xmleditor.connect("xml-requested", 35 self._xmleditor_xml_requested_cb) 36 37 self.builder.connect_signals({ 38 "on_vmm_create_vol_delete_event": self.close, 39 "on_vol_cancel_clicked": self.close, 40 "on_vol_create_clicked": self._create_clicked_cb, 41 42 "on_vol_name_changed": self._vol_name_changed_cb, 43 "on_vol_format_changed": self._vol_format_changed_cb, 44 "on_backing_browse_clicked": self._browse_backing_clicked_cb, 45 }) 46 self.bind_escape_key_close() 47 48 self._init_state() 49 50 51 ####################### 52 # Standard UI methods # 53 ####################### 54 55 def show(self, parent): 56 try: 57 parent_xml = self._parent_pool.xmlobj.get_xml() 58 except Exception: # pragma: no cover 59 log.debug("Error getting parent_pool xml", exc_info=True) 60 parent_xml = None 61 62 log.debug("Showing new volume wizard for parent_pool=\n%s", 63 parent_xml) 64 self._reset_state() 65 self.topwin.set_transient_for(parent) 66 self.topwin.present() 67 68 def close(self, ignore1=None, ignore2=None): 69 log.debug("Closing new volume wizard") 70 self.topwin.hide() 71 if self._storage_browser: 72 self._storage_browser.close() 73 self.set_modal(False) 74 return 1 75 76 def _cleanup(self): 77 self.conn = None 78 self._parent_pool = None 79 self._xmleditor.cleanup() 80 self._xmleditor = None 81 82 if self._storage_browser: 83 self._storage_browser.cleanup() 84 self._storage_browser = None 85 86 87 ############## 88 # Public API # 89 ############## 90 91 def set_name_hint(self, hint): 92 self._name_hint = hint 93 94 def set_modal(self, modal): 95 self.topwin.set_modal(bool(modal)) 96 97 def set_parent_pool(self, pool): 98 self._parent_pool = pool 99 100 101 ########### 102 # UI init # 103 ########### 104 105 def _init_state(self): 106 format_list = self.widget("vol-format") 107 format_model = Gtk.ListStore(str, str) 108 format_list.set_model(format_model) 109 uiutil.init_combo_text_column(format_list, 1) 110 for fmt in ["raw", "qcow2"]: 111 format_model.append([fmt, fmt]) 112 113 def _reset_state(self): 114 self._xmleditor.reset_state() 115 116 vol = self._make_stub_vol() 117 118 hasformat = vol.supports_format() 119 uiutil.set_grid_row_visible(self.widget("vol-format"), hasformat) 120 uiutil.set_list_selection(self.widget("vol-format"), 121 self.conn.get_default_storage_format()) 122 123 self.widget("vol-name").set_text(self._default_vol_name() or "") 124 self.widget("vol-name").grab_focus() 125 self.widget("vol-name").emit("changed") 126 127 self.widget("backing-store").set_text("") 128 self.widget("vol-nonsparse").set_active( 129 not self._should_default_sparse()) 130 self._show_sparse() 131 self._show_backing() 132 self.widget("backing-expander").set_expanded(False) 133 134 pool_avail = int(self._parent_pool.get_available() / 1024 / 1024 / 1024) 135 default_cap = 20 136 self.widget("vol-capacity").set_range(0.1, 1000000) 137 self.widget("vol-capacity").set_value(min(default_cap, pool_avail)) 138 139 self.widget("vol-parent-info").set_markup( 140 _("<b>%(volume)s's</b> available space: %(size)s") % { 141 "volume": self._parent_pool.get_name(), 142 "size": self._parent_pool.get_pretty_available(), 143 }) 144 145 146 ################### 147 # Helper routines # 148 ################### 149 150 def _get_config_format(self): 151 if not self.widget("vol-format").get_visible(): 152 return None 153 return uiutil.get_list_selection(self.widget("vol-format")) 154 155 def _default_vol_name(self): 156 hint = self._name_hint or "vol" 157 suffix = self._default_suffix() 158 ret = "" 159 try: 160 ret = StorageVolume.find_free_name( 161 self.conn.get_backend(), 162 self._parent_pool.get_backend(), 163 hint, suffix=suffix) 164 if ret and suffix: 165 ret = ret.rsplit(".", 1)[0] 166 except Exception: # pragma: no cover 167 log.exception("Error finding a default vol name") 168 169 return ret 170 171 def _default_suffix(self): 172 vol = self._make_stub_vol() 173 if vol.file_type != vol.TYPE_FILE: 174 return "" 175 return StorageVolume.get_file_extension_for_format( 176 self._get_config_format()) 177 178 def _should_default_sparse(self): 179 return self._get_config_format() == "qcow2" 180 181 def _can_sparse(self): 182 dtype = self._parent_pool.xmlobj.get_disk_type() 183 return dtype == StorageVolume.TYPE_FILE 184 185 def _show_sparse(self): 186 uiutil.set_grid_row_visible( 187 self.widget("vol-nonsparse"), self._can_sparse()) 188 189 def _can_backing(self): 190 if self._parent_pool.get_type() == "logical": 191 return True 192 if self._get_config_format() == "qcow2": 193 return True 194 return False 195 196 def _show_backing(self): 197 uiutil.set_grid_row_visible( 198 self.widget("backing-expander"), self._can_backing()) 199 200 def _browse_file(self): 201 if self._storage_browser is None: 202 def cb(src, text): 203 ignore = src 204 self.widget("backing-store").set_text(text) 205 206 from .storagebrowse import vmmStorageBrowser 207 self._storage_browser = vmmStorageBrowser(self.conn) 208 self._storage_browser.set_finish_cb(cb) 209 self._storage_browser.topwin.set_modal(self.topwin.get_modal()) 210 self._storage_browser.set_browse_reason( 211 self.config.CONFIG_DIR_IMAGE) 212 213 self._storage_browser.show(self.topwin) 214 215 def _show_err(self, info, details=None): 216 self.err.show_err(info, details, modal=self.topwin.get_modal()) 217 218 219 ################### 220 # Object building # 221 ################### 222 223 def _make_stub_vol(self, xml=None): 224 vol = StorageVolume(self.conn.get_backend(), parsexml=xml) 225 vol.pool = self._parent_pool.get_backend() 226 return vol 227 228 def _build_xmlobj_from_xmleditor(self): 229 xml = self._xmleditor.get_xml() 230 log.debug("Using XML from xmleditor:\n%s", xml) 231 return self._make_stub_vol(xml=xml) 232 233 def _build_xmlobj_from_ui(self): 234 name = self.widget("vol-name").get_text() 235 suffix = self.widget("vol-name-suffix").get_text() 236 volname = name + suffix 237 fmt = self._get_config_format() 238 cap = self.widget("vol-capacity").get_value() 239 nonsparse = self.widget("vol-nonsparse").get_active() 240 backing = self.widget("backing-store").get_text() 241 242 alloc = 0 243 if nonsparse: 244 alloc = cap 245 246 vol = self._make_stub_vol() 247 vol.name = volname 248 vol.capacity = (cap * 1024 * 1024 * 1024) 249 vol.allocation = (alloc * 1024 * 1024 * 1024) 250 if backing: 251 vol.backing_store = backing 252 if fmt: 253 vol.format = fmt 254 return vol 255 256 def _build_xmlobj(self, check_xmleditor): 257 try: 258 xmlobj = self._build_xmlobj_from_ui() 259 if check_xmleditor and self._xmleditor.is_xml_selected(): 260 xmlobj = self._build_xmlobj_from_xmleditor() 261 return xmlobj 262 except Exception as e: 263 self.err.show_err(_("Error building XML: %s") % str(e)) 264 265 266 ################## 267 # Object install # 268 ################## 269 270 def _pool_refreshed_cb(self, pool, volname): 271 vol = pool.get_volume_by_name(volname) 272 self.emit("vol-created", pool, vol) 273 274 def _finish_cb(self, error, details, vol): 275 self.reset_finish_cursor() 276 277 if error: # pragma: no cover 278 error = _("Error creating vol: %s") % error 279 self._show_err(error, details=details) 280 return 281 self._parent_pool.connect("refreshed", 282 self._pool_refreshed_cb, vol.name) 283 self.idle_add(self._parent_pool.refresh) 284 self.close() 285 286 def _finish(self): 287 vol = self._build_xmlobj(check_xmleditor=True) 288 if not vol: 289 return 290 291 try: 292 vol.validate() 293 except Exception as e: 294 return self._show_err(_("Error validating volume: %s") % str(e)) 295 296 self.set_finish_cursor() 297 progWin = vmmAsyncJob(self._async_vol_create, [vol], 298 self._finish_cb, [vol], 299 _("Creating storage volume..."), 300 _("Creating the storage volume may take a " 301 "while..."), 302 self.topwin) 303 progWin.run() 304 305 def _async_vol_create(self, asyncjob, vol): 306 conn = self.conn.get_backend() 307 308 # Lookup different pool obj 309 newpool = conn.storagePoolLookupByName(self._parent_pool.get_name()) 310 vol.pool = newpool 311 312 meter = asyncjob.get_meter() 313 log.debug("Starting background vol creation.") 314 vol.install(meter=meter) 315 log.debug("vol creation complete.") 316 317 318 ################ 319 # UI listeners # 320 ################ 321 322 def _xmleditor_xml_requested_cb(self, src): 323 xmlobj = self._build_xmlobj(check_xmleditor=False) 324 self._xmleditor.set_xml(xmlobj and xmlobj.get_xml() or "") 325 326 def _vol_format_changed_cb(self, src): 327 self._show_sparse() 328 self.widget("vol-nonsparse").set_active( 329 not self._should_default_sparse()) 330 self._show_backing() 331 self.widget("vol-name").emit("changed") 332 333 def _vol_name_changed_cb(self, src): 334 text = src.get_text() 335 336 suffix = self._default_suffix() 337 if "." in text: 338 suffix = "" 339 self.widget("vol-name-suffix").set_text(suffix) 340 341 def _browse_backing_clicked_cb(self, src): 342 self._browse_file() 343 344 def _create_clicked_cb(self, src): 345 self._finish() 346