44import argparse
55import configobj
66import contextlib
7- import functools
87import json
98import logging
109import os
1312import uuid
1413import yaml
1514
16- import jinja2
17-
1815from copy import deepcopy
1916from io import BytesIO , StringIO
2017from tarfile import ReadError
3027from tasks .cephfs .filesystem import MDSCluster , Filesystem
3128from tasks .daemonwatchdog import DaemonWatchdog
3229from tasks .util import chacra
30+ from tasks import template
3331
3432# these items we use from ceph.py should probably eventually move elsewhere
3533from tasks .ceph import get_mons , healthy
3937log = logging .getLogger (__name__ )
4038
4139
42- def _convert_strs_in (o , conv ):
43- """A function to walk the contents of a dict/list and recurisvely apply
44- a conversion function (`conv`) to the strings within.
45- """
46- if isinstance (o , str ):
47- return conv (o )
48- if isinstance (o , dict ):
49- for k in o :
50- o [k ] = _convert_strs_in (o [k ], conv )
51- if isinstance (o , list ):
52- o [:] = [_convert_strs_in (v , conv ) for v in o ]
53- return o
54-
55-
56- def _apply_template (jinja_env , rctx , template ):
57- """Apply jinja2 templating to the template string `template` via the jinja
58- environment `jinja_env`, passing a dictionary containing top-level context
59- to render into the template.
60- """
61- if '{{' in template or '{%' in template :
62- return jinja_env .from_string (template ).render (** rctx )
63- return template
64-
65-
66- def _template_transform (ctx , config , target ):
67- """Apply jinja2 based templates to strings within the target object,
68- returning a transformed target. Target objects may be a list or dict or
69- str.
70-
71- Note that only string values in the list or dict objects are modified.
72- Therefore one can read & parse yaml or json that contain templates in
73- string values without the risk of changing the structure of the yaml/json.
74- """
75- jenv = getattr (ctx , '_jinja_env' , None )
76- if jenv is None :
77- loader = jinja2 .BaseLoader ()
78- jenv = jinja2 .Environment (loader = loader )
79- jenv .filters ['role_to_remote' ] = _role_to_remote
80- setattr (ctx , '_jinja_env' , jenv )
81- rctx = dict (ctx = ctx , config = config , cluster_name = config .get ('cluster' , '' ))
82- _vip_vars (rctx )
83- conv = functools .partial (_apply_template , jenv , rctx )
84- return _convert_strs_in (target , conv )
85-
86-
87- def _vip_vars (rctx ):
88- """For backwards compat with the previous subst_vip function."""
89- ctx = rctx ['ctx' ]
90- if 'vnet' in getattr (ctx , 'vip' , {}):
91- rctx ['VIPPREFIXLEN' ] = str (ctx .vip ["vnet" ].prefixlen )
92- rctx ['VIPSUBNET' ] = str (ctx .vip ["vnet" ].network_address )
93- if 'vips' in getattr (ctx , 'vip' , {}):
94- vips = ctx .vip ['vips' ]
95- for idx , vip in enumerate (vips ):
96- rctx [f'VIP{ idx } ' ] = str (vip )
97-
98-
99- @jinja2 .pass_context
100- def _role_to_remote (rctx , role ):
101- """Return the first remote matching the given role."""
102- ctx = rctx ['ctx' ]
103- for remote , roles in ctx .cluster .remotes .items ():
104- if role in roles :
105- return remote
106- return None
107-
108-
10940def _shell (ctx , cluster_name , remote , args , extra_cephadm_args = [], ** kwargs ):
11041 teuthology .get_testdir (ctx )
11142 return remote .run (
@@ -1513,22 +1444,6 @@ def stop(ctx, config):
15131444 yield
15141445
15151446
1516- def _expand_roles (ctx , config ):
1517- if 'all-roles' in config and len (config ) == 1 :
1518- a = config ['all-roles' ]
1519- roles = teuthology .all_roles (ctx .cluster )
1520- config = dict ((id_ , a ) for id_ in roles if not id_ .startswith ('host.' ))
1521- elif 'all-hosts' in config and len (config ) == 1 :
1522- a = config ['all-hosts' ]
1523- roles = teuthology .all_roles (ctx .cluster )
1524- config = dict ((id_ , a ) for id_ in roles if id_ .startswith ('host.' ))
1525- elif 'all-roles' in config or 'all-hosts' in config :
1526- raise ValueError (
1527- 'all-roles/all-hosts may not be combined with any other roles'
1528- )
1529- return config
1530-
1531-
15321447def shell (ctx , config ):
15331448 """
15341449 Execute (shell) commands
@@ -1541,8 +1456,8 @@ def shell(ctx, config):
15411456 for k in config .pop ('volumes' , []):
15421457 args .extend (['-v' , k ])
15431458
1544- config = _expand_roles (ctx , config )
1545- config = _template_transform (ctx , config , config )
1459+ config = template . expand_roles (ctx , config )
1460+ config = template . transform (ctx , config , config )
15461461 for role , cmd in config .items ():
15471462 (remote ,) = ctx .cluster .only (role ).remotes .keys ()
15481463 log .info ('Running commands on role %s host %s' , role , remote .name )
@@ -1575,31 +1490,6 @@ def _shell_command(obj):
15751490 raise ValueError (f'invalid command item: { obj !r} ' )
15761491
15771492
1578- def exec (ctx , config ):
1579- """
1580- This is similar to the standard 'exec' task, but does template substitutions.
1581-
1582- TODO: this should probably be moved out of cephadm.py as it's pretty generic.
1583- """
1584- assert isinstance (config , dict ), "task exec got invalid config"
1585- testdir = teuthology .get_testdir (ctx )
1586- config = _expand_roles (ctx , config )
1587- for role , ls in config .items ():
1588- (remote ,) = ctx .cluster .only (role ).remotes .keys ()
1589- log .info ('Running commands on role %s host %s' , role , remote .name )
1590- for c in ls :
1591- c .replace ('$TESTDIR' , testdir )
1592- remote .run (
1593- args = [
1594- 'sudo' ,
1595- 'TESTDIR={tdir}' .format (tdir = testdir ),
1596- 'bash' ,
1597- '-ex' ,
1598- '-c' ,
1599- _template_transform (ctx , config , c )],
1600- )
1601-
1602-
16031493def apply (ctx , config ):
16041494 """
16051495 Apply spec
@@ -1622,7 +1512,7 @@ def apply(ctx, config):
16221512 cluster_name = config .get ('cluster' , 'ceph' )
16231513
16241514 specs = config .get ('specs' , [])
1625- specs = _template_transform (ctx , config , specs )
1515+ specs = template . transform (ctx , config , specs )
16261516 y = yaml .dump_all (specs )
16271517
16281518 log .info (f'Applying spec(s):\n { y } ' )
0 commit comments