Package ZenRelations :: Module ImportRM
[hide private]
[frames] | no frames]

Source Code for Module ZenRelations.ImportRM

  1  #! /usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3  # ########################################################################## 
  4  # 
  5  # This program is part of Zenoss Core, an open source monitoring platform. 
  6  # Copyright (C) 2009, Zenoss Inc. 
  7  # 
  8  # This program is free software; you can redistribute it and/or modify it 
  9  # under the terms of the GNU General Public License version 2 as published by 
 10  # the Free Software Foundation. 
 11  # 
 12  # For complete information please visit: http://www.zenoss.com/oss/ 
 13  # 
 14  # ########################################################################## 
 15   
 16  __doc__ = """ImportRM 
 17  Import RelationshipManager objects into a Zope database 
 18  This provides support methods used by the Python xml.sax library to 
 19  parse and construct an XML document. 
 20   
 21  Descriptions of the XML document format can be found in the 
 22  Developers Guide. 
 23  """ 
 24   
 25  import sys 
 26  import os 
 27  import types 
 28  import transaction 
 29  from DateTime import DateTime 
 30  from xml.sax import make_parser, saxutils 
 31  from xml.sax.handler import ContentHandler 
 32   
 33  from Acquisition import aq_base 
 34  from zExceptions import NotFound 
 35   
 36  from Products.ZenUtils.ZCmdBase import ZCmdBase 
 37  from Products.ZenUtils.Utils import importClass 
 38  from Products.ZenUtils.Utils import getObjByPath 
 39   
 40  from Products.ZenRelations.Exceptions import * 
 41   
 42   
