Change in vdsm[master]: events: introduction of schema

piotr.kliczewski at gmail.com piotr.kliczewski at gmail.com
Tue Mar 15 11:39:12 UTC 2016


Piotr Kliczewski has uploaded a new change for review.

Change subject: events: introduction of schema
......................................................................

events: introduction of schema


Change-Id: I43a9f1852b1459a2e905244049507704f0187201
Signed-off-by: pkliczewski <piotr.kliczewski at gmail.com>
---
M lib/api/Makefile.am
A lib/api/vdsm-events.yaml
M lib/api/vdsmapi.py
M lib/vdsm/rpc/Bridge.py
M tests/schemaValidationTest.py
M tests/vdsmapi_test.py
M vdsm.spec.in
7 files changed, 298 insertions(+), 32 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/54/54754/1

diff --git a/lib/api/Makefile.am b/lib/api/Makefile.am
index b5c081d..ea19347 100644
--- a/lib/api/Makefile.am
+++ b/lib/api/Makefile.am
@@ -29,6 +29,7 @@
 dist_vdsmrpc_DATA = \
 	vdsm-api.yaml \
 	vdsm-api-gluster.yaml \
+	vdsm-events.yaml \
 	$(NULL)
 
 all-local: \
diff --git a/lib/api/vdsm-events.yaml b/lib/api/vdsm-events.yaml
new file mode 100644
index 0000000..c220a15
--- /dev/null
+++ b/lib/api/vdsm-events.yaml
@@ -0,0 +1,188 @@
+types:
+    UUID: &UUID
+        description: A universally unique identifier in RFC 4122 format.eg.
+            "407cb255-34be-432c-be7c-eb43b8de82be"
+        name: UUID
+        sourcetype: string
+        type: alias
+
+    VmStatus: &VmStatus
+        description: An enumeration of possible virtual machine statuses.
+        name: VmStatus
+        type: enum
+        values:
+            Down: The VM is powered off
+            Migration Destination: The VM is migrating to this host
+            Migration Source: The VM is migrating away from this host
+            Paused: The VM is paused
+            Powering down: A shutdown command has been sent to the VM
+            Powering up: The VM is booting
+            RebootInProgress: The VM is currently rebooting
+            Restoring state: The VM is waking from hibernation
+            Saving State: The VM is preparing for hibernation
+            Up: The VM is running
+            WaitForLaunch: The VM is being created
+
+    VmGraphicsDeviceType: &VmGraphicsDeviceType
+        description: An enumeration of VM graphics device types.
+        name: VmGraphicsDeviceType
+        type: enum
+        values:
+            spice: SPICE graphics device
+            vnc: VNC graphics device
+
+    VmDisplayInfo: &VmDisplayInfo
+        description: Display and graphics device information.
+        name: VmDisplayInfo
+        properties:
+        -   description: The port in use for encrypted display data
+            name: tlsPort
+            type: uint
+
+        -   description: The port in use for unencrypted display data
+            name: port
+            type: uint
+
+        -   description: The type of display in use
+            name: type
+            type: *VmGraphicsDeviceType
+
+        -   description: The IP address to use for accessing the VM
+                display
+            name: ipAddress
+            type: string
+        type: object
+
+    VmDisplayType: &VmDisplayType
+        description: An enumeration of VM display modes.
+        name: VmDisplayType
+        type: enum
+        values:
+            qxl: Graphical VM interaction is available via SPICE
+            vnc: Graphical VM interaction is available via the VNC protocol
+
+    StatusDetails: &StatusDetails
+        description: Represent information which are send as part of vm status
+            change event
+        name: StatusDetails
+        properties:
+        -   name: status
+            type: *VmStatus
+            description: 
+
+        -   name: timeOffset
+            type: string
+            description: The time difference from host to the VM in seconds
+            defaultvalue: no-default
+
+        -   name: exitReason
+            type: int
+            description: The specific exit reason code
+            defaultvalue: no-default
+
+        -   name: exitMessage
+            type: string
+            description: Detailed reason for the virtual machine exit
+            defaultvalue: no-default
+
+        -   name: exitCode
+            type: int
+            description: Code indicating whether the VM exit was normal or
+                in error
+            defaultvalue: no-default
+
+        -   name: displayInfo
+            type:
+            - *VmDisplayInfo
+            description: Display and graphics device informations.
+            defaultvalue: no-default
+
+        -   name: hash
+            type: string
+            description: Devices has
+            defaultvalue: no-default
+
+        -   description: Ratio of CPU time spent by the guest VM
+            name: cpuUser
+            type: string
+            defaultvalue: no-default
+
+        -   name: displayIp
+            type: string
+            description: The IP address to use for accessing the VM display
+            defaultvalue: no-default
+
+        -   name: monitorResponse
+            type: int
+            description: Indicates if the qemu monitor is responsive
+            defaultvalue: no-default
+
+        -   name: elapsedTime
+            type: uint
+            description: The number of seconds that the VM has been running
+            defaultvalue: no-default
+
+        -   name: displayType
+            type: *VmDisplayType
+            description: The type of display in use
+            defaultvalue: no-default
+
+        -   name: cpuSys
+            type: string
+            description: Ratio of CPU time spent by qemu on other than guest
+                time
+            defaultvalue: no-default
+
+        -   name: displayPort
+            type: string
+            description: The port in use for unencrypted display data
+            defaultvalue: no-default
+
+        -   name: displaySecurePort
+            type: string
+            description: The port in use for encrypted display data
+            defaultvalue: no-default
+
+        -   name: clientIp
+            type: string
+            description: The IP address of the client connected to the display
+            defaultvalue: no-default
+
+        -   name: vcpuQuota
+            type: string
+            description: Setting for libvirt cpu_quota.
+            defaultvalue: no-default
+
+        -   name: vcpuPeriod
+            type: int
+            description: Setting for libvirt cpu_period.
+            defaultvalue: no-default
+        type: object
+
+    StatusMap: &StatusMap
+        description: A mapping of vm status details indexed by vm UUID.
+        key-type: *UUID
+        name: StatusMap
+        type: map
+        value-type: *StatusDetails
+
+'|virt|VM_status|':
+    description: Provides status information about virtual machines 
+    params:
+    -   name: notify_time
+        type: uint
+        description: auto generated based on monotonic time when an event was
+            sent
+
+    -   name: no_name
+        type: *StatusMap
+        description: A map containing vm status details
+
+'|net|host_conn|':
+    description: Gives a hint to a client that capabilities needs to be
+        refreshed
+    params:
+    -   name: notify_time
+        type: uint
+        description: auto generated based on monotonic time when an event was
+            sent
diff --git a/lib/api/vdsmapi.py b/lib/api/vdsmapi.py
index 43867d6..2e7d7c8 100644
--- a/lib/api/vdsmapi.py
+++ b/lib/api/vdsmapi.py
@@ -80,7 +80,19 @@
             raise SchemaNotFound("Unable to find API schema file")
 
     def get_params(self, class_name, method_name):
