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 # As a special case, it is also possible to specify the IP address
70 linux_device1 manageIp=10.10.10.77, zSnmpCommunity='blue', zSnmpVer="v2c"
71 # A '\' at the end of the line allows you to place more
72 # expressions on a new line. Don't forget the comma...
73 linux_device2 discoverProto='none', zLinks="<a href='http://example.org'>Support site</a>", \
74 zTelnetEnable=True \
75 zTelnetPromptTimeout=15.3
76
77 # A new organizer drops all previous settings, and allows
78 # for new ones to be used. Settings do not span files.
79 /Devices/Server/Windows zWinUser="administrator", zWinPassword='fred'
80 # Bind templates
81 windows_device1 zDeviceTemplates=[ 'Device', 'myTemplate' ]
82 # Override the default from the organizer setting.
83 windows_device2 zWinUser="administrator", zWinPassword='thomas'
84
85 """
86
88 ZCmdBase.__init__(self)
89 self.defaults = {}
90
91 self.loader = self.dmd.DeviceLoader.loadDevice
92
93
94 self.loader_args = dict.fromkeys( self.loader.func_code.co_varnames )
95 unsupportable_args = [
96 'REQUEST', 'device', 'self', 'xmlrpc', 'e', 'handler',
97 ]
98 for opt in unsupportable_args:
99 if opt in self.loader_args:
100 del self.loader_args[opt]
101
103 """
104 Read through all of the files listed as arguments and
105 return a list of device entries.
106
107 @parameter args: list of filenames (uses self.args is this is None)
108 @type args: list of strings
109 @return: list of device specifications
110 @rtype: list of dictionaries
111 """
112 if args is None:
113 args = self.args
114
115 device_list = []
116 for filename in args:
117 try:
118 data = open(filename,'r').readlines()
119 except IOError:
120 self.log.critical("Unable to open the file '%s'" % filename)
121 continue
122
123 temp_dev_list = self.parseDevices(data)
124 if temp_dev_list:
125 device_list += temp_dev_list
126
127 return device_list
128
130 """
131 Apply zProperty settings (if any) to the device.
132
133 @parameter device: device to modify
134 @type device: DMD device object
135 @parameter device_specs: device creation dictionary
136 @type device_specs: dictionary
137 """
138 self.log.debug( "Applying zProperties.." )
139
140
141 dev_zprops = dict( device.zenPropertyItems() )
142
143 for zprop, value in device_specs.items():
144 if not iszprop(zprop):
145 continue
146
147 if zprop in dev_zprops:
148 try:
149 device.setZenProperty(zprop, value)
150 except BadRequest:
151 self.log.warn( "Device %s zproperty %s is invalid or duplicate" % (
152 device_specs['deviceName'], zprop) )
153 else:
154 self.log.debug( "The zproperty %s doesn't exist in %s" % (
155 zprop, device_specs['deviceName']))
156
158 """
159 Read the input and process the devices
160 * create the device entry
161 * set zproperties
162 * model the device
163
164 @parameter device_list: list of device entries
165 @type device_list: list of dictionaries
166 """
167 processed = 0
168 for device_specs in device_list:
169 devobj = self.getDevice(device_specs)
170 if devobj is None:
171 continue
172
173 if device_specs.get('manageIp', ''):
174 manageIp = device_specs['manageIp']
175 devobj.setManageIp(manageIp)
176 self.log.info("Setting %s IP address to '%s'",
177 devobj.id, manageIp)
178
179 self.applyZProps(devobj, device_specs)
180
181
182 if device_specs.get('discoverProto', '') != 'none':
183
184 devobj.manage_snmpCommunity()
185
186
187 commit()
188 try:
189 devobj.collectDevice(setlog=self.options.showModelOutput)
190 except (SystemExit, KeyboardInterrupt):
191 self.log.info("User interrupted modeling")
192 break
193 except Exception, ex:
194 self.log.exception("Modeling error" )
195
196 commit()
197 processed += 1
198
199 self.log.info( "Processed %d of %d devices" % (processed, len(device_list)))
200
202 """
203 Find or create the specified device
204
205 @parameter device_specs: device creation dictionary
206 @type device_specs: dictionary
207 @return: device or None
208 @rtype: DMD device object
209 """
210 if 'deviceName' not in device_specs:
211 return None
212 name = device_specs['deviceName']
213 devobj = self.dmd.Devices.findDevice(name)
214 if devobj is not None:
215 self.log.info("Found existing device %s" % name)
216 return devobj
217
218 specs = {}
219 for key in self.loader_args:
220 if key in device_specs:
221 specs[key] = device_specs[key]
222
223 try:
224 self.log.info("Creating device %s" % name)
225
226
227 specs['discoverProto'] = 'none'
228
229 self.loader(**specs)
230 devobj = self.dmd.Devices.findDevice(name)
231 if devobj is None:
232 self.log.error("Unable to find newly created device %s -- skipping" \
233 % name)
234 except Exception, ex:
235 self.log.exception("Unable to load %s -- skipping" % name )
236
237 return devobj
238
240 """
241 Add our command-line options to the basics
242 """
243 ZCmdBase.buildOptions(self)
244
245 self.parser.add_option('--show_options',
246 dest="show_options", default=False,
247 action="store_true",
248 help="Show the various options understood by the loader")
249
250 self.parser.add_option('--sample_configs',
251 dest="sample_configs", default=False,
252 action="store_true",
253 help="Show an example configuration file.")
254
255 self.parser.add_option('--showModelOutput',
256 dest="showModelOutput", default=True,
257 action="store_false",
258 help="Show modelling activity")
259
261 """
262 From the list of strings in rawDevices, construct a list
263 of device dictionaries, ready to load into Zenoss.
264
265 @parameter data: list of strings representing device entries
266 @type data: list of strings
267 @return: list of parsed device entries
268 @rtype: list of dictionaries
269 """
270 if not data:
271 return []
272
273 comment = re.compile(r'#.*')
274
275 defaults = {'devicePath':"/Discovered" }
276 finalList = []
277 i = 0
278 while i < len(data):
279 line = data[i]
280 line = re.sub(comment, '', line).strip()
281 if line == '':
282 i += 1
283 continue
284
285
286 while line[-1] == '\\' and i < len(data):
287 i += 1
288 line = line[:-1] + data[i]
289 line = re.sub(comment, '', line).strip()
290
291 if line[0] == '/':
292 defaults = self.parseDeviceEntry(line, {})
293 defaults['devicePath'] = defaults['deviceName']
294 del defaults['deviceName']
295
296 else:
297 configs = self.parseDeviceEntry(line, defaults)
298 if configs:
299 finalList.append(configs)
300 i += 1
301
302 return finalList
303
304 - def parseDeviceEntry(self, line, defaults):
305 """
306 Build a dictionary of properties from one line's input
307
308 @parameter line: string containing one device's info
309 @type line: string
310 @parameter defaults: dictionary of default settings
311 @type defaults: dictionary
312 @return: parsed device entry
313 @rtype: dictionary
314 """
315 options = None
316 if line.find(' ') > 0:
317 name, options = line.split(None, 1)
318 else:
319 name = line
320
321 configs = defaults.copy()
322 configs['deviceName'] = name
323
324 if options:
325 try:
326 configs.update( eval( 'dict(' + options + ')' ) )
327 except:
328 self.log.error( "Unable to parse the entry for %s -- skipping" % name )
329 self.log.error( "Raw string: %s" % options )
330 return None
331
332 return configs
333
334
335 if __name__=='__main__':
336 batchLoader = BatchDeviceLoader()
337
338 if batchLoader.options.show_options:
339 print "Options = %s" % sorted( batchLoader.loader_args.keys() )
340 help(batchLoader.loader)
341 sys.exit(0)
342
343 if batchLoader.options.sample_configs:
344 print batchLoader.sample_configs
345 sys.exit(0)
346
347 device_list = batchLoader.loadDeviceList()
348 batchLoader.processDevices(device_list)
349