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.
Files changed (44) hide show
  1. faasmctl/__init__.py +4 -0
  2. faasmctl/bin/gen_proto_files.py +110 -0
  3. faasmctl/main.py +14 -0
  4. faasmctl/tasks/__init__.py +29 -0
  5. faasmctl/tasks/cli.py +150 -0
  6. faasmctl/tasks/delete.py +28 -0
  7. faasmctl/tasks/deploy.py +103 -0
  8. faasmctl/tasks/flush.py +31 -0
  9. faasmctl/tasks/generate.py +14 -0
  10. faasmctl/tasks/invoke.py +102 -0
  11. faasmctl/tasks/logs.py +73 -0
  12. faasmctl/tasks/monitor.py +317 -0
  13. faasmctl/tasks/restart.py +46 -0
  14. faasmctl/tasks/scale.py +47 -0
  15. faasmctl/tasks/status.py +29 -0
  16. faasmctl/tasks/upload.py +36 -0
  17. faasmctl/util/backend.py +2 -0
  18. faasmctl/util/batch.py +14 -0
  19. faasmctl/util/compose.py +399 -0
  20. faasmctl/util/config.py +80 -0
  21. faasmctl/util/deploy.py +152 -0
  22. faasmctl/util/docker.py +52 -0
  23. faasmctl/util/env.py +10 -0
  24. faasmctl/util/faasm.py +25 -0
  25. faasmctl/util/flush.py +38 -0
  26. faasmctl/util/gen_proto/faabric_pb2.py +75 -0
  27. faasmctl/util/gen_proto/planner_pb2.py +65 -0
  28. faasmctl/util/invoke.py +156 -0
  29. faasmctl/util/k8s.py +371 -0
  30. faasmctl/util/message.py +14 -0
  31. faasmctl/util/network.py +38 -0
  32. faasmctl/util/planner.py +201 -0
  33. faasmctl/util/random.py +8 -0
  34. faasmctl/util/restart.py +21 -0
  35. faasmctl/util/results.py +40 -0
  36. faasmctl/util/time.py +6 -0
  37. faasmctl/util/upload.py +61 -0
  38. faasmctl/util/version.py +5 -0
  39. myfaasmctl-0.0.1.dist-info/LICENSE.md +202 -0
  40. myfaasmctl-0.0.1.dist-info/METADATA +42 -0
  41. myfaasmctl-0.0.1.dist-info/RECORD +44 -0
  42. myfaasmctl-0.0.1.dist-info/WHEEL +5 -0
  43. myfaasmctl-0.0.1.dist-info/entry_points.txt +2 -0
  44. myfaasmctl-0.0.1.dist-info/top_level.txt +1 -0
faasmctl/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ # flake8: noqa
2
+
3
+ from . import tasks
4
+ from . import util
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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))