summaryrefslogtreecommitdiffstats
path: root/src/lib/Server/Metadata.py
blob: 957179751033c58e09139ab198a7f673535f1b9d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
'''This file stores persistent metadata for the BCFG Configuration Repository'''
__revision__ = '$Revision$'

from lxml.etree import XML, SubElement, Element
from syslog import syslog, LOG_ERR, LOG_INFO

from Bcfg2.Server.Plugin import SingleXMLFileBacked

class MetadataConsistencyError(Exception):
    '''This error gets raised when metadata is internally inconsistent'''
    pass

class Metadata(object):
    '''The Metadata class is a container for all classes of metadata used by Bcfg2'''
    def __init__(self, all, image, classes, bundles, attributes, hostname, toolset):
        self.all = all
        self.image = image
        self.classes = classes
        self.bundles = bundles
        self.attributes = attributes
        self.hostname = hostname
        self.toolset = toolset

    def Applies(self, other):
        '''Check if metadata styled object applies to current metadata'''
        if (other.all or (other.image and (self.image == other.image)) or
            (other.classes and (other.classes in self.classes)) or
            (other.attributes and (other.attributes in self.attributes)) or
            (other.bundles and (other.bundles in self.bundles)) or
            (other.hostname and (self.hostname == other.hostname)) or
            (other.hostname and (self.hostname.split('.')[0] == other.hostname))):
            return True
        else:
            return False

class Profile(object):
    '''Profiles are configuration containers for sets of classes and attributes'''
    def __init__(self, xml):
        object.__init__(self)
        self.classes = [cls.attrib['name'] for cls in xml.findall("Class")]
        self.attributes = ["%s.%s" % (attr.attrib['scope'], attr.attrib['name']) for
                           attr in xml.findall("Attribute")]

class MetadataStore(SingleXMLFileBacked):
    '''The MetadataStore is a filebacked xml repository that contains all setup info for all clients'''

    def __init__(self, filename, fam):
        # initialize Index data to avoid race
        self.defaults = {}
        self.clients = {}
        self.profiles = {}
        self.classes = {}
        self.images = {}
        self.element = Element("dummy")
        SingleXMLFileBacked.__init__(self, filename, fam)
        
    def Index(self):
        '''Build data structures for XML data'''
        self.element = XML(self.data)
        self.defaults = {}
        self.clients = {}
        self.profiles = {}
        self.classes = {}
        self.images = {}
        for prof in self.element.findall("Profile"):
            self.profiles[prof.attrib['name']] = Profile(prof)
        for cli in self.element.findall("Client"):
            self.clients[cli.attrib['name']] = (cli.attrib['image'], cli.attrib['profile'])
        for cls in self.element.findall("Class"):
            self.classes[cls.attrib['name']] = [bundle.attrib['name'] for bundle in cls.findall("Bundle")]
        for img in self.element.findall("Image"):
            self.images[img.attrib['name']] = img.attrib['toolset']
        for key in [key[8:] for key in self.element.attrib if key[:8] == 'default_']:
            self.defaults[key] = self.element.get("default_%s" % key)

    def FetchMetadata(self, client, image=None, profile=None):
        '''Get metadata for client'''
        if ((image != None) and (profile != None)):
            # Client asserted profile/image
            self.clients[client] = (image, profile)
            syslog(LOG_INFO, "Metadata: Asserted metadata for %s: %s, %s" % (client, image, profile))
            [self.element.remove(cli) for cli in self.element.findall("Client") if cli.get('name') == client]
            SubElement(self.element, "Client", name=client, image=image, profile=profile)
            self.WriteBack()
        else:
            # no asserted metadata
            if self.clients.has_key(client):
                (image, profile) = self.clients[client]
            else:
                # default profile stuff goes here
                (image, profile) = (self.defaults['image'], self.defaults['profile'])
                SubElement(self.element, "Client", name=client, profile=profile, image=image)
                self.WriteBack()

        if not self.profiles.has_key(profile):
            syslog(LOG_ERR, "Metadata: profile %s not defined" % profile)
            raise MetadataConsistencyError
        prof = self.profiles[profile]
        # should we uniq here? V
        bundles = reduce(lambda x, y:x + y, [self.classes.get(cls, []) for cls in prof.classes])
        if not self.images.has_key(image):
            syslog(LOG_ERR, "Metadata: Image %s not defined" % image)
            raise MetadataConsistencyError
        toolset = self.images[image]
        return Metadata(False, image, prof.classes, bundles, prof.attributes, client, toolset)

    def pretty_print(self, element, level=0):
        '''Produce a pretty-printed text representation of element'''
        if element.text:
            fmt = "%s<%%s %%s>%%s</%%s>" % (level*" ")
            data = (element.tag, (" ".join(["%s='%s'" % (key, element.attrib[key]) for key in element.attrib])),
                    element.text, element.tag)
        numchild = len(element.getchildren())
        if numchild:
            fmt = "%s<%%s %%s>\n" % (level*" ",) + (numchild * "%s") + "%s</%%s>\n" % (level*" ")
            data = (element.tag, ) + (" ".join(["%s='%s'" % (key, element.attrib[key]) for key in element.attrib]),)
            data += tuple([self.pretty_print(entry, level+2) for entry in element.getchildren()]) + (element.tag, )
        else:
            fmt = "%s<%%s %%s/>\n" % (level * " ")
            data = (element.tag, " ".join(["%s='%s'" % (key, element.attrib[key]) for key in element.attrib]))
        return fmt % data

    def WriteBack(self):
        '''Write metadata changes back to persistent store'''
        fout = open(self.name, 'w')
        fout.write(self.pretty_print(self.element))
        fout.close()