blob: 31b1636d8d5dfa40fdb65cec9972728f5ecdc6f9 [file] [log] [blame]
Wei-Yu Chen49950b92021-11-08 19:19:18 +08001"""
2Copyright 2020 The Magma Authors.
3
4This source code is licensed under the BSD-style license found in the
5LICENSE file in the root directory of this source tree.
6
7Unless required by applicable law or agreed to in writing, software
8distributed under the License is distributed on an "AS IS" BASIS,
9WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10See the License for the specific language governing permissions and
11limitations under the License.
12"""
13
14import json
15import logging
16from contextlib import closing
17from typing import Any, Dict
18
19import pkg_resources
20import yaml
21from bravado_core.spec import Spec
22from bravado_core.validate import validate_object as bravado_validate
23
24EVENT_REGISTRY = 'event_registry'
25SWAGGER_SPEC = 'swagger_spec'
26BRAVADO_SPEC = 'bravado_spec'
27MODULE = 'module'
28FILENAME = 'filename'
29DEFINITIONS = 'definitions'
30
31
32class EventValidator(object):
33 """
34 gRPC based server for EventD.
35 """
36
37 def __init__(self, config: Dict[str, Any]):
38 self.event_registry = config[EVENT_REGISTRY]
39 self.specs_by_filename = self._load_specs_from_registry()
40
41 def validate_event(self, raw_event: str, event_type: str) -> None:
42 """
43 Checks if an event is registered and validates it based on
44 a registered schema.
45 Args:
46 raw_event: The event to be validated, as a JSON-encoded string
47 event_type: The type of an event, which corresponds
48 to a generated model
49 Returns:
50 Does not return, but throws exceptions if validation fails.
51 """
52 event = json.loads(raw_event)
53
54 # Event not in registry
55 if event_type not in self.event_registry:
56 logging.debug(
57 'Event type %s not among registered event types (%s)',
58 event_type, self.event_registry,
59 )
60 raise KeyError(
61 'Event type {} not registered, '
62 'please add it to the EventD config'.format(event_type),
63 )
64 filename = self.event_registry[event_type][FILENAME]
65 bravado_validate(
66 self.specs_by_filename[filename][BRAVADO_SPEC],
67 self.specs_by_filename[filename][SWAGGER_SPEC][event_type],
68 event,
69 )
70
71 def _load_specs_from_registry(self) -> Dict[str, Any]:
72 """
73 Loads all swagger definitions from the files specified in the
74 event registry.
75 """
76 specs_by_filename = {}
77 for event_type, info in self.event_registry.items():
78 filename = info[FILENAME]
79 if filename in specs_by_filename:
80 # Spec for this file is already registered
81 self._check_event_exists_in_spec(
82 specs_by_filename[filename][SWAGGER_SPEC],
83 filename,
84 event_type,
85 )
86 continue
87
88 module = '{}.swagger.specs'.format(info[MODULE])
89 if not pkg_resources.resource_exists(module, filename):
90 raise LookupError(
91 'File {} not found under {}/swagger, please ensure that '
92 'it exists'.format(filename, info[MODULE]),
93 )
94
95 stream = pkg_resources.resource_stream(module, filename)
96 with closing(stream) as spec_file:
97 swagger_spec = yaml.safe_load(spec_file)
98 self._check_event_exists_in_spec(
99 swagger_spec[DEFINITIONS], filename, event_type,
100 )
101
102 config = {'validate_swagger_spec': False}
103 bravado_spec = Spec.from_dict(swagger_spec, config=config)
104 specs_by_filename[filename] = {
105 SWAGGER_SPEC: swagger_spec[DEFINITIONS],
106 BRAVADO_SPEC: bravado_spec,
107 }
108
109 return specs_by_filename
110
111 @staticmethod
112 def _check_event_exists_in_spec(
113 swagger_definitions: Dict[str, Any],
114 filename: str,
115 event_type: str,
116 ):
117 """
118 Throw a KeyError if the event_type does not exist in swagger_definitions
119 """
120 if event_type not in swagger_definitions:
121 raise KeyError(
122 'Event type {} is not defined in {}, '
123 'please add the definition and re-generate '
124 'swagger specifications'.format(event_type, filename),
125 )