-        return self.get_method(class_name, method_name).get('params', [])
+        return self.get_method('%s.%s' % (class_name,
+                                          method_name)).get('params', [])
+
+    def _trim_subscription_id(self, sub_id):
+        idx = len(sub_id) - sub_id.rfind('|')
+        return sub_id[:1 - idx]
+
+    def get_event_params(self, sub_id):
+        sub_id = self._trim_subscription_id(sub_id)
+        return self.get_method(sub_id).get('params', [])
+
+    def get_event_param_names(self, sub_id):
+        return [param.get('name') for param in self.get_event_params(sub_id)]
 
     def get_param_names(self, class_name, method_name):
         return [param.get('name') for param in self.get_params(class_name,
@@ -97,10 +109,11 @@
                 if 'defaultvalue' in param]
 
     def get_ret_param(self, class_name, method_name):
-        return self.get_method(class_name, method_name).get('return', {})
+        return self.get_method('%s.%s' % (class_name,
+                                          method_name)).get('return', {})
 
-    def get_method(self, class_name, method_name):
-        return self._schema.get('%s.%s' % (class_name, method_name))
+    def get_method(self, name):
+        return self._schema.get(name)
 
     def get_type(self, type_name):
         return self._schema.get('types').get(type_name)
@@ -135,7 +148,8 @@
                             'provided when calling %s.%s' % (name, class_name,
                                                              method_name))
                     continue
