[14130] | 1 | import os, re, string |
---|
[11443] | 2 | |
---|
[14194] | 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 | |
---|
[11437] | 20 | # Inspired by Django tips on: |
---|
| 21 | # http://www.b-list.org/weblog/2006/jun/07/django-tips-write-better-template-tags/ |
---|
[10765] | 22 | from django import template |
---|
[11443] | 23 | from django.template.defaultfilters import stringfilter |
---|
[11739] | 24 | from django.utils.encoding import smart_unicode, force_unicode |
---|
[10765] | 25 | |
---|
[11443] | 26 | from sara_cmt.logger import Logger |
---|
| 27 | logger = Logger().getLogger() |
---|
| 28 | |
---|
[11437] | 29 | #from django.db.models import get_model |
---|
| 30 | |
---|
[10765] | 31 | register = template.Library() |
---|
| 32 | |
---|
[11739] | 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 | |
---|
[14130] | 51 | @stringfilter |
---|
| 52 | def arpanize(value): |
---|
| 53 | """ |
---|
| 54 | Converts a IP (range) to reversed DNS style arpa notation |
---|
[11739] | 55 | |
---|
[14130] | 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 | |
---|
[14133] | 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 | |
---|
[11443] | 177 | @register.tag(name='store') |
---|
[11437] | 178 | def do_save_meta(parser, token): |
---|
[11739] | 179 | """ |
---|
| 180 | Compilation function to use for meta-info. |
---|
[14134] | 181 | |
---|
| 182 | Usage: {% store '/path/to/file' %} |
---|
| 183 | {% store variable %} # variable = '/path/to/file' |
---|
[11739] | 184 | """ |
---|
[14137] | 185 | definition = token.split_contents() |
---|
[11437] | 186 | |
---|
[14137] | 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 | |
---|
[14139] | 195 | #RB: OLDSTYLE |
---|
[14137] | 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 | |
---|
[14139] | 200 | kw_as = definition[2] |
---|
| 201 | kw_output_name = definition[3] |
---|
| 202 | |
---|
[14137] | 203 | path_str = "'%s'" %path_arg |
---|
| 204 | |
---|
[14138] | 205 | # RB: parse the entire template for old-style |
---|
| 206 | nodelist = parser.parse() |
---|
| 207 | |
---|
[14137] | 208 | else: |
---|
[14139] | 209 | #RB: NEWSTYLE |
---|
[14137] | 210 | #RB: 2 arguments can mean: {% store 'string' %} |
---|
| 211 | #RB: 2 arguments can mean: {% store variable %} |
---|
| 212 | |
---|
[14139] | 213 | kw_output_name = None |
---|
| 214 | |
---|
[14137] | 215 | path_str = path_arg |
---|
| 216 | |
---|
[14138] | 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() |
---|
[11437] | 221 | |
---|
[14134] | 222 | # RB: Now lets start writing output files |
---|
[14139] | 223 | return generateStoreOutput(tag, path_str, nodelist, kw_output_name) |
---|
[14134] | 224 | |
---|
| 225 | class generateStoreOutput(template.Node): |
---|
| 226 | |
---|
[14139] | 227 | def __init__(self, tag, path_str, nodelist, kw_output_name=None): |
---|
[14134] | 228 | self.tag = tag |
---|
| 229 | self.nodelist = nodelist |
---|
| 230 | self.path_str = path_str |
---|
[14139] | 231 | self.kw_output_name = kw_output_name |
---|
[14134] | 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 | |
---|
[14139] | 248 | if self.kw_output_name: |
---|
[14138] | 249 | # RB: store 'output' variable filename for BW compat |
---|
| 250 | |
---|
[14139] | 251 | context[ self.kw_output_name ] = mypath_str |
---|
[14138] | 252 | |
---|
[14134] | 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 | |
---|
[11443] | 262 | class ScriptNode(template.Node): |
---|
[11739] | 263 | """ |
---|
| 264 | Renderer, which executes the lines included in the script-tags. |
---|
| 265 | """ |
---|
[11443] | 266 | |
---|
[11739] | 267 | def __init__(self, nodelist): |
---|
| 268 | self.nodelist = nodelist |
---|
[11443] | 269 | |
---|
[11739] | 270 | def render(self, context): |
---|
| 271 | script = self.nodelist.render(context) |
---|
[11928] | 272 | if context.has_key('epilogue'): |
---|
| 273 | context['epilogue'].append(script) |
---|
| 274 | else: |
---|
| 275 | context['epilogue'] = [script] |
---|
[11739] | 276 | # All content between {% epilogue %} and {% endepilogue %} is parsed now |
---|
| 277 | return '' |
---|
| 278 | |
---|
[11443] | 279 | @register.tag(name='epilogue') |
---|
| 280 | def do_epilogue(parser, token): |
---|
[11739] | 281 | """ |
---|
| 282 | Saving the contents between the epilogue-tags |
---|
| 283 | """ |
---|
| 284 | nodelist = parser.parse(('endepilogue',)) |
---|
| 285 | parser.delete_first_token() |
---|
| 286 | return ScriptNode(nodelist) |
---|
[11926] | 287 | |
---|
| 288 | from django.db.models import get_model |
---|
| 289 | |
---|
[14131] | 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: |
---|
[14132] | 373 | self.racks.append( u.rack ) |
---|
[14131] | 374 | |
---|
| 375 | context[ self.name ] = self.racks |
---|
| 376 | return '' |
---|
| 377 | |
---|
[11926] | 378 | @register.tag(name='use') |
---|
| 379 | def do_use(parser, token): |
---|
| 380 | """ |
---|
| 381 | Compilation function to definine Querysets for later use. |
---|
| 382 | |
---|
[12020] | 383 | Usage: {% use <entity> with <attribute>=<value> as <list/var> <key> %} |
---|
[11926] | 384 | """ |
---|
| 385 | tag = token.contents.split()[0] |
---|
[14136] | 386 | |
---|
[11926] | 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 |
---|
[14136] | 398 | |
---|
[11926] | 399 | entity = definition[1] |
---|
| 400 | query = definition[-3] |
---|
| 401 | key = definition[-1] |
---|
[14136] | 402 | |
---|
[11926] | 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 |
---|
[14135] | 412 | self.query = query |
---|
[11926] | 413 | self.key = key |
---|
| 414 | |
---|
| 415 | def render(self, context): |
---|
[14135] | 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('=') |
---|
[11926] | 430 | queryset = get_model('cluster', self.entity).objects.filter(**{attr:val}) |
---|
[12000] | 431 | if len(queryset) == 1: |
---|
| 432 | queryset = queryset[0] |
---|
[11970] | 433 | context[self.key] = queryset |
---|
[11926] | 434 | logger.debug('context = %s'%context) |
---|
| 435 | return '' |
---|
| 436 | |
---|
| 437 | # use <entity> with <attribute>=<value> as <key> |
---|
| 438 | |
---|
| 439 | |
---|
| 440 | |
---|