Compare commits

...
Author SHA1 Message Date
telackey 952389abb0 Add option to recreate deployments rather than update them. (#920)
cherry-pick from cerc-io/stack-orchestrator#912

Reviewed-on: cerc-io/stack-orchestrator#920
Reviewed-by: David Boreham <dboreham@noreply.git.vdb.to>
2024-08-14 20:14:40 +00:00
telackey 5c275aa622 Defensively handle errors examining app requests. (#922)
Related to cerc-io/webapp-deployment-status-api#10

There are two issues in that.  One is that the output probably changed recently, whether in the client or server, where no matching record is found by ID (Note this is specific to `laconic record get --id <v>` and does not seem to apply to the similar command to retrieve a record by name, `laconic name resolve <n>`).

Rather than returning `[]` it is now returning `[ null ]`.  This cause us to think there *was* an application record found, and we attempt to treat the `null` entry like an Application object.  That's fixed by filtering out null responses, which is a good precaution for the deployer, though I think it makes sense to ask whether this new behavior by the client/server is correct.  Seems suspicious.

The other issue is that all the defensive checks we had in place to deal with broken/bad AppDeploymentRequests were around the _build_.  This error was coming much earlier, merely when parsing and examining the request to see if it needed to be handled at all.

I have now added similar defensive error handling around that portion of the code.

Reviewed-on: cerc-io/stack-orchestrator#922
Reviewed-by: zramsay <zramsay@noreply.git.vdb.to>
Co-authored-by: Thomas E Lackey <telackey@bozemanpass.com>
Co-committed-by: Thomas E Lackey <telackey@bozemanpass.com>
2024-08-14 18:04:31 +00:00
telackey 8576137557 Convert port to string. (#919)
The str type check doesn't work if the port is a ruamel.yaml.scalarstring.SingleQuotedScalarString or ruamel.yaml.scalarstring.DoubleQuotedScalarString

Reviewed-on: cerc-io/stack-orchestrator#919
Co-authored-by: Thomas E Lackey <telackey@bozemanpass.com>
Co-committed-by: Thomas E Lackey <telackey@bozemanpass.com>
2024-08-14 00:25:35 +00:00
dboreham 65c1cdf6b1 Fix crash if port has int type in yaml (#918)
Reviewed-on: cerc-io/stack-orchestrator#918
Reviewed-by: Thomas E Lackey <telackey@noreply.git.vdb.to>
Co-authored-by: David Boreham <david@bozemanpass.com>
Co-committed-by: David Boreham <david@bozemanpass.com>
2024-08-13 20:47:09 +00:00
dboreham 265699bc38 Allow to disable kind cluster management for testing (#915)
Reviewed-on: cerc-io/stack-orchestrator#915
Co-authored-by: David Boreham <david@bozemanpass.com>
Co-committed-by: David Boreham <david@bozemanpass.com>
2024-08-13 17:48:14 +00:00
dboreham 4a7670a5d6 Open the json-rpc port (#916)
Reviewed-on: cerc-io/stack-orchestrator#916
Co-authored-by: David Boreham <david@bozemanpass.com>
Co-committed-by: David Boreham <david@bozemanpass.com>
2024-08-13 17:47:56 +00:00
telackey 6087e1cd31 Copy config under a volume for Docker (similar to a ConfigMap for K8S). (#914)
This emulates the K8S ConfigMap behavior on Docker by using a regular volume.

If a directory exists under `config/` which matches a named volume, the contents will be copied to the volume on `create` (provided the destination volume is empty).  That is, rather than a ConfigMap, it is essentially a "config volume".

For example, with a compose file like:

```
version: '3.7'
services:
  caddy:
    image: cerc/caddy-ethcache:local
    restart: always
    volumes:
      - caddyconfig:/etc/caddy:ro
volumes:
  caddyconfig:
```

And a directory:

```
❯ ls stack-orchestrator/config/caddyconfig/
Caddyfile
```

After `laconic-so deploy create --spec-file caddy.yml --deployment-dir /srv/caddy` there will be:

```
❯ ls /srv/caddy/data/caddyconfig
Caddyfile
```

Mounted at `/etc/caddy`

Reviewed-on: cerc-io/stack-orchestrator#914
Reviewed-by: David Boreham <dboreham@noreply.git.vdb.to>
Co-authored-by: Thomas E Lackey <telackey@bozemanpass.com>
Co-committed-by: Thomas E Lackey <telackey@bozemanpass.com>
2024-08-10 02:32:21 +00:00
telackey 1def279d26 Support multiple NodePorts, static NodePort mapping, and add 'replicas' spec option (#913)
NodePort example:

```
network:
  ports:
    caddy:
     - 1234
     - 32020:2020
```

Replicas example:

```
replicas: 2
```

This also adds an optimization for k8s where if a directory matching the name of a configmap exists in beneath config/ in the stack, its contents will be copied into the corresponding configmap.

For example:

```
# Config files in the stack
❯ ls stack-orchestrator/config/caddyconfig
Caddyfile  Caddyfile.one-req-per-upstream-example

# ConfigMap in the spec
❯ cat foo.yml | grep config
...
configmaps:
  caddyconfig: ./configmaps/caddyconfig

# Create the deployment
❯ laconic-so --stack ~/cerc/caddy-ethcache/stack-orchestrator/stacks/caddy-ethcache deploy create --spec-file foo.yml

# The files from beneath config/<config_map_name> have been copied to the ConfigMap directory from the spec.
❯ ls deployment-001/configmaps/caddyconfig
Caddyfile  Caddyfile.one-req-per-upstream-example
```

Reviewed-on: cerc-io/stack-orchestrator#913
Reviewed-by: David Boreham <dboreham@noreply.git.vdb.to>
Co-authored-by: Thomas E Lackey <telackey@bozemanpass.com>
Co-committed-by: Thomas E Lackey <telackey@bozemanpass.com>
2024-08-09 02:32:06 +00:00
dboreham 64691bd206 Merge pull request 'Allow gentx-files to be omitted' (#911) from dboreham/allow-zero-gentx into main
Reviewed-on: cerc-io/stack-orchestrator#911
2024-08-07 20:13:40 +00:00
dboreham aef5986135 Allow gentx-files to be omitted 2024-08-07 14:11:06 -06:00
dboreham 6f8f0340d3 Merge pull request 'Add stage 1 support' (#910) from dboreham/stage1-support into main
Reviewed-on: cerc-io/stack-orchestrator#910
2024-08-07 17:44:28 +00:00
dboreham 7590d6e237 Add stage 1 support 2024-08-07 11:28:10 -06:00
dboreham 573f99dbbe Listen on 0.0.0.0 (#909)
Reviewed-on: cerc-io/stack-orchestrator#909
Co-authored-by: David Boreham <david@bozemanpass.com>
Co-committed-by: David Boreham <david@bozemanpass.com>
2024-08-02 14:06:06 +00:00
dboreham 8052c1c25e Merge pull request 'Laconicd needs to be told its currency' (#908) from dboreham/mainnet-laconic-specify-currency into main
Reviewed-on: cerc-io/stack-orchestrator#908
2024-08-02 03:10:34 +00:00
dboreham a674d13493 Laconicd needs to be told its currency 2024-08-01 21:09:30 -06:00
roysc 0d4f4509c8 Remove Eth fixturenet workflows (#906)
Deletes the now-failing CI workflows for the old `fixturenet-eth` and `fixturenet-plugeth` stacks.

Part of cerc-io/stack-orchestrator#905.

Reviewed-on: cerc-io/stack-orchestrator#906
Reviewed-by: David Boreham <dboreham@noreply.git.vdb.to>
2024-08-01 02:28:05 +00:00
dboreham 5af27b1b3a Merge pull request 'Fix for sh as shell not bash' (#907) from dboreham/fix-script-for-ubuntu into main
Reviewed-on: cerc-io/stack-orchestrator#907
2024-07-31 20:39:37 +00:00
dboreham 6c91b87348 Fix for sh as shell not bash 2024-07-31 14:30:53 -06:00
dboreham 7d18334953 Mainnet-laconic stack fixes for laconicd2 (#904)
Reviewed-on: cerc-io/stack-orchestrator#904
2024-07-31 13:51:28 +00:00
prathamesh 79c1c5ed99 Update fixturenet-laconicd stack to use alnt denom (#902)
Part of [laconicd testnet validator enrollment](https://www.notion.so/laconicd-testnet-validator-enrollment-6fc1d3cafcc64fef8c5ed3affa27c675)

Reviewed-on: cerc-io/stack-orchestrator#902
Reviewed-by: ashwin <ashwin@noreply.git.vdb.to>
Reviewed-by: David Boreham <dboreham@noreply.git.vdb.to>
Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
Co-committed-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
2024-07-31 13:27:54 +00:00
33 changed files with 273 additions and 440 deletions
@@ -1,57 +0,0 @@
name: Fixturenet-Eth-Plugeth-Arm-Test
on:
push:
branches: '*'
paths:
- '!**'
- '.gitea/workflows/triggers/fixturenet-eth-plugeth-arm-test'
schedule: # Note: coordinate with other tests to not overload runners at the same time of day
- cron: '2 14 * * *'
jobs:
test:
name: "Run an Ethereum plugeth fixturenet test"
runs-on: ubuntu-latest-arm
steps:
- name: "Clone project repository"
uses: actions/checkout@v3
# At present the stock setup-python action fails on Linux/aarch64
# Conditional steps below workaroud this by using deadsnakes for that case only
- name: "Install Python for ARM on Linux"
if: ${{ runner.arch == 'arm64' && runner.os == 'Linux' }}
uses: deadsnakes/action@v3.0.1
with:
python-version: '3.8'
- name: "Install Python cases other than ARM on Linux"
if: ${{ ! (runner.arch == 'arm64' && runner.os == 'Linux') }}
uses: actions/setup-python@v4
with:
python-version: '3.8'
- name: "Print Python version"
run: python3 --version
- name: "Install shiv"
run: pip install shiv
- name: "Generate build version file"
run: ./scripts/create_build_tag_file.sh
- name: "Build local shiv package"
run: ./scripts/build_shiv_package.sh
- name: "Run fixturenet-eth tests"
run: ./tests/fixturenet-eth-plugeth/run-test.sh
- name: Notify Vulcanize Slack on CI failure
if: ${{ always() && github.ref_name == 'main' }}
uses: ravsamhq/notify-slack-action@v2
with:
status: ${{ job.status }}
notify_when: 'failure'
env:
SLACK_WEBHOOK_URL: ${{ secrets.VULCANIZE_SLACK_CI_ALERTS }}
- name: Notify DeepStack Slack on CI failure
if: ${{ always() && github.ref_name == 'main' }}
uses: ravsamhq/notify-slack-action@v2
with:
status: ${{ job.status }}
notify_when: 'failure'
env:
SLACK_WEBHOOK_URL: ${{ secrets.DEEPSTACK_SLACK_CI_ALERTS }}
@@ -1,57 +0,0 @@
name: Fixturenet-Eth-Plugeth-Test
on:
push:
branches: '*'
paths:
- '!**'
- '.gitea/workflows/triggers/fixturenet-eth-plugeth-test'
schedule: # Note: coordinate with other tests to not overload runners at the same time of day
- cron: '2 14 * * *'
jobs:
test:
name: "Run an Ethereum plugeth fixturenet test"
runs-on: ubuntu-latest
steps:
- name: "Clone project repository"
uses: actions/checkout@v3
# At present the stock setup-python action fails on Linux/aarch64
# Conditional steps below workaroud this by using deadsnakes for that case only
- name: "Install Python for ARM on Linux"
if: ${{ runner.arch == 'arm64' && runner.os == 'Linux' }}
uses: deadsnakes/action@v3.0.1
with:
python-version: '3.8'
- name: "Install Python cases other than ARM on Linux"
if: ${{ ! (runner.arch == 'arm64' && runner.os == 'Linux') }}
uses: actions/setup-python@v4
with:
python-version: '3.8'
- name: "Print Python version"
run: python3 --version
- name: "Install shiv"
run: pip install shiv
- name: "Generate build version file"
run: ./scripts/create_build_tag_file.sh
- name: "Build local shiv package"
run: ./scripts/build_shiv_package.sh
- name: "Run fixturenet-eth tests"
run: ./tests/fixturenet-eth-plugeth/run-test.sh
- name: Notify Vulcanize Slack on CI failure
if: ${{ always() && github.ref_name == 'main' }}
uses: ravsamhq/notify-slack-action@v2
with:
status: ${{ job.status }}
notify_when: 'failure'
env:
SLACK_WEBHOOK_URL: ${{ secrets.VULCANIZE_SLACK_CI_ALERTS }}
- name: Notify DeepStack Slack on CI failure
if: ${{ always() && github.ref_name == 'main' }}
uses: ravsamhq/notify-slack-action@v2
with:
status: ${{ job.status }}
notify_when: 'failure'
env:
SLACK_WEBHOOK_URL: ${{ secrets.DEEPSTACK_SLACK_CI_ALERTS }}
-55
View File
@@ -1,55 +0,0 @@
name: Fixturenet-Eth-Test
on:
push:
branches: '*'
paths:
- '!**'
- '.gitea/workflows/triggers/fixturenet-eth-test'
jobs:
test:
name: "Run an Ethereum fixturenet test"
runs-on: ubuntu-latest
steps:
- name: "Clone project repository"
uses: actions/checkout@v3
# At present the stock setup-python action fails on Linux/aarch64
# Conditional steps below workaroud this by using deadsnakes for that case only
- name: "Install Python for ARM on Linux"
if: ${{ runner.arch == 'arm64' && runner.os == 'Linux' }}
uses: deadsnakes/action@v3.0.1
with:
python-version: '3.8'
- name: "Install Python cases other than ARM on Linux"
if: ${{ ! (runner.arch == 'arm64' && runner.os == 'Linux') }}
uses: actions/setup-python@v4
with:
python-version: '3.8'
- name: "Print Python version"
run: python3 --version
- name: "Install shiv"
run: pip install shiv
- name: "Generate build version file"
run: ./scripts/create_build_tag_file.sh
- name: "Build local shiv package"
run: ./scripts/build_shiv_package.sh
- name: "Run fixturenet-eth tests"
run: ./tests/fixturenet-eth/run-test.sh
- name: Notify Vulcanize Slack on CI failure
if: ${{ always() && github.ref_name == 'main' }}
uses: ravsamhq/notify-slack-action@v2
with:
status: ${{ job.status }}
notify_when: 'failure'
env:
SLACK_WEBHOOK_URL: ${{ secrets.VULCANIZE_SLACK_CI_ALERTS }}
- name: Notify DeepStack Slack on CI failure
if: ${{ always() && github.ref_name == 'main' }}
uses: ravsamhq/notify-slack-action@v2
with:
status: ${{ job.status }}
notify_when: 'failure'
env:
SLACK_WEBHOOK_URL: ${{ secrets.DEEPSTACK_SLACK_CI_ALERTS }}
@@ -1,2 +0,0 @@
Change this file to trigger running the fixturenet-eth-plugeth-arm-test CI job
@@ -1,3 +0,0 @@
Change this file to trigger running the fixturenet-eth-plugeth-test CI job
trigger
trigger
@@ -1,2 +0,0 @@
Change this file to trigger running the fixturenet-eth-test CI job
@@ -7,3 +7,4 @@ Trigger
Trigger
Trigger
Trigger
Trigger
+1
View File
@@ -34,5 +34,6 @@ volumes_key = "volumes"
security_key = "security"
annotations_key = "annotations"
labels_key = "labels"
replicas_key = "replicas"
kind_config_filename = "kind-config.yml"
kube_config_filename = "kubeconfig.yml"
@@ -10,6 +10,7 @@ MONIKER="localtestnet"
KEYRING="test"
KEYALGO="secp256k1"
LOGLEVEL="${LOGLEVEL:-info}"
DENOM="alnt"
if [ "$1" == "clean" ] || [ ! -d "$HOME/.laconicd/data/blockstore.db" ]; then
@@ -33,7 +34,7 @@ if [ "$1" == "clean" ] || [ ! -d "$HOME/.laconicd/data/blockstore.db" ]; then
laconicd keys add $KEY --keyring-backend $KEYRING --algo $KEYALGO
# Set moniker and chain-id for Ethermint (Moniker can be anything, chain-id must be an integer)
laconicd init $MONIKER --chain-id $CHAINID --default-denom photon
laconicd init $MONIKER --chain-id $CHAINID --default-denom $DENOM
update_genesis() {
jq "$1" $HOME/.laconicd/config/genesis.json > $HOME/.laconicd/config/tmp_genesis.json &&
@@ -89,10 +90,12 @@ if [ "$1" == "clean" ] || [ ! -d "$HOME/.laconicd/data/blockstore.db" ]; then
fi
# Allocate genesis accounts (cosmos formatted addresses)
laconicd genesis add-genesis-account $KEY 100000000000000000000000000photon --keyring-backend $KEYRING
# 10^30 alnt | 10^12 lnt
laconicd genesis add-genesis-account $KEY 1000000000000000000000000000000$DENOM --keyring-backend $KEYRING
# Sign genesis transaction
laconicd genesis gentx $KEY 1000000000000000000000photon --keyring-backend $KEYRING --chain-id $CHAINID
# 10^24 alnt | 10^6 lnt
laconicd genesis gentx $KEY 1000000000000000000000000$DENOM --keyring-backend $KEYRING --chain-id $CHAINID
# Collect genesis tx
laconicd genesis collect-gentxs
@@ -107,7 +110,7 @@ fi
laconicd start \
--pruning=nothing \
--log_level $LOGLEVEL \
--minimum-gas-prices=0.0001photon \
--minimum-gas-prices=1$DENOM \
--api.enable \
--rpc.laddr="tcp://0.0.0.0:26657" \
--gql-server --gql-playground
@@ -6,4 +6,4 @@ services:
bondId:
chainId: laconic_9000-1
gas: 350000
fees: 200000photon
fees: 2000000alnt
@@ -6,4 +6,4 @@ services:
bondId:
chainId: laconic_9000-1
gas: 250000
fees: 200000photon
fees: 2000000alnt
@@ -1,5 +1,5 @@
#!/bin/sh
if [[ -n "$CERC_SCRIPT_DEBUG" ]]; then
if [ -n "$CERC_SCRIPT_DEBUG" ]; then
set -x
fi
@@ -9,7 +9,7 @@ LOGLEVEL="info"
laconicd start \
--pruning=nothing \
--log_level $LOGLEVEL \
--minimum-gas-prices=0.0001photon \
--minimum-gas-prices=1alnt \
--api.enable \
--gql-server \
--gql-playground
@@ -14,7 +14,7 @@ funds_balance=$(echo ${funds_response} | jq -r ".[0].balance[0].quantity")
echo "Balance is: ${funds_balance}"
# Create a bond
bond_create_result=$(${registry_command} bond create --type photon --quantity 1000000000)
bond_create_result=$(${registry_command} bond create --type alnt --quantity 1000000000)
bond_id=$(echo ${bond_create_result} | jq -r .bondId)
echo "Created bond with id: ${bond_id}"
@@ -7,16 +7,16 @@
"rpc": [
{"provider": "LX-tendermint-rpc", "address": "LACONIC_LACONICD_RPC_URL"}
],
"sdk_version": "0.45.1",
"sdk_version": "0.50.3",
"coin_type": "118",
"min_tx_fee": "800",
"addr_prefix": "ethm",
"addr_prefix": "laconic",
"logo": "/logos/cosmos.svg",
"assets": [{
"base": "photon",
"base": "alnt",
"symbol": "LNT",
"exponent": "6",
"coingecko_id": "cosmos",
"exponent": "18",
"coingecko_id": "cosmos",
"logo": "/logos/cosmos.svg"
}]
}
@@ -64,5 +64,6 @@ $ laconic-so --stack fixturenet-laconic-loaded deploy exec cli ./scripts/create-
Balance is: 99998999999999998999600000
Created bond with id: dd88e8d6f9567b32b28e70552aea4419c5dd3307ebae85a284d1fe38904e301a
Published demo-record-1.yml with id: bafyreierh3xnfivexlscdwubvczmddsnf46uytyfvrbdhkjzztvsz6ruly
...
```
The published record should be visible in the console.
The published records should be visible in the console.
@@ -30,4 +30,3 @@ config:
cli:
key: laconicd.mykey
address: laconicd.myaddress
@@ -22,7 +22,6 @@ from stack_orchestrator.opts import opts
from enum import Enum
from pathlib import Path
from shutil import copyfile, copytree
import json
import os
import sys
import tomli
@@ -34,8 +33,9 @@ default_spec_file_content = ""
class SetupPhase(Enum):
INITIALIZE = 1
JOIN = 2
CREATE = 3
ILLEGAL = 3
CONNECT = 3
CREATE = 4
ILLEGAL = 5
def _client_toml_path(network_dir: Path):
@@ -62,29 +62,13 @@ def _get_node_moniker_from_config(network_dir: Path):
return moniker
def _get_node_key_from_gentx(gentx_file_name: str):
gentx_file_path = Path(gentx_file_name)
if gentx_file_path.exists():
with open(Path(gentx_file_name), "rb") as f:
parsed_json = json.load(f)
return parsed_json['body']['messages'][0]['delegator_address']
else:
print(f"Error: gentx file: {gentx_file_name} does not exist")
sys.exit(1)
def _comma_delimited_to_list(list_str: str):
return list_str.split(",") if list_str else []
def _get_node_keys_from_gentx_files(gentx_file_list: str):
node_keys = []
gentx_files = _comma_delimited_to_list(gentx_file_list)
for gentx_file in gentx_files:
node_key = _get_node_key_from_gentx(gentx_file)
if node_key:
node_keys.append(node_key)
return node_keys
def _get_node_keys_from_gentx_files(gentx_address_list: str):
gentx_addresses = _comma_delimited_to_list(gentx_address_list)
return gentx_addresses
def _copy_gentx_files(network_dir: Path, gentx_file_list: str):
@@ -147,6 +131,35 @@ def _enable_cors(config_dir: Path):
output_file.write(app_file_content)
def _set_listen_address(config_dir: Path):
config_file_path = config_dir.joinpath("config.toml")
if not config_file_path.exists():
print("Error: config.toml not found")
sys.exit(1)
with open(config_file_path, "r") as input_file:
config_file_content = input_file.read()
existing_pattern = r'^laddr = "tcp://127.0.0.1:26657"'
replace_with = 'laddr = "tcp://0.0.0.0:26657"'
print(f"Replacing in: {config_file_path}")
config_file_content = re.sub(existing_pattern, replace_with, config_file_content, flags=re.MULTILINE)
with open(config_file_path, "w") as output_file:
output_file.write(config_file_content)
app_file_path = config_dir.joinpath("app.toml")
if not app_file_path.exists():
print("Error: app.toml not found")
sys.exit(1)
with open(app_file_path, "r") as input_file:
app_file_content = input_file.read()
existing_pattern1 = r'^address = "tcp://localhost:1317"'
replace_with1 = 'address = "tcp://0.0.0.0:1317"'
app_file_content = re.sub(existing_pattern1, replace_with1, app_file_content, flags=re.MULTILINE)
existing_pattern2 = r'^address = "localhost:9090"'
replace_with2 = 'address = "0.0.0.0:9090"'
app_file_content = re.sub(existing_pattern2, replace_with2, app_file_content, flags=re.MULTILINE)
with open(app_file_path, "w") as output_file:
output_file.write(app_file_content)
def _phase_from_params(parameters):
phase = SetupPhase.ILLEGAL
if parameters.initialize_network:
@@ -171,6 +184,11 @@ def _phase_from_params(parameters):
print("Can't supply --initialize-network or --join-network with --create-network")
sys.exit(1)
phase = SetupPhase.CREATE
elif parameters.connect_network:
if parameters.initialize_network or parameters.join_network:
print("Can't supply --initialize-network or --join-network with --connect-network")
sys.exit(1)
phase = SetupPhase.CONNECT
return phase
@@ -178,7 +196,7 @@ def setup(command_context: DeployCommandContext, parameters: LaconicStackSetupCo
options = opts.o
currency = "stake" # Does this need to be a parameter?
currency = "alnt" # Does this need to be a parameter?
if options.debug:
print(f"parameters: {parameters}")
@@ -203,11 +221,12 @@ def setup(command_context: DeployCommandContext, parameters: LaconicStackSetupCo
output, status = run_container_command(
command_context,
"laconicd", f"laconicd init {parameters.node_moniker} --home {laconicd_home_path_in_container}\
--chain-id {parameters.chain_id}", mounts)
--chain-id {parameters.chain_id} --default-denom {currency}", mounts)
if options.debug:
print(f"Command output: {output}")
elif phase == SetupPhase.JOIN:
# In the join phase (alternative to connect) we are participating in a genesis ceremony for the chain
if not os.path.exists(network_dir):
print(f"Error: network directory {network_dir} doesn't exist")
sys.exit(1)
@@ -222,7 +241,7 @@ def setup(command_context: DeployCommandContext, parameters: LaconicStackSetupCo
output2, status2 = run_container_command(
command_context,
"laconicd",
f"laconicd add-genesis-account {parameters.key_name} 12900000000000000000000{currency}\
f"laconicd genesis add-genesis-account {parameters.key_name} 12900000000000000000000{currency}\
--home {laconicd_home_path_in_container} --keyring-backend test",
mounts)
if options.debug:
@@ -230,7 +249,7 @@ def setup(command_context: DeployCommandContext, parameters: LaconicStackSetupCo
output3, status3 = run_container_command(
command_context,
"laconicd",
f"laconicd gentx {parameters.key_name} 90000000000{currency} --home {laconicd_home_path_in_container}\
f"laconicd genesis gentx {parameters.key_name} 90000000000{currency} --home {laconicd_home_path_in_container}\
--chain-id {chain_id} --keyring-backend test",
mounts)
if options.debug:
@@ -240,7 +259,28 @@ def setup(command_context: DeployCommandContext, parameters: LaconicStackSetupCo
"laconicd",
f"laconicd keys show {parameters.key_name} -a --home {laconicd_home_path_in_container} --keyring-backend test",
mounts)
print(f"Node validator address: {output4}")
print(f"Node account address: {output4}")
elif phase == SetupPhase.CONNECT:
# In the connect phase (named to not conflict with join) we are making a node that syncs a chain with existing genesis.json
# but not with validator role. We need this kind of node in order to bootstrap it into a validator after it syncs
output1, status1 = run_container_command(
command_context, "laconicd", f"laconicd keys add {parameters.key_name} --home {laconicd_home_path_in_container}\
--keyring-backend test", mounts)
if options.debug:
print(f"Command output: {output1}")
output2, status2 = run_container_command(
command_context,
"laconicd",
f"laconicd keys show {parameters.key_name} -a --home {laconicd_home_path_in_container} --keyring-backend test",
mounts)
print(f"Node account address: {output2}")
output3, status3 = run_container_command(
command_context,
"laconicd",
f"laconicd cometbft show-validator --home {laconicd_home_path_in_container}",
mounts)
print(f"Node validator address: {output3}")
elif phase == SetupPhase.CREATE:
if not os.path.exists(network_dir):
@@ -259,15 +299,13 @@ def setup(command_context: DeployCommandContext, parameters: LaconicStackSetupCo
copyfile(genesis_file_path, os.path.join(network_dir, "config", os.path.basename(genesis_file_path)))
else:
# We're generating the genesis file
if not parameters.gentx_file_list:
print("Error: --gentx-files must be supplied")
sys.exit(1)
# First look in the supplied gentx files for the other nodes' keys
other_node_keys = _get_node_keys_from_gentx_files(parameters.gentx_file_list)
other_node_keys = _get_node_keys_from_gentx_files(parameters.gentx_address_list)
# Add those keys to our genesis, with balances we determine here (why?)
for other_node_key in other_node_keys:
outputk, statusk = run_container_command(
command_context, "laconicd", f"laconicd add-genesis-account {other_node_key} 12900000000000000000000{currency}\
command_context, "laconicd", f"laconicd genesis add-genesis-account {other_node_key} \
12900000000000000000000{currency}\
--home {laconicd_home_path_in_container} --keyring-backend test", mounts)
if options.debug:
print(f"Command output: {outputk}")
@@ -275,7 +313,7 @@ def setup(command_context: DeployCommandContext, parameters: LaconicStackSetupCo
_copy_gentx_files(network_dir, parameters.gentx_file_list)
# Now we can run collect-gentxs
output1, status1 = run_container_command(
command_context, "laconicd", f"laconicd collect-gentxs --home {laconicd_home_path_in_container}", mounts)
command_context, "laconicd", f"laconicd genesis collect-gentxs --home {laconicd_home_path_in_container}", mounts)
if options.debug:
print(f"Command output: {output1}")
print(f"Generated genesis file, please copy to other nodes as required: \
@@ -284,7 +322,7 @@ def setup(command_context: DeployCommandContext, parameters: LaconicStackSetupCo
_remove_persistent_peers(network_dir)
# In both cases we validate the genesis file now
output2, status1 = run_container_command(
command_context, "laconicd", f"laconicd validate-genesis --home {laconicd_home_path_in_container}", mounts)
command_context, "laconicd", f"laconicd genesis validate-genesis --home {laconicd_home_path_in_container}", mounts)
print(f"validate-genesis result: {output2}")
else:
@@ -319,6 +357,7 @@ def create(deployment_context: DeploymentContext, extra_args):
_insert_persistent_peers(deployment_config_dir, initial_persistent_peers)
# Enable CORS headers so explorers and so on can talk to the node
_enable_cors(deployment_config_dir)
_set_listen_address(deployment_config_dir)
# Copy the data directory contents into our deployment
# TODO: change this to work with non local paths
deployment_data_dir = deployment_context.deployment_dir.joinpath("data", "laconicd-data")
@@ -331,7 +370,6 @@ def init(command_context: DeployCommandContext):
def get_state(command_context: DeployCommandContext):
print("Here we get state")
return State.CONFIGURED
@@ -8,8 +8,11 @@ echo "Environment variables:"
env
# Test laconic stack
echo "Running laconic stack test"
# Bit of a hack, test the most recent package
TEST_TARGET_SO=$( ls -t1 ./package/laconic-so* | head -1 )
if [ "$1" == "from-path" ]; then
TEST_TARGET_SO="laconic-so"
else
TEST_TARGET_SO=$( ls -t1 ./package/laconic-so* | head -1 )
fi
# Set a non-default repo dir
export CERC_REPO_BASE_DIR=~/stack-orchestrator-test/repo-base-dir
echo "Testing this package: $TEST_TARGET_SO"
@@ -29,14 +29,14 @@ class DockerDeployer(Deployer):
compose_env_file=compose_env_file)
self.type = type
def up(self, detach, services):
def up(self, detach, skip_cluster_management, services):
if not opts.o.dry_run:
try:
return self.docker.compose.up(detach=detach, services=services)
except DockerException as e:
raise DeployerException(e)
def down(self, timeout, volumes):
def down(self, timeout, volumes, skip_cluster_management):
if not opts.o.dry_run:
try:
return self.docker.compose.down(timeout=timeout, volumes=volumes)
+4 -4
View File
@@ -91,7 +91,7 @@ def create_deploy_context(
return DeployCommandContext(stack, cluster_context, deployer)
def up_operation(ctx, services_list, stay_attached=False):
def up_operation(ctx, services_list, stay_attached=False, skip_cluster_management=False):
global_context = ctx.parent.parent.obj
deploy_context = ctx.obj
cluster_context = deploy_context.cluster_context
@@ -102,18 +102,18 @@ def up_operation(ctx, services_list, stay_attached=False):
print(f"Running compose up with container_exec_env: {container_exec_env}, extra_args: {services_list}")
for pre_start_command in cluster_context.pre_start_commands:
_run_command(global_context, cluster_context.cluster, pre_start_command)
deploy_context.deployer.up(detach=not stay_attached, services=services_list)
deploy_context.deployer.up(detach=not stay_attached, skip_cluster_management=skip_cluster_management, services=services_list)
for post_start_command in cluster_context.post_start_commands:
_run_command(global_context, cluster_context.cluster, post_start_command)
_orchestrate_cluster_config(global_context, cluster_context.config, deploy_context.deployer, container_exec_env)
def down_operation(ctx, delete_volumes, extra_args_list):
def down_operation(ctx, delete_volumes, extra_args_list, skip_cluster_management=False):
timeout_arg = None
if extra_args_list:
timeout_arg = extra_args_list[0]
# Specify shutdown timeout (default 10s) to give services enough time to shutdown gracefully
ctx.obj.deployer.down(timeout=timeout_arg, volumes=delete_volumes)
ctx.obj.deployer.down(timeout=timeout_arg, volumes=delete_volumes, skip_cluster_management=skip_cluster_management)
def status_operation(ctx):
@@ -50,8 +50,10 @@ class LaconicStackSetupCommand:
key_name: str
initialize_network: bool
join_network: bool
connect_network: bool
create_network: bool
gentx_file_list: str
gentx_address_list: str
genesis_file: str
network_dir: str
+3 -2
View File
@@ -13,7 +13,6 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
import os
from typing import List, Any
from stack_orchestrator.deploy.deploy_types import DeployCommandContext, VolumeMapping
from stack_orchestrator.util import get_parsed_stack_config, get_yaml, get_pod_list, resolve_compose_file
@@ -83,7 +82,9 @@ def run_container_command(ctx: DeployCommandContext, service: str, command: str,
docker_output = deployer.run(
container_image,
["-c", command], entrypoint="sh",
user=f"{os.getuid()}:{os.getgid()}",
# Current laconicd container has a bug where it crashes when run not as root
# Commented out line below is a workaround. Created files end up owned by root on the host
# user=f"{os.getuid()}:{os.getgid()}",
volumes=docker_volumes
)
# There doesn't seem to be a way to get an exit code from docker.run()
+2 -2
View File
@@ -20,11 +20,11 @@ from pathlib import Path
class Deployer(ABC):
@abstractmethod
def up(self, detach, services):
def up(self, detach, skip_cluster_management, services):
pass
@abstractmethod
def down(self, timeout, volumes):
def down(self, timeout, volumes, skip_cluster_management):
pass
@abstractmethod
+18 -8
View File
@@ -61,47 +61,57 @@ def make_deploy_context(ctx) -> DeployCommandContext:
cluster_name, env_file, deployment_type)
# TODO: remove legacy up command since it's an alias for start
@command.command()
@click.option("--stay-attached/--detatch-terminal", default=False, help="detatch or not to see container stdout")
@click.option("--skip-cluster-management/--perform-cluster-management",
default=False, help="Skip cluster initialization/tear-down (only for kind-k8s deployments)")
@click.argument('extra_args', nargs=-1) # help: command: up <service1> <service2>
@click.pass_context
def up(ctx, stay_attached, extra_args):
def up(ctx, stay_attached, skip_cluster_management, extra_args):
ctx.obj = make_deploy_context(ctx)
services_list = list(extra_args) or None
up_operation(ctx, services_list, stay_attached)
up_operation(ctx, services_list, stay_attached, skip_cluster_management)
# start is the preferred alias for up
@command.command()
@click.option("--stay-attached/--detatch-terminal", default=False, help="detatch or not to see container stdout")
@click.option("--skip-cluster-management/--perform-cluster-management",
default=False, help="Skip cluster initialization/tear-down (only for kind-k8s deployments)")
@click.argument('extra_args', nargs=-1) # help: command: up <service1> <service2>
@click.pass_context
def start(ctx, stay_attached, extra_args):
def start(ctx, stay_attached, skip_cluster_management, extra_args):
ctx.obj = make_deploy_context(ctx)
services_list = list(extra_args) or None
up_operation(ctx, services_list, stay_attached)
up_operation(ctx, services_list, stay_attached, skip_cluster_management)
# TODO: remove legacy up command since it's an alias for stop
@command.command()
@click.option("--delete-volumes/--preserve-volumes", default=False, help="delete data volumes")
@click.option("--skip-cluster-management/--perform-cluster-management",
default=False, help="Skip cluster initialization/tear-down (only for kind-k8s deployments)")
@click.argument('extra_args', nargs=-1) # help: command: down <service1> <service2>
@click.pass_context
def down(ctx, delete_volumes, extra_args):
def down(ctx, delete_volumes, skip_cluster_management, extra_args):
# Get the stack config file name
# TODO: add cluster name and env file here
ctx.obj = make_deploy_context(ctx)
down_operation(ctx, delete_volumes, extra_args)
down_operation(ctx, delete_volumes, extra_args, skip_cluster_management)
# stop is the preferred alias for down
@command.command()
@click.option("--delete-volumes/--preserve-volumes", default=False, help="delete data volumes")
@click.option("--skip-cluster-management/--perform-cluster-management",
default=False, help="Skip cluster initialization/tear-down (only for kind-k8s deployments)")
@click.argument('extra_args', nargs=-1) # help: command: down <service1> <service2>
@click.pass_context
def stop(ctx, delete_volumes, extra_args):
def stop(ctx, delete_volumes, skip_cluster_management, extra_args):
# TODO: add cluster name and env file here
ctx.obj = make_deploy_context(ctx)
down_operation(ctx, delete_volumes, extra_args)
down_operation(ctx, delete_volumes, extra_args, skip_cluster_management)
@command.command()
+23 -4
View File
@@ -514,6 +514,23 @@ def create_operation(deployment_command_context, spec_file, deployment_dir, netw
os.mkdir(destination_script_dir)
script_paths = get_pod_script_paths(parsed_stack, pod)
_copy_files_to_directory(script_paths, destination_script_dir)
if parsed_spec.is_kubernetes_deployment():
for configmap in parsed_spec.get_configmaps():
source_config_dir = resolve_config_dir(stack_name, configmap)
if os.path.exists(source_config_dir):
destination_config_dir = deployment_dir_path.joinpath("configmaps", configmap)
copytree(source_config_dir, destination_config_dir, dirs_exist_ok=True)
else:
# TODO: We should probably only do this if the volume is marked :ro.
for volume_name, volume_path in parsed_spec.get_volumes().items():
source_config_dir = resolve_config_dir(stack_name, volume_name)
# Only copy if the source exists and is _not_ empty.
if os.path.exists(source_config_dir) and os.listdir(source_config_dir):
destination_config_dir = deployment_dir_path.joinpath(volume_path)
# Only copy if the destination exists and _is_ empty.
if os.path.exists(destination_config_dir) and not os.listdir(destination_config_dir):
copytree(source_config_dir, destination_config_dir, dirs_exist_ok=True)
# Delegate to the stack's Python code
# The deploy create command doesn't require a --stack argument so we need to insert the
# stack member here.
@@ -535,15 +552,17 @@ def create_operation(deployment_command_context, spec_file, deployment_dir, netw
@click.option("--chain-id", help="The new chain id")
@click.option("--key-name", help="Name for new node key")
@click.option("--gentx-files", help="List of comma-delimited gentx filenames from other nodes")
@click.option("--gentx-addresses", type=str, help="List of comma-delimited validator addresses for other nodes")
@click.option("--genesis-file", help="Genesis file for the network")
@click.option("--initialize-network", is_flag=True, default=False, help="Initialize phase")
@click.option("--join-network", is_flag=True, default=False, help="Join phase")
@click.option("--connect-network", is_flag=True, default=False, help="Connect phase")
@click.option("--create-network", is_flag=True, default=False, help="Create phase")
@click.option("--network-dir", help="Directory for network files")
@click.argument('extra_args', nargs=-1)
@click.pass_context
def setup(ctx, node_moniker, chain_id, key_name, gentx_files, genesis_file, initialize_network, join_network, create_network,
network_dir, extra_args):
parmeters = LaconicStackSetupCommand(chain_id, node_moniker, key_name, initialize_network, join_network, create_network,
gentx_files, genesis_file, network_dir)
def setup(ctx, node_moniker, chain_id, key_name, gentx_files, gentx_addresses, genesis_file, initialize_network, join_network,
connect_network, create_network, network_dir, extra_args):
parmeters = LaconicStackSetupCommand(chain_id, node_moniker, key_name, initialize_network, join_network, connect_network,
create_network, gentx_files, gentx_addresses, genesis_file, network_dir)
call_stack_deploy_setup(ctx.obj, parmeters, extra_args)
+33 -18
View File
@@ -78,28 +78,40 @@ class ClusterInfo:
if (opts.o.debug):
print(f"Env vars: {self.environment_variables.map}")
def get_nodeport(self):
def get_nodeports(self):
nodeports = []
for pod_name in self.parsed_pod_yaml_map:
pod = self.parsed_pod_yaml_map[pod_name]
services = pod["services"]
for service_name in services:
service_info = services[service_name]
if "ports" in service_info:
port = int(service_info["ports"][0])
if opts.o.debug:
print(f"service port: {port}")
service = client.V1Service(
metadata=client.V1ObjectMeta(name=f"{self.app_name}-nodeport"),
spec=client.V1ServiceSpec(
type="NodePort",
ports=[client.V1ServicePort(
port=port,
target_port=port
)],
selector={"app": self.app_name}
)
)
return service
for raw_port in [str(p) for p in service_info["ports"]]:
if opts.o.debug:
print(f"service port: {raw_port}")
if ":" in raw_port:
parts = raw_port.split(":")
if len(parts) != 2:
raise Exception(f"Invalid port definition: {raw_port}")
node_port = int(parts[0])
pod_port = int(parts[1])
else:
node_port = None
pod_port = int(raw_port)
service = client.V1Service(
metadata=client.V1ObjectMeta(name=f"{self.app_name}-nodeport-{pod_port}"),
spec=client.V1ServiceSpec(
type="NodePort",
ports=[client.V1ServicePort(
port=pod_port,
target_port=pod_port,
node_port=node_port
)],
selector={"app": self.app_name}
)
)
nodeports.append(service)
return nodeports
def get_ingress(self, use_tls=False, certificate=None, cluster_issuer="letsencrypt-prod"):
# No ingress for a deployment that has no http-proxy defined, for now
@@ -373,9 +385,12 @@ class ClusterInfo:
spec=client.V1PodSpec(containers=containers, image_pull_secrets=image_pull_secrets, volumes=volumes),
)
spec = client.V1DeploymentSpec(
replicas=1, template=template, selector={
replicas=self.spec.get_replicas(),
template=template, selector={
"matchLabels":
{"app": self.app_name}})
{"app": self.app_name}
}
)
deployment = client.V1Deployment(
api_version="apps/v1",
+16 -10
View File
@@ -16,6 +16,7 @@ from datetime import datetime, timezone
from pathlib import Path
from kubernetes import client, config
from typing import List
from stack_orchestrator import constants
from stack_orchestrator.deploy.deployer import Deployer, DeployerConfigGenerator
@@ -51,12 +52,14 @@ class K8sDeployer(Deployer):
networking_api: client.NetworkingV1Api
k8s_namespace: str = "default"
kind_cluster_name: str
skip_cluster_management: bool
cluster_info: ClusterInfo
deployment_dir: Path
deployment_context: DeploymentContext
def __init__(self, type, deployment_context: DeploymentContext, compose_files, compose_project_name, compose_env_file) -> None:
self.type = type
self.skip_cluster_management = False
# TODO: workaround pending refactoring above to cope with being created with a null deployment_context
if deployment_context is None:
return
@@ -182,6 +185,7 @@ class K8sDeployer(Deployer):
if len(host_parts) == 2:
host_as_wild = f"*.{host_parts[1]}"
# TODO: resolve method deprecation below
now = datetime.utcnow().replace(tzinfo=timezone.utc)
fmt = "%Y-%m-%dT%H:%M:%S%z"
@@ -202,15 +206,16 @@ class K8sDeployer(Deployer):
return cert
return None
def up(self, detach, services):
def up(self, detach, skip_cluster_management, services):
self.skip_cluster_management = skip_cluster_management
if not opts.o.dry_run:
if self.is_kind():
if self.is_kind() and not self.skip_cluster_management:
# Create the kind cluster
create_cluster(self.kind_cluster_name, self.deployment_dir.joinpath(constants.kind_config_filename))
# Ensure the referenced containers are copied into kind
load_images_into_kind(self.kind_cluster_name, self.cluster_info.image_set)
self.connect_api()
if self.is_kind():
if self.is_kind() and not self.skip_cluster_management:
# Now configure an ingress controller (not installed by default in kind)
install_ingress_for_kind()
# Wait for ingress to start (deployment provisioning will fail unless this is done)
@@ -246,8 +251,8 @@ class K8sDeployer(Deployer):
if opts.o.debug:
print("No ingress configured")
nodeport: client.V1Service = self.cluster_info.get_nodeport()
if nodeport:
nodeports: List[client.V1Service] = self.cluster_info.get_nodeports()
for nodeport in nodeports:
if opts.o.debug:
print(f"Sending this nodeport: {nodeport}")
if not opts.o.dry_run:
@@ -259,7 +264,8 @@ class K8sDeployer(Deployer):
print("NodePort created:")
print(f"{nodeport_resp}")
def down(self, timeout, volumes): # noqa: C901
def down(self, timeout, volumes, skip_cluster_management): # noqa: C901
self.skip_cluster_management = skip_cluster_management
self.connect_api()
# Delete the k8s objects
@@ -342,10 +348,10 @@ class K8sDeployer(Deployer):
if opts.o.debug:
print("No ingress to delete")
nodeport: client.V1Service = self.cluster_info.get_nodeport()
if nodeport:
nodeports: List[client.V1Service] = self.cluster_info.get_nodeports()
for nodeport in nodeports:
if opts.o.debug:
print(f"Deleting this nodeport: {ingress}")
print(f"Deleting this nodeport: {nodeport}")
try:
self.core_api.delete_namespaced_service(
namespace=self.k8s_namespace,
@@ -357,7 +363,7 @@ class K8sDeployer(Deployer):
if opts.o.debug:
print("No nodeport to delete")
if self.is_kind():
if self.is_kind() and not self.skip_cluster_management:
# Destroy the kind cluster
destroy_cluster(self.kind_cluster_name)
+3
View File
@@ -117,6 +117,9 @@ class Spec:
def get_annotations(self):
return self.obj.get(constants.annotations_key, {})
def get_replicas(self):
return self.obj.get(constants.replicas_key, 1)
def get_labels(self):
return self.obj.get(constants.labels_key, {})
@@ -45,6 +45,7 @@ def process_app_deployment_request(
image_registry,
force_rebuild,
fqdn_policy,
recreate_on_deploy,
logger
):
logger.log("BEGIN - process_app_deployment_request")
@@ -165,6 +166,7 @@ def process_app_deployment_request(
deploy_to_k8s(
deployment_record,
deployment_dir,
recreate_on_deploy,
logger
)
@@ -220,12 +222,13 @@ def dump_known_requests(filename, requests, status="SEEN"):
@click.option("--include-tags", help="Only include requests with matching tags (comma-separated).", default="")
@click.option("--exclude-tags", help="Exclude requests with matching tags (comma-separated).", default="")
@click.option("--force-rebuild", help="Rebuild even if the image already exists.", is_flag=True)
@click.option("--recreate-on-deploy", help="Remove and recreate deployments instead of updating them.", is_flag=True)
@click.option("--log-dir", help="Output build/deployment logs to directory.", default=None)
@click.pass_context
def command(ctx, kube_config, laconic_config, image_registry, deployment_parent_dir, # noqa: C901
request_id, discover, state_file, only_update_state,
dns_suffix, fqdn_policy, record_namespace_dns, record_namespace_deployments, dry_run,
include_tags, exclude_tags, force_rebuild, log_dir):
include_tags, exclude_tags, force_rebuild, recreate_on_deploy, log_dir):
if request_id and discover:
print("Cannot specify both --request-id and --discover", file=sys.stderr)
sys.exit(2)
@@ -274,34 +277,43 @@ def command(ctx, kube_config, laconic_config, image_registry, deployment_parent_
requests_by_name = {}
skipped_by_name = {}
for r in requests:
if r.id in previous_requests and previous_requests[r.id].get("status", "") != "RETRY":
print(f"Skipping request {r.id}, we've already seen it.")
continue
status = None
try:
if r.id in previous_requests and previous_requests[r.id].get("status", "") != "RETRY":
print(f"Skipping request {r.id}, we've already seen it.")
continue
app = laconic.get_record(r.attributes.application)
if not app:
print("Skipping request %s, cannot locate app." % r.id)
continue
app = laconic.get_record(r.attributes.application)
if not app:
print(f"Skipping request {r.id}, cannot locate app.")
status = "SEEN"
continue
requested_name = r.attributes.dns
if not requested_name:
requested_name = generate_hostname_for_app(app)
print("Generating name %s for request %s." % (requested_name, r.id))
requested_name = r.attributes.dns
if not requested_name:
requested_name = generate_hostname_for_app(app)
print("Generating name %s for request %s." % (requested_name, r.id))
if requested_name in skipped_by_name or requested_name in requests_by_name:
print("Ignoring request %s, it has been superseded." % r.id)
continue
if requested_name in skipped_by_name or requested_name in requests_by_name:
print("Ignoring request %s, it has been superseded." % r.id)
continue
if skip_by_tag(r, include_tags, exclude_tags):
print("Skipping request %s, filtered by tag (include %s, exclude %s, present %s)" % (r.id,
include_tags,
exclude_tags,
r.attributes.tags))
skipped_by_name[requested_name] = r
continue
if skip_by_tag(r, include_tags, exclude_tags):
print("Skipping request %s, filtered by tag (include %s, exclude %s, present %s)" % (r.id,
include_tags,
exclude_tags,
r.attributes.tags))
skipped_by_name[requested_name] = r
continue
print("Found request %s to run application %s on %s." % (r.id, r.attributes.application, requested_name))
requests_by_name[requested_name] = r
print("Found request %s to run application %s on %s." % (r.id, r.attributes.application, requested_name))
requests_by_name[requested_name] = r
except Exception as e:
print(f"ERROR examining request {r.id}: " + str(e))
status = "ERROR"
finally:
if status:
dump_known_requests(state_file, [r], status)
# Find deployments.
deployments = laconic.app_deployments()
@@ -365,6 +377,7 @@ def command(ctx, kube_config, laconic_config, image_registry, deployment_parent_
image_registry,
force_rebuild,
fqdn_policy,
recreate_on_deploy,
logger
)
status = "DEPLOYED"
+18 -11
View File
@@ -172,7 +172,7 @@ class LaconicRegistryClient:
name_or_id,
]
parsed = [AttrDict(r) for r in json.loads(logged_cmd(self.log_file, *args))]
parsed = [AttrDict(r) for r in json.loads(logged_cmd(self.log_file, *args)) if r]
if len(parsed):
self._add_to_cache(parsed)
return parsed[0]
@@ -318,17 +318,24 @@ def push_container_image(deployment_dir, logger):
logger.log("Finished pushing images.")
def deploy_to_k8s(deploy_record, deployment_dir, logger):
if not deploy_record:
command = "start"
else:
command = "update"
def deploy_to_k8s(deploy_record, deployment_dir, recreate, logger):
logger.log("Deploying to k8s ...")
logger.log(f"Running {command} command on deployment dir: {deployment_dir}")
result = subprocess.run([sys.argv[0], "deployment", "--dir", deployment_dir, command],
stdout=logger.file, stderr=logger.file)
result.check_returncode()
if recreate:
commands_to_run = ["stop", "start"]
else:
if not deploy_record:
commands_to_run = ["start"]
else:
commands_to_run = ["update"]
for command in commands_to_run:
logger.log(f"Running {command} command on deployment dir: {deployment_dir}")
result = subprocess.run([sys.argv[0], "deployment", "--dir", deployment_dir, command],
stdout=logger.file, stderr=logger.file)
result.check_returncode()
logger.log(f"Finished {command} command on deployment dir: {deployment_dir}")
logger.log("Finished deploying to k8s.")
-44
View File
@@ -1,44 +0,0 @@
#!/usr/bin/env bash
set -e
if [ -n "$CERC_SCRIPT_DEBUG" ]; then
set -x
fi
set -e
echo "Running stack-orchestrator Ethereum plugeth fixturenet test"
# Bit of a hack, test the most recent package
TEST_TARGET_SO=$( ls -t1 ./package/laconic-so* | head -1 )
CERC_STACK_NAME=fixturenet-plugeth-tx
# Set a new unique repo dir
export CERC_REPO_BASE_DIR=$(mktemp -d stack-orchestrator-fixturenet-eth-test.XXXXXXXXXX)
echo "Testing this package: $TEST_TARGET_SO"
echo "Test version command"
reported_version_string=$( $TEST_TARGET_SO version )
echo "Version reported is: ${reported_version_string}"
echo "Cloning repositories into: $CERC_REPO_BASE_DIR"
$TEST_TARGET_SO --stack $CERC_STACK_NAME setup-repositories
echo "Building containers"
$TEST_TARGET_SO --stack $CERC_STACK_NAME build-containers
echo "Images in registry:"
docker image ls
echo "Deploying the cluster"
$TEST_TARGET_SO --stack $CERC_STACK_NAME deploy up
# Verify that the fixturenet is up and running
$TEST_TARGET_SO --stack $CERC_STACK_NAME deploy ps
$TEST_TARGET_SO --stack $CERC_STACK_NAME deploy exec fixturenet-eth-bootnode-lighthouse /scripts/status-internal.sh
initial_block_number=$($TEST_TARGET_SO --stack fixturenet-plugeth-tx deploy exec foundry "cast block-number")
# Check that the block number increases some time later
sleep 12
subsequent_block_number=$($TEST_TARGET_SO --stack $CERC_STACK_NAME deploy exec foundry "cast block-number")
block_number_difference=$((subsequent_block_number - initial_block_number))
# Block height difference should be between 1 and some small number
if [[ $block_number_difference -gt 1 && $block_number_difference -lt 10 ]]; then
echo "Test passed"
test_result=0
else
echo "Test failed: block numbers were ${initial_block_number} and ${subsequent_block_number}"
test_result=1
fi
$TEST_TARGET_SO --stack $CERC_STACK_NAME deploy down
echo "Removing cloned repositories"
rm -rf $CERC_REPO_BASE_DIR
exit $test_result
-80
View File
@@ -1,80 +0,0 @@
#!/usr/bin/env bash
set -e
if [ -n "$CERC_SCRIPT_DEBUG" ]; then
set -x
fi
echo "$(date +"%Y-%m-%d %T"): Running stack-orchestrator Ethereum fixturenet test"
# Bit of a hack, test the most recent package
TEST_TARGET_SO=$( ls -t1 ./package/laconic-so* | head -1 )
# Set a new unique repo dir
export CERC_REPO_BASE_DIR=$(mktemp -d stack-orchestrator-fixturenet-eth-test.XXXXXXXXXX)
echo "$(date +"%Y-%m-%d %T"): Testing this package: $TEST_TARGET_SO"
echo "$(date +"%Y-%m-%d %T"): Test version command"
reported_version_string=$( $TEST_TARGET_SO version )
echo "$(date +"%Y-%m-%d %T"): Version reported is: ${reported_version_string}"
echo "$(date +"%Y-%m-%d %T"): Cloning repositories into: $CERC_REPO_BASE_DIR"
$TEST_TARGET_SO --stack fixturenet-eth setup-repositories
echo "$(date +"%Y-%m-%d %T"): Building containers"
$TEST_TARGET_SO --stack fixturenet-eth build-containers
echo "$(date +"%Y-%m-%d %T"): Starting stack"
$TEST_TARGET_SO --stack fixturenet-eth deploy up
echo "$(date +"%Y-%m-%d %T"): Stack started"
# Verify that the fixturenet is up and running
$TEST_TARGET_SO --stack fixturenet-eth deploy ps
# echo "$(date +"%Y-%m-%d %T"): Getting stack status"
# $TEST_TARGET_SO --stack fixturenet-eth deploy exec fixturenet-eth-bootnode-lighthouse /scripts/status-internal.sh
timeout=900 # 15 minutes
echo "$(date +"%Y-%m-%d %T"): Getting initial block number. Timeout set to $timeout seconds"
start_time=$(date +%s)
elapsed_time=0
initial_block_number=0
while [ "$initial_block_number" -eq 0 ] && [ $elapsed_time -lt $timeout ]; do
sleep 10
echo "$(date +"%Y-%m-%d %T"): Waiting for initial block..."
initial_block_number=$($TEST_TARGET_SO --stack fixturenet-eth deploy exec foundry "cast block-number")
current_time=$(date +%s)
elapsed_time=$((current_time - start_time))
done
subsequent_block_number=$initial_block_number
# if initial block was 0 after timeout, assume chain did not start successfully and skip finding subsequent block
if [[ $initial_block_number -gt 0 ]]; then
timeout=300
echo "$(date +"%Y-%m-%d %T"): Getting subsequent block number. Timeout set to $timeout seconds"
start_time=$(date +%s)
elapsed_time=0
# wait for 5 blocks or timeout
while [ "$subsequent_block_number" -le $((initial_block_number + 5)) ] && [ $elapsed_time -lt $timeout ]; do
sleep 10
echo "$(date +"%Y-%m-%d %T"): Waiting for five blocks or $timeout seconds..."
subsequent_block_number=$($TEST_TARGET_SO --stack fixturenet-eth deploy exec foundry "cast block-number")
current_time=$(date +%s)
elapsed_time=$((current_time - start_time))
done
fi
# will return 0 if either of the above loops timed out
block_number_difference=$((subsequent_block_number - initial_block_number))
echo "$(date +"%Y-%m-%d %T"): Results of block height queries:"
echo "Initial block height: $initial_block_number"
echo "Subsequent block height: $subsequent_block_number"
# Block height difference should be between 1 and some small number
if [[ $block_number_difference -gt 1 && $block_number_difference -lt 100 ]]; then
echo "Test passed"
test_result=0
else
echo "Test failed: block numbers were ${initial_block_number} and ${subsequent_block_number}"
echo "Logs from stack:"
$TEST_TARGET_SO --stack fixturenet-eth deploy logs
test_result=1
fi
$TEST_TARGET_SO --stack fixturenet-eth deploy down --delete-volumes
echo "$(date +"%Y-%m-%d %T"): Removing cloned repositories"
rm -rf $CERC_REPO_BASE_DIR
echo "$(date +"%Y-%m-%d %T"): Test finished"
exit $test_result
+13 -2
View File
@@ -4,6 +4,10 @@ if [ -n "$CERC_SCRIPT_DEBUG" ]; then
set -x
fi
set_ownership () {
sudo chown $USER: -R $1
}
node_count=4
node_dir_prefix="laconic-network-dir"
chain_id="laconic_81337-6"
@@ -15,7 +19,7 @@ do
node_network_dir=${node_dir_prefix}${i}
if [[ -d $node_network_dir ]]; then
echo "Deleting ${node_network_dir}"
rm -rf ${node_network_dir}
sudo rm -rf ${node_network_dir}
fi
done
echo "Deleting any existing deployments..."
@@ -39,6 +43,7 @@ do
node_network_dir=${node_dir_prefix}${i}
node_moniker=${node_moniker_prefix}${i}
laconic-so --stack mainnet-laconic deploy setup --network-dir ${node_network_dir} --initialize-network --chain-id ${chain_id} --node-moniker ${node_moniker}
set_ownership ${node_network_dir}
done
echo "Joining ${node_count} nodes to the network..."
@@ -47,6 +52,7 @@ do
node_network_dir=${node_dir_prefix}${i}
node_moniker=${node_moniker_prefix}${i}
laconic-so --stack mainnet-laconic deploy setup --network-dir ${node_network_dir} --join-network --key-name ${node_moniker}
set_ownership ${node_network_dir}
done
echo "Merging ${node_count} nodes genesis txns..."
@@ -57,11 +63,15 @@ for (( i=2 ; i<=$node_count ; i++ ));
do
node_network_dir=${node_dir_prefix}${i}
node_gentx_file=$(ls ${node_network_dir}/config/gentx/*.json)
node_gentx_address=$(grep address ${node_network_dir}/config/genesis.json | head -1 | cut -d '"' -f 4)
gentx_files+=${delimeter}${node_gentx_file}
gentx_addresses+=${delimeter}${node_gentx_address}
delimeter=","
done
echo "gentx files:"
echo ${gentx_files}
# Generate the genesis file on node 1
laconic-so --stack mainnet-laconic deploy setup --network-dir ${node_dir_prefix}1 --create-network --gentx-files ${gentx_files}
laconic-so --stack mainnet-laconic deploy setup --network-dir ${node_dir_prefix}1 --create-network --gentx-files ${gentx_files} --gentx-addresses ${gentx_addresses}
genesis_file=${node_dir_prefix}1/config/genesis.json
# Now import the genesis file to the other nodes
for (( i=2 ; i<=$node_count ; i++ ));
@@ -69,6 +79,7 @@ do
echo "Importing genesis.json into node ${i}"
node_network_dir=${node_dir_prefix}${i}
laconic-so --stack mainnet-laconic deploy setup --network-dir ${node_network_dir} --create-network --genesis-file ${genesis_file}
set_ownership ${node_network_dir}
done
# Create deployments