-                self._verify_type(param, arg, class_name, method_name)
+                self._verify_type(param, arg, '%s.%s' % (class_name,
+                                                         method_name))
         except Exception as e:
             if type(e).__name__ == 'JsonRpcInvalidParamsError':
                 raise
@@ -143,13 +157,13 @@
                                        ' verification for %s.%s'
                                        % (class_name, method_name))
 
-    def _verify_type(self, param, arg, class_name, method_name):
+    def _verify_type(self, param, arg, identifier):
         if isinstance(param, list):
             if not isinstance(arg, list):
                 self._report_inconsistency('Parameter %s is not a list'
                                            % (arg))
             for a in arg:
-                self._verify_type(param[0], a, class_name, method_name)
+                self._verify_type(param[0], a, identifier)
             return
 
         elif param in primitive_types.keys():
@@ -161,42 +175,37 @@
         if t == 'dict':
             # it seems that there is no other way to have it fixed
             self._report_inconsistency(
-                'Unsupported type %s in %s.%s please fix'
-                % (t, class_name, method_name))
+                'Unsupported type %s in %s please fix' % (t, identifier))
 
         elif t in primitive_types.keys():
             self._check_primitive_type(t, arg, name)
 
         else:
             if isinstance(t, (str, unicode)):
-                self._verify_ctype(t, param, arg, name, class_name,
-                                   method_name)
+                self._verify_ctype(t, param, arg, name, identifier)
             elif isinstance(t, list):
                 if not isinstance(arg, list):
                     self._report_inconsistency('Parameter %s is not list'
                                                % (arg))
                 for a in arg:
-                    self._verify_type(t[0], a, class_name, method_name)
+                    self._verify_type(t[0], a, identifier)
             else:
-                self._verify_ctype(t.get('type'), t, arg, name, class_name,
-                                   method_name)
+                self._verify_ctype(t.get('type'), t, arg, name, identifier)
 
-    def _verify_ctype(self, t_type, t, arg, name, class_name, method_name):
+    def _verify_ctype(self, t_type, t, arg, name, identifier):
         if t_type == 'alias':
             self._check_primitive_type(t.get('sourcetype'), arg, name)
         elif t_type == 'map':
             for key, value in arg.iteritems():
-                self._verify_type(t.get('key-type'), key, class_name,
-                                  method_name)
-                self._verify_type(t.get('value-type'), value, class_name,
-                                  method_name)
+                self._verify_type(t.get('key-type'), key, identifier)
+                self._verify_type(t.get('value-type'), value, identifier)
         elif t_type == 'union':
             for value in t.get('values'):
                 props = value.get('properties')
                 prop_names = [prop.get('name') for prop in props]
                 if not [key for key in arg.keys() if key not in prop_names]:
                     self._verify_ctype(value.get('type'), value, arg, name,
-                                       class_name, method_name)
+                                       identifier)
                     return
             self._report_inconsistency('Provided parameters %s do not match'
                                        ' any of union %s values'
@@ -205,10 +214,9 @@
             if arg not in t.get('values').keys():
                 self._report_inconsistency('Provided value "%s" not'
                                            ' defined in %s enum for'
-                                           ' %s.%s' % (arg,
-                                                       t.get('name'),
-                                                       class_name,
-                                                       method_name))
+                                           ' %s' % (arg,
+                                                    t.get('name'),
+                                                    identifier))
         else:
             # object
             props = t.get('properties')
