Merge "vpp: Initial support of VPP vSwitch"
[vswitchperf.git] / core / loader / loader_servant.py
1 # Copyright 2015-2017 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 from conf import settings
30
31
32 class LoaderServant(object):
33     """Class implements basic dynamic import operations.
34     """
35     _class_name = None
36     _path = None
37     _interface = None
38
39     def __init__(self, path, class_name, interface):
40         """LoaderServant constructor
41
42         Intializes all data needed for import operations.
43
44         Attributes:
45             path: path to directory which contains implementations derived from
46                     interface.
47             class_name: Class name which will be returned in get_class
48                         method, if such definition exists in directory
49                         represented by path,
50             interface: interface type. Every object which doesn't
51                         implement this particular interface will be
52                         filtered out.
53         """
54         self._class_name = class_name
55         self._path = path
56         self._interface = interface
57
58     def get_class(self):
59         """Returns class type based on parameters passed in __init__.
60
61         :return: Type of the found class.
62             None if class hasn't been found
63         """
64
65         return self.load_module(path=self._path,
66                                 interface=self._interface,
67                                 class_name=self._class_name)
68
69     def get_classes(self):
70         """Returns all classes in path derived from interface
71
72         :return: Dictionary with following data:
73             - key: String representing class name,
74             - value: Class type.
75         """
76         return self.load_modules(path=self._path,
77                                  interface=self._interface)
78
79     def get_classes_printable(self):
80         """Returns all classes derived from _interface found in path
81
82         :return: String - list of classes in printable format.
83         """
84
85         out = self.load_modules(path=self._path,
86                                 interface=self._interface)
87         results = []
88
89         # sort modules to produce the same output everytime
90         for (name, mod) in sorted(out.items()):
91             desc = (mod.__doc__ or 'No description').strip().split('\n')[0]
92             results.append((name, desc))
93
94         header = 'Classes derived from: ' + self._interface.__name__
95         output = [header + '\n' + '=' * len(header) + '\n']
96
97         for (name, desc) in results:
98             output.append('* %-18s%s' % ('%s:' % name, desc))
99
100             output.append('')
101
102         output.append('')
103
104         return '\n'.join(output)
105
106     @staticmethod
107     def load_module(path, interface, class_name):
108         """Imports everything from given path and returns class type
109
110         This is based on following conditions:
111             - Class is derived from interface,
112             - Class type name matches class_name.
113
114         :return: Type of the found class.
115             None if class hasn't been found
116         """
117
118         results = LoaderServant.load_modules(
119             path=path, interface=interface)
120
121         if class_name in results:
122             logging.info(
123                 "Class found: " + class_name + ".")
124             return results.get(class_name)
125
126         return None
127
128     @staticmethod
129     def load_modules(path, interface):
130         """Returns dictionary of class name/class type found in path
131
132         This is based on following conditions:
133             - classes found under path are derived from interface.
134             - class is not interface itself.
135
136         :return: Dictionary with following data:
137             - key: String representing class name,
138             - value: Class type.
139         """
140         result = {}
141
142         for _, mod in LoaderServant._load_all_modules(path):
143             # find all classes derived from given interface, but suppress
144             # interface itself and any abstract class starting with iface name
145             gens = dict((k, v) for (k, v) in list(mod.__dict__.items())
146                         if isinstance(v, type) and
147                         issubclass(v, interface) and
148                         not k.startswith(interface.__name__))
149             if gens:
150                 for (genname, gen) in list(gens.items()):
151                     result[genname] = gen
152         return result
153
154     @staticmethod
155     def _load_all_modules(path):
156         """Load all modules from ``path`` directory.
157
158         This is based on the design used by OFTest:
159             https://github.com/floodlight/oftest/blob/master/oft
160
161         :param path: Path to a folder of modules.
162
163         :return: List of modules in a folder.
164         """
165         mods = []
166
167         for root, _, filenames in os.walk(path):
168             # Iterate over each python file
169             for filename in fnmatch.filter(filenames, '[!.]*.py'):
170                 modname = os.path.splitext(os.path.basename(filename))[0]
171
172                 # skip module load if it is excluded by configuration
173                 if modname in settings.getValue('EXCLUDE_MODULES'):
174                     continue
175
176                 try:
177                     if modname in sys.modules:
178                         mod = sys.modules[modname]
179                     else:
180                         mod = imp.load_module(
181                             modname, *imp.find_module(modname, [root]))
182                 except ImportError:
183                     logging.error('Could not import file ' + filename)
184                     raise
185
186                 mods.append((modname, mod))
187
188         return mods