From bf9b8fa53cd26fb5a06388f338cd7c1c824325e9 Mon Sep 17 00:00:00 2001 From: Camillarhi Date: Thu, 27 Feb 2025 02:42:24 +0100 Subject: [PATCH 1/7] circuit breaker plugin readme --- resources/plugins/circuitbreaker/README.md | 151 +++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 resources/plugins/circuitbreaker/README.md diff --git a/resources/plugins/circuitbreaker/README.md b/resources/plugins/circuitbreaker/README.md new file mode 100644 index 000000000..5fb1e7f9c --- /dev/null +++ b/resources/plugins/circuitbreaker/README.md @@ -0,0 +1,151 @@ +# Circuit Breaker Plugin + +## Overview +The Circuit Breaker plugin integrates the [circuitbreaker](https://github.com/lightningequipment/circuitbreaker) tool with Warnet to protect Lightning Network nodes from being flooded with HTLCs. Circuit Breaker functions like a firewall for Lightning, allowing node operators to set limits on in-flight HTLCs and implement rate limiting on a per-peer basis. + +## What is Circuit Breaker? +Circuit Breaker is to Lightning what firewalls are to the internet. It provides protection against: +- HTLC flooding attacks +- Channel slot exhaustion (max 483 slots per channel) +- DoS/spam attacks using large numbers of fast-resolving HTLCs +- Channel balance probing attacks + +Circuit Breaker offers insights into HTLC traffic and provides configurable operating modes to handle excess traffic. + +## Usage +In your Python virtual environment with Warnet installed and set up, create a new Warnet user folder: + +``` +$ warnet new user_folder +$ cd user_folder +``` + +Deploy a network with Circuit Breaker enabled: + +``` +$ warnet deploy networks/circuitbreaker +``` + +## Configuration in `network.yaml` +You can incorporate the Circuit Breaker plugin into your `network.yaml` file as shown below: + +```yaml +nodes: + - name: tank-0000 + addnode: + - tank-0001 + ln: + lnd: true + + - name: tank-0001 + addnode: + - tank-0002 + ln: + lnd: true + + - name: tank-0002 + addnode: + - tank-0000 + ln: + lnd: true + + - name: tank-0003 + addnode: + - tank-0000 + ln: + lnd: true + lnd: + channels: + - id: + block: 300 + index: 1 + target: tank-0004-ln + capacity: 100000 + push_amt: 50000 + +plugins: + postDeploy: + circuitbreaker: + entrypoint: "../../plugins/circuitbreaker" + nodes: ["tank-0000-ln", "tank-0003-ln"] # Nodes to apply Circuit Breaker to + mode: "fail" # Operating mode: fail, queue, or queue_peer_initiated + maxPendingHtlcs: 10 # Default maximum pending HTLCs per peer + rateLimit: 1 # Minimum seconds between HTLCs (token bucket rate limit) +``` + +## Plugin Parameters + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `nodes` | List of LN node names to apply Circuit Breaker to | Required | +| `mode` | Operating mode (`fail`, `queue`, or `queue_peer_initiated`) | `fail` | +| `maxPendingHtlcs` | Default maximum number of pending HTLCs per peer | `30` | +| `rateLimit` | Minimum interval in seconds between HTLCs | `0` (disabled) | +| `port` | Port to expose the Circuit Breaker UI on | `9235` | +| `trusted_peers` | Map of node pubkeys to their individual HTLC limits | `{}` | + +## Operating Modes + +- **fail**: Fail HTLCs when limits are exceeded. Minimizes liquidity lock-up but affects routing reputation. +- **queue**: Queue HTLCs when limits are exceeded, forwarding them when space becomes available. Penalizes upstream nodes for bad traffic. +- **queue_peer_initiated**: Queue only HTLCs from channels that the remote node initiated. Uses fail mode for channels we initiated. + +**WARNING**: Queue modes require LND 0.16+ with auto-fail support to prevent force-closes. + +## Accessing the UI + +After deploying, you can port-forward to access the Circuit Breaker UI: + +``` +$ kubectl port-forward pod/circuitbreaker-tank-0000 9235:9235 +``` + +Then open http://127.0.0.1:9235 in a browser to view and configure Circuit Breaker settings. + +## Advanced Configuration Example + +```yaml +plugins: + postDeploy: + circuitbreaker: + entrypoint: "../../plugins/circuitbreaker" + nodes: ["tank-0000-ln", "tank-0003-ln"] + mode: "fail" + maxPendingHtlcs: 15 + rateLimit: 0.5 + trusted_peers: { + "03abcdef...": 50, + "02123456...": 100 + } +``` + + + +## Limitations + +- Circuit Breaker is alpha quality software. Use with caution, especially on mainnet. +- LND interfaces are not optimized for this purpose, which may lead to edge cases. +- Queue modes require LND 0.16+ to prevent channel force-closes. + +## Development + +To build your own version of the Circuit Breaker plugin: + +1. Clone the Circuit Breaker repository: `git clone https://github.com/lightningequipment/circuitbreaker.git` +2. Follow the build instructions in the repository +3. Update the plugin's `values.yaml` to point to your custom image \ No newline at end of file From f0ff2a36c1d4cd15f0e139b56af0369e94800e30 Mon Sep 17 00:00:00 2001 From: Camillarhi Date: Thu, 27 Feb 2025 02:42:49 +0100 Subject: [PATCH 2/7] circuit breaker plugin skeleton setup --- .../charts/circuitbreaker/.helmignore | 23 ++++ .../charts/circuitbreaker/Chart.yaml | 5 + .../circuitbreaker/templates/_helpers.tpl | 7 ++ .../charts/circuitbreaker/templates/pod.yaml | 17 +++ .../charts/circuitbreaker/values.yaml | 5 + resources/plugins/circuitbreaker/plugin.py | 117 ++++++++++++++++++ 6 files changed, 174 insertions(+) create mode 100644 resources/plugins/circuitbreaker/charts/circuitbreaker/.helmignore create mode 100644 resources/plugins/circuitbreaker/charts/circuitbreaker/Chart.yaml create mode 100644 resources/plugins/circuitbreaker/charts/circuitbreaker/templates/_helpers.tpl create mode 100644 resources/plugins/circuitbreaker/charts/circuitbreaker/templates/pod.yaml create mode 100644 resources/plugins/circuitbreaker/charts/circuitbreaker/values.yaml create mode 100644 resources/plugins/circuitbreaker/plugin.py diff --git a/resources/plugins/circuitbreaker/charts/circuitbreaker/.helmignore b/resources/plugins/circuitbreaker/charts/circuitbreaker/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/resources/plugins/circuitbreaker/charts/circuitbreaker/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/resources/plugins/circuitbreaker/charts/circuitbreaker/Chart.yaml b/resources/plugins/circuitbreaker/charts/circuitbreaker/Chart.yaml new file mode 100644 index 000000000..ab03ed1a3 --- /dev/null +++ b/resources/plugins/circuitbreaker/charts/circuitbreaker/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v2 +name: circuitbreaker +description: A Helm chart to deploy Circuit Breaker +version: 0.1.0 +appVersion: "0.1.0" diff --git a/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/_helpers.tpl b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/_helpers.tpl new file mode 100644 index 000000000..a699083e5 --- /dev/null +++ b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/_helpers.tpl @@ -0,0 +1,7 @@ +{{- define "mychart.name" -}} +{{- .Chart.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "mychart.fullname" -}} +{{- printf "%s-%s" (include "mychart.name" .) .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} diff --git a/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/pod.yaml b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/pod.yaml new file mode 100644 index 000000000..a15a891f2 --- /dev/null +++ b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/pod.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{ include "mychart.fullname" . }} + labels: + app: {{ include "mychart.name" . }} + mission: {{ .Values.name }} +spec: + containers: + - name: {{ .Values.name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: ["sh", "-c"] + args: + - echo "Hello {{ .Values.mode }}"; + resources: {} + \ No newline at end of file diff --git a/resources/plugins/circuitbreaker/charts/circuitbreaker/values.yaml b/resources/plugins/circuitbreaker/charts/circuitbreaker/values.yaml new file mode 100644 index 000000000..a439d37f7 --- /dev/null +++ b/resources/plugins/circuitbreaker/charts/circuitbreaker/values.yaml @@ -0,0 +1,5 @@ +name: "circuitbreaker" +image: + repository: "camillarhi/circuitbreaker" + tag: "0.2.3" + pullPolicy: IfNotPresent \ No newline at end of file diff --git a/resources/plugins/circuitbreaker/plugin.py b/resources/plugins/circuitbreaker/plugin.py new file mode 100644 index 000000000..07cb3bef1 --- /dev/null +++ b/resources/plugins/circuitbreaker/plugin.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +import json +import logging +from enum import Enum +from pathlib import Path +import time +from typing import Optional + +import click + +from warnet.constants import PLUGIN_ANNEX, AnnexMember, HookValue, WarnetContent +from warnet.process import run_command + +MISSION = "circuitbreaker" +PRIMARY_CONTAINER = MISSION + +PLUGIN_DIR_TAG = "plugin_dir" + + +class PluginError(Exception): + pass + + +log = logging.getLogger(MISSION) +if not log.hasHandlers(): + console_handler = logging.StreamHandler() + console_handler.setLevel(logging.DEBUG) + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") + console_handler.setFormatter(formatter) + log.addHandler(console_handler) +log.setLevel(logging.DEBUG) +log.propagate = True + +class PluginContent(Enum): + MODE = "mode" + MAX_PENDING_HTLCS = "maxPendingHtlcs" + RATE_LIMIT = "rateLimit" + +@click.group() +@click.pass_context +def circuitbreaker(ctx): + """Commands for the Circuit Breaker plugin""" + ctx.ensure_object(dict) + plugin_dir = Path(__file__).resolve().parent + ctx.obj[PLUGIN_DIR_TAG] = Path(plugin_dir) + + +@circuitbreaker.command() +@click.argument("plugin_content", type=str) +@click.argument("warnet_content", type=str) +@click.pass_context +def entrypoint(ctx, plugin_content: str, warnet_content: str): + """Plugin entrypoint""" + plugin_content: dict = json.loads(plugin_content) + warnet_content: dict = json.loads(warnet_content) + + hook_value = warnet_content.get(WarnetContent.HOOK_VALUE.value) + + assert hook_value in { + item.value for item in HookValue + }, f"{hook_value} is not a valid HookValue" + + if warnet_content.get(PLUGIN_ANNEX): + for annex_member in [annex_item for annex_item in warnet_content.get(PLUGIN_ANNEX)]: + assert annex_member in { + item.value for item in AnnexMember + }, f"{annex_member} is not a valid AnnexMember" + + warnet_content[WarnetContent.HOOK_VALUE.value] = HookValue(hook_value) + + _entrypoint(ctx, plugin_content, warnet_content) + + +def _entrypoint(ctx, plugin_content: dict, warnet_content: dict): + """Called by entrypoint""" + hook_value = warnet_content[WarnetContent.HOOK_VALUE.value] + + match hook_value: + case ( + HookValue.PRE_NETWORK + | HookValue.POST_NETWORK + | HookValue.PRE_DEPLOY + | HookValue.POST_DEPLOY + ): + data = get_data(plugin_content) + if data: + _launch_circuit_breaker(ctx, node_name=hook_value.value.lower()) + else: + _launch_circuit_breaker(ctx, node_name=hook_value.value.lower()) + case HookValue.PRE_NODE: + name = warnet_content[PLUGIN_ANNEX][AnnexMember.NODE_NAME.value] + "-pre-pod" + _launch_circuit_breaker(ctx, node_name=hook_value.value.lower() + "-" + name) + case HookValue.POST_NODE: + name = warnet_content[PLUGIN_ANNEX][AnnexMember.NODE_NAME.value] + "-post-pod" + _launch_circuit_breaker(ctx, node_name=hook_value.value.lower() + "-" + name) + +def get_data(plugin_content: dict) -> Optional[dict]: + data = { + key: plugin_content.get(key) + for key in (PluginContent.MAX_PENDING_HTLCS.value, PluginContent.RATE_LIMIT.value) + if plugin_content.get(key) + } + return data or None + + +def _launch_circuit_breaker(ctx, node_name: str): + timestamp = int(time.time()) + release_name = f"cb-{node_name}-{timestamp}" + + command = f"helm upgrade --install {node_name} {ctx.obj[PLUGIN_DIR_TAG]}/charts/circuitbreaker --set node={node_name}" + + log.info(command) + log.info(run_command(command)) + + +if __name__ == "__main__": + circuitbreaker() From 4f4bde88dbfefa0f27eebcbd19ae5b912b6eedb5 Mon Sep 17 00:00:00 2001 From: Camillarhi Date: Thu, 27 Feb 2025 23:45:48 +0100 Subject: [PATCH 3/7] Circuit breaker plugin integration --- resources/networks/hello/network.yaml | 42 +++++++++++++++++++ .../circuitbreaker/templates/_helpers.tpl | 7 ---- .../charts/circuitbreaker/templates/pod.yaml | 9 ++-- .../charts/circuitbreaker/values.yaml | 2 +- resources/plugins/circuitbreaker/plugin.py | 33 +++++++++++---- 5 files changed, 71 insertions(+), 22 deletions(-) delete mode 100644 resources/plugins/circuitbreaker/charts/circuitbreaker/templates/_helpers.tpl diff --git a/resources/networks/hello/network.yaml b/resources/networks/hello/network.yaml index f5acf0a83..359dc1bfe 100644 --- a/resources/networks/hello/network.yaml +++ b/resources/networks/hello/network.yaml @@ -59,6 +59,13 @@ plugins: # Each plugin section has a number of hooks available (preDeploy, post entrypoint: "../../plugins/hello" # This entrypoint path is relative to the network.yaml file podName: "hello-pre-deploy" helloTo: "preDeploy!" + circuitbreaker: + entrypoint: "../../plugins/circuitbreaker" + url: "http://127.0.0.1:9235" + apiUrl: "/api" + mode: "fail" # Operating mode: fail, queue, or queue_peer_initiated + maxPendingHtlcs: 10 # Default maximum pending HTLCs per peer + rateLimit: 1 # Minimum seconds between HTLCs (token bucket rate limit) postDeploy: hello: entrypoint: "../../plugins/hello" @@ -67,21 +74,56 @@ plugins: # Each plugin section has a number of hooks available (preDeploy, post simln: # You can have multiple plugins per hook entrypoint: "../../plugins/simln" activity: '[{"source": "tank-0003-ln", "destination": "tank-0005-ln", "interval_secs": 1, "amount_msat": 2000}]' + circuitbreaker: + entrypoint: "../../plugins/circuitbreaker" + url: "http://127.0.0.1:9235" + apiUrl: "/api" + mode: "fail" # Operating mode: fail, queue, or queue_peer_initiated + maxPendingHtlcs: 10 # Default maximum pending HTLCs per peer + rateLimit: 1 # Minimum seconds between HTLCs (token bucket rate limit) preNode: # preNode plugins run before each node is deployed hello: entrypoint: "../../plugins/hello" helloTo: "preNode!" + circuitbreaker: + entrypoint: "../../plugins/circuitbreaker" + url: "http://127.0.0.1:9235" + apiUrl: "/api" + mode: "fail" # Operating mode: fail, queue, or queue_peer_initiated + maxPendingHtlcs: 10 # Default maximum pending HTLCs per peer + rateLimit: 1 # Minimum seconds between HTLCs (token bucket rate limit) postNode: hello: entrypoint: "../../plugins/hello" helloTo: "postNode!" + circuitbreaker: + entrypoint: "../../plugins/circuitbreaker" + url: "http://127.0.0.1:9235" + apiUrl: "/api" + mode: "fail" # Operating mode: fail, queue, or queue_peer_initiated + maxPendingHtlcs: 10 # Default maximum pending HTLCs per peer + rateLimit: 1 # Minimum seconds between HTLCs (token bucket rate limit) preNetwork: hello: entrypoint: "../../plugins/hello" helloTo: "preNetwork!" podName: "hello-pre-network" + circuitbreaker: + entrypoint: "../../plugins/circuitbreaker" + url: "http://127.0.0.1:9235" + apiUrl: "/api" + mode: "fail" # Operating mode: fail, queue, or queue_peer_initiated + maxPendingHtlcs: 10 # Default maximum pending HTLCs per peer + rateLimit: 1 # Minimum seconds between HTLCs (token bucket rate limit) postNetwork: hello: entrypoint: "../../plugins/hello" helloTo: "postNetwork!" podName: "hello-post-network" + circuitbreaker: + entrypoint: "../../plugins/circuitbreaker" + url: "http://127.0.0.1:9235" + apiUrl: "/api" + mode: "fail" # Operating mode: fail, queue, or queue_peer_initiated + maxPendingHtlcs: 10 # Default maximum pending HTLCs per peer + rateLimit: 1 # Minimum seconds between HTLCs (token bucket rate limit) diff --git a/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/_helpers.tpl b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/_helpers.tpl deleted file mode 100644 index a699083e5..000000000 --- a/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/_helpers.tpl +++ /dev/null @@ -1,7 +0,0 @@ -{{- define "mychart.name" -}} -{{- .Chart.Name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{- define "mychart.fullname" -}} -{{- printf "%s-%s" (include "mychart.name" .) .Release.Name | trunc 63 | trimSuffix "-" -}} -{{- end -}} diff --git a/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/pod.yaml b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/pod.yaml index a15a891f2..534a2b528 100644 --- a/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/pod.yaml +++ b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/pod.yaml @@ -1,17 +1,16 @@ apiVersion: v1 kind: Pod metadata: - name: {{ include "mychart.fullname" . }} + name: {{ .Values.name }} labels: - app: {{ include "mychart.name" . }} - mission: {{ .Values.name }} + app: {{ .Chart.Name }} spec: containers: - - name: {{ .Values.name }} + - name: {{ .Values.name }}-container image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} command: ["sh", "-c"] args: - - echo "Hello {{ .Values.mode }}"; + - echo "Hello {{ .Values.name }}"; resources: {} \ No newline at end of file diff --git a/resources/plugins/circuitbreaker/charts/circuitbreaker/values.yaml b/resources/plugins/circuitbreaker/charts/circuitbreaker/values.yaml index a439d37f7..7acc1a6a1 100644 --- a/resources/plugins/circuitbreaker/charts/circuitbreaker/values.yaml +++ b/resources/plugins/circuitbreaker/charts/circuitbreaker/values.yaml @@ -1,5 +1,5 @@ name: "circuitbreaker" image: repository: "camillarhi/circuitbreaker" - tag: "0.2.3" + tag: "latest" pullPolicy: IfNotPresent \ No newline at end of file diff --git a/resources/plugins/circuitbreaker/plugin.py b/resources/plugins/circuitbreaker/plugin.py index 07cb3bef1..9667a7e87 100644 --- a/resources/plugins/circuitbreaker/plugin.py +++ b/resources/plugins/circuitbreaker/plugin.py @@ -11,6 +11,15 @@ from warnet.constants import PLUGIN_ANNEX, AnnexMember, HookValue, WarnetContent from warnet.process import run_command +from warnet.k8s import ( + download, + get_default_namespace, + get_mission, + get_static_client, + wait_for_init, + write_file_to_container, +) + MISSION = "circuitbreaker" PRIMARY_CONTAINER = MISSION @@ -84,15 +93,15 @@ def _entrypoint(ctx, plugin_content: dict, warnet_content: dict): ): data = get_data(plugin_content) if data: - _launch_circuit_breaker(ctx, node_name=hook_value.value.lower()) + _launch_circuit_breaker(ctx, node_name=hook_value.value.lower()+"breaker",hook_value=hook_value.value) else: - _launch_circuit_breaker(ctx, node_name=hook_value.value.lower()) + _launch_circuit_breaker(ctx, node_name=hook_value.value.lower()+"breaker",hook_value=hook_value.value) case HookValue.PRE_NODE: name = warnet_content[PLUGIN_ANNEX][AnnexMember.NODE_NAME.value] + "-pre-pod" - _launch_circuit_breaker(ctx, node_name=hook_value.value.lower() + "-" + name) + _launch_circuit_breaker(ctx, node_name=hook_value.value.lower() + "-" + name, hook_value=hook_value.value) case HookValue.POST_NODE: name = warnet_content[PLUGIN_ANNEX][AnnexMember.NODE_NAME.value] + "-post-pod" - _launch_circuit_breaker(ctx, node_name=hook_value.value.lower() + "-" + name) + _launch_circuit_breaker(ctx, node_name=hook_value.value.lower() + "-" + name, hook_value=hook_value.value) def get_data(plugin_content: dict) -> Optional[dict]: data = { @@ -103,14 +112,20 @@ def get_data(plugin_content: dict) -> Optional[dict]: return data or None -def _launch_circuit_breaker(ctx, node_name: str): +def _launch_circuit_breaker(ctx, node_name: str, hook_value: str): timestamp = int(time.time()) - release_name = f"cb-{node_name}-{timestamp}" - - command = f"helm upgrade --install {node_name} {ctx.obj[PLUGIN_DIR_TAG]}/charts/circuitbreaker --set node={node_name}" + release_name = f"cb-{node_name}" + # command = f"helm upgrade --install {release_name} {ctx.obj[PLUGIN_DIR_TAG]}/charts/circuitbreaker" + command = ( + f"helm upgrade --install {release_name} {ctx.obj[PLUGIN_DIR_TAG]}/charts/circuitbreaker " + f"--set name={release_name}" + ) log.info(command) - log.info(run_command(command)) + run_command(command) + + if(hook_value==HookValue.POST_DEPLOY): + wait_for_init(release_name, namespace=get_default_namespace(), quiet=True) if __name__ == "__main__": From a114d8991d84f1937f5a1708c1dfcd896031e5e7 Mon Sep 17 00:00:00 2001 From: Camillarhi Date: Mon, 3 Mar 2025 00:31:05 +0100 Subject: [PATCH 4/7] circuit breaker setup --- resources/networks/hello/network.yaml | 45 +---- .../charts/circuitbreaker/Chart.yaml | 2 +- .../circuitbreaker/templates/deployment.yaml | 36 ++++ .../charts/circuitbreaker/templates/pod.yaml | 16 -- .../circuitbreaker/templates/service.yaml | 11 ++ .../charts/circuitbreaker/values.yaml | 4 +- resources/plugins/circuitbreaker/plugin.py | 159 ++++++++++++++---- 7 files changed, 181 insertions(+), 92 deletions(-) create mode 100644 resources/plugins/circuitbreaker/charts/circuitbreaker/templates/deployment.yaml delete mode 100644 resources/plugins/circuitbreaker/charts/circuitbreaker/templates/pod.yaml create mode 100644 resources/plugins/circuitbreaker/charts/circuitbreaker/templates/service.yaml diff --git a/resources/networks/hello/network.yaml b/resources/networks/hello/network.yaml index 359dc1bfe..92c3af386 100644 --- a/resources/networks/hello/network.yaml +++ b/resources/networks/hello/network.yaml @@ -59,13 +59,6 @@ plugins: # Each plugin section has a number of hooks available (preDeploy, post entrypoint: "../../plugins/hello" # This entrypoint path is relative to the network.yaml file podName: "hello-pre-deploy" helloTo: "preDeploy!" - circuitbreaker: - entrypoint: "../../plugins/circuitbreaker" - url: "http://127.0.0.1:9235" - apiUrl: "/api" - mode: "fail" # Operating mode: fail, queue, or queue_peer_initiated - maxPendingHtlcs: 10 # Default maximum pending HTLCs per peer - rateLimit: 1 # Minimum seconds between HTLCs (token bucket rate limit) postDeploy: hello: entrypoint: "../../plugins/hello" @@ -76,54 +69,24 @@ plugins: # Each plugin section has a number of hooks available (preDeploy, post activity: '[{"source": "tank-0003-ln", "destination": "tank-0005-ln", "interval_secs": 1, "amount_msat": 2000}]' circuitbreaker: entrypoint: "../../plugins/circuitbreaker" - url: "http://127.0.0.1:9235" - apiUrl: "/api" - mode: "fail" # Operating mode: fail, queue, or queue_peer_initiated - maxPendingHtlcs: 10 # Default maximum pending HTLCs per peer - rateLimit: 1 # Minimum seconds between HTLCs (token bucket rate limit) + podName: "circuitbreaker-pod" + rpcserver: "tank-0000-ln:10009" + httplisten: "0.0.0.0:9235" preNode: # preNode plugins run before each node is deployed hello: entrypoint: "../../plugins/hello" helloTo: "preNode!" - circuitbreaker: - entrypoint: "../../plugins/circuitbreaker" - url: "http://127.0.0.1:9235" - apiUrl: "/api" - mode: "fail" # Operating mode: fail, queue, or queue_peer_initiated - maxPendingHtlcs: 10 # Default maximum pending HTLCs per peer - rateLimit: 1 # Minimum seconds between HTLCs (token bucket rate limit) postNode: hello: entrypoint: "../../plugins/hello" helloTo: "postNode!" - circuitbreaker: - entrypoint: "../../plugins/circuitbreaker" - url: "http://127.0.0.1:9235" - apiUrl: "/api" - mode: "fail" # Operating mode: fail, queue, or queue_peer_initiated - maxPendingHtlcs: 10 # Default maximum pending HTLCs per peer - rateLimit: 1 # Minimum seconds between HTLCs (token bucket rate limit) preNetwork: hello: entrypoint: "../../plugins/hello" helloTo: "preNetwork!" podName: "hello-pre-network" - circuitbreaker: - entrypoint: "../../plugins/circuitbreaker" - url: "http://127.0.0.1:9235" - apiUrl: "/api" - mode: "fail" # Operating mode: fail, queue, or queue_peer_initiated - maxPendingHtlcs: 10 # Default maximum pending HTLCs per peer - rateLimit: 1 # Minimum seconds between HTLCs (token bucket rate limit) postNetwork: hello: entrypoint: "../../plugins/hello" helloTo: "postNetwork!" - podName: "hello-post-network" - circuitbreaker: - entrypoint: "../../plugins/circuitbreaker" - url: "http://127.0.0.1:9235" - apiUrl: "/api" - mode: "fail" # Operating mode: fail, queue, or queue_peer_initiated - maxPendingHtlcs: 10 # Default maximum pending HTLCs per peer - rateLimit: 1 # Minimum seconds between HTLCs (token bucket rate limit) + podName: "hello-post-network" \ No newline at end of file diff --git a/resources/plugins/circuitbreaker/charts/circuitbreaker/Chart.yaml b/resources/plugins/circuitbreaker/charts/circuitbreaker/Chart.yaml index ab03ed1a3..6cb99f1b8 100644 --- a/resources/plugins/circuitbreaker/charts/circuitbreaker/Chart.yaml +++ b/resources/plugins/circuitbreaker/charts/circuitbreaker/Chart.yaml @@ -2,4 +2,4 @@ apiVersion: v2 name: circuitbreaker description: A Helm chart to deploy Circuit Breaker version: 0.1.0 -appVersion: "0.1.0" +appVersion: "0.1.0" \ No newline at end of file diff --git a/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/deployment.yaml b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/deployment.yaml new file mode 100644 index 000000000..865fb3b47 --- /dev/null +++ b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/deployment.yaml @@ -0,0 +1,36 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.podName }} +spec: + replicas: 1 + selector: + matchLabels: + app: circuitbreaker + template: + metadata: + labels: + app: circuitbreaker + spec: + containers: + - name: circuitbreaker + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + args: + - "--rpcserver={{ .Values.rpcserver }}" + - "--httplisten={{ .Values.httplisten }}" + ports: + - containerPort: 9235 + volumeMounts: + - name: lnd-tls-cert + mountPath: /root/.lnd/tls.cert + subPath: tls.cert + - name: lnd-macaroon + mountPath: /root/.lnd/data/chain/bitcoin/regtest/admin.macaroon + subPath: admin.macaroon + volumes: + - name: lnd-tls-cert + secret: + secretName: lnd-tls-cert-{{ .Values.podName }} + - name: lnd-macaroon + secret: + secretName: lnd-macaroon-{{ .Values.podName }} \ No newline at end of file diff --git a/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/pod.yaml b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/pod.yaml deleted file mode 100644 index 534a2b528..000000000 --- a/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/pod.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: {{ .Values.name }} - labels: - app: {{ .Chart.Name }} -spec: - containers: - - name: {{ .Values.name }}-container - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - command: ["sh", "-c"] - args: - - echo "Hello {{ .Values.name }}"; - resources: {} - \ No newline at end of file diff --git a/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/service.yaml b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/service.yaml new file mode 100644 index 000000000..45ddc5397 --- /dev/null +++ b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.podName }} +spec: + selector: + app: circuitbreaker + ports: + - protocol: TCP + port: 9235 + targetPort: 9235 \ No newline at end of file diff --git a/resources/plugins/circuitbreaker/charts/circuitbreaker/values.yaml b/resources/plugins/circuitbreaker/charts/circuitbreaker/values.yaml index 7acc1a6a1..f9c66f7e8 100644 --- a/resources/plugins/circuitbreaker/charts/circuitbreaker/values.yaml +++ b/resources/plugins/circuitbreaker/charts/circuitbreaker/values.yaml @@ -1,4 +1,6 @@ -name: "circuitbreaker" +podName: "circuitbreaker-pod" +rpcserver: "localhost:10009" +httplisten: "0.0.0.0:9235" image: repository: "camillarhi/circuitbreaker" tag: "latest" diff --git a/resources/plugins/circuitbreaker/plugin.py b/resources/plugins/circuitbreaker/plugin.py index 9667a7e87..3de4147b9 100644 --- a/resources/plugins/circuitbreaker/plugin.py +++ b/resources/plugins/circuitbreaker/plugin.py @@ -3,6 +3,7 @@ import logging from enum import Enum from pathlib import Path +import subprocess import time from typing import Optional @@ -25,11 +26,9 @@ PLUGIN_DIR_TAG = "plugin_dir" - class PluginError(Exception): pass - log = logging.getLogger(MISSION) if not log.hasHandlers(): console_handler = logging.StreamHandler() @@ -41,9 +40,9 @@ class PluginError(Exception): log.propagate = True class PluginContent(Enum): - MODE = "mode" - MAX_PENDING_HTLCS = "maxPendingHtlcs" - RATE_LIMIT = "rateLimit" + POD_NAME = "podName" + LND_RPC_SERVER = "rpcserver" + HTTP_LISTEN = "httplisten" @click.group() @click.pass_context @@ -85,47 +84,141 @@ def _entrypoint(ctx, plugin_content: dict, warnet_content: dict): hook_value = warnet_content[WarnetContent.HOOK_VALUE.value] match hook_value: - case ( - HookValue.PRE_NETWORK - | HookValue.POST_NETWORK - | HookValue.PRE_DEPLOY - | HookValue.POST_DEPLOY - ): - data = get_data(plugin_content) - if data: - _launch_circuit_breaker(ctx, node_name=hook_value.value.lower()+"breaker",hook_value=hook_value.value) - else: - _launch_circuit_breaker(ctx, node_name=hook_value.value.lower()+"breaker",hook_value=hook_value.value) - case HookValue.PRE_NODE: - name = warnet_content[PLUGIN_ANNEX][AnnexMember.NODE_NAME.value] + "-pre-pod" - _launch_circuit_breaker(ctx, node_name=hook_value.value.lower() + "-" + name, hook_value=hook_value.value) - case HookValue.POST_NODE: - name = warnet_content[PLUGIN_ANNEX][AnnexMember.NODE_NAME.value] + "-post-pod" - _launch_circuit_breaker(ctx, node_name=hook_value.value.lower() + "-" + name, hook_value=hook_value.value) + case HookValue.POST_DEPLOY: + # data = get_data(plugin_content) + # if data: + # log.info(f"Launching circuit breaker with data: {data}") + # _create_secrets() + _launch_circuit_breaker(ctx, plugin_content) + # else: + # _launch_circuit_breaker(ctx, install_name="circuitbreaker") + case _: + log.info(f"No action required for hook {hook_value}") def get_data(plugin_content: dict) -> Optional[dict]: data = { key: plugin_content.get(key) - for key in (PluginContent.MAX_PENDING_HTLCS.value, PluginContent.RATE_LIMIT.value) + for key in (PluginContent.POD_NAME.value, PluginContent.LND_RPC_SERVER.value, PluginContent.HTTP_LISTEN.value) if plugin_content.get(key) } return data or None +# def _create_secrets(): +# """Use local LND files for testing""" +# log.info("Using local LND files for testing") +# tls_cert_path = Path.home() / ".lnd" / "tls.cert" +# admin_macaroon_path = Path.home() / ".lnd" / "data" / "chain" / "bitcoin" / "signet" / "admin.macaroon" -def _launch_circuit_breaker(ctx, node_name: str, hook_value: str): +# if not tls_cert_path.exists(): +# raise PluginError(f"TLS certificate not found at {tls_cert_path}") +# if not admin_macaroon_path.exists(): +# raise PluginError(f"Admin macaroon not found at {admin_macaroon_path}") + +# log.info(f"Using TLS certificate: {tls_cert_path}") +# log.info(f"Using admin macaroon: {admin_macaroon_path}") + +# def _create_secrets(): +# """Create Kubernetes secrets for each LND node""" +# lnd_pods = subprocess.check_output(["kubectl", "get", "pods", "-l", "mission=lightning", "-o", "name"]).decode().splitlines() +# # lnd_pods = subprocess.check_output(["kubectl", "get", "pods", "-l", "app=warnet", "-l", "mission=lightning", "-o", "name"]).decode().splitlines() +# for node in lnd_pods: +# node_name = node.split('/')[-1] +# log.info(f"Waiting for {node_name} to be ready...") +# wait_for_init(node_name, namespace=get_default_namespace(), quiet=True) +# log.info(f"Creating secrets for {node_name}") +# subprocess.run(["kubectl", "cp", f"{node}:/root/.lnd/tls.cert", "./tls.cert"], check=True) +# subprocess.run(["kubectl", "cp", f"{node}:/root/.lnd/data/chain/bitcoin/regtest/admin.macaroon", "./admin.macaroon"], check=True) +# subprocess.run(["kubectl", "create", "secret", "generic", f"lnd-tls-cert-{node_name}", "--from-file=tls.cert=./tls.cert"], check=True) +# subprocess.run(["kubectl", "create", "secret", "generic", f"lnd-macaroon-{node_name}", "--from-file=admin.macaroon=./admin.macaroon"], check=True) + +def _create_secrets(): + """Create Kubernetes secrets for each LND node""" + lnd_pods = subprocess.check_output( + ["kubectl", "get", "pods", "-l", "mission=lightning", "-o", "name"] + ).decode().splitlines() + + for node in lnd_pods: + node_name = node.split('/')[-1] + log.info(f"Waiting for {node_name} to be ready...") + + # Wait for the pod to be ready + max_retries = 10 + retry_delay = 10 # seconds + for attempt in range(max_retries): + try: + # Check if the pod is ready + pod_status = subprocess.check_output( + ["kubectl", "get", "pod", node_name, "-o", "jsonpath='{.status.phase}'"] + ).decode().strip("'") + + if pod_status == "Running": + log.info(f"{node_name} is ready.") + break + else: + log.info(f"{node_name} is not ready yet (status: {pod_status}). Retrying in {retry_delay} seconds...") + except subprocess.CalledProcessError as e: + log.error(f"Failed to check pod status for {node_name}: {e}") + if attempt == max_retries - 1: + raise PluginError(f"Pod {node_name} did not become ready after {max_retries} attempts.") + + time.sleep(retry_delay) + + # Create secrets for the pod + log.info(f"Creating secrets for {node_name}") + try: + subprocess.run( + ["kubectl", "cp", f"{node_name}:/root/.lnd/tls.cert", "./tls.cert"], + check=True + ) + subprocess.run( + ["kubectl", "cp", f"{node_name}:/root/.lnd/data/chain/bitcoin/regtest/admin.macaroon", "./admin.macaroon"], + check=True + ) + subprocess.run( + ["kubectl", "create", "secret", "generic", f"lnd-tls-cert-{node_name}", "--from-file=tls.cert=./tls.cert"], + check=True + ) + subprocess.run( + ["kubectl", "create", "secret", "generic", f"lnd-macaroon-{node_name}", "--from-file=admin.macaroon=./admin.macaroon"], + check=True + ) + except subprocess.CalledProcessError as e: + log.error(f"Failed to create secrets for {node_name}: {e}") + raise PluginError(f"Failed to create secrets for {node_name}.") + +def _launch_circuit_breaker(ctx, + plugin_content: dict, + install_name: str="circuitbreaker", + podName: str ="circuitbreaker-pod", + rpcserver: str = "localhost:10009", + httplisten: str = "0.0.0.0:9235"): timestamp = int(time.time()) - release_name = f"cb-{node_name}" + # release_name = f"cb-{install_name}" + lnd_pods = subprocess.check_output(["kubectl", "get", "pods", "-l", "app=warnet", "-l", "mission=lightning", "-o", "name"]).decode().splitlines() + for node in lnd_pods: + node_name = node.split('/')[-1] + log.info(f"Launching Circuit Breaker for {node_name}") + release_name = f"circuitbreaker-{node_name}" + + command = ( + f"helm upgrade --install {release_name} {ctx.obj[PLUGIN_DIR_TAG]}/charts/circuitbreaker " + f"--set podName={release_name} --set rpcserver=localhost:10009 --set httplisten=0.0.0.0:9235" + ) + # command = f"helm upgrade --install {release_name} {ctx.obj[PLUGIN_DIR_TAG]}/charts/circuitbreaker" - command = ( - f"helm upgrade --install {release_name} {ctx.obj[PLUGIN_DIR_TAG]}/charts/circuitbreaker " - f"--set name={release_name}" - ) - log.info(command) - run_command(command) + # command = ( + # f"helm upgrade --install {install_name} {ctx.obj[PLUGIN_DIR_TAG]}/charts/circuitbreaker " + # f"--set podName={podName} --set rpcserver={rpcserver} --set httplisten={httplisten}" + # ) + log.info(command) + try: + run_command(command) - if(hook_value==HookValue.POST_DEPLOY): - wait_for_init(release_name, namespace=get_default_namespace(), quiet=True) + # if(hook_value==HookValue.POST_DEPLOY): + wait_for_init(release_name, namespace=get_default_namespace(), quiet=True) + except Exception as e: + log.error(f"Failed to launch Circuit Breaker for {node_name}: {e}") if __name__ == "__main__": From f01f3833f3df11780a7fbb4879ceaa23498d7345 Mon Sep 17 00:00:00 2001 From: Camillarhi Date: Mon, 3 Mar 2025 02:00:52 +0100 Subject: [PATCH 5/7] circuit breaker setup --- .../circuitbreaker/templates/_helpers.tpl | 7 +++ .../circuitbreaker/templates/configmap.yaml | 21 ++++++++ .../circuitbreaker/templates/deployment.yaml | 36 ------------- .../charts/circuitbreaker/templates/pod.yaml | 46 ++++++++++++++++ .../circuitbreaker/templates/service.yaml | 11 ---- .../charts/circuitbreaker/values.yaml | 15 ++++-- resources/plugins/circuitbreaker/plugin.py | 53 ++++++------------- 7 files changed, 101 insertions(+), 88 deletions(-) create mode 100644 resources/plugins/circuitbreaker/charts/circuitbreaker/templates/_helpers.tpl create mode 100644 resources/plugins/circuitbreaker/charts/circuitbreaker/templates/configmap.yaml delete mode 100644 resources/plugins/circuitbreaker/charts/circuitbreaker/templates/deployment.yaml create mode 100644 resources/plugins/circuitbreaker/charts/circuitbreaker/templates/pod.yaml delete mode 100644 resources/plugins/circuitbreaker/charts/circuitbreaker/templates/service.yaml diff --git a/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/_helpers.tpl b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/_helpers.tpl new file mode 100644 index 000000000..a699083e5 --- /dev/null +++ b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/_helpers.tpl @@ -0,0 +1,7 @@ +{{- define "mychart.name" -}} +{{- .Chart.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "mychart.fullname" -}} +{{- printf "%s-%s" (include "mychart.name" .) .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} diff --git a/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/configmap.yaml b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/configmap.yaml new file mode 100644 index 000000000..9688722b6 --- /dev/null +++ b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/configmap.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "mychart.fullname" . }}-data +data: + tls.cert: | + -----BEGIN CERTIFICATE----- + MIIB8TCCAZagAwIBAgIUJDsR6mmY+TaO9pCfjtotlbOkzJMwCgYIKoZIzj0EAwIw + MjEfMB0GA1UECgwWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEPMA0GA1UEAwwGd2Fy + bmV0MB4XDTI0MTExMTE2NTM1MFoXDTM0MTEwOTE2NTM1MFowMjEfMB0GA1UECgwW + bG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEPMA0GA1UEAwwGd2FybmV0MFkwEwYHKoZI + zj0CAQYIKoZIzj0DAQcDQgAEBVltIvaTlAQI/3FFatTqVflZuZdRJ0SmRMSJrFLP + tp0fxE7hmteSt6gjQriy90fP8j9OJXBNAjt915kLY4zVvqOBiTCBhjAOBgNVHQ8B + Af8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAd + BgNVHQ4EFgQU5d8QMrwhLgTkDjWA+eXZGz+dybUwLwYDVR0RBCgwJoIJbG9jYWxo + b3N0ggEqhwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMAoGCCqGSM49BAMCA0kAMEYC + IQDPofN0fEl5gTwCYhk3nZbjMqJhZ8BsSJ6K8XRhxr7zbwIhAPsgQCFOqUWg632O + NEO53OQ6CIqnpxSskjsFNH4ZBQOE + -----END CERTIFICATE----- + admin.macaroon.hex: | + 0201036c6e6402f801030a1062beabbf2a614b112128afa0c0b4fdd61201301a160a0761646472657373120472656164120577726974651a130a04696e666f120472656164120577726974651a170a08696e766f69636573120472656164120577726974651a210a086d616361726f6f6e120867656e6572617465120472656164120577726974651a160a076d657373616765120472656164120577726974651a170a086f6666636861696e120472656164120577726974651a160a076f6e636861696e120472656164120577726974651a140a057065657273120472656164120577726974651a180a067369676e6572120867656e657261746512047265616400000620b17be53e367290871681055d0de15587f6d1cd47d1248fe2662ae27f62cfbdc6 diff --git a/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/deployment.yaml b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/deployment.yaml deleted file mode 100644 index 865fb3b47..000000000 --- a/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/deployment.yaml +++ /dev/null @@ -1,36 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ .Values.podName }} -spec: - replicas: 1 - selector: - matchLabels: - app: circuitbreaker - template: - metadata: - labels: - app: circuitbreaker - spec: - containers: - - name: circuitbreaker - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - args: - - "--rpcserver={{ .Values.rpcserver }}" - - "--httplisten={{ .Values.httplisten }}" - ports: - - containerPort: 9235 - volumeMounts: - - name: lnd-tls-cert - mountPath: /root/.lnd/tls.cert - subPath: tls.cert - - name: lnd-macaroon - mountPath: /root/.lnd/data/chain/bitcoin/regtest/admin.macaroon - subPath: admin.macaroon - volumes: - - name: lnd-tls-cert - secret: - secretName: lnd-tls-cert-{{ .Values.podName }} - - name: lnd-macaroon - secret: - secretName: lnd-macaroon-{{ .Values.podName }} \ No newline at end of file diff --git a/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/pod.yaml b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/pod.yaml new file mode 100644 index 000000000..459371b33 --- /dev/null +++ b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/pod.yaml @@ -0,0 +1,46 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{ include "mychart.fullname" . }} + labels: + app: {{ include "mychart.name" . }} + mission: {{ .Values.name }} +spec: + initContainers: + - name: "init" + image: "busybox" + command: + - "sh" + - "-c" + args: + - > + mkdir -p /shared/.lnd/data/chain/bitcoin/mainnet && + cp /configmap/tls.cert /shared/.lnd/tls.cert && + cat /configmap/admin.macaroon.hex | xxd -r -p > /shared/.lnd/data/chain/bitcoin/mainnet/admin.macaroon + volumeMounts: + - name: shared-volume + mountPath: /shared + - name: configmap-volume + mountPath: /configmap + containers: + - name: {{ .Values.name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: + - "sh" + - "-c" + args: + - > + mkdir -p /root/.lnd/data/chain/bitcoin/mainnet && + ln -s /shared/.lnd/tls.cert /root/.lnd/tls.cert && + ln -s /shared/.lnd/data/chain/bitcoin/mainnet/admin.macaroon /root/.lnd/data/chain/bitcoin/mainnet/admin.macaroon && + circuitbreaker --rpcserver={{ .Values.lnd.rpcserver }} --httplisten={{ .Values.lnd.httplisten }} + volumeMounts: + - name: shared-volume + mountPath: /shared + volumes: + - name: configmap-volume + configMap: + name: {{ include "mychart.fullname" . }}-data + - name: shared-volume + emptyDir: {} \ No newline at end of file diff --git a/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/service.yaml b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/service.yaml deleted file mode 100644 index 45ddc5397..000000000 --- a/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/service.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ .Values.podName }} -spec: - selector: - app: circuitbreaker - ports: - - protocol: TCP - port: 9235 - targetPort: 9235 \ No newline at end of file diff --git a/resources/plugins/circuitbreaker/charts/circuitbreaker/values.yaml b/resources/plugins/circuitbreaker/charts/circuitbreaker/values.yaml index f9c66f7e8..e539f0010 100644 --- a/resources/plugins/circuitbreaker/charts/circuitbreaker/values.yaml +++ b/resources/plugins/circuitbreaker/charts/circuitbreaker/values.yaml @@ -1,7 +1,14 @@ -podName: "circuitbreaker-pod" -rpcserver: "localhost:10009" -httplisten: "0.0.0.0:9235" +name: "circuitbreaker" image: repository: "camillarhi/circuitbreaker" tag: "latest" - pullPolicy: IfNotPresent \ No newline at end of file + pullPolicy: IfNotPresent +workingVolume: + name: working-volume + mountPath: /working +configmapVolume: + name: configmap-volume + mountPath: /configmap +lnd: + rpcserver: "host.docker.internal:10009" # Default LND RPC server address + httplisten: "0.0.0.0:9235" # Default HTTP listen address \ No newline at end of file diff --git a/resources/plugins/circuitbreaker/plugin.py b/resources/plugins/circuitbreaker/plugin.py index 3de4147b9..6700c0b88 100644 --- a/resources/plugins/circuitbreaker/plugin.py +++ b/resources/plugins/circuitbreaker/plugin.py @@ -85,13 +85,11 @@ def _entrypoint(ctx, plugin_content: dict, warnet_content: dict): match hook_value: case HookValue.POST_DEPLOY: - # data = get_data(plugin_content) - # if data: - # log.info(f"Launching circuit breaker with data: {data}") - # _create_secrets() - _launch_circuit_breaker(ctx, plugin_content) - # else: - # _launch_circuit_breaker(ctx, install_name="circuitbreaker") + data = get_data(plugin_content) + if data: + _launch_pod(ctx, install_name="circuitbreaker", **data) + else: + _launch_pod(ctx, install_name="circuitbreaker") case _: log.info(f"No action required for hook {hook_value}") @@ -186,40 +184,21 @@ def _create_secrets(): log.error(f"Failed to create secrets for {node_name}: {e}") raise PluginError(f"Failed to create secrets for {node_name}.") -def _launch_circuit_breaker(ctx, - plugin_content: dict, - install_name: str="circuitbreaker", - podName: str ="circuitbreaker-pod", - rpcserver: str = "localhost:10009", - httplisten: str = "0.0.0.0:9235"): +def _launch_pod(ctx, + install_name: str = "circuitbreaker", + podName: str = "circuitbreaker-pod", + rpcserver: str = "localhost:10009", + httplisten: str = "0.0.0.0:9235"): timestamp = int(time.time()) # release_name = f"cb-{install_name}" - lnd_pods = subprocess.check_output(["kubectl", "get", "pods", "-l", "app=warnet", "-l", "mission=lightning", "-o", "name"]).decode().splitlines() - for node in lnd_pods: - node_name = node.split('/')[-1] - log.info(f"Launching Circuit Breaker for {node_name}") - release_name = f"circuitbreaker-{node_name}" - - command = ( - f"helm upgrade --install {release_name} {ctx.obj[PLUGIN_DIR_TAG]}/charts/circuitbreaker " - f"--set podName={release_name} --set rpcserver=localhost:10009 --set httplisten=0.0.0.0:9235" - ) - - # command = f"helm upgrade --install {release_name} {ctx.obj[PLUGIN_DIR_TAG]}/charts/circuitbreaker" - # command = ( - # f"helm upgrade --install {install_name} {ctx.obj[PLUGIN_DIR_TAG]}/charts/circuitbreaker " - # f"--set podName={podName} --set rpcserver={rpcserver} --set httplisten={httplisten}" - # ) - log.info(command) - try: - run_command(command) + command = ( + f"helm upgrade --install {install_name} {ctx.obj[PLUGIN_DIR_TAG]}/charts/circuitbreaker " + f"--set podName={podName} --set rpcserver={rpcserver} --set httplisten={httplisten}" + ) - # if(hook_value==HookValue.POST_DEPLOY): - wait_for_init(release_name, namespace=get_default_namespace(), quiet=True) - except Exception as e: - log.error(f"Failed to launch Circuit Breaker for {node_name}: {e}") - + log.info(command) + log.info(run_command(command)) if __name__ == "__main__": circuitbreaker() From da1cb41907b1482a8bf0aeff9121af4725427116 Mon Sep 17 00:00:00 2001 From: Camillarhi Date: Tue, 4 Mar 2025 00:49:25 +0100 Subject: [PATCH 6/7] connect to the local lnd node and access circuit breaker from the UI --- resources/networks/hello/network.yaml | 2 +- .../circuitbreaker/templates/configmap.yaml | 26 ++++++++++--------- .../charts/circuitbreaker/values.yaml | 2 +- resources/plugins/circuitbreaker/plugin.py | 3 +++ 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/resources/networks/hello/network.yaml b/resources/networks/hello/network.yaml index 92c3af386..75204a641 100644 --- a/resources/networks/hello/network.yaml +++ b/resources/networks/hello/network.yaml @@ -70,7 +70,7 @@ plugins: # Each plugin section has a number of hooks available (preDeploy, post circuitbreaker: entrypoint: "../../plugins/circuitbreaker" podName: "circuitbreaker-pod" - rpcserver: "tank-0000-ln:10009" + rpcserver: "172.29.34.166:10009" httplisten: "0.0.0.0:9235" preNode: # preNode plugins run before each node is deployed hello: diff --git a/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/configmap.yaml b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/configmap.yaml index 9688722b6..3216c79ea 100644 --- a/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/configmap.yaml +++ b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/configmap.yaml @@ -5,17 +5,19 @@ metadata: data: tls.cert: | -----BEGIN CERTIFICATE----- - MIIB8TCCAZagAwIBAgIUJDsR6mmY+TaO9pCfjtotlbOkzJMwCgYIKoZIzj0EAwIw - MjEfMB0GA1UECgwWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEPMA0GA1UEAwwGd2Fy - bmV0MB4XDTI0MTExMTE2NTM1MFoXDTM0MTEwOTE2NTM1MFowMjEfMB0GA1UECgwW - bG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEPMA0GA1UEAwwGd2FybmV0MFkwEwYHKoZI - zj0CAQYIKoZIzj0DAQcDQgAEBVltIvaTlAQI/3FFatTqVflZuZdRJ0SmRMSJrFLP - tp0fxE7hmteSt6gjQriy90fP8j9OJXBNAjt915kLY4zVvqOBiTCBhjAOBgNVHQ8B - Af8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAd - BgNVHQ4EFgQU5d8QMrwhLgTkDjWA+eXZGz+dybUwLwYDVR0RBCgwJoIJbG9jYWxo - b3N0ggEqhwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMAoGCCqGSM49BAMCA0kAMEYC - IQDPofN0fEl5gTwCYhk3nZbjMqJhZ8BsSJ6K8XRhxr7zbwIhAPsgQCFOqUWg632O - NEO53OQ6CIqnpxSskjsFNH4ZBQOE + MIICRTCCAeygAwIBAgIRAMe5IfFsBM9nqG1hwA1tswAwCgYIKoZIzj0EAwIwOzEf + MB0GA1UEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEYMBYGA1UEAxMPREVTS1RP + UC00OEJVR0xTMB4XDTI1MDIwMTE2NDAyNFoXDTI2MDMyOTE2NDAyNFowOzEfMB0G + A1UEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEYMBYGA1UEAxMPREVTS1RPUC00 + OEJVR0xTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIl4bWvtGVb1T4iUyjLfj + U2IVnF1yJBwbTa2diRJh+a0UbwjUSdn/hIVkNALr9f3NKYWmotyq8IGOmjwhAFis + HKOB0DCBzTAOBgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYD + VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU38wWLmz1lsVv7vtZZamSgkcoQUcwdgYD + VR0RBG8wbYIPREVTS1RPUC00OEJVR0xTgglsb2NhbGhvc3SCBHVuaXiCCnVuaXhw + YWNrZXSCB2J1ZmNvbm6HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAGHBAr///6HBKwd + IqaHEP6AAAAAAAAAAhVd//6GM+MwCgYIKoZIzj0EAwIDRwAwRAIgNe9zoH9iz7Tw + 1j8+Jk05DU6nJ48a5mbP0viZ50UGu7sCIEK0AoPBrqxnicdhEEInONWyIm5VUR/l + YURZZyNuJ8lJ -----END CERTIFICATE----- admin.macaroon.hex: | - 0201036c6e6402f801030a1062beabbf2a614b112128afa0c0b4fdd61201301a160a0761646472657373120472656164120577726974651a130a04696e666f120472656164120577726974651a170a08696e766f69636573120472656164120577726974651a210a086d616361726f6f6e120867656e6572617465120472656164120577726974651a160a076d657373616765120472656164120577726974651a170a086f6666636861696e120472656164120577726974651a160a076f6e636861696e120472656164120577726974651a140a057065657273120472656164120577726974651a180a067369676e6572120867656e657261746512047265616400000620b17be53e367290871681055d0de15587f6d1cd47d1248fe2662ae27f62cfbdc6 + 0201036C6E6402F801030A107EC4D3E96DE93FA58F70968A1729AE6C1201301A160A0761646472657373120472656164120577726974651A130A04696E666F120472656164120577726974651A170A08696E766F69636573120472656164120577726974651A210A086D616361726F6F6E120867656E6572617465120472656164120577726974651A160A076D657373616765120472656164120577726974651A170A086F6666636861696E120472656164120577726974651A160A076F6E636861696E120472656164120577726974651A140A057065657273120472656164120577726974651A180A067369676E6572120867656E65726174651204726561640000062023AFC3BF7DB1D186342905D79461793FFCB59F583858F495C253F0A1EB4D33C2 diff --git a/resources/plugins/circuitbreaker/charts/circuitbreaker/values.yaml b/resources/plugins/circuitbreaker/charts/circuitbreaker/values.yaml index e539f0010..e052a8f91 100644 --- a/resources/plugins/circuitbreaker/charts/circuitbreaker/values.yaml +++ b/resources/plugins/circuitbreaker/charts/circuitbreaker/values.yaml @@ -10,5 +10,5 @@ configmapVolume: name: configmap-volume mountPath: /configmap lnd: - rpcserver: "host.docker.internal:10009" # Default LND RPC server address + rpcserver: "172.29.34.166:10009" # Default LND RPC server address httplisten: "0.0.0.0:9235" # Default HTTP listen address \ No newline at end of file diff --git a/resources/plugins/circuitbreaker/plugin.py b/resources/plugins/circuitbreaker/plugin.py index 6700c0b88..d0111cd42 100644 --- a/resources/plugins/circuitbreaker/plugin.py +++ b/resources/plugins/circuitbreaker/plugin.py @@ -197,6 +197,9 @@ def _launch_pod(ctx, f"--set podName={podName} --set rpcserver={rpcserver} --set httplisten={httplisten}" ) + # Use this to port-forward the circuitbreaker pod to localhost + # kubectl port-forward pod/circuitbreaker-circuitbreaker 9235:9235 + log.info(command) log.info(run_command(command)) From cb50c540bd902ca4bfdbb3ff8da48fdaf6ea4f26 Mon Sep 17 00:00:00 2001 From: Camillarhi Date: Tue, 4 Mar 2025 02:02:43 +0100 Subject: [PATCH 7/7] use nodeport to expose circuit breaker UI --- .../charts/circuitbreaker/templates/service.yaml | 14 ++++++++++++++ resources/plugins/circuitbreaker/plugin.py | 3 --- 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 resources/plugins/circuitbreaker/charts/circuitbreaker/templates/service.yaml diff --git a/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/service.yaml b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/service.yaml new file mode 100644 index 000000000..42d17b602 --- /dev/null +++ b/resources/plugins/circuitbreaker/charts/circuitbreaker/templates/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "mychart.fullname" . }}-service + labels: + app: {{ include "mychart.name" . }} +spec: + type: NodePort + ports: + - port: 9235 + targetPort: 9235 + nodePort: 30000 # Choose a port between 30000-32767 + selector: + app: {{ include "mychart.name" . }} \ No newline at end of file diff --git a/resources/plugins/circuitbreaker/plugin.py b/resources/plugins/circuitbreaker/plugin.py index d0111cd42..6700c0b88 100644 --- a/resources/plugins/circuitbreaker/plugin.py +++ b/resources/plugins/circuitbreaker/plugin.py @@ -197,9 +197,6 @@ def _launch_pod(ctx, f"--set podName={podName} --set rpcserver={rpcserver} --set httplisten={httplisten}" ) - # Use this to port-forward the circuitbreaker pod to localhost - # kubectl port-forward pod/circuitbreaker-circuitbreaker 9235:9235 - log.info(command) log.info(run_command(command))