@@ -228,7 +236,7 @@
                     if value == 'needs updating':
                         self._report_inconsistency(
                             'No default value specified for %s parameter in'
-                            ' %s.%s' % (p_name, class_name, method_name))
+                            ' %s' % (p_name, identifier))
                     if value == 'no-default':
                         continue
                     if a is None or a == value:
@@ -237,18 +245,18 @@
                     if a is None:
                         self._report_inconsistency(
                             'Required property %s is not provided when calling'
-                            ' %s.%s' % (p_name, class_name, method_name))
+                            ' %s' % (p_name, identifier))
                         continue
 
-                self._verify_type(prop, a, class_name, method_name)
+                self._verify_type(prop, a, identifier)
 
     def verify_ret_params(self, class_name, method_name, ret):
         try:
             ret_args = self.get_ret_param(class_name, method_name)
 
             if ret_args:
-                self._verify_type(ret_args.get('type'), ret, class_name,
-                                  method_name)
+                self._verify_type(ret_args.get('type'), ret,
+                                  '%s.%s' % (class_name, method_name))
         except Exception as e:
             if type(e).__name__ == 'JsonRpcInvalidParamsError':
                 raise
@@ -256,3 +264,28 @@
             self._report_inconsistency('Unexpected issue with response type'
                                        ' verification for %s.%s'
                                        % (class_name, method_name))
+
+    def verify_event_params(self, sub_id, args):
+        try:
+            # do to issue with typing we are not able to find unknown params
+            for param in self.get_event_params(sub_id):
+                name = param.get('name')
+                if name == 'no_name':
+                    # we need to fix an issue with current events
+                    key, value = args.items()[1]
+                    self._verify_type(param, {key: value}, sub_id)
+                    continue
+                arg = args.get(name)
+                if arg is None:
+                    if 'defaultvalue' not in param:
+                        self._report_inconsistency(
+                            'Required parameter %s is not '
+                            'provided when sending %s' % (name, sub_id))
+                    continue
+                self._verify_type(param, arg, sub_id)
+        except Exception as e:
+            if type(e).__name__ == 'JsonRpcInvalidParamsError':
+                raise
+
+            self._report_inconsistency('Unexpected issue with event type'
+                                       ' verification for %s' % (sub_id))
diff --git a/lib/vdsm/rpc/Bridge.py b/lib/vdsm/rpc/Bridge.py
index c66ccc9..b18a2ca 100644
--- a/lib/vdsm/rpc/Bridge.py
+++ b/lib/vdsm/rpc/Bridge.py
@@ -97,10 +97,11 @@
 
     def dispatch(self, method):
         try:
-            className, methodName = method.split('.', 1)
-            self._cache.get_method(className, methodName)
+            self._cache.get_method(method)
         except (KeyError, ValueError):
             raise yajsonrpc.JsonRpcMethodNotFoundError(method)
+
+        className, methodName = method.split('.', 1)
         return partial(self._dynamicMethod, className, methodName)
 
     def _convert_class_name(self, name):
diff --git a/tests/schemaValidationTest.py b/tests/schemaValidationTest.py
index c9320a0..ac374bd 100644
--- a/tests/schemaValidationTest.py
+++ b/tests/schemaValidationTest.py
@@ -59,6 +59,9 @@
             apiobj = self._get_api('gapi')
             self._validate(apiobj)
 
+    def test_verify_events_schema(self):
+        pass
+
     def test_line_length(self):
         for fname in self._get_paths():
             with open(fname) as f:
@@ -87,7 +90,8 @@
         testPath = os.path.realpath(__file__)
         dirName = os.path.split(testPath)[0]
 
