Vinzenz Feenstra has uploaded a new change for review. Change subject: Query interface for all VM fields ......................................................................
Query interface for all VM fields Change-Id: Iddb4657d738ca0406950d07b84a7c85721e80b78 Bug-Url: https://bugzilla.redhat.com/show_bug.cgi?id=1083070 Signed-off-by: Vinzenz Feenstra <vfeen...@redhat.com> --- A lib/vdsm/trackable.py M vdsm/API.py M vdsm/rpc/vdsmapi-schema.json M vdsm/virt/vm.py 4 files changed, 511 insertions(+), 0 deletions(-) git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/19/28819/1 diff --git a/lib/vdsm/trackable.py b/lib/vdsm/trackable.py new file mode 100644 index 0000000..7455025 --- /dev/null +++ b/lib/vdsm/trackable.py @@ -0,0 +1,223 @@ +# +# Copyright 2014 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Refer to the README and COPYING files for full details of the license +# +""" + Implementation for Trackable, a wrapper for objects whose values should be + trackable for changes +""" + +import collections +import threading + + +class ChangeCounter(object): + def __init__(self): + self._counter_lock = threading.Lock() + self._current = 0 + + def next(self): + with self._counter_lock: + self._current += 1 + return self._current + + +_counter = ChangeCounter() + + +class Trackable(object): + """ + Trackable implements common functionality of TrackableMapping and + TrackableSequence and it is not required to be used. + """ + def __init__(self): + super(Trackable, self).__init__() + self._last_changed = _counter.next() + self._item_last_changed = {} + self._tracked = None + self._watcher = None + self._track_depth = 0 + + def _set_item(self, key, value): + needs_update = True + if key in self._tracked: + old = self._tracked[key] + if isinstance(old, Trackable): + old._update(value) + # if an item is updated through _update it does not need to + # we are not propagating the update, this would bubble up on + # it's own + needs_update = False + elif old == value: + # Not updated, no change required + needs_update = False + else: + self._tracked.__setitem__(key, _wrap(value, self, + self._track_depth)) + else: + self._tracked.__setitem__(key, _wrap(value, self, + self._track_depth)) + if needs_update: + self._on_item_changed(key) + + def _on_item_removed(self, key): + changed = _counter.next() + self._last_changed = changed + if self._watcher: + self._on_subitem_changed(changed) + del self._item_last_changed[key] + + def _on_item_changed(self, key): + changed = _counter.next() + self._last_changed = changed + if self._watcher: + self._on_subitem_changed(changed) + self._item_last_changed[key] = changed + + def _on_subitem_changed(self, changed): + self._last_changed = changed + if self._watcher: + self._watcher._on_subitem_changed(changed) + + def changed(self, since, key=None): + if not key: + return self._last_changed > since + if isinstance(self._tracked.get(key, None), Trackable): + return self._tracked[key].changed(since=since) + return self._item_last_changed[key] > since + + def last_changed(self): + return self._last_changed + + +def _wrap(v, watcher, track_depth): + if track_depth == 0 or isinstance(v, Trackable): + + return v + elif isinstance(v, collections.MutableSequence): + return TrackableSequence(tracked=v, _watcher=watcher, + track_depth=track_depth - 1) + elif isinstance(v, collections.MutableMapping): + return TrackableMapping(tracked=v, _watcher=watcher, + track_depth=track_depth - 1) + return v + + +class TrackableSequence(collections.MutableSequence, Trackable): + """ + TrackableSequence implements a mutable sequence interface to + track changes to the elements in the list. + """ + def __init__(self, tracked=None, track_depth=0, _watcher=None): + super(TrackableSequence, self).__init__() + self._tracked = tracked.__class__() if tracked else [] + self._track_depth = track_depth + self._watcher = _watcher + if tracked: + for elem in tracked: + self.append(elem) + + def insert(self, index, value): + self._tracked.insert(index, _wrap(value, self, self._track_depth)) + self._on_item_changed(index) + + def _update(self, value): + # lists when different need a full update + if self._tracked != value: + self._item_last_changed = {} + self._tracked = self._tracked.__class__() + for elem in value: + self.append(elem) + + def __setitem__(self, key, value): + self._set_item(key, value) + + def __getitem__(self, key): + return self._tracked.__getitem__(key) + + def __delitem__(self, idx): + self._tracked.__delitem__(idx) + if not isinstance(self._tracked, Trackable): + self._on_item_removed(idx) + + def __len__(self): + return self._tracked.__len__() + + def __iter__(self): + return self._tracked.__iter__() + + def __hash__(self): + return self._tracked.__hash__() + + +class TrackableMapping(Trackable, collections.MutableMapping): + """ + TrackableMapping implements a mutable mapping interface to track + changes in the keys/values in a mapping and acts like a proxy thereof + """ + def __init__(self, tracked=None, track_depth=0, _watcher=None): + """ + :param tracked: The object which should be tracked usually a dict + :param track_depth: The depth the tracking should be applied + the default value is 0 which means only the current + objects keys are tracked. + :return: + """ + super(TrackableMapping, self).__init__() + self._tracked = tracked.__class__() if tracked else {} + self._track_depth = track_depth + self._watcher = _watcher + if tracked: + for k, v in tracked.iteritems(): + self[k] = v + + def update(self, value, filter_if=None): + for k in value.keys(): + if filter and filter(k): + continue + self[k] = value[k] + + def _update(self, value): + for k in self._tracked.keys(): + if k not in value: + del self[k] + elif self._tracked[k] != value[k]: + self[k] = value[k] + for k in value.keys(): + if k not in self._tracked: + self[k] = value[k] + + def __setitem__(self, key, value): + self._set_item(key, value) + + def __getitem__(self, key): + return self._tracked.__getitem__(key) + + def __delitem__(self, key): + self._tracked.__delitem__(key) + if not isinstance(self._tracked, Trackable): + self._on_item_removed(key) + + def __len__(self): + return self._tracked.__len__() + + def __iter__(self): + return self._tracked.__iter__() + + def __hash__(self): + return self._tracked.__hash__() \ No newline at end of file diff --git a/vdsm/API.py b/vdsm/API.py index ff2627e..5464d5f 100644 --- a/vdsm/API.py +++ b/vdsm/API.py @@ -353,6 +353,20 @@ return errCode['noVM'] return v.migrateStatus() + def query(self, fields=None, changedSince=-1, runHooks=True): + v = self._cif.vmContainer.get(self._UUID) + if not v: + return errCode['noVM'] + + if runHooks: + hooks.before_get_vm_stats() + stats = v.query(fields=fields, changedSince=changedSince).copy() + stats['vmId'] = self._UUID + if runHooks: + stats = hooks.after_get_vm_stats([stats])[0] + return {'status': doneCode, 'dataList': [stats]} + + def getStats(self, runHooks=True): """ Obtain statistics of the specified VM @@ -1194,6 +1208,21 @@ self.log.error("failed to retrieve hardware info", exc_info=True) return errCode['hwInfoErr'] + def queryVms(self, vmIDs=None, fields=None, changedSince=-1): + hooks.before_get_all_vm_stats() + vms = self.getVMList() + statsList = [] + for s in vms['vmList']: + if vmIDs and s['vmId'] not in vmIDs: + continue + response = VM(s['vmId']).query(fields=fields, + changedSince=changedSince, + runHooks=False) + if response: + statsList.append(response['dataList'][0]) + statsList = hooks.after_get_all_vm_stats(statsList) + return {'status': doneCode, 'dataList': statsList} + def getAllVmStats(self): """ Get statistics of all running VMs. diff --git a/vdsm/rpc/vdsmapi-schema.json b/vdsm/rpc/vdsmapi-schema.json index af563c0..dc475de 100644 --- a/vdsm/rpc/vdsmapi-schema.json +++ b/vdsm/rpc/vdsmapi-schema.json @@ -3190,6 +3190,248 @@ {'type': 'VmShortStatus', 'data': {'vmId': 'UUID', 'status': 'VmStatus'}} + +## +# @VmData: +# +# All available queryable data fields of a VM instance. +# +# @acpiEnable: #optional Indicates if ACPI is enabled inside the +# VM +# +# @appsList: #optional A list of installed applications with +# their versions +# +# @balloonInfo: #optional Guest memory balloon information +# +# @boot: #optional An alias for the type of device used to +# boot the VM +# +# @cdrom: #optional The path to an ISO image used in the VM's +# CD-ROM device +# +# @clientIp: #optional The IP address of the client connected to +# the display +# +# @copyPasteEnable: #optional Specify if copy and paste is enabled. +# Currently relevant for @qxl devices only. +# +# @cpuShares: #optional The host scheduling priority (relative to +# other VMs). In case both cpuShares and nice are +# present, cpuShares will be used. +# +# @cpuSys: #optional Ratio of CPU time spent by qemu on other +# than guest time +# +# @cpuType: #optional The type of CPU being emulated +# special values 'hostPassthrough' and 'hostModel' +# are reserved for host-passthrough and host-mode cpu +# mode +# +# @cpuUser: #optional Ratio of CPU time spent by the guest VM +# +# @custom: #optional A dictionary of custom, free-form +# properties +# +# @devices: #optional An array of VM devices present +# +# @disks: #optional Disk bandwidth/utilization statistics +# +# @disksUsage: #optional Info about mounted filesystems as +# reported by the agent +# +# @display: #optional The type of display +# +# @displayInfo: #optional Display and graphics device informations. +# +# @displayIp: #optional The IP address to use for accessing the +# VM display +# +# @displayPort: #optional The port in use for unencrypted display +# data +# +# @displaySecurePort: #optional The port in use for encrypted display +# data +# +# @displayType: #optional The type of display in use +# +# @elapsedTime: #optional The number of seconds that the VM has +# been running +# +# @emulatedMachine: #optional The machine specification being emulated +# +# @exitCode: #optional The exit code f the VM process has ended +# +# @exitMessage: #optional Explains the reason that the VM process +# has exited +# +# @guestCPUCount: #optional The number of CPU cores are visible as +# online on the guest OS. This value is -1 if not +# supported to report +# +# @guestFQDN: #optional Fully qualified domain name of the guest +# OS. (Reported by the guest agent) +# +# @guestIPs: #optional A space separated string of assigned IPv4 +# addresses +# +# @keyboardLayout: #optional The keyboard layout string (eg. 'en-us') +# +# @kvmEnable: #optional Indicates if KVM hardware acceleration is +# enabled +# +# @maxVCpus: #optional Maximum number of CPU available for the +# guest. It is the upper boundry for hot plug CPU +# action +# +# @memGuaranteedSize: #optional The amount of memory guaranteed to the VM +# in MB +# +# @memSize: #optional The amount of memory assigned to the VM +# in MB +# +# @memUsage: #optional The percent of memory in use by the guest +# +# @memoryStats: #optional Memory statistics as reported by the +# guest agent +# +# @migrationProgress: #optional Indicates the percentage progress of a +# Migration, when there is one active. +# +# @monitorResponse: #optional Indicates if the qemu monitor is +# responsive +# +# @netIfaces: #optional Network device address info as reported +# by the agent +# +# @network: #optional Network bandwidth/utilization statistics +# +# @nicModel: #optional A comma-separated list of NIC models in +# use by the VM +# +# @nice: #optional The host scheduling priority +# +# @pauseCode: #optional Indicates the reason a VM has been paused +# +# @pid: #optional The process ID of the underlying qemu +# process +# +# @serial: #optional Serial number for the VM. +# +# @session: #optional The current state of user interaction +# with the VM +# +# @smp: #optional The number of CPUs presented to the VM +# +# @smpCoresPerSocket: #optional Indicates the number of CPU cores per +# socket +# +# @smpThreadsPerCore: #optional Indicates the number of CPU threads per +# core +# +# @statsAge: #optional The age of these statistics in seconds +# +# @status: #optional The current VM status +# +# @timeOffset: #optional The time difference from host to the VM +# in seconds +# +# @transparentHugePages: #optional Indicates if the Transparent Huge Pages +# feature is enabled for this virtual machine +# +# @username: #optional The username associated with the current +# session +# +# @vNodeRuntimeInfo: #optional Information about the vm numa node +# runtime pinning to host numa node. +# +# @vmId: #optional The VM UUID +# +# @vmJobs: #optional Info about active vm jobs +# +# @vmName: #optional The VM name +# +# @vmType: #optional The type of VM +# +# @watchdogEvent: #optional Information about the most recent +# watchdog event +# +# Since: 4.15.0 +{'type': 'VmQueryResult', + 'data': { + '*acpiEnable': 'bool', '*appsList': ['str'], '*balloonInfo': 'BalloonInfo', + '*boot': 'VmBootMode', '*cdrom': 'str', '*clientIp': 'str', + '*copyPasteEnable': 'bool', '*cpuShares': 'str', '*cpuSys': 'float', + '*cpuType': 'str', '*cpuUser': 'float', '*custom': 'StringMap', + '*devices': ['VmDevice'], '*disks': 'VmDiskStatsMap', + '*disksUsage': ['GuestMountInfo'], '*display': 'VmDisplayType', + '*displayInfo': ['VmDisplayInfo'], '*displayIp': 'str', + '*displayPort': 'uint', '*displaySecurePort': 'uint', + '*displayType': 'VmDisplayType', '*elapsedTime': 'uint', + '*emulatedMachine': 'str', '*exitCode': 'int', '*exitMessage': 'str', + '*guestCPUCount': 'int', '*guestFQDN': 'str', '*guestIPs': 'str', + '*keyboardLayout': 'str', '*kvmEnable': 'bool', '*maxVCpus': 'uint', + '*memGuaranteedSize': 'uint', '*memSize': 'uint', '*memUsage': 'uint', + '*memoryStats': 'GuestMemoryStats', '*migrationProgress': 'uint', + '*monitorResponse': 'int', '*netIfaces': ['GuestNetworkDeviceInfo'], + '*network': 'NetworkInterfaceStatsMap', '*nicModel': 'str', '*nice': 'int', + '*pauseCode': 'str', '*pid': 'uint', '*serial': 'str', + '*session': 'GuestSessionState', '*smp': 'uint', + '*smpCoresPerSocket': 'uint', '*smpThreadsPerCore': 'uint', + '*statsAge': 'float', '*status': 'VmStatus', '*timeOffset': 'uint', + '*transparentHugePages': 'bool', '*username': 'str', + '*vNodeRuntimeInfo': 'VmNumaNodeRuntimeInfoMap', '*vmId': 'UUID', + '*vmJobs': 'VmJobsMap', '*vmName': 'str', '*vmType': 'VmType', + '*watchdogEvent': 'WatchdogEvent'}} + +## +# @VM.query: +# +# Get statistics about a running virtual machine. +# +# @vmID: The UUID of the VM +# +# @fields: #optional A list of strings which specify the keys which +# should be queried. All fields will be returned if this +# value is not specified. +# +# @changedSince: #optional If specified, all modifications since the given +# change id are returned. Values for fields where the value +# has not changed since, will not be returned. +# +# Returns: +# An array containing a single VmData record +# +# Since: 4.10.0 +## +{'command': {'class': 'VM', 'name': 'getStats'}, + 'data': {'vmID': 'UUID', '*fields': ['string'], '*changedSince': 'uint'}, + 'returns': ['VmData']} + +## +# @Host.queryVms: +# +# Query all specified virtual machines for the specified fields. +# +# @vmIDs: #optional A list of VM UUIDs. If not set, all VMs are used. +# +# @fields: #optional A list of strings which specify the keys which +# should be queried. All fields will be returned if this +# value is not specified. +# +# @changedSince: #optional If specified, all modifications since the given +# change id are returned. Values for fields where the value +# has not changed since, will not be returned. +# +# Returns: +# A list of query results for all VMs in vmIDs or all VMs if vmIDs not set +# +# Since: 4.15.0 +## +{'command': {'class': 'Host', 'name': 'queryAllVm'}, + 'data': {'*vmIDs': ['UUID'], '*fields': ['string'], '*changedSince': 'uint'}, + 'returns': ['VmData']} + ## # @VmDefinition: # diff --git a/vdsm/virt/vm.py b/vdsm/virt/vm.py index 4b00c51..839f72c 100644 --- a/vdsm/virt/vm.py +++ b/vdsm/virt/vm.py @@ -41,6 +41,7 @@ from vdsm import libvirtconnection from vdsm import netinfo from vdsm import qemuimg +from vdsm import trackable from vdsm import utils from vdsm.compat import pickle from vdsm.config import config @@ -1773,6 +1774,8 @@ self._watchdogEvent = {} self.sdIds = [] self.arch = caps.getTargetArch() + self._queryCacheLock = threading.Lock() + self._queryCache = trackable.TrackableMapping() if (self.arch not in ['ppc64', 'x86_64']): raise RuntimeError('Unsupported architecture: %s' % self.arch) @@ -2663,6 +2666,20 @@ stats["watchdogEvent"] = self._watchdogEvent return stats + def query(self, fields=None, changedSince=-1, runHooks=False): + # First we need to update the query cache + stats = self.getStats() + with self._queryCacheLock: + self._queryCache.update(stats) + self._queryCache.update(self.conf, lambda x: x.startswith('_')) + + # Now we build up the response + result = {} + for k in (fields or self._queryCache.keys()): + if self._queryCache[k].changed(changedSince): + result[k] = self._queryCache[k] + return result + def _getStatsInternal(self): """ used by API.Vm.getStats -- To view, visit http://gerrit.ovirt.org/28819 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Iddb4657d738ca0406950d07b84a7c85721e80b78 Gerrit-PatchSet: 1 Gerrit-Project: vdsm Gerrit-Branch: master Gerrit-Owner: Vinzenz Feenstra <vfeen...@redhat.com> _______________________________________________ vdsm-patches mailing list vdsm-patches@lists.fedorahosted.org https://lists.fedorahosted.org/mailman/listinfo/vdsm-patches