1 | import os, re, string |
---|
2 | |
---|
3 | # This file is part of CMT, a Cluster Management Tool made at SARA. |
---|
4 | # Copyright (C) 2012 Sil Westerveld |
---|
5 | # |
---|
6 | # This program is free software; you can redistribute it and/or modify |
---|
7 | # it under the terms of the GNU General Public License as published by |
---|
8 | # the Free Software Foundation; either version 2 of the License, or |
---|
9 | # (at your option) any later version. |
---|
10 | # |
---|
11 | # This program is distributed in the hope that it will be useful, |
---|
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
14 | # GNU General Public License for more details. |
---|
15 | # |
---|
16 | # You should have received a copy of the GNU General Public License |
---|
17 | # along with this program; if not, write to the Free Software |
---|
18 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
---|
19 | |
---|
20 | # Inspired by Django tips on: |
---|
21 | # http://www.b-list.org/weblog/2006/jun/07/django-tips-write-better-template-tags/ |
---|
22 | from django import template |
---|
23 | from django.template.defaultfilters import stringfilter |
---|
24 | from django.utils.encoding import smart_unicode, force_unicode |
---|
25 | |
---|
26 | from sara_cmt.logger import Logger |
---|
27 | logger = Logger().getLogger() |
---|
28 | |
---|
29 | #from django.db.models import get_model |
---|
30 | |
---|
31 | register = template.Library() |
---|
32 | |
---|
33 | class NoBlankLinesNode(template.Node): |
---|
34 | """ |
---|
35 | Renderer that'll remove all blank lines. |
---|
36 | """ |
---|
37 | |
---|
38 | def __init__(self, nodelist): |
---|
39 | self.nodelist = nodelist |
---|
40 | |
---|
41 | def render(self, context): |
---|
42 | return re.sub('\n([\ \t]*\n)+', '\n', force_unicode( |
---|
43 | self.nodelist.render(context))) |
---|
44 | |
---|
45 | @register.tag |
---|
46 | def noblanklines(parser, token): |
---|
47 | nodelist = parser.parse(('endnoblanklines',)) |
---|
48 | parser.delete_first_token() |
---|
49 | return NoBlankLinesNode(nodelist) |
---|
50 | |
---|
51 | @stringfilter |
---|
52 | def arpanize(value): |
---|
53 | """ |
---|
54 | Converts a IP (range) to reversed DNS style arpa notation |
---|
55 | |
---|
56 | Usage: |
---|
57 | {{{ <variable>|arpanize }}} |
---|
58 | |
---|
59 | I.e.: |
---|
60 | {% assign broadcast = '192.168.1.0' %} |
---|
61 | {{{ broastcast|arpanize }}} |
---|
62 | Results in output: |
---|
63 | 1.168.192.in-addr.arpa |
---|
64 | """ |
---|
65 | ip_blocks = value.split('.') |
---|
66 | |
---|
67 | reverse_block = [ ip_blocks[2], ip_blocks[1], ip_blocks[0], 'in-addr.arpa' ] |
---|
68 | |
---|
69 | return string.join( reverse_block, '.' ) |
---|
70 | |
---|
71 | register.filter( 'arpanize', arpanize ) |
---|
72 | |
---|
73 | @stringfilter |
---|
74 | def base_net(value): |
---|
75 | """ |
---|
76 | Converts a IP (range) to it's first 3 octects |
---|
77 | |
---|
78 | Usage: |
---|
79 | {{{ <variable>|base_net }}} |
---|
80 | |
---|
81 | I.e.: |
---|
82 | {% assign broadcast = '192.168.1.0' %} |
---|
83 | {{{ broastcast|base_net }}} |
---|
84 | Results in output: |
---|
85 | 192.168.1 |
---|
86 | """ |
---|
87 | ip_blocks = value.split('.') |
---|
88 | |
---|
89 | return string.join( ip_blocks[:3], '.' ) |
---|
90 | |
---|
91 | register.filter( 'base_net', base_net ) |
---|
92 | |
---|
93 | @stringfilter |
---|
94 | def ip_last_digit(value): |
---|
95 | """ |
---|
96 | Converts a IP (range) to it's last octect |
---|
97 | |
---|
98 | Usage: |
---|
99 | {{{ <variable>|ip_last_digit }}} |
---|
100 | |
---|
101 | I.e.: |
---|
102 | {% assign myip = '192.168.1.123' %} |
---|
103 | {{{ myip|ip_last_digit }}} |
---|
104 | Results in output: |
---|
105 | 123 |
---|
106 | """ |
---|
107 | ip_blocks = value.split('.') |
---|
108 | |
---|
109 | return ip_blocks[3] |
---|
110 | |
---|
111 | register.filter( 'ip_last_digit', ip_last_digit ) |
---|
112 | |
---|
113 | @register.tag(name='assign') |
---|
114 | def do_assign(parser,token): |
---|
115 | |
---|
116 | """ |
---|
117 | Variable assignment within template |
---|
118 | |
---|
119 | Usage: {% assign newvar = <space seperated list of strings/vars> %} |
---|
120 | i.e.: {% assign file_name = '/var/tmp/rack-' rack.label '.txt' %} |
---|
121 | """ |
---|
122 | definition = token.split_contents() |
---|
123 | |
---|
124 | if len(definition) < 4: |
---|
125 | raise template.TemplateSyntaxError, '%r tag requires at least 4 arguments' % tag |
---|
126 | |
---|
127 | tag = definition[0] |
---|
128 | new_var = definition[1] |
---|
129 | is_teken = definition[2] |
---|
130 | assignees = definition[3:] |
---|
131 | |
---|
132 | return resolveVariables( new_var, assignees ) |
---|
133 | |
---|
134 | class resolveVariables(template.Node): |
---|
135 | |
---|
136 | def __init__(self, varname, varlist ): |
---|
137 | |
---|
138 | self.varname = varname |
---|
139 | self.varlist = varlist |
---|
140 | |
---|
141 | def render(self, context): |
---|
142 | resvars = [ ] |
---|
143 | |
---|
144 | for a in self.varlist: |
---|
145 | |
---|
146 | var_str = '' |
---|
147 | |
---|
148 | if not (a[0] == a[-1] and a[0] in ('"', "'")): |
---|
149 | try: |
---|
150 | # RB: no quotes must mean its a variable |
---|
151 | # |
---|
152 | a_var = template.Variable( a ) |
---|
153 | var_str = a_var.resolve(context) |
---|
154 | |
---|
155 | except template.VariableDoesNotExist: |
---|
156 | |
---|
157 | #RB: still think not allowed to raise exceptions from render function |
---|
158 | # |
---|
159 | #raise template.TemplateSyntaxError, 'cannot resolve variable %r' %( str( self.path ) ) |
---|
160 | pass |
---|
161 | |
---|
162 | resvars.append( str(var_str) ) |
---|
163 | |
---|
164 | else: |
---|
165 | #RB: assume strings are quoted |
---|
166 | #RB: strip quotes from string |
---|
167 | # |
---|
168 | a = str( a.strip("'").strip('"') ) |
---|
169 | resvars.append( str(a) ) |
---|
170 | |
---|
171 | #RB: finally assign the concatenated string to new varname |
---|
172 | context[ self.varname ] = string.join( resvars, '' ) |
---|
173 | |
---|
174 | #RB: Django render functions not supposed/allowed to raise Exception, I think |
---|
175 | return '' |
---|
176 | |
---|
177 | @register.tag(name='store') |
---|
178 | def do_save_meta(parser, token): |
---|
179 | """ |
---|
180 | Compilation function to use for meta-info. |
---|
181 | |
---|
182 | Usage: {% store '/path/to/file' %} |
---|
183 | {% store variable %} # variable = '/path/to/file' |
---|
184 | """ |
---|
185 | definition = token.split_contents() |
---|
186 | |
---|
187 | if len(definition) != 4 and len(definition) != 2: |
---|
188 | raise template.TemplateSyntaxError, '%r tag requires at least 1 arguments' % tag |
---|
189 | |
---|
190 | tag = definition[0] |
---|
191 | path_arg = definition[1] |
---|
192 | |
---|
193 | if len(definition) == 4: |
---|
194 | |
---|
195 | #RB: OLDSTYLE |
---|
196 | #RB: 4 arguments means: {% store /path/filename as output %} |
---|
197 | #RB: old style: DONT try to resolve variable |
---|
198 | #RB: instead convert filename to quoted string |
---|
199 | |
---|
200 | kw_as = definition[2] |
---|
201 | kw_output_name = definition[3] |
---|
202 | |
---|
203 | path_str = "'%s'" %path_arg |
---|
204 | |
---|
205 | # RB: parse the entire template for old-style |
---|
206 | nodelist = parser.parse() |
---|
207 | |
---|
208 | else: |
---|
209 | #RB: NEWSTYLE |
---|
210 | #RB: 2 arguments can mean: {% store 'string' %} |
---|
211 | #RB: 2 arguments can mean: {% store variable %} |
---|
212 | |
---|
213 | kw_output_name = None |
---|
214 | |
---|
215 | path_str = path_arg |
---|
216 | |
---|
217 | # RB: parse the template thing until %endstore found |
---|
218 | nodelist = parser.parse(('endstore',)) |
---|
219 | # RB: throw away %endstore tag |
---|
220 | parser.delete_first_token() |
---|
221 | |
---|
222 | # RB: Now lets start writing output files |
---|
223 | return generateStoreOutput(tag, path_str, nodelist, kw_output_name) |
---|
224 | |
---|
225 | class generateStoreOutput(template.Node): |
---|
226 | |
---|
227 | def __init__(self, tag, path_str, nodelist, kw_output_name=None): |
---|
228 | self.tag = tag |
---|
229 | self.nodelist = nodelist |
---|
230 | self.path_str = path_str |
---|
231 | self.kw_output_name = kw_output_name |
---|
232 | |
---|
233 | def render(self, context): |
---|
234 | |
---|
235 | if (self.path_str[0] == self.path_str[-1] and self.path_str[0] in ('"', "'")): |
---|
236 | |
---|
237 | mypath_str = str(self.path_str)[1:-1] |
---|
238 | |
---|
239 | else: |
---|
240 | # RB: Not quoted: must be a variable: attempt to resolve to value |
---|
241 | try: |
---|
242 | pathvar = template.Variable( str(self.path_str) ) |
---|
243 | mypath_str = pathvar.resolve(context) |
---|
244 | except template.VariableDoesNotExist: |
---|
245 | #raise template.TemplateSyntaxError, '%r tag argument 1: not a variable %r' %( tag, path_str ) |
---|
246 | pass |
---|
247 | |
---|
248 | if self.kw_output_name: |
---|
249 | # RB: store 'output' variable filename for BW compat |
---|
250 | |
---|
251 | context[ self.kw_output_name ] = mypath_str |
---|
252 | |
---|
253 | # RB: render template between store tags |
---|
254 | output = self.nodelist.render(context) |
---|
255 | |
---|
256 | # RB: store output in context dict for later writing to file |
---|
257 | context['stores'][ mypath_str ] = output |
---|
258 | |
---|
259 | # RB: output generated into context dict, so we return nothing |
---|
260 | return '' |
---|
261 | |
---|
262 | class ScriptNode(template.Node): |
---|
263 | """ |
---|
264 | Renderer, which executes the lines included in the script-tags. |
---|
265 | """ |
---|
266 | |
---|
267 | def __init__(self, nodelist): |
---|
268 | self.nodelist = nodelist |
---|
269 | |
---|
270 | def render(self, context): |
---|
271 | script = self.nodelist.render(context) |
---|
272 | if context.has_key('epilogue'): |
---|
273 | context['epilogue'].append(script) |
---|
274 | else: |
---|
275 | context['epilogue'] = [script] |
---|
276 | # All content between {% epilogue %} and {% endepilogue %} is parsed now |
---|
277 | return '' |
---|
278 | |
---|
279 | @register.tag(name='epilogue') |
---|
280 | def do_epilogue(parser, token): |
---|
281 | """ |
---|
282 | Saving the contents between the epilogue-tags |
---|
283 | """ |
---|
284 | nodelist = parser.parse(('endepilogue',)) |
---|
285 | parser.delete_first_token() |
---|
286 | return ScriptNode(nodelist) |
---|
287 | |
---|
288 | from django.db.models import get_model |
---|
289 | |
---|
290 | @register.tag(name='getbasenets') |
---|
291 | def do_getbasenets(parser, token): |
---|
292 | |
---|
293 | try: |
---|
294 | tag, network_name, kw_as, varname = token.split_contents() |
---|
295 | except ValueError: |
---|
296 | raise template.TemplateSyntaxError, '%r tag requires exactly 4 arguments' % tag |
---|
297 | |
---|
298 | return getBaseNets( varname, network_name ) |
---|
299 | |
---|
300 | class getBaseNets(template.Node): |
---|
301 | |
---|
302 | """ |
---|
303 | Get list of basenets in a network (name) |
---|
304 | |
---|
305 | Usage: {% getbasenets <network name> as <listname> %} |
---|
306 | """ |
---|
307 | |
---|
308 | def __init__(self, varname, network_name ): |
---|
309 | |
---|
310 | self.varname = varname |
---|
311 | self.network_name = network_name.strip("'").strip('"').__str__() |
---|
312 | self.basenets = [ ] |
---|
313 | |
---|
314 | def render(self, context): |
---|
315 | |
---|
316 | if (self.network_name[0] == self.network_name[-1] and self.network_name[0] in ('"', "'")): |
---|
317 | |
---|
318 | network_str = str( self.network_name.strip("'").strip('"') ) |
---|
319 | else: |
---|
320 | # RB: Not quoted: must be a variable: attempt to resolve to value |
---|
321 | try: |
---|
322 | networkvar = template.Variable( str(self.network_name) ) |
---|
323 | network_str = networkvar.resolve(context) |
---|
324 | except template.VariableDoesNotExist: |
---|
325 | #raise template.TemplateSyntaxError, '%r tag argument 1: not a variable %r' %( tag, path_str ) |
---|
326 | pass |
---|
327 | |
---|
328 | from IPy import IP |
---|
329 | |
---|
330 | network_units = get_model('cluster', 'Network').objects.filter( name=str(network_str) ) |
---|
331 | |
---|
332 | for n in network_units: |
---|
333 | |
---|
334 | for ipnum in IP( n.cidr ): |
---|
335 | if not base_net( ipnum ) in self.basenets: |
---|
336 | self.basenets.append( str( base_net( ipnum ) ) ) |
---|
337 | |
---|
338 | context[ self.varname ] = self.basenets |
---|
339 | self.basenets = [ ] |
---|
340 | |
---|
341 | return '' |
---|
342 | |
---|
343 | @register.tag(name='getracks') |
---|
344 | def do_getracks(parser, token): |
---|
345 | |
---|
346 | try: |
---|
347 | tag, cluster, kw_as, name = token.split_contents() |
---|
348 | except ValueError: |
---|
349 | raise template.TemplateSyntaxError, '%r tag requires exactly 4 arguments' % tag |
---|
350 | |
---|
351 | return getRacks( name, cluster ) |
---|
352 | |
---|
353 | class getRacks(template.Node): |
---|
354 | |
---|
355 | """ |
---|
356 | Get list of racks in a cluster |
---|
357 | |
---|
358 | Usage: {% getracks <cluster> as <listname> %} |
---|
359 | """ |
---|
360 | |
---|
361 | def __init__(self, name, cluster): |
---|
362 | |
---|
363 | self.name = name |
---|
364 | self.cluster = cluster.strip("'").strip('"').__str__() |
---|
365 | self.racks = [ ] |
---|
366 | |
---|
367 | def render(self, context): |
---|
368 | |
---|
369 | cluster_units = get_model('cluster', 'HardwareUnit').objects.filter( cluster__name=str(self.cluster) ) |
---|
370 | |
---|
371 | for u in cluster_units: |
---|
372 | if u.rack not in self.racks: |
---|
373 | self.racks.append( u.rack ) |
---|
374 | |
---|
375 | context[ self.name ] = self.racks |
---|
376 | return '' |
---|
377 | |
---|
378 | @register.tag(name='use') |
---|
379 | def do_use(parser, token): |
---|
380 | """ |
---|
381 | Compilation function to definine Querysets for later use. |
---|
382 | |
---|
383 | Usage: {% use <entity> with <attribute>=<value> as <list/var> <key> %} |
---|
384 | """ |
---|
385 | tag = token.contents.split()[0] |
---|
386 | |
---|
387 | try: |
---|
388 | definition = token.split_contents() |
---|
389 | # definition should look like ['use', <entity>, 'with' <query>, 'as', '<key>'] |
---|
390 | except ValueError: |
---|
391 | raise template.TemplateSyntaxError, '%r tag requires at least 5 arguments' % tag |
---|
392 | if len(definition) != 6: |
---|
393 | raise template.TemplateSyntaxError, '%r tag requires at least 5 arguments' % tag |
---|
394 | if definition[2] != 'with': |
---|
395 | raise template.TemplateSyntaxError, "second argument of %r tag has to be 'with'" % tag |
---|
396 | if definition[-2] != 'as': |
---|
397 | raise template.TemplateSyntaxError, "second last argument of %r tag has to be 'as'" % tag |
---|
398 | |
---|
399 | entity = definition[1] |
---|
400 | query = definition[-3] |
---|
401 | key = definition[-1] |
---|
402 | |
---|
403 | return QuerySetNode(entity, query, key) |
---|
404 | |
---|
405 | class QuerySetNode(template.Node): |
---|
406 | """ |
---|
407 | Renderer, which fetches objects from the database. |
---|
408 | """ |
---|
409 | |
---|
410 | def __init__(self, entity, query, key): |
---|
411 | self.entity = entity |
---|
412 | self.query = query |
---|
413 | self.key = key |
---|
414 | |
---|
415 | def render(self, context): |
---|
416 | |
---|
417 | if (self.query[0] == self.query[-1] and self.query[0] in ('"', "'")): |
---|
418 | |
---|
419 | myquery_str = str( self.query.strip("'").strip('"') ) |
---|
420 | else: |
---|
421 | # RB: Not quoted: must be a variable: attempt to resolve to value |
---|
422 | try: |
---|
423 | queryvar = template.Variable( str(self.query) ) |
---|
424 | myquery_str = queryvar.resolve(context) |
---|
425 | except template.VariableDoesNotExist: |
---|
426 | #raise template.TemplateSyntaxError, '%r tag argument 1: not a variable %r' %( tag, path_str ) |
---|
427 | pass |
---|
428 | |
---|
429 | attr, val = myquery_str.split('=') |
---|
430 | queryset = get_model('cluster', self.entity).objects.filter(**{attr:val}) |
---|
431 | if len(queryset) == 1: |
---|
432 | queryset = queryset[0] |
---|
433 | context[self.key] = queryset |
---|
434 | logger.debug('context = %s'%context) |
---|
435 | return '' |
---|
436 | |
---|
437 | # use <entity> with <attribute>=<value> as <key> |
---|
438 | |
---|
439 | |
---|
440 | |
---|