D7net
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
proc
/
self
/
root
/
opt
/
saltstack
/
salt
/
lib
/
python3.10
/
site-packages
/
salt
/
runners
/
Filename :
lxc.py
back
Copy
""" Control Linux Containers via Salt :depends: lxc execution module """ import copy import logging import os import time from collections import OrderedDict as _OrderedDict import salt.client import salt.key import salt.utils.args import salt.utils.cloud import salt.utils.files import salt.utils.stringutils import salt.utils.virt log = logging.getLogger(__name__) # Don't shadow built-in's. __func_alias__ = {"list_": "list"} def _do(name, fun, path=None): """ Invoke a function in the lxc module with no args path path to the container parent default: /var/lib/lxc (system default) .. versionadded:: 2015.8.0 """ host = find_guest(name, quiet=True, path=path) if not host: return False with salt.client.get_local_client(__opts__["conf_file"]) as client: cmd_ret = client.cmd_iter( host, f"lxc.{fun}", [name], kwarg={"path": path}, timeout=60 ) data = next(cmd_ret) data = data.get(host, {}).get("ret", None) if data: data = {host: data} return data def _do_names(names, fun, path=None): """ Invoke a function in the lxc module with no args path path to the container parent default: /var/lib/lxc (system default) .. versionadded:: 2015.8.0 """ ret = {} hosts = find_guests(names, path=path) if not hosts: return False with salt.client.get_local_client(__opts__["conf_file"]) as client: for host, sub_names in hosts.items(): cmds = [] for name in sub_names: cmds.append( client.cmd_iter( host, f"lxc.{fun}", [name], kwarg={"path": path}, timeout=60, ) ) for cmd in cmds: data = next(cmd) data = data.get(host, {}).get("ret", None) if data: ret.update({host: data}) return ret def find_guest(name, quiet=False, path=None): """ Returns the host for a container. path path to the container parent default: /var/lib/lxc (system default) .. versionadded:: 2015.8.0 .. code-block:: bash salt-run lxc.find_guest name """ if quiet: log.warning("'quiet' argument is being deprecated. Please migrate to --quiet") for data in _list_iter(path=path): host, l = next(iter(data.items())) for x in "running", "frozen", "stopped": if name in l[x]: if not quiet: __jid_event__.fire_event( {"data": host, "outputter": "lxc_find_host"}, "progress" ) return host return None def find_guests(names, path=None): """ Return a dict of hosts and named guests path path to the container parent default: /var/lib/lxc (system default) .. versionadded:: 2015.8.0 """ ret = {} names = names.split(",") for data in _list_iter(path=path): host, stat = next(iter(data.items())) for state in stat: for name in stat[state]: if name in names: if host in ret: ret[host].append(name) else: ret[host] = [name] return ret def init(names, host=None, saltcloud_mode=False, quiet=False, **kwargs): """ Initialize a new container .. code-block:: bash salt-run lxc.init name host=minion_id [cpuset=cgroups_cpuset] \\ [cpushare=cgroups_cpushare] [memory=cgroups_memory] \\ [template=lxc_template_name] [clone=original name] \\ [profile=lxc_profile] [network_proflile=network_profile] \\ [nic=network_profile] [nic_opts=nic_opts] \\ [start=(true|false)] [seed=(true|false)] \\ [install=(true|false)] [config=minion_config] \\ [snapshot=(true|false)] names Name of the containers, supports a single name or a comma delimited list of names. host Minion on which to initialize the container **(required)** path path to the container parent default: /var/lib/lxc (system default) .. versionadded:: 2015.8.0 saltcloud_mode init the container with the saltcloud opts format instead See lxc.init_interface module documentation cpuset cgroups cpuset. cpushare cgroups cpu shares. memory cgroups memory limit, in MB .. versionchanged:: 2015.5.0 If no value is passed, no limit is set. In earlier Salt versions, not passing this value causes a 1024MB memory limit to be set, and it was necessary to pass ``memory=0`` to set no limit. template Name of LXC template on which to base this container clone Clone this container from an existing container profile A LXC profile (defined in config or pillar). network_profile Network profile to use for the container .. versionadded:: 2015.5.2 nic .. deprecated:: 2015.5.0 Use ``network_profile`` instead nic_opts Extra options for network interfaces. E.g.: ``{"eth0": {"mac": "aa:bb:cc:dd:ee:ff", "ipv4": "10.1.1.1", "ipv6": "2001:db8::ff00:42:8329"}}`` start Start the newly created container. seed Seed the container with the minion config and autosign its key. Default: true install If salt-minion is not already installed, install it. Default: true config Optional config parameters. By default, the id is set to the name of the container. """ path = kwargs.get("path", None) if quiet: log.warning("'quiet' argument is being deprecated. Please migrate to --quiet") ret = {"comment": "", "result": True} if host is None: # TODO: Support selection of host based on available memory/cpu/etc. ret["comment"] = "A host must be provided" ret["result"] = False return ret if isinstance(names, str): names = names.split(",") if not isinstance(names, list): ret["comment"] = "Container names are not formed as a list" ret["result"] = False return ret # check that the host is alive alive = False with salt.client.get_local_client(__opts__["conf_file"]) as client: try: if client.cmd(host, "test.ping", timeout=20).get(host, None): alive = True except (TypeError, KeyError): pass if not alive: ret["comment"] = f"Host {host} is not reachable" ret["result"] = False return ret log.info("Searching for LXC Hosts") data = __salt__["lxc.list"](host, quiet=True, path=path) for host, containers in data.items(): for name in names: if name in sum(containers.values(), []): log.info( "Container '%s' already exists on host '%s', init " "can be a NO-OP", name, host, ) if host not in data: ret["comment"] = f"Host '{host}' was not found" ret["result"] = False return ret kw = salt.utils.args.clean_kwargs(**kwargs) pub_key = kw.get("pub_key", None) priv_key = kw.get("priv_key", None) explicit_auth = pub_key and priv_key approve_key = kw.get("approve_key", True) seeds = {} seed_arg = kwargs.get("seed", True) if approve_key and not explicit_auth: with salt.key.Key(__opts__) as skey: all_minions = skey.all_keys().get("minions", []) for name in names: seed = seed_arg if name in all_minions: try: if client.cmd(name, "test.ping", timeout=20).get( name, None ): seed = False except (TypeError, KeyError): pass seeds[name] = seed kv = salt.utils.virt.VirtKey(host, name, __opts__) if kv.authorize(): log.info("Container key will be preauthorized") else: ret["comment"] = "Container key preauthorization failed" ret["result"] = False return ret log.info("Creating container(s) '%s' on host '%s'", names, host) cmds = [] for name in names: args = [name] kw = salt.utils.args.clean_kwargs(**kwargs) if saltcloud_mode: kw = copy.deepcopy(kw) kw["name"] = name saved_kwargs = kw kw = client.cmd( host, "lxc.cloud_init_interface", args + [kw], tgt_type="list", timeout=600, ).get(host, {}) kw.update(saved_kwargs) name = kw.pop("name", name) # be sure not to seed an already seeded host kw["seed"] = seeds.get(name, seed_arg) if not kw["seed"]: kw.pop("seed_cmd", "") cmds.append( ( host, name, client.cmd_iter(host, "lxc.init", args, kwarg=kw, timeout=600), ) ) done = ret.setdefault("done", []) errors = ret.setdefault("errors", _OrderedDict()) for ix, acmd in enumerate(cmds): hst, container_name, cmd = acmd containers = ret.setdefault(hst, []) herrs = errors.setdefault(hst, _OrderedDict()) serrs = herrs.setdefault(container_name, []) sub_ret = next(cmd) error = None if isinstance(sub_ret, dict) and host in sub_ret: j_ret = sub_ret[hst] container = j_ret.get("ret", {}) if container and isinstance(container, dict): if not container.get("result", False): error = container else: error = "Invalid return for {}: {} {}".format( container_name, container, sub_ret ) else: error = sub_ret if not error: error = "unknown error (no return)" if error: ret["result"] = False serrs.append(error) else: container["container_name"] = name containers.append(container) done.append(container) # marking ping status as True only and only if we have at # least provisioned one container ret["ping_status"] = bool(len(done)) # for all provisioned containers, last job is to verify # - the key status # - we can reach them for container in done: # explicitly check and update # the minion key/pair stored on the master container_name = container["container_name"] key = os.path.join(__opts__["pki_dir"], "minions", container_name) if explicit_auth: fcontent = "" if os.path.exists(key): with salt.utils.files.fopen(key) as fic: fcontent = salt.utils.stringutils.to_unicode(fic.read()).strip() pub_key = salt.utils.stringutils.to_unicode(pub_key) if pub_key.strip() != fcontent: with salt.utils.files.fopen(key, "w") as fic: fic.write(salt.utils.stringutils.to_str(pub_key)) fic.flush() mid = j_ret.get("mid", None) if not mid: continue def testping(**kw): mid_ = kw["mid"] ping = client.cmd(mid_, "test.ping", timeout=20) time.sleep(1) if ping: return "OK" raise Exception(f"Unresponsive {mid_}") ping = salt.utils.cloud.wait_for_fun(testping, timeout=21, mid=mid) if ping != "OK": ret["ping_status"] = False ret["result"] = False # if no lxc detected as touched (either inited or verified) # we result to False if not done: ret["result"] = False if not quiet: __jid_event__.fire_event({"message": ret}, "progress") return ret def cloud_init(names, host=None, quiet=False, **kwargs): """ Wrapper for using lxc.init in saltcloud compatibility mode names Name of the containers, supports a single name or a comma delimited list of names. host Minion to start the container on. Required. path path to the container parent default: /var/lib/lxc (system default) .. versionadded:: 2015.8.0 saltcloud_mode init the container with the saltcloud opts format instead """ if quiet: log.warning("'quiet' argument is being deprecated. Please migrate to --quiet") return __salt__["lxc.init"]( names=names, host=host, saltcloud_mode=True, quiet=quiet, **kwargs ) def _list_iter(host=None, path=None): """ Return a generator iterating over hosts path path to the container parent default: /var/lib/lxc (system default) .. versionadded:: 2015.8.0 """ tgt = host or "*" with salt.client.get_local_client(__opts__["conf_file"]) as client: for container_info in client.cmd_iter(tgt, "lxc.list", kwarg={"path": path}): if not container_info: continue if not isinstance(container_info, dict): continue chunk = {} id_ = next(iter(container_info.keys())) if host and host != id_: continue if not isinstance(container_info[id_], dict): continue if "ret" not in container_info[id_]: continue if not isinstance(container_info[id_]["ret"], dict): continue chunk[id_] = container_info[id_]["ret"] yield chunk def list_(host=None, quiet=False, path=None): """ List defined containers (running, stopped, and frozen) for the named (or all) host(s). path path to the container parent default: /var/lib/lxc (system default) .. versionadded:: 2015.8.0 .. code-block:: bash salt-run lxc.list [host=minion_id] """ it = _list_iter(host, path=path) ret = {} for chunk in it: ret.update(chunk) if not quiet: __jid_event__.fire_event( {"data": chunk, "outputter": "lxc_list"}, "progress" ) return ret def purge(name, delete_key=True, quiet=False, path=None): """ Purge the named container and delete its minion key if present. WARNING: Destroys all data associated with the container. path path to the container parent default: /var/lib/lxc (system default) .. versionadded:: 2015.8.0 .. code-block:: bash salt-run lxc.purge name """ data = _do_names(name, "destroy", path=path) if data is False: return data if delete_key: with salt.key.Key(__opts__) as skey: skey.delete_key(name) if data is None: return if not quiet: __jid_event__.fire_event({"data": data, "outputter": "lxc_purge"}, "progress") return data def start(name, quiet=False, path=None): """ Start the named container. path path to the container parent default: /var/lib/lxc (system default) .. versionadded:: 2015.8.0 .. code-block:: bash salt-run lxc.start name """ data = _do_names(name, "start", path=path) if data and not quiet: __jid_event__.fire_event({"data": data, "outputter": "lxc_start"}, "progress") return data def stop(name, quiet=False, path=None): """ Stop the named container. path path to the container parent default: /var/lib/lxc (system default) .. versionadded:: 2015.8.0 .. code-block:: bash salt-run lxc.stop name """ data = _do_names(name, "stop", path=path) if data and not quiet: __jid_event__.fire_event( {"data": data, "outputter": "lxc_force_off"}, "progress" ) return data def freeze(name, quiet=False, path=None): """ Freeze the named container path path to the container parent default: /var/lib/lxc (system default) .. versionadded:: 2015.8.0 .. code-block:: bash salt-run lxc.freeze name """ data = _do_names(name, "freeze") if data and not quiet: __jid_event__.fire_event({"data": data, "outputter": "lxc_pause"}, "progress") return data def unfreeze(name, quiet=False, path=None): """ Unfreeze the named container path path to the container parent default: /var/lib/lxc (system default) .. versionadded:: 2015.8.0 .. code-block:: bash salt-run lxc.unfreeze name """ data = _do_names(name, "unfreeze", path=path) if data and not quiet: __jid_event__.fire_event({"data": data, "outputter": "lxc_resume"}, "progress") return data def info(name, quiet=False, path=None): """ Returns information about a container. path path to the container parent default: /var/lib/lxc (system default) .. versionadded:: 2015.8.0 .. code-block:: bash salt-run lxc.info name """ data = _do_names(name, "info", path=path) if data and not quiet: __jid_event__.fire_event({"data": data, "outputter": "lxc_info"}, "progress") return data