source: dassmodus/trunk/dassmodus/nosferatu/nosferatu/bacresources.py @ 959

Last change on this file since 959 was 959, checked in by pstorz, 10 years ago

fixed onlinehelp index

File size: 46.6 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4# $Id: bacresources.py 12826 2011-04-03 16:49:56Z pstorz $
5
6import time
7
8#from directive import *
9#from configrules import *
10from auto_configrules import *
11
12import logging
13from auto_onlinehelp import *
14logger = logging.getLogger('bacresources')
15
16import os
17import tarfile
18import tempfile
19import glob
20import shutil
21import string
22import random
23
24#TODO: we have a problem if inside of a password there is a special character like {}
25
26
27def genPw(self,length=32, chars=string.letters + string.digits):
28  # from http://code.activestate.com/recipes/59873-random-password-generation/
29  return ''.join([random.choice(chars) for i in range(length)])
30
31
32
33
34def config2blocks(configstring,  processEventsCallback = None):
35    """
36    Parses the configstring and returns a list of blocks.
37    Each block is represented by its start- and end characterindex
38    saved in 'start' and 'end'
39    the configstring is expected without comment lines
40    """
41    blockdepth = 0
42    blockstack = []
43    blocks = []
44    instring = False # are we in a string between (") ?
45    lastcharbackslash = False
46
47    for i in range(0, len(configstring)):
48     
49      #if processEventsCallback is not None and not (i%100):
50      ##  #print "calling processEventsCallback"
51      #  processEventsCallback(i)
52       
53      chr =  configstring[i]
54      if chr == '\\':
55        lastcharbackslash = True
56        #print 'foundbackslash'
57      else:
58        lastcharbackslash = False
59       
60      if chr == '"' and not lastcharbackslash:  # we may have a brace in a password, but can escape with \"
61        instring = not instring                 # TODO:password with { or } inside this still make problems!
62       
63      if chr == '{' and not instring:
64        blockdepth += 1
65        #print '\n ',blockdepth,' **{** ->',configstring[i-10:i+20],'<-',
66        b = {}
67        b['start'] = i
68        b['level'] = blockdepth
69        blockstack.append(b) 
70           
71      elif chr == '}' and not instring:
72        blockdepth -= 1
73        b = blockstack.pop()
74        b['end'] = i
75        #print '\n ', blockdepth , ' **}** ->',configstring[i-10:i+1],'<-'
76        # find name just before the block beginning
77        s = configstring[:b['start']]
78        m = RXP_RESTYPE.search(s)
79        #logger.debug( "searching for ResType in >" + s + "<" )
80        if m:
81          b['namestart'] = b['start'] - len(m.group('resourcetype')) - 1 # start of resname......{ instead of {
82          b['name'] = m.group('resourcetype').strip(' =\n').replace(' ','') # there are also resname = {xxx}
83          #b['name'] = m.group('resourcetype').strip(' =\n') # there are also resname = {xxx}
84          if b['name'].endswith('\n'):
85            print '>>>>>>>>>>>>>>>>>>>>>>>>><>' + b['name'] + '<'
86          blocks.append(b)
87        else:
88          b['name'] = 'name not found!'
89          logger.error('Problem parsing the config file, regex did not match')
90         
91    if blockdepth != 0:
92      # TODO: throw exception
93      print "unbalanced braces in config2blocks"
94      return "unbalanced braces in config2blocks"
95    return blocks
96 
97
98
99
100'''
101Class for ressources inside of configurations
102hierarchy:
103Config -> Resource -> Items+
104                  |-> Resource -> Items
105'''
106
107class Resource(object):
108  """
109  This class represents a bacula resource .
110  On creation, it is automatically filled with the possible configuration items
111  for the specific resource type.
112  Some items can exist multiple times (like file=, plugin= etc.),
113  These are created one time and set to their default value
114  """
115  def __init__(self, parentconfig, resourcetype, indentlevel = 1, printall = False, onlinehelpURL = ''):
116  #def __init__(self, parentconfig, resourcetype,  name = None, indentlevel = 1, printall = False):
117    self.onlinehelpURL = onlinehelpURL
118    self.indentlevel = indentlevel
119    #print resourcetype, type(self.indentlevel) ,self.indentlevel
120    self.shortname = res2shrtn[resourcetype]
121    self.parentconfig = parentconfig # what configuration does this resource belong to?
122    #print "my parentconfig is" + repr(parentconfig)
123    self.printall = printall
124    self.resources = []
125    self.items = []
126    self.reflist = [] # what resources reference this resource?
127    if resourcetype == 'runscript':
128      items = eval ( "dird_"+ res2shrtn[resourcetype] + "_items[:]") #TODO : ensure this works
129      self.items = copy.deepcopy(items)
130      logging.debug('eval: ' + " dird_"+ res2shrtn[resourcetype] + "_items[:]") 
131    elif resourcetype == 'messages':
132      items = eval ( "lib_"+ res2shrtn[resourcetype] + "_items[:]") #TODO : ensure this works
133      self.items = copy.deepcopy(items)
134      logging.debug('eval: ' + " lib_"+ res2shrtn[resourcetype] + "_items[:]")
135    elif resourcetype == 'options':
136      items = eval ( "inc_"+ res2shrtn[resourcetype] + "_items[:]") #TODO : ensure this works
137      self.items = copy.deepcopy(items)
138      logging.debug('eval: ' + "inc_"+ res2shrtn[resourcetype] + "_items[:]")
139    else: 
140      #print resourcetype
141      #self.items = eval ( self.parentconfig.shortname+"_"+ res2shrtn[resourcetype] + "_items[:]") #TODO : ensure this works
142      items = eval ( self.parentconfig.shortname+"_"+ res2shrtn[resourcetype] + "_items[:]") #TODO : ensure this works
143      self.items = copy.deepcopy(items)
144      logging.debug('eval: ' + self.parentconfig.shortname+"_"+ res2shrtn[resourcetype] + "_items[:]" )
145   
146    #for item in self.items:
147    #  logger.debug(item.name + ' id: '+ str(id(item)))
148    #  if item.name in MULTIPLE_ALLOWED_ITEMS_SET:
149    #    del item
150       
151     
152   
153    # set parent resource for all own items
154    for item in self.items:
155      item.parentresource = self
156      item.indentlevel = 1
157     
158   
159   
160    self.__create_valid_items_set__()
161    self.__create_items_dict___()
162    self.resourcetype = resourcetype # director, filedaemon ...
163    #self.name = name                 # bacula-dir, bacula-fd ...
164    #self.items.sort()
165    logger.debug('Created ressource ' +  resourcetype + ' with id #' + str(id(self)) )
166
167  def getResourcesListByResType(self,restype):
168    """
169    returns a list of resources of the specified resource type.
170    """
171    reslist = []
172    if restype == 'client' or restype == 'filedaemon':
173      for res in self.resources:
174        if res.resourcetype == 'client' or res.resourcetype == 'filedaemon':
175          reslist.append(res)
176    else: 
177      for res in self.resources:
178        if res.resourcetype == restype:
179          reslist.append(res)
180    return reslist
181 
182  def getItemsListByItemName(self,itemname):
183    '''
184    returns a list of items of the specified item name
185    '''
186    itemlist = []
187    for item in self.items:
188      if item.name == itemname:
189        itemlist.append(item)
190    return itemlist
191
192
193  def getName(self):
194    '''
195    gets the value of the item "name"
196    '''
197    if self.items_dict.has_key('name'):
198      return self.items_dict['name'].storage.value.strip('"')
199    else:
200      return ''
201   
202  def setName(self,name):
203    '''
204    gets the value of the item "name"
205    '''
206    if self.items_dict.has_key('name'):
207      self.items_dict['name'].setValue(name)
208    else:
209      print "could not set name " + name   
210
211  def getPassword(self):
212    '''
213    gets the value of the item "password"
214    '''
215    if self.items_dict.has_key('password'):
216      return self.items_dict['password'].storage.value.strip('"')
217    else:
218      return "ERROR: no password defined"
219   
220  def getItemValue(self, itemname):
221    '''
222    gets the value of the itemname
223    '''
224    if self.items_dict.has_key(itemname):
225      return self.items_dict[itemname].storage.value
226    else:
227      return "ERROR: no item " + itemname + "defined"
228 
229  def setItemValue(self, itemname, newValue):
230    '''
231    gets the value of the itemname
232    '''
233    if self.items_dict.has_key(itemname):
234      self.items_dict[itemname].setValue(newValue)
235    else:
236      print "ERROR: no item " + itemname + "defined"
237
238  def __create_valid_items_set__(self):
239    """
240    create a set of the valid items for this resource.
241    """
242    self.validitemsset = set()
243    for item in self.items:
244      self.validitemsset.add(item.name)
245 
246  def __create_items_dict___(self):
247    """
248    create a dict with the name of the items as key
249    for easier access
250    """
251    self.items_dict = {}
252    for item in self.items:
253      self.items_dict[item.name] = item
254      item.indentlevel = self.indentlevel + 1
255   
256  def __str__(self):
257    """
258    create a string representation of the resource
259    including subresources and items
260    """
261    if self.resourcetype in RESSOURCES_WITH_EQUALSIGN:
262      s = "%s%s = { \n" % (self.indentlevel * INDENTSTRING, prettyName(self.resourcetype))#, self.__class__.__name__ +' id: '+ str(id(self)) )
263    else:
264      s = "%s%s { \n" % (self.indentlevel * INDENTSTRING, prettyName(self.resourcetype))#, self.__class__.__name__ +' id: '+ str(id(self)) )
265   
266
267   
268    # print own items
269    for item in self.items:
270      item.printall = self.printall
271      if str(item) != '':
272        s +=  str(item)
273       
274    # print subresources
275    for v in self.resources:
276      s +=   str(v)
277             
278    s += '%s}\n'  % ( self.indentlevel * INDENTSTRING )
279   
280    return s
281 
282  def parse_string(self, configstring):
283    '''
284    parse resource configuration string and
285    set the resource items accordingly.
286    '''
287    logger.debug( self.resourcetype +  " : parse_string called with \n\"" + configstring + '\"' )
288
289    #TODO REMOVE THE FOUND SUBBLOCK FROM THE ANALYSIS INSIND OF THE PARENTBLOCK:
290    # search for subblocks   
291    block_to_check = configstring
292    blocks = config2blocks(configstring)
293    for block in blocks:
294      if block['level'] == 1:
295        # find containing blocks and only parse the block without the subblocks (resources)
296        #logger.debug( "found block:" + str(block['start']) + ':' + str(block['end']) )
297        #logger.debug( "block now  :" + block_to_check )
298        block_to_del = configstring[block['namestart']:block['end']+1]
299        logger.debug( "delete:"+ block_to_del + 'in' + block_to_check)
300        tmp = block_to_check.replace(block_to_del,'')  # remove the subblock/resource
301        block_to_check = tmp
302       
303        #logger.debug(self.resourcetype +':' + block['name'].lower() )
304        #                parentconfig, resourcetype,  name = None, indentlevel = 1, printall = False
305        newres = Resource(self, block['name'].lower(), self.indentlevel + 1 )
306        if newres != None:
307          newres.parse_string(configstring[block['start']+1:block['end']])
308          #newres.parse_string(configstring[block['start']+1:block['end']])
309          self.resources.append(newres)
310          logger.debug( "just appended resource :" + str(id(newres)) )
311        else:
312          logger.error('did not get a resource back, maybe resource not allowed in this configuration?')
313
314   
315    ''' find all occurrences of "simple" item = value lines'''
316    for line in RXP_SIMPLE_ITEM.finditer(block_to_check):
317      logger.debug("checking for simple_items: " +block_to_check)
318      #logger.debug( line.groups())
319      logger.debug('SIMPLE_ITEM_MATCH:*' + self.resourcetype + '* >' + line.group('itemname') + '<    >' + line.group('value') + '<')
320      normalized_itemname = line.group('itemname').strip().lower().replace(' ','')
321      #logger.debug(str(id(self))+ '*' + str(self))
322      if normalized_itemname  not in self.validitemsset:
323        logger.error( normalized_itemname + ' not allowed in this ressource')
324      else:
325        logger.debug( normalized_itemname + ' ok in this ressource')
326       
327        #if normalized_itemname == 'commandacl':
328        #  self.items_dict[normalized_itemname].ACLDict = commands.keys();
329         
330        if normalized_itemname not in  MULTIPLE_ALLOWED_ITEMS_SET:
331          self.items_dict[normalized_itemname].setValue(line.group('value').strip())
332     
333       
334        elif normalized_itemname == 'file':
335          newitem = Item('file', 0, 0, False, 'store_dir', False, indentlevel = self.indentlevel + 1) # "File" Items can exist multiple times
336          logger.debug( 'created new  \"' + normalized_itemname + '\" Item with id #' + str(id(newitem)) )
337          newitem.setValue(line.group('value'))
338          self.items.append(newitem)
339          newitem.parentresource = self
340          #logger.debug('appended #' + str(id(newitem)) + str(newitem) + ' to #' + str(id(self)) + self.name + ' ' + self.resourcetype)
341          #logger.debug(self.items)
342        elif normalized_itemname == 'run':
343          newitem =  Item('run', '', 0, False, 'store_run', False, indentlevel = self.indentlevel + 1) # "run" Items can exist multiple times
344          logger.debug( 'created new  \"' + normalized_itemname + '\" Item with id #' + str(id(newitem)) )
345          newitem.setValue(line.group('value'))
346          self.items.append(newitem)
347          newitem.parentresource = self
348 
349        else:
350          logger.debug( 'please create  logic for multiple item type  ' + normalized_itemname )
351       
352       
353   
354
355
356
357class BaculaConfig(object):
358  '''
359  base class for bacula config file (console, dir, sd, fd)
360  if processEventsCallback is given,it is called at regular basis to enable the
361  update of the gui.
362  '''
363  def __init__(self, configAsString, parentdatacenter = None, processEventsCallback = None, filename = None):
364    self.filename = filename
365    self.parentdatacenter = parentdatacenter
366    self.validresources = eval(self.shortname+'_resources') # TODO: Try/Except
367    #self.onlinehelp = {}
368    if not self.resources:
369      self.resources = []
370   
371    self.name = '' # Name of director, filedaemon, storage daemon, console
372    self.__create_validresources_set__()
373   
374    if len(configAsString) > 0 :              # if configstring is not empty
375      self.parse_configfile(configAsString, processEventsCallback)
376    else:     
377      for r in self.validresourcesset:       # if configstring is empty -> initialize needed resources
378        if (self.__class__.__name__ == 'FileDaemonConfig'
379            and r == 'client'): # Filedaemon and Client are the same, we only need one
380          continue
381        logger.debug("initializing new resource:" + r)
382        self.createResource(r)
383   
384    logger.debug( 'Created BaculaConfig ' + self.__class__.__name__ )
385   
386  def getName(self):
387    '''
388    get name from own main resource:
389    in director config the name of the director resource,
390    other resource files respectively
391    ''' 
392    mainresources = self.getResourcesListByResType(self.longname)
393    logger.debug(mainresources, self.longname)
394    #assert len(mainresources) < 2
395    # this assertion can break on console.confs, where multiple consoles can be defined.
396    # TODO: check this
397    # we only take the name of the first defined console, maybe we should show all names?
398    if len(mainresources) == 0:
399      return ''
400    elif len(mainresources) == 1:
401      return mainresources[0].getName()
402    else:
403      namelist = []
404      for res in mainresources:
405        namelist.append(res.getName())
406      return ','.join(namelist)
407     
408     
409
410  def getPassword(self):
411    '''
412    get password from own main resource:
413    in director config the name of the director resource,
414    other resource files respectively
415    ''' 
416    mainresources = self.getResourcesListByResType(self.longname)
417    logger.debug(mainresources, self.longname)
418    #assert len(mainresources) < 2
419    # this assertion can break on console.confs, where multiple consoles can be defined.
420    # TODO: check this
421    # we only take the name of the first defined console, maybe we should show all names?
422    if len(mainresources) == 0:
423      return ''
424    else:
425      #return mainresources[0].items_dict['name'].storage.value
426      return mainresources[0].getPassword()
427
428  def getResourcesListByResType(self,restype):
429    """
430    returns a list of resources of the specified resource type.
431    """
432    reslist = []
433    for res in self.resources:
434      if res.resourcetype == restype:
435        reslist.append(res)
436    return reslist
437 
438  def getResourceByName(self,name):
439    """
440    returns a list of resources with name in parameter "name"
441    """
442    for res in self.resources:
443      #print res.items_dict['name'].storage.value , name
444      if str(res.items_dict['name'].storage.value).strip('"') == name.strip('"'):
445        return res
446    return None
447 
448  def getReferencingResourcesListForResource(self,resource):
449    """
450    return a list of resources referencing the given resource
451    """
452    reslist = []
453    for res in self.resources:
454      for item in res.items:
455        referredType = item.name
456        if referredType.endswith('pool'): #incrementalpool, differentialpool, etc
457          #print "setting referredType to pool as", referredType
458          referredType = 'pool'
459        #print "checking:", referredType, resource.resourcetype
460       
461        # check for overrides in run entry for references
462        if referredType == 'run':
463          if hasattr(item.storage,'runentry'):# check for runentry in schedules
464            #print item.storage.runentry.overrides
465            for ov,val in item.storage.runentry.overrides.iteritems():
466              #print "checking: ",ov,val
467              if val is None:
468                continue
469              #if ov.endswith('pool'):
470              #  ovcheck = 'pool'
471              #else:
472              ovcheck = val
473              #print "OVERRIDE_CHECKING",ovcheck,resource.items_dict['name'].storage.value
474              if ovcheck == resource.items_dict['name'].storage.value:
475                #print "FOUND_OVERRIDE:", ovcheck,resource.items_dict['name'].storage.value
476                reslist.append(res)
477             
478        if referredType == resource.resourcetype:
479          #print "found:",item.name, referredType, resource.resourcetype
480          #print "comparing", str(item.storage.value).strip('"'), str(resource.items_dict['name'].storage.value).strip('"')
481          if item.storage.value != None:
482            if str(item.storage.value).strip('"') == resource.items_dict['name'].storage.value.strip('"'):
483              #print "FOUND:", item.storage.value, resource.items_dict['name'].storage.value
484              reslist.append(res)
485              #print item.name,item.storage.value, resource.resourcetype, resource.items_dict['name'].storage.value
486         
487         
488          #if item.value == resource.items_dict['name']:
489         
490    return reslist     
491   
492  def parse_configfile(self, configstring, callback):
493    """
494    parses the configstring: removes the comment lines,
495    """
496 
497    cf = ''
498    for line in configstring.splitlines(True):
499      cf += line[:line.find('#')]+'\n'    # remove comments
500     
501    blocks = config2blocks(cf)
502   
503    #b = 1
504    for block in blocks:
505      #print "%d of %d blocks" % (b, len(blocks))
506      #b+=1
507      if block['level'] == 1:
508        logger.debug( block['name'].lower() )
509        # TODO: can we call Resource() directly?
510       
511        newres = self.createResource( block['name'].lower() )
512        if callback is not None:
513            callback("creating new " + block['name'].lower() + " resource" ) # call callback function after every resource
514        if newres != None:
515          newres.parse_string(cf[block['start']+1:block['end']])
516          if callback is not None:
517            callback(newres.getName()) # call callback function after every resource
518        else:
519          logger.error('did not get a ressource back, maybe ressource not allowed in this configuration?')
520
521
522     
523  def __str__(self):
524    """
525    creates the string representation of the configuration file
526    by recursively calling the string representation of all resources
527    """
528    s = '' # string representation
529    now = time.asctime()
530   
531    # set verid string to the date
532    # TODO: Make this configurable
533    #dirconfs = self.getResourcesListByResType('director')
534    #
535    #for d in dirconfs:
536    #  try:
537    #    d.items_dict['verid'].storage.setValue('" | configuration file created on :' + now + '"' )
538    #  except:
539    #    logger.error('verid setting impossible, maybe object not yet initialized?')
540   
541   
542       
543    s += '# config file written on ' + now + '\n'
544    for v in self.resources:
545      s +=  str(v)
546      s += "\n"
547    return s
548 
549 
550  def selfcheck(self):
551    """
552    selfcheck for valid conditions
553    TODO documentation
554    """
555    retval = True
556    logger.debug(self.__class__.__name__ + "Selfcheck starts")
557    # check if defined resources are allowed
558    for res in self.resources:
559      if res.resourcetype not in self.validresourcesset:
560        logger.error( "Error: resource name " + res.resourcetype + " not allowed")
561        retval =  False
562      else:
563        logger.debug(res.resourcetype + " is allowed")
564   
565    # check if at least one of the needed resources is defined
566    for res in self.validresourcesset:
567      if self.getResourcesListByResType(res) == None:
568        logger.error("Resourctype " + " is missing!")
569        retval = False
570      else:
571        logger.debug(res + " is configured, OK")
572    logger.debug(self.__class__.__name__ + "Selfcheck end")
573    return retval
574     
575   
576
577  def createResource(self, resourcetype ):
578    '''
579    creates a ressource of type "ressourcetype"
580    inside of this bacula configuration
581    '''
582    if resourcetype not in self.validresourcesset:
583      logger.error("ressource type \"" + resourcetype + "\" not allowed in this configuration")
584    else:
585      if self.onlinehelp.has_key(resourcetype):
586        helpurl = self.onlinehelp[resourcetype]
587      else:
588        helpurl = ''
589      newres = Resource( self, resourcetype, indentlevel = 0, onlinehelpURL = helpurl)
590      self.resources.append( newres )
591     
592      return newres
593
594  def deleteResource(self, resource ):
595    '''
596    delete given resource from own resources
597    '''
598    try:
599      self.resources.remove(resource)
600      retval = True
601    except:
602      logger.error("could not remove resource")
603      retval = False
604    return retval
605   
606   
607 
608  def __create_validresources_set__(self):
609    '''
610    Create a set of allowed ressources
611    inside of this bacula configuration
612    '''
613    self.validresourcesset = set()
614    for validresource in self.validresources:
615      self.validresourcesset.add(validresource[0]) # add name
616    #print self.validresourcesset
617
618   
619
620
621class DirectorConfig(BaculaConfig):
622  '''
623  Class for configuration of one director ( bacula-dir.conf )
624  '''
625  def __init__(self, configAsString, parentdatacenter = None, processEventsCallback = None, filename = None):
626    self.shortname = 'dird'
627    self.longname  = 'director'
628    self.onlinehelp = onlinehelp[self.longname]
629    self.resources = []
630    BaculaConfig.__init__(self, configAsString, parentdatacenter, processEventsCallback, filename)
631       
632
633
634class StorageDaemonConfig(BaculaConfig):
635  '''
636  Class for configuration of one storage daemon ( bacula-sd.conf )
637  '''
638  def __init__(self, configAsString, parentdatacenter = None, processEventsCallback = None, filename = None):
639    self.shortname = 'stored'
640    #self.longname  = 'storagedaemon'
641    self.longname  = 'storage'
642    self.onlinehelp = onlinehelp['storage']
643    self.resources = []
644    BaculaConfig.__init__(self, configAsString, parentdatacenter, processEventsCallback, filename)
645
646
647class FileDaemonConfig(BaculaConfig):
648  '''
649  Class for configuration of one storage daemon ( bacula-fd.conf )
650  '''
651  def __init__(self, configAsString,  parentdatacenter = None, processEventsCallback = None, filename = None):
652    self.shortname = 'filed'
653    self.longname  = 'filedaemon'
654    self.onlinehelp = onlinehelp['client']
655    self.resources = []
656    BaculaConfig.__init__(self, configAsString, parentdatacenter , processEventsCallback, filename)
657 
658 
659 
660  def createFdConfigFromDirClient(self, dirClient = None):
661    '''
662    create a Filedaemon Config from a
663    Client Config in Director
664    we have to set some required values to defaults
665    TODO: we should be able to use different defaults for
666    different classes of clients (windows, linux, solaris etc.)
667    '''
668   
669    directors = self.getResourcesListByResType('director')
670    if len(directors) != 1:
671      logger.error("here we should have one director!")
672      return None
673   
674    messages = self.getResourcesListByResType('messages')
675    if len(messages) != 1:
676      logger.error("here we should have one messages!")
677      return None
678     
679    filedaemons = self.getResourcesListByResType('filedaemon') # same as client
680    if len(filedaemons) != 1:
681      logger.error("here we should have one filedaemon!")
682      return None
683     
684    dirDirector = dirClient.parentconfig.getResourcesListByResType('director')[0]
685     
686    # get password and dirname from director config     
687    directors[0].items_dict['name'].storage.value     = dirDirector.items_dict['name'].storage.value
688    directors[0].items_dict['password'].storage.value = dirClient.items_dict['password'].storage.value
689    filedaemons[0].items_dict['name'].storage.value   = dirClient.items_dict['name'].storage.value
690   
691    self.name = filedaemons[0].items_dict['name'].storage.value
692   
693    # TODO: make the following configurable
694    filedaemons[0].items_dict['workingdirectory'].storage.value = '/var/lib/bacula'
695    filedaemons[0].items_dict['piddirectory'].storage.value = '/var/run'
696   
697    messages[0].items_dict['name'].storage.value = 'Standard'
698    messagesentry =  directors[0].items_dict['name'].storage.value + ' = all, !skipped, !restored'
699    messages[0].items_dict['director'].storage.setValue(messagesentry)
700    #messages[0].items_dict['director'].storage.value = directors[0].items_dict['name'].storage.value + ' = all, !skipped, !restored'
701     
702    #filedaemons[0].items_dict['password'].storage.value = dirClient.items_dict['password'].storage.value
703   
704    #dirClient.parentconfig.getResourceByName(parentname).items_dict['name']
705   
706   
707
708    #self.items_dict['password'] = DirDirector.items_dict['password']
709   
710
711
712class ConsoleConfig(BaculaConfig):
713  '''
714  Class for configuration of a console ( bconsole.conf )
715  '''
716  def __init__(self, configAsString, parentdatacenter = None, processEventsCallback = None, filename = None):
717    self.shortname = 'console'
718    self.longname  = 'console'
719    self.onlinehelp = onlinehelp[self.longname]
720    self.resources = []
721    BaculaConfig.__init__(self, configAsString, parentdatacenter, processEventsCallback, filename)
722
723
724
725
726
727
728class DataCenter(object):
729  '''
730  A datacenter is the sum of all configurations that
731  make a running bacula system, i.e. director, storaged,
732  console and filed configurations
733  A datacenter resides in an own subdirectory
734  and has subdirs for consoles, directors, storagedaemons and filedaemons
735  /datacenter/
736  /datacenter/consoles/
737  /datacenter/directors/
738  /datacenter/storagedaemons/
739  /datacenter/filedaemons/
740 
741  this directory structure is hidden inside of a tar.gz file
742  '''
743 
744  def __createDataCenterDirs__(self, basedir):
745    olddir = os.getcwdu()
746    os.mkdir(basedir)
747    #os.chdir(basedir)
748    for subdir in self.subdirs:
749      os.mkdir( os.path.normpath(basedir+'/'+subdir) )
750    #os.chdir(olddir)
751
752 
753 
754  def __init__(self, targzfile, callbackfunction = None):
755    '''
756    extract the targz file,
757    open the *.conf file inside the
758    subdirs and create the configurations accordingly 
759    '''
760    self.filename = targzfile
761    self.subdirs = ['consoles','directors','storagedaemons','filedaemons']
762    self.directors = set()
763    self.consoles = set()
764    self.storagedaemons = set()
765    self.filedaemons = set()
766    tmpdir = tempfile.mkdtemp(prefix = 'dassmodus-')
767    # TODO: delete this after use
768
769    # if the targz file does not exist, then create an empty
770    # datacenter file
771    if not os.path.isfile(targzfile):
772      print targzfile + ' does not exist, creating new file'
773      self.name='datacenter'
774      os.mkdir(tmpdir + '/' + self.name)# datacentername
775      for subdir in self.subdirs:
776        os.mkdir(tmpdir + '/' + self.name + '/' + subdir)
777
778      # create a new tarfile 
779      tar = tarfile.open(targzfile, "w:gz")
780      dirs = glob.glob(os.path.normpath(tmpdir + '/*/*'))
781      for d in dirs:
782        tar.add(d, arcname = d.replace(tmpdir + '/',''))
783      tar.close()
784
785
786    # extract subdirs inside of the tarfile into tmp dir
787    tar = tarfile.open(targzfile, "r:gz")
788
789    #olddir = os.getcwdu()
790    #os.chdir(self.tmpdir)
791    tar.extractall(path = tmpdir)
792    tar.close
793
794    basedir = os.listdir(tmpdir)
795    datacenterdir = os.path.normpath(tmpdir + '/' + basedir[0])
796    #os.chdir(basedir[0]) # change into the
797   
798    self.name = basedir[0]
799
800
801
802    for subdir in self.subdirs:
803      files = glob.glob(os.path.normpath(datacenterdir + '/' + subdir + '/*.conf' ) )
804      for filename in files:
805        if callbackfunction is not None:
806          callbackfunction('parsing file ' + filename)
807       
808        if subdir == 'directors':
809          configstring = open(filename).read()
810          conf = DirectorConfig(configstring, parentdatacenter = self, filename = filename, processEventsCallback = callbackfunction )
811          self.directors.add(conf)
812
813        elif subdir == 'storagedaemons':
814          configstring = open(filename).read()
815          conf = StorageDaemonConfig(configstring, parentdatacenter = self,  filename = filename, processEventsCallback = callbackfunction)
816          self.storagedaemons.add(conf)
817
818        elif subdir == 'filedaemons':
819          configstring = open(filename).read()
820          conf = FileDaemonConfig(configstring, parentdatacenter = self,  filename = filename ,processEventsCallback = callbackfunction)
821          self.filedaemons.add(conf)
822
823        elif subdir == 'consoles':
824          configstring = open(filename).read()
825          conf = ConsoleConfig(configstring, parentdatacenter = self,  filename = filename, processEventsCallback = callbackfunction)
826          self.consoles.add(conf)
827        else:
828          raise NameError('help me')
829   
830 
831    # remove temporary files
832    shutil.rmtree(tmpdir)
833    #os.chdir(olddir) # restore working dir
834
835   
836  def getName(self):
837    return self.name
838 
839  def setName(self, name):
840    self.name = name
841 
842  def setFileName(self, filename):
843    self.filename = filename
844    self.name = os.path.basename(filename).split('.')[0]
845   
846  def importConfigurationFile(self, filename, configtype):
847    '''
848    import a existing configuration file into this
849    datacenter.
850    allowed configtypes: filedaemon, storagedaemon, console, director
851    '''
852    print "importing file " + filename + ' ' + configtype
853    if configtype == 'filedaemon':
854      configstring = open(filename).read()
855      conf = FileDaemonConfig(configstring, filename = filename)
856      self.filedaemons.add(conf)
857
858    elif configtype == 'storagedaemon':
859      configstring = open(filename).read()
860      conf = StorageDaemonConfig(configstring, filename = filename)
861      self.storagedaemons.add(conf)
862
863    elif configtype == 'console':
864      configstring = open(filename).read()
865      conf = ConsoleConfig(configstring,  filename = filename)
866      self.consoles.add(conf)
867
868    elif configtype == 'director':
869      configstring = open(filename).read()
870      conf = DirectorConfig(configstring, filename = filename)
871      self.directors.add(conf)
872
873    else:
874      raise NameError('Unknown configtype, allowed : filedaemon, storagedaemon, console, director')
875      return False
876
877
878  def deleteConfiguration(self, configuration):
879    '''
880    delete the refered configuration resource/configfile
881    '''
882    for configtype in [self.directors, self.filedaemons, self.storagedaemons, self.consoles]:
883      if configuration in configtype:
884        configtype.remove(configuration)
885        del configuration
886        break
887     
888     
889 
890 
891  def checkFileDaemonsIntegrity(self):
892    '''
893    Possible configuration problems:
894      1a: - dirconf refers to filedaemon, but no filedaemon conf exists
895      1b: - dirconf refers to filedaemon, but filedaemon conf has wrong password for this director
896      2a: - filedaemon conf refers to Director that does not exist
897      2b: - filedaemon conf refers to existing Director, but password is wrong for this director 
898      1b and 2b are the same
899     
900    this leads to
901     3 cases:
902     - Dir does not exist,
903     - Filedaemon does not exist,
904     - Passwords do not match
905    '''
906   
907    s = '' # return string
908    director_filedaemons_passwords  = {}          #  dictionary of director -> filedaemonname -> password, extracted from dirconfs
909    filedaemons_directors_passwords = {}          #  dictionary of filedaemon -> director -> password , extracted from fdconfs
910   
911    # collect info about all defined filedaemons/clients in ALL Director Configurations
912    for dir in self.directors:
913      dirfds = dir.getResourcesListByResType('client')
914      director_filedaemons_passwords[dir.getName()] = {}
915      for fd in dirfds:
916        director_filedaemons_passwords[dir.getName()][fd.getName()] = fd.getPassword()
917   
918    # collect info about all definied Directors in all Filedaemon Configurations
919    for fdconf in self.filedaemons:
920      filedaemons_directors_passwords[fdconf.getName()] = {}
921      for dir in fdconf.getResourcesListByResType('director'):
922        filedaemons_directors_passwords[fdconf.getName()][dir.getName()] =  dir.getPassword()
923   
924    #print "Filedaemon View:"
925    # check from the view of filedaemons configurations
926    for f,d in filedaemons_directors_passwords.iteritems():
927      for dir,pw in d.iteritems():
928        #print f,dir,pw
929        if director_filedaemons_passwords.has_key(dir):        # do we have a director config of this director?
930          if  director_filedaemons_passwords[dir].has_key(f):  # has this dir a definition of this fd?
931            if director_filedaemons_passwords[dir][f] == pw:   # and is the password identical?
932              s += "OK: Filedaemon %s has definition of Director %s and password is the same\n" % (f,dir)
933              #s += "INFO: Filedaemon %s has definition of Director %s and password is the same: %s\n" % (f,dir,pw)
934            else:
935              s+= "ERROR: Filedaemon %s has definition of Director %s but password is different: %s != %s\n" % (f,dir,pw,director_filedaemons_passwords[dir][f])             
936          else:
937            s+= "ERROR: Filedaemon %s has not a definition of Director %s\n" % (dir,f)
938        else:
939          s+= "ERROR: Director configuration for %s is MISSING , it was referenced by configfile of %s\n" % (dir,f)                                     
940                                     
941    #print "Director View:"
942    # check from the view of director configurations
943    for d,f in director_filedaemons_passwords.iteritems():
944      for fs,pwd in f.iteritems():
945        #print d,fs,pwd
946        if filedaemons_directors_passwords.has_key(fs):       # do we have a fd-conf for this filedaemon? 
947          if filedaemons_directors_passwords[fs].has_key(d):  # does this fd-conf have a reference to this dir?             
948            if filedaemons_directors_passwords[fs][d] == pwd: # is the password identical?
949              s+= "OK: Director %s is known by Filedaemon %s and passwords are the same.\n" %(d, fs)
950              #s+= "OK: Director %s is known by Filedaemon %s and passwords are the same: %s\n" %(d, fs, pwd)
951            else:
952              s+= "ERROR: Director %s is known by Filedaemon %s, but passwords are NOT the same: %s != %s\n" % (d, fs, pwd,filedaemons_directors_passwords[fs][d])     
953          else:
954            s+= "WARNING: Director %s does not reference Filedaemon %s,that may be OK\n" % (dir, fs)
955            # Not every Dir has to reference every Filedaemon
956        else:
957          s+= "ERROR: fd-conf is MISSING for Filedaemon %s :  please import or create.\n" %(fs)
958
959    return s
960
961   
962 
963 
964     
965  def checkStorageDaemonsIntegrity(self):
966    '''
967    Possible configuration problems:
968      1a: - dirconf refers to storage, but no storagedaemon conf exists with this storage
969      1b: - dirconf refers to storage, storagedaemon has this storage but has wrong password for this director
970     
971      2a: - StorageDaemon conf refers to Director that does not exist
972      2b: - StorageDaemon conf refers to existing Director, but password is wrong for this director 
973     
974      3a: - dirconf refers to Device or Autochanger that is not defined in Storage Daemon
975      3b: - Storage Daemon defines Device or Autochanger that is not used by any Director
976            (info, not problem, may be used in a autochanger)
977      4a: - dirconf refers to Device or Autochanger that is defined, but the *MediaType* is not the same
978    '''
979   
980    '''
981    Datastructures we need:
982     
983    # Director view
984    directorName - storageName - password
985                               - Device
986                               - Media Type
987                               - AutoChanger?
988   
989    # Storage view
990    storageName - directorName - Password
991                - DeviceName - MediaType
992                - AutochangerName[Devices]
993    '''
994 
995    director_storages = {}
996    storage_directors = {}
997   
998    # collect info about all defined storages in ALL Director Configurations
999    for dir in self.directors:
1000      dirstorages = dir.getResourcesListByResType('storage')
1001      director_storages[dir.getName()] = {}
1002      for storage in dirstorages:
1003        director_storages[dir.getName()]['password'] = storage.getPassword()
1004        director_storages[dir.getName()]['device'] = storage.getItemValue('device')
1005        director_storages[dir.getName()]['mediatype'] = storage.getItemValue('mediatype')
1006   
1007    # collect info about all definied Directors in all Filedaemon Configurations
1008    for sdconf in self.storagedaemons:
1009      storage_directors[sdconf.getName()] = {}
1010       
1011      for dir in sdconf.getResourcesListByResType('director'):
1012        storage_directors[sdconf.getName()][dir.getName()] =  dir.getPassword()
1013             
1014      for device in sdconf.getResourcesListByResType('device'):
1015        storage_directors[sdconf.getName()][device.getName()] =  device.getItemValue('mediatype')
1016       
1017      for autochanger in sdconf.getResourcesListByResType('autochanger'):
1018        storage_directors[sdconf.getName()][autochanger.getName()] =  device.getItemValue('device')
1019           
1020      '''
1021      TODO: implement the checks and output
1022      TODO: implement test cases for this function!
1023      '''   
1024
1025 
1026  def checkConsolesIntegrity(self):
1027    '''
1028    check if we have a console conf for every main dir in each dirconf (passwds)
1029    and if we have a console for every named console in each dir (names)
1030    including passwords check
1031    '''
1032    '''
1033    possible configuration problems:
1034    - no console.conf for director entry in director configuration (check passwds)
1035    - no console.conf for named console entries in director configurations
1036    - console.conf for named console entry but passwords not the same
1037    - console.conf, whose password does not match name/password of named consoles and
1038      not password of anonymous console
1039    - console.conf contains console entry that refers a director that is not defined inside of this console conf
1040   
1041    Datastructures we need:
1042    # director view:
1043    directorname - mainpassword              # anonymous/default console
1044                 - consolename - password    # named console
1045    # console view:
1046    console - directorname - password        # anonymous/default console
1047            - consolename - directorname     # named console, refers to defined director inside of this configfile
1048    # consoles can have multiple director entries, and multiple console entries referring these directors       
1049    '''
1050   
1051    directors_consoles = {}
1052    consoles_directors = {}
1053   
1054    for dir in self.directors: # look in all directors of our DataCenter
1055      dirname = dir.getName()
1056      defaultconsolepassword = dir.getPassword()
1057      #dir.getResourcesListByResType('director')[0].getPassword()  # we can only have 1 director entry per director conf
1058      directors_consoles[dirname]={}
1059      directors_consoles[dirname]['defaultconsole'] = defaultconsolepassword
1060     
1061      for console in dir.getResourcesListByResType('console'):
1062        directors_consoles[dirname][console.getName()] = console.getPassword()
1063   
1064   
1065    defconsoles = 0 
1066    for consoleconf in self.consoles: # check all consoles in DC
1067      #print consoleconf
1068      consolename = consoleconf.getName()
1069      if consolename == '':  # we have an defaultconsole
1070        defconsoles += 1
1071        consolename = '__defaultconsole__%d' % (defconsoles)
1072      consoles_directors[consolename]={}
1073      consoles_directors[consolename]['directors']= {}
1074      consoles_directors[consolename]['consoles']  = {}
1075     
1076      for dir in consoleconf.getResourcesListByResType('director'):
1077        consoles_directors[consolename]['directors'][dir.getName()] = dir.getPassword()
1078       
1079      for con in consoleconf.getResourcesListByResType('console'):
1080        consoles_directors[consolename]['consoles'][con.getItemValue('director')] = {}
1081        consoles_directors[consolename]['consoles'][con.getItemValue('director')]['name'] = con.getName()
1082        consoles_directors[consolename]['consoles'][con.getItemValue('director')]['password'] = con.getPassword()
1083    print "break"
1084   
1085   
1086    s = ''
1087    # check the directorconfig view
1088    for dir in directors_consoles:
1089      defaultpw = directors_consoles[dir]['defaultconsole']
1090      for consolename, dirinfo in consoles_directors.iteritems(): # console resource in console config
1091        unreferred_dirs = set() # main directors in dirconfs -> Default Consoles
1092        if consolename.startswith('__defaultconsole__'): # we have a default console
1093          for d,pw in dirinfo['directors'].iteritems():
1094            unreferred_dirs.add(d) # remember dirs
1095            if d == dir:
1096              unreferred_dirs.remove(d)
1097              s += "found defaultconsole in console.conf for dir %s" % ( d )
1098              if defaultpw == pw:
1099                s += " and passwords are the same"
1100              else:
1101                s += " and passwords are different!"
1102            else:
1103              s+= "default console not configured for this dir"
1104             
1105            #print out unreferred main dir confs   
1106            for unrefdir in unreferred_dirs:
1107              s+= "director %s not referred by any console.conf as default console" % (unrefdir)
1108             
1109        else: # named console
1110          print "found named console" ,consolename
1111          for c, consdir in dirinfo['consoles'].iteritems():
1112            print c, consdir
1113             
1114          for d,pw in dirinfo['directors'].iteritems():
1115            #print d,pw
1116            print d, pw, dirinfo['consoles'][d]['password']
1117            #print consolename, dirinfo,
1118            try:
1119              print directors_consoles[dir][d]
1120            except:
1121              print "no named console for dir " , d 
1122             
1123               
1124           
1125            # check if we have
1126           
1127           
1128           
1129           
1130            #unreferred_dirs.add(d) # remember dirs
1131            if d == dir:
1132              unreferred_dirs.remove(d)
1133              s += "found defaultconsole in console.conf for dir %s" % ( d )
1134              if defaultpw == pw:
1135                s += " and passwords are the same"
1136              else:
1137                s += " and passwords are different!"
1138            else:
1139              s+= "default console not configured for this dir"
1140             
1141            #print out unreferred main dir confs   
1142            for unrefdir in unreferred_dirs:
1143              s+= "director %s not referred by any console.conf as default console" % (unrefdir)
1144         
1145         
1146         
1147        #for conscons in consoles_directors
1148        #  print conscons
1149          #referred_dirs.add(conscons[dir])
1150   
1151        #for consdir in console['directors']:
1152      ''' now we have collected all info about defined dirs and consoles in this Datacenter
1153       Checks:
1154        Director view
1155       A: iterate over all directors,
1156       I:check if we have a anonymous console conf that refers to the given password
1157       II:iterate over every named console:
1158           1.: do we have a director entry in the console conf with the correct name TODO:(address/port)
1159           2.: we have a console entry for this named console with the correct name and dirname
1160               AND do we have a director with this dirname defined inside of this particular console conf
1161       
1162       
1163        Console view       
1164       B: iterate over all consoles,
1165       I   check if we have a director for every defined director in console (check names)
1166       II  check if our console definitions refer to defined Director resources inside of this consoleconfig (If we have consoles)
1167       III check if we have a named console definition in dirconf of director DIRNAME with the Name of our console conf and the same password
1168       IV  check for not by console entries referred directors if the console Director Passwd is identical to the Director default password   
1169     
1170      '''
1171                 
1172      '''
1173      TODO: implement the checks and output
1174      TODO: implement test cases for this function!
1175      '''
1176   
1177       
1178 
1179  def safeDatacenter(self):
1180    '''
1181    safe the Datacenter under the known filename
1182    '''
1183    self.writeDatacenter(os.path.dirname(self.filename))
1184   
1185  def writeDatacenter(self,targetdir):
1186    '''
1187    create the subdirs
1188    write the configs
1189    create tarfile
1190    '''
1191    tar = tarfile.open(targetdir + '/' + self.name + '.dmdz','w:gz')
1192    writedir = tempfile.mkdtemp(prefix = 'dassmodus-')
1193    writebasedir = writedir+'/' + self.name
1194    self.__createDataCenterDirs__(writebasedir)
1195   
1196    for director in self.directors:
1197      #print director.getName()
1198      writefile = open(writebasedir + '/' + 'directors' + '/' + director.getName() + '.conf','w')
1199      writefile.write(str(director))
1200      writefile.close()
1201     
1202    for storagedaemon in self.storagedaemons:
1203      #print storagedaemon.getName()
1204      writefile = open(writebasedir + '/' + 'storagedaemons' + '/' + storagedaemon.getName() + '.conf','w')
1205      writefile.write(str(storagedaemon))
1206      writefile.close()
1207     
1208    for filedaemon in self.filedaemons:
1209      #print storagedaemon.getName()
1210      writefile = open(writebasedir + '/' + 'filedaemons' + '/' + filedaemon.getName() + '.conf','w')
1211      writefile.write(str(filedaemon))
1212      writefile.close()
1213   
1214    consolenr = 0
1215    for console in self.consoles:
1216      #print storagedaemon.getName()
1217      name = console.getName()
1218      if name == '':
1219        consolenr += 1
1220        name = "bconsole-%d" % ( consolenr )
1221      writefile = open(writebasedir + '/' + 'consoles' + '/' + name + '.conf','w')
1222      writefile.write(str(console))
1223      writefile.close()
1224     
1225   
1226    #os.chdir(writedir)
1227   
1228    dirs = glob.glob(os.path.normpath(writedir + '/*/*'))
1229    for d in dirs:
1230      tar.add(d, arcname = d.replace(writedir+'/',''))
1231    tar.close()
1232   
1233    #  remove temp dir
1234    shutil.rmtree(writedir)
1235   
1236   
1237   
1238
1239
1240if __name__ == "__main__":
1241  #msgsstore = store_msgs("root@localhost = mount")
1242  import logging
1243  logging.basicConfig(level=logging.DEBUG,
1244                    format='%(asctime)s %(levelname)s \t (%(module)s:%(lineno)d) %(message)s ',
1245                    #filename='vanHelsing.log',
1246                    filemode='w')
1247  cfg = '''
1248  Pool {
1249  LabelFormat = "${Year}-${Month:p/2/0/r}-${Day:p/2/0/r}"
1250}
1251'''
1252  dconfig = DirectorConfig(cfg)
Note: See TracBrowser for help on using the repository browser.