xref: /openbmc/phosphor-fan-presence/control/gen-fan-zone-defs.py (revision 18c9103015fd4218146cd75b9e99bb745d97cf76)
1 #!/usr/bin/env python
2 
3 """
4 This script reads in fan definition and zone definition YAML
5 files and generates a set of structures for use by the fan control code.
6 """
7 
8 import os
9 import sys
10 import yaml
11 from argparse import ArgumentParser
12 from mako.template import Template
13 from mako.lookup import TemplateLookup
14 
15 
16 def convertToMap(listOfDict):
17     """
18     Converts a list of dictionary entries to a std::map initialization list.
19     """
20     listOfDict = listOfDict.replace('\'', '\"')
21     listOfDict = listOfDict.replace('[', '{')
22     listOfDict = listOfDict.replace(']', '}')
23     listOfDict = listOfDict.replace(':', ',')
24     return listOfDict
25 
26 
27 def genEvent(event):
28     """
29     Generates the source code of an event and returns it as a string
30     """
31     e = "SetSpeedEvent{\n"
32 
33     e += "Group{\n"
34     for group in event['groups']:
35         for member in group['members']:
36             e += "{\n"
37             e += "\"" + member['object'] + "\",\n"
38             e += "{\"" + member['interface'] + "\",\n"
39             e += " \"" + member['property'] + "\"}\n"
40         e += "},\n"
41     e += "},\n"
42 
43     e += "std::vector<Action>{\n"
44     for a in event['action']:
45         if len(a['parameters']) != 0:
46             e += "make_action(action::" + a['name'] + "(\n"
47         else:
48             e += "make_action(action::" + a['name'] + "\n"
49         for i, p in enumerate(a['parameters']):
50             if (i+1) != len(a['parameters']):
51                 e += p + ",\n"
52             else:
53                 e += p + ")\n"
54         e += "),\n"
55     e += "},\n"
56 
57     e += "TimerConf{\n"
58     e += "\t" + event['timer']['interval'] + ",\n"
59     e += "\t" + event['timer']['type'] + "\n"
60     e += "},\n"
61 
62     e += "std::vector<Signal>{\n"
63     for s in event['signals']:
64         e += "Signal{\n"
65         e += "match::" + s['match'] + "(\n"
66         for i, mp in enumerate(s['mparams']):
67             if (i+1) != len(s['mparams']):
68                 e += "\"" + mp + "\",\n"
69             else:
70                 e += "\"" + mp + "\"\n"
71         e += "),\n"
72         e += "make_handler(\n"
73         if ('type' in s['sparams']) and (s['sparams']['type'] is not None):
74             e += s['signal'] + "<" + s['sparams']['type'] + ">(\n"
75         else:
76             e += s['signal'] + "(\n"
77         for sp in s['sparams']['params']:
78             e += s['sparams'][sp] + ",\n"
79         if ('type' in s['hparams']) and (s['hparams']['type'] is not None):
80             e += ("handler::" + s['handler'] +
81                   "<" + s['hparams']['type'] + ">(\n")
82         else:
83             e += "handler::" + s['handler'] + "(\n)"
84         for i, hp in enumerate(s['hparams']['params']):
85             if (i+1) != len(s['hparams']['params']):
86                 e += s['hparams'][hp] + ",\n"
87             else:
88                 e += s['hparams'][hp] + "\n"
89         e += "))\n"
90         e += ")\n"
91     e += "},\n"
92     e += "}\n"
93 
94     e += "}"
95 
96     return e
97 
98 
99 def getGroups(zNum, zCond, edata, events):
100     """
101     Extract and construct the groups for the given event.
102     """
103     groups = []
104     for eGroups in edata['groups']:
105         if ('zone_conditions' in eGroups) and \
106            (eGroups['zone_conditions'] is not None):
107             # Zone conditions are optional in the events yaml but skip
108             # if this event's condition is not in this zone's conditions
109             if all('name' in z and z['name'] is not None and
110                    not any(c['name'] == z['name'] for c in zCond)
111                    for z in eGroups['zone_conditions']):
112                 continue
113 
114             # Zone numbers are optional in the events yaml but skip if this
115             # zone's zone number is not in the event's zone numbers
116             if all('zones' in z and z['zones'] is not None and
117                    zNum not in z['zones']
118                    for z in eGroups['zone_conditions']):
119                 continue
120         eGroup = next(g for g in events['groups']
121                       if g['name'] == eGroups['name'])
122 
123         group = {}
124         members = []
125         group['name'] = eGroup['name']
126         for m in eGroup['members']:
127             member = {}
128             member['path'] = eGroup['type']
129             member['object'] = (eGroup['type'] + m)
130             member['interface'] = eGroups['interface']
131             member['property'] = eGroups['property']['name']
132             member['type'] = eGroups['property']['type']
133             # Use defined service to note member on zone object
134             if ('service' in eGroup) and \
135                (eGroup['service'] is not None):
136                 member['service'] = eGroup['service']
137             # Add expected group member's property value if given
138             if ('value' in eGroups['property']) and \
139                (eGroups['property']['value'] is not None):
140                     if isinstance(eGroups['property']['value'], str) or \
141                             "string" in str(member['type']).lower():
142                         member['value'] = (
143                             "\"" + eGroups['property']['value'] + "\"")
144                     else:
145                         member['value'] = eGroups['property']['value']
146             members.append(member)
147         group['members'] = members
148         groups.append(group)
149     return groups
150 
151 
152 def getActions(zNum, zCond, edata, actions, events):
153     """
154     Extracts and constructs the make_action function call for
155     all the actions within the given event.
156     """
157     action = []
158     for eActions in actions['actions']:
159         actions = {}
160         eAction = next(a for a in events['actions']
161                        if a['name'] == eActions['name'])
162         actions['name'] = eAction['name']
163         params = []
164         if ('parameters' in eAction) and \
165            (eAction['parameters'] is not None):
166             for p in eAction['parameters']:
167                 param = "static_cast<"
168                 if type(eActions[p]) is not dict:
169                     if p == 'actions':
170                         param = "std::vector<Action>{"
171                         pActs = getActions(zNum,
172                                            zCond,
173                                            edata,
174                                            eActions,
175                                            events)
176                         for a in pActs:
177                             if (len(a['parameters']) != 0):
178                                 param += (
179                                     "make_action(action::" +
180                                     a['name'] +
181                                     "(\n")
182                                 for i, ap in enumerate(a['parameters']):
183                                     if (i+1) != len(a['parameters']):
184                                         param += (ap + ",")
185                                     else:
186                                         param += (ap + ")")
187                             else:
188                                 param += ("make_action(action::" + a['name'])
189                             param += "),"
190                         param += "}"
191                     elif p == 'defevents' or p == 'altevents':
192                         param = "std::vector<SetSpeedEvent>{\n"
193                         for i, e in enumerate(eActions[p]):
194                             aEvent = getEvent(zNum, zCond, e, events)
195                             if not aEvent:
196                                 continue
197                             if (i+1) != len(eActions[p]):
198                                 param += genEvent(aEvent) + ",\n"
199                             else:
200                                 param += genEvent(aEvent) + "\n"
201                         param += "\t}"
202                     elif p == 'property':
203                         if isinstance(eActions[p], str) or \
204                            "string" in str(eActions[p]['type']).lower():
205                             param += (
206                                 str(eActions[p]['type']).lower() +
207                                 ">(\"" + str(eActions[p]) + "\")")
208                         else:
209                             param += (
210                                 str(eActions[p]['type']).lower() +
211                                 ">(" + str(eActions[p]['value']).lower() + ")")
212                     else:
213                         # Default type to 'size_t' when not given
214                         param += ("size_t>(" + str(eActions[p]).lower() + ")")
215                 else:
216                     if p == 'timer':
217                         param = (
218                             "TimerConf{static_cast<std::chrono::seconds>(" +
219                             str(eActions[p]['delay']) + "), " +
220                             "TimerType::" +
221                             str(eActions[p]['type']) + "}")
222                     else:
223                         param += (str(eActions[p]['type']).lower() + ">(")
224                         if p != 'map':
225                             if isinstance(eActions[p]['value'], str) or \
226                                "string" in str(eActions[p]['type']).lower():
227                                 param += \
228                                     "\"" + str(eActions[p]['value']) + "\")"
229                             else:
230                                 param += \
231                                     str(eActions[p]['value']).lower() + ")"
232                         else:
233                             param += (
234                                 str(eActions[p]['type']).lower() +
235                                 convertToMap(str(eActions[p]['value'])) + ")")
236                 params.append(param)
237         actions['parameters'] = params
238         action.append(actions)
239     return action
240 
241 
242 def getEvent(zone_num, zone_conditions, e, events_data):
243     """
244     Parses the sections of an event and populates the properties
245     that construct an event within the generated source.
246     """
247     event = {}
248 
249     # Add set speed event groups
250     grps = getGroups(zone_num, zone_conditions, e, events_data)
251     if not grps:
252         return
253     event['groups'] = grps
254 
255     # Add optional set speed actions and function parameters
256     event['action'] = []
257     if ('actions' in e) and \
258        (e['actions'] is not None):
259         event['action'] = getActions(zone_num,
260                                      zone_conditions,
261                                      e,
262                                      e,
263                                      events_data)
264 
265     # Add signal handlers
266     signals = []
267     for group in event['groups']:
268         for member in group['members']:
269             for eMatches in e['matches']:
270                 signal = {}
271                 eMatch = next(m for m in events_data['matches']
272                               if m['name'] == eMatches['name'])
273                 # If service not given, subscribe to signal match
274                 if ('service' not in member):
275                     signal['match'] = eMatch['name']
276                     params = []
277                     if ('parameters' in eMatch) and \
278                        (eMatch['parameters'] is not None):
279                         for p in eMatch['parameters']:
280                             params.append(member[str(p)])
281                     signal['mparams'] = params
282 
283                 if ('parameters' in eMatch['signal']) and \
284                    (eMatch['signal']['parameters'] is not None):
285                     eSignal = eMatch['signal']
286                 else:
287                     eSignal = next(s for s in events_data['signals']
288                                    if s['name'] == eMatch['signal'])
289                 signal['signal'] = eSignal['name']
290                 sparams = {}
291                 if ('parameters' in eSignal) and \
292                    (eSignal['parameters'] is not None):
293                     splist = []
294                     for p in eSignal['parameters']:
295                         sp = str(p)
296                         if (sp != 'type'):
297                             splist.append(sp)
298                             if (sp != 'group'):
299                                 sparams[sp] = "\"" + member[sp] + "\""
300                             else:
301                                 sparams[sp] = "Group{\n"
302                                 for m in group['members']:
303                                     sparams[sp] += (
304                                         "{\n" +
305                                         "\"" + str(m['object']) + "\",\n" +
306                                         "{\"" + str(m['interface']) + "\"," +
307                                         "\"" + str(m['property']) + "\"}\n" +
308                                         "},\n")
309                                 sparams[sp] += "}"
310                         else:
311                             sparams[sp] = member[sp]
312                     sparams['params'] = splist
313                 signal['sparams'] = sparams
314                 # Add signal handler
315                 eHandler = next(h for h in events_data['handlers']
316                                 if h['name'] == eSignal['handler'])
317                 signal['handler'] = eHandler['name']
318                 hparams = {}
319                 if ('parameters' in eHandler) and \
320                    (eHandler['parameters'] is not None):
321                     hplist = []
322                     for p in eHandler['parameters']:
323                         hp = str(p)
324                         if (hp != 'type'):
325                             hplist.append(hp)
326                             if (hp != 'group'):
327                                 hparams[hp] = "\"" + member[hp] + "\""
328                             else:
329                                 hparams[hp] = "Group{\n"
330                                 for m in group['members']:
331                                     hparams[hp] += (
332                                         "{\n" +
333                                         "\"" + str(m['object']) + "\",\n" +
334                                         "{\"" + str(m['interface']) + "\"," +
335                                         "\"" + str(m['property']) + "\"}\n" +
336                                         "},\n")
337                                 hparams[hp] += "}"
338                         else:
339                             hparams[hp] = member[hp]
340                     hparams['params'] = hplist
341                 signal['hparams'] = hparams
342                 signals.append(signal)
343     event['signals'] = signals
344 
345     # Add optional action call timer
346     timer = {}
347     interval = "static_cast<std::chrono::seconds>"
348     if ('timer' in e) and \
349        (e['timer'] is not None):
350         timer['interval'] = (interval +
351                              "(" +
352                              str(e['timer']['interval']) +
353                              ")")
354     else:
355         timer['interval'] = (interval +
356                              "(" + str(0) + ")")
357     timer['type'] = "TimerType::repeating"
358     event['timer'] = timer
359 
360     return event
361 
362 
363 def addPrecondition(zNum, zCond, event, events_data):
364     """
365     Parses the precondition section of an event and populates the necessary
366     structures to generate a precondition for a set speed event.
367     """
368     precond = {}
369     # Add set speed event precondition group
370     grps = getGroups(zNum, zCond, event['precondition'], events_data)
371     if not grps:
372         return
373     precond['pcgrps'] = grps
374 
375     # Add set speed event precondition actions
376     pc = []
377     pcs = {}
378     pcs['name'] = event['precondition']['name']
379     epc = next(p for p in events_data['preconditions']
380                if p['name'] == event['precondition']['name'])
381     params = []
382     for p in epc['parameters']:
383         param = {}
384         if p == 'groups':
385             param['type'] = "std::vector<PrecondGroup>"
386             param['open'] = "{"
387             param['close'] = "}"
388             values = []
389             for group in precond['pcgrps']:
390                 for pcgrp in group['members']:
391                     value = {}
392                     value['value'] = (
393                         "PrecondGroup{\"" +
394                         str(pcgrp['object']) + "\",\"" +
395                         str(pcgrp['interface']) + "\",\"" +
396                         str(pcgrp['property']) + "\"," +
397                         "static_cast<" +
398                         str(pcgrp['type']).lower() + ">")
399                     if isinstance(pcgrp['value'], str) or \
400                        "string" in str(pcgrp['type']).lower():
401                         value['value'] += ("(" + str(pcgrp['value']) + ")}")
402                     else:
403                         value['value'] += \
404                             ("(" + str(pcgrp['value']).lower() + ")}")
405                     values.append(value)
406             param['values'] = values
407         params.append(param)
408     pcs['params'] = params
409     pc.append(pcs)
410     precond['pcact'] = pc
411 
412     pcevents = []
413     for pce in event['precondition']['events']:
414         pcevent = getEvent(zNum, zCond, pce, events_data)
415         if not pcevent:
416             continue
417         pcevents.append(pcevent)
418     precond['pcevts'] = pcevents
419 
420     # Add precondition signal handlers
421     signals = []
422     for group in precond['pcgrps']:
423         for member in group['members']:
424             for eMatches in event['precondition']['matches']:
425                 signal = {}
426                 eMatch = next(m for m in events_data['matches']
427                               if m['name'] == eMatches['name'])
428                 # If service not given, subscribe to signal match
429                 if ('service' not in member):
430                     signal['match'] = eMatch['name']
431                     params = []
432                     if ('parameters' in eMatch) and \
433                        (eMatch['parameters'] is not None):
434                         for p in eMatch['parameters']:
435                             params.append(member[str(p)])
436                     signal['mparams'] = params
437 
438                 if ('parameters' in eMatch['signal']) and \
439                    (eMatch['signal']['parameters'] is not None):
440                     eSignal = eMatch['signal']
441                 else:
442                     eSignal = next(s for s in events_data['signals']
443                                    if s['name'] == eMatch['signal'])
444                 signal['signal'] = eSignal['name']
445                 sparams = {}
446                 if ('parameters' in eSignal) and \
447                    (eSignal['parameters'] is not None):
448                     splist = []
449                     for p in eSignal['parameters']:
450                         sp = str(p)
451                         if (sp != 'type'):
452                             splist.append(sp)
453                             if (sp != 'group'):
454                                 sparams[sp] = "\"" + member[sp] + "\""
455                             else:
456                                 sparams[sp] = "Group{\n"
457                                 for m in group:
458                                     sparams[sp] += (
459                                         "{\n" +
460                                         "\"" + str(m['object']) + "\",\n" +
461                                         "{\"" + str(m['interface']) + "\"," +
462                                         "\"" + str(m['property']) + "\"}\n" +
463                                         "},\n")
464                                 sparams[sp] += "}"
465                         else:
466                             sparams[sp] = member[sp]
467                     sparams['params'] = splist
468                 signal['sparams'] = sparams
469                 # Add signal handler
470                 eHandler = next(h for h in events_data['handlers']
471                                 if h['name'] == eSignal['handler'])
472                 signal['handler'] = eHandler['name']
473                 hparams = {}
474                 if ('parameters' in eHandler) and \
475                    (eHandler['parameters'] is not None):
476                     hplist = []
477                     for p in eHandler['parameters']:
478                         hp = str(p)
479                         if (hp != 'type'):
480                             hplist.append(hp)
481                             if (hp != 'group'):
482                                 hparams[hp] = "\"" + member[hp] + "\""
483                             else:
484                                 hparams[hp] = "Group{\n"
485                                 for m in group:
486                                     hparams[hp] += (
487                                         "{\n" +
488                                         "\"" + str(m['object']) + "\",\n" +
489                                         "{\"" + str(m['interface']) + "\"," +
490                                         "\"" + str(m['property']) + "\"}\n" +
491                                         "},\n")
492                                 hparams[hp] += "}"
493                         else:
494                             hparams[hp] = member[hp]
495                     hparams['params'] = hplist
496                 signal['hparams'] = hparams
497                 signals.append(signal)
498     precond['pcsigs'] = signals
499 
500     # Add optional action call timer
501     timer = {}
502     interval = "static_cast<std::chrono::seconds>"
503     if ('timer' in event['precondition']) and \
504        (event['precondition']['timer'] is not None):
505         timer['interval'] = (interval +
506                              "(" +
507                              str(event['precondition']['timer']['interval']) +
508                              ")")
509     else:
510         timer['interval'] = (interval +
511                              "(" + str(0) + ")")
512     timer['type'] = "TimerType::repeating"
513     precond['pctime'] = timer
514 
515     return precond
516 
517 
518 def getEventsInZone(zone_num, zone_conditions, events_data):
519     """
520     Constructs the event entries defined for each zone using the events yaml
521     provided.
522     """
523     events = []
524 
525     if 'events' in events_data:
526         for e in events_data['events']:
527             event = {}
528             # Add precondition if given
529             if ('precondition' in e) and \
530                (e['precondition'] is not None):
531                 event['pc'] = addPrecondition(zone_num,
532                                               zone_conditions,
533                                               e,
534                                               events_data)
535             else:
536                 event = getEvent(zone_num, zone_conditions, e, events_data)
537                 if not event:
538                     continue
539             events.append(event)
540 
541     return events
542 
543 
544 def getFansInZone(zone_num, profiles, fan_data):
545     """
546     Parses the fan definition YAML files to find the fans
547     that match both the zone passed in and one of the
548     cooling profiles.
549     """
550 
551     fans = []
552 
553     for f in fan_data['fans']:
554 
555         if zone_num != f['cooling_zone']:
556             continue
557 
558         # 'cooling_profile' is optional (use 'all' instead)
559         if f.get('cooling_profile') is None:
560             profile = "all"
561         else:
562             profile = f['cooling_profile']
563 
564         if profile not in profiles:
565             continue
566 
567         fan = {}
568         fan['name'] = f['inventory']
569         fan['sensors'] = f['sensors']
570         fan['target_interface'] = f.get(
571             'target_interface',
572             'xyz.openbmc_project.Control.FanSpeed')
573         fans.append(fan)
574 
575     return fans
576 
577 
578 def getConditionInZoneConditions(zone_condition, zone_conditions_data):
579     """
580     Parses the zone conditions definition YAML files to find the condition
581     that match both the zone condition passed in.
582     """
583 
584     condition = {}
585 
586     for c in zone_conditions_data['conditions']:
587 
588         if zone_condition != c['name']:
589             continue
590         condition['type'] = c['type']
591         properties = []
592         for p in c['properties']:
593             property = {}
594             property['property'] = p['property']
595             property['interface'] = p['interface']
596             property['path'] = p['path']
597             property['type'] = p['type'].lower()
598             property['value'] = str(p['value']).lower()
599             properties.append(property)
600         condition['properties'] = properties
601 
602         return condition
603 
604 
605 def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data):
606     """
607     Combines the zone definition YAML and fan
608     definition YAML to create a data structure defining
609     the fan cooling zones.
610     """
611 
612     zone_groups = []
613 
614     for group in zone_data:
615         conditions = []
616         # zone conditions are optional
617         if 'zone_conditions' in group and group['zone_conditions'] is not None:
618             for c in group['zone_conditions']:
619 
620                 if not zone_conditions_data:
621                     sys.exit("No zone_conditions YAML file but " +
622                              "zone_conditions used in zone YAML")
623 
624                 condition = getConditionInZoneConditions(c['name'],
625                                                          zone_conditions_data)
626 
627                 if not condition:
628                     sys.exit("Missing zone condition " + c['name'])
629 
630                 conditions.append(condition)
631 
632         zone_group = {}
633         zone_group['conditions'] = conditions
634 
635         zones = []
636         for z in group['zones']:
637             zone = {}
638 
639             # 'zone' is required
640             if ('zone' not in z) or (z['zone'] is None):
641                 sys.exit("Missing fan zone number in " + zone_yaml)
642 
643             zone['num'] = z['zone']
644 
645             zone['full_speed'] = z['full_speed']
646 
647             zone['default_floor'] = z['default_floor']
648 
649             # 'increase_delay' is optional (use 0 by default)
650             key = 'increase_delay'
651             zone[key] = z.setdefault(key, 0)
652 
653             # 'decrease_interval' is optional (use 0 by default)
654             key = 'decrease_interval'
655             zone[key] = z.setdefault(key, 0)
656 
657             # 'cooling_profiles' is optional (use 'all' instead)
658             if ('cooling_profiles' not in z) or \
659                     (z['cooling_profiles'] is None):
660                 profiles = ["all"]
661             else:
662                 profiles = z['cooling_profiles']
663 
664             fans = getFansInZone(z['zone'], profiles, fan_data)
665             events = getEventsInZone(z['zone'], group['zone_conditions'],
666                                      events_data)
667 
668             if len(fans) == 0:
669                 sys.exit("Didn't find any fans in zone " + str(zone['num']))
670 
671             zone['fans'] = fans
672             zone['events'] = events
673             zones.append(zone)
674 
675         zone_group['zones'] = zones
676         zone_groups.append(zone_group)
677 
678     return zone_groups
679 
680 
681 if __name__ == '__main__':
682     parser = ArgumentParser(
683         description="Phosphor fan zone definition parser")
684 
685     parser.add_argument('-z', '--zone_yaml', dest='zone_yaml',
686                         default="example/zones.yaml",
687                         help='fan zone definitional yaml')
688     parser.add_argument('-f', '--fan_yaml', dest='fan_yaml',
689                         default="example/fans.yaml",
690                         help='fan definitional yaml')
691     parser.add_argument('-e', '--events_yaml', dest='events_yaml',
692                         help='events to set speeds yaml')
693     parser.add_argument('-c', '--zone_conditions_yaml',
694                         dest='zone_conditions_yaml',
695                         help='conditions to determine zone yaml')
696     parser.add_argument('-o', '--output_dir', dest='output_dir',
697                         default=".",
698                         help='output directory')
699     args = parser.parse_args()
700 
701     if not args.zone_yaml or not args.fan_yaml:
702         parser.print_usage()
703         sys.exit(1)
704 
705     with open(args.zone_yaml, 'r') as zone_input:
706         zone_data = yaml.safe_load(zone_input) or {}
707 
708     with open(args.fan_yaml, 'r') as fan_input:
709         fan_data = yaml.safe_load(fan_input) or {}
710 
711     events_data = {}
712     if args.events_yaml:
713         with open(args.events_yaml, 'r') as events_input:
714             events_data = yaml.safe_load(events_input) or {}
715 
716     zone_conditions_data = {}
717     if args.zone_conditions_yaml:
718         with open(args.zone_conditions_yaml, 'r') as zone_conditions_input:
719             zone_conditions_data = yaml.safe_load(zone_conditions_input) or {}
720 
721     zone_config = buildZoneData(zone_data.get('zone_configuration', {}),
722                                 fan_data, events_data, zone_conditions_data)
723 
724     manager_config = zone_data.get('manager_configuration', {})
725 
726     if manager_config.get('power_on_delay') is None:
727         manager_config['power_on_delay'] = 0
728 
729     tmpls_dir = os.path.join(
730         os.path.dirname(os.path.realpath(__file__)),
731         "templates")
732     output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp")
733     if sys.version_info < (3, 0):
734         lkup = TemplateLookup(
735             directories=tmpls_dir.split(),
736             disable_unicode=True)
737     else:
738         lkup = TemplateLookup(
739             directories=tmpls_dir.split())
740     tmpl = lkup.get_template('fan_zone_defs.mako.cpp')
741     with open(output_file, 'w') as output:
742         output.write(tmpl.render(zones=zone_config,
743                                  mgr_data=manager_config))
744