1# Copyright (C) 2008, 2013 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
7import time
8
9from virtinst import log
10from virtinst import pollhelpers
11from virtinst import StoragePool, StorageVolume
12
13from .libvirtobject import vmmLibvirtObject
14
15
16def _pretty_bytes(val):
17    val = int(val)
18    if val > (1024 * 1024 * 1024):
19        return "%2.2f GiB" % (val / (1024.0 * 1024.0 * 1024.0))
20    else:
21        return "%2.2f MiB" % (val / (1024.0 * 1024.0))
22
23
24POOL_TYPE_DESCS = {
25    StoragePool.TYPE_DIR: _("Filesystem Directory"),
26    StoragePool.TYPE_FS: _("Pre-Formatted Block Device"),
27    StoragePool.TYPE_NETFS: _("Network Exported Directory"),
28    StoragePool.TYPE_LOGICAL: _("LVM Volume Group"),
29    StoragePool.TYPE_DISK: _("Physical Disk Device"),
30    StoragePool.TYPE_ISCSI: _("iSCSI Target"),
31    StoragePool.TYPE_SCSI: _("SCSI Host Adapter"),
32    StoragePool.TYPE_MPATH: _("Multipath Device Enumerator"),
33    StoragePool.TYPE_GLUSTER: _("Gluster Filesystem"),
34    StoragePool.TYPE_RBD: _("RADOS Block Device/Ceph"),
35    StoragePool.TYPE_SHEEPDOG: _("Sheepdog Filesystem"),
36    StoragePool.TYPE_ZFS: _("ZFS Pool"),
37}
38
39
40class vmmStorageVolume(vmmLibvirtObject):
41    def __init__(self, conn, backend, key):
42        vmmLibvirtObject.__init__(self, conn, backend, key, StorageVolume)
43
44
45    ##########################
46    # Required class methods #
47    ##########################
48
49    def _conn_tick_poll_param(self):
50        return None  # pragma: no cover
51    def class_name(self):
52        return "volume"  # pragma: no cover
53
54    def _XMLDesc(self, flags):
55        try:
56            return self._backend.XMLDesc(flags)
57        except Exception as e:  # pragma: no cover
58            log.debug("XMLDesc for vol=%s failed: %s",
59                self._backend.key(), e)
60            raise
61
62    def _get_backend_status(self):
63        return self._STATUS_ACTIVE
64
65
66    ###########
67    # Actions #
68    ###########
69
70    def get_parent_pool(self):
71        name = self._backend.storagePoolLookupByVolume().name()
72        for pool in self.conn.list_pools():
73            if pool.get_name() == name:
74                return pool
75
76    def delete(self, force=True):
77        ignore = force
78        self._backend.delete(0)
79        self._backend = None
80
81
82    #################
83    # XML accessors #
84    #################
85
86    def get_key(self):
87        return self.get_xmlobj().key or ""
88    def get_target_path(self):
89        return self.get_xmlobj().target_path or ""
90    def get_format(self):
91        return self.get_xmlobj().format
92    def get_capacity(self):
93        return self.get_xmlobj().capacity
94
95    def get_pretty_capacity(self):
96        return _pretty_bytes(self.get_capacity())
97
98    def get_pretty_name(self, pooltype):
99        name = self.get_name()
100        if pooltype != "iscsi":
101            return name
102
103        key = self.get_key()
104        ret = name
105        if key:
106            ret += " (%s)" % key
107        return ret
108
109
110class vmmStoragePool(vmmLibvirtObject):
111    __gsignals__ = {
112        "refreshed": (vmmLibvirtObject.RUN_FIRST, None, [])
113    }
114
115    @staticmethod
116    def supports_volume_creation(pool_type, clone=False):
117        """
118        Returns if pool supports volume creation.  If @clone is set to True
119        returns if pool supports volume cloning (virVolCreateXMLFrom).
120        """
121        supported = [
122            StoragePool.TYPE_DIR,
123            StoragePool.TYPE_FS,
124            StoragePool.TYPE_NETFS,
125            StoragePool.TYPE_DISK,
126            StoragePool.TYPE_LOGICAL,
127            StoragePool.TYPE_RBD,
128        ]
129        if not clone:
130            supported.extend([
131                StoragePool.TYPE_SHEEPDOG,
132                StoragePool.TYPE_ZFS,
133            ])
134        return pool_type in supported
135
136    @staticmethod
137    def pretty_type(pool_type):
138        return POOL_TYPE_DESCS.get(pool_type, "%s pool" % pool_type)
139
140    @staticmethod
141    def list_types():
142        return sorted(list(POOL_TYPE_DESCS.keys()))
143
144    def __init__(self, conn, backend, key):
145        vmmLibvirtObject.__init__(self, conn, backend, key, StoragePool)
146
147        self._last_refresh_time = 0
148        self._volumes = None
149
150
151    ##########################
152    # Required class methods #
153    ##########################
154
155    def _conn_tick_poll_param(self):
156        return "pollpool"
157    def class_name(self):
158        return "pool"
159
160    def _XMLDesc(self, flags):
161        return self._backend.XMLDesc(flags)
162    def _define(self, xml):
163        return self.conn.define_pool(xml)
164    def _using_events(self):
165        return self.conn.using_storage_pool_events
166    def _get_backend_status(self):
167        return (bool(self._backend.isActive()) and
168                self._STATUS_ACTIVE or
169                self._STATUS_INACTIVE)
170
171    def _init_libvirt_state(self):
172        super()._init_libvirt_state()
173        if not self.conn.is_active():
174            # We only want to refresh a pool on initial conn startup,
175            # since the pools may be out of date. But if a storage pool
176            # shows up while the conn is connected, this means it was
177            # just 'defined' recently and doesn't need to be refreshed.
178            self.refresh(_from_object_init=True)
179        for vol in self.get_volumes():
180            vol.init_libvirt_state()
181
182    def _invalidate_xml(self):
183        vmmLibvirtObject._invalidate_xml(self)
184        self._volumes = None
185
186    def _cleanup(self):
187        vmmLibvirtObject._cleanup(self)
188        for vol in self._volumes:
189            vol.cleanup()
190        self._volumes = None
191
192
193    ###########
194    # Actions #
195    ###########
196
197    @vmmLibvirtObject.lifecycle_action
198    def start(self):
199        self._backend.create(0)
200
201    @vmmLibvirtObject.lifecycle_action
202    def stop(self):
203        self._backend.destroy()
204
205    @vmmLibvirtObject.lifecycle_action
206    def delete(self, force=True):
207        ignore = force
208        self._backend.undefine()
209        self._backend = None
210
211    def refresh(self, _from_object_init=False):
212        """
213        :param _from_object_init: Only used for the refresh() call from
214            _init_libvirt_state. Tells us to not refresh the XML, since
215            we just updated it.
216        """
217        if not self.is_active():
218            return  # pragma: no cover
219
220        self._backend.refresh(0)
221        if self._using_events() and not _from_object_init:
222            # If we are using events, we let the event loop trigger
223            # the cache update for us. Except if from init_libvirt_state,
224            # we want the update to be done immediately
225            return
226
227        self.refresh_pool_cache_from_event_loop(
228            _from_object_init=_from_object_init)
229
230    def refresh_pool_cache_from_event_loop(self, _from_object_init=False):
231        if not _from_object_init:
232            self.recache_from_event_loop()
233        self._update_volumes(force=True)
234        self.idle_emit("refreshed")
235        self._last_refresh_time = time.time()
236
237    def secs_since_last_refresh(self):
238        return time.time() - self._last_refresh_time
239
240
241    ###################
242    # Volume handling #
243    ###################
244
245    def get_volume_by_name(self, name):
246        for vol in self.get_volumes():
247            if vol.get_name() == name:
248                return vol
249
250    def get_volumes(self):
251        self._update_volumes(force=False)
252        return self._volumes[:]
253
254    def _update_volumes(self, force):
255        if not self.is_active():
256            self._volumes = []
257            return
258        if not force and self._volumes is not None:
259            return
260
261        keymap = dict((o.get_name(), o) for o in self._volumes or [])
262        def cb(obj, key):
263            return vmmStorageVolume(self.conn, obj, key)
264        (dummy1, dummy2, allvols) = pollhelpers.fetch_volumes(
265            self.conn.get_backend(), self.get_backend(), keymap, cb)
266        self._volumes = allvols
267
268
269    #########################
270    # XML/config operations #
271    #########################
272
273    def set_autostart(self, value):
274        self._backend.setAutostart(value)
275    def get_autostart(self):
276        return self._backend.autostart()
277
278    def get_type(self):
279        return self.get_xmlobj().type
280    def get_target_path(self):
281        return self.get_xmlobj().target_path or ""
282
283    def get_allocation(self):
284        return self.get_xmlobj().allocation
285    def get_available(self):
286        return self.get_xmlobj().available
287    def get_capacity(self):
288        return self.get_xmlobj().capacity
289
290    def get_pretty_allocation(self):
291        return _pretty_bytes(self.get_allocation())
292    def get_pretty_available(self):
293        return _pretty_bytes(self.get_available())
294