zfs.py (salt-3002.1) | : | zfs.py (salt-3002.2) | ||
---|---|---|---|---|
# -*- coding: utf-8 -*- | ||||
""" | """ | |||
States for managing zfs datasets | States for managing zfs datasets | |||
:maintainer: Jorge Schrauwen <sjorge@blackdot.be> | :maintainer: Jorge Schrauwen <sjorge@blackdot.be> | |||
:maturity: new | :maturity: new | |||
:depends: salt.utils.zfs, salt.modules.zfs | :depends: salt.utils.zfs, salt.modules.zfs | |||
:platform: smartos, illumos, solaris, freebsd, linux | :platform: smartos, illumos, solaris, freebsd, linux | |||
.. versionadded:: 2016.3.0 | .. versionadded:: 2016.3.0 | |||
.. versionchanged:: 2018.3.1 | .. versionchanged:: 2018.3.1 | |||
skipping to change at line 46 | skipping to change at line 45 | |||
- snapshot: test/shares/yuki@frozen | - snapshot: test/shares/yuki@frozen | |||
test/shares/moka: | test/shares/moka: | |||
zfs.filesystem_present: | zfs.filesystem_present: | |||
- cloned_from: test/shares/yuki@frozen | - cloned_from: test/shares/yuki@frozen | |||
test/shares/moka@tsukune: | test/shares/moka@tsukune: | |||
zfs.snapshot_absent | zfs.snapshot_absent | |||
""" | """ | |||
from __future__ import absolute_import, print_function, unicode_literals | ||||
# Import Python libs | ||||
import logging | import logging | |||
from datetime import datetime | from datetime import datetime | |||
# Import Salt libs | ||||
from salt.utils.odict import OrderedDict | from salt.utils.odict import OrderedDict | |||
log = logging.getLogger(__name__) | log = logging.getLogger(__name__) | |||
# Define the state's virtual name | # Define the state's virtual name | |||
__virtualname__ = "zfs" | __virtualname__ = "zfs" | |||
# Compare modifiers for zfs.schedule_snapshot | # Compare modifiers for zfs.schedule_snapshot | |||
comp_hour = {"minute": 0} | comp_hour = {"minute": 0} | |||
comp_day = {"minute": 0, "hour": 0} | comp_day = {"minute": 0, "hour": 0} | |||
skipping to change at line 110 | skipping to change at line 106 | |||
if not __opts__["test"]: | if not __opts__["test"]: | |||
mod_res = __salt__["zfs.destroy"]( | mod_res = __salt__["zfs.destroy"]( | |||
name, **{"force": force, "recursive": recursive} | name, **{"force": force, "recursive": recursive} | |||
) | ) | |||
else: | else: | |||
mod_res = OrderedDict([("destroyed", True)]) | mod_res = OrderedDict([("destroyed", True)]) | |||
ret["result"] = mod_res["destroyed"] | ret["result"] = mod_res["destroyed"] | |||
if ret["result"]: | if ret["result"]: | |||
ret["changes"][name] = "destroyed" | ret["changes"][name] = "destroyed" | |||
ret["comment"] = "{0} {1} was destroyed".format(dataset_type, name,) | ret["comment"] = "{} {} was destroyed".format(dataset_type, name,) | |||
else: | else: | |||
ret["comment"] = "failed to destroy {0} {1}".format(dataset_type, na me,) | ret["comment"] = "failed to destroy {} {}".format(dataset_type, name ,) | |||
if "error" in mod_res: | if "error" in mod_res: | |||
ret["comment"] = mod_res["error"] | ret["comment"] = mod_res["error"] | |||
else: | else: | |||
## NOTE: no dataset found with name of the dataset_type | ## NOTE: no dataset found with name of the dataset_type | |||
ret["comment"] = "{0} {1} is absent".format(dataset_type, name) | ret["comment"] = "{} {} is absent".format(dataset_type, name) | |||
return ret | return ret | |||
def filesystem_absent(name, force=False, recursive=False): | def filesystem_absent(name, force=False, recursive=False): | |||
""" | """ | |||
ensure filesystem is absent on the system | ensure filesystem is absent on the system | |||
name : string | name : string | |||
name of filesystem | name of filesystem | |||
force : boolean | force : boolean | |||
skipping to change at line 143 | skipping to change at line 139 | |||
If a volume with ``name`` exists, this state will succeed without | If a volume with ``name`` exists, this state will succeed without | |||
destroying the volume specified by ``name``. This module is dataset type sensitive. | destroying the volume specified by ``name``. This module is dataset type sensitive. | |||
""" | """ | |||
if not __utils__["zfs.is_dataset"](name): | if not __utils__["zfs.is_dataset"](name): | |||
ret = { | ret = { | |||
"name": name, | "name": name, | |||
"changes": {}, | "changes": {}, | |||
"result": False, | "result": False, | |||
"comment": "invalid dataset name: {0}".format(name), | "comment": "invalid dataset name: {}".format(name), | |||
} | } | |||
else: | else: | |||
ret = _absent(name, "filesystem", force, recursive) | ret = _absent(name, "filesystem", force, recursive) | |||
return ret | return ret | |||
def volume_absent(name, force=False, recursive=False): | def volume_absent(name, force=False, recursive=False): | |||
""" | """ | |||
ensure volume is absent on the system | ensure volume is absent on the system | |||
name : string | name : string | |||
skipping to change at line 171 | skipping to change at line 167 | |||
If a filesystem with ``name`` exists, this state will succeed without | If a filesystem with ``name`` exists, this state will succeed without | |||
destroying the filesystem specified by ``name``. This module is dataset type sensitive. | destroying the filesystem specified by ``name``. This module is dataset type sensitive. | |||
""" | """ | |||
if not __utils__["zfs.is_dataset"](name): | if not __utils__["zfs.is_dataset"](name): | |||
ret = { | ret = { | |||
"name": name, | "name": name, | |||
"changes": {}, | "changes": {}, | |||
"result": False, | "result": False, | |||
"comment": "invalid dataset name: {0}".format(name), | "comment": "invalid dataset name: {}".format(name), | |||
} | } | |||
else: | else: | |||
ret = _absent(name, "volume", force, recursive) | ret = _absent(name, "volume", force, recursive) | |||
return ret | return ret | |||
def snapshot_absent(name, force=False, recursive=False): | def snapshot_absent(name, force=False, recursive=False): | |||
""" | """ | |||
ensure snapshot is absent on the system | ensure snapshot is absent on the system | |||
name : string | name : string | |||
skipping to change at line 194 | skipping to change at line 190 | |||
try harder to destroy the dataset (zfs destroy -f) | try harder to destroy the dataset (zfs destroy -f) | |||
recursive : boolean | recursive : boolean | |||
also destroy all the child datasets (zfs destroy -r) | also destroy all the child datasets (zfs destroy -r) | |||
""" | """ | |||
if not __utils__["zfs.is_snapshot"](name): | if not __utils__["zfs.is_snapshot"](name): | |||
ret = { | ret = { | |||
"name": name, | "name": name, | |||
"changes": {}, | "changes": {}, | |||
"result": False, | "result": False, | |||
"comment": "invalid snapshot name: {0}".format(name), | "comment": "invalid snapshot name: {}".format(name), | |||
} | } | |||
else: | else: | |||
ret = _absent(name, "snapshot", force, recursive) | ret = _absent(name, "snapshot", force, recursive) | |||
return ret | return ret | |||
def bookmark_absent(name, force=False, recursive=False): | def bookmark_absent(name, force=False, recursive=False): | |||
""" | """ | |||
ensure bookmark is absent on the system | ensure bookmark is absent on the system | |||
name : string | name : string | |||
skipping to change at line 217 | skipping to change at line 213 | |||
try harder to destroy the dataset (zfs destroy -f) | try harder to destroy the dataset (zfs destroy -f) | |||
recursive : boolean | recursive : boolean | |||
also destroy all the child datasets (zfs destroy -r) | also destroy all the child datasets (zfs destroy -r) | |||
""" | """ | |||
if not __utils__["zfs.is_bookmark"](name): | if not __utils__["zfs.is_bookmark"](name): | |||
ret = { | ret = { | |||
"name": name, | "name": name, | |||
"changes": {}, | "changes": {}, | |||
"result": False, | "result": False, | |||
"comment": "invalid bookmark name: {0}".format(name), | "comment": "invalid bookmark name: {}".format(name), | |||
} | } | |||
else: | else: | |||
ret = _absent(name, "bookmark", force, recursive) | ret = _absent(name, "bookmark", force, recursive) | |||
return ret | return ret | |||
def hold_absent(name, snapshot, recursive=False): | def hold_absent(name, snapshot, recursive=False): | |||
""" | """ | |||
ensure hold is absent on the system | ensure hold is absent on the system | |||
name : string | name : string | |||
skipping to change at line 244 | skipping to change at line 240 | |||
""" | """ | |||
ret = {"name": name, "changes": {}, "result": True, "comment": ""} | ret = {"name": name, "changes": {}, "result": True, "comment": ""} | |||
## log configuration | ## log configuration | |||
log.debug("zfs.hold_absent::%s::config::snapshot = %s", name, snapshot) | log.debug("zfs.hold_absent::%s::config::snapshot = %s", name, snapshot) | |||
log.debug("zfs.hold_absent::%s::config::recursive = %s", name, recursive) | log.debug("zfs.hold_absent::%s::config::recursive = %s", name, recursive) | |||
## check we have a snapshot/tag name | ## check we have a snapshot/tag name | |||
if not __utils__["zfs.is_snapshot"](snapshot): | if not __utils__["zfs.is_snapshot"](snapshot): | |||
ret["result"] = False | ret["result"] = False | |||
ret["comment"] = "invalid snapshot name: {0}".format(snapshot) | ret["comment"] = "invalid snapshot name: {}".format(snapshot) | |||
return ret | return ret | |||
if ( | if ( | |||
__utils__["zfs.is_snapshot"](name) | __utils__["zfs.is_snapshot"](name) | |||
or __utils__["zfs.is_bookmark"](name) | or __utils__["zfs.is_bookmark"](name) | |||
or name == "error" | or name == "error" | |||
): | ): | |||
ret["result"] = False | ret["result"] = False | |||
ret["comment"] = "invalid tag name: {0}".format(name) | ret["comment"] = "invalid tag name: {}".format(name) | |||
return ret | return ret | |||
## release hold if required | ## release hold if required | |||
holds = __salt__["zfs.holds"](snapshot) | holds = __salt__["zfs.holds"](snapshot) | |||
if name in holds: | if name in holds: | |||
## NOTE: hold found for snapshot, release it | ## NOTE: hold found for snapshot, release it | |||
if not __opts__["test"]: | if not __opts__["test"]: | |||
mod_res = __salt__["zfs.release"]( | mod_res = __salt__["zfs.release"]( | |||
name, snapshot, **{"recursive": recursive} | name, snapshot, **{"recursive": recursive} | |||
) | ) | |||
else: | else: | |||
mod_res = OrderedDict([("released", True)]) | mod_res = OrderedDict([("released", True)]) | |||
ret["result"] = mod_res["released"] | ret["result"] = mod_res["released"] | |||
if ret["result"]: | if ret["result"]: | |||
ret["changes"] = {snapshot: {name: "released"}} | ret["changes"] = {snapshot: {name: "released"}} | |||
ret["comment"] = "hold {0} released".format(name,) | ret["comment"] = "hold {} released".format(name,) | |||
else: | else: | |||
ret["comment"] = "failed to release hold {0}".format(name,) | ret["comment"] = "failed to release hold {}".format(name,) | |||
if "error" in mod_res: | if "error" in mod_res: | |||
ret["comment"] = mod_res["error"] | ret["comment"] = mod_res["error"] | |||
elif "error" in holds: | elif "error" in holds: | |||
## NOTE: we have an error | ## NOTE: we have an error | |||
ret["result"] = False | ret["result"] = False | |||
ret["comment"] = holds["error"] | ret["comment"] = holds["error"] | |||
else: | else: | |||
## NOTE: no hold found with name for snapshot | ## NOTE: no hold found with name for snapshot | |||
ret["comment"] = "hold {0} is absent".format(name,) | ret["comment"] = "hold {} is absent".format(name,) | |||
return ret | return ret | |||
def hold_present(name, snapshot, recursive=False): | def hold_present(name, snapshot, recursive=False): | |||
""" | """ | |||
ensure hold is present on the system | ensure hold is present on the system | |||
name : string | name : string | |||
name of holdt | name of holdt | |||
snapshot : string | snapshot : string | |||
skipping to change at line 306 | skipping to change at line 302 | |||
""" | """ | |||
ret = {"name": name, "changes": {}, "result": True, "comment": ""} | ret = {"name": name, "changes": {}, "result": True, "comment": ""} | |||
## log configuration | ## log configuration | |||
log.debug("zfs.hold_present::%s::config::snapshot = %s", name, snapshot) | log.debug("zfs.hold_present::%s::config::snapshot = %s", name, snapshot) | |||
log.debug("zfs.hold_present::%s::config::recursive = %s", name, recursive) | log.debug("zfs.hold_present::%s::config::recursive = %s", name, recursive) | |||
## check we have a snapshot/tag name | ## check we have a snapshot/tag name | |||
if not __utils__["zfs.is_snapshot"](snapshot): | if not __utils__["zfs.is_snapshot"](snapshot): | |||
ret["result"] = False | ret["result"] = False | |||
ret["comment"] = "invalid snapshot name: {0}".format(snapshot) | ret["comment"] = "invalid snapshot name: {}".format(snapshot) | |||
return ret | return ret | |||
if ( | if ( | |||
__utils__["zfs.is_snapshot"](name) | __utils__["zfs.is_snapshot"](name) | |||
or __utils__["zfs.is_bookmark"](name) | or __utils__["zfs.is_bookmark"](name) | |||
or name == "error" | or name == "error" | |||
): | ): | |||
ret["result"] = False | ret["result"] = False | |||
ret["comment"] = "invalid tag name: {0}".format(name) | ret["comment"] = "invalid tag name: {}".format(name) | |||
return ret | return ret | |||
## place hold if required | ## place hold if required | |||
holds = __salt__["zfs.holds"](snapshot) | holds = __salt__["zfs.holds"](snapshot) | |||
if name in holds: | if name in holds: | |||
## NOTE: hold with name already exists for snapshot | ## NOTE: hold with name already exists for snapshot | |||
ret["comment"] = "hold {0} is present for {1}".format(name, snapshot,) | ret["comment"] = "hold {} is present for {}".format(name, snapshot,) | |||
else: | else: | |||
## NOTE: no hold found with name for snapshot | ## NOTE: no hold found with name for snapshot | |||
if not __opts__["test"]: | if not __opts__["test"]: | |||
mod_res = __salt__["zfs.hold"](name, snapshot, **{"recursive": recur sive}) | mod_res = __salt__["zfs.hold"](name, snapshot, **{"recursive": recur sive}) | |||
else: | else: | |||
mod_res = OrderedDict([("held", True)]) | mod_res = OrderedDict([("held", True)]) | |||
ret["result"] = mod_res["held"] | ret["result"] = mod_res["held"] | |||
if ret["result"]: | if ret["result"]: | |||
ret["changes"] = OrderedDict([(snapshot, OrderedDict([(name, "held") ]))]) | ret["changes"] = OrderedDict([(snapshot, OrderedDict([(name, "held") ]))]) | |||
ret["comment"] = "hold {0} added to {1}".format(name, snapshot) | ret["comment"] = "hold {} added to {}".format(name, snapshot) | |||
else: | else: | |||
ret["comment"] = "failed to add hold {0} to {1}".format(name, snapsh ot) | ret["comment"] = "failed to add hold {} to {}".format(name, snapshot ) | |||
if "error" in mod_res: | if "error" in mod_res: | |||
ret["comment"] = mod_res["error"] | ret["comment"] = mod_res["error"] | |||
return ret | return ret | |||
def _dataset_present( | def _dataset_present( | |||
dataset_type, | dataset_type, | |||
name, | name, | |||
volume_size=None, | volume_size=None, | |||
sparse=False, | sparse=False, | |||
skipping to change at line 419 | skipping to change at line 415 | |||
log.debug( | log.debug( | |||
"zfs.%s_present::%s::config::cloned_from = %s", dataset_type, name, clon ed_from | "zfs.%s_present::%s::config::cloned_from = %s", dataset_type, name, clon ed_from | |||
) | ) | |||
log.debug( | log.debug( | |||
"zfs.%s_present::%s::config::properties = %s", dataset_type, name, prope rties | "zfs.%s_present::%s::config::properties = %s", dataset_type, name, prope rties | |||
) | ) | |||
## check we have valid filesystem name/volume name/clone snapshot | ## check we have valid filesystem name/volume name/clone snapshot | |||
if not __utils__["zfs.is_dataset"](name): | if not __utils__["zfs.is_dataset"](name): | |||
ret["result"] = False | ret["result"] = False | |||
ret["comment"] = "invalid dataset name: {0}".format(name) | ret["comment"] = "invalid dataset name: {}".format(name) | |||
return ret | return ret | |||
if cloned_from and not __utils__["zfs.is_snapshot"](cloned_from): | if cloned_from and not __utils__["zfs.is_snapshot"](cloned_from): | |||
ret["result"] = False | ret["result"] = False | |||
ret["comment"] = "{0} is not a snapshot".format(cloned_from) | ret["comment"] = "{} is not a snapshot".format(cloned_from) | |||
return ret | return ret | |||
## ensure dataset is in correct state | ## ensure dataset is in correct state | |||
## NOTE: update the dataset | ## NOTE: update the dataset | |||
if __salt__["zfs.exists"](name, **{"type": dataset_type}): | if __salt__["zfs.exists"](name, **{"type": dataset_type}): | |||
## NOTE: fetch current volume properties | ## NOTE: fetch current volume properties | |||
properties_current = __salt__["zfs.get"]( | properties_current = __salt__["zfs.get"]( | |||
name, type=dataset_type, fields="value", depth=0, parsable=True, | name, type=dataset_type, fields="value", depth=0, parsable=True, | |||
).get(name, OrderedDict()) | ).get(name, OrderedDict()) | |||
skipping to change at line 471 | skipping to change at line 467 | |||
mod_res = OrderedDict([("set", True)]) | mod_res = OrderedDict([("set", True)]) | |||
if mod_res["set"]: | if mod_res["set"]: | |||
if name not in ret["changes"]: | if name not in ret["changes"]: | |||
ret["changes"][name] = {} | ret["changes"][name] = {} | |||
ret["changes"][name][prop] = properties[prop] | ret["changes"][name][prop] = properties[prop] | |||
else: | else: | |||
ret["result"] = False | ret["result"] = False | |||
if ret["comment"] == "": | if ret["comment"] == "": | |||
ret["comment"] = "The following properties were not updated: " | ret["comment"] = "The following properties were not updated: " | |||
ret["comment"] = "{0} {1}".format(ret["comment"], prop) | ret["comment"] = "{} {}".format(ret["comment"], prop) | |||
## NOTE: update comment | ## NOTE: update comment | |||
if ret["result"] and name in ret["changes"]: | if ret["result"] and name in ret["changes"]: | |||
ret["comment"] = "{0} {1} was updated".format(dataset_type, name) | ret["comment"] = "{} {} was updated".format(dataset_type, name) | |||
elif ret["result"]: | elif ret["result"]: | |||
ret["comment"] = "{0} {1} is uptodate".format(dataset_type, name) | ret["comment"] = "{} {} is uptodate".format(dataset_type, name) | |||
else: | else: | |||
ret["comment"] = "{0} {1} failed to be updated".format(dataset_type, name) | ret["comment"] = "{} {} failed to be updated".format(dataset_type, n ame) | |||
## NOTE: create or clone the dataset | ## NOTE: create or clone the dataset | |||
else: | else: | |||
mod_res_action = "cloned" if cloned_from else "created" | mod_res_action = "cloned" if cloned_from else "created" | |||
if __opts__["test"]: | if __opts__["test"]: | |||
## NOTE: pretend to create/clone | ## NOTE: pretend to create/clone | |||
mod_res = OrderedDict([(mod_res_action, True)]) | mod_res = OrderedDict([(mod_res_action, True)]) | |||
elif cloned_from: | elif cloned_from: | |||
## NOTE: add volsize to properties | ## NOTE: add volsize to properties | |||
if volume_size: | if volume_size: | |||
skipping to change at line 515 | skipping to change at line 511 | |||
"volume_size": volume_size, | "volume_size": volume_size, | |||
"sparse": sparse, | "sparse": sparse, | |||
} | } | |||
) | ) | |||
ret["result"] = mod_res[mod_res_action] | ret["result"] = mod_res[mod_res_action] | |||
if ret["result"]: | if ret["result"]: | |||
ret["changes"][name] = mod_res_action | ret["changes"][name] = mod_res_action | |||
if properties: | if properties: | |||
ret["changes"][name] = properties | ret["changes"][name] = properties | |||
ret["comment"] = "{0} {1} was {2}".format( | ret["comment"] = "{} {} was {}".format(dataset_type, name, mod_res_a | |||
dataset_type, name, mod_res_action, | ction,) | |||
) | ||||
else: | else: | |||
ret["comment"] = "failed to {0} {1} {2}".format( | ret["comment"] = "failed to {} {} {}".format( | |||
mod_res_action[:-1], dataset_type, name, | mod_res_action[:-1], dataset_type, name, | |||
) | ) | |||
if "error" in mod_res: | if "error" in mod_res: | |||
ret["comment"] = mod_res["error"] | ret["comment"] = mod_res["error"] | |||
return ret | return ret | |||
def filesystem_present(name, create_parent=False, properties=None, cloned_from=N one): | def filesystem_present(name, create_parent=False, properties=None, cloned_from=N one): | |||
""" | """ | |||
ensure filesystem exists and has properties set | ensure filesystem exists and has properties set | |||
skipping to change at line 626 | skipping to change at line 620 | |||
""" | """ | |||
ret = {"name": name, "changes": {}, "result": True, "comment": ""} | ret = {"name": name, "changes": {}, "result": True, "comment": ""} | |||
## log configuration | ## log configuration | |||
log.debug("zfs.bookmark_present::%s::config::snapshot = %s", name, snapshot) | log.debug("zfs.bookmark_present::%s::config::snapshot = %s", name, snapshot) | |||
## check we have valid snapshot/bookmark name | ## check we have valid snapshot/bookmark name | |||
if not __utils__["zfs.is_snapshot"](snapshot): | if not __utils__["zfs.is_snapshot"](snapshot): | |||
ret["result"] = False | ret["result"] = False | |||
ret["comment"] = "invalid snapshot name: {0}".format(name) | ret["comment"] = "invalid snapshot name: {}".format(name) | |||
return ret | return ret | |||
if "#" not in name and "/" not in name: | if "#" not in name and "/" not in name: | |||
## NOTE: simple snapshot name | ## NOTE: simple snapshot name | |||
# take the snapshot name and replace the snapshot but with the si mple name | # take the snapshot name and replace the snapshot but with the si mple name | |||
# e.g. pool/fs@snap + bm --> pool/fs#bm | # e.g. pool/fs@snap + bm --> pool/fs#bm | |||
name = "{0}#{1}".format(snapshot[: snapshot.index("@")], name) | name = "{}#{}".format(snapshot[: snapshot.index("@")], name) | |||
ret["name"] = name | ret["name"] = name | |||
if not __utils__["zfs.is_bookmark"](name): | if not __utils__["zfs.is_bookmark"](name): | |||
ret["result"] = False | ret["result"] = False | |||
ret["comment"] = "invalid bookmark name: {0}".format(name) | ret["comment"] = "invalid bookmark name: {}".format(name) | |||
return ret | return ret | |||
## ensure bookmark exists | ## ensure bookmark exists | |||
if not __salt__["zfs.exists"](name, **{"type": "bookmark"}): | if not __salt__["zfs.exists"](name, **{"type": "bookmark"}): | |||
## NOTE: bookmark the snapshot | ## NOTE: bookmark the snapshot | |||
if not __opts__["test"]: | if not __opts__["test"]: | |||
mod_res = __salt__["zfs.bookmark"](snapshot, name) | mod_res = __salt__["zfs.bookmark"](snapshot, name) | |||
else: | else: | |||
mod_res = OrderedDict([("bookmarked", True)]) | mod_res = OrderedDict([("bookmarked", True)]) | |||
ret["result"] = mod_res["bookmarked"] | ret["result"] = mod_res["bookmarked"] | |||
if ret["result"]: | if ret["result"]: | |||
ret["changes"][name] = snapshot | ret["changes"][name] = snapshot | |||
ret["comment"] = "{0} bookmarked as {1}".format(snapshot, name) | ret["comment"] = "{} bookmarked as {}".format(snapshot, name) | |||
else: | else: | |||
ret["comment"] = "failed to bookmark {0}".format(snapshot) | ret["comment"] = "failed to bookmark {}".format(snapshot) | |||
if "error" in mod_res: | if "error" in mod_res: | |||
ret["comment"] = mod_res["error"] | ret["comment"] = mod_res["error"] | |||
else: | else: | |||
## NOTE: bookmark already exists | ## NOTE: bookmark already exists | |||
ret["comment"] = "bookmark is present" | ret["comment"] = "bookmark is present" | |||
return ret | return ret | |||
def snapshot_present(name, recursive=False, properties=None): | def snapshot_present(name, recursive=False, properties=None): | |||
""" | """ | |||
skipping to change at line 691 | skipping to change at line 685 | |||
log.debug("zfs.snapshot_present::%s::config::recursive = %s", name, recursiv e) | log.debug("zfs.snapshot_present::%s::config::recursive = %s", name, recursiv e) | |||
log.debug("zfs.snapshot_present::%s::config::properties = %s", name, propert ies) | log.debug("zfs.snapshot_present::%s::config::properties = %s", name, propert ies) | |||
## ensure properties are zfs values | ## ensure properties are zfs values | |||
if properties: | if properties: | |||
properties = __utils__["zfs.from_auto_dict"](properties) | properties = __utils__["zfs.from_auto_dict"](properties) | |||
## check we have valid snapshot name | ## check we have valid snapshot name | |||
if not __utils__["zfs.is_snapshot"](name): | if not __utils__["zfs.is_snapshot"](name): | |||
ret["result"] = False | ret["result"] = False | |||
ret["comment"] = "invalid snapshot name: {0}".format(name) | ret["comment"] = "invalid snapshot name: {}".format(name) | |||
return ret | return ret | |||
## ensure snapshot exits | ## ensure snapshot exits | |||
if not __salt__["zfs.exists"](name, **{"type": "snapshot"}): | if not __salt__["zfs.exists"](name, **{"type": "snapshot"}): | |||
## NOTE: create the snapshot | ## NOTE: create the snapshot | |||
if not __opts__["test"]: | if not __opts__["test"]: | |||
mod_res = __salt__["zfs.snapshot"]( | mod_res = __salt__["zfs.snapshot"]( | |||
name, **{"recursive": recursive, "properties": properties} | name, **{"recursive": recursive, "properties": properties} | |||
) | ) | |||
else: | else: | |||
mod_res = OrderedDict([("snapshotted", True)]) | mod_res = OrderedDict([("snapshotted", True)]) | |||
ret["result"] = mod_res["snapshotted"] | ret["result"] = mod_res["snapshotted"] | |||
if ret["result"]: | if ret["result"]: | |||
ret["changes"][name] = "snapshotted" | ret["changes"][name] = "snapshotted" | |||
if properties: | if properties: | |||
ret["changes"][name] = properties | ret["changes"][name] = properties | |||
ret["comment"] = "snapshot {0} was created".format(name) | ret["comment"] = "snapshot {} was created".format(name) | |||
else: | else: | |||
ret["comment"] = "failed to create snapshot {0}".format(name) | ret["comment"] = "failed to create snapshot {}".format(name) | |||
if "error" in mod_res: | if "error" in mod_res: | |||
ret["comment"] = mod_res["error"] | ret["comment"] = mod_res["error"] | |||
else: | else: | |||
## NOTE: snapshot already exists | ## NOTE: snapshot already exists | |||
ret["comment"] = "snapshot is present" | ret["comment"] = "snapshot is present" | |||
return ret | return ret | |||
def promoted(name): | def promoted(name): | |||
""" | """ | |||
skipping to change at line 738 | skipping to change at line 732 | |||
only one dataset can be the origin, | only one dataset can be the origin, | |||
if you promote a clone the original will now point to the promoted datas et | if you promote a clone the original will now point to the promoted datas et | |||
""" | """ | |||
ret = {"name": name, "changes": {}, "result": True, "comment": ""} | ret = {"name": name, "changes": {}, "result": True, "comment": ""} | |||
## check we if we have a valid dataset name | ## check we if we have a valid dataset name | |||
if not __utils__["zfs.is_dataset"](name): | if not __utils__["zfs.is_dataset"](name): | |||
ret["result"] = False | ret["result"] = False | |||
ret["comment"] = "invalid dataset name: {0}".format(name) | ret["comment"] = "invalid dataset name: {}".format(name) | |||
return ret | return ret | |||
## ensure dataset is the primary instance | ## ensure dataset is the primary instance | |||
if not __salt__["zfs.exists"](name, **{"type": "filesystem,volume"}): | if not __salt__["zfs.exists"](name, **{"type": "filesystem,volume"}): | |||
## NOTE: we don't have a dataset | ## NOTE: we don't have a dataset | |||
ret["result"] = False | ret["result"] = False | |||
ret["comment"] = "dataset {0} does not exist".format(name) | ret["comment"] = "dataset {} does not exist".format(name) | |||
else: | else: | |||
## NOTE: check if we have a blank origin (-) | ## NOTE: check if we have a blank origin (-) | |||
if ( | if ( | |||
__salt__["zfs.get"]( | __salt__["zfs.get"]( | |||
name, **{"properties": "origin", "fields": "value", "parsable": True} | name, **{"properties": "origin", "fields": "value", "parsable": True} | |||
)[name]["origin"]["value"] | )[name]["origin"]["value"] | |||
== "-" | == "-" | |||
): | ): | |||
## NOTE: we're already promoted | ## NOTE: we're already promoted | |||
ret["comment"] = "{0} already promoted".format(name) | ret["comment"] = "{} already promoted".format(name) | |||
else: | else: | |||
## NOTE: promote dataset | ## NOTE: promote dataset | |||
if not __opts__["test"]: | if not __opts__["test"]: | |||
mod_res = __salt__["zfs.promote"](name) | mod_res = __salt__["zfs.promote"](name) | |||
else: | else: | |||
mod_res = OrderedDict([("promoted", True)]) | mod_res = OrderedDict([("promoted", True)]) | |||
ret["result"] = mod_res["promoted"] | ret["result"] = mod_res["promoted"] | |||
if ret["result"]: | if ret["result"]: | |||
ret["changes"][name] = "promoted" | ret["changes"][name] = "promoted" | |||
ret["comment"] = "{0} promoted".format(name) | ret["comment"] = "{} promoted".format(name) | |||
else: | else: | |||
ret["comment"] = "failed to promote {0}".format(name) | ret["comment"] = "failed to promote {}".format(name) | |||
if "error" in mod_res: | if "error" in mod_res: | |||
ret["comment"] = mod_res["error"] | ret["comment"] = mod_res["error"] | |||
return ret | return ret | |||
def _schedule_snapshot_retrieve(dataset, prefix, snapshots): | def _schedule_snapshot_retrieve(dataset, prefix, snapshots): | |||
""" | """ | |||
Update snapshots dict with current snapshots | Update snapshots dict with current snapshots | |||
dataset: string | dataset: string | |||
skipping to change at line 798 | skipping to change at line 792 | |||
for snap in sorted( | for snap in sorted( | |||
__salt__["zfs.list"]( | __salt__["zfs.list"]( | |||
dataset, **{"recursive": True, "depth": 1, "type": "snapshot"} | dataset, **{"recursive": True, "depth": 1, "type": "snapshot"} | |||
).keys() | ).keys() | |||
): | ): | |||
## NOTE: we only want the actualy name | ## NOTE: we only want the actualy name | |||
## myzpool/data@zbck-20171201_000248 -> zbck-20171201_000248 | ## myzpool/data@zbck-20171201_000248 -> zbck-20171201_000248 | |||
snap_name = snap[snap.index("@") + 1 :] | snap_name = snap[snap.index("@") + 1 :] | |||
## NOTE: we only want snapshots matching our prefix | ## NOTE: we only want snapshots matching our prefix | |||
if not snap_name.startswith("{0}-".format(prefix)): | if not snap_name.startswith("{}-".format(prefix)): | |||
continue | continue | |||
## NOTE: retrieve the holds for this snapshot | ## NOTE: retrieve the holds for this snapshot | |||
snap_holds = __salt__["zfs.holds"](snap) | snap_holds = __salt__["zfs.holds"](snap) | |||
## NOTE: this snapshot has no holds, eligable for pruning | ## NOTE: this snapshot has no holds, eligable for pruning | |||
if not snap_holds: | if not snap_holds: | |||
snapshots["_prunable"].append(snap) | snapshots["_prunable"].append(snap) | |||
## NOTE: update snapshots based on holds (if any) | ## NOTE: update snapshots based on holds (if any) | |||
skipping to change at line 849 | skipping to change at line 843 | |||
snapshots["_create"][snapshot_create_name] = [] | snapshots["_create"][snapshot_create_name] = [] | |||
for hold, hold_count in snapshots["_schedule"].items(): | for hold, hold_count in snapshots["_schedule"].items(): | |||
## NOTE: skip hold if we don't keep snapshots for it | ## NOTE: skip hold if we don't keep snapshots for it | |||
if hold_count == 0: | if hold_count == 0: | |||
continue | continue | |||
## NOTE: figure out if we need the current hold on the new snapshot | ## NOTE: figure out if we need the current hold on the new snapshot | |||
if snapshots[hold]: | if snapshots[hold]: | |||
## NOTE: extract datetime from snapshot name | ## NOTE: extract datetime from snapshot name | |||
timestamp = datetime.strptime( | timestamp = datetime.strptime( | |||
snapshots[hold][-1], "{0}@{1}-%Y%m%d_%H%M%S".format(dataset, pre fix), | snapshots[hold][-1], "{}@{}-%Y%m%d_%H%M%S".format(dataset, prefi x), | |||
).replace(second=0, microsecond=0) | ).replace(second=0, microsecond=0) | |||
## NOTE: compare current timestamp to timestamp from snapshot | ## NOTE: compare current timestamp to timestamp from snapshot | |||
if hold == "minute" and timestamp_now <= timestamp: | if hold == "minute" and timestamp_now <= timestamp: | |||
continue | continue | |||
elif hold == "hour" and timestamp_now.replace( | elif hold == "hour" and timestamp_now.replace( | |||
**comp_hour | **comp_hour | |||
) <= timestamp.replace(**comp_hour): | ) <= timestamp.replace(**comp_hour): | |||
continue | continue | |||
elif hold == "day" and timestamp_now.replace( | elif hold == "day" and timestamp_now.replace( | |||
skipping to change at line 916 | skipping to change at line 910 | |||
## initialize defaults | ## initialize defaults | |||
schedule_holds = ["minute", "hour", "day", "month", "year"] | schedule_holds = ["minute", "hour", "day", "month", "year"] | |||
snapshots = OrderedDict( | snapshots = OrderedDict( | |||
[("_create", OrderedDict()), ("_prunable", []), ("_schedule", OrderedDic t())] | [("_create", OrderedDict()), ("_prunable", []), ("_schedule", OrderedDic t())] | |||
) | ) | |||
## strict configuration validation | ## strict configuration validation | |||
## NOTE: we need a valid dataset | ## NOTE: we need a valid dataset | |||
if not __utils__["zfs.is_dataset"](name): | if not __utils__["zfs.is_dataset"](name): | |||
ret["result"] = False | ret["result"] = False | |||
ret["comment"] = "invalid dataset name: {0}".format(name) | ret["comment"] = "invalid dataset name: {}".format(name) | |||
if not __salt__["zfs.exists"](name, **{"type": "filesystem,volume"}): | if not __salt__["zfs.exists"](name, **{"type": "filesystem,volume"}): | |||
ret["comment"] = "dataset {0} does not exist".format(name) | ret["comment"] = "dataset {} does not exist".format(name) | |||
ret["result"] = False | ret["result"] = False | |||
## NOTE: prefix must be 4 or longer | ## NOTE: prefix must be 4 or longer | |||
if not prefix or len(prefix) < 4: | if not prefix or len(prefix) < 4: | |||
ret["comment"] = "prefix ({0}) must be at least 4 long".format(prefix) | ret["comment"] = "prefix ({}) must be at least 4 long".format(prefix) | |||
ret["result"] = False | ret["result"] = False | |||
## NOTE: validate schedule | ## NOTE: validate schedule | |||
total_count = 0 | total_count = 0 | |||
for hold in schedule_holds: | for hold in schedule_holds: | |||
snapshots[hold] = [] | snapshots[hold] = [] | |||
if hold not in schedule: | if hold not in schedule: | |||
snapshots["_schedule"][hold] = 0 | snapshots["_schedule"][hold] = 0 | |||
elif isinstance(schedule[hold], int): | elif isinstance(schedule[hold], int): | |||
snapshots["_schedule"][hold] = schedule[hold] | snapshots["_schedule"][hold] = schedule[hold] | |||
else: | else: | |||
ret["result"] = False | ret["result"] = False | |||
ret["comment"] = "schedule value for {0} is not an integer".format(h old,) | ret["comment"] = "schedule value for {} is not an integer".format(ho ld,) | |||
break | break | |||
total_count += snapshots["_schedule"][hold] | total_count += snapshots["_schedule"][hold] | |||
if ret["result"] and total_count == 0: | if ret["result"] and total_count == 0: | |||
ret["result"] = False | ret["result"] = False | |||
ret["comment"] = "schedule is not valid, you need to keep atleast 1 snap shot" | ret["comment"] = "schedule is not valid, you need to keep atleast 1 snap shot" | |||
## NOTE: return if configuration is not valid | ## NOTE: return if configuration is not valid | |||
if not ret["result"]: | if not ret["result"]: | |||
return ret | return ret | |||
skipping to change at line 975 | skipping to change at line 969 | |||
## NOTE: create snapshot | ## NOTE: create snapshot | |||
if not __opts__["test"]: | if not __opts__["test"]: | |||
mod_res = __salt__["zfs.snapshot"]( | mod_res = __salt__["zfs.snapshot"]( | |||
snapshot_name, **{"recursive": recursive} | snapshot_name, **{"recursive": recursive} | |||
) | ) | |||
else: | else: | |||
mod_res = OrderedDict([("snapshotted", True)]) | mod_res = OrderedDict([("snapshotted", True)]) | |||
if not mod_res["snapshotted"]: | if not mod_res["snapshotted"]: | |||
ret["result"] = False | ret["result"] = False | |||
ret["comment"] = "error creating snapshot ({0})".format(snapshot_nam e) | ret["comment"] = "error creating snapshot ({})".format(snapshot_name ) | |||
else: | else: | |||
## NOTE: create holds (if we have a snapshot) | ## NOTE: create holds (if we have a snapshot) | |||
for hold in snapshot_holds: | for hold in snapshot_holds: | |||
if not __opts__["test"]: | if not __opts__["test"]: | |||
mod_res = __salt__["zfs.hold"]( | mod_res = __salt__["zfs.hold"]( | |||
hold, snapshot_name, **{"recursive": recursive} | hold, snapshot_name, **{"recursive": recursive} | |||
) | ) | |||
else: | else: | |||
mod_res = OrderedDict([("held", True)]) | mod_res = OrderedDict([("held", True)]) | |||
if not mod_res["held"]: | if not mod_res["held"]: | |||
ret["result"] = False | ret["result"] = False | |||
ret["comment"] = "error adding hold ({0}) to snapshot ({1})" .format( | ret["comment"] = "error adding hold ({}) to snapshot ({})".f ormat( | |||
hold, snapshot_name, | hold, snapshot_name, | |||
) | ) | |||
break | break | |||
snapshots[hold].append(snapshot_name) | snapshots[hold].append(snapshot_name) | |||
if ret["result"]: | if ret["result"]: | |||
ret["comment"] = "scheduled snapshots updated" | ret["comment"] = "scheduled snapshots updated" | |||
if "created" not in ret["changes"]: | if "created" not in ret["changes"]: | |||
ret["changes"]["created"] = [] | ret["changes"]["created"] = [] | |||
skipping to change at line 1017 | skipping to change at line 1011 | |||
## NOTE: release hold for snapshot | ## NOTE: release hold for snapshot | |||
if not __opts__["test"]: | if not __opts__["test"]: | |||
mod_res = __salt__["zfs.release"]( | mod_res = __salt__["zfs.release"]( | |||
hold, snapshot_name, **{"recursive": recursive} | hold, snapshot_name, **{"recursive": recursive} | |||
) | ) | |||
else: | else: | |||
mod_res = OrderedDict([("released", True)]) | mod_res = OrderedDict([("released", True)]) | |||
if not mod_res["released"]: | if not mod_res["released"]: | |||
ret["result"] = False | ret["result"] = False | |||
ret["comment"] = "error adding hold ({0}) to snapshot ({1})".for mat( | ret["comment"] = "error adding hold ({}) to snapshot ({})".forma t( | |||
hold, snapshot_name, | hold, snapshot_name, | |||
) | ) | |||
## NOTE: mark as prunable | ## NOTE: mark as prunable | |||
if not __salt__["zfs.holds"](snapshot_name): | if not __salt__["zfs.holds"](snapshot_name): | |||
snapshots["_prunable"].append(snapshot_name) | snapshots["_prunable"].append(snapshot_name) | |||
## prune snapshot(s) | ## prune snapshot(s) | |||
for snapshot_name in snapshots["_prunable"]: | for snapshot_name in snapshots["_prunable"]: | |||
## NOTE: destroy snapshot | ## NOTE: destroy snapshot | |||
End of changes. 51 change blocks. | ||||
53 lines changed or deleted | 48 lines changed or added |