1 """
2 @copyright: 2011 Mark LaPerriere
3
4 @license:
5 Licensed under the Apache License, Version 2.0 (the "License");
6 you may not use this file except in compliance with the License.
7 You may obtain a copy of the License at
8
9 U{http://www.apache.org/licenses/LICENSE-2.0}
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16
17 @summary:
18 pyHai base classes
19
20 @author: Mark LaPerriere
21 @contact: pyhai@mindmind.com
22 @organization: Mind Squared Design / www.mindmind.com
23 @version: 0.1.3
24 @date: Jan 19, 2012
25 """
26 import abc
27 import os
28 import sys
29 import datetime
30 import time
31 import utils
32 import profilers.base
33 import profilers.default
34
35 import logging
36
37 _logger = logging.getLogger(__name__)
38 _logger.addHandler(logging.NullHandler())
39
40
41 __VERSION__ = (0, 1, 3)
42 VERSION = '.'.join(map(str, __VERSION__))
43
44
45 PACKAGE_PLUGIN_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'plugins')
46 DEFAULT_CUSTOM_PLUGIN_PATH = os.path.join(os.path.dirname(os.path.abspath('.')), 'plugins')
47 PLUGIN_LOADER_EXCLUSIONS = ('.', '..', '__init__.py', '__init__.pyc', '__init__.pyo')
48 DEFAULT_PROFILER_CLASS = profilers.default.DefaultProfiler
49
50
52 """
53 Auditor class
54
55 @ivar custom_plugin_path: The path to any custom plugins
56 @type custom_plugin_path: C{str}
57 @ivar profile: A dictionary of properties for this host
58 @type profile: C{dict}
59 @ivar architecture: The name of the architecture as is normally returned by platform.architecture()[0]
60 @type architecture: C{str}
61 @ivar plugins: A list of successfully loaded plugins
62 @type plugins: C{list}
63 """
64 plugin_paths = [PACKAGE_PLUGIN_PATH]
65 profile = None
66 plugins = {}
67
68 - def __init__(self, plugin_paths=None, **kwargs):
69 """
70 Initialize System object
71
72 @param plugin_paths: A path (or list of paths) to a custom set of plugins
73 @type plugin_paths: C{str | list}
74 @keyword profiler_class: The name of a class that extends L{ProfilerBase} or the name of the module where the
75 L{ProfilerBase} class can be found. If supplying a module, must supply the profiler_class keyword arg
76 @type profiler_class: C{class | str}
77 @keyword enable_default_plugins: A flag to use (or suppress) the builtin plugins
78 @type enable_default_plugins: C{bool}
79 @keyword profiler_package: The name of the package that contains a class that extends L{ProfilerBase}
80 @type profiler_package: C{str}
81 """
82 profiler_class = kwargs.get('profiler_class', DEFAULT_PROFILER_CLASS)
83 profiler_package = kwargs.get('profiler_package', None)
84 enable_default_plugins = kwargs.get('enable_default_plugins', True)
85
86
87 if type(profiler_class) is str:
88 profiler_package = kwargs.get('profiler_package', '')
89 if profiler_package:
90 profiler_class = self.__load_module(profiler_package, profiler_class)
91 else:
92 profiler_class = self.__load_module(profiler_class)
93
94 if issubclass(profiler_class, profilers.base.ProfilerBase):
95 profiler = profiler_class()
96 if hasattr(profiler, 'profile'):
97 self.profile = profiler.profile()
98 if type(self.profile) is dict and 'pyhai_version' not in self.profile:
99 self.profile['pyhai_version'] = VERSION
100 self.system_class = profiler.system_class()
101 self.system = profiler.system()
102 _logger.debug('Successfully loaded the profiler: %s', profiler.__class__.__name__)
103 else:
104 raise Exception('Failed to load a valid profiler: %s' % profiler.__class__.__name__)
105 else:
106 raise Exception('Arguments supplied for profiler are not valid')
107
108 if plugin_paths:
109 if type(plugin_paths) is str:
110 plugin_paths = [plugin_paths]
111
112 if enable_default_plugins:
113 for path in plugin_paths:
114 if path not in self.plugin_paths:
115 self.plugin_paths.append(path)
116 else:
117 self.plugin_paths = plugin_paths
118 elif not plugin_paths and not enable_default_plugins:
119
120
121 try:
122 raise ValueError('Incompatible init params... plugin_paths is empty and enable_default_plugins=False')
123 except:
124 _logger.exception('Must provide a list for plugin_paths or set enable_default_plugins=True. Nothing to do.')
125 raise
126
127 for path in self.plugin_paths:
128 sys.path.insert(0, path)
129 _logger.debug('Setting plugin_paths to: ["%s"]', '", "'.join(self.plugin_paths))
130 self.__load_plugins(self.system_class, self.system)
131
132
134 """
135 Loads a profiler plugin
136
137 @param profiler: The name of a class that extends L{ProfilerBase}
138 @type profiler: C{str}
139 @param package: The name of the package where the profiler exists
140 @type package: C{str}
141 """
142 return self.__load_module(profiler, package)
143
144
146 """
147 Imports plugin modules and stores the list of successfully loaded plugins
148
149 @param plugin_module: A specific plugin_module to load
150 @type plugin_module: C{str}
151 """
152 _logger.info('Loading plugins...')
153 if plugin_module:
154 raise NotImplementedError('Planned for future release')
155 else:
156 plugin_map = self.__resolve_plugin_paths(system_class, system)
157 for plugin, (path, namespace) in plugin_map.items():
158 _logger.debug('Loading plugin: %s, path: %s, package: %s', plugin, path, namespace)
159 if path not in sys.path:
160 sys.path.insert(0, path)
161 try:
162 plugin_class = '%s%s' % (utils._underscore_to_camel_case(plugin), 'Plugin')
163 self.plugins[plugin] = self.__load_module(namespace, plugin_class)
164 _logger.debug('Loaded plugin: %s [%s::%s]', plugin, namespace, plugin_class)
165 except:
166 _logger.exception('Failed to load plugin: %s [%s::%s]', plugin, namespace, plugin_class)
167
168
170 """
171 Loads a module and class dynamically return a reference to the class
172
173 @param module: The name of module to load
174 @type module: C{str}
175 @param cls: The name of a class to load
176 @type cls: C{str}
177 @return: A reference to the loaded class
178 @rtype: C{class}
179 """
180 try:
181 module_instance = __import__(module, globals(), locals(), [cls])
182 if cls and hasattr(module_instance, cls):
183 _logger.debug('Successfully loaded module: %s, class: %s', module, cls)
184 return getattr(module_instance, cls)
185 except:
186 _logger.exception('Failed to import module: %s, class: %s', module, cls)
187 raise
188
189
191 """
192 Checks plugin paths for validity and returns only those that are valid
193
194 @param system: The type of system
195 @type system: C{str}
196 @keyword plugin_paths: A path (or list of paths) to a custom set of plugins
197 @type plugin_paths: C{str}|C{list}
198 @return: A list of valid plugin paths
199 @rtype: C{list}
200 """
201 plugins = {}
202 plugin_paths = kwargs.get('plugin_paths', self.plugin_paths)
203 for plugin_path in plugin_paths:
204 _logger.debug('Searching plugin path: %s', plugin_path)
205 system_class_path = os.path.join(plugin_path, system_class)
206 system_path = os.path.join(system_class_path, system)
207
208 valid_plugins = self.__validate_plugins(plugin_path)
209 valid_plugins = self.__validate_plugins(system_class_path, system_class, valid_plugins)
210 valid_plugins = self.__validate_plugins(system_path, '%s.%s' % (system_class, system), valid_plugins)
211
212 if valid_plugins is not None and len(valid_plugins) > 0:
213 _logger.debug('Merging plugins dictionaries')
214 plugins = dict(plugins.items() + valid_plugins.items())
215
216 return plugins
217
218
220 """
221 Performs an initial sanity check on all the plugins found in a path
222
223 @param path: The path to look for plugins
224 @type path: C{str}
225 @param base: The base of the package name if path is a subfolder of a python package
226 - I{To assist with the import, i.e. from package.module import plugin}
227 @type base: C{str}
228 """
229 if plugins is None:
230 _logger.debug('Creating plugins dict')
231 plugins = {}
232
233 if os.path.exists(path) and os.path.isdir(path):
234 for plugin_entry in os.listdir(path):
235 _logger.debug('Validating file as plugin: %s', plugin_entry)
236 if plugin_entry in PLUGIN_LOADER_EXCLUSIONS or os.path.isdir(plugin_entry) or not plugin_entry.endswith('.py') or plugin_entry.startswith('_'):
237 continue
238
239 plugin = plugin_entry[:-3]
240 package = plugin
241
242 if base is not None:
243 package = '%s.%s' % (base, plugin)
244 _logger.debug('package: %s', package)
245
246 _logger.debug('File appears to be a valid plugin: %s, package: %s [%s]', plugin, package, os.path.join(path, plugin_entry))
247
248 plugins[plugin] = (path, package)
249
250 return plugins
251
252
253 - def audit(self, convert_date_to_iso=True):
254 """
255 Profiles the system using the default plugins and all custom plugins, returning a dictionary of the results
256
257 @param convert_date_to_iso: Converts the 'audit_completed' date to iso format before returning
258 @type convert_date_to_iso: C{bool}
259 @return: A dictionary representing the current state of the system
260 @rtype: C{dict}
261 """
262 start = time.time()
263 if len(self.plugins) <= 0:
264
265 try:
266 raise ValueError('No plugins found')
267 except:
268 _logger.exception('List of plugins is empty. Nothing to do.')
269 raise
270
271 results = {'profile': self.profile}
272 for plugin_name, plugin_class in self.plugins.items():
273 _logger.debug('Running plugin: %s [%s]', plugin_name, plugin_class.__name__)
274 plugin = plugin_class(self.profile, results)
275 results[plugin_name] = plugin._get_results()
276 end = time.time()
277 now = datetime.datetime.now()
278 if convert_date_to_iso:
279 results['profile']['audit_completed'] = now.isoformat()
280 else:
281 results['profile']['audit_completed'] = now
282 results['profile']['audit_took_sec'] = end - start
283 return results
284
285
286
288 """
289 Instatiates a System object and executes it's profile method
290
291 @param plugin_paths: A path (or list of paths) to a custom set of plugins
292 @type plugin_paths: C{str}|C{list}
293 @keyword debug: Set the logging level to DEBUG
294 @type debug: C{bool}
295 """
296 debug = kwargs.pop('debug', False)
297
298 if debug:
299 _logger.setLevel(logging.DEBUG)
300
301 auditor = Auditor(plugin_paths, **kwargs)
302 return auditor.audit()
303