-        for tail in ('vdsm-api.yaml', 'vdsm-api-gluster.yaml'):
+        for tail in ('vdsm-api.yaml', 'vdsm-api-gluster.yaml',
+                     'vdsm-events.yaml'):
             yield os.path.join(
                 dirName, '..', 'lib', 'api', tail)
 
diff --git a/tests/vdsmapi_test.py b/tests/vdsmapi_test.py
index d447c4d..eeb0c14 100644
--- a/tests/vdsmapi_test.py
+++ b/tests/vdsmapi_test.py
@@ -37,6 +37,7 @@
 
     def __init__(self):
         self._cache = None
+        self._events_cache = None
 
     def cache(self):
         if self._cache is None:
@@ -45,6 +46,12 @@
                 paths.append(vdsmapi.find_schema('vdsm-api-gluster'))
             self._cache = vdsmapi.SchemaCache(paths)
         return self._cache
+
+    def events_cache(self):
+        if self._events_cache is None:
+            path = [vdsmapi.find_schema('vdsm-events')]
+            self._events_cache = vdsmapi.SchemaCache(path)
+        return self._events_cache
 
     def _name_args(self, args, kwargs, arglist):
         kwargs = kwargs.copy()
@@ -66,6 +73,10 @@
     def verify_ret(self, class_name, method_name, ret):
         _cache.cache().verify_ret_params(class_name, method_name, ret)
 
+    def verify_event(self, subscription_id, params):
+        _events_cache.events_cache()._events_cache(subscription_id, params)
+
+_events_cache = CacheWrapper()
 _cache = CacheWrapper()
 
 
@@ -312,3 +323,30 @@
                                              'hostdevListByCaps', ret)
 
         self.assertIn('is not a list', e.exception.message)
+
+    @MonkeyPatch(vdsmapi, 'config',
+                 make_config([('devel', 'strict_mode', 'true')]))
+    def test_events_params(self):
+        params = {"notify_time": 4303947020,
+                  "426aef82-ea1d-4442-91d3-fd876540e0f0":
+                      {"status": "Up",
+                       "displayInfo": [{"tlsPort": "5901",
+                                        "ipAddress": "0",
+                                        "type": "spice",
+                                        "port": "5900"}],
+                       "hash": "880508647164395013",
+                       "cpuUser": "0.00",
+                       "displayIp": "0",
+                       "monitorResponse": "0",
+                       "elapsedTime": "110",
+                       "displayType": "qxl",
+                       "cpuSys": "0.00",
+                       "displayPort": "5900",
+                       "displaySecurePort": "5901",
+                       "timeOffset": "0",
+                       "clientIp": "",
+                       "vcpuQuota": "-1",
+                       "vcpuPeriod": 100000}}
+        sub_id = '|virt|VM_status|426aef82-ea1d-4442-91d3-fd876540e0f0'
+
+        _events_cache.events_cache().verify_event_params(sub_id, params)
diff --git a/vdsm.spec.in b/vdsm.spec.in
index 9e4be0f..ca657d0 100644
--- a/vdsm.spec.in
+++ b/vdsm.spec.in
@@ -1501,6 +1501,7 @@
 %dir %{python_sitelib}/%{vdsm_name}
 %dir %{python_sitelib}/%{vdsm_name}/rpc
 %{python_sitelib}/%{vdsm_name}/rpc/vdsm-api.yaml
+%{python_sitelib}/%{vdsm_name}/rpc/vdsm-events.yaml
 %{python_sitelib}/api/vdsmapi.py*
 %{python_sitelib}/api/__init__.py*
 %if ! 0%{?with_gluster_mgmt}


-- 
To view, visit https://gerrit.ovirt.org/54754
To unsubscribe, visit https://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I43a9f1852b1459a2e905244049507704f0187201
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Piotr Kliczewski <piotr.kliczewski at gmail.com>


More information about the vdsm-patches mailing list