133 lines
3.4 KiB
Plaintext
Executable File
133 lines
3.4 KiB
Plaintext
Executable File
#!/usr/local/sbin/charm-env python3
|
|
|
|
import os
|
|
import re
|
|
import shlex
|
|
import subprocess
|
|
import sys
|
|
|
|
from charms import layer
|
|
|
|
from etcdctl import EtcdCtl
|
|
|
|
from charmhelpers.core.hookenv import action_get, action_set, action_fail, action_name
|
|
|
|
|
|
CTL = EtcdCtl()
|
|
|
|
|
|
def action_fail_now(*args, **kw):
|
|
"""Call action_fail() and exit immediately."""
|
|
action_fail(*args, **kw)
|
|
sys.exit(0)
|
|
|
|
|
|
def requires_etcd_version(version_regex, human_version=None):
|
|
"""Decorator that enforces a specific version of etcdctl be present.
|
|
|
|
The decorated function will only be executed if the required version
|
|
of etcdctl is present. Otherwise, action_fail() will be called and
|
|
the process will exit immediately.
|
|
|
|
"""
|
|
|
|
def wrap(f):
|
|
def wrapped_f(*args):
|
|
version = CTL.version()
|
|
if not re.match(version_regex, version):
|
|
required_version = human_version or version_regex
|
|
action_fail_now(
|
|
"This action requires etcd version {}".format(required_version)
|
|
)
|
|
f(*args)
|
|
|
|
return wrapped_f
|
|
|
|
return wrap
|
|
|
|
|
|
requires_etcd_v2 = requires_etcd_version(r"2\..*", human_version="2.x")
|
|
requires_etcd_v3 = requires_etcd_version(r"3\..*", human_version="3.x")
|
|
|
|
|
|
@requires_etcd_v3
|
|
def alarm_disarm():
|
|
"""Call `etcdctl alarm disarm`."""
|
|
try:
|
|
output = CTL.run("alarm disarm")
|
|
action_set(dict(output=output))
|
|
except subprocess.CalledProcessError as e:
|
|
action_fail_now(e.output)
|
|
|
|
|
|
@requires_etcd_v3
|
|
def alarm_list():
|
|
"""Call `etcdctl alarm list`."""
|
|
try:
|
|
output = CTL.run("alarm list")
|
|
action_set(dict(output=output))
|
|
except subprocess.CalledProcessError as e:
|
|
action_fail_now(e.output)
|
|
|
|
|
|
@requires_etcd_v3
|
|
def compact():
|
|
"""Call `etcdctl compact`."""
|
|
|
|
def get_latest_revision():
|
|
try:
|
|
output = CTL.run("endpoint status --write-out json")
|
|
except subprocess.CalledProcessError as e:
|
|
action_fail_now(
|
|
"Failed to determine latest revision for " "compaction: {}".format(e)
|
|
)
|
|
|
|
m = re.search(r'"revision":(\d*)', output)
|
|
if not m:
|
|
action_fail_now(
|
|
"Failed to get revision from 'endpoint status' "
|
|
"output: {}".format(output)
|
|
)
|
|
return m.group(1)
|
|
|
|
revision = action_get("revision") or get_latest_revision()
|
|
physical = "true" if action_get("physical") else "false"
|
|
command = "compact {} --physical={}".format(revision, physical)
|
|
try:
|
|
output = CTL.run(command)
|
|
action_set(dict(output=output))
|
|
except subprocess.CalledProcessError as e:
|
|
action_fail_now(e.output)
|
|
|
|
|
|
@requires_etcd_v3
|
|
def defrag():
|
|
"""Call `etcdctl defrag`."""
|
|
try:
|
|
output = CTL.run("defrag")
|
|
action_set(dict(output=output))
|
|
except subprocess.CalledProcessError as e:
|
|
action_fail_now(e.output)
|
|
|
|
|
|
def health():
|
|
"""Call etcdctl cluster-health"""
|
|
try:
|
|
output = CTL.cluster_health(True)
|
|
action_set(dict(output=output))
|
|
except subprocess.CalledProcessError as e:
|
|
action_fail_now(e.output)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
ACTIONS = {
|
|
"alarm-disarm": alarm_disarm,
|
|
"alarm-list": alarm_list,
|
|
"compact": compact,
|
|
"defrag": defrag,
|
|
"health": health,
|
|
}
|
|
|
|
action = action_name()
|
|
ACTIONS[action]()
|