Skip to content

Commit 488d7bc

Browse files
committed
Add DFaaS plugin implementation and tests
1 parent 71494bf commit 488d7bc

37 files changed

+5510
-0
lines changed

lb_plugins/plugins/dfaas/README.md

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# DFaaS Plugin
2+
3+
This plugin implements the legacy DFaaS sampling workflow using k6 on a
4+
dedicated host and OpenFaaS/Prometheus on the target.
5+
6+
## Architecture
7+
- Target host: runner + k3s + OpenFaaS + Prometheus + exporters.
8+
- k6 host: executes load, controlled via SSH from the target host.
9+
- Network paths:
10+
- target -> k6-host over SSH (port 22 by default)
11+
- k6-host -> OpenFaaS gateway (port 31112 by default)
12+
- target -> Prometheus (NodePort 30411 by default)
13+
14+
## Prerequisites
15+
- SSH access from target host to k6 host using the configured key.
16+
- Target host packages: `kubectl`, `helm`, `faas-cli`, `ansible-playbook`.
17+
- Open ports: 22 (SSH), 31112 (OpenFaaS gateway), 30411 (Prometheus).
18+
19+
## Setup steps
20+
1. Install k6 on the k6 host:
21+
- Run `lb_plugins/plugins/dfaas/ansible/setup_k6.yml` against the k6 host.
22+
2. Install k3s/OpenFaaS/Prometheus on the target host:
23+
- Run `lb_plugins/plugins/dfaas/ansible/setup_target.yml` against the target.
24+
3. Verify:
25+
- `kubectl get nodes` shows Ready.
26+
- `faas-cli list --gateway http://<target>:31112` lists functions.
27+
- `curl http://<target>:30411/-/ready` returns 200.
28+
29+
## Run flow
30+
- Generate all function combinations and rate vectors.
31+
- For each config:
32+
- Enforce cooldown (idle CPU/RAM/POWER and replicas < 2).
33+
- Generate a k6 script and run it on the k6 host.
34+
- Parse the k6 summary and query Prometheus.
35+
- Compute overload and skip dominant configs when needed.
36+
37+
## Outputs
38+
In the workload output directory:
39+
- `results.csv`, `skipped.csv`, `index.csv` (legacy-compatible headers).
40+
- `summaries/summary-<config>-iter<iter>-rep<rep>.json`
41+
- `metrics/metrics-<config>-iter<iter>-rep<rep>.csv`
42+
- `k6_scripts/config-<config>.js`
43+
44+
## Config loading
45+
- `config_path` can be passed via workload options to load a YAML/JSON file that
46+
contains `common` and `plugins.dfaas` sections.
47+
- `plugins.dfaas` overrides `common`, and any options passed alongside
48+
`config_path` override both.
49+
50+
## Config schema
51+
Top-level fields (in `plugins.dfaas`):
52+
- `k6_host` (str): k6 host address.
53+
- `k6_user` (str): SSH user for the k6 host.
54+
- `k6_ssh_key` (str): SSH private key path.
55+
- `k6_port` (int): SSH port.
56+
- `k6_workspace_root` (str): workspace root on the k6 host.
57+
- `output_dir` (str): optional output directory override for DFaaS artifacts.
58+
- `run_id` (str): optional run identifier used for k6 workspace.
59+
- `gateway_url` (str): OpenFaaS gateway URL.
60+
- `prometheus_url` (str): Prometheus base URL (default NodePort 30411).
61+
- `functions` (list): list of function objects (name/method/body/headers/max_rate).
62+
- `rates` (object): `min_rate`, `max_rate`, `step`.
63+
- `combinations` (object): `min_functions`, `max_functions` (max is exclusive).
64+
- `duration` (str): k6 duration string (e.g. `30s`).
65+
- `iterations` (int): iterations per configuration.
66+
- `cooldown` (object): `max_wait_seconds`, `sleep_step_seconds`,
67+
`idle_threshold_pct`.
68+
- `overload` (object): `cpu_overload_pct_of_capacity`, `ram_overload_pct`,
69+
`success_rate_node_min`, `success_rate_function_min`,
70+
`replicas_overload_threshold`.
71+
- `queries_path` (str): path to the Prometheus queries file.
72+
- `deploy_functions` (bool): deploy OpenFaaS store functions.
73+
- `scaphandre_enabled` (bool): enable power metrics via Scaphandre.
74+
- `function_pid_regexes` (map): optional PID regex per function when Scaphandre is enabled.
75+
76+
Common base fields:
77+
- `max_retries` (int): retries for the workload (default 0).
78+
- `timeout_buffer` (int): safety buffer added to expected runtime (default 10).
79+
- `tags` (list[str]): workload tags.
80+
81+
Function object fields:
82+
- `name` (str): OpenFaaS function name.
83+
- `method` (str): HTTP method (GET/POST/etc).
84+
- `body` (str): request payload (match legacy payloads in
85+
`legacy_materials/samples_generator/utils.py`).
86+
- `headers` (map): HTTP headers.
87+
- `max_rate` (int, optional): per-function maximum rate (requests/sec).
88+
89+
## Example config (YAML)
90+
```
91+
common:
92+
timeout_buffer: 10
93+
94+
plugins:
95+
dfaas:
96+
k6_host: "10.0.0.50"
97+
k6_user: "ubuntu"
98+
k6_ssh_key: "~/.ssh/id_rsa"
99+
k6_port: 22
100+
101+
gateway_url: "http://<target-ip>:31112"
102+
prometheus_url: "http://<target-ip>:30411"
103+
104+
functions:
105+
- name: "figlet"
106+
method: "POST"
107+
body: "Hello DFaaS!"
108+
headers:
109+
Content-Type: "text/plain"
110+
max_rate: 100
111+
- name: "eat-memory"
112+
method: "GET"
113+
body: ""
114+
115+
rates:
116+
min_rate: 0
117+
max_rate: 200
118+
step: 10
119+
120+
combinations:
121+
min_functions: 1
122+
max_functions: 2
123+
124+
duration: "30s"
125+
iterations: 3
126+
127+
cooldown:
128+
max_wait_seconds: 180
129+
sleep_step_seconds: 5
130+
idle_threshold_pct: 15
131+
132+
overload:
133+
cpu_overload_pct_of_capacity: 80
134+
ram_overload_pct: 90
135+
success_rate_node_min: 0.95
136+
success_rate_function_min: 0.90
137+
replicas_overload_threshold: 15
138+
139+
queries_path: "lb_plugins/plugins/dfaas/queries.yml"
140+
deploy_functions: true
141+
scaphandre_enabled: false
142+
```
143+
144+
## Formal rules (legacy)
145+
- Rate list: `rates = [min_rate..max_rate step]` inclusive, ascending.
146+
- Combinations: function sets from `min_functions..max_functions` (max exclusive).
147+
- Dominance: Config B dominates A if for every function `rate_B >= rate_A` and
148+
for at least one function `rate_B > rate_A`. If A is overloaded, skip all
149+
dominant configs.
150+
- Cooldown: wait until CPU/RAM/POWER <= idle + idle * 15% and replicas < 2, with
151+
a max wait of 180s.
152+
- Overload:
153+
- Node overloaded if avg success rate < 0.95 OR CPU > 80% capacity OR
154+
RAM > 90% OR any function overload.
155+
- Function overloaded if success rate < 0.90 OR replicas >= 15.
156+
157+
## Troubleshooting
158+
- OpenFaaS gateway unreachable: confirm NodePort 31112 and `faas-cli login`.
159+
- Prometheus query timeouts: verify `prometheus_url` and NodePort 30411.
160+
- k6 SSH failures: confirm `k6_host`, `k6_user`, and `k6_ssh_key` are correct.
161+
- Cooldown never completes: check for stuck replicas or sustained CPU/RAM load.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""DFaaS workload plugin package."""
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: prometheus
5+
namespace: openfaas
6+
labels:
7+
app: prometheus
8+
spec:
9+
replicas: 1
10+
selector:
11+
matchLabels:
12+
app: prometheus
13+
template:
14+
metadata:
15+
labels:
16+
app: prometheus
17+
spec:
18+
securityContext:
19+
runAsNonRoot: true
20+
runAsUser: 65534
21+
fsGroup: 65534
22+
serviceAccountName: prometheus
23+
containers:
24+
- name: prometheus
25+
image: prom/prometheus:v2.52.0
26+
args:
27+
- "--config.file=/etc/prometheus/prometheus.yml"
28+
- "--storage.tsdb.path=/prometheus"
29+
- "--storage.tsdb.retention.time=24h"
30+
ports:
31+
- containerPort: 9090
32+
volumeMounts:
33+
- name: prometheus-config
34+
mountPath: /etc/prometheus
35+
- name: prometheus-data
36+
mountPath: /prometheus
37+
volumes:
38+
- name: prometheus-config
39+
configMap:
40+
name: prometheus-config
41+
- name: prometheus-data
42+
emptyDir: {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
apiVersion: v1
2+
kind: ServiceAccount
3+
metadata:
4+
name: prometheus
5+
namespace: openfaas
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
- name: Run k6 workload for a single DFaaS config
3+
hosts: all
4+
become: true
5+
vars:
6+
k6_workspace_root: "/var/lib/dfaas-k6"
7+
target_name: ""
8+
run_id: ""
9+
config_id: ""
10+
script_src: ""
11+
summary_dest: ""
12+
summary_fetch_dest: ""
13+
tasks:
14+
- name: Validate required variables
15+
ansible.builtin.assert:
16+
that:
17+
- target_name | length > 0
18+
- run_id | length > 0
19+
- config_id | length > 0
20+
- script_src | length > 0
21+
fail_msg: "target_name, run_id, config_id, and script_src are required."
22+
23+
- name: Build workspace paths
24+
ansible.builtin.set_fact:
25+
config_dir: "{{ k6_workspace_root }}/{{ target_name }}/{{ run_id }}/{{ config_id }}"
26+
script_dest: "{{ k6_workspace_root }}/{{ target_name }}/{{ run_id }}/{{ config_id }}/config-{{ config_id }}.js"
27+
28+
- name: Ensure config workspace exists
29+
ansible.builtin.file:
30+
path: "{{ config_dir }}"
31+
state: directory
32+
mode: "0755"
33+
34+
- name: Copy k6 script to workspace
35+
ansible.builtin.copy:
36+
src: "{{ script_src }}"
37+
dest: "{{ script_dest }}"
38+
mode: "0644"
39+
40+
- name: Determine summary and log paths
41+
ansible.builtin.set_fact:
42+
summary_path: >-
43+
{{
44+
(summary_dest | length > 0)
45+
| ternary(summary_dest, config_dir + '/summary.json')
46+
}}
47+
log_path: "{{ config_dir }}/k6.log"
48+
49+
- name: Run k6 with summary export
50+
ansible.builtin.shell: >
51+
k6 run --summary-export "{{ summary_path }}" "{{ script_dest }}"
52+
> "{{ log_path }}" 2>&1
53+
args:
54+
executable: /bin/bash
55+
56+
- name: Fetch summary JSON to controller
57+
ansible.builtin.fetch:
58+
src: "{{ summary_path }}"
59+
dest: "{{ summary_fetch_dest }}"
60+
flat: true
61+
when: summary_fetch_dest | length > 0
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
---
2+
- name: Install k6 and prepare workspace
3+
hosts: all
4+
become: true
5+
vars:
6+
k6_workspace_root: "/var/lib/dfaas-k6"
7+
k6_key_url: "https://dl.k6.io/key.gpg"
8+
k6_keyring_path: "/usr/share/keyrings/k6-archive-keyring.gpg"
9+
k6_repo: "deb [signed-by={{ k6_keyring_path }}] https://dl.k6.io/deb stable main"
10+
k6_version: "0.49.0"
11+
k6_arch_map:
12+
x86_64: "amd64"
13+
amd64: "amd64"
14+
aarch64: "arm64"
15+
arm64: "arm64"
16+
k6_arch: "{{ k6_arch_map.get(ansible_architecture, 'amd64') }}"
17+
k6_tarball_url: "https://github.com/grafana/k6/releases/download/v{{ k6_version }}/k6-v{{ k6_version }}-linux-{{ k6_arch }}.tar.gz"
18+
k6_extract_dir: "/tmp/k6-v{{ k6_version }}-linux-{{ k6_arch }}"
19+
tasks:
20+
- name: Install apt dependencies
21+
ansible.builtin.apt:
22+
name:
23+
- ca-certificates
24+
- gnupg
25+
state: present
26+
update_cache: true
27+
28+
- name: Ensure keyring directory exists
29+
ansible.builtin.file:
30+
path: "/usr/share/keyrings"
31+
state: directory
32+
mode: "0755"
33+
34+
- name: Download k6 repository key
35+
ansible.builtin.get_url:
36+
url: "{{ k6_key_url }}"
37+
dest: "/tmp/k6-key.gpg"
38+
mode: "0644"
39+
40+
- name: Install k6 repository key
41+
ansible.builtin.command: "gpg --dearmor -o {{ k6_keyring_path }} /tmp/k6-key.gpg"
42+
args:
43+
creates: "{{ k6_keyring_path }}"
44+
45+
- name: Add k6 APT repository
46+
ansible.builtin.apt_repository:
47+
repo: "{{ k6_repo }}"
48+
filename: "k6"
49+
state: present
50+
51+
- name: Install k6 via APT
52+
block:
53+
- name: Install k6
54+
ansible.builtin.apt:
55+
name: k6
56+
state: present
57+
update_cache: true
58+
rescue:
59+
- name: Download k6 tarball
60+
ansible.builtin.get_url:
61+
url: "{{ k6_tarball_url }}"
62+
dest: "/tmp/k6.tar.gz"
63+
mode: "0644"
64+
65+
- name: Extract k6 tarball
66+
ansible.builtin.unarchive:
67+
src: "/tmp/k6.tar.gz"
68+
dest: "/tmp"
69+
remote_src: true
70+
71+
- name: Install k6 binary
72+
ansible.builtin.copy:
73+
src: "{{ k6_extract_dir }}/k6"
74+
dest: "/usr/local/bin/k6"
75+
mode: "0755"
76+
remote_src: true
77+
78+
- name: Ensure k6 workspace root exists
79+
ansible.builtin.file:
80+
path: "{{ k6_workspace_root }}"
81+
state: directory
82+
mode: "0755"

0 commit comments

Comments
 (0)