Skip to content

Commit a5b9d64

Browse files
authored
Merge pull request ceph#55719 from phlogistonjohn/jjm-teuth-tasks-cephadm-jt
qa/tasks/cephadm: add generic templating where subst_vip was used Reviewed-by: Adam King <[email protected]>
2 parents 0389bfe + 4f1f095 commit a5b9d64

File tree

2 files changed

+91
-21
lines changed

2 files changed

+91
-21
lines changed

qa/tasks/cephadm.py

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
import argparse
55
import configobj
66
import contextlib
7+
import functools
8+
import json
79
import logging
810
import os
9-
import json
1011
import re
1112
import uuid
1213
import yaml
1314

15+
import jinja2
16+
1417
from copy import deepcopy
1518
from io import BytesIO, StringIO
1619
from tarfile import ReadError
@@ -27,13 +30,68 @@
2730

2831
# these items we use from ceph.py should probably eventually move elsewhere
2932
from tasks.ceph import get_mons, healthy
30-
from tasks.vip import subst_vip
3133

3234
CEPH_ROLE_TYPES = ['mon', 'mgr', 'osd', 'mds', 'rgw', 'prometheus']
3335

3436
log = logging.getLogger(__name__)
3537

3638

39+
def _convert_strs_in(o, conv):
40+
"""A function to walk the contents of a dict/list and recurisvely apply
41+
a conversion function (`conv`) to the strings within.
42+
"""
43+
if isinstance(o, str):
44+
return conv(o)
45+
if isinstance(o, dict):
46+
for k in o:
47+
o[k] = _convert_strs_in(o[k], conv)
48+
if isinstance(o, list):
49+
o[:] = [_convert_strs_in(v, conv) for v in o]
50+
return o
51+
52+
53+
def _apply_template(jinja_env, rctx, template):
54+
"""Apply jinja2 templating to the template string `template` via the jinja
55+
environment `jinja_env`, passing a dictionary containing top-level context
56+
to render into the template.
57+
"""
58+
if '{{' in template or '{%' in template:
59+
return jinja_env.from_string(template).render(**rctx)
60+
return template
61+
62+
63+
def _template_transform(ctx, config, target):
64+
"""Apply jinja2 based templates to strings within the target object,
65+
returning a transformed target. Target objects may be a list or dict or
66+
str.
67+
68+
Note that only string values in the list or dict objects are modified.
69+
Therefore one can read & parse yaml or json that contain templates in
70+
string values without the risk of changing the structure of the yaml/json.
71+
"""
72+
jenv = getattr(ctx, '_jinja_env', None)
73+
if jenv is None:
74+
loader = jinja2.BaseLoader()
75+
jenv = jinja2.Environment(loader=loader)
76+
setattr(ctx, '_jinja_env', jenv)
77+
rctx = dict(ctx=ctx, config=config, cluster_name=config.get('cluster', ''))
78+
_vip_vars(rctx)
79+
conv = functools.partial(_apply_template, jenv, rctx)
80+
return _convert_strs_in(target, conv)
81+
82+
83+
def _vip_vars(rctx):
84+
"""For backwards compat with the previous subst_vip function."""
85+
ctx = rctx['ctx']
86+
if 'vnet' in getattr(ctx, 'vip', {}):
87+
rctx['VIPPREFIXLEN'] = str(ctx.vip["vnet"].prefixlen)
88+
rctx['VIPSUBNET'] = str(ctx.vip["vnet"].network_address)
89+
if 'vips' in getattr(ctx, 'vip', {}):
90+
vips = ctx.vip['vips']
91+
for idx, vip in enumerate(vips):
92+
rctx[f'VIP{idx}'] = str(vip)
93+
94+
3795
def _shell(ctx, cluster_name, remote, args, extra_cephadm_args=[], **kwargs):
3896
teuthology.get_testdir(ctx)
3997
return remote.run(
@@ -1356,18 +1414,19 @@ def shell(ctx, config):
13561414
roles = teuthology.all_roles(ctx.cluster)
13571415
config = dict((id_, a) for id_ in roles if id_.startswith('host.'))
13581416

1417+
config = _template_transform(ctx, config, config)
13591418
for role, cmd in config.items():
13601419
(remote,) = ctx.cluster.only(role).remotes.keys()
13611420
log.info('Running commands on role %s host %s', role, remote.name)
13621421
if isinstance(cmd, list):
13631422
for c in cmd:
13641423
_shell(ctx, cluster_name, remote,
1365-
['bash', '-c', subst_vip(ctx, c)],
1424+
['bash', '-c', c],
13661425
extra_cephadm_args=args)
13671426
else:
13681427
assert isinstance(cmd, str)
13691428
_shell(ctx, cluster_name, remote,
1370-
['bash', '-ex', '-c', subst_vip(ctx, cmd)],
1429+
['bash', '-ex', '-c', cmd],
13711430
extra_cephadm_args=args)
13721431

13731432

@@ -1393,7 +1452,8 @@ def apply(ctx, config):
13931452
cluster_name = config.get('cluster', 'ceph')
13941453

13951454
specs = config.get('specs', [])
1396-
y = subst_vip(ctx, yaml.dump_all(specs))
1455+
specs = _template_transform(ctx, config, specs)
1456+
y = yaml.dump_all(specs)
13971457

13981458
log.info(f'Applying spec(s):\n{y}')
13991459
_shell(

qa/tasks/vip.py

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from teuthology import misc as teuthology
77
from teuthology.config import config as teuth_config
8+
from teuthology.exceptions import ConfigError
89

910
log = logging.getLogger(__name__)
1011

@@ -68,24 +69,33 @@ def exec(ctx, config):
6869
)
6970

7071

71-
def map_vips(mip, count):
72-
for mapping in teuth_config.get('vip', []):
72+
def _map_vips(mip, count):
73+
vip_entries = teuth_config.get('vip', [])
74+
if not vip_entries:
75+
raise ConfigError(
76+
'at least one item must be configured for "vip" config key'
77+
' to use the vip task'
78+
)
79+
for mapping in vip_entries:
7380
mnet = ipaddress.ip_network(mapping['machine_subnet'])
7481
vnet = ipaddress.ip_network(mapping['virtual_subnet'])
7582
if vnet.prefixlen >= mnet.prefixlen:
7683
log.error(f"virtual_subnet {vnet} prefix >= machine_subnet {mnet} prefix")
77-
return None
78-
if mip in mnet:
79-
pos = list(mnet.hosts()).index(mip)
80-
log.info(f"{mip} in {mnet}, pos {pos}")
81-
r = []
82-
for sub in vnet.subnets(new_prefix=mnet.prefixlen):
83-
r += [list(sub.hosts())[pos]]
84-
count -= 1
85-
if count == 0:
86-
break
87-
return vnet, r
88-
return None
84+
raise ConfigError('virtual subnet too small')
85+
if mip not in mnet:
86+
# not our machine subnet
87+
log.info(f"machine ip {mip} not in machine subnet {mnet}")
88+
continue
89+
pos = list(mnet.hosts()).index(mip)
90+
log.info(f"{mip} in {mnet}, pos {pos}")
91+
r = []
92+
for sub in vnet.subnets(new_prefix=mnet.prefixlen):
93+
r += [list(sub.hosts())[pos]]
94+
count -= 1
95+
if count == 0:
96+
break
97+
return vnet, r
98+
raise ConfigError(f"no matching machine subnet found for {mip}")
8999

90100

91101
@contextlib.contextmanager
@@ -136,14 +146,14 @@ def task(ctx, config):
136146
ip = remote.ssh.get_transport().getpeername()[0]
137147
log.info(f'peername {ip}')
138148
mip = ipaddress.ip_address(ip)
139-
vnet, vips = map_vips(mip, count + 1)
149+
vnet, vips = _map_vips(mip, count + 1)
140150
static = vips.pop(0)
141151
log.info(f"{remote.hostname} static {static}, vnet {vnet}")
142152

143153
if not ctx.vip:
144154
# do this only once (use the first remote we see), since we only need 1
145155
# set of virtual IPs, regardless of how many remotes we have.
146-
log.info("VIPs are {map(str, vips)}")
156+
log.info(f"VIPs are {vips!r}")
147157
ctx.vip = {
148158
'vnet': vnet,
149159
'vips': vips,

0 commit comments

Comments
 (0)