From b581b6e001e51f4888db5a2b89f554fe79d4c1a1 Mon Sep 17 00:00:00 2001 From: Patrick Niemeyer Date: Thu, 16 Nov 2023 23:41:15 -0600 Subject: [PATCH] str: Work in progress on Orchid Storage framework. --- str-twincoding/.gitignore | 9 +- str-twincoding/README-in.md | 75 +++++ str-twincoding/README.md | 305 ++++++++++++++++-- str-twincoding/README.md.in | 44 --- str-twincoding/build_readme.sh | 31 +- str-twincoding/clean.sh | 2 +- str-twincoding/config.py | 92 ------ str-twincoding/docs/.gitignore | 1 + str-twincoding/docs/monitor.png | Bin 0 -> 199717 bytes str-twincoding/docs/usage.sh | 32 ++ str-twincoding/{ => encoding}/chunks.py | 19 +- str-twincoding/{ => encoding}/file_decoder.py | 49 ++- str-twincoding/{ => encoding}/file_encoder.py | 32 +- .../{ => encoding}/node_recovery_client.py | 50 ++- .../{ => encoding}/node_recovery_source.py | 39 ++- str-twincoding/{ => encoding}/tests.py | 3 - str-twincoding/{ => encoding}/twin_coding.py | 0 .../{ => encoding}/twin_coding_batched.py | 0 str-twincoding/env.sh | 6 + str-twincoding/examples.sh | 43 --- str-twincoding/examples/.gitignore | 2 + str-twincoding/examples/clean.sh | 1 + str-twincoding/examples/examples.sh | 79 +++++ str-twincoding/examples/test-cluster.sh | 118 +++++++ str-twincoding/examples/test-content.sh | 19 ++ str-twincoding/examples/test-discover.sh | 47 +++ str-twincoding/monitor.sh | 3 + str-twincoding/monitor/file_table.py | 103 ++++++ str-twincoding/monitor/monitor_cli.py | 288 +++++++++++++++++ str-twincoding/monitor/monitor_config.py | 15 + str-twincoding/monitor/server_table.py | 115 +++++++ str-twincoding/requirements.txt | 5 +- str-twincoding/server.conf | 19 -- str-twincoding/server.py | 16 - str-twincoding/server.sh | 10 + str-twincoding/server/cluster.jsonc | 13 + str-twincoding/server/server.jsonc | 4 + str-twincoding/server/server_api.py | 66 ++++ str-twincoding/server/server_args.py | 38 +++ str-twincoding/server/server_cli.py | 8 + str-twincoding/server/server_config.py | 65 ++++ str-twincoding/storage.py | 97 ------ str-twincoding/storage.sh | 4 +- str-twincoding/storage/config.jsonc | 8 + str-twincoding/storage/config.py | 57 ++++ str-twincoding/storage/repository.py | 105 ++++++ str-twincoding/storage/storage_cli.py | 245 ++++++++++++++ str-twincoding/storage/util.py | 53 +++ str-twincoding/tests.sh | 2 +- str-twincoding/util.py | 21 -- 50 files changed, 2024 insertions(+), 434 deletions(-) create mode 100644 str-twincoding/README-in.md delete mode 100644 str-twincoding/README.md.in delete mode 100644 str-twincoding/config.py create mode 100644 str-twincoding/docs/.gitignore create mode 100644 str-twincoding/docs/monitor.png create mode 100644 str-twincoding/docs/usage.sh rename str-twincoding/{ => encoding}/chunks.py (88%) rename str-twincoding/{ => encoding}/file_decoder.py (79%) rename str-twincoding/{ => encoding}/file_encoder.py (81%) rename str-twincoding/{ => encoding}/node_recovery_client.py (65%) rename str-twincoding/{ => encoding}/node_recovery_source.py (66%) rename str-twincoding/{ => encoding}/tests.py (77%) rename str-twincoding/{ => encoding}/twin_coding.py (100%) rename str-twincoding/{ => encoding}/twin_coding_batched.py (100%) create mode 100755 str-twincoding/env.sh delete mode 100644 str-twincoding/examples.sh create mode 100644 str-twincoding/examples/.gitignore create mode 100755 str-twincoding/examples/clean.sh create mode 100755 str-twincoding/examples/examples.sh create mode 100755 str-twincoding/examples/test-cluster.sh create mode 100755 str-twincoding/examples/test-content.sh create mode 100755 str-twincoding/examples/test-discover.sh create mode 100755 str-twincoding/monitor.sh create mode 100644 str-twincoding/monitor/file_table.py create mode 100644 str-twincoding/monitor/monitor_cli.py create mode 100644 str-twincoding/monitor/monitor_config.py create mode 100644 str-twincoding/monitor/server_table.py delete mode 100644 str-twincoding/server.conf delete mode 100644 str-twincoding/server.py create mode 100755 str-twincoding/server.sh create mode 100644 str-twincoding/server/cluster.jsonc create mode 100644 str-twincoding/server/server.jsonc create mode 100644 str-twincoding/server/server_api.py create mode 100644 str-twincoding/server/server_args.py create mode 100644 str-twincoding/server/server_cli.py create mode 100644 str-twincoding/server/server_config.py delete mode 100644 str-twincoding/storage.py create mode 100644 str-twincoding/storage/config.jsonc create mode 100644 str-twincoding/storage/config.py create mode 100644 str-twincoding/storage/repository.py create mode 100644 str-twincoding/storage/storage_cli.py create mode 100644 str-twincoding/storage/util.py delete mode 100644 str-twincoding/util.py diff --git a/str-twincoding/.gitignore b/str-twincoding/.gitignore index de3a1206d..94fed7b0a 100644 --- a/str-twincoding/.gitignore +++ b/str-twincoding/.gitignore @@ -4,8 +4,11 @@ venv file_1MB.dat *.encoded *.dat -recovered a.py +a.sh +a.svg +recovered twin_coding_nogf.py - - +output.html +repository +providers.jsonc diff --git a/str-twincoding/README-in.md b/str-twincoding/README-in.md new file mode 100644 index 000000000..40080fa7b --- /dev/null +++ b/str-twincoding/README-in.md @@ -0,0 +1,75 @@ + +## Orchid Storage Project + +_*Orchid Storage is a **work in progress*** - Orchid is an open source project. Help +us build a truly decentralized storage system._ + +***TODO: Link to whitepaper*** + +This repository contains work in progress on the file encoding CLI and server framework. + +![monitor](docs/monitor.png "Monitor") + +A key aspect of the Orchid Storage project is the development of an efficient encoding scheme that minimizes +bandwidth costs incurred during migration of distributed data through providers over time. + +**Twin Coding** is a hybrid encoding scheme that works with any two linear coding schemes and combines +them to achieve a space-bandwidth tradeoff, minimizing the amount of data that must be transferred +between storage nodes in order to recover a lost shard of data. In contrast to a traditional +erasure scheme, in which restoration of a lost node requires a full reconstruction of the original +file, Twin Coding allows for the recovery of a lost data shard with data transfer totalling exactly +the size of the lost data shard, with no additional transfer overhead. + + +This repository contains an implementation of Twin Coding, as well as a command line API for encoding +files, decoding files with erasures, and optimally recovering lost shards. There is also a + +See [`twin_coding.py`](encoding/twin_coding.py) for an explanation of the algorithm, example code, and a link to the original paper. + + +## Installation + +``` +# Create a virtual environment +python3 -m venv venv +``` + +``` +# Activate the virtual environment +# For macOS and Linux: +source venv/bin/activate +# For Windows: +.\venv\Scripts\activate +``` + +``` +# Install the dependencies +pip install -r requirements.txt +``` + +## Example Usage +``` +INSERT_USAGE +``` + +## Encoding CLI Examples + +See also [`examples.sh`](examples/examples.sh) + +``` +INSERT_EXAMPLES +``` + +## CLI Docs + +INSERT_STORAGE_DOCS + +## Server Docs +``` +INSERT_SERVER_DOCS +``` + +## Monitor Docs +``` +INSERT_MONITOR_DOCS +``` diff --git a/str-twincoding/README.md b/str-twincoding/README.md index b61d267bd..e041d6dc6 100644 --- a/str-twincoding/README.md +++ b/str-twincoding/README.md @@ -1,17 +1,30 @@ -## About +## Orchid Storage Project -Twin Coding is a hybrid encoding scheme that works with any two linear coding schemes and combines +_*Orchid Storage is a **work in progress*** - Orchid is an open source project. Help +us build a truly decentralized storage system._ + +***TODO: Link to whitepaper*** + +This repository contains work in progress on the file encoding CLI and server framework. + +![monitor](docs/monitor.png "Monitor") + +A key aspect of the Orchid Storage project is the development of an efficient encoding scheme that minimizes +bandwidth costs incurred during migration of distributed data through providers over time. + +**Twin Coding** is a hybrid encoding scheme that works with any two linear coding schemes and combines them to achieve a space-bandwidth tradeoff, minimizing the amount of data that must be transferred between storage nodes in order to recover a lost shard of data. In contrast to a traditional erasure scheme, in which restoration of a lost node requires a full reconstruction of the original file, Twin Coding allows for the recovery of a lost data shard with data transfer totalling exactly the size of the lost data shard, with no additional transfer overhead. + This repository contains an implementation of Twin Coding, as well as a command line API for encoding -files, decoding files with erasures, and optimally recovering lost shards. +files, decoding files with erasures, and optimally recovering lost shards. There is also a -See `twin_coding.py` for an explanation of the algorithm, example code, and a link to the original paper. +See [`twin_coding.py`](encoding/twin_coding.py) for an explanation of the algorithm, example code, and a link to the original paper. ## Installation @@ -34,53 +47,289 @@ source venv/bin/activate pip install -r requirements.txt ``` -## Usage +## Example Usage +``` +# Generate some test files +test-content.sh + +# Import a file into the default local repository with default encoding +storage.sh import data/foo_file.dat + +# List the repository +storage.sh repo list + +# Start a test provider server cluster +test-cluster.sh start 5001 5002 5003 5004 5005 + +# Confirm that the test servers are running +test-cluster.sh list + +# "Discover" these providers, adding them to our known provider list +# This will normally be done via the directory service and performed at file push time. +test-discover.sh 5001 5002 5003 5004 5005 + +# Start the monitor application (in another window) +monitor.sh --update 1 + +# Push the file by name +storage.sh push foo_file.dat -See also `examples.sh`. +# TODO: +# Monitor file availability while: +# Observing resilient upload progress +# Killing servers and prompting efficient rebuilds +# Shut downt the servers +test-cluster.sh stop ``` -# Generate some random data -dd if=/dev/urandom of="file_1KB.dat" bs=1K count=1 +## Encoding CLI Examples +See also [`examples.sh`](examples/examples.sh) + +``` # Encode a file, writing n files for each of the two node types to a ".encoded" directory. -./storage.sh encode \ - --path "file_1KB.dat" \ +encoded_file_path=$(storage.sh repo --path "$repository" file_path --file "$file") +storage.sh encode \ + --path "$file" \ + --output_path "$encoded_file_path" \ --encoding0 reed_solomon --k0 3 --n0 5 \ --encoding1 reed_solomon --k1 3 --n1 5 \ --overwrite +# This import command is equivalent to the above encode, usign the default repository path and encoding type. +storage.sh import "$file" + +# List files in the repository. +storage.sh repo --path "$repository" list + # Decode a file from an encoded storage directory, tolerant of missing files (erasures). -./storage.sh decode \ - --encoded "file_1KB.dat.encoded" \ - --recovered "recovered.dat" \ +recovered_file=$(storage.sh repo --path "$repository" tmp_file_path --file "recovered_${file}") +storage.sh decode \ + --encoded "$encoded_file_path" \ + --recovered "$recovered_file" \ --overwrite # Compare the original and decoded files. -cmp -s "file_1KB.dat" "recovered.dat" && echo "Passed" || echo "Failed" +cmp -s "$file" "$recovered_file" && echo "Passed" || echo "Failed" -# Generate shard recovery files for restoration of node type 1 index 0, using 3 (k) type 0 -# node sources (helper nodes), -for helper_node in 0 1 2 +# Prepare node recovery: Generate shard recovery source files for restoration of +# node type 1 index 0, using 3 (k) type 0 node sources (helper nodes), +recover_node_type=1 +recover_node_index=0 +for helper_node_index in 0 1 2 do -./storage.sh generate_recovery_file \ - --recover_node_index 0 \ - --recover_encoding reed_solomon --k 3 --n 5 \ - --data_path "file_1KB.dat.encoded/type0_node${helper_node}.dat" \ - --output_path "recover_type1_node0/recover_${helper_node}.dat" \ - --overwrite + helper_node_type=0 + helper_shard_file=$(storage.sh repo --path "$repository" shard_path \ + --file "$file" --node_type $helper_node_type --node_index $helper_node_index) + recovery_source_file=$(storage.sh repo --path "$repository" recovery_file_path \ + --file "$file" --recover_node_type $recover_node_type --recover_node_index $recover_node_index \ + --helper_node_index "$helper_node_index") + storage.sh generate_recovery_file \ + --recover_node_type $recover_node_type \ + --recover_node_index $recover_node_index \ + --recover_encoding reed_solomon --k 3 --n 5 \ + --data_path "$helper_shard_file" \ + --output_path "$recovery_source_file" \ + --overwrite done -# Recover the shard for node type 1 index 0 from the k (3) recovery files. -./storage.sh recover_node \ + +# Complete node recovery: Recover the shard for node type 1 index 0 from the k (3) recovery files. +recovered_shard_file=$(storage.sh repo --path "$repository" tmp_file_path \ + --file "recovered_${file}_type${recover_node_type}_node${recover_node_index}.dat") +storage.sh recover_node \ --k 3 --n 5 --encoding reed_solomon \ - --files_dir "recover_type1_node0" \ - --output_path "recovered_type1_0.dat" \ + --recover_node_type $recover_node_type \ + --recover_node_index $recover_node_index \ + --files_dir "$encoded_file_path" \ + --output_path "$recovered_shard_file" \ --overwrite # Compare the original and recovered data shards. -cmp -s "file_1KB.dat.encoded/type1_node0.dat" "recovered_type1_0.dat" && echo "Passed" || echo "Failed" +original_shard_file=$(storage.sh repo --path "$repository" shard_path \ + --file "$file" --node_type 1 --node_index 0) +cmp -s "$original_shard_file" "$recovered_shard_file" && echo "Passed" || echo "Failed" + +``` + +## CLI Docs +###`repo` ``` +usage: storage repo [-h] [--path PATH] REPO_COMMAND ... +positional arguments: + REPO_COMMAND Repository path commands available. + list List files in the repository. + file_path Get the path to an encoded file. + shard_path Get the path to a shard of the encoded file. + recovery_file_path + Get the path for a recovery file. + tmp_file_path Get the path for a temporary file. + +options: + -h, --help show this help message and exit + --path PATH Path to the repository. +None +``` +###`encode` +``` +usage: storage encode [-h] --path PATH --output_path OUTPUT_PATH --k0 K0 --n0 + N0 --k1 K1 --n1 N1 [--encoding0 ENCODING0] + [--encoding1 ENCODING1] [--overwrite] + +options: + -h, --help show this help message and exit + --path PATH Path to the file to encode. + --output_path OUTPUT_PATH + Output path for the encoded file. + --k0 K0 k value for node type 0. + --n0 N0 n value for node type 0. + --k1 K1 k value for node type 1. + --n1 N1 n value for node type 1. + --encoding0 ENCODING0 + Encoding for node type 0. + --encoding1 ENCODING1 + Encoding for node type 1. + --overwrite Overwrite existing files. +None +``` +###`decode` +``` +usage: storage decode [-h] --encoded ENCODED --recovered RECOVERED + [--overwrite] + +options: + -h, --help show this help message and exit + --encoded ENCODED Path to the encoded file. + --recovered RECOVERED + Path to the recovered file. + --overwrite Overwrite existing files. +None +``` +###`generate_recovery_file` +``` +usage: storage generate_recovery_file [-h] --recover_node_type + RECOVER_NODE_TYPE --recover_node_index + RECOVER_NODE_INDEX + [--recover_encoding RECOVER_ENCODING] + --k K --n N --data_path DATA_PATH + --output_path OUTPUT_PATH [--overwrite] + +options: + -h, --help show this help message and exit + --recover_node_type RECOVER_NODE_TYPE + Type of the recovering node. + --recover_node_index RECOVER_NODE_INDEX + Index of the recovering node. + --recover_encoding RECOVER_ENCODING + Encoding for the recovering node. + --k K k value for the recovering node. + --n N n value for the recovering node. + --data_path DATA_PATH + Path to the source node data. + --output_path OUTPUT_PATH + Path to the output recovery file. + --overwrite Overwrite existing files. +None +``` +###`recover_node` +``` +usage: storage recover_node [-h] --recover_node_type RECOVER_NODE_TYPE + --recover_node_index RECOVER_NODE_INDEX --k K --n + N [--encoding ENCODING] --files_dir FILES_DIR + --output_path OUTPUT_PATH [--overwrite] + +options: + -h, --help show this help message and exit + --recover_node_type RECOVER_NODE_TYPE + Type of the recovering node. + --recover_node_index RECOVER_NODE_INDEX + Index of the recovering node. + --k K k value for node type. + --n N n value for node type. + --encoding ENCODING Encoding for node type. + --files_dir FILES_DIR + Path to the recovery files. + --output_path OUTPUT_PATH + Path to the recovered file. + --overwrite Overwrite existing files. +None +``` +###`import` +``` +usage: storage import [-h] [--repo REPO] [--k0 K0] [--n0 N0] [--k1 K1] + [--n1 N1] [--encoding0 ENCODING0] + [--encoding1 ENCODING1] [--overwrite] + path + +positional arguments: + path Path to the file to import. + +options: + -h, --help show this help message and exit + --repo REPO Path to the repository. + --k0 K0 k value for node type 0. + --n0 N0 n value for node type 0. + --k1 K1 k value for node type 1. + --n1 N1 n value for node type 1. + --encoding0 ENCODING0 + Encoding for node type 0. + --encoding1 ENCODING1 + Encoding for node type 1. + --overwrite Overwrite existing files. +None +``` +###`push` +``` +usage: storage push [-h] [--repo REPO] [--servers [SERVERS ...]] [--validate] + file + +positional arguments: + file Name of the file in the repository. + +options: + -h, --help show this help message and exit + --repo REPO Path to the repository. + --servers [SERVERS ...] + List of server names or urls to push to. + --validate After push, download and reconstruct the file. +None +``` + +## Server Docs +``` +Using default repository: /Users/pat/Desktop/OrchidProject/lab.orchid.com/orchid/str-twincoding/repository +usage: server_cli.py [-h] [--config CONFIG] [--interface INTERFACE] + [--port PORT] [--repository_dir REPOSITORY_DIR] + [--auth_key AUTH_KEY] [--show_console] + +Flask server with argument parsing + +options: + -h, --help show this help message and exit + --config CONFIG server config file + --interface INTERFACE + Interface the server listens on + --port PORT Port the server listens on + --repository_dir REPOSITORY_DIR + Directory to store repository files + --auth_key AUTH_KEY Authentication key to validate requests + --show_console Flag to show console logs +``` + +## Monitor Docs +``` +usage: monitor_cli.py [-h] [--providers PROVIDERS] [--debug] [--update UPDATE] + +Process command line arguments. + +options: + -h, --help show this help message and exit + --providers PROVIDERS + Providers file path + --debug Show debug + --update UPDATE Update view with polling period seconds +``` diff --git a/str-twincoding/README.md.in b/str-twincoding/README.md.in deleted file mode 100644 index f2bf35d82..000000000 --- a/str-twincoding/README.md.in +++ /dev/null @@ -1,44 +0,0 @@ - -## About - -Twin Coding is a hybrid encoding scheme that works with any two linear coding schemes and combines -them to achieve a space-bandwidth tradeoff, minimizing the amount of data that must be transferred -between storage nodes in order to recover a lost shard of data. In contrast to a traditional -erasure scheme, in which restoration of a lost node requires a full reconstruction of the original -file, Twin Coding allows for the recovery of a lost data shard with data transfer totalling exactly -the size of the lost data shard, with no additional transfer overhead. - -This repository contains an implementation of Twin Coding, as well as a command line API for encoding -files, decoding files with erasures, and optimally recovering lost shards. - -See `twin_coding.py` for an explanation of the algorithm, example code, and a link to the original paper. - - -## Installation - -``` -# Create a virtual environment -python3 -m venv venv -``` - -``` -# Activate the virtual environment -# For macOS and Linux: -source venv/bin/activate -# For Windows: -.\venv\Scripts\activate -``` - -``` -# Install the dependencies -pip install -r requirements.txt -``` - -## Usage - -See also `examples.sh`. - -``` -INSERT_USAGE -``` - diff --git a/str-twincoding/build_readme.sh b/str-twincoding/build_readme.sh index d676b20b0..66b303bd7 100755 --- a/str-twincoding/build_readme.sh +++ b/str-twincoding/build_readme.sh @@ -1 +1,30 @@ -sed '/INSERT_USAGE/r examples.sh' README.md.in | sed '/INSERT_USAGE/d' > README.md +#!/bin/bash + +source "$(dirname "$0")/env.sh" + +# examples +tmp=/tmp/readme.$$ +sed -n '/# START_EXAMPLES/,/# END_EXAMPLES/p' examples/examples.sh | sed '/# START_EXAMPLES/d; /# END_EXAMPLES/d' > $tmp +sed "/INSERT_EXAMPLES/r $tmp" README-in.md | sed '/INSERT_EXAMPLES/d' > README.md + +# usage +sed "/INSERT_USAGE/r docs/usage.sh" README.md | sed '/INSERT_USAGE/d' > out.md +mv out.md README.md + +# storage cli +storage.sh docs > $tmp +sed "/INSERT_STORAGE_DOCS/r $tmp" README.md | sed '/INSERT_STORAGE_DOCS/d' > out.md +mv out.md README.md + +# server cli +server.sh --help > $tmp +sed "/INSERT_SERVER_DOCS/r $tmp" README.md | sed '/INSERT_SERVER_DOCS/d' > out.md +mv out.md README.md + +# monitor cli +monitor.sh --help > $tmp +sed "/INSERT_MONITOR_DOCS/r $tmp" README.md | sed '/INSERT_MONITOR_DOCS/d' > out.md +mv out.md README.md + +rm $tmp + diff --git a/str-twincoding/clean.sh b/str-twincoding/clean.sh index 7cfce6d79..090ad4559 100755 --- a/str-twincoding/clean.sh +++ b/str-twincoding/clean.sh @@ -1,3 +1,3 @@ -rm -rf *.encoded recovered.dat recover_type1_node0 recovered_type1_0.dat file_1KB.dat file_1MB.dat +rm -rf *.encoded recovered.dat file_1KB.dat file_1MB.dat repository encoding/repository server/repository diff --git a/str-twincoding/config.py b/str-twincoding/config.py deleted file mode 100644 index 824240fae..000000000 --- a/str-twincoding/config.py +++ /dev/null @@ -1,92 +0,0 @@ -import commentjson as json -from pydantic import BaseModel - -config_str = """ -{ - "config_version": "1.0", - - # Console - "console": { - "public_url": "http://localhost:8080/", - "port": 8080, - }, - - "encoder": { - # num_cores: 4, - }, - - # Cluster - "cluster": { - "name": "testnet", - "type0": { - "encoding": "reed_solomon", - "k": 3, - "n": 5 - }, - "type1": { - "encoding": "reed_solomon", - "k": 3, - "n": 5 - }, - }, -} -""" - - -class NodeType(BaseModel): - encoding: str - k: int - n: int - transpose: bool = None - - -class NodeType0(NodeType): - transpose: bool = False - - -class NodeType1(NodeType): - transpose: bool = True - - -class Console(BaseModel): - public_url: str - port: int - - -class Cluster(BaseModel): - name: str - type0: NodeType0 - type1: NodeType1 - - -class Config(BaseModel): - config_version: str - console: Console - cluster: Cluster - - -class EncodedFileConfig(BaseModel): - name: str - type0: NodeType0 - type1: NodeType1 - file_length: int - # file_hash: str - - -def load_config(file_path): - # with open(file_path, 'r') as f: - # config_dict = json.load(f) - print("Loading internal config") - config_dict = json.loads(config_str) - return Config(**config_dict) - - -def load_file_config(path) -> EncodedFileConfig: - with open(path, 'rb') as f: - config_dict = json.load(f) - return EncodedFileConfig(**config_dict) - - -if __name__ == '__main__': - config = load_config('config.json') - print(f"config version: {config.config_version}") diff --git a/str-twincoding/docs/.gitignore b/str-twincoding/docs/.gitignore new file mode 100644 index 000000000..318ef5dc1 --- /dev/null +++ b/str-twincoding/docs/.gitignore @@ -0,0 +1 @@ +monitor.org.png diff --git a/str-twincoding/docs/monitor.png b/str-twincoding/docs/monitor.png new file mode 100644 index 0000000000000000000000000000000000000000..56d618d87dfc05aa6c4a51fabe4ebf53ad41a8c3 GIT binary patch literal 199717 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