43 -class ImportRM(ZCmdBase, ContentHandler):
44 """ 45 Wrapper module to interface between Zope and the Python SAX XML library. 46 The xml.sax.parse() calls different routines depending on what it finds. 47 48 A simple example of a valid XML file can be found in the objects.xml file 49 for a ZenPack. 50 51 <?xml version="1.0"?> 52 <objects> 53 <!-- ('', 'zport', 'dmd', 'Devices', 'rrdTemplates', 'HelloWorld') --> 54 <object id='/zport/dmd/Devices/rrdTemplates/HelloWorld' module='Products.ZenModel.RRDTemplate' class='RRDTemplate'> 55 <property type="text" id="description" mode="w" > This is the glorious description that shows up when we click on our RRD template </property> 56 <tomanycont id='datasources'> 57 <object id='hello' module='Products.ZenModel.BasicDataSource' class='BasicDataSource'> 58 <property select_variable="sourcetypes" type="selection" id="sourcetype" mode="w" > SNMP </property> 59 <property type="boolean" id="enabled" mode="w" > True </property> 60 <property type="string" id="eventClass" mode="w" > /Cmd/Fail </property> 61 <property type="int" id="severity" mode="w" > 3 </property> 62 <property type="int" id="cycletime" mode="w" > 300 </property> 63 <property type="boolean" id="usessh" mode="w" > False </property> 64 <tomanycont id='datapoints'> 65 <object id='hello' module='Products.ZenModel.RRDDataPoint' class='RRDDataPoint'> 66 <property select_variable="rrdtypes" type="selection" id="rrdtype" mode="w" > GAUGE </property> 67 <property type="boolean" id="isrow" mode="w" > True </property> 68 </object> 69 </tomanycont> 70 </object> 71 72 <!-- snip --> 73 74 </objects> 75 """ 76 rootpath = '' 77 skipobj = 0 78
79 - def __init__(self, noopts=0, app=None, keeproot=False):
80 """ 81 Initializer 82 83 @param noopts: don't use sys.argv for command-line options 84 @type noopts: boolean 85 @param app: app 86 @type app: object 87 @param keeproot: keeproot 88 @type keeproot: boolean 89 """ 90 ZCmdBase.__init__(self, noopts, app, keeproot) 91 ContentHandler.__init__(self)
92
93 - def context(self):
94 """ 95 Return the bottom object in the stack 96 97 @return: 98 @rtype: object 99 """ 100 return self.objstack[-1]
101
102 - def cleanattrs(self, attrs):
103 """ 104 Convert all attributes to string values 105 106 @param attrs: (key,val) pairs 107 @type attrs: list 108 @return: same list, but with all values as strings 109 @rtype: list 110 """ 111 myattrs = {} 112 for (key, val) in attrs.items(): 113 myattrs[key] = str(val) 114 return myattrs
115
116 - def startElement(self, name, attrs):
117 """ 118 Function called when the parser finds the starting element 119 in the XML file. 120 121 @param name: name of the element 122 @type name: string 123 @param attrs: list of (key, value) tuples 124 @type attrs: list 125 """ 126 ignoredElements = [ 'objects' ] 127 attrs = self.cleanattrs(attrs) 128 if self.skipobj > 0: 129 self.skipobj += 1 130 return 131 132 self.log.debug('tag %s, context %s, line %s' % ( 133 name, self.context().id, self._locator.getLineNumber() )) 134 135 if name == 'object': 136 137 obj = self.createObject(attrs) 138 139 if obj is None: 140 formattedAttrs = '' 141 for key, value in attrs.items(): 142 formattedAttrs += ' * %s: %s\n' % (key, value) 143 raise Exception('Unable to create object using the following ' 144 'attributes:\n%s' % formattedAttrs) 145 146 if not self.options.noindex and hasattr(aq_base(obj), 147 'reIndex') and not self.rootpath: 148 self.rootpath = obj.getPrimaryId() 149 150 self.objstack.append(obj) 151 152 elif name == 'tomanycont' or name == 'tomany': 153 nextobj = self.context()._getOb(attrs['id'], None) 154 if nextobj is None: 155 self.skipobj = 1 156 return 157 else: 158 self.objstack.append(nextobj) 159 elif name == 'toone': 160 relname = attrs.get('id') 161 self.log.debug('toone %s, on object %s', relname, 162 self.context().id) 163 rel = getattr(aq_base(self.context()), relname, None) 164 if rel is None: 165 return 166 objid = attrs.get('objid') 167 self.addLink(rel, objid) 168 elif name == 'link': 169 self.addLink(self.context(), attrs['objid']) 170 elif name == 'property': 171 self.curattrs = attrs 172 elif name in ignoredElements: 173 pass 174 else: 175 self.log.warning( "Ignoring an unknown XML element type: %s" % name )
176
177 - def endElement(self, name):
178 """ 179 Function called when the parser finds the starting element 180 in the XML file. 181 182 @param name: name of the ending element 183 @type name: string 184 """ 185 ignoredElements = [ 'toone', 'link' ] 186 if self.skipobj > 0: 187 self.skipobj -= 1 188 return 189 190 if name in ('object', 'tomany', 'tomanycont'): 191 obj = self.objstack.pop() 192 if hasattr(aq_base(obj), 'index_object'): 193 obj.index_object() 194 if self.rootpath == obj.getPrimaryId(): 195 self.log.info('Calling reIndex %s', obj.getPrimaryId()) 196 obj.reIndex() 197 self.rootpath = '' 198 199 elif name == 'objects': # ie end of the file 200 self.log.info('End loading objects') 201 self.log.info('Processing links') 202 self.processLinks() 203 if not self.options.noCommit: 204 self.commit() 205 self.log.info('Loaded %d objects into the ZODB database' 206 % self.objectnumber) 207 else: 208 self.log.info('Would have created %d objects in the ZODB database' 209 % self.objectnumber) 210 211 elif name == 'property': 212 self.setProperty(self.context(), self.curattrs, 213 self.charvalue) 214 # We've closed off a tag, so now we need to re-initialize 215 # the area that stores the contents of elements 216 self.charvalue = '' 217 218 elif name in ignoredElements: 219 pass 220 else: 221 self.log.warning( "Ignoring an unknown XML element type: %s" % name )
222
223 - def characters(self, chars):
224 """ 225 Called by xml.sax.parse() with data found in an element 226 eg <object>my characters stuff</object> 227 228 Note that this can be called character by character. 229 230 @param chars: chars 231 @type chars: string 232 """ 233 self.charvalue += saxutils.unescape(chars)
234
235 - def createObject(self, attrs):
236 """ 237 Create an object and set it into its container 238 239 @param attrs: attrs 240 @type attrs: string 241 @return: newly created object 242 @rtype: object 243 """ 244 # Does the object exist already? 245 id = attrs.get('id') 246 obj = None 247 try: 248 if id.startswith('/'): 249 obj = getObjByPath(self.app, id) 250 else: 251 obj = self.context()._getOb(id) 252 except (KeyError, AttributeError, NotFound): 253 pass 254 255 if obj is None: 256 klass = importClass(attrs.get('module'), attrs.get('class')) 257 if id.find('/') > -1: 258 (contextpath, id) = os.path.split(id) 259 try: 260 pathobj = getObjByPath(self.context(), contextpath) 261 except (KeyError, AttributeError, NotFound): 262 self.log.warn( "Unable to find context path %s (line %s ?) for %s" % ( 263 contextpath, self._locator.getLineNumber(), id )) 264 if not self.options.noCommit: 265 self.log.warn( "Not committing any changes" ) 266 self.options.noCommit = True 267 return None 268 self.objstack.append(pathobj) 269 obj = klass(id) 270 self.context()._setObject(obj.id, obj) 271 obj = self.context()._getOb(obj.id) 272 self.objectnumber += 1 273 if self.objectnumber % 5000 == 0: 274 transaction.savepoint() 275 self.log.debug('Added object %s to database' 276 % obj.getPrimaryId()) 277 else: 278 self.log.debug('Object %s already exists -- skipping' % id) 279 return obj
280
281 - def setProperty(self, obj, attrs, value):
282 """ 283 Set the value of a property on an object. 284 285 @param obj: obj 286 @type obj: string 287 @param attrs: attrs 288 @type attrs: string 289 @param value: value 290 @type value: string 291 @return: 292 @rtype: 293 """ 294 name = attrs.get('id') 295 proptype = attrs.get('type') 296 setter = attrs.get('setter', None) 297 self.log.debug('Setting object %s attr %s type %s value %s' 298 % (obj.id, name, proptype, value)) 299 linenum = self._locator.getLineNumber() 300 301 # Sanity check the value 302 value = value.strip() 303 try: 304 value = str(value) 305 except UnicodeEncodeError: 306 self.log.warn('UnicodeEncodeError at line %s while attempting' % linenum + \ 307 ' str(%s) for object %s attribute %s -- ignoring' % ( 308 obj.id, name, proptype, value)) 309 310 # Guess at how to interpret the value given the property type 311 if proptype == 'selection': 312 try: 313 firstElement = getattr(obj, name)[0] 314 if type(firstElement) in types.StringTypes: 315 proptype = 'string' 316 except (TypeError, IndexError): 317 self.log.warn("Error at line %s when trying to " % linenum + \ 318 " use (%s) as the value for object %s attribute %s -- assuming a string" 319 % (obj.id, name, proptype, value)) 320 proptype = 'string' 321 322 if proptype == 'date': 323 try: 324 value = float(value) 325 except ValueError: 326 pass 327 value = DateTime(value) 328 329 elif proptype != 'string' and proptype != 'text': 330 try: 331 value = eval(value) 332 except SyntaxError: 333 self.log.debug("Non-fatal SyntaxError at line %s while eval'ing '%s'" % ( 334 linenum, value) ) 335 336 # Actually use the value 337 if not obj.hasProperty(name): 338 obj._setProperty(name, value, type=proptype, setter=setter) 339 else: 340 obj._updateProperty(name, value)
341 353 369
370 - def buildOptions(self):
371 """ 372 Command-line options specific to this command 373 """ 374 ZCmdBase.buildOptions(self) 375 self.parser.add_option('-i', '--infile', dest='infile', 376 help='Input file for import. Default is stdin' 377 ) 378 self.parser.add_option('--noindex', dest='noindex', 379 action='store_true', default=False, 380 help='Do not try to index the freshly loaded objects.' 381 ) 382 self.parser.add_option( 383 '-n', 384 '--noCommit', 385 dest='noCommit', 386 action='store_true', 387 default=0, 388 help='Do not store changes to the DMD (ie for debugging purposes)', 389 )
390
391 - def loadObjectFromXML(self, xmlfile=''):
392 """ 393 This method can be used to load data for the root of Zenoss (default 394 behavior) or it can be used to operate on a specific point in the 395 Zenoss hierarchy (ZODB). 396 397 Upon loading the XML file to be processed, the content of the XML file 398 is handled by the methods in this class when called by xml.sax.parse(). 399 400 Reads from a file if xmlfile is specified, otherwise reads 401 from the command-line option --infile. If no files are found from 402 any of these places, read from standard input. 403 404 @param xmlfile: Name of XML file to load, or file-like object 405 @type xmlfile: string or file-like object 406 """ 407 self.objstack = [self.app] 408 self.links = [] 409 self.objectnumber = 0 410 self.charvalue = '' 411 if xmlfile and type(xmlfile) in types.StringTypes: 412 self.infile = open(xmlfile) 413 elif hasattr(xmlfile, 'read'): 414 self.infile = xmlfile 415 elif self.options.infile: 416 self.infile = open(self.options.infile) 417 else: 418 self.infile = sys.stdin 419 parser = make_parser() 420 parser.setContentHandler(self) 421 try: 422 parser.parse(self.infile) 423 finally: 424 self.infile.close()
425
426 - def loadDatabase(self):
427 """ 428 The default behavior of loadObjectFromXML() will be to use the Zope 429 app object, and thus operatate on the whole of Zenoss. 430 """ 431 self.loadObjectFromXML()
432
433 - def commit(self):
434 """ 435 Wrapper around the Zope database commit() 436 """ 437 trans = transaction.get() 438 trans.note('Import from file %s using %s' 439 % (self.options.infile, self.__class__.__name__)) 440 trans.commit()
441 442
443 -class SpoofedOptions(object):
444 """ 445 SpoofedOptions 446 """ 447
448 - def __init__(self):
449 self.infile = '' 450 self.noCommit = True 451 self.noindex = True
452 453
454 -class NoLoginImportRM(ImportRM):
455 """ 456 An ImportRM that does not call the __init__ method on ZCmdBase 457 """ 458
459 - def __init__(self, app):
460 """ 461 Initializer 462 463 @param app: app 464 @type app: string 465 """ 466 self.app = app 467 ContentHandler.__init__(self) 468 import logging 469 self.log = logging.getLogger('zen.ImportRM') 470 self.options = SpoofedOptions()
471 472 473 if __name__ == '__main__': 474 im = ImportRM() 475 im.loadDatabase() 476