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 try:
184 devobj.collectDevice(setlog=self.options.showModelOutput)
185 except (SystemExit, KeyboardInterrupt):
186 self.log.info("User interrupted modelling")
187 break
188 except Exception, ex:
189 self.log.exception("Modelling error" )
190
191 commit()
192 processed += 1
193
194 self.log.info( "Processed %d of %d devices" % (processed, len(device_list)))
195
197 """
198 Find or create the specified device
199
200 @parameter device_specs: device creation dictionary
201 @type device_specs: dictionary
202 @return: device or None
203 @rtype: DMD device object
204 """
205 if 'deviceName' not in device_specs:
206 return None
207 name = device_specs['deviceName']
208 devobj = self.dmd.Devices.findDevice(name)
209 if devobj is not None:
210 self.log.info("Found existing device %s" % name)
211 return devobj
212
213 specs = {}
214 for key in self.loader_args:
215 if key in device_specs:
216 specs[key] = device_specs[key]
217
218 try:
219 self.log.info("Creating device %s" % name)
220
221
222 specs['discoverProto'] = 'none'
223
224 self.loader(**specs)
225 devobj = self.dmd.Devices.findDevice(name)
226 if devobj is None:
227 self.log.error("Unable to find newly created device %s -- skipping" \
228 % name)
229 except Exception, ex:
230 self.log.exception("Unable to load %s -- skipping" % name )
231
232 return devobj
233
235 """
236 Add our command-line options to the basics
237 """
238 ZCmdBase.buildOptions(self)
239
240 self.parser.add_option('--show_options',
241 dest="show_options", default=False,
242 action="store_true",
243 help="Show the various options understood by the loader")
244
245 self.parser.add_option('--sample_configs',
246 dest="sample_configs", default=False,
247 action="store_true",
248 help="Show an example configuration file.")
249
250 self.parser.add_option('--showModelOutput',
251 dest="showModelOutput", default=True,
252 action="store_false",
253 help="Show modelling activity")
254
256 """
257 From the list of strings in rawDevices, construct a list
258 of device dictionaries, ready to load into Zenoss.
259
260 @parameter data: list of strings representing device entries
261 @type data: list of strings
262 @return: list of parsed device entries
263 @rtype: list of dictionaries
264 """
265 if not data:
266 return []
267
268 comment = re.compile(r'#.*')
269
270 defaults = {'devicePath':"/Discovered" }
271 finalList = []
272 i = 0
273 while i < len(data):
274 line = data[i]
275 line = re.sub(comment, '', line).strip()
276 if line == '':
277 i += 1
278 continue
279
280
281 while line[-1] == '\\' and i < len(data):
282 i += 1
283 line = line[:-1] + data[i]
284 line = re.sub(comment, '', line).strip()
285
286 if line[0] == '/':
287 defaults = self.parseDeviceEntry(line, {})
288 defaults['devicePath'] = defaults['deviceName']
289 del defaults['deviceName']
290
291 else:
292 configs = self.parseDeviceEntry(line, defaults)
293 if configs:
294 finalList.append(configs)
295 i += 1
296
297 return finalList
298
299 - def parseDeviceEntry(self, line, defaults):
300 """
301 Build a dictionary of properties from one line's input
302
303 @parameter line: string containing one device's info
304 @type line: string
305 @parameter defaults: dictionary of default settings
306 @type defaults: dictionary
307 @return: parsed device entry
308 @rtype: dictionary
309 """
310 options = None
311 if line.find(' ') > 0:
312 name, options = line.split(' ', 1)
313 else:
314 name = line
315
316 configs = defaults.copy()
317 configs['deviceName'] = name
318
319 if options:
320 try:
321 configs.update( eval( 'dict(' + options + ')' ) )
322 except:
323 self.log.error( "Unable to parse the entry for %s -- skipping" % name )
324 self.log.error( "Raw string: %s" % options )
325 return None
326
327 return configs
328
329
330 if __name__=='__main__':
331 batchLoader = BatchDeviceLoader()
332
333 if batchLoader.options.show_options:
334 print "Options = %s" % sorted( batchLoader.loader_args.keys() )
335 help(batchLoader.loader)
336 sys.exit(0)
337
338 if batchLoader.options.sample_configs:
339 print batchLoader.sample_configs
340 sys.exit(0)
341
342 device_list = batchLoader.loadDeviceList()
343 batchLoader.processDevices(device_list)
344