Merge "test_spec: Clarify LTD.Throughput.RFC2544.PacketLossRatioFrameModification"
[vswitchperf.git] / core / loader / loader_servant.py
1 # Copyright 2015 Intel Corporation.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #   http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 """Loader servant module used by Loader.
16
17 Module is inteded to be private to serve only Loader itself, nevertheless
18 some methods are exposed outside and can be used by any other clients:
19 - load_modules(self, path, interface)
20 - load_module(self, path, interface, class_name)
21 Those method are stateless static members.
22
23 """
24 import os
25 from os import sys
26 import imp
27 import fnmatch
28 import logging
29
30
31 class LoaderServant(object):
32     """Class implements basic dynamic import operations.
33     """
34     _class_name = None
35     _path = None
36     _interface = None
37
38     def __init__(self, path, class_name, interface):
39         """LoaderServant constructor
40
41         Intializes all data needed for import operations.
42
43         Attributes:
44             path: path to directory which contains implementations derived from
45                     interface.
46             class_name: Class name which will be returned in get_class
47                         method, if such definition exists in directory
48                         represented by path,
49             interface: interface type. Every object which doesn't
50                         implement this particular interface will be
51                         filtered out.
52         """
53         self._class_name = class_name
54         self._path = path
55         self._interface = interface
56
57     def get_class(self):
58         """Returns class type based on parameters passed in __init__.
59
60         :return: Type of the found class.
61             None if class hasn't been found
62         """
63
64         return self.load_module(path=self._path,
65                                 interface=self._interface,
66                                 class_name=self._class_name)
67
68     def get_classes(self):
69         """Returns all classes in path derived from interface
70
71         :return: Dictionary with following data:
72             - key: String representing class name,
73             - value: Class type.
74         """
75         return self.load_modules(path=self._path,
76                                  interface=self._interface)
77
78     def get_classes_printable(self):
79         """Returns all classes derived from _interface found in path
80
81         :return: String - list of classes in printable format.
82         """
83
84         out = self.load_modules(path=self._path,
85                                 interface=self._interface)
86         results = []
87
88         for (name, mod) in list(out.items()):
89             desc = (mod.__doc__ or 'No description').strip().split('\n')[0]
90             results.append((name, desc))
91
92         output = [
93             'Classes derived from: ' + self._interface.__name__ + '\n======\n']
94
95         for (name, desc) in results:
96             output.append('* %-18s%s' % ('%s:' % name, desc))
97
98             output.append('')
99
100         output.append('')
101
102         return '\n'.join(output)
103
104     @staticmethod
105     def load_module(path, interface, class_name):
106         """Imports everything from given path and returns class type
107
108         This is based on following conditions:
109             - Class is derived from interface,
110             - Class type name matches class_name.
111
112         :return: Type of the found class.
113             None if class hasn't been found
114         """
115
116         results = LoaderServant.load_modules(
117             path=path, interface=interface)
118
119         if class_name in results:
120             logging.info(
121                 "Class found: " + class_name + ".")
122             return results.get(class_name)
123
124         return None
125
126     @staticmethod
127     def load_modules(path, interface):
128         """Returns dictionary of class name/class type found in path
129
130         This is based on following conditions:
131             - classes found under path are derived from interface.
132             - class is not interface itself.
133
134         :return: Dictionary with following data:
135             - key: String representing class name,
136             - value: Class type.
137         """
138         result = {}
139
140         for _, mod in LoaderServant._load_all_modules(path):
141             # find all system metric loggers defined in the module
142             gens = dict((k, v) for (k, v) in list(mod.__dict__.items())
143                         if type(v) == type and
144                         issubclass(v, interface) and k != interface.__name__)
145             if gens:
146                 for (genname, gen) in list(gens.items()):
147                     result[genname] = gen
148         return result
149
150     @staticmethod
151     def _load_all_modules(path):
152         """Load all modules from ``path`` directory.
153
154         This is based on the design used by OFTest:
155             https://github.com/floodlight/oftest/blob/master/oft
156
157         :param path: Path to a folder of modules.
158
159         :return: List of modules in a folder.
160         """
161         mods = []
162
163         for root, _, filenames in os.walk(path):
164             # Iterate over each python file
165             for filename in fnmatch.filter(filenames, '[!.]*.py'):
166                 modname = os.path.splitext(os.path.basename(filename))[0]
167
168                 try:
169                     if modname in sys.modules:
170                         mod = sys.modules[modname]
171                     else:
172                         mod = imp.load_module(
173                             modname, *imp.find_module(modname, [root]))
174                 except ImportError:
175                     logging.error('Could not import file ' + filename)
176                     raise
177
178                 mods.append((modname, mod))
179
180         return mods