myfaasmctl 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- faasmctl/__init__.py +4 -0
- faasmctl/bin/gen_proto_files.py +110 -0
- faasmctl/main.py +14 -0
- faasmctl/tasks/__init__.py +29 -0
- faasmctl/tasks/cli.py +150 -0
- faasmctl/tasks/delete.py +28 -0
- faasmctl/tasks/deploy.py +103 -0
- faasmctl/tasks/flush.py +31 -0
- faasmctl/tasks/generate.py +14 -0
- faasmctl/tasks/invoke.py +102 -0
- faasmctl/tasks/logs.py +73 -0
- faasmctl/tasks/monitor.py +317 -0
- faasmctl/tasks/restart.py +46 -0
- faasmctl/tasks/scale.py +47 -0
- faasmctl/tasks/status.py +29 -0
- faasmctl/tasks/upload.py +36 -0
- faasmctl/util/backend.py +2 -0
- faasmctl/util/batch.py +14 -0
- faasmctl/util/compose.py +399 -0
- faasmctl/util/config.py +80 -0
- faasmctl/util/deploy.py +152 -0
- faasmctl/util/docker.py +52 -0
- faasmctl/util/env.py +10 -0
- faasmctl/util/faasm.py +25 -0
- faasmctl/util/flush.py +38 -0
- faasmctl/util/gen_proto/faabric_pb2.py +75 -0
- faasmctl/util/gen_proto/planner_pb2.py +65 -0
- faasmctl/util/invoke.py +156 -0
- faasmctl/util/k8s.py +371 -0
- faasmctl/util/message.py +14 -0
- faasmctl/util/network.py +38 -0
- faasmctl/util/planner.py +201 -0
- faasmctl/util/random.py +8 -0
- faasmctl/util/restart.py +21 -0
- faasmctl/util/results.py +40 -0
- faasmctl/util/time.py +6 -0
- faasmctl/util/upload.py +61 -0
- faasmctl/util/version.py +5 -0
- myfaasmctl-0.0.1.dist-info/LICENSE.md +202 -0
- myfaasmctl-0.0.1.dist-info/METADATA +42 -0
- myfaasmctl-0.0.1.dist-info/RECORD +44 -0
- myfaasmctl-0.0.1.dist-info/WHEEL +5 -0
- myfaasmctl-0.0.1.dist-info/entry_points.txt +2 -0
- myfaasmctl-0.0.1.dist-info/top_level.txt +1 -0
faasmctl/__init__.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from os import environ, listdir, makedirs
|
|
4
|
+
from os.path import dirname, exists, join, realpath
|
|
5
|
+
from shutil import rmtree
|
|
6
|
+
from sys import argv
|
|
7
|
+
from subprocess import run
|
|
8
|
+
|
|
9
|
+
FAASMCTL_ROOT = dirname(dirname(realpath(__file__)))
|
|
10
|
+
|
|
11
|
+
# Unfortunately, we need to duplicate this constants here as we need to be
|
|
12
|
+
# able to run this file in standalone mode, as if the proto files are not
|
|
13
|
+
# generated, some imports in `faasmctl` will fail
|
|
14
|
+
GEN_PROTO_DIR = join(FAASMCTL_ROOT, "util", "gen_proto")
|
|
15
|
+
FAASM_CLI_IMAGE = "faasm.azurecr.io/cli"
|
|
16
|
+
|
|
17
|
+
PROTO_FILES = [
|
|
18
|
+
"faabric_pb2.py",
|
|
19
|
+
"planner_pb2.py",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# We can not import faasmctl here
|
|
24
|
+
def get_faasm_version():
|
|
25
|
+
ver_file = join(FAASMCTL_ROOT, "util", "faasm.py")
|
|
26
|
+
cmd = "cat {} | grep 'FAASM_VERSION =' | cut -d' ' -f3".format(ver_file)
|
|
27
|
+
version = (
|
|
28
|
+
run(cmd, shell=True, check=True, capture_output=True)
|
|
29
|
+
.stdout.decode("utf-8")
|
|
30
|
+
.strip()[1:-1]
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return version
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def gen_proto_files(clean=False):
|
|
37
|
+
"""
|
|
38
|
+
Generate python proto files to interact with the Faasm cluster
|
|
39
|
+
"""
|
|
40
|
+
if clean:
|
|
41
|
+
rmtree(GEN_PROTO_DIR)
|
|
42
|
+
|
|
43
|
+
if not exists(GEN_PROTO_DIR):
|
|
44
|
+
makedirs(GEN_PROTO_DIR)
|
|
45
|
+
|
|
46
|
+
pb_files = [f for f in listdir(GEN_PROTO_DIR) if f.endswith("pb2.py")]
|
|
47
|
+
pb_files.sort()
|
|
48
|
+
|
|
49
|
+
if pb_files == PROTO_FILES:
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
if "FAASM_VERSION" in environ:
|
|
53
|
+
faasm_ver = environ["FAASM_VERSION"]
|
|
54
|
+
else:
|
|
55
|
+
faasm_ver = get_faasm_version()
|
|
56
|
+
|
|
57
|
+
print("Generating Faasm (v{}) protobuf files...".format(faasm_ver))
|
|
58
|
+
tmp_ctr_name = "faasm_gen_proto"
|
|
59
|
+
cm = "docker run -d -i --name {} {}:{}".format(
|
|
60
|
+
tmp_ctr_name, FAASM_CLI_IMAGE, faasm_ver
|
|
61
|
+
)
|
|
62
|
+
run(cm, shell=True, check=True)
|
|
63
|
+
|
|
64
|
+
# Find the right protoc binary
|
|
65
|
+
docker_exec_prefix = "docker exec {}".format(tmp_ctr_name)
|
|
66
|
+
find_protoc_cmd = "{} bash -c 'find ~/.conan -name protoc'".format(
|
|
67
|
+
docker_exec_prefix
|
|
68
|
+
)
|
|
69
|
+
protoc_bin = (
|
|
70
|
+
run(find_protoc_cmd, shell=True, capture_output=True)
|
|
71
|
+
.stdout.decode("utf-8")
|
|
72
|
+
.split("\n")
|
|
73
|
+
)
|
|
74
|
+
p_bin = [p_bin for p_bin in protoc_bin if "build_subfolder" in p_bin]
|
|
75
|
+
p_bin = p_bin[0].strip()
|
|
76
|
+
|
|
77
|
+
# Generate python protobuf files
|
|
78
|
+
code_dir = "/usr/local/code/faasm/faabric"
|
|
79
|
+
protoc_cmd = [
|
|
80
|
+
p_bin,
|
|
81
|
+
"--proto_path={}".format(code_dir),
|
|
82
|
+
"--python_out={}".format(code_dir),
|
|
83
|
+
"{}/src/planner/planner.proto".format(code_dir),
|
|
84
|
+
"{}/src/proto/faabric.proto".format(code_dir),
|
|
85
|
+
]
|
|
86
|
+
protoc_cmd = " ".join(protoc_cmd)
|
|
87
|
+
docker_cmd = "{} bash -c '{}'".format(docker_exec_prefix, protoc_cmd)
|
|
88
|
+
run(docker_cmd, shell=True, check=True)
|
|
89
|
+
|
|
90
|
+
# Finally, copy the generated protobuf files to the desired location
|
|
91
|
+
for proto_file in PROTO_FILES:
|
|
92
|
+
if "planner" in proto_file:
|
|
93
|
+
proto_ctr_path = join(code_dir, "src", "planner", proto_file)
|
|
94
|
+
else:
|
|
95
|
+
proto_ctr_path = join(code_dir, "src", "proto", proto_file)
|
|
96
|
+
proto_faasmctl_path = join(GEN_PROTO_DIR, proto_file)
|
|
97
|
+
|
|
98
|
+
docker_cp_cmd = "docker cp {}:{} {}".format(
|
|
99
|
+
tmp_ctr_name, proto_ctr_path, proto_faasmctl_path
|
|
100
|
+
)
|
|
101
|
+
run(docker_cp_cmd, shell=True, check=True)
|
|
102
|
+
|
|
103
|
+
# Delete container
|
|
104
|
+
run("docker rm -f {}".format(tmp_ctr_name), shell=True, check=True)
|
|
105
|
+
print("Done generating protobuf files!")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
if __name__ == "__main__":
|
|
109
|
+
clean = len(argv) == 2 and argv[1] == "--clean"
|
|
110
|
+
gen_proto_files(clean)
|
faasmctl/main.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from invoke import Program
|
|
2
|
+
from faasmctl.tasks import task_ns
|
|
3
|
+
from faasmctl.util.version import get_version
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def main():
|
|
7
|
+
program = Program(
|
|
8
|
+
name="faasmctl",
|
|
9
|
+
binary="faasmctl",
|
|
10
|
+
binary_names=["faasmctl"],
|
|
11
|
+
namespace=task_ns,
|
|
12
|
+
version=get_version(),
|
|
13
|
+
)
|
|
14
|
+
program.run()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from invoke import Collection
|
|
2
|
+
|
|
3
|
+
from . import cli
|
|
4
|
+
from . import delete
|
|
5
|
+
from . import deploy
|
|
6
|
+
from . import flush
|
|
7
|
+
from . import generate
|
|
8
|
+
from . import invoke
|
|
9
|
+
from . import logs
|
|
10
|
+
from . import monitor
|
|
11
|
+
from . import restart
|
|
12
|
+
from . import scale
|
|
13
|
+
from . import status
|
|
14
|
+
from . import upload
|
|
15
|
+
|
|
16
|
+
task_ns = Collection(
|
|
17
|
+
cli,
|
|
18
|
+
delete,
|
|
19
|
+
deploy,
|
|
20
|
+
generate,
|
|
21
|
+
flush,
|
|
22
|
+
invoke,
|
|
23
|
+
logs,
|
|
24
|
+
monitor,
|
|
25
|
+
restart,
|
|
26
|
+
scale,
|
|
27
|
+
status,
|
|
28
|
+
upload,
|
|
29
|
+
)
|
faasmctl/tasks/cli.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
from faasmctl.util.compose import run_compose_cmd, wait_for_venv
|
|
2
|
+
from faasmctl.util.config import (
|
|
3
|
+
BACKEND_INI_STRING,
|
|
4
|
+
get_faasm_ini_file,
|
|
5
|
+
get_faasm_ini_value,
|
|
6
|
+
)
|
|
7
|
+
from faasmctl.util.docker import get_docker_tag
|
|
8
|
+
from invoke import task
|
|
9
|
+
from os.path import abspath, exists
|
|
10
|
+
from subprocess import run
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def do_run_cmd(cli, cmd, cp_in, cp_out, env, ini_file):
|
|
14
|
+
if not ini_file:
|
|
15
|
+
ini_file = get_faasm_ini_file()
|
|
16
|
+
|
|
17
|
+
backend = get_faasm_ini_value(ini_file, "Faasm", BACKEND_INI_STRING)
|
|
18
|
+
if backend == "compose":
|
|
19
|
+
# To run a command in a `compose` backend, we fist start the CLI
|
|
20
|
+
# container in dettached mode (if it does not exist yet) and then we
|
|
21
|
+
# exec into it, copying the ini file
|
|
22
|
+
up_cmd = "up -d --no-recreate {}".format(cli)
|
|
23
|
+
run_compose_cmd(ini_file, up_cmd)
|
|
24
|
+
|
|
25
|
+
# Second, copy the the ini file inside the container
|
|
26
|
+
ini_file = abspath(ini_file)
|
|
27
|
+
ini_file_ctr_path = "/tmp/faasm.ini"
|
|
28
|
+
cp_cmd = "cp {} {}:{}".format(ini_file, cli, ini_file_ctr_path)
|
|
29
|
+
run_compose_cmd(ini_file, cp_cmd)
|
|
30
|
+
|
|
31
|
+
# Third, wait for the virtual environment to be ready if we need to. We
|
|
32
|
+
# only need to wait for the virtual environment if we are either
|
|
33
|
+
# mounting the source, or using one of the clients
|
|
34
|
+
wait_for_venv(ini_file, cli)
|
|
35
|
+
|
|
36
|
+
# Copy files in before execution
|
|
37
|
+
if cp_in:
|
|
38
|
+
host_path = abspath(cp_in.split(":")[0])
|
|
39
|
+
if not exists(host_path):
|
|
40
|
+
print("Host path to copy from does not exist ({})".format(host_path))
|
|
41
|
+
raise RuntimeError("Host path to copy from does not exist")
|
|
42
|
+
ctr_path = cp_in.split(":")[1]
|
|
43
|
+
|
|
44
|
+
cp_cmd = "cp {} {}:{}".format(host_path, cli, ctr_path)
|
|
45
|
+
run_compose_cmd(ini_file, cp_cmd)
|
|
46
|
+
|
|
47
|
+
# Lastly, actually run the requested command
|
|
48
|
+
compose_cmd = [
|
|
49
|
+
"exec",
|
|
50
|
+
"-e FAASM_INI_FILE={}".format(ini_file_ctr_path),
|
|
51
|
+
" ".join(["-e {}".format(var) for var in env.split(",")])
|
|
52
|
+
if env is not None
|
|
53
|
+
else "",
|
|
54
|
+
"-it" if not cmd else "",
|
|
55
|
+
cli,
|
|
56
|
+
"bash" if not cmd else cmd,
|
|
57
|
+
]
|
|
58
|
+
compose_cmd = " ".join(compose_cmd)
|
|
59
|
+
run_compose_cmd(ini_file, compose_cmd)
|
|
60
|
+
|
|
61
|
+
# Copy files out after execution
|
|
62
|
+
if cp_out:
|
|
63
|
+
ctr_path = cp_out.split(":")[0]
|
|
64
|
+
host_path = abspath(cp_out.split(":")[1])
|
|
65
|
+
|
|
66
|
+
cp_cmd = "cp {}:{} {}".format(cli, ctr_path, host_path)
|
|
67
|
+
run_compose_cmd(ini_file, cp_cmd)
|
|
68
|
+
|
|
69
|
+
elif backend == "k8s":
|
|
70
|
+
# Using a CLI container with a cluster that runs on a `k8s` backend is
|
|
71
|
+
# only supported to upload functions from the CPP or Python container.
|
|
72
|
+
# As such we: (i) disable cli.faasm calls on a `k8s` backend, and (ii)
|
|
73
|
+
# use a standalone container to execute the command that we remove
|
|
74
|
+
# immediately after (without mounting any files)
|
|
75
|
+
ini_file_ctr_path = "/tmp/faasm.ini"
|
|
76
|
+
work_dir = get_faasm_ini_value(ini_file, "Faasm", "working_dir")
|
|
77
|
+
|
|
78
|
+
if cli == "cpp":
|
|
79
|
+
image_tag = get_docker_tag(work_dir, "CPP_CLI_IMAGE")
|
|
80
|
+
elif cli == "python":
|
|
81
|
+
image_tag = get_docker_tag(work_dir, "PYTHON_CLI_IMAGE")
|
|
82
|
+
else:
|
|
83
|
+
raise RuntimeError("Can't call cli.faasm on a `k8s` cluster!")
|
|
84
|
+
|
|
85
|
+
docker_cmd = [
|
|
86
|
+
"docker run --rm",
|
|
87
|
+
"-v {}:{}".format(ini_file, ini_file_ctr_path),
|
|
88
|
+
"-e FAASM_INI_FILE={}".format(ini_file_ctr_path),
|
|
89
|
+
"-it" if not cmd else "",
|
|
90
|
+
image_tag,
|
|
91
|
+
"bash" if not cmd else cmd,
|
|
92
|
+
]
|
|
93
|
+
docker_cmd = " ".join(docker_cmd)
|
|
94
|
+
|
|
95
|
+
run(docker_cmd, shell=True)
|
|
96
|
+
|
|
97
|
+
else:
|
|
98
|
+
raise RuntimeError("Unrecognised backend: {}".format(backend))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@task
|
|
102
|
+
def faasm(ctx, cmd=None, cp_in=None, cp_out=None, env=None, ini_file=None):
|
|
103
|
+
"""
|
|
104
|
+
Run a command in the Faasm CLI container
|
|
105
|
+
|
|
106
|
+
By default, if --cmd is set to None, we will get a shell into the
|
|
107
|
+
container. If the --cmd argument is set, we execute the cmd string
|
|
108
|
+
|
|
109
|
+
Parameters:
|
|
110
|
+
- cmd (str): command to run in the CLI container
|
|
111
|
+
- cp_in (str): file to copy into the CLI as host_path:ctr_path
|
|
112
|
+
- cp_out (str): file to copy out of the CLI as ctr_path:host_path
|
|
113
|
+
- env (str): comma-separated ENV=VAR environment variables for cmd
|
|
114
|
+
- ini_file (str): path to the cluster's INI file
|
|
115
|
+
"""
|
|
116
|
+
do_run_cmd("faasm-cli", cmd, cp_in, cp_out, env, ini_file)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@task
|
|
120
|
+
def cpp(ctx, cmd=None, cp_in=None, cp_out=None, env=None, ini_file=None):
|
|
121
|
+
"""
|
|
122
|
+
Run a command in the CPP CLI container
|
|
123
|
+
|
|
124
|
+
By default, if --cmd is set to None, we will get a shell into the
|
|
125
|
+
container. If the --cmd argument is set, we execute the cmd string
|
|
126
|
+
|
|
127
|
+
Parameters:
|
|
128
|
+
- cmd (str): command to run in the CLI container
|
|
129
|
+
- cp_in (str): file to copy into the CLI as host_path:ctr_path
|
|
130
|
+
- cp_out (str): file to copy out of the CLI as ctr_path:host_path
|
|
131
|
+
- ini_file (str): path to the cluster's INI file
|
|
132
|
+
"""
|
|
133
|
+
do_run_cmd("cpp", cmd, cp_in, cp_out, env, ini_file)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@task
|
|
137
|
+
def python(ctx, cmd=None, cp_in=None, cp_out=None, env=None, ini_file=None):
|
|
138
|
+
"""
|
|
139
|
+
Run a command in the Python CLI container
|
|
140
|
+
|
|
141
|
+
By default, if --cmd is set to None, we will get a shell into the
|
|
142
|
+
container. If the --cmd argument is set, we execute the cmd string
|
|
143
|
+
|
|
144
|
+
Parameters:
|
|
145
|
+
- cmd (str): command to run in the CLI container
|
|
146
|
+
- cp_in (str): file to copy into the CLI as host_path:ctr_path
|
|
147
|
+
- cp_out (str): file to copy out of the CLI as ctr_path:host_path
|
|
148
|
+
- ini_file (str): path to the cluster's INI file
|
|
149
|
+
"""
|
|
150
|
+
do_run_cmd("python", cmd, cp_in, cp_out, env, ini_file)
|
faasmctl/tasks/delete.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from faasmctl.util.compose import delete_compose_cluster
|
|
2
|
+
from faasmctl.util.config import get_faasm_ini_file, get_faasm_ini_value
|
|
3
|
+
from faasmctl.util.k8s import delete_k8s_cluster
|
|
4
|
+
from invoke import task
|
|
5
|
+
from os import remove
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@task(default=True)
|
|
9
|
+
def delete(ctx, ini_file=None):
|
|
10
|
+
"""
|
|
11
|
+
Delete a running Faasm cluster
|
|
12
|
+
|
|
13
|
+
Parameters:
|
|
14
|
+
- ini_file (str): path to the cluster's INI file (FAASM_INI_FILE env. var
|
|
15
|
+
if not found)
|
|
16
|
+
"""
|
|
17
|
+
if not ini_file:
|
|
18
|
+
ini_file = get_faasm_ini_file()
|
|
19
|
+
|
|
20
|
+
backend = get_faasm_ini_value(ini_file, "Faasm", "backend")
|
|
21
|
+
if backend == "compose":
|
|
22
|
+
delete_compose_cluster(ini_file)
|
|
23
|
+
elif backend == "k8s":
|
|
24
|
+
delete_k8s_cluster(ini_file)
|
|
25
|
+
else:
|
|
26
|
+
raise RuntimeError("Unsupported backend: {}".format(backend))
|
|
27
|
+
|
|
28
|
+
remove(ini_file)
|
faasmctl/tasks/deploy.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from faasmctl.util.compose import (
|
|
2
|
+
deploy_compose_cluster,
|
|
3
|
+
populate_host_sysroot,
|
|
4
|
+
run_compose_cmd,
|
|
5
|
+
)
|
|
6
|
+
from faasmctl.util.deploy import fetch_faasm_code
|
|
7
|
+
from faasmctl.util.k8s import DEFAULT_KUBECONFIG_PATH, deploy_k8s_cluster
|
|
8
|
+
from invoke import task
|
|
9
|
+
from os import environ
|
|
10
|
+
from os.path import abspath
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@task
|
|
14
|
+
def compose(ctx, workers=2, mount_source=None, clean=False, ini_file=None):
|
|
15
|
+
"""
|
|
16
|
+
Deploy a Faasm cluster on docker compose
|
|
17
|
+
|
|
18
|
+
Parameters:
|
|
19
|
+
- workers (int): number of workers to deploy
|
|
20
|
+
- mount_source (str): path to the Faasm's source code checkout
|
|
21
|
+
- clean (bool): flag to indicate whether we clean cached checkouts
|
|
22
|
+
- ini_file (str): path to the ini_file to generate (if selected)
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
- (str): path to the generated ini_file
|
|
26
|
+
"""
|
|
27
|
+
# First, check-out the Faasm source if necessary
|
|
28
|
+
if mount_source:
|
|
29
|
+
mount_source = abspath(mount_source)
|
|
30
|
+
if clean:
|
|
31
|
+
print(
|
|
32
|
+
"WARNING: using the --clean flag with --mount_source "
|
|
33
|
+
"env. variable is disabled, as local changes could be "
|
|
34
|
+
"erased!"
|
|
35
|
+
)
|
|
36
|
+
faasm_checkout, faasm_ver = fetch_faasm_code(
|
|
37
|
+
faasm_source=mount_source, force=False
|
|
38
|
+
)
|
|
39
|
+
mount_source = True
|
|
40
|
+
|
|
41
|
+
# If mounting the source code, we need to populate the host's
|
|
42
|
+
# sysroot to mount it into the containers
|
|
43
|
+
populate_host_sysroot(faasm_checkout)
|
|
44
|
+
else:
|
|
45
|
+
# Otherwise, we will check out the code in a faasmctl-specific working
|
|
46
|
+
# directoy
|
|
47
|
+
mount_source = False
|
|
48
|
+
faasm_checkout, faasm_ver = fetch_faasm_code(force=clean)
|
|
49
|
+
|
|
50
|
+
return deploy_compose_cluster(faasm_checkout, workers, mount_source, ini_file)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@task
|
|
54
|
+
def dist_tests(ctx, mount_source=None, ini_file=None):
|
|
55
|
+
"""
|
|
56
|
+
Deploy a development Faasm cluster to run distributed tests
|
|
57
|
+
|
|
58
|
+
Parameters:
|
|
59
|
+
- mount_source (str): path to the Faasm's source code checkout
|
|
60
|
+
- ini_file (str): optional path to a running cluster
|
|
61
|
+
"""
|
|
62
|
+
# If the user provided a path to the ini_file, it means that we are
|
|
63
|
+
# deploying the dist-tests on top of an existing cluster. Otherwise start
|
|
64
|
+
# a new compose clustter
|
|
65
|
+
if ini_file is None:
|
|
66
|
+
if not mount_source:
|
|
67
|
+
raise RuntimeError(
|
|
68
|
+
"When deploying a dist-tests cluster, you need to"
|
|
69
|
+
" specify the --mount-source"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
ini_file = compose(ctx, workers=0, mount_source=mount_source, clean=False)
|
|
73
|
+
|
|
74
|
+
# Second, start the dist-test-server
|
|
75
|
+
run_compose_cmd(ini_file, "up -d dist-test-server")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@task
|
|
79
|
+
def k8s(ctx, workers=2, context=None, clean=False, ini_file=None):
|
|
80
|
+
"""
|
|
81
|
+
Deploy a Faasm cluster on k8s
|
|
82
|
+
|
|
83
|
+
Parameters:
|
|
84
|
+
- workers (int): number of workers to deploy
|
|
85
|
+
- context (str): path to the k8s config to use (defaults to ~/.kube/config)
|
|
86
|
+
- clean (bool): flag to clean the local checkout of the code tag
|
|
87
|
+
- ini_file (str): path to the ini_file to generate (if selected)
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
- (str): path to the generated ini_file
|
|
91
|
+
"""
|
|
92
|
+
# First, check-out the Faasm source if necessary (we need it for the k8s
|
|
93
|
+
# deployment files, eventually we could publish them as helm charts)
|
|
94
|
+
faasm_checkout, faasm_ver = fetch_faasm_code(force=clean)
|
|
95
|
+
|
|
96
|
+
if context:
|
|
97
|
+
context = abspath(context)
|
|
98
|
+
elif "KUBECONFIG" in environ:
|
|
99
|
+
context = abspath(environ["KUBECONFIG"])
|
|
100
|
+
else:
|
|
101
|
+
context = DEFAULT_KUBECONFIG_PATH
|
|
102
|
+
|
|
103
|
+
return deploy_k8s_cluster(context, faasm_checkout, workers, ini_file)
|
faasmctl/tasks/flush.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from faasmctl.util.flush import flush_hosts, flush_workers
|
|
2
|
+
from invoke import task
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@task()
|
|
6
|
+
def hosts(ctx):
|
|
7
|
+
"""
|
|
8
|
+
Flush the list of registered and available hosts in the Faasm cluster
|
|
9
|
+
"""
|
|
10
|
+
flush_hosts()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@task()
|
|
14
|
+
def workers(ctx):
|
|
15
|
+
"""
|
|
16
|
+
Flush the state and wasm files cached in each worker
|
|
17
|
+
"""
|
|
18
|
+
flush_workers()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@task()
|
|
22
|
+
def all(ctx):
|
|
23
|
+
"""
|
|
24
|
+
Flush functions, state and shared files from all workers
|
|
25
|
+
"""
|
|
26
|
+
# First flush the workers
|
|
27
|
+
workers(ctx)
|
|
28
|
+
|
|
29
|
+
# Last flush the available host list (note that after this call no other
|
|
30
|
+
# flushing works, as the planner is unaware of any hosts in the cluster)
|
|
31
|
+
hosts(ctx)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from invoke import task
|
|
2
|
+
from os.path import join
|
|
3
|
+
from subprocess import run
|
|
4
|
+
from faasmctl.util.env import PROJ_ROOT
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@task
|
|
8
|
+
def proto(ctx):
|
|
9
|
+
"""
|
|
10
|
+
(Re)generate the Python protobuf files necessary to interact with Faasm
|
|
11
|
+
"""
|
|
12
|
+
gen_proto_bin = join(PROJ_ROOT, "bin", "gen_proto_files.py")
|
|
13
|
+
gen_cmd = "python3 {} --clean".format(gen_proto_bin)
|
|
14
|
+
run(gen_cmd, shell=True, check=True)
|
faasmctl/tasks/invoke.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from base64 import b64encode
|
|
2
|
+
from faasmctl.util.invoke import invoke_wasm
|
|
3
|
+
from faasmctl.util.planner import get_available_hosts
|
|
4
|
+
from faasmctl.util.results import (
|
|
5
|
+
get_execution_time_from_message_results,
|
|
6
|
+
get_return_code_from_message_results,
|
|
7
|
+
)
|
|
8
|
+
from invoke import task
|
|
9
|
+
from sys import exit
|
|
10
|
+
from time import time
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@task(default=True)
|
|
14
|
+
def invoke(
|
|
15
|
+
ctx,
|
|
16
|
+
user,
|
|
17
|
+
func,
|
|
18
|
+
ini_file=None,
|
|
19
|
+
cmdline=None,
|
|
20
|
+
input_data=None,
|
|
21
|
+
mpi_world_size=None,
|
|
22
|
+
single_host=False,
|
|
23
|
+
host_dist=None,
|
|
24
|
+
):
|
|
25
|
+
"""
|
|
26
|
+
Invoke the execution of a user/func pair
|
|
27
|
+
|
|
28
|
+
TODO: think how to enable all the possible command line values in a
|
|
29
|
+
scalable way.
|
|
30
|
+
"""
|
|
31
|
+
num_messages = 1
|
|
32
|
+
req_dict = {"user": user, "function": func}
|
|
33
|
+
msg_dict = {"user": user, "function": func}
|
|
34
|
+
|
|
35
|
+
if cmdline is not None:
|
|
36
|
+
msg_dict["cmdline"] = cmdline
|
|
37
|
+
if input_data is not None:
|
|
38
|
+
msg_dict["input_data"] = b64encode(input_data.encode("utf-8")).decode("utf-8")
|
|
39
|
+
if mpi_world_size is not None:
|
|
40
|
+
msg_dict["mpi_world_size"] = int(mpi_world_size)
|
|
41
|
+
if single_host:
|
|
42
|
+
req_dict["singleHostHint"] = single_host
|
|
43
|
+
if host_dist:
|
|
44
|
+
host_dist = host_dist.split(",")
|
|
45
|
+
# Prepare a host distribution
|
|
46
|
+
available_hosts = get_available_hosts()
|
|
47
|
+
|
|
48
|
+
if len(host_dist) > len(available_hosts.hosts):
|
|
49
|
+
print(
|
|
50
|
+
"ERROR: requested execution among {} hosts but only {} "
|
|
51
|
+
"available!".format(len(host_dist), len(available_hosts.hosts))
|
|
52
|
+
)
|
|
53
|
+
return 1
|
|
54
|
+
|
|
55
|
+
# We assume that the available host list will always come in the same
|
|
56
|
+
# order (for the same set of hosts)
|
|
57
|
+
|
|
58
|
+
host_list = []
|
|
59
|
+
for host in host_dist:
|
|
60
|
+
host_ind = int(host.split(":")[0])
|
|
61
|
+
num_in_host = int(host.split(":")[1])
|
|
62
|
+
host_list += [available_hosts.hosts[host_ind].ip] * num_in_host
|
|
63
|
+
else:
|
|
64
|
+
host_list = None
|
|
65
|
+
|
|
66
|
+
# Invoke WASM using main API function
|
|
67
|
+
start_ts = time()
|
|
68
|
+
result = invoke_wasm(
|
|
69
|
+
msg_dict,
|
|
70
|
+
num_messages=num_messages,
|
|
71
|
+
req_dict=req_dict,
|
|
72
|
+
ini_file=ini_file,
|
|
73
|
+
host_list=host_list,
|
|
74
|
+
)
|
|
75
|
+
end_ts = time()
|
|
76
|
+
|
|
77
|
+
# Pretty-print a summary of the execution
|
|
78
|
+
|
|
79
|
+
user = result.messageResults[0].user
|
|
80
|
+
function = result.messageResults[0].function
|
|
81
|
+
output = result.messageResults[0].outputData
|
|
82
|
+
ret_val = get_return_code_from_message_results(result)
|
|
83
|
+
# Wall time is the time elapsed as measured from the calling python script
|
|
84
|
+
wall_time = "{:.2f} s".format(end_ts - start_ts)
|
|
85
|
+
# Exec time is the time the function actually executed inside Faasm
|
|
86
|
+
exec_time = "{:.2f} s".format(
|
|
87
|
+
get_execution_time_from_message_results(result, unit="s")
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
print("======================= Faasm Execution =========================")
|
|
91
|
+
print("Function: \t\t\t{}/{}".format(user, function))
|
|
92
|
+
print("Return value: \t\t\t{}".format(ret_val))
|
|
93
|
+
print("Wall time: \t\t\t{}".format(wall_time))
|
|
94
|
+
print("Execution time: \t\t{}".format(exec_time))
|
|
95
|
+
print("-----------------------------------------------------------------")
|
|
96
|
+
print("Output:\n{}".format(output))
|
|
97
|
+
print("=================================================================")
|
|
98
|
+
|
|
99
|
+
# Use sys/exit to propagate the error code to the bash process. If
|
|
100
|
+
# execution fails, we want the bash process to have a non-zero exit code.
|
|
101
|
+
# This is very helpful for testing environments like GHA
|
|
102
|
+
exit(ret_val)
|
faasmctl/tasks/logs.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from faasmctl.util.backend import COMPOSE_BACKEND, K8S_BACKEND
|
|
2
|
+
from faasmctl.util.compose import run_compose_cmd
|
|
3
|
+
from faasmctl.util.config import (
|
|
4
|
+
BACKEND_INI_STRING,
|
|
5
|
+
get_faasm_ini_file,
|
|
6
|
+
get_faasm_ini_value,
|
|
7
|
+
)
|
|
8
|
+
from faasmctl.util.k8s import run_k8s_cmd
|
|
9
|
+
from invoke import task
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_compose_logs(s, follow, ini_file, last_restart):
|
|
13
|
+
compose_cmd = [
|
|
14
|
+
"logs",
|
|
15
|
+
"--since {}".format(last_restart),
|
|
16
|
+
"-f" if follow else "",
|
|
17
|
+
"{}".format(" ".join(s)),
|
|
18
|
+
]
|
|
19
|
+
compose_cmd = " ".join(compose_cmd)
|
|
20
|
+
run_compose_cmd(ini_file, compose_cmd)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_k8s_logs(s, follow, ini_file, last_restart):
|
|
24
|
+
if len(s) > 1:
|
|
25
|
+
raise RuntimeError(
|
|
26
|
+
"Getting the logs for a K8s service only works with one service at a time!"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
service_to_k8s_str = {
|
|
30
|
+
"planner": "pod/planner",
|
|
31
|
+
"worker": "-l run=faasm-worker",
|
|
32
|
+
"upload": "pod/upload",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
service = s[0]
|
|
36
|
+
if service not in service_to_k8s_str:
|
|
37
|
+
raise RuntimeError(
|
|
38
|
+
"Unrecognised service name: {} (must be one in: {})".format(
|
|
39
|
+
service, service_to_k8s_str.keys()
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
k8s_cmd = [
|
|
44
|
+
"logs",
|
|
45
|
+
"-f" if follow else "",
|
|
46
|
+
service_to_k8s_str[service],
|
|
47
|
+
"--tail=-1",
|
|
48
|
+
]
|
|
49
|
+
k8s_cmd = " ".join(k8s_cmd)
|
|
50
|
+
k8s_config = get_faasm_ini_value(ini_file, "Faasm", "k8s_config")
|
|
51
|
+
run_k8s_cmd(k8s_config, "faasm", k8s_cmd)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@task(default=True, iterable=["s"])
|
|
55
|
+
def logs(ctx, s, follow=False, ini_file=None):
|
|
56
|
+
"""
|
|
57
|
+
Get the logs of a running service in the cluster
|
|
58
|
+
|
|
59
|
+
Parameters:
|
|
60
|
+
- s (str, repeateble): service to get the logs from
|
|
61
|
+
- ini_file (str): path to the cluster's INI file
|
|
62
|
+
"""
|
|
63
|
+
if not ini_file:
|
|
64
|
+
ini_file = get_faasm_ini_file()
|
|
65
|
+
|
|
66
|
+
backend = get_faasm_ini_value(ini_file, "Faasm", BACKEND_INI_STRING)
|
|
67
|
+
last_restart = get_faasm_ini_value(ini_file, "Faasm", "last_restart")
|
|
68
|
+
if backend == COMPOSE_BACKEND:
|
|
69
|
+
get_compose_logs(s, follow, ini_file, last_restart)
|
|
70
|
+
elif backend == K8S_BACKEND:
|
|
71
|
+
get_k8s_logs(s, follow, ini_file, last_restart)
|
|
72
|
+
else:
|
|
73
|
+
raise RuntimeError("Unrecognised backend: {}".format(backend))
|