summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Server/Plugins/Deps.py
blob: 9b848baaeff22e4574d6f1f4cc71f45887a0e1dd (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
129
130
131
132
133
134
"""This plugin provides automatic dependency handling."""

import lxml.etree

import Bcfg2.Server.Plugin


class DNode(Bcfg2.Server.Plugin.INode):
    """DNode provides supports for single predicate types for dependencies."""
    raw = {'Group': "lambda m, e:'%(name)s' in m.groups and predicate(m, e)"}
    containers = ['Group']

    def __init__(self, data, idict, parent=None):
        self.data = data
        self.contents = {}
        if parent == None:
            self.predicate = lambda x, d: True
        else:
            predicate = parent.predicate
            if data.tag in list(self.raw.keys()):
                self.predicate = eval(self.raw[data.tag] %
                                      {'name': data.get('name')},
                                      {'predicate': predicate})
            else:
                raise Exception
        mytype = self.__class__
        self.children = []
        for item in data.getchildren():
            if item.tag in self.containers:
                self.children.append(mytype(item, idict, self))
            else:
                data = [(child.tag, child.get('name'))
                        for child in item.getchildren()]
                try:
                    self.contents[item.tag][item.get('name')] = data
                except KeyError:
                    self.contents[item.tag] = {item.get('name'): data}


class DepXMLSrc(Bcfg2.Server.Plugin.XMLSrc):
    __node__ = DNode


class Deps(Bcfg2.Server.Plugin.PrioDir,
           Bcfg2.Server.Plugin.StructureValidator):
    name = 'Deps'
    __author__ = 'bcfg-dev@mcs.anl.gov'
    __child__ = DepXMLSrc

    # Override the default sort_order (of 500) so that this plugin
    # gets handled after others running at the default. In particular,
    # we want to run after Packages, so we can see the final set of
    # packages that will be installed on the client.
    sort_order = 750

    def __init__(self, core, datastore):
        Bcfg2.Server.Plugin.PrioDir.__init__(self, core, datastore)
        Bcfg2.Server.Plugin.StructureValidator.__init__(self)
        self.cache = {}

    def HandleEvent(self, event):
        self.cache = {}
        Bcfg2.Server.Plugin.PrioDir.HandleEvent(self, event)

    def validate_structures(self, metadata, structures):
        """Examine the passed structures and append any additional
        prerequisite entries as defined by the files in Deps.
        """
        entries = []
        for structure in structures:
            for entry in structure.getchildren():
                tag = entry.tag
                if tag.startswith('Bound'):
                    tag = tag[5:]
                if (tag, entry.get('name')) not in entries \
                        and not isinstance(entry, lxml.etree._Comment):
                    entries.append((tag, entry.get('name')))
        entries.sort()
        entries = tuple(entries)
        gdata = list(metadata.groups)
        gdata.sort()
        gdata = tuple(gdata)

        # Check to see if we have cached the prereqs already
        if (entries, gdata) in self.cache:
            prereqs = self.cache[(entries, gdata)]
        else:
            prereqs = self.calculate_prereqs(metadata, entries)
            self.cache[(entries, gdata)] = prereqs

        newstruct = lxml.etree.Element("Independent")
        for tag, name in prereqs:
            try:
                lxml.etree.SubElement(newstruct, tag, name=name)
            except:
                self.logger.error("Failed to add dep entry for %s:%s" % (tag, name))
        structures.append(newstruct)


    def calculate_prereqs(self, metadata, entries):
        """Calculate the prerequisites defined in Deps for the passed
        set of entries.
        """
        prereqs = []
        [src.Cache(metadata) for src in self.entries.values()]

        toexamine = list(entries[:])
        while toexamine:
            entry = toexamine.pop()
            matching = [src for src in list(self.entries.values())
                        if src.cache and entry[0] in src.cache[1]
                        and entry[1] in src.cache[1][entry[0]]]
            if len(matching) > 1:
                prio = [int(src.priority) for src in matching]
                if prio.count(max(prio)) > 1:
                    self.logger.error("Found conflicting %s sources with same priority for %s, pkg %s" %
                                      (entry[0].lower(), metadata.hostname, entry[1]))
                    raise Bcfg2.Server.Plugin.PluginExecutionError
                index = prio.index(max(prio))
                matching = [matching[index]]
            elif len(matching) == 1:
                for prq in matching[0].cache[1][entry[0]][entry[1]]:
                    # XML comments seem to show up in the cache as a
                    # tuple with item 0 being callable. The logic
                    # below filters them out. Would be better to
                    # exclude them when we load the cache in the first
                    # place.
                    if prq not in prereqs and prq not in entries and not callable(prq[0]):
                        toexamine.append(prq)
                        prereqs.append(prq)
            else:
                continue

        return prereqs