1# Copyright (C) 2010, 2013 Red Hat, Inc.
2# Copyright (C) 2010 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 virtinst import log
8from virtinst import xmlutil
9
10from ..baseclass import vmmGObject
11
12
13class vmmLibvirtObject(vmmGObject):
14    __gsignals__ = {
15        "state-changed": (vmmGObject.RUN_FIRST, None, []),
16        "initialized": (vmmGObject.RUN_FIRST, None, [bool]),
17    }
18
19    _STATUS_ACTIVE = 1
20    _STATUS_INACTIVE = 2
21
22    def __init__(self, conn, backend, name, parseclass):
23        vmmGObject.__init__(self)
24        self._conn = conn
25        self._backend = backend
26        self._parseclass = parseclass
27        self._name = name
28
29        self.__initialized = False
30        self.__status = None
31
32        self._xmlobj = None
33        self._xmlobj_to_define = None
34        self._is_xml_valid = False
35
36        # These should be set by the child classes if necessary
37        self._inactive_xml_flags = 0
38        self._active_xml_flags = 0
39
40    @staticmethod
41    def log_redefine_xml_diff(obj, origxml, newxml):
42        if origxml == newxml:
43            log.debug("Redefine requested for %s, but XML didn't change!",
44                          obj)
45            return
46
47        diff = xmlutil.diff(origxml, newxml, "Original XML", "New XML")
48        log.debug("Redefining %s with XML diff:\n%s", obj, diff)
49
50    @staticmethod
51    def lifecycle_action(fn):
52        """
53        Decorator for object lifecycle actions like start, stop, delete.
54        Will make sure any necessary state is updated accordingly.
55        """
56        def newfn(self, *args, **kwargs):
57            ret = fn(self, *args, **kwargs)
58
59            # If events are supported, this is a no-op, but the event loop
60            # will trigger force_status_update, which will refresh_xml as well.
61            #
62            # If events aren't supported, the priority tick will call
63            # self.tick(), which will call force_status_update
64            poll_param = self._conn_tick_poll_param()  # pylint: disable=protected-access
65            tick_kwargs = {poll_param: True}
66            self.conn.schedule_priority_tick(**tick_kwargs)
67
68            return ret
69        return newfn
70
71    def __repr__(self):
72        # pylint: disable=arguments-differ
73        try:
74            name = self.get_name()
75        except Exception:
76            name = ""
77        return "<%s name=%s id=%s>" % (
78                self.__class__.__name__, name, hex(id(self)))
79
80    def _cleanup(self):
81        self._backend = None
82
83    def _get_conn(self):
84        return self._conn
85    conn = property(_get_conn)
86
87    def get_backend(self):
88        return self._backend
89
90    def is_domain(self):
91        return self.class_name() == "domain"
92    def is_network(self):
93        return self.class_name() == "network"
94    def is_pool(self):
95        return self.class_name() == "pool"
96    def is_nodedev(self):
97        return self.class_name() == "nodedev"
98
99    def change_name_backend(self, newbackend):
100        # Used for changing the backing object after a rename
101        self._backend = newbackend
102
103    def define_name(self, newname):
104        oldname = self.get_xmlobj().name
105
106        self.ensure_latest_xml()
107        xmlobj = self._make_xmlobj_to_define()
108        if xmlobj.name == newname:
109            return  # pragma: no cover
110
111        log.debug("Changing %s name from %s to %s",
112                      self, oldname, newname)
113        origxml = xmlobj.get_xml()
114        xmlobj.name = newname
115        newxml = xmlobj.get_xml()
116
117        try:
118            self._name = newname
119            self.conn.rename_object(self, origxml, newxml)
120        except Exception:  # pragma: no cover
121            self._name = oldname
122            raise
123        finally:
124            self.__force_refresh_xml()
125
126
127    #############################################################
128    # Functions that should probably be overridden in sub class #
129    #############################################################
130
131    def _XMLDesc(self, flags):
132        raise NotImplementedError()
133    def class_name(self):
134        raise NotImplementedError()
135    def _conn_tick_poll_param(self):
136        # The parameter name for conn.tick() object polling. So
137        # for vmmDomain == "pollvm"
138        raise NotImplementedError()
139
140    def reports_stats(self):
141        return False
142    def _using_events(self):
143        return False
144    def _get_backend_status(self):
145        raise NotImplementedError()
146
147    def _define(self, xml):  # pragma: no cover
148        ignore = xml
149        return
150
151    def delete(self, force=True):  # pragma: no cover
152        ignore = force
153
154    def get_name(self):
155        return self._name
156
157    def tick(self, stats_update=True):
158        ignore = stats_update
159        self._refresh_status()
160
161    def _init_libvirt_state(self):
162        self.tick()
163
164    def init_libvirt_state(self):
165        """
166        Function called by vmmConnection to populate initial state when
167        a new object appears.
168        """
169        if self.__initialized:
170            return  # pragma: no cover
171
172        initialize_failed = False
173        try:
174            if self.config.CLITestOptions.object_denylist == self._name:
175                raise RuntimeError("fake initialization error")
176
177            self._init_libvirt_state()
178        except Exception:  # pragma: no cover
179            log.debug("Error initializing libvirt state for %s", self,
180                exc_info=True)
181            initialize_failed = True
182
183        self.__initialized = True
184        self.idle_emit("initialized", initialize_failed)
185
186
187    ###################
188    # Status handling #
189    ###################
190
191    def _get_status(self):
192        return self.__status
193
194    def is_active(self):
195        # vmmDomain overwrites this since it has more fine grained statuses
196        return self._get_status() == self._STATUS_ACTIVE
197
198    def run_status(self):
199        if self.is_active():
200            return _("Active")
201        return _("Inactive")
202
203    def _refresh_status(self, newstatus=None, cansignal=True):
204        """
205        Grab the object status/active state from libvirt, and if the
206        status has changed, update the XML cache. Typically called from
207        object tick functions for manually updating the object state.
208
209        :param newstatus: Used by vmmDomain as a small optimization to
210            avoid polling info() twice
211        :param cansignal: If True, this function will signal state-changed
212            if required.
213        :returns: True if status changed, false otherwise
214        """
215        if (self._using_events() and
216            self.__status is not None):
217            return False
218
219        if newstatus is None:
220            newstatus = self._get_backend_status()
221        status = newstatus
222        if status == self.__status:
223            return False
224        self.__status = status
225
226        self.ensure_latest_xml(nosignal=True)
227        if cansignal:
228            self.idle_emit("state-changed")
229        return True
230
231
232    ##################
233    # Public XML API #
234    ##################
235
236    def recache_from_event_loop(self):
237        """
238        Updates the VM status and XML, because we received an event from
239        libvirt's event implementations. That's the only time this should
240        be used.
241
242        We refresh status and XML because they are tied together in subtle
243        ways, like runtime XML changing when a VM is started.
244        """
245        try:
246            self.__force_refresh_xml(nosignal=True)
247            # status = None forces a signal to be emitted
248            self.__status = None
249            self._refresh_status()
250        except Exception as e:
251            # If we hit an exception here, it's often that the object
252            # disappeared, so request the poll loop to be updated
253            log.debug("Error refreshing %s from events: %s", self, e)
254            poll_param = self._conn_tick_poll_param()
255            if poll_param:
256                kwargs = {"force": True, poll_param: True}
257                log.debug("Scheduling priority tick with: %s", kwargs)
258                self.conn.schedule_priority_tick(**kwargs)
259
260    def ensure_latest_xml(self, nosignal=False):
261        """
262        Refresh XML if it isn't up to date, basically if we aren't using
263        events.
264        """
265        if (self._using_events() and
266            self._xmlobj and
267            self._is_xml_valid):
268            return
269        self.__force_refresh_xml(nosignal=nosignal)
270
271    def __force_refresh_xml(self, nosignal=False):
272        """
273        Force an xml update. Signal 'state-changed' if domain xml has
274        changed since last refresh
275
276        :param nosignal: If true, don't send state-changed. Used by
277            callers that are going to send it anyways.
278        """
279        origxml = None
280        if self._xmlobj:
281            origxml = self._xmlobj.get_xml()
282
283        self._invalidate_xml()
284        active_xml = self._XMLDesc(self._active_xml_flags)
285        self._xmlobj = self._parseclass(self.conn.get_backend(),
286            parsexml=active_xml)
287        self._is_xml_valid = True
288
289        if not nosignal and origxml != active_xml:
290            self.idle_emit("state-changed")
291
292    def get_xmlobj(self, inactive=False, refresh_if_nec=True):
293        """
294        Get object xml, return it wrapped in a virtinst object.
295        If cached xml is invalid, update.
296
297        :param inactive: Return persistent XML, not the running config.
298            No effect if domain is not running. Use this flag
299            if the XML will be used for redefining a guest
300        :param refresh_if_nec: Check if XML is out of date, and if so,
301            refresh it (default behavior). Skipping a refresh is
302            useful to prevent updating xml in the tick loop when
303            it's not that important (disk/net stats)
304        """
305        if inactive:
306            # If inactive XML requested, always return a fresh object even if
307            # the current object is inactive XML (like when the domain is
308            # stopped). Callers that request inactive are basically expecting
309            # a new copy.
310            inactive_xml = self._XMLDesc(self._inactive_xml_flags)
311            return self._parseclass(self.conn.get_backend(),
312                parsexml=inactive_xml)
313
314        if (self._xmlobj is None or
315            (refresh_if_nec and not self._is_xml_valid)):
316            self.ensure_latest_xml()
317
318        return self._xmlobj
319
320    @property
321    def xmlobj(self):
322        return self.get_xmlobj()
323
324    def get_xml_to_define(self):
325        """
326        Return the raw inactive XML we would use to alter/define an
327        object. Used by the xmleditor UI
328        """
329        return self._make_xmlobj_to_define().get_xml()
330
331    def define_xml(self, xml):
332        """
333        Define the passed in XML, and log a diff against the current XML.
334        Generally subclasses should use _redefine_xmlobj with higher
335        level wrappers, but this is needed for the XML editor
336        """
337        origxml = self.get_xml_to_define()
338        newxml = xml
339        self._redefine_xml_internal(origxml, newxml)
340
341
342    #########################
343    # Internal XML routines #
344    #########################
345
346    def _invalidate_xml(self):
347        """
348        Mark cached XML as invalid. Subclasses may extend this
349        to invalidate any specific caches of their own
350        """
351        # While for events we do want to clear cached XML values like
352        # _name, the XML is never invalid.
353        self._is_xml_valid = self._using_events()
354
355    def _make_xmlobj_to_define(self):
356        """
357        Build an xmlobj that should be used for defining new XML.
358
359        Most subclasses shouldn't touch this, but vmmDomainVirtinst needs to.
360        """
361        return self.get_xmlobj(inactive=True)
362
363    def _redefine_xml_internal(self, origxml, newxml):
364        self.log_redefine_xml_diff(self, origxml, newxml)
365
366        self._define(newxml)
367        if self._using_events():
368            return
369
370        self.ensure_latest_xml(nosignal=True)
371        self.idle_emit("state-changed")
372
373    def _redefine_xmlobj(self, xmlobj):
374        """
375        Redefine the passed xmlobj, which should be generated with
376        self._make_xmlobj_to_define() and which has accumulated edits
377        from UI fields.
378
379        Most subclasses shouldn't alter this, but vmmDomainVirtinst needs to.
380        """
381        origxml = self._make_xmlobj_to_define().get_xml()
382        newxml = xmlobj.get_xml()
383        self._redefine_xml_internal(origxml, newxml)
384