diff --git a/confluent_client/doc/man/nodeattrib.ronn.tmpl b/confluent_client/doc/man/nodeattrib.ronn.tmpl index 2dd4e5be..cbe91380 100644 --- a/confluent_client/doc/man/nodeattrib.ronn.tmpl +++ b/confluent_client/doc/man/nodeattrib.ronn.tmpl @@ -130,6 +130,21 @@ to a blank value will allow masking a group defined attribute with an empty valu `node12: net.compute.ipv4_address: 172.17.0.12/16` `node12: net.compute.team_mode: lacp` +* Passing additional settings to the network backend of the deployed OS with `net.extra_settings` + (semicolon-delimited key=value pairs, keys in the native syntax of the respective backend). + On a NetworkManager based OS (e.g. Enterprise Linux), keys are nmcli properties: + `# nodeattrib node12 net.mgmt.extra_settings='connection.zone=internal;ipv4.routes=10.0.0.0/8 192.168.1.254, 172.16.0.0/12 192.168.1.254;ipv4.route-metric=200'` + `node12: net.mgmt.extra_settings: connection.zone=internal;ipv4.routes=10.0.0.0/8 192.168.1.254, 172.16.0.0/12 192.168.1.254;ipv4.route-metric=200` + +* On a netplan based OS (e.g. Ubuntu), keys are netplan YAML paths (nested keys dotted, values in YAML flow syntax). + Since Confluent uses braces for attribute expressions, literal braces must be escaped as `{{` and `}}` when setting the attribute: + `# nodeattrib node13 net.mgmt.extra_settings='routes=[{{to: 10.0.0.0/8, via: 192.168.1.254}}];nameservers.search=[lab.example.com]'` + `node13: net.mgmt.extra_settings: routes=[{to: 10.0.0.0/8, via: 192.168.1.254}];nameservers.search=[lab.example.com]` + +* On a wicked based OS (e.g. SUSE), keys are ifcfg variables (routes are not supported through this mechanism on wicked): + `# nodeattrib node14 net.mgmt.extra_settings='ZONE=internal;ETHTOOL_OPTIONS=-K iface tso off'` + `node14: net.mgmt.extra_settings: ZONE=internal;ETHTOOL_OPTIONS=-K iface tso off` + * Clear attribute on nodes of a simple noderange, if you want to retain the variable set the attribute to "": `# nodeattrib n1-n2 -c console.method` `# nodeattrib n1-n2 console.method` diff --git a/confluent_osdeploy/common/profile/scripts/confignet b/confluent_osdeploy/common/profile/scripts/confignet index d4adc8b4..0108c540 100644 --- a/confluent_osdeploy/common/profile/scripts/confignet +++ b/confluent_osdeploy/common/profile/scripts/confignet @@ -114,6 +114,14 @@ def get_interface_name(iname, settings): return iname return None +def parse_extra_settings(stgs): + extras = {} + for kv in stgs.get('extra_settings', '').split(';'): + k, _, v = kv.partition('=') + if k.strip(): + extras[k.strip()] = v.strip() + return extras + class NetplanManager(object): def __init__(self, deploycfg): self.cfgbydev = {} @@ -229,6 +237,24 @@ class NetplanManager(object): if dnsdomain not in currdnsdomain: needcfgwrite = True currdnsdomain.append(dnsdomain) + extras = parse_extra_settings(stgs) + if extras: + currcfg = self.cfgbybond if devname in self.cfgbybond else self.cfgbydev + devdict = currcfg.setdefault(devname, {}) + for key in extras: + try: + val = yaml.safe_load(extras[key]) + except yaml.YAMLError: + val = extras[key] + keyptr = devdict + keypath = key.split('.') + for k in keypath[:-1]: + if not isinstance(keyptr.get(k, None), dict): + keyptr[k] = {} + keyptr = keyptr[k] + if keyptr.get(keypath[-1], None) != val: + needcfgwrite = True + keyptr[keypath[-1]] = val prune_from_cloudinit = [] if needcfgwrite: needcfgapply = True @@ -336,6 +362,9 @@ class WickedManager(object): if stgs.get('ipv6_address', None): ipcfg += 'IPADDR_V6=' + stgs['ipv6_address'] + '\n' v6gw = stgs.get('ipv6_gateway', None) + extras = parse_extra_settings(stgs) + for key in extras: + ipcfg += '{0}={1}\n'.format(key, shlex.quote(extras[key])) cname = None if len(cfg['interfaces']) > 1: # creating new team if not stgs.get('team_mode', None): @@ -475,6 +504,7 @@ class NetworkManager(object): cmdargs['ipv4.dns'] = ','.join(dns4) if dns6: cmdargs['ipv6.dns'] = ','.join(dns6) + cmdargs.update(parse_extra_settings(stgs)) if len(cfg['interfaces']) > 1: # team time.. should be.. if not cfg['settings'].get('team_mode', None): sys.stderr.write("Warning, multiple interfaces ({0}) without a team_mode, skipping setup\n".format(','.join(cfg['interfaces']))) diff --git a/confluent_osdeploy/debian/profiles/default/scripts/confignet b/confluent_osdeploy/debian/profiles/default/scripts/confignet index 78126b6f..91734b5a 100644 --- a/confluent_osdeploy/debian/profiles/default/scripts/confignet +++ b/confluent_osdeploy/debian/profiles/default/scripts/confignet @@ -111,6 +111,14 @@ def get_interface_name(iname, settings): return iname return None +def parse_extra_settings(stgs): + extras = {} + for kv in stgs.get('extra_settings', '').split(';'): + k, _, v = kv.partition('=') + if k.strip(): + extras[k.strip()] = v.strip() + return extras + class NetplanManager(object): def __init__(self, deploycfg): self.cfgbydev = {} @@ -192,6 +200,23 @@ class NetplanManager(object): if dnsdomain not in currdnsdomain: needcfgwrite = True currdnsdomain.append(dnsdomain) + extras = parse_extra_settings(stgs) + if extras: + devdict = self.cfgbydev.setdefault(devname, {}) + for key in extras: + try: + val = yaml.safe_load(extras[key]) + except yaml.YAMLError: + val = extras[key] + keyptr = devdict + keypath = key.split('.') + for k in keypath[:-1]: + if not isinstance(keyptr.get(k, None), dict): + keyptr[k] = {} + keyptr = keyptr[k] + if keyptr.get(keypath[-1], None) != val: + needcfgwrite = True + keyptr[keypath[-1]] = val if needcfgwrite: needcfgapply = True newcfg = {'network': {'version': 2, 'ethernets': {devname: self.cfgbydev[devname]}}} @@ -261,6 +286,9 @@ class WickedManager(object): if stgs.get('ipv6_address', None): ipcfg += 'IPADDR_V6=' + stgs['ipv6_address'] + '\n' v6gw = stgs.get('ipv6_gateway', None) + extras = parse_extra_settings(stgs) + for key in extras: + ipcfg += '{0}={1}\n'.format(key, shlex.quote(extras[key])) cname = None if len(cfg['interfaces']) > 1: # creating new team if not stgs.get('team_mode', None): @@ -400,6 +428,7 @@ class NetworkManager(object): cmdargs['ipv4.dns'] = ','.join(dns4) if dns6: cmdargs['ipv6.dns'] = ','.join(dns6) + cmdargs.update(parse_extra_settings(stgs)) if len(cfg['interfaces']) > 1: # team time.. should be.. if not cfg['settings'].get('team_mode', None): sys.stderr.write("Warning, multiple interfaces ({0}) without a team_mode, skipping setup\n".format(','.join(cfg['interfaces']))) diff --git a/confluent_server/confluent/config/attributes.py b/confluent_server/confluent/config/attributes.py index 21cc2b29..06f430ed 100644 --- a/confluent_server/confluent/config/attributes.py +++ b/confluent_server/confluent/config/attributes.py @@ -589,6 +589,16 @@ node = { 'To support this scenario, the switch should be set up to allow independent operation of member ports (e.g. lacp bypass mode or fallback mode).', 'validvalues': ('lacp', 'loadbalance', 'roundrobin', 'activebackup', 'none') }, + 'net.extra_settings': { + 'description': 'Additional network settings to apply to the connection, as ' + 'semicolon-delimited key=value pairs (e.g. ' + '"connection.zone=internal;ipv4.routes=10.0.0.0/8 192.168.1.254"). ' + 'The keys are passed through to the network configuration backend of ' + 'the deployed OS and use its native syntax: nmcli property names on ' + 'NetworkManager based systems, netplan YAML keys (values in YAML flow ' + 'syntax, nested keys dotted) on netplan based systems, or ifcfg ' + 'variables on wicked based systems.', + }, 'power.pdu': { 'description': 'Specifies the managed PDU associated with a power input on the node' }, diff --git a/confluent_server/confluent/netutil.py b/confluent_server/confluent/netutil.py index 5d3636ff..14c8cda8 100644 --- a/confluent_server/confluent/netutil.py +++ b/confluent_server/confluent/netutil.py @@ -246,6 +246,9 @@ class NetManager(object): teammod = attribs.get('team_mode', None) if teammod: myattribs['team_mode'] = teammod + extrastgs = attribs.get('extra_settings', None) + if extrastgs: + myattribs['extra_settings'] = extrastgs method = attribs.get('ipv4_method', None) if method != 'dhcp': ipv4addr = attribs.get('ipv4_address', None)