1
2
3
4
5
6
7
8
9
10
11
12
13 __doc__ = """BatchDeviceLoader.py
14
15 zenbatchload loads a list of devices read from a file.
16 """
17
18 import sys
19 import re
20
21 import Globals
22 from transaction import commit
23
24 from Products.ZenUtils.ZCmdBase import ZCmdBase
25 from Products.ZenRelations.ZenPropertyManager import iszprop
26 from zExceptions import BadRequest
27
28
30 """
31 Base class wrapping around dmd.DeviceLoader
32 """
33
34 sample_configs = """#
35 # Example zenbatchloader device file
36 #
37 # This file is formatted with one entry per line, like this:
38 #
39 # /Devices/device_class_name Python-expression
40 # hostname Python-expression
41 #
42 # For organizers (ie the /Devices path), the Python-expression
43 # is used to define defaults to be used for devices listed
44 # after the organizer. The defaults that can be specified are:
45 #
46 # * loader arguments (use the --show_options flag to show these)
47 # * zPropertie (from a device, use the More -> zProperties
48 # menu option to see the available ones.)
49 #
50 # NOTE: new zProperties *cannot* be created through this file
51 #
52 # The Python-expression is used to create a dictionary of settings.
53 # device_settings = eval( 'dict(' + python-expression + ')' )
54 #
55
56
57 # If no organizer is specified at the beginning of the file,
58 # defaults to the /Devices/Discovered device class.
59 device0 comments="A simple device"
60 # All settings must be seperated by a comma.
61 device1 comments="A simple device" zSnmpCommunity='blue', zSnmpVer='v1'
62
63 # Notes for this file:
64 # * Organizer names cannot contain spaces
65 # * Oraganizer names *must* start with '/'
66 #
67 /Devices/Server/Linux zSnmpPort=1543
68 # Python strings can use either ' or " -- there's no difference.
69 linux_device1 zSnmpCommunity='blue', zSnmpVer="v2c"
70 # A '\' at the end of the line allows you to place more
71 # expressions on a new line. Don't forget the comma...
72 linux_device2 discoverProto='none', zLinks="<a href='http://example.org'>Support site</a>", \
73 zTelnetEnable=True \
74 zTelnetPromptTimeout=15.3
75
76 # A new organizer drops all previous settings, and allows
77 # for new ones to be used. Settings do not span files.
78 /Devices/Server/Windows zWinUser="administrator", zWinPassword='fred'
79 # Bind templates
80 windows_device1 zDeviceTemplates=[ 'Device', 'myTemplate' ]
81 # Override the default from the organizer setting.
82 windows_device2 zWinUser="administrator", zWinPassword='thomas'
83
84 """
85
87 ZCmdBase.__init__(self)
88 self.defaults = {}
89
90 self.loader = self.dmd.DeviceLoader.loadDevice
91
92
93 self.loader_args = dict.fromkeys( self.loader.func_code.co_varnames )
94 unsupportable_args = [
95 'REQUEST', 'device', 'self', 'xmlrpc', 'e', 'handler',
96 ]
97 for opt in unsupportable_args:
98 if self.loader_args.has_key(opt):
99 del self.loader_args[opt]
100
102 """
103 Read through all of the files listed as arguments and
104 return a list of device entries.
105
106 @parameter args: list of filenames (uses self.args is this is None)
107 @type args: list of strings
108 @return: list of device specifications
109 @rtype: list of dictionaries
110 """
111 if args is None:
112 args = self.args
113
114 device_list = []
115 for filename in args:
116 try:
117 data = open(filename,'r').readlines()
118 except IOError:
119 self.log.critical("Unable to open the file '%s'" % filename)
120 continue
121
122 temp_dev_list = self.parseDevices(data)
123 if temp_dev_list:
124 device_list += temp_dev_list
125
126 return device_list
127
129 """
130 Apply zProperty settings (if any) to the device.
131
132 @parameter device: device to modify
133 @type device: DMD device object
134 @parameter device_specs: device creation dictionary
135 @type device_specs: dictionary
136 """
137 self.log.debug( "Applying zProperties.." )
138
139
140 dev_zprops = dict( device.zenPropertyItems() )
141
142 for zprop, value in device_specs.items():
143 if not iszprop(zprop):
144 continue
145
146 if zprop in dev_zprops:
147 try:
148 device.setZenProperty(zprop, value)
149 except BadRequest:
150 self.log.warn( "Device %s zproperty %s is invalid or duplicate" % (
151 device_specs['deviceName'], zprop) )
152 else:
153 self.log.debug( "The zproperty %s doesn't exist in %s" % (
154 zprop, device_specs['deviceName']))
155
157 """
158 Read the input and process the devices
159 * create the device entry
160 * set zproperties
161 * model the device
162
163 @parameter device_list: list of device entries
164 @type device_list: list of dictionaries
165 """
166 processed = 0
167 for device_specs in device_list:
168 devobj = self.getDevice(device_specs)
169 if devobj is None:
170 continue
171
172 self.applyZProps(devobj, device_specs)
173
174
175 if device_specs.get('discoverProto', '') != 'none':
176 try:
177 devobj.collectDevice(setlog=self.options.showModelOutput)
178 except (SystemExit, KeyboardInterrupt):
179 self.log.info("User interrupted modelling")
180 break
181 except Exception, ex:
182 self.log.exception("Modelling error" )
183
184 commit()
185 processed += 1
186
187 self.log.info( "Processed %d of %d devices" % (processed, len(device_list)))
188
190 """
191 Find or create the specified device
192
193 @parameter device_specs: device creation dictionary
194 @type device_specs: dictionary
195 @return: device or None
196 @rtype: DMD device object
197 """
198 if not device_specs.has_key('deviceName'):
199 return None
200 name = device_specs['deviceName']
201 devobj = self.dmd.Devices.findDevice(name)
202 if devobj is not None:
203 self.log.info("Found existing device %s" % name)
204 return devobj
205
206 specs = {}
207 for key in self.loader_args:
208 if key in device_specs:
209 specs[key] = device_specs[key]
210
211 try:
212 self.log.info("Creating device %s" % name)
213
214
215 specs['discoverProto'] = 'none'
216
217 self.loader(**specs)
218 devobj = self.dmd.Devices.findDevice(name)
219 if devobj is None:
220 self.log.error("Unable to find newly created device %s -- skipping" \
221 % name)
222 except Exception, ex:
223 self.log.exception("Unable to load %s -- skipping" % name )
224
225 return devobj
226
228 """
229 Add our command-line options to the basics
230 """
231 ZCmdBase.buildOptions(self)
232
233 self.parser.add_option('--show_options',
234 dest="show_options", default=False,
235 action="store_true",
236 help="Show the various options understood by the loader")
237
238 self.parser.add_option('--sample_configs',
239 dest="sample_configs", default=False,
240 action="store_true",
241 help="Show an example configuration file.")
242
243 self.parser.add_option('--showModelOutput',
244 dest="showModelOutput", default=True,
245 action="store_false",
246 help="Show modelling activity")
247
249 """
250 From the list of strings in rawDevices, construct a list
251 of device dictionaries, ready to load into Zenoss.
252
253 @parameter data: list of strings representing device entries
254 @type data: list of strings
255 @return: list of parsed device entries
256 @rtype: list of dictionaries
257 """
258 if not data:
259 return []
260
261 comment = re.compile(r'#.*')
262
263 defaults = {'devicePath':"/Discovered" }
264 finalList = []
265 i = 0
266 while i < len(data):
267 line = data[i]
268 line = re.sub(comment, '', line).strip()
269 if line == '':
270 i += 1
271 continue
272
273
274 while line[-1] == '\\' and i < len(data):
275 i += 1
276 line = line[:-1] + data[i]
277 line = re.sub(comment, '', line).strip()
278
279 if line[0] == '/':
280 defaults = self.parseDeviceEntry(line, {})
281 defaults['devicePath'] = defaults['deviceName']
282 del defaults['deviceName']
283
284 else:
285 configs = self.parseDeviceEntry(line, defaults)
286 if configs:
287 finalList.append(configs)
288 i += 1
289
290 return finalList
291
292 - def parseDeviceEntry(self, line, defaults):
293 """
294 Build a dictionary of properties from one line's input
295
296 @parameter line: string containing one device's info
297 @type line: string
298 @parameter defaults: dictionary of default settings
299 @type defaults: dictionary
300 @return: parsed device entry
301 @rtype: dictionary
302 """
303 options = None
304 if line.find(' ') > 0:
305 name, options = line.split(' ', 1)
306 else:
307 name = line
308
309 configs = defaults.copy()
310 configs['deviceName'] = name
311
312 if options:
313 try:
314 configs.update( eval( 'dict(' + options + ')' ) )
315 except:
316 self.log.error( "Unable to parse the entry for %s -- skipping" % name )
317 self.log.error( "Raw string: %s" % options )
318 return None
319
320 return configs
321
322
323 if __name__=='__main__':
324 batchLoader = BatchDeviceLoader()
325
326 if batchLoader.options.show_options:
327 print "Options = %s" % sorted( batchLoader.loader_args.keys() )
328 help(batchLoader.loader)
329 sys.exit(0)
330
331 if batchLoader.options.sample_configs:
332 print batchLoader.sample_configs
333 sys.exit(0)
334
335 device_list = batchLoader.loadDeviceList()
336 batchLoader.processDevices(device_list)
337