modules/lxc: rework settings for consistency

Signed-off-by: Mathieu Le Marec - Pasquet <kiorky@cryptelium.net>
This commit is contained in:
Mathieu Le Marec - Pasquet 2015-05-22 15:52:06 +02:00
parent ce11d8352e
commit 999391551c

View file

@ -22,6 +22,7 @@ import time
import shutil
import re
import random
import distutils.version
# Import salt libs
import salt
@ -82,6 +83,31 @@ def __virtual__():
return False
def version():
'''
Return the actual lxc client version
.. versionadded:: 2015.5.2
CLI Example:
.. code-block:: bash
salt '*' lxc.version
'''
k = 'lxc.version'
if not __context__.get(k, None):
cversion = __salt__['cmd.run_all']('lxc-ls --version')
if not cversion['retcode']:
ver = distutils.version.LooseVersion(cversion['stdout'])
if ver < '1.0':
raise CommandExecutionError('LXC should be at least 1.0')
__context__[k] = "{0}".format(ver)
return __context__.get(k, None)
def _clear_context():
'''
Clear any lxc variables set in __context__
@ -135,7 +161,7 @@ def search_lxc_bridges():
if ifc in running_bridges:
bridges.add(ifc)
elif os.path.exists(
'a/sys/devices/virtual/net/{0}/bridge'.format(ifc)
'/sys/devices/virtual/net/{0}/bridge'.format(ifc)
):
bridges.add(ifc)
bridges = list(bridges)
@ -208,36 +234,16 @@ def cloud_init_interface(name, vm_=None, **kwargs):
name
name of the lxc container to create
from_container
which container we use as a template
when running lxc.clone
image
which template do we use when we
are using lxc.create. This is the default
mode unless you specify something in from_container
backing
which backing store to use.
Values can be: overlayfs, dir(default), lvm, zfs, brtfs
fstype
When using a blockdevice level backing store,
which filesystem to use on
size
When using a blockdevice level backing store,
which size for the filesystem to use on
snapshot
Use snapshot when cloning the container source
vgname
if using LVM: vgname
lgname
if using LVM: lvname
pub_key
public key to preseed the minion with.
Can be the keycontent or a filepath
priv_key
private key to preseed the minion with.
Can be the keycontent or a filepath
profile
:ref:`profile <tutorial-lxc-profiles-container>` selection
network_profile
salt lxc network profile selection
:ref:`network profile <tutorial-lxc-profiles-network>` selection
nic_opts
per interface settings compatibles with
network profile (ipv4/ipv6/link/gateway/mac/netmask)
@ -262,15 +268,34 @@ def cloud_init_interface(name, vm_=None, **kwargs):
autostart the container at boot time
password
administrative password for the container
users
administrative users for the container
default: [root] and [root, ubuntu] on ubuntu
default_nic
name of the first interface, you should
really not override this
Legacy but still supported options:
.. warning::
Legacy but still supported options:
from_container
which container we use as a template
when running lxc.clone
image
which template do we use when we
are using lxc.create. This is the default
mode unless you specify something in from_container
backing
which backing store to use.
Values can be: overlayfs, dir(default), lvm, zfs, brtfs
fstype
When using a blockdevice level backing store,
which filesystem to use on
size
When using a blockdevice level backing store,
which size for the filesystem to use on
snapshot
Use snapshot when cloning the container source
vgname
if using LVM: vgname
lgname
if using LVM: lvname
ip
ip for the primary nic
mac
@ -298,6 +323,13 @@ def cloud_init_interface(name, vm_=None, **kwargs):
'netmask': '', (default)
'ip': '22.1.4.25'}
users
administrative users for the container
default: [root] and [root, ubuntu] on ubuntu
default_nic
name of the first interface, you should
really not override this
CLI Example:
.. code-block:: bash
@ -309,62 +341,63 @@ def cloud_init_interface(name, vm_=None, **kwargs):
vm_ = {}
vm_ = copy.deepcopy(vm_)
vm_ = salt.utils.dictupdate.update(vm_, kwargs)
if 'profile' in vm_:
profile_data = copy.deepcopy(vm_['profile'])
if isinstance(profile_data, dict):
profile_name = profile_data.pop('name', name)
else:
# assuming profile is a string (profile name)
profile_name = profile_data
profile_data = {}
profile = get_container_profile(profile_name, **profile_data)
else:
profile = {}
profile_data = copy.deepcopy(
vm_.get('lxc_profile',
vm_.get('profile', {})))
if not isinstance(profile_data, (dict, six.string_types)):
profile_data = {}
profile = get_container_profile(profile_data)
def _cloud_get(k, default=None):
return vm_.get(k, profile.get(k, default))
if name is None:
name = vm_['name']
# if we are on ubuntu, default to ubuntu
default_template = ''
if __grains__.get('os', '') in ['Ubuntu']:
default_template = 'ubuntu'
image = vm_.get('image', profile.get('template',
default_template))
backing = vm_.get('backing', 'dir')
image = _cloud_get('image')
if not image:
_cloud_get('template', default_template)
backing = _cloud_get('backing', 'dir')
if image:
profile['template'] = image
vgname = vm_.get('vgname', None)
vgname = _cloud_get('vgname', None)
if vgname:
profile['vgname'] = vgname
if backing:
profile['backing'] = backing
snapshot = vm_.get('snapshot', False)
autostart = bool(vm_.get('autostart', True))
dnsservers = vm_.get('dnsservers', [])
snapshot = _cloud_get('snapshot', False)
autostart = bool(_cloud_get('autostart', True))
dnsservers = _cloud_get('dnsservers', [])
if not dnsservers:
dnsservers = ['8.8.8.8', '4.4.4.4']
password = vm_.get('password', 's3cr3t')
password_encrypted = vm_.get('password_encrypted', False)
fstype = vm_.get('fstype', None)
lvname = vm_.get('lvname', None)
pub_key = vm_.get('pub_key', None)
priv_key = vm_.get('priv_key', None)
size = vm_.get('size', '20G')
script = vm_.get('script', None)
script_args = vm_.get('script_args', None)
users = vm_.get('users', None)
password = _cloud_get('password', 's3cr3t')
password_encrypted = _cloud_get('password_encrypted', False)
fstype = _cloud_get('fstype', None)
lvname = _cloud_get('lvname', None)
pub_key = _cloud_get('pub_key', None)
priv_key = _cloud_get('priv_key', None)
size = _cloud_get('size', '20G')
script = _cloud_get('script', None)
script_args = _cloud_get('script_args', None)
users = _cloud_get('users', None)
if users is None:
users = []
ssh_username = vm_.get('ssh_username', None)
ssh_username = _cloud_get('ssh_username', None)
if ssh_username and (ssh_username not in users):
users.append(ssh_username)
net_profile = vm_.get('network_profile', _marker)
nic_opts = kwargs.pop('nic_opts', None)
netmask = vm_.get('netmask', '24')
bridge = vm_.get('bridge', None)
gateway = vm_.get('gateway', None)
unconditional_install = vm_.get('unconditional_install', False)
force_install = vm_.get('force_install', True)
config = _get_salt_config(vm_.get('config', {}), **vm_)
default_nic = vm_.get('default_nic', DEFAULT_NIC)
network_profile = _cloud_get('network_profile', None)
nic_opts = kwargs.get('nic_opts', None)
netmask = _cloud_get('netmask', '24')
bridge = _cloud_get('bridge', None)
gateway = _cloud_get('gateway', None)
unconditional_install = _cloud_get('unconditional_install', False)
force_install = _cloud_get('force_install', True)
config = _get_salt_config(_cloud_get('config', {}), **vm_)
default_nic = _cloud_get('default_nic', DEFAULT_NIC)
# do the interface with lxc.init mainly via nic_opts
# to avoid extra and confusing extra use cases.
if not isinstance(nic_opts, dict):
@ -379,9 +412,9 @@ def cloud_init_interface(name, vm_=None, **kwargs):
nic_opts = bnic_opts
gw = None
# legacy salt.cloud scheme for network interfaces settings support
bridge = vm_.get('bridge', None)
ip = vm_.get('ip', None)
mac = vm_.get('mac', None)
bridge = _cloud_get('bridge', None)
ip = _cloud_get('ip', None)
mac = _cloud_get('mac', None)
if ip:
fullip = ip
if netmask:
@ -389,7 +422,7 @@ def cloud_init_interface(name, vm_=None, **kwargs):
eth0['ipv4'] = fullip
if mac is not None:
eth0['mac'] = mac
for ix, iopts in enumerate(vm_.get("additional_ips", [])):
for ix, iopts in enumerate(_cloud_get("additional_ips", [])):
ifh = "eth{0}".format(ix+1)
ethx = nic_opts.setdefault(ifh, {})
if gw is None:
@ -440,13 +473,13 @@ def cloud_init_interface(name, vm_=None, **kwargs):
lxc_init_interface = {}
lxc_init_interface['name'] = name
lxc_init_interface['config'] = config
lxc_init_interface['memory'] = vm_.get('memory', 0) # nolimit
lxc_init_interface['memory'] = _cloud_get('memory', 0) # nolimit
lxc_init_interface['pub_key'] = pub_key
lxc_init_interface['priv_key'] = priv_key
lxc_init_interface['nic_opts'] = nic_opts
for clone_from in ['clone_from', 'clone', 'from_container']:
# clone_from should default to None if not available
lxc_init_interface['clone_from'] = vm_.get(clone_from, None)
lxc_init_interface['clone_from'] = _cloud_get(clone_from, None)
if lxc_init_interface['clone_from'] is not None:
break
lxc_init_interface['profile'] = profile
@ -462,21 +495,65 @@ def cloud_init_interface(name, vm_=None, **kwargs):
)
lxc_init_interface['bootstrap_url'] = script
lxc_init_interface['bootstrap_args'] = script_args
lxc_init_interface['bootstrap_shell'] = vm_.get('bootstrap_shell', 'sh')
lxc_init_interface['bootstrap_shell'] = _cloud_get('bootstrap_shell', 'sh')
lxc_init_interface['autostart'] = autostart
lxc_init_interface['users'] = users
lxc_init_interface['password'] = password
lxc_init_interface['password_encrypted'] = password_encrypted
# be sure not to let objects goes inside the return
# as this return will be msgpacked for use in the runner !
if net_profile is not _marker:
lxc_init_interface['network_profile'] = net_profile
lxc_init_interface['network_profile'] = network_profile
for i in ['cpu', 'cpuset', 'cpushare']:
if vm_.get(i, None):
lxc_init_interface[i] = vm_[i]
if _cloud_get(i, None):
try:
lxc_init_interface[i] = vm_[i]
except KeyError:
lxc_init_interface[i] = profile[i]
return lxc_init_interface
def _get_profile(key, old_key, name, **kwargs):
if isinstance(name, dict):
profilename = name.pop('name', None)
return _get_profile(key, old_key, profilename, **name)
if name is None:
profile_match = {}
else:
profile_match = \
__salt__['config.get'](
'lxc.{1}:{0}'.format(name, key),
default=None,
merge='recurse'
)
if profile_match is None:
# Try legacy profile location
profile_match = \
__salt__['config.get'](
'lxc.{1}:{0}'.format(name, old_key), None)
if profile_match is not None:
salt.utils.warn_until(
'Boron',
'lxc.{1} has been deprecated, please configure LXC '
'container profiles under lxc.{0} instead'.format(
key, old_key))
else:
# No matching profile, make the profile an empty dict so that
# overrides can be applied below.
profile_match = {}
if not isinstance(profile_match, dict):
raise CommandExecutionError('lxc.{0} must be a dictionary'.format(key))
# Overlay the kwargs to override matched profile data
overrides = salt.utils.clean_kwargs(**copy.deepcopy(kwargs))
profile_match = salt.utils.dictupdate.update(
copy.deepcopy(profile_match),
overrides
)
return profile_match
def get_container_profile(name=None, **kwargs):
'''
.. versionadded:: 2015.5.0
@ -514,51 +591,11 @@ def get_container_profile(name=None, **kwargs):
salt-call lxc.get_container_profile centos
salt-call lxc.get_container_profile ubuntu template=ubuntu backing=overlayfs
'''
if isinstance(name, dict):
profilename = name.pop('name', None)
return get_container_profile(profilename, **name)
if name is None:
profile_match = {}
else:
profile_match = \
__salt__['config.get'](
'lxc.container_profile:{0}'.format(name),
default=None,
merge='recurse'
)
if profile_match is None:
# Try legacy profile location
profile_match = \
__salt__['config.get']('lxc.profile:{0}'.format(name), None)
if profile_match is not None:
salt.utils.warn_until(
'Boron',
'lxc.profile has been deprecated, please configure LXC '
'container profiles under lxc.container_profile instead'
)
else:
# No matching profile, make the profile an empty dict so that
# overrides can be applied below.
profile_match = {}
if not isinstance(profile_match, dict):
raise CommandExecutionError('Container profile must be a dictionary')
# Overlay the kwargs to override matched profile data
overrides = copy.deepcopy(kwargs)
for key in overrides.keys():
if key.startswith('__'):
# Remove pub data from kwargs
overrides.pop(key)
profile_match = salt.utils.dictupdate.update(
copy.deepcopy(profile_match),
overrides
)
return profile_match
profile = _get_profile('container_profile', 'profile', name, **kwargs)
return profile
def get_network_profile(name=None):
def get_network_profile(name=None, **kwargs):
'''
.. versionadded:: 2015.5.0
@ -583,6 +620,14 @@ def get_network_profile(name=None):
type: veth
flags: up
To disable entirely net:
.. code-block:: yaml
lxc.network_profile.centos:
eth0:
disable: true
Parameters set in a profile can be overridden by passing additional
arguments to this function.
@ -605,29 +650,9 @@ def get_network_profile(name=None):
salt-call lxc.get_network_profile default
'''
if name is None:
net_profile = None
else:
net_profile = \
__salt__['config.get'](
'lxc.network_profile:{0}'.format(name),
default=None,
merge='recurse'
)
if net_profile is None:
# Try legacy profile location
net_profile = \
__salt__['config.get']('lxc.nic:{0}'.format(name), None)
if net_profile is not None:
salt.utils.warn_until(
'Boron',
'lxc.nic has been deprecated, please configure LXC '
'network profiles under lxc.network_profile instead'
)
if name == DEFAULT_NIC and not net_profile:
net_profile = {DEFAULT_NIC: {}}
return net_profile if net_profile is not None else {}
profile = _get_profile('network_profile', 'nic', name, **kwargs)
return profile
def _rand_cpu_str(cpu):
@ -658,117 +683,121 @@ def _network_conf(conf_tuples=None, **kwargs):
overrides or extra nics in the form {nic_name: {set: tings}
'''
nic = kwargs.pop('network_profile', None)
nic = kwargs.get('network_profile', None)
ret = []
nic_opts = kwargs.pop('nic_opts', {})
nic_opts = kwargs.get('nic_opts', {})
if nic_opts is None:
# coming from elsewhere
nic_opts = {}
if not conf_tuples:
conf_tuples = []
old = _get_veths(conf_tuples)
if not nic and not nic_opts and not old:
return ret
kwargs = copy.deepcopy(kwargs)
gateway = kwargs.pop('gateway', None)
bridge = kwargs.get('bridge', None)
if not old:
old = {}
# if we have a profile name, get the profile and load the network settings
# this will obviously by default look for a profile called "eth0"
# or by what is defined in nic_opts
# and complete each nic settings by sane defaults
if isinstance(nic, six.string_types):
if nic and isinstance(nic, (six.string_types, dict)):
nicp = get_network_profile(nic)
if nic_opts:
for dev, args in nic_opts.items():
ethx = nicp.setdefault(dev, {})
try:
ethx = salt.utils.dictupdate.update(ethx, args)
except AttributeError:
raise SaltInvocationError('Invalid nic_opts configuration')
ifs = [a for a in nicp]
ifs += [a for a in old if a not in nicp]
ifs.sort()
gateway_set = False
for dev in ifs:
args = nicp.get(dev, {})
opts = nic_opts.get(dev, {}) if nic_opts else {}
old_if = old.get(dev, {})
flags = opts.get('flags', '')
mac = opts.get('mac', '')
type_ = opts.get('type', args.get('type', ''))
link = opts.get('link', args.get('link', ''))
ipv4 = opts.get('ipv4')
ipv6 = opts.get('ipv6')
infos = salt.utils.odict.OrderedDict([
('lxc.network.type', {
'test': not type_,
'value': type_,
'old': old_if.get('lxc.network.type'),
'default': 'veth'}),
('lxc.network.name', {
'test': False,
'value': dev,
'old': dev,
'default': dev}),
('lxc.network.flags', {
'test': not flags,
'value': flags,
'old': old_if.get('lxc.network.flags'),
'default': 'up'}),
('lxc.network.link', {
'test': not link,
'value': link,
'old': old_if.get('lxc.network.link'),
'default': search_lxc_bridge()}),
('lxc.network.hwaddr', {
'test': not mac,
'value': mac,
'old': old_if.get('lxc.network.hwaddr'),
'default': salt.utils.gen_mac()}),
('lxc.network.ipv4', {
'test': not ipv4,
'value': ipv4,
'old': old_if.get('lxc.network.ipv4', ''),
'default': None}),
('lxc.network.ipv6', {
'test': not ipv6,
'value': ipv6,
'old': old_if.get('lxc.network.ipv6', ''),
'default': None})])
# for each parameter, if not explicitly set, the
# config value present in the LXC configuration should
# take precedence over the profile configuration
for info in list(infos.keys()):
bundle = infos[info]
if bundle['test']:
if bundle['old']:
bundle['value'] = bundle['old']
elif bundle['default']:
bundle['value'] = bundle['default']
for info, data in infos.items():
if data['value']:
ret.append({info: data['value']})
for key, val in six.iteritems(args):
if key == 'link' and bridge:
val = bridge
val = opts.get(key, val)
if key in [
'type', 'flags', 'name',
'gateway', 'mac', 'link', 'ipv4', 'ipv6'
]:
continue
ret.append({'lxc.network.{0}'.format(key): val})
# gateway (in automode) must be appended following network conf !
if not gateway:
gateway = args.get('gateway', None)
if gateway is not None and not gateway_set:
ret.append({'lxc.network.ipv4.gateway': gateway})
# only one network gateway ;)
gateway_set = True
# normally, this wont happen
# set the gateway if specified even if we did
# not managed the network underlying
else:
nicp = {}
if DEFAULT_NIC not in nicp:
nicp[DEFAULT_NIC] = {}
kwargs = copy.deepcopy(kwargs)
gateway = kwargs.pop('gateway', None)
bridge = kwargs.get('bridge', None)
if nic_opts:
for dev, args in six.iteritems(nic_opts):
ethx = nicp.setdefault(dev, {})
try:
ethx = salt.utils.dictupdate.update(ethx, args)
except AttributeError:
raise SaltInvocationError('Invalid nic_opts configuration')
ifs = [a for a in nicp]
ifs += [a for a in old if a not in nicp]
ifs.sort()
gateway_set = False
for dev in ifs:
args = nicp.get(dev, {})
opts = nic_opts.get(dev, {}) if nic_opts else {}
old_if = old.get(dev, {})
disable = opts.get('disable', args.get('disable', False))
if disable:
continue
mac = opts.get('mac',
opts.get('hwaddr',
args.get('mac',
args.get('hwaddr', ''))))
type_ = opts.get('type', args.get('type', ''))
flags = opts.get('flags', args.get('flags', ''))
link = opts.get('link', args.get('link', ''))
ipv4 = opts.get('ipv4', args.get('ipv4', ''))
ipv6 = opts.get('ipv6', args.get('ipv6', ''))
infos = salt.utils.odict.OrderedDict([
('lxc.network.type', {
'test': not type_,
'value': type_,
'old': old_if.get('lxc.network.type'),
'default': 'veth'}),
('lxc.network.name', {
'test': False,
'value': dev,
'old': dev,
'default': dev}),
('lxc.network.flags', {
'test': not flags,
'value': flags,
'old': old_if.get('lxc.network.flags'),
'default': 'up'}),
('lxc.network.link', {
'test': not link,
'value': link,
'old': old_if.get('lxc.network.link'),
'default': search_lxc_bridge()}),
('lxc.network.hwaddr', {
'test': not mac,
'value': mac,
'old': old_if.get('lxc.network.hwaddr'),
'default': salt.utils.gen_mac()}),
('lxc.network.ipv4', {
'test': not ipv4,
'value': ipv4,
'old': old_if.get('lxc.network.ipv4', ''),
'default': None}),
('lxc.network.ipv6', {
'test': not ipv6,
'value': ipv6,
'old': old_if.get('lxc.network.ipv6', ''),
'default': None})])
# for each parameter, if not explicitly set, the
# config value present in the LXC configuration should
# take precedence over the profile configuration
for info in list(infos.keys()):
bundle = infos[info]
if bundle['test']:
if bundle['old']:
bundle['value'] = bundle['old']
elif bundle['default']:
bundle['value'] = bundle['default']
for info, data in infos.items():
if data['value']:
ret.append({info: data['value']})
for key, val in six.iteritems(args):
if key == 'link' and bridge:
val = bridge
val = opts.get(key, val)
if key in [
'type', 'flags', 'name',
'gateway', 'mac', 'link', 'ipv4', 'ipv6'
]:
continue
ret.append({'lxc.network.{0}'.format(key): val})
# gateway (in automode) must be appended following network conf !
if not gateway:
gateway = args.get('gateway', None)
if gateway is not None and not gateway_set:
ret.append({'lxc.network.ipv4.gateway': gateway})
# only one network gateway ;)
@ -800,6 +829,14 @@ def _network_conf(conf_tuples=None, **kwargs):
for val in new.values():
for row in val:
ret.append(salt.utils.odict.OrderedDict([(row, val[row])]))
# on old versions of lxc, still support the gateway auto mode
# if we didnt explicitly say no to
# (lxc.network.ipv4.gateway: auto)
if (
distutils.version.LooseVersion(version()) <= '1.0.7' and
True not in ['ipv4.gateway' in a[0] for a in ret]
):
ret.append({'lxc.network.ipv4.gateway': 'auto'})
return ret
@ -1042,7 +1079,7 @@ def init(name,
cpushare=None,
memory=None,
profile=None,
network_profile=_marker,
network_profile=None,
nic=_marker,
nic_opts=None,
cpu=None,
@ -1230,6 +1267,7 @@ def init(name,
ret = {'name': name,
'changes': {}}
profile = get_container_profile(copy.deepcopy(profile))
if bool(nic) and nic is not _marker:
salt.utils.warn_until(
'Boron',
@ -1237,7 +1275,9 @@ def init(name,
'please use \'network_profile\' instead.'
)
network_profile = nic
if network_profile is _marker:
if not network_profile:
network_profile = profile.get('network_profile')
if not network_profile:
network_profile = DEFAULT_NIC
try:
@ -1266,7 +1306,6 @@ def init(name,
if user not in users:
users.append(user)
profile = get_container_profile(copy.deepcopy(profile))
kw_overrides = copy.deepcopy(kwargs)
def select(key, default=None):
@ -4113,6 +4152,13 @@ def apply_network_profile(name, network_profile, nic_opts=None):
salt 'minion' lxc.apply_network_profile web1 \\
"{'eth0': {'mac': 'xx:xx:xx:xx:xx:yy'}}"
nic_opts="{'eth0': {'mac': 'xx:xx:xx:xx:xx:xx'}}"
The special case to disable use of ethernet nics:
.. code-block:: bash
salt 'minion' lxc.apply_network_profile web1 centos \\
"{eth0: {disable: true}}"
'''
path = '/var/lib/lxc/{0}/config'.format(name)