q{~=9d|6qCA9ZW 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∾9U2_h|D+nl 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 literal 0 HcmV?d00001 diff --git a/str-twincoding/docs/usage.sh b/str-twincoding/docs/usage.sh new file mode 100644 index 000000000..64e60933b --- /dev/null +++ b/str-twincoding/docs/usage.sh @@ -0,0 +1,32 @@ +# Generate some test files +test-content.sh + +# Import a file into the default local repository with default encoding +storage.sh import data/foo_file.dat + +# List the repository +storage.sh repo list + +# Start a test provider server cluster +test-cluster.sh start 5001 5002 5003 5004 5005 + +# Confirm that the test servers are running +test-cluster.sh list + +# "Discover" these providers, adding them to our known provider list +# This will normally be done via the directory service and performed at file push time. +test-discover.sh 5001 5002 5003 5004 5005 + +# Start the monitor application (in another window) +monitor.sh --update 1 + +# Push the file by name +storage.sh push foo_file.dat + +# TODO: +# Monitor file availability while: +# Observing resilient upload progress +# Killing servers and prompting efficient rebuilds + +# Shut downt the servers +test-cluster.sh stop diff --git a/str-twincoding/chunks.py b/str-twincoding/encoding/chunks.py similarity index 88% rename from str-twincoding/chunks.py rename to str-twincoding/encoding/chunks.py index f85bb7a91..8aae00f15 100644 --- a/str-twincoding/chunks.py +++ b/str-twincoding/encoding/chunks.py @@ -1,9 +1,13 @@ -import math +import io import os import time +from typing import Optional + +import math import numpy as np from tqdm import tqdm + # Note: We will parallelize these in a future update. Lots of opportunity here to read chunks # Note: in batches and perform encoding/decoding in parallel. @@ -93,3 +97,16 @@ def update_pbar(self, ci: int, num_files: int, pbar: tqdm, start: float): rate = ci * self.chunk_size * num_files / (time.time() - start) pbar.set_postfix({"Rate": f"{rate / (1024 * 1024):.4f}MB/s"}, refresh=True) pbar.update(1) + + +def open_output_file(output_path: str, overwrite: bool) -> Optional[io.BufferedWriter]: + if not overwrite and os.path.exists(output_path): + print(f"Output file already exists: {output_path}.") + return None + + # Make intervening directories if needeed + directory = os.path.dirname(output_path) + if directory: + os.makedirs(directory, exist_ok=True) + + return io.BufferedWriter(open(output_path, 'wb')) diff --git a/str-twincoding/file_decoder.py b/str-twincoding/encoding/file_decoder.py similarity index 79% rename from str-twincoding/file_decoder.py rename to str-twincoding/encoding/file_decoder.py index 2ec318723..120b8614d 100644 --- a/str-twincoding/file_decoder.py +++ b/str-twincoding/encoding/file_decoder.py @@ -1,6 +1,5 @@ import filecmp import os -import re import time import uuid from collections import OrderedDict @@ -8,10 +7,12 @@ import numpy as np from tqdm import tqdm -from chunks import ChunksReader -from config import NodeType, load_file_config -from twin_coding import rs_generator_matrix -from util import open_output_file, assert_rs +from storage.config import NodeType, EncodedFileConfig +from storage.util import assert_rs +from storage.repository import Repository + +from encoding.chunks import ChunksReader, open_output_file +from encoding.twin_coding import rs_generator_matrix # Decode a set of erasure-coded files supplied as a file map or from a storage-encoded directory. @@ -53,9 +54,9 @@ def __init__(self, # at least k files of the same type. @staticmethod def from_encoded_dir(path: str, output_path: str = None, overwrite: bool = False): - file_config = load_file_config(f'{path}/config.json') + file_config = EncodedFileConfig.load(os.path.join(path, 'config.json')) assert file_config.type0.k == file_config.type1.k, "Config node types must have the same k." - recover_from_files = FileDecoder.map_files(path, k=file_config.type0.k) + recover_from_files = FileDecoder.get_threshold_files(path, k=file_config.type0.k) if os.path.basename(list(recover_from_files)[0]).startswith("type0_"): node_type = file_config.type0 else: @@ -72,21 +73,13 @@ def from_encoded_dir(path: str, output_path: str = None, overwrite: bool = False # Map the files in a file store encoded directory. At least k files of the same type must be present # to succeed. Returns a map of the first k files of either type found. - @staticmethod - def map_files(files_dir: str, k: int) -> dict[str, int]: - type0_files, type1_files = {}, {} - for filename in os.listdir(files_dir): - match = re.match(r'type([01])_node(\d+).dat', filename) - if not match: - continue - type_no, index_no = int(match.group(1)), int(match.group(2)) - files = type0_files if type_no == 0 else type1_files - files[os.path.join(files_dir, filename)] = index_no - + @classmethod + def get_threshold_files(cls, files_dir: str, k: int) -> dict[str, int]: + type0_files, type1_files = Repository.map_files(files_dir) if len(type0_files) >= k: - return OrderedDict(sorted(type0_files.items(), key=lambda x: x[1])[:k]) + return OrderedDict(list(type0_files.items())[:k]) elif len(type1_files) >= k: - return OrderedDict(sorted(type1_files.items(), key=lambda x: x[1])[:k]) + return OrderedDict(list(type1_files.items())[:k]) else: raise ValueError( f"Insufficient files in {files_dir} to recover: {len(type0_files)} type 0 files, " @@ -131,13 +124,17 @@ def close(self): if __name__ == '__main__': - file = 'file_1KB.dat' - encoded = f'{file}.encoded' - recovered = 'recovered.dat' + repo = Repository('./repository') + filename = 'file_1KB.dat' + original_file = repo.tmp_file_path(filename) + encoded_file = repo.file_path(filename) + print(repo.status_str(filename)) + + recovered_file = repo.tmp_file_path(f'recovered_{filename}') decoder = FileDecoder.from_encoded_dir( - path=encoded, - output_path=recovered, + path=encoded_file, + output_path=recovered_file, overwrite=True ) decoder.decode() - print("Passed" if filecmp.cmp(file, recovered) else "Failed") + print("Passed" if filecmp.cmp(original_file, recovered_file) else "Failed") diff --git a/str-twincoding/file_encoder.py b/str-twincoding/encoding/file_encoder.py similarity index 81% rename from str-twincoding/file_encoder.py rename to str-twincoding/encoding/file_encoder.py index b6a5c681a..e9d508ee7 100644 --- a/str-twincoding/file_encoder.py +++ b/str-twincoding/encoding/file_encoder.py @@ -1,12 +1,13 @@ import os from contextlib import ExitStack import galois -from chunks import ChunkReader -from config import EncodedFileConfig, NodeType0, NodeType1 -from twin_coding import rs_generator_matrix, Code, twin_code +from encoding.chunks import ChunkReader +from encoding.twin_coding import rs_generator_matrix, Code, twin_code +from storage.config import EncodedFileConfig, NodeType0, NodeType1 +from storage.repository import Repository +from storage.util import assert_rs from tqdm import tqdm import time -from util import assert_rs # Erasure code a file into two sets of shards, one for each node type in the twin coding scheme. @@ -24,22 +25,23 @@ class FileEncoder(ChunkReader): def __init__(self, node_type0: NodeType0, node_type1: NodeType1, - path: str, + input_file: str, output_path: str = None, overwrite: bool = False): assert_rs(node_type0) assert_rs(node_type1) assert node_type0.k == node_type1.k, "The two node types must have the same k." + assert node_type0.n > node_type0.k and node_type1.n > node_type1.k, "The node type must have n > k." self.node_type0 = node_type0 self.node_type1 = node_type1 self.k = node_type0.k - self.path = path - self.output_dir = output_path or path + '.encoded' + self.path = input_file + self.output_dir = output_path or input_file + '.encoded' self.overwrite = overwrite chunk_size = self.k ** 2 - super().__init__(path=path, chunk_size=chunk_size) + super().__init__(path=input_file, chunk_size=chunk_size) # Initialize the output directory that will hold the erasure-encoded chunks. def init_output_dir(self) -> bool: @@ -109,11 +111,21 @@ def close(self): if __name__ == '__main__': - path = 'file_1KB.dat' + repo = Repository('./repository') + + # Random test file + filename = 'file_1KB.dat' + file = repo.tmp_file_path(filename) + # if the file doesn't exist create it + if not os.path.exists(file): + with open(file, "wb") as f: + f.write(os.urandom(1024)) + encoder = FileEncoder( node_type0=NodeType0(k=3, n=5, encoding='reed_solomon'), node_type1=NodeType1(k=3, n=5, encoding='reed_solomon'), - path=path, + input_file=file, + output_path=repo.file_path(filename, expected=False), overwrite=True ) encoder.encode() diff --git a/str-twincoding/node_recovery_client.py b/str-twincoding/encoding/node_recovery_client.py similarity index 65% rename from str-twincoding/node_recovery_client.py rename to str-twincoding/encoding/node_recovery_client.py index 8797042ba..07644df66 100644 --- a/str-twincoding/node_recovery_client.py +++ b/str-twincoding/encoding/node_recovery_client.py @@ -1,15 +1,18 @@ import filecmp import os +import re import time import uuid from collections import OrderedDict import galois import numpy as np from tqdm import tqdm -from chunks import ChunksReader -from config import NodeType -from twin_coding import rs_generator_matrix -from util import assert_rs, open_output_file + +from encoding.chunks import ChunksReader, open_output_file +from encoding.twin_coding import rs_generator_matrix +from storage.config import NodeType, NodeType1 +from storage.repository import Repository +from storage.util import assert_rs # Consume recovery files from k nodes of the opposite type to recover a lost node's data. @@ -39,12 +42,18 @@ def __init__(self, # Map recovery files in a directory. Exactly k recovery files should be present. @staticmethod - def map_files(files_dir: str, k: int) -> dict[str, int]: + def map_files(files_dir: str, + recover_node_type: int, + recover_node_index: int, + k: int) -> dict[str, int]: files = {} - + prefix = f"recover_type{recover_node_type}_node{recover_node_index}" for filename in os.listdir(files_dir): - if filename.startswith("recover_") and filename.endswith(".dat"): - index = int(filename.split("_")[1].split(".")[0]) + if filename.startswith(prefix) and filename.endswith(".dat"): + match = re.search(r"from(\d+)", filename) + index = int(match.group(1)) if match else None + if index is None: + continue files[os.path.join(files_dir, filename)] = index assert len(files) == k, "Exactly k recovery files must be present." @@ -76,16 +85,27 @@ def recover_node(self): if __name__ == '__main__': + file = 'file_1KB.dat' + repo = Repository('./repository') + # Use recovery files generated for type 1 node index 0 to recover the lost data shard. - recovery_files_dir = 'recover_type1_node0' - recovered = 'recovered_type1_node0.dat' + recovery_files_dir = repo.file_path(file) + recover_node_type = 1 + recover_node_index = 0 + recovered_shard = repo.tmp_file_path( + f'recovered_{file}_type{recover_node_type}_node{recover_node_index}.dat') + NodeRecoveryClient( - recovery_source_node_type=NodeType(k=3, n=5, encoding='reed_solomon'), - file_map=NodeRecoveryClient.map_files(files_dir=recovery_files_dir, k=3), - output_path=recovered, + recovery_source_node_type=NodeType1(k=3, n=5, encoding='reed_solomon'), + file_map=NodeRecoveryClient.map_files( + files_dir=recovery_files_dir, + recover_node_type=recover_node_type, + recover_node_index=recover_node_index, + k=3), + output_path=recovered_shard, overwrite=True ).recover_node() - compare_file = 'file_1KB.dat.encoded/type1_node0.dat' - print("Passed" if filecmp.cmp(compare_file, recovered) else "Failed") + original_shard = repo.shard_path(file, node_type=1, node_index=0) + print("Passed" if filecmp.cmp(original_shard, recovered_shard) else "Failed") ... diff --git a/str-twincoding/node_recovery_source.py b/str-twincoding/encoding/node_recovery_source.py similarity index 66% rename from str-twincoding/node_recovery_source.py rename to str-twincoding/encoding/node_recovery_source.py index 5fe407eb2..d0b83ddef 100644 --- a/str-twincoding/node_recovery_source.py +++ b/str-twincoding/encoding/node_recovery_source.py @@ -2,10 +2,10 @@ import galois from tqdm import tqdm -from chunks import ChunkReader -from config import NodeType, NodeType1 -from twin_coding import rs_generator_matrix -from util import open_output_file +from storage.config import NodeType, NodeType1 +from storage.repository import Repository +from encoding.chunks import ChunkReader, open_output_file +from encoding.twin_coding import rs_generator_matrix # Generate a node recovery file for a specified node of the opposite type. @@ -52,13 +52,32 @@ def generate(self): if __name__ == '__main__': - # Use a type 0 node source to generate recovery files for recovering node type 1 index 0. - for i in range(3): + file = 'file_1KB.dat' + repo = Repository('./repository') + + # The recovering node + recover_node_encoding = NodeType1(k=3, n=5, encoding='reed_solomon') + recover_node_index = 0 + + # Use three helper nodes of type 0 to generate recovery files for a node of type 1. + helper_node_type = 0 + for helper_node_index in range(3): + # The input path of the helper node's shard + helper_shard_path = repo.shard_path( + file, node_type=helper_node_type, node_index=helper_node_index) + # The output path of the recovery file + recovery_file_path = repo.recovery_file_path( + file, + recover_node_type=recover_node_encoding.type, + recover_node_index=recover_node_index, + helper_node_index=helper_node_index, + expected=False) + NodeRecoverySource( - recover_node_type=NodeType1(k=3, n=5, encoding='reed_solomon'), - recover_node_index=0, - data_path=f'file_1KB.dat.encoded/type0_node{i}.dat', - output_path=f'recover_type1_node0/recover_{i}.dat', + recover_node_type=recover_node_encoding, + recover_node_index=recover_node_index, + data_path=helper_shard_path, + output_path=recovery_file_path, overwrite=True ).generate() ... diff --git a/str-twincoding/tests.py b/str-twincoding/encoding/tests.py similarity index 77% rename from str-twincoding/tests.py rename to str-twincoding/encoding/tests.py index 37714241e..c6d371201 100644 --- a/str-twincoding/tests.py +++ b/str-twincoding/encoding/tests.py @@ -1,11 +1,8 @@ -import os import runpy # # See also `examples.sh` # -with open("file_1KB.dat", "wb") as f: - f.write(os.urandom(1024)) runpy.run_module('file_encoder', run_name='__main__') runpy.run_module('file_decoder', run_name='__main__') diff --git a/str-twincoding/twin_coding.py b/str-twincoding/encoding/twin_coding.py similarity index 100% rename from str-twincoding/twin_coding.py rename to str-twincoding/encoding/twin_coding.py diff --git a/str-twincoding/twin_coding_batched.py b/str-twincoding/encoding/twin_coding_batched.py similarity index 100% rename from str-twincoding/twin_coding_batched.py rename to str-twincoding/encoding/twin_coding_batched.py diff --git a/str-twincoding/env.sh b/str-twincoding/env.sh new file mode 100755 index 000000000..9f466baac --- /dev/null +++ b/str-twincoding/env.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# Source this file into your shell +export STRHOME=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +source $STRHOME/venv/bin/activate +export PYTHONPATH="$STRHOME" +export PATH=$PATH:"$STRHOME" diff --git a/str-twincoding/examples.sh b/str-twincoding/examples.sh deleted file mode 100644 index 9e8f811f4..000000000 --- a/str-twincoding/examples.sh +++ /dev/null @@ -1,43 +0,0 @@ - -# Generate some random data -dd if=/dev/urandom of="file_1KB.dat" bs=1K count=1 - -# Encode a file, writing n files for each of the two node types to a ".encoded" directory. -./storage.sh encode \ - --path "file_1KB.dat" \ - --encoding0 reed_solomon --k0 3 --n0 5 \ - --encoding1 reed_solomon --k1 3 --n1 5 \ - --overwrite - -# Decode a file from an encoded storage directory, tolerant of missing files (erasures). -./storage.sh decode \ - --encoded "file_1KB.dat.encoded" \ - --recovered "recovered.dat" \ - --overwrite - -# Compare the original and decoded files. -cmp -s "file_1KB.dat" "recovered.dat" && echo "Passed" || echo "Failed" - - -# Generate shard recovery files for restoration of node type 1 index 0, using 3 (k) type 0 -# node sources (helper nodes), -for helper_node in 0 1 2 -do -./storage.sh generate_recovery_file \ - --recover_node_index 0 \ - --recover_encoding reed_solomon --k 3 --n 5 \ - --data_path "file_1KB.dat.encoded/type0_node${helper_node}.dat" \ - --output_path "recover_type1_node0/recover_${helper_node}.dat" \ - --overwrite -done - -# Recover the shard for node type 1 index 0 from the k (3) recovery files. -./storage.sh recover_node \ - --k 3 --n 5 --encoding reed_solomon \ - --files_dir "recover_type1_node0" \ - --output_path "recovered_type1_0.dat" \ - --overwrite - -# Compare the original and recovered data shards. -cmp -s "file_1KB.dat.encoded/type1_node0.dat" "recovered_type1_0.dat" && echo "Passed" || echo "Failed" - diff --git a/str-twincoding/examples/.gitignore b/str-twincoding/examples/.gitignore new file mode 100644 index 000000000..b2c8bf4f3 --- /dev/null +++ b/str-twincoding/examples/.gitignore @@ -0,0 +1,2 @@ +data +repository diff --git a/str-twincoding/examples/clean.sh b/str-twincoding/examples/clean.sh new file mode 100755 index 000000000..8b808bbc8 --- /dev/null +++ b/str-twincoding/examples/clean.sh @@ -0,0 +1 @@ +rm -rf data diff --git a/str-twincoding/examples/examples.sh b/str-twincoding/examples/examples.sh new file mode 100755 index 000000000..009c0093f --- /dev/null +++ b/str-twincoding/examples/examples.sh @@ -0,0 +1,79 @@ +#!/bin/bash +set -euo pipefail + +source "$(dirname "$(readlink -f "$0")")/../env.sh" + +# The repository dir +repository="$STRHOME/repository" +mkdir -p "$repository" + +# Generate some random data +file="file_1KB.dat" +[ -f "$file" ] || dd if=/dev/urandom of="$file" bs=1K count=1 + +# START_EXAMPLES +# Encode a file, writing n files for each of the two node types to a ".encoded" directory. +encoded_file_path=$(storage.sh repo --path "$repository" file_path --file "$file") +storage.sh encode \ + --path "$file" \ + --output_path "$encoded_file_path" \ + --encoding0 reed_solomon --k0 3 --n0 5 \ + --encoding1 reed_solomon --k1 3 --n1 5 \ + --overwrite + +# This import command is equivalent to the above encode, usign the default repository path and encoding type. +storage.sh import "$file" + +# List files in the repository. +storage.sh repo --path "$repository" list + +# Decode a file from an encoded storage directory, tolerant of missing files (erasures). +recovered_file=$(storage.sh repo --path "$repository" tmp_file_path --file "recovered_${file}") +storage.sh decode \ + --encoded "$encoded_file_path" \ + --recovered "$recovered_file" \ + --overwrite + +# Compare the original and decoded files. +cmp -s "$file" "$recovered_file" && echo "Passed" || echo "Failed" + + +# Prepare node recovery: Generate shard recovery source files for restoration of +# node type 1 index 0, using 3 (k) type 0 node sources (helper nodes), +recover_node_type=1 +recover_node_index=0 +for helper_node_index in 0 1 2 +do + helper_node_type=0 + helper_shard_file=$(storage.sh repo --path "$repository" shard_path \ + --file "$file" --node_type $helper_node_type --node_index $helper_node_index) + recovery_source_file=$(storage.sh repo --path "$repository" recovery_file_path \ + --file "$file" --recover_node_type $recover_node_type --recover_node_index $recover_node_index \ + --helper_node_index "$helper_node_index") + storage.sh generate_recovery_file \ + --recover_node_type $recover_node_type \ + --recover_node_index $recover_node_index \ + --recover_encoding reed_solomon --k 3 --n 5 \ + --data_path "$helper_shard_file" \ + --output_path "$recovery_source_file" \ + --overwrite +done + + +# Complete node recovery: Recover the shard for node type 1 index 0 from the k (3) recovery files. +recovered_shard_file=$(storage.sh repo --path "$repository" tmp_file_path \ + --file "recovered_${file}_type${recover_node_type}_node${recover_node_index}.dat") +storage.sh recover_node \ + --k 3 --n 5 --encoding reed_solomon \ + --recover_node_type $recover_node_type \ + --recover_node_index $recover_node_index \ + --files_dir "$encoded_file_path" \ + --output_path "$recovered_shard_file" \ + --overwrite + +# Compare the original and recovered data shards. +original_shard_file=$(storage.sh repo --path "$repository" shard_path \ + --file "$file" --node_type 1 --node_index 0) +cmp -s "$original_shard_file" "$recovered_shard_file" && echo "Passed" || echo "Failed" + +# END_EXAMPLES diff --git a/str-twincoding/examples/test-cluster.sh b/str-twincoding/examples/test-cluster.sh new file mode 100755 index 000000000..4f890e6b2 --- /dev/null +++ b/str-twincoding/examples/test-cluster.sh @@ -0,0 +1,118 @@ +#!/bin/bash + +source "$(dirname "$0")/../env.sh" + +# STRHOME should have been set by env.sh above +if [ -z "$STRHOME" ]; then + echo "STRHOME is not set." + exit 1 +fi +app="$STRHOME/server/server_cli.py" +data="$STRHOME/examples/data" +mkdir -p "$data" + +# Function to start Flask servers +start_servers() { + echo "Starting servers..." + for port in "$@" + do + repo="$data/$port/repo" + log="$data/$port/log" + mkdir -p "$repo" + python "$app" --port $port --repository "$repo" > "$log" 2>&1 & + echo "Started Flask server on port $port with repository $repo" + done +} + +# Function to stop a specific server +stop_server() { + echo "Stopping server on port $1..." + pid=$(lsof -i :$1 -t) + if [ -z "$pid" ]; then + echo "No server found on port $1" + else + kill -9 $pid + echo "Stopped server with PID $pid" + fi +} + +list_all() { + echo "All instances of $app" + + # Print header + printf "%-10s %-10s %-10s\n" "PID" "PORT" "TIME" + + # Find all PIDs for the given process name and extract relevant information + ps auxw | grep "$app" | grep -v grep | awk '{ + pid = $2; + time = $10; + command = $11; + for (i = 12; i <= NF; i++) command = command " " $i; + port = "N/A"; + + # Extract port if present in the command + if (match(command, /--port[= ]+[0-9]+/)) { + port = substr(command, RSTART+7, RLENGTH-7); + } + + printf "%-10s %-10s %-10s\n", pid, port, time + }' +} + +stop_all() { + echo "Killing all instances of $app..." + + # Find all PIDs for the given process name + pids=$(ps auxw | grep "$app" | grep -v grep | awk '{print $2}') + + # Check if any PIDs were found + if [ -z "$pids" ]; then + echo "No processes found with the name $process_name." + return + fi + + # Kill the processes + for pid in $pids + do + kill -9 $pid + echo "Stopped process $pid" + done +} + + +# Check command line arguments +if [ $# -eq 0 ] +then + echo "No arguments provided. Usage: ./script.sh start|stop|kill [ports...]" + exit 1 +fi + +# Main logic +case $1 in + start) + shift + start_servers "$@" + ;; + stop) + shift + if [ $# -eq 0 ]; then + stop_all + else + for port in "$@" + do + stop_server $port + done + fi + ;; + list) + list_all + ;; + stop-all) + stop_all + ;; + *) + echo "Invalid command. Usage: ./script.sh start|stop|stop-all|kill [ports...]" + exit 1 + ;; +esac + diff --git a/str-twincoding/examples/test-content.sh b/str-twincoding/examples/test-content.sh new file mode 100755 index 000000000..34bd88023 --- /dev/null +++ b/str-twincoding/examples/test-content.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -euo pipefail + +source "$(dirname "$0")/../env.sh" +data="$STRHOME/examples/data" +mkdir -p "$data" + +function add_file { + file=$1 + name=$(basename $file) + echo "Generating file $name in data" + [ -f "$file" ] || dd if=/dev/urandom of="$file" bs=1K count=1 status=none +} + +# Add a few test files to the examples data dir +add_file "$data/foo_file.dat" +add_file "$data/bar_file.dat" +add_file "$data/baz_file.dat" + diff --git a/str-twincoding/examples/test-discover.sh b/str-twincoding/examples/test-discover.sh new file mode 100755 index 000000000..feb2895d6 --- /dev/null +++ b/str-twincoding/examples/test-discover.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# +# { +# "providers": [ +# { "name": "5001", "url": "http://localhost:5001" }, +# { "name": "5002", "url": "http://localhost:5002" }, +# { "name": "5003", "url": "http://localhost:5003" } +# ] +# } +# +source "$(dirname "$0")/../env.sh" +providers_file="$STRHOME/providers.jsonc" + +# if no args, print help +if [ $# -eq 0 ]; then + echo "Usage: $0 ..." + exit 1 +fi + +# validate that all args are numbers +for port in "$@" +do + if ! [[ "$port" =~ ^[0-9]+$ ]]; then + echo "Invalid port: $port" + exit 1 + fi +done + +# if the file exists, bail +if [ -f "$providers_file" ]; then + echo "File exists: $providers_file" + exit 1 +fi + +# Generate the file +echo '{' > "$providers_file" +echo ' "providers": [' >> "$providers_file" +for port in "$@" +do + echo ' { "name": "'"$port"'", "url": "http://localhost:'"$port"'" },' >> "$providers_file" +done +# jsonc is tolerant of trailing commas +#sed -i '$ s/,$//' "$providers_file" +echo ' ]' >> "$providers_file" +echo '}' >> "$providers_file" + +echo "Generated file: $providers_file" diff --git a/str-twincoding/monitor.sh b/str-twincoding/monitor.sh new file mode 100755 index 000000000..2b0c8c692 --- /dev/null +++ b/str-twincoding/monitor.sh @@ -0,0 +1,3 @@ +#!/bin/bash +source "$(dirname "$0")/env.sh" +python "$STRHOME/monitor/monitor_cli.py" "$@" diff --git a/str-twincoding/monitor/file_table.py b/str-twincoding/monitor/file_table.py new file mode 100644 index 000000000..144ae4479 --- /dev/null +++ b/str-twincoding/monitor/file_table.py @@ -0,0 +1,103 @@ +import time +from pydantic import BaseModel +from rich.box import * +from rich.console import Console +from rich.layout import Layout +from rich.live import Live +from rich.table import Table + +from server_table import ServerTable, ServerStatusView, example_servers + + +class FileStatusView(BaseModel): + name: str + availability: float = 0 + encoding: str + auth: float = 0 + payments: float = 0 + + +class FileTable: + def __init__(self, + file: FileStatusView, + servers: List[ServerStatusView] = None, + ): + self.file = file + self.servers = servers or [] + ... + + @staticmethod + def header_table(): + # table = Table(expand=True, show_lines=False, show_edge=False, box=SIMPLE_HEAVY) + table = Table(expand=True, show_lines=False, show_edge=False, box=MINIMAL_HEAVY_HEAD) + + table.add_column("File", style="magenta") + table.add_column("Availability", style="green") + table.add_column("Encoding", style="blue") + table.add_column("Auth", style="cyan") + table.add_column("Payments", style="cyan") + + return table + + def file_row(self, file: FileStatusView): + table = self.header_table() + # table.show_header = False + + # progress_meter = Status.create_progress_meter(cpu_usage) + table.add_row( + file.name, + f'{file.availability:.2f}', + file.encoding, + f'{file.auth:.2f}', + f'{file.payments:.2f}', + ) + + return table + + def get(self): + # Create the main table with a single column + main_table = Table(expand=True, show_header=False, box=ROUNDED, show_lines=False) + main_table.add_column("Main Column", justify="left") + + # file row + file_row = self.file_row(self.file) + main_table.add_row(file_row) + + # server row + main_table.add_row() + server_row = ServerTable(self.servers, show_edge=False, + hide_cols={'status', 'stake', 'payments'}).get() + main_table.add_row(server_row) + + return main_table + + def output(self): + Console(record=True, force_terminal=True).print(self.get()) + + def run(self): + console = Console(record=True, force_terminal=True) + layout = Layout() + layout.split(Layout(name="main")) + + def update(): layout['main'].update(self.get()) + + update() + with Live(layout, console=console, refresh_per_second=1) as live: + # while True: + for _ in range(3): + update() + # await asyncio.sleep(1) + time.sleep(1) + + +example_file = FileStatusView( + name="file1.log", + availability=0.0, + encoding="UTF-8", + auth=0.0, + payments=0.0, +) + +if __name__ == '__main__': + FileTable(example_file, example_servers).output() + # FileTable(example_files, example_servers).run() diff --git a/str-twincoding/monitor/monitor_cli.py b/str-twincoding/monitor/monitor_cli.py new file mode 100644 index 000000000..d83d9f5f0 --- /dev/null +++ b/str-twincoding/monitor/monitor_cli.py @@ -0,0 +1,288 @@ +import asyncio +import json +import os +import random +from asyncio import Task +from datetime import datetime +from typing import Dict, Optional + +from aiohttp import ClientSession, ClientTimeout +from icecream import ic +from rich.box import * +from rich.console import Console +from rich.layout import Layout +from rich.live import Live +from rich.table import Table + +from monitor.file_table import FileStatusView, FileTable +from monitor.monitor_config import ServerStatus, MonitorConfig +from server.server_config import Server, ServerFileStatus, ServerFile +from server_table import ServerStatusView, ServerTable +from storage.util import summarize_ranges + + +class Monitor: + def __init__(self, + providers_config: Optional[str] = None, + providers: Optional[List[Server]] = None, + polling_period: int = 0, + timeout: int = 30, + simulate_latency: int = 0, + debug: bool = False + ): + + # config or servers + assert providers_config or providers + assert not (providers_config and providers) + self.providers_config_last_update: Optional[datetime] = None + self.providers_config = providers_config + self.servers = providers + + self.polling_period = polling_period + self.timeout = timeout + self.server_status: Dict[Server, ServerStatus] = {} + self.file_status: Dict[Server, List[ServerFileStatus]] = {} + self.simulate_latency = simulate_latency + self.task_map: Dict[Server, Task] = {} + self.debug = debug + + async def fetch(self, session: ClientSession, server: Server, after_seconds: int = 0): + url = f'{server.url}/list' + self.log(f"\nFetching {(server.name or '') + server.url}") + + if after_seconds > 0: + await asyncio.sleep(after_seconds) + + if self.simulate_latency > 0: + await asyncio.sleep(random.random() * self.simulate_latency) + + def clear_status(): + self.server_status[server] = ServerStatus.UNKNOWN + self.file_status.pop(server, None) + + try: + async with session.post(url, data={'auth_key': server.auth_token}) as response: + # server status + if response.status != 200: + clear_status() + self.log("error: ", response.status, response.reason) + return + self.server_status[server] = ServerStatus.OK + self.log("response OK") + + # file status + text = await response.text() + # self.log("text: ", text) + from_json = json.loads(text) + status = [ServerFileStatus(**file) for file in from_json] + self.log("status: ", status) + self.file_status[server] = status + except Exception as e: + self.log(f"Exception: {e}") + clear_status() + + # Update the list of servers from the server config if required + def update_server_config(self): + if not self.providers_config: + return self.servers + config_current_mod = datetime.fromtimestamp(os.path.getmtime(self.providers_config)) + if not self.servers or self.providers_config_last_update is None or config_current_mod > self.providers_config_last_update: + config = MonitorConfig.load(self.providers_config) + self.servers = config.providers + self.providers_config_last_update = config_current_mod + + # ensure that all servers are being polled + def update_task_map(self, session: ClientSession): + for server in self.servers: + # TODO: We don't currently clear tasks for servers that are removed from the config + if server not in self.task_map: + task: Task = asyncio.create_task(self.fetch(session, server)) + self.task_map[server] = task + + # Reschedule completed tasks + def schedule_tasks(self, session: ClientSession, delay: int = 0): + for server, task in self.task_map.items(): + if task.done(): + self.task_map[server] = asyncio.create_task( + self.fetch(session, server, after_seconds=delay)) + + async def poll_servers_loop(self): + async with ClientSession(timeout=ClientTimeout(total=self.timeout)) as session: + if self.polling_period > 0: + while True: + self.update_server_config() + self.update_task_map(session) + period = self.polling_period if self.server_status else 0 + self.schedule_tasks(session, period) + await asyncio.sleep(1) + else: + self.update_server_config() + self.update_task_map(session) + self.schedule_tasks(session) + await asyncio.gather(*self.task_map.values()) + + # Generate the table of known providers + def get_provider_table(self) -> Table: + servers = [ + ServerStatusView(name=server.url, status=self.server_status[server]) + for server in self.servers if server in self.server_status + ] + return ServerTable(servers=servers, hide_cols={'shards', 'last_validated'}).get() + + # Generate a table for each file that we found + def get_file_tables(self) -> Table: + self.log("server status:", self.server_status) + self.log("file status:", self.file_status) + + # Map of distinct files across all servers results + distinct_files = {file_status.file: file_status + for server in self.file_status for file_status in self.file_status[server]} + + def find_servers_with_file(file: ServerFile) -> List[Server]: + return [server for server in self.file_status if + file in [status.file for status in self.file_status[server]]] + + def find_file_status_in_server_list(file: ServerFile, server: Server) -> Optional[ServerFileStatus]: + try: + return [status for status in self.file_status[server] if status.file == file][0] + except IndexError: + return None + + # Invert the server data to a per-file map: {file: {server: file_status}} + file_status_map = {file: + { + server: find_file_status_in_server_list(file, server) + for server in find_servers_with_file(file) + } + for file in distinct_files + } + self.log("file status map:", file_status_map) + + file_tables = [self.create_file_table(file, file_status_map[file]) for file in distinct_files] + + # Combine the file tables into a single table + combined_file_table = Table(expand=True, show_header=False, show_lines=False, show_edge=False, pad_edge=False) + combined_file_table.add_column("main", justify="left") + for file_table in file_tables: + combined_file_table.add_row(file_table) + return combined_file_table + + # Create a file table for a single file + def create_file_table(self, file: ServerFile, server_map: Dict[Server, ServerFileStatus]): + + # calculate availability + distinct_shards0 = list({shard for server in server_map for shard in server_map[server].shards0}) + distinct_shards1 = list({shard for server in server_map for shard in server_map[server].shards1}) + count0 = len(distinct_shards0) + availability0 = count0 / file.k0 if count0 >= file.k0 else 0 + count1 = len(distinct_shards1) + availability1 = count1 / file.k1 if count1 >= file.k1 else 0 + availability = availability0 + availability1 + + server_views = [] + for server in server_map: + status: ServerFileStatus = server_map[server] + shards0 = f"type0: {summarize_ranges(status.shards0)}" + shards1 = f"type1: {summarize_ranges(status.shards1)}" + if status.shards0 and status.shards1: + shards = f"{shards0} | {shards1}" + else: + shards = shards0 if status.shards0 else shards1 + + view = ServerStatusView( + name=server.url, + status=self.server_status[server], + shards=shards, + ) + server_views.append(view) + + file_view = FileStatusView(name=file.name, + availability=availability, + encoding=file.encoding_str(), + auth=0.0, payments=0.0) + return FileTable(file=file_view, servers=server_views).get() + + async def draw_screen_loop(self): + if self.debug: + return + + console = Console(record=True, force_terminal=True) + + def create_layout(): + _layout = Layout() + height = 3 + len(self.servers) * 2 # header + server rows with lines + _layout.split( + Layout(name="servers", size=height), + Layout(name="files") + ) + return _layout + + def update(): + layout['servers'].update(self.get_provider_table()) + layout['files'].update(self.get_file_tables()) + + if self.live: + layout = create_layout() + layout_server_len = len(self.servers) + update() + with Live(layout, console=console) as live: + while True: + if len(self.servers) != layout_server_len: + layout = create_layout() + live.update(layout) + layout_server_len = len(self.servers) + update() + await asyncio.sleep(1 if self.server_status else 0.1) + else: + console.print(self.get_provider_table()) + console.print(self.get_file_tables()) + + async def main(self): + if self.live: + await asyncio.gather( + asyncio.create_task(self.poll_servers_loop()), + asyncio.create_task(self.draw_screen_loop()) + ) + else: + await self.poll_servers_loop() + await self.draw_screen_loop() + + def run(self): + asyncio.run(self.main()) + + @property + def live(self): + return self.polling_period > 0 + + def log(self, *args): + if self.debug: + print(*args) + + +if __name__ == '__main__': + import argparse + + parser = argparse.ArgumentParser(description='Process command line arguments.') + parser.add_argument('--providers', type=str, help='Providers file path') + parser.add_argument('--debug', action='store_true', help='Show debug') + parser.add_argument('--update', type=int, help='Update view with polling period seconds') + args = parser.parse_args() + + providers = args.providers + if not args.providers: + STRHOME = os.environ.get('STRHOME') or '.' + providers = os.path.join(STRHOME, 'providers.jsonc') + print(f"Using default providers file: {providers}") + + monitor = Monitor( + providers_config=providers, + polling_period=args.update or 0, + debug=args.debug or False, + simulate_latency=0, timeout=3, + # servers=[ + # Server(name="1", url='http://localhost:8080'), + # Server(name="2", url='http://localhost:8080'), + # Server(name="3", url='http://localhost:8080'), + # ] + ) + monitor.run() diff --git a/str-twincoding/monitor/monitor_config.py b/str-twincoding/monitor/monitor_config.py new file mode 100644 index 000000000..7472e2171 --- /dev/null +++ b/str-twincoding/monitor/monitor_config.py @@ -0,0 +1,15 @@ +from enum import Enum +from typing import List + +from server.server_config import Server +from storage.config import ModelBase + + +class ServerStatus(Enum): + OK = "OK" + UNKNOWN = "UNKNOWN" + NA = "-" + + +class MonitorConfig(ModelBase): + providers: List[Server] diff --git a/str-twincoding/monitor/server_table.py b/str-twincoding/monitor/server_table.py new file mode 100644 index 000000000..dc06cef84 --- /dev/null +++ b/str-twincoding/monitor/server_table.py @@ -0,0 +1,115 @@ +from datetime import datetime +from enum import Enum +from typing import List, Dict, Set + +from pydantic import BaseModel +from rich.box import SIMPLE, MINIMAL_HEAVY_HEAD, ROUNDED +from rich.console import Console +from rich.table import Table + +from monitor.monitor_config import ServerStatus + + +class ServerStatusView(BaseModel): + name: str + status: ServerStatus = ServerStatus.UNKNOWN + stake: float = 0 + auth: float = 0 + payments: float = 0 + shards: str = None + validated: datetime = None + + +class ServerTable: + def __init__(self, + servers: List[ServerStatusView] = None, + show_edge: bool = True, + hide_cols: Set[str] = None + ): + self.servers = servers or [] + self.show_edge = show_edge + self.hide_cols = hide_cols or {} + ... + + def get(self): + # table = Table(expand=True, show_lines=False, title="Known Providers", title_justify="left") + table = Table( + expand=True, + # title="Known Providers" if self.standalone else None, + show_edge=self.show_edge, + show_lines=True, + box=ROUNDED, + ) + + table.add_column("Provider", style="magenta") + if "status" not in self.hide_cols: + table.add_column("Status", style="green") + if "stake" not in self.hide_cols: + table.add_column("Stake", style="red") + if "shards" not in self.hide_cols: + table.add_column("Shards", style="blue") + if "auth" not in self.hide_cols: + table.add_column("Auth", style="cyan") + if "payments" not in self.hide_cols: + table.add_column("Payments", style="cyan") + if "last_validated" not in self.hide_cols: + table.add_column("Last Validated", style="blue") + + # Add rows - you can replace this with actual server status data + for server in self.servers: + # progress_meter = Status.create_progress_meter(cpu_usage) + # table.add_row( + # server.name, + # server.status.name, + # f'{server.stake:.2f}', + # f'{server.shards if server.shards else "-"}', + # f'{server.auth:.2f}', + # f'{server.payments:.2f}', + # f'{server.validated.strftime("%Y-%m-%d %H:%M:%S") if server.validated else "-"}' + # ) + + row_data = [ server.name ] + if "status" not in self.hide_cols: + row_data.append(server.status.name) + if "stake" not in self.hide_cols: + row_data.append(f'{server.stake:.2f}') + if "shards" not in self.hide_cols: + row_data.append(f'{server.shards if server.shards else "-"}') + if "auth" not in self.hide_cols: + row_data.append(f'{server.auth:.2f}') + if "payments" not in self.hide_cols: + row_data.append(f'{server.payments:.2f}') + if "last_validated" not in self.hide_cols: + row_data.append(f'{server.validated.strftime("%Y-%m-%d %H:%M:%S") if server.validated else "-"}') + + table.add_row(*row_data) + + return table + + def output(self): + Console(record=True, force_terminal=True).print(self.get()) + + +example_servers = [ + ServerStatusView( + name="server1.example.com", + status=ServerStatus.OK, + stake=0.0, auth=0.0, payments=0.0, hosting=0, + validated=datetime.now() + ), + ServerStatusView( + name="server2.example.com", + status=ServerStatus.OK, + stake=0.0, auth=0.0, payments=0.0, hosting=0, + validated=datetime.now() + ), + ServerStatusView( + name="server3.example.com", + status=ServerStatus.OK, + stake=0.0, auth=0.0, payments=0.0, hosting=0, + validated=datetime.now() + ), +] + +if __name__ == '__main__': + ServerTable(example_servers).output() diff --git a/str-twincoding/requirements.txt b/str-twincoding/requirements.txt index d0e4f9ba7..580dfcf43 100644 --- a/str-twincoding/requirements.txt +++ b/str-twincoding/requirements.txt @@ -3,4 +3,7 @@ commentjson~=0.9.0 pydantic~=2.4.2 numpy==1.25.0 # galois requires 1.25.0? galois~=0.3.6 -tqdm~=4.66.1 \ No newline at end of file +tqdm~=4.66.1 +rich==13.6.0 +aiohttp==3.8.6 +icecream==2.1.3 \ No newline at end of file diff --git a/str-twincoding/server.conf b/str-twincoding/server.conf deleted file mode 100644 index 6caa6d3c4..000000000 --- a/str-twincoding/server.conf +++ /dev/null @@ -1,19 +0,0 @@ -{ - "config_version": "1.0", - "console_url": "http://localhost:8080/", - - # Cluster definition - "cluster": { - "name": "testnet", - "type0": { - "encoding": "reed_solomon", - "k": 3, - "n": 5, - }, - "type1": { - "encoding": "reed_solomon", - "k": 3, - "n": 5, - }, - }, -} diff --git a/str-twincoding/server.py b/str-twincoding/server.py deleted file mode 100644 index 4437a36cc..000000000 --- a/str-twincoding/server.py +++ /dev/null @@ -1,16 +0,0 @@ -from flask import Flask -from config import load_config - -app = Flask(__name__) - - -@app.route('/') -def hello_world(): - return 'Hello, World!' - - -if __name__ == '__main__': - config = load_config('config.json') - print(f"config version: {config.config_version}") - app.run(port=config.console.port) - diff --git a/str-twincoding/server.sh b/str-twincoding/server.sh new file mode 100755 index 000000000..70d2bf822 --- /dev/null +++ b/str-twincoding/server.sh @@ -0,0 +1,10 @@ +#!/bin/bash +source "$(dirname "$0")/env.sh" + +# if "--repository" is not specified, use the default repository +if [[ "$*" != *"--repository"* ]]; then + echo "Using default repository: $STRHOME/repository" + set -- "$@" --repository "$STRHOME/repository" +fi + +python "$STRHOME/server/server_cli.py" "$@" diff --git a/str-twincoding/server/cluster.jsonc b/str-twincoding/server/cluster.jsonc new file mode 100644 index 000000000..8b9a825e8 --- /dev/null +++ b/str-twincoding/server/cluster.jsonc @@ -0,0 +1,13 @@ +{ + "config_version": "1.0", + "name": "testnet", + "servers": [ + { "url": "http://localhost:8101" }, + { "url": "http://localhost:8102" }, + { "url": "http://localhost:8103" }, + { "url": "http://localhost:8104" }, + { "url": "http://localhost:8105" }, + { "url": "http://localhost:8106" }, + { "url": "http://localhost:8107" }, + ], +} diff --git a/str-twincoding/server/server.jsonc b/str-twincoding/server/server.jsonc new file mode 100644 index 000000000..85af43196 --- /dev/null +++ b/str-twincoding/server/server.jsonc @@ -0,0 +1,4 @@ +{ + "config_version": "1.0", + +} diff --git a/str-twincoding/server/server_api.py b/str-twincoding/server/server_api.py new file mode 100644 index 000000000..723078b74 --- /dev/null +++ b/str-twincoding/server/server_api.py @@ -0,0 +1,66 @@ +# In your views.py or a similar file +import json + +from flask import Blueprint, jsonify, request +from server_args import app +from server_config import ServerFileStatus, ServerFile + +bp = Blueprint('my_blueprint', __name__, url_prefix='/') + + +# Placeholder for authorization logic and payment system. +def is_authorized(request): + return request.headers.get('Authorization') == app.auth_key or (not app.auth_key) + + +@bp.route('/') +def hello_world(): + return 'Storage Server' + + +@bp.route('/health', methods=['POST', 'GET']) +def health_check(): + if not is_authorized(request): + return jsonify({"error": "Unauthorized"}), 401 + return jsonify({"status": "OK"}) + + +@bp.route('/list', methods=['POST', 'GET']) +def list_files(): + if not is_authorized(request): + return jsonify({"error": "Unauthorized"}), 401 + try: + print([map_file(file).model_dump() for file in app.repository.list()]) + return jsonify([map_file(file).model_dump() for file in app.repository.list()]) + except Exception as e: + return jsonify({"error": str(e)}), 500 + + +def map_file(filename): + type0_files, type1_files = app.repository.map(filename) + t0 = list(type0_files.values()) + t1 = list(type1_files.values()) + config = app.repository.file_config(filename) + return ServerFileStatus( + file=ServerFile(name=filename, + encoding0=config.type0.encoding, + k0=config.type0.k, + n0=config.type0.n, + encoding1=config.type1.encoding, + k1=config.type1.k, + n1=config.type1.n), + shards0=t0, + shards1=t1, + ) + + +if __name__ == '__main__': + file = app.repository.list()[0] + type0_files, type1_files = app.repository.map(file) + config = app.repository.file_config(file) + to_json = json.dumps([map_file(file).model_dump() for file in app.repository.list()]) + from_json = json.loads(to_json) + for file in from_json: + fs = ServerFileStatus(**file) + print(fs.name) + ... diff --git a/str-twincoding/server/server_args.py b/str-twincoding/server/server_args.py new file mode 100644 index 000000000..a773f35d0 --- /dev/null +++ b/str-twincoding/server/server_args.py @@ -0,0 +1,38 @@ +from server.server_config import ServerConfig +import argparse + +from storage.repository import Repository +from storage.util import dump_docs_md + + +class App: + def __init__(self): + # Define the command line argument parser + parser = argparse.ArgumentParser(description="Flask server with argument parsing") + parser.add_argument("--config", type=str, help="server config file") + parser.add_argument("--interface", type=str, help="Interface the server listens on") + parser.add_argument("--port", type=int, help="Port the server listens on") + parser.add_argument("--repository_dir", type=str, help="Directory to store repository files") + parser.add_argument("--auth_key", type=str, help="Authentication key to validate requests") + parser.add_argument("--show_console", action='store_true', help="Flag to show console logs") + + args = parser.parse_args() + + # Config file + config = ServerConfig.load(args.config) if args.config else ServerConfig() + print(f"config version: {config.config_version}") + + # Merge params + self.interface = args.interface or config.interface + self.port = args.port or config.port or 8080 + + # Repository + self.repository = Repository(args.repository_dir) if args.repository_dir else Repository.default() + + self.auth_key = args.auth_key or config.auth_key + if not self.auth_key: + print("WARNING: No client auth key db provided. Requests will not be validated.") + self.show_console = args.show_console or False + + +app: App = App() diff --git a/str-twincoding/server/server_cli.py b/str-twincoding/server/server_cli.py new file mode 100644 index 000000000..8a4872bc0 --- /dev/null +++ b/str-twincoding/server/server_cli.py @@ -0,0 +1,8 @@ +from flask import Flask +from server_api import bp +from server_args import app + +if __name__ == '__main__': + flask = Flask(__name__) + flask.register_blueprint(bp) + flask.run(host=app.interface, port=app.port, debug=app.show_console) diff --git a/str-twincoding/server/server_config.py b/str-twincoding/server/server_config.py new file mode 100644 index 000000000..b88ef31df --- /dev/null +++ b/str-twincoding/server/server_config.py @@ -0,0 +1,65 @@ +from typing import Optional, List +from storage.config import ModelBase + + +class Server(ModelBase): + name: str = None # for testing + url: str + # An auth token for the server-client pair allows listing files. + # We could also support stateless discovery by hash. + auth_token: Optional[str] = None + + class Config: + frozen = True # hashable + ... + + +class Cluster(ModelBase): + name: str + servers: List[Server] + + +class ServerConfig(ModelBase): + config_version: Optional[str] = None + interface: Optional[str] = None + port: Optional[int] = 8080 + auth_key: Optional[str] = None + repository_dir: Optional[str] = None + cluster: Optional[Cluster] = None + + +class ServerFile(ModelBase): + name: str + encoding0: str + k0: int + n0: int + encoding1: str + k1: int + n1: int + + def encoding_str(self): + encoding0 = f'{self.encoding0}:{self.k0}/{self.n0}' + encoding1 = f'{self.encoding1}:{self.k1}/{self.n1}' + return encoding0 if encoding0 == encoding1 else f"{encoding0}|{encoding1}" + + class Config: + frozen = True # hashable + + +class ServerFileStatus(ModelBase): + file: ServerFile + shards0: List[int] + shards1: List[int] + + class Config: + frozen = True # hashable + + +if __name__ == '__main__': + # config = Cluster.load('cluster.jsonc') + # print(config) + # print(f"config version: {config.config_version}") + + file = ServerFile(name='test', encoding0='erasure', k0=2, n0=3, encoding1='erasure', k1=2, n1=3) + file2 = ServerFile(name='test', encoding0='erasure', k0=2, n0=3, encoding1='erasure', k1=2, n1=3) + print(hash(file) == hash(file2)) diff --git a/str-twincoding/storage.py b/str-twincoding/storage.py deleted file mode 100644 index 7e8c53d64..000000000 --- a/str-twincoding/storage.py +++ /dev/null @@ -1,97 +0,0 @@ -import argparse -from config import NodeType, NodeType0, NodeType1 -from file_decoder import FileDecoder -from file_encoder import FileEncoder -from node_recovery_client import NodeRecoveryClient -from node_recovery_source import NodeRecoverySource - - -# -# Command line interface for node encoding and recovery. -# - -def encode_file(args): - FileEncoder( - node_type0=NodeType0(k=args.k0, n=args.n0, encoding=args.encoding0), - node_type1=NodeType1(k=args.k1, n=args.n1, encoding=args.encoding1), - path=args.path, - overwrite=args.overwrite - ).encode() - - -def decode_file(args): - FileDecoder.from_encoded_dir( - path=args.encoded, - output_path=args.recovered, - overwrite=args.overwrite - ).decode() - - -def generate_recovery_file(args): - NodeRecoverySource( - recover_node_type=NodeType(k=args.k, n=args.n, encoding=args.recover_encoding), - recover_node_index=args.recover_node_index, - data_path=args.data_path, - output_path=args.output_path, - overwrite=args.overwrite - ).generate() - - -def recover_node(args): - NodeRecoveryClient( - recovery_source_node_type=NodeType(k=args.k, n=args.n, encoding=args.encoding), - file_map=NodeRecoveryClient.map_files(files_dir=args.files_dir, k=args.k), - output_path=args.output_path, - overwrite=args.overwrite - ).recover_node() - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="File encoding and decoding utility") - subparsers = parser.add_subparsers(metavar='COMMAND', help="Sub-commands available.") - - # Encoding - parser_encode = subparsers.add_parser('encode', help="Encode a file.") - parser_encode.add_argument('--path', required=True, help="Path to the file to encode.") - parser_encode.add_argument('--k0', type=int, required=True, help="k value for node type 0.") - parser_encode.add_argument('--n0', type=int, required=True, help="n value for node type 0.") - parser_encode.add_argument('--k1', type=int, required=True, help="k value for node type 1.") - parser_encode.add_argument('--n1', type=int, required=True, help="n value for node type 1.") - parser_encode.add_argument('--encoding0', default='reed_solomon', help="Encoding for node type 0.") - parser_encode.add_argument('--encoding1', default='reed_solomon', help="Encoding for node type 1.") - parser_encode.add_argument('--overwrite', action='store_true', help="Overwrite existing files.") - parser_encode.set_defaults(func=encode_file) - - # Decoding - parser_decode = subparsers.add_parser('decode', help="Decode an encoded file.") - parser_decode.add_argument('--encoded', required=True, help="Path to the encoded file.") - parser_decode.add_argument('--recovered', required=True, help="Path to the recovered file.") - parser_decode.add_argument('--overwrite', action='store_true', help="Overwrite existing files.") - parser_decode.set_defaults(func=decode_file) - - # Recovery Generation - parser_gen_rec = subparsers.add_parser('generate_recovery_file', help="Generate recovery files.") - parser_gen_rec.add_argument('--recover_node_index', type=int, required=True, help="Index of the recovering node.") - parser_gen_rec.add_argument('--recover_encoding', default='reed_solomon', help="Encoding for the recovering node.") - parser_gen_rec.add_argument('--k', type=int, required=True, help="k value for the recovering node.") - parser_gen_rec.add_argument('--n', type=int, required=True, help="n value for the recovering node.") - parser_gen_rec.add_argument('--data_path', required=True, help="Path to the source node data.") - parser_gen_rec.add_argument('--output_path', required=True, help="Path to the output recovery file.") - parser_gen_rec.add_argument('--overwrite', action='store_true', help="Overwrite existing files.") - parser_gen_rec.set_defaults(func=generate_recovery_file) - - # Node Recovery - parser_rec_node = subparsers.add_parser('recover_node', help="Recover a node from recovery files.") - parser_rec_node.add_argument('--k', type=int, required=True, help="k value for node type.") - parser_rec_node.add_argument('--n', type=int, required=True, help="n value for node type.") - parser_rec_node.add_argument('--encoding', default='reed_solomon', help="Encoding for node type.") - parser_rec_node.add_argument('--files_dir', required=True, help="Path to the recovery files.") - parser_rec_node.add_argument('--output_path', required=True, help="Path to the recovered file.") - parser_rec_node.add_argument('--overwrite', action='store_true', help="Overwrite existing files.") - parser_rec_node.set_defaults(func=recover_node) - - args = parser.parse_args() - if hasattr(args, 'func'): - args.func(args) - else: - parser.print_help() diff --git a/str-twincoding/storage.sh b/str-twincoding/storage.sh index 5c6e5a5f2..234934ef4 100755 --- a/str-twincoding/storage.sh +++ b/str-twincoding/storage.sh @@ -1,3 +1,3 @@ #!/bin/bash -source "$(dirname "$0")/venv/bin/activate" -python "$(dirname "$0")/storage.py" "$@" +source "$(dirname "$0")/env.sh" +python "$STRHOME/storage/storage_cli.py" "$@" diff --git a/str-twincoding/storage/config.jsonc b/str-twincoding/storage/config.jsonc new file mode 100644 index 000000000..64efe68e1 --- /dev/null +++ b/str-twincoding/storage/config.jsonc @@ -0,0 +1,8 @@ +{ + "config_version": "1.0", + + // Cluster definition + "cluster": { + "name": "testnet", + }, +} diff --git a/str-twincoding/storage/config.py b/str-twincoding/storage/config.py new file mode 100644 index 000000000..5e16ef913 --- /dev/null +++ b/str-twincoding/storage/config.py @@ -0,0 +1,57 @@ +import commentjson as json +from pydantic import BaseModel + + +class ModelBase(BaseModel): + @classmethod + def load(cls, file_path): + with open(file_path, 'r') as f: + return cls(**json.load(f)) + + +# +# File config +# + +class NodeType(ModelBase): + type: int + encoding: str + k: int + n: int + + @property + def transpose(self): + return self.type == 1 + + +class NodeType0(NodeType): + # transpose: bool = False + type: int = 0 + + +class NodeType1(NodeType): + # transpose: bool = True + type: int = 1 + + +class EncodedFileConfig(ModelBase): + name: str + type0: NodeType0 + type1: NodeType1 + file_length: int + # file_hash: str + + +if __name__ == '__main__': + # config = load_config('config.jsonc') + # print(config) + # print(f"config version: {config.config_version}") + + node = NodeType0(encoding='reed_solomon', k=3, n=5) + print(node, node.transpose) + node = NodeType(type=0, encoding='reed_solomon', k=3, n=5) + print(node, node.transpose) + node = NodeType1(encoding='reed_solomon', k=3, n=5) + print(node, node.transpose) + node = NodeType(type=1, encoding='reed_solomon', k=3, n=5) + print(node, node.transpose) diff --git a/str-twincoding/storage/repository.py b/str-twincoding/storage/repository.py new file mode 100644 index 000000000..0ddb84d91 --- /dev/null +++ b/str-twincoding/storage/repository.py @@ -0,0 +1,105 @@ +import os +import re +from collections import OrderedDict +from typing import Any + +from storage.config import EncodedFileConfig +from storage.util import * + + +class Repository: + @staticmethod + def default() -> 'Repository': + STRHOME = os.environ.get('STRHOME') or '.' + path = os.path.join(STRHOME, 'repository') + print(f"Using default repository path: {path}") + return Repository(path=path) + + def __init__(self, path: str, init: bool = True): + self._path = path + if init and not os.path.exists(path): + os.makedirs(os.path.join(self._path, 'tmp')) + ... + + # Get the path to an encoded file. + # 'file' must be a simple filename (not a path) + def file_path(self, file: str, expected: bool = True) -> str: + assert os.path.basename(file) == file, "file must be a filename only with no path." + path = os.path.join(self._path, f'{file}.encoded') + if expected: + assert os.path.exists(path), f"Encoded file not found: {path}" + return path + + def file_config(self, file: str) -> EncodedFileConfig: + path = self.file_path(file) + return EncodedFileConfig.load(os.path.join(path, 'config.json')) + + # Get the path to a shard of the encoded file by node type and index + def shard_path(self, file: str, node_type: int, node_index: int, expected: bool = True) -> str: + assert_node_type(node_type) + path = f'{self.file_path(file)}/type{node_type}_node{node_index}.dat' + if expected: + assert os.path.exists(path), f"Shard not found: {path}" + return path + + def recovery_file_path(self, file: str, + recover_node_type: int, recover_node_index: int, + helper_node_index: int, expected: bool = True) -> str: + assert_node_type(recover_node_type) + path = (f'{self.file_path(file)}/recover' + f'_type{recover_node_type}' + f'_node{recover_node_index}' + f'_from{helper_node_index}.dat') + if expected: + assert os.path.exists(path), f"Recovery file not found: {path}" + return path + + def tmp_file_path(self, file: str): + return os.path.join(self._path, 'tmp', f'{file}') + + # Map the files in an encoded file directory. + # This returns two ordered dicts, one for type 0 files and one for type 1 files, filename -> node index + def map(self, file: str) -> (dict[str, int], dict[str, int]): + return Repository.map_files(self.file_path(file)) + + # Produce s compact string summarizing the availability of a file. + def status_str(self, file: str) -> str: + type0_files, type1_files = self.map(file) + # config = load_file_config(f'{self.path(file)}/config.json') + return (f"{file}: Availability: Type 0 shards: {summarize_ranges(list(type0_files.values()))}, " + f"Type 1 shards: {summarize_ranges(list(type1_files.values()))}") + + # Generate a list of the files in the repository. + def list(self) -> List[str]: + encoded = [f for f in os.listdir(self._path) if f.endswith('.encoded')] + return [f[:-8] for f in encoded] + + @staticmethod + # Return maps of type 0 and type 1 files, file path -> node index + def map_files(files_dir: str) -> (dict[str, int], dict[str, int]): + type0_files: dict[Any, Any] + type0_files, type1_files = {}, {} + for filename in os.listdir(files_dir): + match = re.match(r'type([01])_node(\d+).dat', filename) + if not match: + continue + type_no, index_no = int(match.group(1)), int(match.group(2)) + files = type0_files if type_no == 0 else type1_files + files[os.path.join(files_dir, filename)] = index_no + + return (OrderedDict(sorted(type0_files.items(), key=lambda x: x[1])), + OrderedDict(sorted(type1_files.items(), key=lambda x: x[1]))) + + +if __name__ == '__main__': + repo = Repository('./repository') + + files = repo.list() + print("Files in repository:") + for file in files: + print(' ', repo.status_str(file)) + + # print() + # file = 'file_1KB.dat' + # print(repo.file_path(file)) + # print(repo.shard_path(file, node_type=0, node_index=0)) diff --git a/str-twincoding/storage/storage_cli.py b/str-twincoding/storage/storage_cli.py new file mode 100644 index 000000000..8076c002b --- /dev/null +++ b/str-twincoding/storage/storage_cli.py @@ -0,0 +1,245 @@ +import argparse +import os + +from icecream import ic + +from storage.config import NodeType, NodeType0, NodeType1 +from storage.repository import Repository +from encoding.file_decoder import FileDecoder +from encoding.file_encoder import FileEncoder +from encoding.node_recovery_client import NodeRecoveryClient +from encoding.node_recovery_source import NodeRecoverySource +from storage.util import dump_docs_md + + +# +# Command line interface for node encoding and recovery. +# + +def repo_list(args): + repo = Repository(path=args.path) if args.path else Repository.default() + files = repo.list() + for file in files: + print(repo.status_str(file)) + + +def repo_file_path(args): + print(Repository(path=args.path).file_path(file=args.file, expected=False)) + + +def repo_shard_path(args): + print(Repository(path=args.path).shard_path(file=args.file, node_type=args.node_type, node_index=args.node_index, + expected=False)) + + +def repo_recovery_file_path(args): + print(Repository(path=args.path).recovery_file_path( + file=args.file, + recover_node_type=args.recover_node_type, + recover_node_index=args.recover_node_index, + helper_node_index=args.helper_node_index, + expected=False)) + + +def repo_tmp_file_path(args): + print(Repository(path=args.path).tmp_file_path(file=args.file)) + + +def encode_file(args): + FileEncoder( + node_type0=NodeType0(k=args.k0, n=args.n0, encoding=args.encoding0), + node_type1=NodeType1(k=args.k1, n=args.n1, encoding=args.encoding1), + input_file=args.path, + output_path=args.output_path, + overwrite=args.overwrite + ).encode() + + +# Import command: Similar to encode_file but defaults to repository paths +def import_file(args): + # Default the file path + filename = os.path.basename(args.path) + repo = Repository(path=args.repo) if args.repo else Repository.default() + + output_path = repo.file_path(file=filename, expected=False) + FileEncoder( + node_type0=NodeType0(k=args.k0, n=args.n0, encoding=args.encoding0), + node_type1=NodeType1(k=args.k1, n=args.n1, encoding=args.encoding1), + input_file=args.path, + output_path=output_path, + overwrite=args.overwrite + ).encode() + + +def push_file(args): + ic(args.validate) + assert not args.validate, "Validation not implemented yet." + # prepare file + filename = args.file + repo = Repository(path=args.repo) if args.repo else Repository.default() + encoded_file_config = repo.file_config(file=filename) + # TODO: validate the file before uploading? + type0_shards, type1_shards = repo.map(filename) + # inverted_dict = {value: key for key, value in original_dict.items()} + + # prepare servers + servers = args.servers + if not servers: + # pick n0+n1 servers from known providers round-robin + servers = [] + + # FileUploader( ).encode() + + +def decode_file(args): + FileDecoder.from_encoded_dir( + path=args.encoded, + output_path=args.recovered, + overwrite=args.overwrite + ).decode() + + +def generate_recovery_file(args): + NodeRecoverySource( + recover_node_type=NodeType( + type=args.recover_node_type, + k=args.k, n=args.n, encoding=args.recover_encoding), + recover_node_index=args.recover_node_index, + data_path=args.data_path, + output_path=args.output_path, + overwrite=args.overwrite + ).generate() + + +def recover_node(args): + NodeRecoveryClient( + recovery_source_node_type=NodeType( + # assume we are the opposite type of the node we are recovering + type=0 if args.recover_node_type == 1 else 1, + k=args.k, n=args.n, encoding=args.encoding), + file_map=NodeRecoveryClient.map_files( + files_dir=args.files_dir, + recover_node_type=args.recover_node_type, + recover_node_index=args.recover_node_index, + k=args.k), + output_path=args.output_path, + overwrite=args.overwrite + ).recover_node() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(prog="storage", description="File encoding and decoding utility") + subparsers = parser.add_subparsers(metavar='COMMAND', help="Sub-commands available.") + + # Repository + parser_repo = subparsers.add_parser('repo', help="Repository path related operations.") + parser_repo.add_argument('--path', required=False, help="Path to the repository.") + repo_subparsers = parser_repo.add_subparsers(metavar='REPO_COMMAND', help='Repository path commands available.') + + # Repository - list files + parser_repo_list = repo_subparsers.add_parser('list', help="List files in the repository.") + parser_repo_list.set_defaults(func=repo_list) + + # Repository - file_path command + parser_file_path = repo_subparsers.add_parser('file_path', help="Get the path to an encoded file.") + parser_file_path.add_argument('--file', required=True, help="The filename to get the path for.") + parser_file_path.set_defaults(func=repo_file_path) + + # Repository - shard_path command + parser_shard_path = repo_subparsers.add_parser('shard_path', help="Get the path to a shard of the encoded file.") + parser_shard_path.add_argument('--file', required=True, help="The filename to get the shard path for.") + parser_shard_path.add_argument('--node_type', type=int, required=True, help="The node type for the shard.") + parser_shard_path.add_argument('--node_index', type=int, required=True, help="The node index for the shard.") + parser_shard_path.set_defaults(func=repo_shard_path) + + # Repository - recovery_file_path command + parser_recovery_file_path = repo_subparsers.add_parser('recovery_file_path', + help="Get the path for a recovery file.") + parser_recovery_file_path.add_argument('--file', required=True, help="The filename to get the recovery path for.") + parser_recovery_file_path.add_argument('--recover_node_type', type=int, required=True, + help="The node type for recovery.") + parser_recovery_file_path.add_argument('--recover_node_index', type=int, required=True, + help="The node index for recovery.") + parser_recovery_file_path.add_argument('--helper_node_index', type=int, required=True, + help="The helper node index for recovery.") + parser_recovery_file_path.set_defaults(func=repo_recovery_file_path) + + # Repository - tmp_file_path command + parser_tmp_file_path = repo_subparsers.add_parser('tmp_file_path', help="Get the path for a temporary file.") + parser_tmp_file_path.add_argument('--file', required=True, help="The filename to get the temporary path for.") + parser_tmp_file_path.set_defaults(func=repo_tmp_file_path) + + # Encode + parser_encode = subparsers.add_parser('encode', help="Encode a file.") + parser_encode.add_argument('--path', required=True, help="Path to the file to encode.") + parser_encode.add_argument('--output_path', required=True, help="Output path for the encoded file.") + parser_encode.add_argument('--k0', type=int, required=True, help="k value for node type 0.") + parser_encode.add_argument('--n0', type=int, required=True, help="n value for node type 0.") + parser_encode.add_argument('--k1', type=int, required=True, help="k value for node type 1.") + parser_encode.add_argument('--n1', type=int, required=True, help="n value for node type 1.") + parser_encode.add_argument('--encoding0', default='reed_solomon', help="Encoding for node type 0.") + parser_encode.add_argument('--encoding1', default='reed_solomon', help="Encoding for node type 1.") + parser_encode.add_argument('--overwrite', action='store_true', help="Overwrite existing files.") + parser_encode.set_defaults(func=encode_file) + + # Decode + parser_decode = subparsers.add_parser('decode', help="Decode an encoded file.") + parser_decode.add_argument('--encoded', required=True, help="Path to the encoded file.") + parser_decode.add_argument('--recovered', required=True, help="Path to the recovered file.") + parser_decode.add_argument('--overwrite', action='store_true', help="Overwrite existing files.") + parser_decode.set_defaults(func=decode_file) + + # Recovery Generation + parser_gen_rec = subparsers.add_parser('generate_recovery_file', help="Generate recovery files.") + parser_gen_rec.add_argument('--recover_node_type', type=int, required=True, help="Type of the recovering node.") + parser_gen_rec.add_argument('--recover_node_index', type=int, required=True, help="Index of the recovering node.") + parser_gen_rec.add_argument('--recover_encoding', default='reed_solomon', help="Encoding for the recovering node.") + parser_gen_rec.add_argument('--k', type=int, required=True, help="k value for the recovering node.") + parser_gen_rec.add_argument('--n', type=int, required=True, help="n value for the recovering node.") + parser_gen_rec.add_argument('--data_path', required=True, help="Path to the source node data.") + parser_gen_rec.add_argument('--output_path', required=True, help="Path to the output recovery file.") + parser_gen_rec.add_argument('--overwrite', action='store_true', help="Overwrite existing files.") + parser_gen_rec.set_defaults(func=generate_recovery_file) + + # Node Recovery + parser_rec_node = subparsers.add_parser('recover_node', help="Recover a node from recovery files.") + parser_rec_node.add_argument('--recover_node_type', type=int, required=True, help="Type of the recovering node.") + parser_rec_node.add_argument('--recover_node_index', type=int, required=True, help="Index of the recovering node.") + parser_rec_node.add_argument('--k', type=int, required=True, help="k value for node type.") + parser_rec_node.add_argument('--n', type=int, required=True, help="n value for node type.") + parser_rec_node.add_argument('--encoding', default='reed_solomon', help="Encoding for node type.") + parser_rec_node.add_argument('--files_dir', required=True, help="Path to the recovery files.") + parser_rec_node.add_argument('--output_path', required=True, help="Path to the recovered file.") + parser_rec_node.add_argument('--overwrite', action='store_true', help="Overwrite existing files.") + parser_rec_node.set_defaults(func=recover_node) + + # Import: Like Encode but using the default repo paths and encoding + parser_import = subparsers.add_parser('import', help="Import file using default repo and encoding.") + parser_import.add_argument('--repo', required=False, help="Path to the repository.") + parser_import.add_argument('--k0', type=int, default="3", help="k value for node type 0.") + parser_import.add_argument('--n0', type=int, default="5", help="n value for node type 0.") + parser_import.add_argument('--k1', type=int, default="3", help="k value for node type 1.") + parser_import.add_argument('--n1', type=int, default="5", help="n value for node type 1.") + parser_import.add_argument('--encoding0', default='reed_solomon', help="Encoding for node type 0.") + parser_import.add_argument('--encoding1', default='reed_solomon', help="Encoding for node type 1.") + parser_import.add_argument('--overwrite', action='store_true', help="Overwrite existing files.") + parser_import.add_argument('path', help="Path to the file to import.") # Positional argument + parser_import.set_defaults(func=import_file) + + # Push: Send a file in the repository to one or more providers + parser_push = subparsers.add_parser('push', help="Send a file in the repository to one or more providers.") + parser_push.add_argument('--repo', required=False, help="Path to the repository.") + parser_push.add_argument('--servers', nargs='*', help="List of server names or urls to push to.") + parser_push.add_argument('--validate', action='store_true', help="After push, download and reconstruct the file.") + parser_push.add_argument('file', help="Name of the file in the repository.") + parser_push.set_defaults(func=push_file) + + # Dump all help for docs + parser_docs = subparsers.add_parser('docs') + parser_docs.set_defaults(func=lambda args: dump_docs_md(args, subparsers)) + + args = parser.parse_args() + if hasattr(args, 'func'): + args.func(args) + else: + parser.print_help() diff --git a/str-twincoding/storage/util.py b/str-twincoding/storage/util.py new file mode 100644 index 000000000..2b5c0a44a --- /dev/null +++ b/str-twincoding/storage/util.py @@ -0,0 +1,53 @@ +import argparse +from typing import List +from storage.config import NodeType + + +def assert_rs(node_type: NodeType): + assert node_type.encoding == 'reed_solomon', "Only reed solomon encoding is currently supported." + + +def assert_node_type(node_type: int): + assert node_type in [0, 1], "node_type must be 0 or 1." + + +def summarize_ranges(numbers: List[int]) -> str: + """Summarize a list of integers into a compact range string.""" + if not numbers: + return "" + + # Start with the first number, and initialize current range + ranges = [] + start = numbers[0] + end = numbers[0] + + for n in numbers[1:]: + if n == end + 1: + # Continue the range + end = n + else: + # Finish the current range and start a new one + if start == end: + ranges.append(str(start)) + else: + ranges.append(f"{start}-{end}") + start = end = n + + # Add the last range + if start == end: + ranges.append(str(start)) + else: + ranges.append(f"{start}-{end}") + + return ", ".join(ranges) + + +# Dump all help for docs for the argparse subparsers as markdown +def dump_docs_md(_, subparsers: argparse._SubParsersAction): + for name, subparser in subparsers._name_parser_map.items(): + if name == 'docs': + continue + print(f"###`{name}`") + print('```') + print(subparser.print_help()) + print('```') diff --git a/str-twincoding/tests.sh b/str-twincoding/tests.sh index 633b23d71..5a985d2cb 120000 --- a/str-twincoding/tests.sh +++ b/str-twincoding/tests.sh @@ -1 +1 @@ -examples.sh \ No newline at end of file +examples/examples.sh \ No newline at end of file diff --git a/str-twincoding/util.py b/str-twincoding/util.py deleted file mode 100644 index 050d79e4a..000000000 --- a/str-twincoding/util.py +++ /dev/null @@ -1,21 +0,0 @@ -import io -import os -from typing import Optional -from config import NodeType - - -def open_output_file(output_path: str, overwrite: bool) -> Optional[io.BufferedWriter]: - if not overwrite and os.path.exists(output_path): - print(f"Output file already exists: {output_path}.") - return None - - # Make intervening directories if needeed - directory = os.path.dirname(output_path) - if directory: - os.makedirs(directory, exist_ok=True) - - return io.BufferedWriter(open(output_path, 'wb')) - - -def assert_rs(node_type: NodeType): - assert node_type.encoding == 'reed_solomon', "Only reed solomon encoding is currently supported."