Compare commits

..
Author SHA1 Message Date
benbierens 7eed6160eb working as intended 2024-06-28 09:16:12 +02:00
benbierens c57dc4daa1 concurrent purchases 2024-06-28 08:47:09 +02:00
Ben 4c75cebcd6 Implements autoclient with image generator 2024-06-27 15:38:13 +02:00
Ben a820788c7d Merge branch 'master' into feature/auto-client
# Conflicts:
#	cs-codex-dist-testing.sln
2024-06-27 13:42:54 +02:00
Ben 52a02abd3f Fixes incorrect block numbers in eventFormatter 2024-06-27 11:43:25 +02:00
Ben a92455b2a5 bump codex image to v0.1.2 + prover 2024-06-27 11:16:50 +02:00
Ben 933d5e7d4d Merge branch 'better-chain-events' 2024-06-27 11:16:34 +02:00
Ben ba43fd90c6 Adds admin option to set custom replacements 2024-06-27 11:16:17 +02:00
Ben 01ee514c73 Implements known username replacement 2024-06-27 10:44:37 +02:00
Ben 1eb30329c6 Pulls out chain events sender 2024-06-27 10:14:23 +02:00
Ben 0ef55abdf4 Adds events formatter 2024-06-27 10:07:10 +02:00
Ben 8341807d92 Disables role rewards (not configured in real server) 2024-06-26 11:27:19 +02:00
Ben c2712daccb latest images 2024-06-25 13:02:05 +02:00
benbierens 8033da1176 Updates to new Codex image. Fixes tests 2024-06-21 11:29:05 +02:00
benbierens 5afab577a7 Merge branch 'chainstate-update' 2024-06-21 10:19:32 +02:00
benbierens 4be9b9df9a Finished! 2024-06-21 10:19:09 +02:00
benbierens a905f0ce53 fixes crash in market buffer again 2024-06-21 09:59:24 +02:00
benbierens 0c4d3be912 Fixes crash in marketBuffer 2024-06-21 09:33:52 +02:00
benbierens 2b7ba61543 Cleanup 2024-06-21 09:18:00 +02:00
benbierens f9c7e18985 Merge branch 'master' into chainstate-update 2024-06-21 08:56:43 +02:00
benbierens 32e7028029 wip 2024-06-21 08:56:20 +02:00
Ben cc3eddf02d wip 2024-06-20 12:15:59 +02:00
Ben d73ad6db15 Merge branch 'feature/keymaker' 2024-06-19 14:06:20 +02:00
Ben 5280f0732c Sets up docker image for creating keys 2024-06-19 14:04:27 +02:00
Ben 5ca135646a Container crash detection during start-up 2024-06-19 10:39:14 +02:00
Ben e1da38b6e8 Adds test for making sure node does not crash after mysterious CID is downloaded 2024-06-18 11:04:04 +02:00
Ben f7c45d17d7 wip 2024-06-18 09:57:38 +02:00
Ben cb4cdfe69a better logging 2024-06-17 16:00:51 +02:00
Ben bed57dd35b Finish replace of old chainstate object in rewarder bot 2024-06-17 15:34:08 +02:00
benbierens c38a2242ba wip 2024-06-14 11:09:51 +02:00
benbierens d3488dc907 Adds block numbers to chainstate event logging 2024-06-14 10:22:30 +02:00
benbierens 1fa7787d3b Merge branch 'master' into chainstate-update
# Conflicts:
#	ProjectPlugins/CodexPlugin/CodexNode.cs
#	ProjectPlugins/CodexPlugin/MarketplaceAccess.cs
2024-06-14 09:28:34 +02:00
benbierens 54f053cfcc Improves crash detection 2024-06-14 09:05:56 +02:00
Ben 7b5e802efb Applies delete in scalability tests 2024-06-13 08:51:52 +02:00
Ben 390b9de54a Replaces delete with disk info log. 2024-06-12 15:28:08 +02:00
Ben 4fc9835f43 Attempt to log disk space before and after uploads and downloads 2024-06-12 10:48:52 +02:00
Ben 9a46e20b4a Fixes exception in CodexDistTests 2024-06-11 11:33:02 +02:00
Ben 74ca512f92 Sets up test for downloading content by contract CID 2024-06-10 15:15:27 +02:00
Ben 1c856f7615 Fixes log 2024-06-10 14:17:19 +02:00
Ben cc2513bd2f Better chain state representation 2024-06-10 14:04:25 +02:00
Ben 22527a5d93 Deletes block repostore folder in container when Codex node stops. 2024-06-10 10:58:50 +02:00
benbierens 25663b59a0 sets trace level for scalability tests 2024-06-08 10:50:30 +02:00
benbierens aa416d50b3 ensuring enough mounted disk space 2024-06-08 10:36:23 +02:00
benbierens 16bf5ce5ab I think I may be on to something 2024-06-07 18:16:19 +02:00
benbierens 52ae671234 focussing on the interesting numbers for now 2024-06-07 17:08:14 +02:00
benbierens 5ffff1ed07 Disables metrics in scalability test. Downloads container log when retry attempt failed too quickly to be a timeout. 2024-06-07 17:07:35 +02:00
benbierens 18a02b1717 Implements mapping of debug/info routing table nodes 2024-06-07 16:37:31 +02:00
benbierens 3a61fc89c6 Adds WaitForCleanup test attribute to allow tests to wait for resources to be cleaned up 2024-06-06 15:09:52 +02:00
Ben 38c2d1749a Links up status method for checking. 2024-06-06 10:31:31 +02:00
Ben 9900db8d25 simply scalability test failure handling 2024-06-06 10:12:11 +02:00
Ben e53a644718 Updates to new codex image 2024-06-06 10:11:51 +02:00
Ben 62b56e198b Adds logging of node status to base codexdistTest 2024-06-06 09:54:50 +02:00
Ben f4d1dae478 Disables large network scalability test for now 2024-06-05 10:00:50 +02:00
Ben bf75c25152 Cannot assume retry uploads will require as much space as file is large 2024-06-05 09:59:03 +02:00
Ben b888999b61 Adds space check for upload call 2024-06-05 09:34:43 +02:00
Ben e9555cf99e Upload/download failure now automatically investigates node 2024-06-05 09:20:00 +02:00
Ben 11b866986d wip retry upgrade 2024-06-04 11:06:19 +02:00
benbierens 67fc2183b7 Better logging for slot-filled event checking 2024-06-03 12:56:19 +02:00
benbierens 02baa72c1e DebugInfo log 2024-06-03 11:42:52 +02:00
benbierens 383102b988 Adds container name to crash messge. 2024-06-03 11:36:48 +02:00
benbierens 3da36725e3 Updates codex image 2024-06-03 10:58:04 +02:00
benbierens ac7d323201 wip 2024-06-03 10:38:48 +02:00
benbierens a846d51c0c Implements ChainState in CodexContracts plugin 2024-05-31 11:19:50 +02:00
benbierens f5da80dc9c Sets up ChainEvents object 2024-05-31 10:16:57 +02:00
benbierens f94a67adb4 Extracts event methods from CodexContractsAccess 2024-05-31 10:06:34 +02:00
Ben f1d453251c cleanup 2024-05-30 11:33:16 +02:00
Ben 12dc7efd5b wip 2024-05-30 11:17:13 +02:00
Ben 117a30bb82 wip 2024-05-30 10:55:33 +02:00
Ben ccc6c815e4 Logging of entire chain state as seen by rewarder bot 2024-05-29 14:05:16 +02:00
benbierens ba9e4b098f checking that all rewards are sent. 2024-05-24 16:11:51 +02:00
benbierens 69aa3a998f Fixes bot test contract 2024-05-24 15:34:42 +02:00
benbierens 00fd2cebf9 Merge branch 'master' into feature/rewardbot-tests 2024-05-24 14:34:16 +02:00
benbierens 684a99027b nice checks for timings of successful contract 2024-05-24 14:33:59 +02:00
Ben 5143361dcd wip 2024-05-23 11:37:57 +02:00
Ben 6c956c1a64 wip 2024-05-23 11:37:17 +02:00
Ben a749f82ca5 Switches blocktime logs to debug 2024-05-23 11:24:32 +02:00
Ben 6e9ea47b7d Tracking contract state change moments in MarketplaceAccess 2024-05-23 11:15:14 +02:00
Ben b1818400ca Merge branch 'master' into feature/rewardbot-tests
# Conflicts:
#	Tests/CodexTests/UtilityTests/DiscordBotTests.cs
2024-05-22 11:11:47 +02:00
Ben f7bdafbdc5 Improved representation of TestToken amounts 2024-05-22 11:06:34 +02:00
Ben a236544ee9 parameterizing the bot test 2024-05-21 16:10:14 +02:00
Ben e082f8a31c Changes Mint-TT bot parameter to BigInt type. 2024-05-21 13:09:46 +02:00
Ben fa1b560a91 Adds waiting for debug-mode start message 2024-05-21 12:58:17 +02:00
benbierens d6f7e225be successful downloading of bot log 2024-05-20 16:18:01 +02:00
Ben 22cf82b99b Adds no-discord debug option to discord bot. 2024-05-16 16:00:19 +02:00
Ben ad2181db0b Fixes invariant-culture in assigner. Adds support for ulong. 2024-05-16 14:58:42 +02:00
Ben 3525d13e69 Merge branch 'feature/bot-use-ids' 2024-05-16 14:07:29 +02:00
Ben 8847de116d cleanup rewarder bot config 2024-05-16 14:07:14 +02:00
Ben 7d9dcb263d Prints default ArgsUniform values. Applies discord IDs instead of names. 2024-05-16 14:05:58 +02:00
Ben b3771cce32 Extract assigner from argsUniform 2024-05-16 12:14:03 +02:00
Ben 9717591224 Updates bot config to use ids instead of names 2024-05-16 11:54:31 +02:00
Ben 64ae7c8efe Latest image of Codex 2024-05-15 10:34:40 +02:00
benbierens d8d7605ce5 Increases marketplace graceperiod timespan. 2024-05-10 10:08:45 +02:00
benbierens 6995cbfb23 hacky way to download container logs while retries are failing. 2024-05-09 09:38:04 +02:00
benbierens c6a757d6fb Rename AddCodex. Better retry logging 2024-05-09 09:32:48 +02:00
benbierens c01f9dbb21 Fixes crash in blocktimefinder. 2024-05-09 08:57:19 +02:00
Ben 0ec43a9325 Shuts up json-serialization codex logging 2024-05-07 11:04:32 +02:00
Ben 266c661958 Updates codex API 2024-05-07 10:21:05 +02:00
Ben 8ea4c4ee37 Clean up codex long tests 2024-05-07 10:15:02 +02:00
Ben 725dfc23a6 Cleanup of codex tests 2024-05-07 09:49:00 +02:00
Ben 39ff757b39 Update + instructions for codex contracts 2024-05-06 16:12:26 +02:00
benbierens e187bfc941 Changes time.retry to fixed timelength instead of fixed number of retries 2024-05-02 08:41:20 +02:00
benbierens dd36929a81 Sets scalability tests to current codex image 2024-05-01 13:51:46 +02:00
Ben 2902f6baab latest master image of Codex 2024-05-01 10:05:46 +02:00
benbierens f38f7861a0 Log comparing test 2024-04-27 18:08:54 +02:00
benbierens 84a68521b0 sets up parser for codex log lines 2024-04-27 14:20:31 +02:00
benbierens ce7ba47a32 trying to detect upload slowdown related to number of blocks in node 2024-04-27 11:33:13 +02:00
Ben a20e9a30cf bumps to image with proof support 2024-04-26 11:10:14 +02:00
Ben 52cec0e9f3 bump codex image 2024-04-26 10:43:36 +02:00
Ben 9a6866cf8e clean up logs in Codex 2024-04-25 15:04:02 +02:00
Ben 15d7e6483e Allows parsing longs only from a certain timestamp 2024-04-25 10:57:19 +02:00
Ben e1c710a093 Merge branch 'less-announce-tests' 2024-04-25 10:25:29 +02:00
Ben 0c700ded9d Test for detecting common log lines 2024-04-25 10:25:12 +02:00
Ben 50b7e2300d sets quota 2024-04-25 09:25:00 +02:00
Ben a6379d02f1 Adds test for upload/download large file with single node 2024-04-24 09:41:05 +02:00
Ben 6545f3469f dumb mistake by me 2024-04-23 13:16:11 +02:00
Ben 58f7f9384a Test for disc speeds 2024-04-23 09:10:10 +02:00
Ben 7ec9934751 Update to multipeer download test 2024-04-22 11:17:47 +02:00
Ben c4c3f61a23 parameterizes tests 2024-04-19 11:52:39 +02:00
Ben c856f404e3 Adds test to show from which hosts blocks are downloaded. 2024-04-19 11:40:32 +02:00
benbierens eed989cbf5 Handle errors during log download 2024-04-15 11:37:14 +02:00
benbierens 630dc2814a disable new test 2024-04-15 08:13:43 +02:00
benbierens 570b174a00 Adds everyone-test-a-file test 2024-04-15 08:12:57 +02:00
benbierens 700fc0ea40 Sets quota for codex nodes. Sets loglevel for bootstrap node. 2024-04-15 07:57:13 +02:00
benbierens 23ebd4166b Disables downloading logs 2024-04-15 07:36:12 +02:00
benbierens 3683044bf7 Dont download container logs 2024-04-14 11:17:59 +02:00
benbierens fb10906816 removes 20node value 2024-04-14 10:43:06 +02:00
benbierens 86074dab6a Bump k8s operation timeout for long timeset 2024-04-14 09:29:13 +02:00
benbierens 015d8da21d Better logging for Time.WaitUntil. 2024-04-14 09:22:55 +02:00
benbierens d847c4f3ec Adds names to kube wait functions 2024-04-14 08:56:22 +02:00
gmega a2e4869403 use long timeouts 2024-04-13 17:33:21 +03:00
gmega 5ffe34bb83 stop crash watcher before stopping pod 2024-04-13 17:20:23 +03:00
gmega e3b16fd742 add ability to stop single containers 2024-04-13 17:12:14 +03:00
gmega 80261959e7 set log level to info 2024-04-13 16:36:16 +03:00
gmega 7db9360ba4 add network/file scalability test 2024-04-13 16:27:47 +03:00
benbierens 899d775873 Adds finished requests 2024-04-13 11:15:39 +02:00
benbierens 581cc80d5d Implements getting blockTimeEntries 2024-04-13 09:19:20 +02:00
benbierens 24cf6c70b8 Improve event logging 2024-04-13 08:57:46 +02:00
benbierens ae25b58610 Fixes exception in market averages 2024-04-12 09:12:51 +02:00
benbierens 2cf5a26934 Sends updates to discord bot when no rewards are found. 2024-04-12 08:44:14 +02:00
benbierens 5ed78da30b Fixes possible crash in market averages 2024-04-11 17:28:12 +02:00
benbierens 3fb1b212b6 Merge branch 'feature/bot-upgrade' 2024-04-11 13:15:55 +02:00
benbierens ca7258ef28 async dispatches sending of event overview to discord channel 2024-04-11 13:14:21 +02:00
benbierens 1dbb749732 Removes ChangedEvents field from chain-state, not necessary 2024-04-11 08:50:03 +02:00
benbierens 525fdcf4a7 Merge branch 'master' into feature/bot-upgrade 2024-04-11 08:12:02 +02:00
benbierens 40208373e4 Merge branch 'feature/better-start-waiting' 2024-04-11 08:11:47 +02:00
benbierens 4a0885cf2d Check for marketplace contract up-to-date 2024-04-11 07:46:00 +02:00
benbierens 0224f17733 fixes serialization of request state 2024-04-09 13:24:30 +02:00
benbierens b7ab2f994e Fixes market test 2024-04-09 10:57:02 +02:00
benbierens 0182ce134f Applies future container to existing plugins 2024-04-09 10:23:07 +02:00
benbierens 69666d3fee Setting up future-containers 2024-04-09 09:30:45 +02:00
benbierens 605004286a Merge branch 'feature/bot-upgrade' 2024-04-08 16:08:05 +02:00
benbierens bbc975141f Creates events overview in rewarder bot 2024-04-08 16:07:52 +02:00
benbierens 0b2dcef57e Sets up showing of chain events in discord bot 2024-04-08 13:55:39 +02:00
benbierens 9f7e95c515 Applies async/await to getbalance and mint commands 2024-04-08 13:36:55 +02:00
benbierens 8db84bfe7d Merge branch 'feature/bot-upgrade' 2024-04-07 14:06:14 +02:00
benbierens 9c8151bdb7 Adds Market command 2024-04-07 14:04:31 +02:00
benbierens 43aaed225d Updates codex image 2024-04-03 20:40:13 +02:00
Ben b805c5f004 Prevents duplicate addresses to be sent in the same reward request 2024-04-03 09:21:10 +02:00
Ben 29344451d6 workaround for getpurchases api call 2024-04-02 14:53:45 +02:00
Ben 5511f8ed32 Extra logging for discordbot reward API 2024-04-02 13:32:06 +02:00
benbierens 362040bd3c Sets up docker image and CI 2024-04-01 21:29:02 +02:00
benbierens 40393c3a5b Using raw version of purchase-status call. API doesn't line up 2024-04-01 21:09:24 +02:00
benbierens 7f972bac85 Mvoes autoclient 2024-04-01 20:47:56 +02:00
benbierens d532d9505a Sets up autoclient. 2024-04-01 20:40:03 +02:00
benbierens 4cd22f3719 Rather embarrasing mistake in rewarderbot interval config param. 2024-04-01 15:55:47 +02:00
benbierens f5291517c1 Merge branch 'feature/bot-upgrade'
# Conflicts:
#	Tests/CodexTests/BasicTests/MarketplaceTests.cs
2024-04-01 15:32:43 +02:00
benbierens ceed913143 moves bot tests 2024-04-01 15:31:54 +02:00
benbierens 3086aa78d5 Uses 3 hosts in marketplace test 2024-04-01 15:22:12 +02:00
benbierens 6c4b9345cb sensible time range for bot test 2024-04-01 14:08:30 +02:00
benbierens 2ab84e2a61 Time handling for rewarder 2024-04-01 13:56:07 +02:00
benbierens 48e7f98956 Adds some logging 2024-04-01 13:46:30 +02:00
benbierens d7c7d47a61 Fixes crash in blocktimefinder 2024-04-01 13:42:07 +02:00
benbierens be268ace42 Find bug in blocktime finder 2024-04-01 13:21:21 +02:00
benbierens a6b0b16909 ready for bot test 2024-04-01 11:22:28 +02:00
benbierens 8c64352acf Merge branch 'master' into feature/bot-upgrade 2024-04-01 11:07:16 +02:00
Ben Bierens fd38cc336b Merge pull request #101 from codex-storage/feat/multi-stage-builds-for-rewarder
Use multi-stage builds for Rewarder
2024-04-01 11:06:52 +02:00
benbierens f5429b9c14 Merge branch 'master' into feature/bot-upgrade
# Conflicts:
#	Tests/CodexTests/BasicTests/ExampleTests.cs
2024-04-01 11:06:06 +02:00
benbierens 1344a6e3d2 Cleanup 2024-04-01 11:01:21 +02:00
benbierens 9a2e889ef9 Fixes convert issue in mapper. Removes CodexPlugin from discord bot dependencies. 2024-04-01 10:11:44 +02:00
Slava a58c9aba59 Use multi-stage builds for BiblioTech 2024-04-01 11:05:17 +03:00
benbierens 0263cc4eff Force rebuild of openapi client code each CodexPlugin build. 2024-04-01 10:04:32 +02:00
benbierens b40215dd36 Updates image and api 2024-04-01 08:58:45 +02:00
benbierens 4e978bd5b5 Restores continuous tests 2024-04-01 08:29:55 +02:00
Slava 0a8083d4d5 Use multi-stage builds for Rewarder 2024-03-31 22:18:48 +03:00
Ben 376fd60974 debugging image 2024-03-29 12:09:01 +01:00
Ben 8bc63c1fdb Configurable testtoken and eth amounts for mint command 2024-03-29 11:24:11 +01:00
Ben 4f461e4cb3 Checking onchain events to debug missing slotFilled event 2024-03-28 13:49:02 +01:00
Ben b25c747522 Ready for test. Need new bot images 2024-03-27 15:49:08 +01:00
Ben 01d6b8f227 Applies blockInterval 2024-03-27 15:39:42 +01:00
Ben 330552563c Merge branch 'master' into feature/bot-upgrade
# Conflicts:
#	Tests/CodexTests/BasicTests/ExampleTests.cs
2024-03-27 15:25:58 +01:00
Ben 581cf2a813 Merge branch 'feature/codex-openapi' 2024-03-27 15:24:40 +01:00
Ben f6edf6cbd5 Overdue readme updates 2024-03-27 15:01:32 +01:00
Ben db6db4d38c Fixes metrics tests 2024-03-27 09:41:12 +01:00
Ben 127b7daecc Improves plugin log initialization 2024-03-27 08:41:18 +01:00
Ben 6597728e5c Makes contract-clock log topic configurable 2024-03-27 08:13:36 +01:00
Ben 9e9773f45d Merge branch 'master' into feature/codex-openapi
# Conflicts:
#	ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs
2024-03-26 16:16:39 +01:00
Ben 9fe97f5d40 Debugs compatibility check. 2024-03-26 16:10:50 +01:00
Ben a53d2de13b Moves ethAccount to Geth plugin. Enables setting ethAccount when Codex starts. 2024-03-26 15:35:26 +01:00
Ben d9d8441b6e compiletime checking of all map calls 2024-03-26 15:12:28 +01:00
Ben e72c1b037c Implements api compatiblity checker 2024-03-26 14:42:47 +01:00
Ben bce9a2c124 Lines up new openAPI.yaml 2024-03-26 14:07:06 +01:00
Ben f57188914e Restores marketplace access 2024-03-26 12:23:38 +01:00
Ben 3bd297a22d restores connect-to-peer 2024-03-26 12:14:02 +01:00
Ben 85774847b7 Restoring marketplace types 2024-03-26 11:39:59 +01:00
Ben 8fcf351613 Restores debug/peer 2024-03-26 10:31:49 +01:00
Ben 05d28d1d0a Restores debug info 2024-03-26 10:03:52 +01:00
Ben a3253b42d8 Introduces endpoint component from old http functionality 2024-03-26 09:10:06 +01:00
Ben b69059fd37 restoring debug-info 2024-03-26 08:58:16 +01:00
Ben c122aa9910 sets up http client 2024-03-25 15:46:45 +01:00
Ben 03a3ccb4de prototype of pre-build yaml hash injection 2024-03-25 13:48:20 +01:00
Ben 87ec67778b poc: upload and download streams working 2024-03-25 11:37:41 +01:00
Ben bd5fe0cae1 Sets up client generation with NSwag 2024-03-25 10:20:44 +01:00
Ben e8213b9515 Updates codex image 2024-03-25 10:17:32 +01:00
Slava 617b656cf7 Use just folder as a path for digests upload (#99) 2024-03-23 21:13:25 +02:00
Ben 042285e664 increases block cache size 2024-03-22 14:16:55 +01:00
Ben 7d28b62206 new image 2024-03-22 13:36:33 +01:00
Ben a27b294031 Updates codex marketplace API types 2024-03-22 11:15:22 +01:00
Ben 0a4c4c60c5 Merge branch 'master' into feature/bot-upgrade
# Conflicts:
#	Framework/NethereumWorkflow/BlockTimeFinder.cs
#	Framework/NethereumWorkflow/NethereumInteraction.cs
2024-03-22 10:59:57 +01:00
Ben 635849c37a Merge branch 'feature/persistence-prover' 2024-03-22 10:56:50 +01:00
Ben 3cc3a2a9dd cleanup 2024-03-21 16:26:48 +01:00
Ben 6d4d6fcdb9 working blocktime finder 2024-03-21 16:11:28 +01:00
Ben 4fdb310ca4 Sets up tests for blocktimefinder. 2024-03-21 12:18:04 +01:00
Ben Bierens 9ce82cb011 Merge pull request #72 from GoodDaisy/master
fix Continous typo
2024-03-20 14:45:09 +01:00
Ben db37143db6 cleanup 2024-03-20 13:36:06 +01:00
Ben 2a5b3e0eec fixes marketplace test 2024-03-20 11:32:16 +01:00
Ben ef53fe02a3 Improve logfile checking 2024-03-20 11:11:59 +01:00
Ben a6c2bf5230 Cleanup marketplace-access methods 2024-03-20 11:11:41 +01:00
Ben 32f56e8213 Marketplace test fails: submitted proof is invalid. 2024-03-15 16:07:10 +01:00
Ben f757e64ba4 wip 2024-03-15 13:50:39 +01:00
benbierens 188f765b5c bash, not sh 2024-03-15 12:48:25 +01:00
benbierens 8c8b1748ee Merge branch 'master' into feature/bot-upgrade
# Conflicts:
#	Tests/CodexTests/BasicTests/ExampleTests.cs
2024-03-15 09:35:27 +01:00
Ben c204ab09d1 combinatorial tests for finding issue 2024-03-14 14:30:00 +01:00
Ben db5c5444c5 Removes contract clock from logging 2024-03-14 14:29:28 +01:00
Ben 209eb31c58 fixes issue where kubernetes does not honor container entrypoints when command override is used 2024-03-13 12:09:25 +01:00
Ben 956cdbcfbe sets contracts image to latest 2024-03-13 10:57:35 +01:00
Ben c5fb066c75 Allows for non-blocking stop of containers 2024-03-13 10:57:26 +01:00
Ben 90b90be3cb Updates usages of EnableMarketplace 2024-03-13 10:29:26 +01:00
Ben a6a8c3f1e7 Update codex container recipe 2024-03-13 10:18:10 +01:00
Ben b7904f7ee0 Updates codexSetup and Marketplace configuration 2024-03-13 10:14:11 +01:00
Ben 23396f54e0 Revert "Adds support for command overrides to container recipes."
This reverts commit d1a70d2465.
2024-03-13 10:01:47 +01:00
Ben e42f1ddbd7 Adds support for command overrides to container recipes. 2024-03-13 10:01:37 +01:00
Ben d1a70d2465 Adds support for command overrides to container recipes. 2024-03-13 10:01:14 +01:00
Slava 3c8efbd0a0 ci: upldate actions to the latest versions (#98) 2024-03-12 16:32:31 +02:00
benbierens 247e010fba Cleanup marketplace test 2024-02-28 10:06:11 +01:00
benbierens f0167a800f Speed up basic tests 2024-02-27 13:55:05 +01:00
benbierens b35cf3139d Bumps contracts image to latest 2024-02-26 15:08:04 +01:00
benbierens 64ecf531bd Adds missing error message check to marketplaceAccess 2024-02-26 09:02:46 +01:00
Giuliano Mega ec0f7a6790 Remove run ID and introduce deploy ID (#93)
This PR removes the notion of a run id and replaces it with a deploy id in continuous tests. Deploy ids can be set at deploy time only (duh), and will be picked up by the test runner from the deploy file on subsequent runs of the continuous test runner. As a consequence, the deploy id becomes a deployer parameter, and can no longer be overridden at the runner. For non-continuous tests, the deploy ID is created on-the-fly.
2024-02-22 10:41:07 -03:00
Slava 930c44f037 ci: use temporary branch for setup-kubectl action (#96) 2024-02-19 20:50:42 +02:00
benbierens a6fce1084d Check connection no more than once every 30 seconds 2024-02-19 15:59:49 +01:00
benbierens 23b7bbd548 Fixes sending of rewards to bot 2024-02-19 15:57:28 +01:00
benbierens 42d3c5cd2c debugging rewards 2024-02-19 15:41:48 +01:00
benbierens 1bf693938c Block range type 2024-02-19 15:20:12 +01:00
benbierens da4101a042 Debugging time segmenter 2024-02-19 14:56:49 +01:00
Slava dc33f0655c ci: upldate actions to the latest versions (#95) 2024-02-19 13:05:51 +02:00
benbierens 3c210f96fc debugging reward api 2024-02-19 09:11:36 +01:00
benbierens dd5baeda46 Adds reward api port to bot config 2024-02-01 17:02:10 -05:00
benbierens 7d33c1c113 Sets up rewarder bot container recipe 2024-01-31 11:52:02 -05:00
benbierens 8a4d9fecea Merge branch 'feature/bot-upgrade' 2024-01-31 11:31:52 -05:00
benbierens cb259a9086 Setting up docker for rewarder bot 2024-01-31 11:30:29 -05:00
benbierens 9d8ec6f794 loads up reward-repo with test server roles 2024-01-31 11:18:22 -05:00
benbierens acc9526cd5 Extracts reward config from bot and rewarder 2024-01-29 11:02:47 -05:00
benbierens 43a160a9cc Wires everything up 2024-01-27 09:12:20 -05:00
benbierens 4de2626a33 wip 2024-01-26 18:17:56 -05:00
benbierens 255e08a301 Implements check classes 2024-01-26 17:29:57 -05:00
benbierens 71409b06ae Increases timeout for codex contracts deployment 2024-01-26 11:37:18 -05:00
benbierens f25bca727d Fixes crash in blocktime finder. Increases smart contract deploy timeout. 2024-01-26 11:27:37 -05:00
benbierens fb73312e80 Merge branch 'master' into feature/bot-upgrade 2024-01-26 08:47:23 -05:00
Slava 9a626b7260 Updates contracts image to latest working one (#92) 2024-01-25 19:10:49 +02:00
Slava ede80410ea Disable Report workflows (#91) 2024-01-23 10:47:36 +02:00
benbierens 00c720137a Adds method for getting request state and receiving cancelled events 2024-01-22 16:27:32 +01:00
benbierens 890cff93d5 Adds method for getting request-fulfilled events 2024-01-22 16:05:04 +01:00
benbierens 678b719cef wip 2024-01-22 11:47:28 +01:00
benbierens 1b7c11b849 Setting up rewards 2024-01-22 10:27:07 +01:00
benbierens c6a7489f11 wip 2024-01-20 13:07:56 +01:00
benbierens f7fcef56c7 upgrades log filtering 2024-01-18 10:24:59 +01:00
benbierens 29fa554146 Adds log topics for block exchange 2024-01-18 09:55:07 +01:00
benbierens 2f10b30283 Sets up rewards api and handling. 2023-12-20 15:56:03 +01:00
benbierens 391a2653d9 Sets up getting of slot filled and freed events 2023-12-20 13:21:53 +01:00
benbierens 55be07d711 Implements getting slot host address 2023-12-20 11:34:23 +01:00
benbierens 75757e37fb Can fetch storage requests from chain 2023-12-20 10:55:29 +01:00
benbierens 2be31a4d3b Cleanup 2023-12-20 10:33:44 +01:00
benbierens 4b74a9d5fe working block time finder 2023-12-20 09:48:22 +01:00
benbierens 4f2539c59f Adds marketplace generated code 2023-12-19 15:43:26 +01:00
benbierens 7df1f3da7b block time finder 2023-12-19 15:30:46 +01:00
benbierens fb57998aa8 Merge branch 'master' into feature/bot-upgrade 2023-12-19 14:02:31 +01:00
Slava c4eceacafb Trigger Docker builds on folders content changes (#90) 2023-12-18 16:42:23 +02:00
Slava 974fc460c4 Remove preview from MintCommand explorer link (#89) 2023-12-18 16:38:16 +02:00
benbierens 87e84ae90d Adds method for checking of codex contracts are deployed 2023-12-18 13:32:58 +01:00
benbierens 2c026f99ca Merge branch 'feature/bot-upgrade' 2023-12-18 11:28:22 +01:00
benbierens 6298744918 Better logging 2023-12-18 11:27:28 +01:00
Slava 5342261db0 Update Report workflows (#88) 2023-12-18 11:30:15 +02:00
Slava fd540a53b1 Add Discordbot docker builds (#87)
* Add BiblioTech Docker builds

https://github.com/codex-storage/infra-codex/issues/113

* Organize workflows

https://github.com/codex-storage/infra-codex/issues/113
2023-12-15 14:07:52 +02:00
benbierens 03283414cb Adds transaction ID to record of user activity 2023-12-15 11:09:15 +01:00
benbierens beaa67c280 Adds transaction links to mint output 2023-12-15 11:02:06 +01:00
benbierens d67ddab290 Checking for all unwanted log entries 2023-12-15 09:32:03 +01:00
benbierens b143136590 Merge branch 'feature/public-testnet-deploying' 2023-12-14 15:59:22 +01:00
benbierens 8fc8a3f7cf Adds check for block verification failures 2023-12-12 14:18:26 +01:00
benbierens 2b21722cf3 forgot to hook up getSPR command 2023-12-11 11:19:05 +01:00
benbierens c3fb7384e3 Sets up deploying discordbot with geth env vars 2023-12-11 11:09:29 +01:00
benbierens fde19383df Removes deployment.json from discord bot. 2023-12-11 11:01:12 +01:00
benbierens 777e414f0a Prevents duplicate deployments. 2023-12-11 09:59:09 +01:00
benbierens 6d7309cc9c Update bot for deployment without codex nodes. 2023-12-11 08:51:46 +01:00
benbierens ec03be6936 Fixes initialization of geth node 2023-12-11 08:38:31 +01:00
benbierens 5dc918287c Merge branch 'master' into feature/public-testnet-deploying 2023-12-11 08:30:25 +01:00
benbierens 55811b20da Fixes validation for not deploying codex nodes 2023-12-11 08:30:05 +01:00
benbierens 09554da362 Adds transfer speeds to status logs. 2023-12-06 10:50:02 +01:00
benbierens 074f5ebfae Sets up transferSpeed class 2023-12-06 09:59:45 +01:00
benbierens 260bc93414 Updates contracts image to latest 2023-12-06 09:34:22 +01:00
benbierens ad05dc07f0 wip report for november 2023-12-05 08:31:28 +01:00
Slava 12ea002660 Update Runners Jobs nodeSelector (#86)
https://github.com/codex-storage/infra-codex/issues/96
2023-11-27 19:22:28 +02:00
Slava 4c46a708ab Change affinity label (#85)
https://github.com/codex-storage/infra-codex/issues/100
2023-11-27 19:06:04 +02:00
Slava b57a727195 Adjust C-Tests for autoruns (#84) 2023-11-26 21:20:04 +02:00
Slava 00207de04d Auto cleanup old Continuous tests Jobs (#83) 2023-11-24 20:29:50 +02:00
benbierens 3761e236a3 Sets node critical priority for codex and geth nodes 2023-11-23 14:50:54 +01:00
benbierens ff52e8e841 Defines public IP fetching service 2023-11-23 14:15:37 +01:00
benbierens b61f5d835c Fixes request-storage endpoint 2023-11-23 14:10:59 +01:00
benbierens 1a86d3459d Fixes local files endpoint 2023-11-23 13:43:19 +01:00
benbierens 6bd7098cfe Fixes download endpoint. 2023-11-23 13:32:58 +01:00
Slava 06ff7c9760 Add CODEXDOCKERIMAGE input to C-Tests workflow (#82) 2023-11-22 21:04:59 +02:00
Slava 703f410161 Fix reports workflows scheduler (#81) 2023-11-22 12:38:19 +02:00
benbierens 46ab3b31ca Checking that we can bootstrap geth nodes together. 2023-11-21 10:33:11 +01:00
benbierens a84d6a3c22 Replaces debug/info fetching with list of known SPRs, settable via admin commands. 2023-11-21 09:38:58 +01:00
benbierens 54471d41d5 Allows for deployment without codex nodes 2023-11-21 09:03:52 +01:00
benbierens 8b224f6922 Updates public ip to point to service 2023-11-20 11:19:10 +01:00
benbierens 005c9b4259 Merge branch 'master' into feature/public-testnet-deploying 2023-11-20 11:17:17 +01:00
Slava 6f53e78e6a Run C-Tests reports by Scheduler (#80) 2023-11-19 21:13:40 +02:00
benbierens f4532f3801 Target duration now supports timespan string format. 2023-11-17 14:51:32 +01:00
benbierens b90c47a994 Switches to automatic public IP address for codex and geth containers. 2023-11-15 14:53:25 +01:00
benbierens db55792f42 Catching exceptions in task-factory. 2023-11-14 13:28:50 +01:00
benbierens 5996c0fa63 Adds involvedpods, error, and duration in seconds to status logs for continuous tests. 2023-11-14 12:56:47 +01:00
benbierens 485e3cf02e Merge branch 'master' into feature/public-testnet-deploying 2023-11-14 10:50:41 +01:00
benbierens ee319a6d0f Merge branch 'feature/scheduling-affinity' 2023-11-14 10:50:32 +01:00
benbierens b47b596062 Fetches used external ports in order to guarantee no collisions. 2023-11-14 10:49:14 +01:00
benbierens 7de0e5a1c4 Sets up tests-runners as avoided scheduling affinity. 2023-11-14 10:16:00 +01:00
Slava 7114fd1c00 Use dedicated node pool for tests runners (#79)
https://github.com/codex-storage/infra-codex/issues/88
2023-11-13 22:37:00 +02:00
benbierens 4192952a37 Prevents reuse of external port numbers 2023-11-13 16:05:41 +01:00
benbierens b82c74865e Adds numberSource for correct range for external ports 2023-11-13 15:27:52 +01:00
benbierens ca350604e3 Debugging nodeport 2023-11-13 15:11:49 +01:00
benbierens 5a7608460b retry by setting nodeport explicitly 2023-11-13 13:58:34 +01:00
benbierens 2565ced7fe Adds test report for october 2023-11-13 13:29:28 +01:00
benbierens a81f0b9145 Merge branch 'master' into feature/public-testnet-deploying 2023-11-13 13:15:23 +01:00
benbierens 1f841ca629 Removes modified/clean indicator 2023-11-13 13:05:44 +01:00
benbierens 537c37351a Merge branch 'master' of https://github.com/status-im/cs-codex-dist-tests 2023-11-13 11:57:48 +01:00
benbierens 0f9e01e393 Adds git revision information to logs for testing framework status 2023-11-13 11:56:02 +01:00
Slava 90118b3481 Use separate log folder per Continuous Tests run (#78) 2023-11-13 12:50:32 +02:00
benbierens 412a4d3c7a Sets exit code for continuous tests runner 2023-11-13 11:17:26 +01:00
benbierens 960bef455a Adds codex revision to metadata. 2023-11-13 09:37:45 +01:00
benbierens 344d3cd44f Merge branch 'master' into feature/continuous-test-status-logs
# Conflicts:
#	Tests/DistTestCore/Logs/StatusLog.cs
2023-11-13 09:33:24 +01:00
benbierens 6636ef3ac1 Moves routing table test into peerDiscovery tests. 2023-11-13 09:32:10 +01:00
Slava 496f8fa370 Add Run ID to the Tests Identification (#77) 2023-11-12 18:35:11 +02:00
Slava 25148db971 Show Tests Identification in runners workflows (#76) 2023-11-12 17:07:53 +02:00
benbierens 71ad471958 Cleanup routing table tests. Adds retry. 2023-11-12 11:24:58 +01:00
benbierens 96ff3c38bb Adds test for routing table checks. 2023-11-12 10:36:48 +01:00
benbierens ed56d9edcc Cleanup of kubernetesWorkflow assembly. 2023-11-12 10:07:23 +01:00
benbierens b78f527c39 Adds test-type to STATUS log jsons. 2023-11-10 15:28:53 +01:00
benbierens 096282ae1a Fixes debug/peer serialization. Adds retry for pod-finding. 2023-11-10 15:23:16 +01:00
benbierens 0be6df46bc Merge branch 'deployment-json-rework' into feature/continuous-test-status-logs 2023-11-10 09:49:35 +01:00
benbierens c5cc15daa0 Updates endpoint to match latest Codex 2023-11-10 08:20:08 +01:00
benbierens 0301c3b076 Adds status log entry for each test run in continuous tests. 2023-11-09 11:35:45 +01:00
benbierens 84382b4ab4 Removes server-admin requirement for admin commands 2023-11-09 11:14:17 +01:00
benbierens 26ec841db1 locks out default permissions for admin commands 2023-11-09 10:45:28 +01:00
benbierens 1ed93bbdde Adds required admin permission for admin commands. 2023-11-09 10:37:57 +01:00
benbierens ef83441b2f Correct chunking of long messages 2023-11-09 10:13:34 +01:00
benbierens 180bf3fd35 Fixes debugPeer response formatting 2023-11-08 13:33:48 +01:00
benbierens d1403c04c4 updates bot secret name 2023-11-08 11:59:31 +01:00
benbierens 8d12bc45a1 Loops in kubeconfig and namespace for discord bot. 2023-11-08 11:49:21 +01:00
benbierens 9f0f7c374a port update 2023-11-08 10:19:06 +01:00
benbierens b8ce4c49d6 Merge branch 'master' into deployment-json-rework
# Conflicts:
#	Tests/CodexContinuousTests/run.sh
#	Tools/CodexNetDeployer/deploy-continuous-testnet.sh
2023-11-08 09:29:22 +01:00
benbierens 90070de028 Enable debug logging from single environment variable or static field. 2023-11-08 09:24:39 +01:00
benbierens 073f0d86a1 Cleans up runnerLocationUtils. 2023-11-07 16:02:09 +01:00
benbierens ec6c987ef9 determine runner location with environment variables 2023-11-07 15:51:28 +01:00
benbierens 3f8699a59b Removes dependency on pod information for elastic search download 2023-11-07 14:33:45 +01:00
benbierens 2b463d060d Restores debug setting 2023-11-07 14:00:08 +01:00
benbierens 9d464708b5 Updates local address format 2023-11-07 13:49:28 +01:00
benbierens 3e49ce6ae9 Adds kube namespace to internal addresses 2023-11-07 13:13:06 +01:00
benbierens 6e60a8614c Removes runnerlocation field from StartResult. 2023-11-07 13:06:49 +01:00
benbierens eecdcf308d Rewrites runner location set 2023-11-07 12:48:18 +01:00
benbierens 3e500e8346 Unknown runner location option. 2023-11-07 12:42:01 +01:00
benbierens ea66b5b408 debugging address selection in cluster 2023-11-07 12:02:17 +01:00
benbierens 655f4895d0 Fixes serialization issue for running containers for discord bot and metrics container 2023-11-07 11:17:20 +01:00
Ben Bierens dae4aa1f64 Merge pull request #74 from codex-storage/feature/automate-continuous-tests
Automate Continuous Tests using GitHub Actions
2023-11-07 10:48:45 +01:00
benbierens 20f92633f7 updates metrics name in twoclient test 2023-11-07 10:42:31 +01:00
benbierens d0bed64c68 Disables resource limits 2023-11-07 09:25:51 +01:00
benbierens 1412dd84fe Get ports explicitly for internal or external endpoints 2023-11-06 16:52:55 +01:00
benbierens ad88560061 Creates internal service ports for external ports from container recipe. 2023-11-06 16:38:32 +01:00
benbierens 8d0b3feff7 logs runner location 2023-11-06 16:12:18 +01:00
benbierens 6672427565 Restores marketplace test 2023-11-06 16:10:19 +01:00
benbierens 4f9c0e0ce7 Restores metrics 2023-11-06 15:27:23 +01:00
benbierens dc9f3ab090 removes dependency on static pod name and address info 2023-11-06 14:33:47 +01:00
Veaceslav Doina ead1ff7ec6 Automate Continuous Tests using GitHub Actions (#69) 2023-11-06 15:09:51 +02:00
Veaceslav Doina 807e8b7b5f Fix TwoClientTest metric name
https://github.com/codex-storage/nim-codex/pull/603
2023-11-06 15:06:47 +02:00
benbierens db0a21bc60 Merge branch 'automated-teststarter' into deployment-json-rework 2023-11-03 14:57:32 +01:00
benbierens 19d466d5d6 better service names 2023-11-03 14:53:27 +01:00
benbierens f9408ab3b5 Merge branch 'feature/public-testnet-deploying' into automated-teststarter 2023-11-03 14:46:17 +01:00
benbierens 438c4271e1 Updates response message instead of deleting it. 2023-11-02 15:04:53 +01:00
benbierens fe11a97458 Fixes initialization issue for deployments monitor 2023-11-02 14:25:17 +01:00
benbierens b8774e7273 Adds SPR-fetching command. 2023-11-02 12:30:48 +01:00
benbierens bc51fc2e30 Fixes faulty persistent volume claim creation 2023-11-02 11:32:24 +01:00
benbierens 5241144e99 Locks up deploy-and-run script after tests are started. 2023-11-01 10:27:31 +01:00
benbierens 49300273e0 Cleanup continuous tests nicely even if no tests were selected for running. 2023-10-31 15:33:00 +01:00
benbierens fcb5a527a9 Fixes pod-readback labeling issue. 2023-10-31 15:04:59 +01:00
benbierens a6f7bc2393 Removes knownPods class. 2023-10-31 14:48:16 +01:00
benbierens ac07327d77 Upgrades volume support for use with deploy-and-run container 2023-10-31 14:20:50 +01:00
benbierens b5e5570145 Creates volume mount for kubeconfig file 2023-10-31 11:38:54 +01:00
benbierens c348ca9849 sets up test starter tool 2023-10-31 11:22:59 +01:00
benbierens bfdbebb36e Adds deploy-and-run plugin 2023-10-31 11:15:08 +01:00
benbierens e87f255f48 Creates dockerimage that will deploy-and-run from environment args 2023-10-31 11:01:10 +01:00
benbierens 85ad0b414f Cleans up some gethnode arguments. 2023-10-30 13:30:14 +01:00
benbierens 901259d0fa Adds debug-peer command 2023-10-29 10:40:01 +01:00
benbierens 62cc9e0587 Formats the json response 2023-10-29 10:02:51 +01:00
benbierens d7827a4531 Forgot to connect command handler. 2023-10-29 09:47:34 +01:00
benbierens f17d123af8 Adds netinfo command 2023-10-29 09:25:50 +01:00
benbierens d5b87253ae Explicitly turn off auto-nat. 2023-10-27 11:21:43 +02:00
benbierens 529f923595 Sleep before fetching debug info. 2023-10-27 10:56:30 +02:00
benbierens a362eb1e96 Separate port for geth listening 2023-10-27 08:33:07 +02:00
GoodDaisy 65473d8c15 fix Continous typo 2023-10-26 20:27:44 +08:00
benbierens 9b1ab3185f Idiot developer forgets to use variable. 2023-10-25 14:42:53 +02:00
benbierens 939eed544d Fixes error where empty string is passed as datapath 2023-10-25 14:33:47 +02:00
benbierens 3914d58a6a Sets up support for UDP ports and applies them to discovery ports. 2023-10-25 14:23:07 +02:00
benbierens 4102ce0a04 Moves discord bot datafiles to volume 2023-10-25 11:53:33 +02:00
benbierens 69296577f8 adds Whois command 2023-10-25 11:25:27 +02:00
benbierens bd9fc3a3cf Ephemeral messages. 2023-10-25 10:54:26 +02:00
benbierens f148598a21 Restrict admin commands to admin channel 2023-10-25 10:38:21 +02:00
benbierens 6e82d6b1e6 Disallow duplicate eth addresses 2023-10-25 10:25:00 +02:00
benbierens 6d44a0ccfc Important user feedback message update 2023-10-25 09:54:08 +02:00
benbierens fb70daa2e9 Adds discord bot to deploy script 2023-10-25 09:40:47 +02:00
benbierens 14e8222dfe Adds codex debug-info to deployment json. Needed for bootstrapping later on. 2023-10-25 09:14:35 +02:00
benbierens dc0edce251 Moves continuous-tests default log path to persistent volume 2023-10-25 08:56:48 +02:00
benbierens b8b9e6b997 Successful deployment add and remove. 2023-10-24 15:45:40 +02:00
benbierens 29672ece68 remove command 2023-10-24 15:43:07 +02:00
benbierens a0461a446e cleanup of admin commands 2023-10-24 15:25:45 +02:00
benbierens 5aff8c6f6d setting up subcommands. 2023-10-24 14:07:15 +02:00
benbierens dfe477d192 Removes codexdeployment from input of discord bot. 2023-10-24 13:48:24 +02:00
benbierens ade08a27fe Wires codex bot into deployer. 2023-10-24 10:29:07 +02:00
benbierens cc8a860f41 First attempt to send deployment json into discord bot container. 2023-10-24 10:17:57 +02:00
benbierens d4522f0d9c Sets up bot container recipe 2023-10-24 10:06:07 +02:00
benbierens b74349cc68 Setting up codex discord bot plugin 2023-10-24 09:51:29 +02:00
benbierens 49f6c7e37e Merge branch 'master' into feature/public-testnet-deploying 2023-10-24 09:42:37 +02:00
benbierens e073f7a881 Fixes locking and retrying in http class 2023-10-24 09:41:37 +02:00
benbierens 3326c42f7a Switching to ip + port internal addresses 2023-10-24 09:03:56 +02:00
benbierens eb70fe612b trying with podIP for internal addresses. 2023-10-23 16:12:45 +02:00
benbierens b3ba39b2e5 Debug logging for nethereum interactions 2023-10-23 16:04:50 +02:00
benbierens abb9560b6d Better logging of k8s exceptions 2023-10-23 15:49:14 +02:00
benbierens 2fae9505d6 attempt to fix contract deployment in testnet mode 2023-10-23 15:28:20 +02:00
benbierens b2b338d0a5 how did this not get commited? 2023-10-23 14:25:13 +02:00
benbierens 0e087c6fee Logs continuous test config at startup. 2023-10-23 14:17:52 +02:00
benbierens 58816430e3 Adds internal ports to container-port array as long as they have tags. 2023-10-23 14:05:08 +02:00
benbierens 5a021a4bfe Capture only first Geth account. 2023-10-23 13:36:20 +02:00
benbierens a68e849768 Wires up geth public IP and discovery port. 2023-10-23 13:19:57 +02:00
benbierens 020865f5c0 Wires up deployer config 2023-10-23 13:08:49 +02:00
benbierens 4280f910ae Makes making storage available optional. 2023-10-23 12:52:47 +02:00
benbierens ec8a041257 Setting up deployer config for public testnet deploying. 2023-10-23 12:43:50 +02:00
benbierens bcb690d143 Adds option to deploy codex in a public test net. 2023-10-23 12:33:48 +02:00
benbierens 840e794761 wip: updating codex container recipe for public deployment 2023-10-23 11:30:54 +02:00
benbierens 854325f10c Adds option to deploy geth for public testnet. 2023-10-23 11:23:44 +02:00
benbierens 7a6d7d787b Splits up metrics-scraper from metrics-endpoints configuration in deployer. 2023-10-23 10:57:59 +02:00
benbierens cc2e8d5992 Merge branch 'app/discord-bot' 2023-10-23 10:32:23 +02:00
benbierens 4adce837ec Logs total run duration in overview log. 2023-10-23 10:32:11 +02:00
benbierens e11a7d1600 Gives deployments a name. 2023-10-23 10:19:52 +02:00
benbierens ad70394333 Merge branch 'master' into app/discord-bot 2023-10-23 10:13:23 +02:00
benbierens 50fbf0ad52 Replaces retry-time with maxNumberOfRetries in timesets. 2023-10-23 10:11:02 +02:00
benbierens 45fbd699a9 Disables calls to custom API endpoints. 2023-10-23 09:36:31 +02:00
benbierens bf18fa03a2 adds sleep to the starting of the test screens 2023-10-22 11:29:16 +02:00
benbierens 116f62e73e Adds eth address validation. 2023-10-22 11:26:00 +02:00
benbierens 8ef2e6023e All works 2023-10-22 11:10:45 +02:00
benbierens e16b1ce079 Sets up guild role checking 2023-10-22 10:38:46 +02:00
benbierens 4aa4731480 setting up all the commands 2023-10-22 10:10:52 +02:00
benbierens 8ad2dee67c Adds user repo. 2023-10-22 09:32:03 +02:00
benbierens 869aeb9253 Deals with timeout for operations that may take a while. 2023-10-20 11:20:38 +02:00
benbierens 8910c7ff27 Stores contracts deployment as part of CodexDeployment json. 2023-10-20 10:15:38 +02:00
benbierens 2b10f2ec58 Adds mint command 2023-10-20 10:14:56 +02:00
benbierens 991927b95f Setting up balance-getting command 2023-10-20 09:49:23 +02:00
benbierens b1bd1de027 Merge branch 'feature/multiple-container-addresses' 2023-10-20 08:31:45 +02:00
benbierens 3b258c9e2e Pins contract image to one compatible with current main codex 2023-10-20 08:31:23 +02:00
benbierens 0fd6a6f06e Fixes port tag mismatch 2023-10-19 15:48:49 +02:00
benbierens 2fea475237 multiple service ports 2023-10-19 14:03:36 +02:00
benbierens 45050c34e4 Implements GetAddress method for runningContainers. 2023-10-19 11:18:59 +02:00
benbierens 43fa57dc97 Mandatory port tags for exposed ports 2023-10-19 11:12:08 +02:00
benbierens 3a8bb760ef Adding support for multiple exposed container ports 2023-10-19 11:08:30 +02:00
benbierens 766e2f5c20 Very basic endpoint pinging that might not even work. 2023-10-18 14:59:39 +02:00
benbierens 888b19d8e5 working example of slash commands with arguments 2023-10-18 13:55:56 +02:00
benbierens f33866efc1 setting up slash commands 2023-10-18 13:48:15 +02:00
benbierens 8c7229504e do not print token 2023-10-18 11:21:06 +02:00
benbierens bcb05cd0c9 Dockerizes discord bot 2023-10-18 11:01:24 +02:00
benbierens 7179c70463 Sets up an echo command 2023-10-18 09:10:04 +02:00
benbierens b3da42522f Sets up project 2023-10-18 08:57:59 +02:00
benbierens 6b1102efa7 Adds name argument to deploy-and-run script 2023-10-17 14:32:05 +02:00
benbierens 8c82b4c527 defaults the codex log topics to warn 2023-10-17 13:52:04 +02:00
benbierens d0cafb83a1 Fixes compile error in single test runner 2023-10-16 13:10:45 +02:00
benbierens 8e4d43b73b Adds start and finished times to deployment json. 2023-10-16 11:19:57 +02:00
benbierens 8f37b4cf38 Sets long timeouts for debug/repostore call. 2023-10-10 18:08:21 +02:00
benbierens 1a277ef1b5 Logging repostore content on twoclient test failure 2023-10-10 17:54:19 +02:00
benbierens b81d574a4b adds block exchange tests. Updates namespaces 2023-10-09 16:59:52 +02:00
benbierens 7aae48d489 Merge branch 'experiment/deploy-replication' 2023-10-09 16:37:09 +02:00
benbierens b4a187f0b6 undo custom image 2023-10-09 16:35:09 +02:00
benbierens b3ab369dde adds peer id 2023-10-09 12:59:05 +02:00
benbierens f08986f6c0 Setup call to debug blockexchange endpoint. 2023-10-09 12:33:35 +02:00
benbierens b0610393af rigged for twoclient test with full container log download 2023-10-08 19:11:31 +02:00
benbierens e4b7e461e9 Adds pod name to downloaded logs 2023-10-08 18:58:15 +02:00
benbierens 0dcf3b0b16 Adds pod and deployment name 2023-10-08 18:56:00 +02:00
benbierens f3555f2703 Adds node id to downloaded container logs 2023-10-08 08:31:48 +02:00
benbierens 24ba0b4f09 Adds missing libp2p topics 2023-10-08 08:04:04 +02:00
benbierens e2a6a2f792 fixes possible null-ref 2023-10-08 07:46:48 +02:00
benbierens 4f764ac4c7 Wires up the codex log topics config options. 2023-10-08 07:29:55 +02:00
benbierens ab9257ce56 moving the deployment json 2023-10-07 10:29:09 +02:00
benbierens 1a48c40ad7 Example of setting codex log topics for libp2p and discv5 2023-10-07 09:48:12 +02:00
benbierens 092128d77b with working loops this time 2023-10-07 08:04:11 +02:00
benbierens c653afa554 trying with script 2023-10-07 07:53:10 +02:00
benbierens fab855c4bb adds log and data paths 2023-10-07 07:49:55 +02:00
benbierens 75e263be31 Try with script 2023-10-07 07:47:54 +02:00
benbierens 58016378c4 Increase deployer kubernetes timeout 2023-10-07 07:40:20 +02:00
benbierens b365d4c0ba bumps timeout because cluster might need to start more nodes 2023-10-05 08:58:05 +02:00
benbierens 8fb63213bc Trying deploy replication 2023-10-05 08:55:13 +02:00
benbierens ffe762f860 makes output deployment json configurable. 2023-10-05 08:46:59 +02:00
benbierens 9cb8c57615 Adds test report for September 2023-10-05 08:31:44 +02:00
benbierens 1ca943b189 Fixes partial rename in k8sController. 2023-10-04 10:37:18 +02:00
benbierens 5db8be3252 Merge branch 'feature/log-mon'
# Conflicts:
#	Tests/CodexContinuousTests/ContinuousTestRunner.cs
2023-10-04 09:48:21 +02:00
benbierens ccc4732752 Cleanup 2023-10-04 09:36:59 +02:00
benbierens 562f886e30 Bumps k8s operation timeout for continuous test runner. 2023-10-04 09:26:11 +02:00
benbierens 3f2c789dd5 Fixes issue where boolean arguments can't be set to false. 2023-10-04 09:19:45 +02:00
benbierens 917d715497 Fixes startup of log reconstruction 2023-10-03 16:11:14 +02:00
benbierens 696fb10386 debugging the downloader in the cluster 2023-10-03 15:36:44 +02:00
benbierens a69bea47af Debugging downloader in cluster 2023-10-03 15:18:58 +02:00
benbierens adbd481c7c Sets elasticsearch downloader for in-cluster running. 2023-10-03 14:32:31 +02:00
benbierens 1bca2bb928 Updates parser for new counter format. 2023-10-03 13:34:39 +02:00
benbierens 6fe9d38eb3 Adds filter option for selecting tests. Adds cleanup option for deleting deployment namespace. 2023-10-03 09:53:02 +02:00
benbierens 6a96bd7639 Implements elastic search log downloader. Requires log-counter codex update! 2023-10-02 14:42:36 +02:00
benbierens c3ec64f25e very very WIP for how to download complete log from elastic search 2023-10-02 12:01:49 +02:00
benbierens 3dcbb78204 Prevents multiple continuous tests from running interleaved. 2023-10-02 11:18:27 +02:00
benbierens 73c49b42c6 Fixes incorrect log message when test run gets cancelled. 2023-10-02 09:24:01 +02:00
benbierens da855b8d0d Proper log monitoring shutdown 2023-10-01 10:52:05 +02:00
benbierens b23c66c86e Working timerange limited log download 2023-10-01 09:57:32 +02:00
benbierens cec6d787de Adds methods for streaming container log 2023-10-01 08:56:21 +02:00
Ben Bierens b96751cd2c Merge pull request #68 from codex-storage/feature/deploy-local-codex
Feature/deploy local codex
2023-09-29 10:45:15 +02:00
benbierens ce995b3df7 Adds log prefixer to local builder 2023-09-29 10:43:21 +02:00
benbierens ac7fa4905d Adds dockertag description 2023-09-29 10:40:48 +02:00
benbierens dcdcd110a1 Updates READMEs 2023-09-29 10:19:59 +02:00
benbierens 8d2ce8ae7a Adds Readme to codex net deployer 2023-09-28 15:29:29 +02:00
benbierens 3e1b361b7b Fixes cleanup of online codexnodes in Codex test fixture 2023-09-28 15:04:21 +02:00
benbierens 993f2ff813 Fixes issue where autobootstrap node doesn't clear old bootstrap node handles. 2023-09-28 14:23:48 +02:00
benbierens 260294fb35 Fixes marketplace interference check tests. 2023-09-28 14:00:13 +02:00
benbierens b0f72cf453 Automated local docker build is working. 2023-09-28 12:22:35 +02:00
benbierens 0f12bcd086 Sets up local builder in codex plugin. 2023-09-28 11:31:09 +02:00
benbierens 453a1a35a2 Adds status log to continuous tests 2023-09-28 09:39:15 +02:00
benbierens dabf836838 Makes default run id static. 2023-09-28 08:53:01 +02:00
benbierens 8caa7ab4fa Adds option to limit continuous test run to set duration. 2023-09-27 11:33:54 +02:00
benbierens 6a2bd11dd5 Adds codex environment variables to startup log of continuous tests. 2023-09-27 09:09:42 +02:00
benbierens d830ccb48e Sets timestamp as default runID when environment variable is not set. 2023-09-27 08:49:36 +02:00
benbierens 42b0ab67a0 Logging bytes per millisecond for twoclient upload and download 2023-09-26 15:17:35 +02:00
benbierens 0a2bca9f52 Restores pod labels in deployer. Adds metrics assert for twoclient continuous test. 2023-09-26 14:32:28 +02:00
Slava 52345adb49 Update Continuous-Tests documentation (#67)
Some minor changes to reflect updated framework architecture
2023-09-25 20:04:06 +03:00
benbierens 9dbd33a906 Fixes serialization issue for running-pod type. 2023-09-25 09:57:27 +02:00
benbierens 3a24a77803 Adds option for partial matches of kube node names. 2023-09-25 09:19:29 +02:00
benbierens 10697f1047 Updates container location support 2023-09-25 08:47:19 +02:00
benbierens e78659690b Pod labels hook for containers deployed with CodexNetDeployer. 2023-09-22 11:10:22 +02:00
benbierens b8b59508aa Nicens up the timeouts for the net deployer. 2023-09-22 10:02:16 +02:00
benbierens ea09605827 Reverts combining http and ws ports. Fixes runner location utils. 2023-09-22 09:52:30 +02:00
benbierens e0a734591f Sets geth http and ws port to same number. 2023-09-22 09:17:56 +02:00
benbierens 100a9f15b8 Downloads container log when codex contract deployment fails. 2023-09-22 08:42:18 +02:00
benbierens d2854bb905 Cleanup 2023-09-22 07:43:46 +02:00
benbierens 192079f9c8 Adds name to geth container 2023-09-21 14:48:41 +02:00
benbierens e1a529175a Merge branch 'plugin-refactor' 2023-09-21 14:42:57 +02:00
benbierens 385fab498d updates ci command 2023-09-21 14:42:45 +02:00
benbierens 418daf1e3f Updates the docs 2023-09-21 14:39:41 +02:00
benbierens 01b2ff2181 Merge branch 'master' into plugin-refactor
# Conflicts:
#	DistTestCore/CodexStarter.cs
#	DistTestCore/GethStarter.cs
#	ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs
#	ProjectPlugins/CodexPlugin/CodexSetup.cs
#	ProjectPlugins/CodexPlugin/CodexStartupConfig.cs
2023-09-21 11:07:27 +02:00
Ben Bierens 002d3a6ade Merge pull request #58 from codex-storage/feature/marketplace-validator-sim-failures
[feature] Enable marketplace simulation of proof failures
2023-09-21 11:00:27 +02:00
Ben Bierens c569a795ce Merge pull request #59 from codex-storage/fix/wait-until
Fix re-evaluation of condition in `Time.WaitUntil`
2023-09-21 10:59:01 +02:00
Ben Bierens 7c45f213d9 Merge pull request #60 from codex-storage/feature/wait-for-contract-finished
Add `WaitForStorageContractFinished` for marketplace tests
2023-09-21 10:58:41 +02:00
benbierens e4716b5471 Adds IHasContainer to geth objects. 2023-09-21 10:56:48 +02:00
Ben Bierens 3e24bcaf50 Merge pull request #64 from codex-storage/decrease-codex-pods-resources-request
Decrease Codex Pods resources request
2023-09-21 10:53:49 +02:00
benbierens 2f81927bef Merge branch 'master' into plugin-refactor 2023-09-21 10:49:39 +02:00
benbierens dbf0ed714c Restores continuous test runner. 2023-09-21 10:33:09 +02:00
benbierens b5e0c9bfe0 Console output alignment is important. 2023-09-21 08:49:09 +02:00
Slava fb1ee57373 Cosmetic changes (#66) 2023-09-20 20:08:11 +03:00
benbierens 5b2557b3f4 Set up loading of plugins. 2023-09-20 13:56:01 +02:00
benbierens 8cde69a483 Restores continuous test runner 2023-09-20 13:33:58 +02:00
benbierens 48da92c737 Removes codex net downloader 2023-09-20 12:56:02 +02:00
benbierens 3b6fdaa3bc Restores CodexNetDeployer 2023-09-20 12:55:09 +02:00
benbierens 75369d68f7 Restores CodexNetDeployer 2023-09-20 12:02:32 +02:00
benbierens bf4c8d8026 Restores project references. 2023-09-20 10:59:52 +02:00
benbierens 09670e00e9 Moves projects into folders 2023-09-20 10:51:47 +02:00
benbierens df8ab6bcf5 Serialization gate requirement passed. 2023-09-20 10:13:29 +02:00
benbierens 5fa4e0ff9f Sets serialization gate between each deploy and wrap to ensure application lifecycle invariance. 2023-09-20 09:16:57 +02:00
benbierens cedaf84740 Wires up marketplace access 2023-09-20 08:45:55 +02:00
Slava 2e13954152 Pass a command during automated tests run (#65) 2023-09-19 17:53:15 +03:00
ThatBen 6cbf363bb1 restores balance asserts. 2023-09-19 16:22:07 +02:00
benbierens 4bc225f1d9 Cleanup 2023-09-19 13:58:45 +02:00
benbierens 6cf86af3b5 Successful testtoken mint and balance 2023-09-19 13:39:24 +02:00
benbierens 58b1c1e03c Can send eth 2023-09-19 11:51:59 +02:00
benbierens a20fc6864b working out marketplace details 2023-09-19 10:28:02 +02:00
Slava 50a1281a92 Decrease Codex Pods resources request 2023-09-18 17:24:11 +03:00
Slava c2a84c7f0e Add basic documentation how to run Continuous Tests (#63) 2023-09-18 17:06:35 +03:00
ThatBen 825200b386 Allows container resources to be conditional in container recipes. 2023-09-18 15:45:21 +02:00
ThatBen 3d4370b154 deploys codex contract 2023-09-15 16:27:08 +02:00
ThatBen 4cc93eba73 Can start Geth 2023-09-15 15:52:02 +02:00
ThatBen f2a8c123a5 Setting up geth plugin 2023-09-15 12:36:35 +02:00
ThatBen f7c69d6f24 Nicer deployment names. 2023-09-15 12:25:10 +02:00
ThatBen fb7488769d Clean up core accessibility 2023-09-14 15:40:15 +02:00
ThatBen ae7ab3d84b Container simplify for metrics plugin 2023-09-14 15:30:09 +02:00
ThatBen 7ef3f615e1 Moves log downloading to core. 2023-09-14 15:26:46 +02:00
Eric f2ad9e1198 Add WaitForStorageContractFinished for marketplace tests 2023-09-14 15:24:12 +10:00
Eric 0d9aa230da Fix re-evaluation of condition in Time.WaitUntil
For example, seller.Marketplace.GetBalance() is captured once on call, and then is compared to a continually updated sellerWithFailures.Marketplace.GetBalance.

Update RetryAssert such that `var c = constraint.Resolve();` is executed inside of the `Time.WaitUntil` instead of outside, so it is continually reevaluated.
2023-09-14 15:22:46 +10:00
Eric d412665f97 revert change to codex image 2023-09-14 15:19:06 +10:00
Eric 5dbaaaee0f replace tab chars with spaces 2023-09-14 15:17:30 +10:00
Eric fd012196f8 cleanup 2023-09-14 15:11:04 +10:00
Eric 52b907e52e Add LogLevelWithTopics, Get simulate proof failures test running 2023-09-14 15:04:37 +10:00
Eric f765933efa update WithLogLevel to accept params string[] 2023-09-14 15:03:09 +10:00
Eric 5f9371e95e Add validator and simulation of proof failures 2023-09-14 15:02:53 +10:00
benbierens 0b67dc56bb wiring up metadata 2023-09-13 16:06:05 +02:00
benbierens a2e07fbd2e Automatic downloading of container logs on test failure 2023-09-13 15:10:19 +02:00
benbierens ca5981e852 Cleanup codex log level and namespace override 2023-09-13 14:37:53 +02:00
benbierens ec5aebb47b Restores full-connectivity tests. 2023-09-13 14:24:43 +02:00
benbierens 84dd514517 Restores connectivity test helpers 2023-09-13 12:24:46 +02:00
benbierens d1895bab02 Implements bringOffline for codex nodes 2023-09-13 12:09:44 +02:00
benbierens 53bb9968ff Metrics test passed 2023-09-13 11:59:21 +02:00
benbierens 1ca3ddc67e Implements metrics plugin 2023-09-13 11:25:08 +02:00
benbierens a05a82c030 cleanup of tools creation, adds automatic log prefixing. 2023-09-13 10:23:05 +02:00
benbierens 3c724f6206 initialize plugins with constructor 2023-09-13 10:03:11 +02:00
benbierens d900416d7c setting up for metrics 2023-09-13 09:12:18 +02:00
benbierens 7e7414d491 Removes core-interface associate methods. 2023-09-13 08:55:04 +02:00
benbierens dd6b99c670 Brings back all the tests. 2023-09-12 15:43:30 +02:00
benbierens 140dd37c6e fixes issue with unassociated coreinterfaces. 2023-09-12 15:18:36 +02:00
benbierens 8849c4dfa7 Extracts plugin mapping to core assembly. 2023-09-12 14:50:18 +02:00
benbierens dc1bed6861 Extracts core from disttest core. 2023-09-12 13:34:58 +02:00
benbierens 32ad778a91 twoclient test passed. 2023-09-12 11:43:46 +02:00
benbierens 4cbd9ac828 workflow creator lifecycle 2023-09-12 11:37:20 +02:00
benbierens c995732f35 Fun with assembly loading 2023-09-12 11:25:04 +02:00
benbierens 0f86642524 simple test lined up 2023-09-12 10:31:55 +02:00
ThatBen 48dda1735c Good progress 2023-09-11 16:57:57 +02:00
Slava ef2c18c599 Fix Dist-Tests workflow input TESTID (#57) 2023-09-11 15:36:36 +03:00
benbierens 83d184177a Moving all codex details to CodexPlugin 2023-09-11 11:59:33 +02:00
benbierens 6915e90861 Extracts file manager utils to separate assembly 2023-09-11 10:43:27 +02:00
benbierens 07396644a4 Merge branch 'feature/automatic-volumes' 2023-09-11 10:00:28 +02:00
benbierens 0bcca1e845 unused usings 2023-09-11 09:58:23 +02:00
benbierens 1b3b45a6a4 Bumps codex resources 2023-09-08 18:13:54 +02:00
benbierens a5749824fb Update to full connectivity helper 2023-09-08 15:43:10 +02:00
benbierens d074f64c42 a 2023-09-08 14:03:47 +02:00
benbierens 75b16bd2ca Include request resource sets. 2023-09-08 13:47:49 +02:00
benbierens 9d2dbbbcab Fixes missing usings 2023-09-08 10:21:40 +02:00
benbierens 13dcaff5d1 Reduces resources for codex instances 2023-09-08 10:16:12 +02:00
benbierens 195527b650 Wires up resource limits 2023-09-08 10:14:52 +02:00
benbierens df6da29a69 Moves ByteSize to Utils assembly. 2023-09-08 09:39:56 +02:00
benbierens 075d9e6ae2 switched to ReadWriteOnce access mode 2023-09-07 10:55:35 +02:00
benbierens 999ed6cfc8 Creates persistent-volume-claims for codex nodes 2023-09-07 10:37:52 +02:00
benbierens aa3e500a58 Adds volume support to container recipe factory 2023-09-07 08:19:19 +02:00
Ben Bierens a03d9cd92f Merge pull request #53 from codex-storage/feature/make-grafana-dashboard-more-portable
Make Grafana dashboard more portable
2023-09-07 08:11:07 +02:00
Slava fdc98665e2 Fix Dist-Tests workflow inputs (#54) 2023-09-06 19:29:37 +03:00
Slava d409f6c01e Make Grafana dashboard more portable 2023-09-06 15:25:43 +03:00
benbierens 6115366656 August test report 2023-09-05 10:17:12 +02:00
Slava 983e1b84cc Allow access from Prometheus namespace (#51) (#52) 2023-09-04 15:58:43 +03:00
benbierens 937c3db160 Setup values for two-client test run. 2023-09-04 09:19:04 +02:00
benbierens a528c0111d Merge branch 'feature/continuous-peer-test' 2023-09-04 09:15:30 +02:00
benbierens c673aff0fe Merge branch 'master' into feature/continuous-peer-test 2023-09-04 09:13:15 +02:00
Slavaandbenbierens a4b6b561d7 Feature/add annotations for prometheus (#49)
* Add Prometheus annotations for Codex Pods (#48)

* Remove unnecessary condition (#48)

* Update Annotations to be handled via ContainerRecipe (#48)

* Wires up metrics port to codex pod annotation

* Cleans up handling of pod labels

* Fix annotations names (#48)

---------

Co-authored-by: benbierens <thatbenbierens@gmail.com>
2023-09-04 10:08:34 +03:00
benbierens 23f5318dfc More uploads 2023-09-01 14:40:48 +02:00
benbierens 3df4a97bba debugging local 2023-09-01 10:39:41 +02:00
benbierens a26d38b749 Moves container logs into log folder. 2023-09-01 09:28:04 +02:00
benbierens a29d646027 Removes future-numbers. 2023-09-01 08:45:23 +02:00
benbierens cf9d2ffe19 Fix for marketplace request deserialization 2023-08-31 11:19:53 +02:00
benbierens 647022a27e Updates overview log for all-nodes tests. 2023-08-31 09:50:34 +02:00
benbierens 7d042d0a1c Log replacements for node ids in continuous test fixture logs. 2023-08-30 10:57:20 +02:00
benbierens 8d7a7b4d1b Better exception logging for tests with multiple time-moments. 2023-08-30 10:13:48 +02:00
benbierens a1ec187919 Configured runner to stop after 10 failures 2023-08-30 09:23:13 +02:00
benbierens a5773bf710 More logging. Set container names for codex deployment. 2023-08-30 08:53:09 +02:00
benbierens 0a8928b5aa Adds logging and connectivity check. 2023-08-30 08:33:38 +02:00
benbierens 8f61c46ff0 Adds simple test to ensure routing table is correctly filled. 2023-08-29 16:08:40 +02:00
Ben Bierens 4cc09c268a Merge pull request #47 from codex-storage/feature/handle-non-200-response-code
Add better error handling for non-200 codes
2023-08-29 15:59:00 +02:00
benbierens 6bb7b994ed Merge branch 'feature/peer-connection-check-on-deploy' 2023-08-29 14:48:14 +02:00
benbierens facfd10316 Removes log asserts, they are unreliable in the cluster. 2023-08-29 14:48:03 +02:00
benbierens 3aea8f41b8 wires up timing measurement for peer download helper. 2023-08-29 10:09:48 +02:00
benbierens 553a368714 Adds a container-restart check at the end of the deployment. 2023-08-28 11:53:59 +02:00
benbierens 249b13d503 Adds tests to ensure marketplace and metrics don't interfere with peer discovery 2023-08-28 11:16:58 +02:00
benbierens c22ba346af Adds peer info to dashboard 2023-08-25 08:32:08 +02:00
benbierens b276592daf Fixes download stream error in peerDownloadHelper 2023-08-24 15:56:56 +02:00
benbierens 7da90fc53b cleanup 2023-08-24 15:45:21 +02:00
benbierens eec70a9b8c Adds peer connectivity check after deployment 2023-08-24 11:32:32 +02:00
benbierens 7eda26d177 Cleanup of full-connectivity helper. 2023-08-24 10:59:11 +02:00
benbierens 3c94727101 Slows down the two continuous tests 2023-08-24 10:44:06 +02:00
benbierens 561024738a Fixes issue with log downloading in holdmybeer test 2023-08-24 09:33:46 +02:00
benbierens 9a117cca70 rigs continuous testnet for holdmybeer test. 2023-08-23 08:29:16 +02:00
benbierens e340b3cd41 Merge branch 'spike/local-continuous-debug' 2023-08-22 15:56:05 +02:00
benbierens 2803b8b484 Merge branch 'master' into spike/local-continuous-debug
# Conflicts:
#	DistTestCore/Codex/CodexContainerRecipe.cs
2023-08-22 15:53:07 +02:00
benbierens 47aedc2ac8 cleanup 2023-08-22 15:51:39 +02:00
benbierens 7e5121a642 Fixes test for cloud environment 2023-08-22 09:06:44 +02:00
benbierens ce3850b0b0 I accidentally filled up my harddrive with random numbers last night. 2023-08-21 07:58:58 +02:00
benbierens 1682dfc08f Adds holdmybeer test to continuous test runner 2023-08-18 10:31:20 +02:00
Eric 70c56eadec Add better error handling for non-200 codes
Also add better json deserialization error handling
2023-08-18 15:40:40 +10:00
Slava c716fde17b Add documentation how to run tests in remote Kubernetes (#41)
https://github.com/codex-storage/cs-codex-dist-tests/issues/40
2023-08-17 14:43:13 +03:00
benbierens aaca9e1ec5 Bumps to new images. 2023-08-17 11:06:45 +02:00
benbierens c8cb04d859 Allows downloading only log-tails. 2023-08-16 16:13:29 +02:00
Slava 543f2b97dd Add reusable workflow for Docker (#42) (#45) 2023-08-16 14:46:17 +03:00
benbierens 30364abf21 Correct checking of upload and download log statements 2023-08-16 11:39:24 +02:00
benbierens 6486dba289 Asserting correct filesize and block numbers in the codex logs 2023-08-16 10:52:44 +02:00
benbierens 57c46004b1 cleanup 2023-08-15 15:00:47 +02:00
benbierens 3abb1770c2 Fixes crash log downloads 2023-08-15 12:55:30 +02:00
benbierens 08ee09af1e wires crashWatcher into CodexStarter 2023-08-15 11:01:18 +02:00
benbierens 72fa368357 working example of crash log recovery 2023-08-15 10:03:01 +02:00
benbierens 86bae93e98 Wires up block-maintenance number of blocks param 2023-08-14 16:37:31 +02:00
benbierens c0b7d3a747 Hooks up block maintenance interval config param. 2023-08-14 15:51:03 +02:00
benbierens 6e5e30afd4 rigged up a pod crash detector maybe 2023-08-14 15:10:36 +02:00
benbierens 9f0600d6c1 Automatic quota line. Holding test. 2023-08-14 10:35:42 +02:00
benbierens f2346d5d09 Merge branch 'feature/grafana-dashboard' into spike/local-continuous-debug
# Conflicts:
#	ContinuousTests/ContinuousTestRunner.cs
2023-08-14 08:19:09 +02:00
benbierens b4302ab6f7 Decent dashboard setup 2023-08-13 11:19:35 +02:00
benbierens 75cdb94e27 use cluster-external addresses for dashboard URLs 2023-08-13 10:10:47 +02:00
benbierens bce8a48cad logs grafana image id 2023-08-13 10:07:47 +02:00
benbierens de66236383 Cleans up grafana starter. 2023-08-13 10:01:06 +02:00
benbierens f1131abf79 Wires dashboard into deployer 2023-08-13 09:07:23 +02:00
benbierens fc3f424208 Splits up grafana starter 2023-08-13 08:40:32 +02:00
benbierens 75228c815d fixes dashboard permissions 2023-08-13 08:27:30 +02:00
benbierens 485af03367 very wip: automatic grafana dashboard 2023-08-11 12:38:26 +02:00
benbierens 2acb669c31 automatically adds prometheus as datasource 2023-08-11 11:06:40 +02:00
benbierens 79908b8a54 disables grafana auth 2023-08-11 10:16:19 +02:00
benbierens 9037ddab6e initial setup of a grafana container 2023-08-11 09:37:30 +02:00
benbierens 5d92b99926 Determine runner location by pinging pod IP. 2023-08-11 09:09:03 +02:00
benbierens fff02656b7 automatically determine runner location 2023-08-11 08:39:51 +02:00
benbierens 5f1b9bf8d4 lines up test type names 2023-08-10 14:31:08 +02:00
benbierens 7280fcc3cf Merge branch 'master' into spike/local-continuous-debug
# Conflicts:
#	ContinuousTests/ContinuousTestRunner.cs
2023-08-10 14:26:36 +02:00
benbierens ea767e23ae Merge branch 'feature/labeling-pods' 2023-08-10 14:25:43 +02:00
benbierens 3de8e29198 turns on metrics by default for deployment 2023-08-10 14:25:32 +02:00
benbierens c7e5ddb0cb additional application ids, fixes deployment namespace issue 2023-08-10 13:58:50 +02:00
benbierens 683cc882be dash is disallowed 2023-08-10 11:59:57 +02:00
benbierens 9c1b469f0d same for fixture name 2023-08-10 11:56:29 +02:00
benbierens 4995665fe5 deals with adhoc test context categories 2023-08-10 11:53:49 +02:00
benbierens 5a861df968 local tests passed 2023-08-10 11:47:34 +02:00
benbierens 01ec6f7d8b sets up additional pod labels 2023-08-10 11:25:22 +02:00
benbierens 17673e8fa5 cleanup some workflow/lifecycle madness 2023-08-10 10:58:18 +02:00
benbierens 9a63a82a49 Merge branch 'master' into feature/labeling-pods
# Conflicts:
#	DistTestCore/Marketplace/CodexContractsContainerRecipe.cs
2023-08-10 09:50:15 +02:00
benbierens 061694f83a Adds option to set block TTL from codex-setup 2023-08-10 09:44:23 +02:00
benbierens ed5b2d5b80 bumps to latest contracts image 2023-08-10 08:39:32 +02:00
benbierens 68d19353aa moves to 80MB and long timeouts 2023-08-08 14:42:59 +02:00
benbierens eaf5db5e91 Run test against increasing file sizes 2023-08-08 10:45:16 +02:00
benbierens 598dc766fa use extra log codex image 2023-08-08 09:44:38 +02:00
benbierens ab42fa8004 removes just-wait tests 2023-08-07 18:31:13 +02:00
benbierens ee1c11542e Merge branch 'master' into spike/local-continuous-debug 2023-08-07 18:28:18 +02:00
benbierens 0259c30bb5 Wires up pod labels 2023-08-07 15:51:44 +02:00
benbierens 1e7470d476 Lays out the plan 2023-08-07 15:07:20 +02:00
benbierens 9840c50e9f Rolls back node version in contracts container. 2023-08-07 14:33:04 +02:00
benbierens 2bafab8b22 Adds check against failure to deploy codex contracts 2023-08-07 11:21:26 +02:00
benbierens 5ad2aa4c6a Merge branch 'master' into spike/local-continuous-debug 2023-08-07 10:58:27 +02:00
benbierens 85b8b6122f Merge branch 'feature/fix-pod-multiples' 2023-08-07 10:51:27 +02:00
benbierens 8a6c5e95ee Makes sure codex nodes are always started in their own pods. 2023-08-07 10:44:48 +02:00
benbierens 771a952f02 Download container logs when EnsureOnline fails 2023-08-04 08:19:03 +02:00
benbierens 9fab7757b0 Pins codex image to fixed hash 2023-08-04 07:55:46 +02:00
benbierens 3884a6a7f7 a 2023-08-02 15:11:27 +02:00
benbierens 4234719e53 Adds continuous test net report for July 2023 2023-08-02 14:44:15 +02:00
benbierens 032506535f Merge branch 'feature/codex-version-info' 2023-08-02 14:43:39 +02:00
benbierens 5675c1d77a use codex version info in logging 2023-07-31 11:51:29 +02:00
benbierens 29137263fb Merge branch 'feature/docker-image-testruns' 2023-07-31 09:56:57 +02:00
benbierens 8d229f1720 cleans up waiting for storage contract state helper methods. 2023-07-31 09:56:37 +02:00
benbierens 9a7946c863 Basic tests pass 2023-07-31 09:16:52 +02:00
benbierens 10ffd29257 Cleanup 2023-07-21 09:44:46 +02:00
benbierens 933932b091 Fixes fetching of docker image for status log 2023-07-21 09:42:38 +02:00
benbierens dbbd05ea97 Merge branch 'master' into feature/docker-image-testruns
# Conflicts:
#	DistTestCore/Codex/CodexContainerRecipe.cs
2023-07-21 09:34:37 +02:00
benbierens dc4749aca6 Merge branch 'feature/log-continuous-downloader' 2023-07-21 09:31:28 +02:00
benbierens ce7ad1ba70 restores downloader default config 2023-07-21 09:29:14 +02:00
benbierens 71f45ca5ea Merge branch 'master' into feature/log-continuous-downloader 2023-07-21 09:25:41 +02:00
benbierens 754e74f6d8 Merge branch 'feature/logs-update-for-kibana' 2023-07-21 09:23:31 +02:00
benbierens 344c004299 Adds test duration to status log 2023-07-21 09:20:28 +02:00
Slava 07b85bda5e Add new variables to the job/pod (#35) 2023-07-21 10:03:10 +03:00
benbierens 8b3589a489 Adds one post-test log download 2023-07-21 09:00:07 +02:00
benbierens c762e30331 autoconfirms deploy 2023-07-21 07:58:10 +02:00
benbierens 0807b16aa4 Merge branch 'master' into feature/log-continuous-downloader 2023-07-21 07:45:05 +02:00
benbierens 0f9e6b6b58 Bumps to latest codex image 2023-07-21 07:44:13 +02:00
benbierens 9de7e1d320 Moves log downloader into continous test runner 2023-07-20 13:37:11 +02:00
benbierens 2dd5775296 Very simple log appender 2023-07-20 10:52:19 +02:00
benbierens f2517b2ade Bump to latest codex master image 2023-07-20 09:02:17 +02:00
benbierens c744fd12c3 Rigs sh scripts for debugging of continuous tests. 2023-07-19 08:49:43 +02:00
benbierens bbf66bb0f0 Cleanup of full connectivity helpers 2023-07-18 16:07:52 +02:00
benbierens bfefba9633 Fixes large discovery tests 2023-07-18 14:26:21 +02:00
benbierens d6d32bf8e6 Adds category to status log. Updates timestamp format 2023-07-18 13:18:26 +02:00
benbierens 07776dc6fb Adds log tag to each log file. 2023-07-18 11:08:05 +02:00
benbierens 87d346ee20 Merge branch 'master' into feature/logs-update-for-kibana 2023-07-18 10:39:47 +02:00
benbierens fa1057ddfd Sets correct output filename for status log. 2023-07-18 10:39:27 +02:00
benbierens 009ce1e9f3 Wires up the status log. 2023-07-18 09:47:44 +02:00
benbierens 5f89db4012 Bumps to latest codex image 2023-07-18 09:18:43 +02:00
benbierens f50b8dfc83 Sets up StatusLog 2023-07-18 09:02:41 +02:00
benbierens 6892080128 Disables future-counting API call 2023-07-18 08:03:16 +02:00
benbierens fe25166478 Better http timeout and retrying values 2023-07-17 15:21:10 +02:00
benbierens 1154544e99 prints images before confirmation 2023-07-17 13:57:56 +02:00
benbierens ac33d3c7be Moves some of the fully-connected download tests to the long-tests 2023-07-17 13:47:41 +02:00
benbierens 78434048b7 Example docker-compose 2023-07-17 11:29:27 +02:00
benbierens fa7ba9149e Sets docker images in constructor of recipes 2023-07-17 11:12:14 +02:00
benbierens 6147c14c9b setting up 2023-07-17 09:26:54 +02:00
benbierens 3257e42a02 Merge branch 'regress/peer-discovery' 2023-07-17 08:56:29 +02:00
benbierens 4815194db0 Removes variable image tests. 2023-07-17 08:56:18 +02:00
benbierens adeba21665 Disables debug output by default 2023-07-17 08:54:07 +02:00
benbierens b295314bde fixes blockTTL envvar 2023-07-14 10:45:26 +02:00
benbierens f23926636c Setting up way to run different codex images 2023-07-14 10:18:37 +02:00
benbierens 2de7fc7f20 Bumps to latest official codex image. 2023-07-13 10:36:43 +02:00
benbierens a3a5adaa19 Merge branch 'feature/codex-net-deployer'
# Conflicts:
#	DistTestCore/Marketplace/MarketplaceAccess.cs
2023-07-13 10:34:34 +02:00
benbierens 4a17129204 Merge branch 'feature/continuous-test-future-counting'
# Conflicts:
#	DistTestCore/Marketplace/MarketplaceAccess.cs
2023-07-13 10:33:31 +02:00
benbierens 6109921849 Enables auto-NAT option 2023-07-13 10:05:40 +02:00
benbierens 1f8fc52cb1 Adds three-client test to check discovery relaying 2023-07-13 09:20:24 +02:00
benbierens 1ef65f00f9 Merge branch 'master' into feature/codex-net-deployer
# Conflicts:
#	DistTestCore/Marketplace/MarketplaceAccess.cs
2023-07-12 14:55:42 +02:00
benbierens 66161e8d41 Merge branch 'feature/update-env-vars' 2023-07-12 14:53:44 +02:00
benbierens 11502717c3 Fixes hexint interface to marketplace API 2023-07-12 14:53:27 +02:00
benbierens 512aeb79a0 Merge branch 'master' into feature/codex-net-deployer 2023-07-12 14:40:14 +02:00
Slava 1f2635ea7d Update docker docs (#29)
* Update Docker docs

* Update example manifest
2023-07-11 21:54:58 +03:00
Slava 671ee4ea62 Update Docker workflow (#28) 2023-07-11 19:01:42 +03:00
benbierens 42acb862b6 Removes docker images from config because they aren't configurable this way. 2023-07-11 14:56:40 +02:00
benbierens 17935f4c9e Adds metrics container to deployment json 2023-07-11 12:21:48 +02:00
benbierens 1be7b1c144 Merge branch 'feature/continuous-test-future-counting' into feature/codex-net-deployer
# Conflicts:
#	CodexNetDeployer/Configuration.cs
2023-07-11 11:12:29 +02:00
benbierens 9778ef51a7 Loops in metrics deployment 2023-07-11 10:59:41 +02:00
benbierens 76dfd7a86c Support for boolean uniform-args 2023-07-11 10:10:47 +02:00
benbierens cccf9e426c Adds deploy confirm interaction 2023-07-11 10:05:00 +02:00
benbierens 5fbf538eb6 Merge branch 'master' into feature/codex-net-deployer 2023-07-11 09:55:06 +02:00
benbierens 7d856c276b Only display new threshold breaches 2023-07-11 08:19:14 +02:00
benbierens d982f39870 Adds threshold checking test 2023-07-07 08:52:53 +02:00
benbierens 3fff152a3d Merge branch 'master' into feature/continuous-test-future-counting 2023-07-07 08:28:23 +02:00
benbierens 52c9ec9211 Restores two-client test 2023-07-04 16:04:18 +02:00
benbierens f9a221bad5 Merge branch 'feature/codex-net-deployer' 2023-07-04 15:47:37 +02:00
benbierens 05e60a6b74 Future-counting test 2023-07-03 08:39:11 +02:00
benbierens 9970f225cc Makes codexAccessFactory follow configuration-runner location 2023-06-30 10:08:51 +02:00
benbierens b0a4ed93d8 script for easy running of continuous tests in cluster 2023-06-30 09:58:59 +02:00
benbierens 4dab688b6f Adds delay in deployer to ensure accounts are unlocked on time. 2023-06-30 09:54:13 +02:00
benbierens 6e03301b7a Missing include 2023-06-30 09:43:20 +02:00
benbierens 0987a71f1e deploy script for easy use in the cluster 2023-06-30 09:34:54 +02:00
benbierens 0caa53bf8c support for bool in argsuniform 2023-06-30 09:14:54 +02:00
benbierens 0d4960f3ff cleanup help cli args. Adds option to stop tests on first failure and download cluster logs. 2023-06-30 09:09:59 +02:00
benbierens e7e464b4fa Adds container alias to log. Useful for transient nodes 2023-06-30 08:39:18 +02:00
benbierens 66e6cdc027 Flattens CodexNode into CodexAccess 2023-06-29 16:07:49 +02:00
benbierens d985e3191a Adds blockTTL to configuration of codex containers 2023-06-29 16:03:45 +02:00
benbierens caf5de678a Prevents starting a test-run when one is already started. 2023-06-29 13:39:05 +02:00
benbierens 6a5cde2b91 Separate config option for storage space to sell. 2023-06-29 11:05:58 +02:00
benbierens 4b6c8a9191 Sets up codex net downloader for log inspection 2023-06-29 10:45:29 +02:00
benbierens dc38797ee7 Correct cancelling behavior 2023-06-29 10:23:04 +02:00
benbierens b42570be14 Merge branch 'feature/codex-net-deployer' into feature/continuous-testing
# Conflicts:
#	DistTestCore/Marketplace/MarketplaceAccess.cs
2023-06-29 09:09:20 +02:00
benbierens eaa218f8e0 Setting up cancelling 2023-06-28 16:19:37 +02:00
benbierens efc638a0f9 Debugging logging output 2023-06-28 15:11:20 +02:00
benbierens 11269d1c21 Sets up transient node test 2023-06-28 12:01:20 +02:00
benbierens 9a3f45e60d Extracts node-running logic from marketplace test 2023-06-28 11:48:05 +02:00
benbierens f7edfd4eee Checking for collisions on custom k8s namespaces and account indices 2023-06-28 11:01:10 +02:00
benbierens fe1ee45775 Debugging and cleanin up marketplace test 2023-06-28 10:41:04 +02:00
benbierens 2f5bf4b76e Adds configuration for selling of storage space 2023-06-28 08:48:46 +02:00
benbierens 91a8f6a869 Merge branch 'master' into feature/continuous-testing 2023-06-27 15:28:44 +02:00
benbierens ccd5119b06 Merge branch 'master' into feature/codex-net-deployer 2023-06-27 15:28:35 +02:00
benbierens 63068aae1d Debugging marketplace test 2023-06-27 15:28:00 +02:00
benbierens 86d954e103 Debugging continuous test runner 2023-06-27 10:16:59 +02:00
benbierens 731ab90ce1 Easier local deployments 2023-06-27 08:29:39 +02:00
benbierens 063b69ef99 Stops marketplace test on first failure 2023-06-26 16:00:16 +02:00
benbierens 330d46bb43 Excludes codex-deployment json 2023-06-26 15:48:22 +02:00
benbierens 4cda3ad22f Merge branch 'feature/continuous-testing' 2023-06-26 15:47:11 +02:00
benbierens f437c5df9d Sets up long-running market test. 2023-06-26 15:46:04 +02:00
benbierens c91d9cc7dc nicer arg parsing 2023-06-26 14:44:21 +02:00
benbierens 868553f27d uniform config parsing 2023-06-26 13:58:41 +02:00
benbierens 125c3715a8 Merge branch 'master' into feature/continuous-testing 2023-06-26 11:12:53 +02:00
benbierens 25a795f0fd Applies isolated test context per moment-run 2023-06-26 11:10:56 +02:00
benbierens 55998419ee Don't start all tests at the same time. 2023-06-26 09:14:02 +02:00
benbierens 9028711475 Adds test name in overview log 2023-06-25 11:24:32 +02:00
benbierens 0ebfed6014 Merge branch 'feature/continuous-testing' 2023-06-25 11:07:09 +02:00
benbierens 83c2088185 Adds overview log 2023-06-25 11:06:47 +02:00
benbierens 4e2e4830a1 Merge branch 'feature/continuous-testing' 2023-06-25 10:56:45 +02:00
benbierens ae3b4df92c Restores performance tests 2023-06-25 10:55:55 +02:00
benbierens 40b4d8aba3 Running two client test 2023-06-25 10:50:01 +02:00
benbierens 9a9c740e1c Sets up new test execution loop 2023-06-25 09:53:10 +02:00
benbierens c08507e1b8 wip: figuring out log handling for continous tests 2023-06-23 11:38:30 +02:00
benbierens 9d3874c88f Runner correctly ingests codex-deployment.json 2023-06-23 10:35:23 +02:00
benbierens 091eae36cc Wires in codex-deployment json. 2023-06-23 10:14:16 +02:00
benbierens 8bceba0182 Merge branch 'feature/codex-net-deployer' into feature/continuous-testing 2023-06-23 09:18:10 +02:00
benbierens 2e09c9135e Adds metadata about codex deployment 2023-06-23 09:08:18 +02:00
benbierens d9665f3e79 Successful remote test deploy. Adds option to remove random guid from k8s namespace. 2023-06-23 08:57:31 +02:00
benbierens a21078f4a9 Merge branch 'feature/codex-net-deployer' into feature/continuous-testing 2023-06-23 08:53:07 +02:00
benbierens acfd1f1f10 Removes local debug help 2023-06-23 08:45:16 +02:00
benbierens aa9b667940 Writes codex-deployment.json which can be used by the test running. 2023-06-23 08:44:27 +02:00
benbierens 4f0a278df1 Flips internal/external default. 2023-06-23 08:18:48 +02:00
benbierens f242ad5a16 Adds option to declare validators. 2023-06-22 15:58:18 +02:00
benbierens cd69565c2e Successful local deploy 2023-06-22 14:37:37 +02:00
benbierens 7fdca9ffdb all wired up 2023-06-22 10:33:21 +02:00
benbierens ee5a466940 Wiring up the starter class 2023-06-22 10:17:12 +02:00
benbierens e5a7f04c4e Arg and env-var parsing 2023-06-22 09:51:25 +02:00
benbierens 40046d9bcb New console app 2023-06-22 08:29:33 +02:00
benbierens 8d2d2d6197 Performance tests 2023-06-21 11:45:45 +02:00
benbierens ae5309c2b6 Deletes log files for tests and runs that passed. 2023-06-21 11:01:48 +02:00
benbierens 0d9918b401 Sleep per test and per all-tests 2023-06-21 10:34:29 +02:00
benbierens c5c54f5963 Better logging alignment for continous tests 2023-06-21 10:06:54 +02:00
benbierens 978d2d3a84 Instantiate node-handles for each individual test. Allows for time-set selection. 2023-06-21 09:27:59 +02:00
benbierens d23f5aa29d Initial setup for continuous test runner 2023-06-21 08:28:40 +02:00
benbierens 62d646836d Merge branch 'feature/fully-connected-download-tests' 2023-06-20 13:39:07 +02:00
benbierens 9b03fff032 Time formatting in peer download test helper 2023-06-20 13:37:58 +02:00
benbierens 519069f97f Cleanup 2023-06-08 13:23:26 +02:00
benbierens b4f144a7fb Speeds up file-equal assertions hugely 2023-06-07 10:56:25 +02:00
benbierens 677ead4fd3 Faster test file generation. 2023-06-07 09:59:00 +02:00
benbierens 7ae03938c3 Fixes storage quota for large file tests. 2023-06-07 09:32:56 +02:00
benbierens d2d7f3dea5 Logging upload/download times and adds performance threshold assertions to large-file tests. 2023-06-07 08:30:10 +02:00
benbierens 56dc1c632d Variable size download tests 2023-06-06 16:10:30 +02:00
benbierens e33a6776e5 Cleanup 2023-06-06 14:37:02 +02:00
Slava 9fe9f2ed36 Run tests in Kubernetes using GitHub Actions (#23)
* Add GitHub Actions workflow to run tests

* Text formatted
2023-06-04 20:28:17 +03:00
benbierens 10a0ac3311 Splits up download tests from connectivity tests and improves testfile logging 2023-06-04 08:59:51 +02:00
benbierens bc4035e723 Merge branch 'feature/configurability' 2023-06-04 07:56:50 +02:00
benbierens 7c4a2ea12c Fixes the fix of the failure for container-recipes with no exposed ports. 2023-06-02 11:38:52 +02:00
benbierens 346a63abaa Fixes crash for container-recipes that expose no external ports. 2023-06-02 11:30:03 +02:00
benbierens 303eb99f49 Removes application-level managed namespaces. Use GUIDs instead. 2023-06-02 11:10:16 +02:00
benbierens 8ba4b1a290 Removes pre-assigned service ports and reads back service-ports assigned by k8s cluster. 2023-06-02 11:07:36 +02:00
benbierens cda63ba245 Dynamically allocates Locations-enum to available k8s nodes in cluster. 2023-06-02 10:27:57 +02:00
benbierens ad71cff465 Automatically map location enum to available k8s nodes. 2023-06-02 10:04:07 +02:00
benbierens e7d059ceed Makes default configuration overridable from environment variables. 2023-06-02 09:03:46 +02:00
benbierens e8d99b83ff Merge branch 'feature/cluster-internal-and-external-container-addresses' 2023-06-02 08:19:10 +02:00
benbierens 09e550df79 Adds AssertHelper for better less log-spamming retry-assert. 2023-06-01 16:28:34 +02:00
benbierens 9328f04f4a Adds retry for assertions of metrics and account balances. 2023-06-01 16:09:38 +02:00
benbierens 44e237e60e Fixes incorrect call to node interaction from gethCompantionNodeStarter 2023-06-01 15:56:26 +02:00
benbierens 56c716a4ea force update to new contracts image 2023-06-01 15:33:50 +02:00
benbierens 37f7f5293f Adds debug logging for containerlog-searching operations 2023-06-01 15:20:21 +02:00
benbierens 99c9b25487 Adds better check for successful deployment of codex contracts 2023-06-01 15:04:04 +02:00
benbierens b590e24de8 Debugging marketplace deploy in cluster 2023-06-01 14:18:09 +02:00
benbierens b16eb4e88a Fixes issue where fetching of enode fails 2023-06-01 14:03:16 +02:00
benbierens 9eae7cf25e Fixes issue where containers with no exposed ports cannot start. 2023-06-01 12:44:48 +02:00
benbierens 4d654e106c Remaps running container internal address to service endpoint 2023-06-01 12:31:14 +02:00
benbierens 9747846787 Increase retry timeout 2023-06-01 12:27:54 +02:00
benbierens 61d2185bb6 Attempt internal connection using pod IP and exposed port. 2023-06-01 10:14:32 +02:00
benbierens b92c1b970d Sets up support for internal and external container addresses 2023-06-01 09:35:18 +02:00
Ben Bierens 8f313720f0 Merge pull request #17 from codex-storage/allow-access-from-runner-namespace
Allow access from runner namespace
2023-06-01 08:17:04 +02:00
Veaceslav Doina 2f40412d51 Allow access from runner namespace (#12) 2023-05-31 18:37:34 +03:00
Ben Bierens 1d919544af Merge pull request #14 from codex-storage/fix/check-network-policies
Fix/check network policies
2023-05-31 15:25:07 +02:00
benbierens c22125201f Fixing host address formatting for cluster run 2023-05-31 15:02:07 +02:00
benbierens 0ebe59ea2f Fixes issue where invalid log-replace statements cause test crash 2023-05-31 14:49:06 +02:00
benbierens 59692fafa3 Merge branch 'feature/peer-discovery-tests' 2023-05-31 14:12:22 +02:00
benbierens 3f7e623eb6 Merge branch 'master' into feature/peer-discovery-tests 2023-05-31 14:06:38 +02:00
benbierens 3ecdce7b07 Readies k8sCluster class for run against online cluster. 2023-05-31 13:50:52 +02:00
benbierens 3d1d82f582 pushes codex loglevel to configuration 2023-05-31 13:34:12 +02:00
benbierens f2145a747f Clearly distinguishes between peer-connections that can't be made and problems with the node-to-test link. 2023-05-31 13:15:41 +02:00
Slava cfae2b2579 Merge branch 'master' into fix/check-network-policies 2023-05-30 23:03:00 +03:00
Veaceslav Doina fdf5cec841 Update links to codex-storage organization 2023-05-30 22:48:39 +03:00
Veaceslav Doina 2aaf55a53c Use codexstorage/nim-codex Docker images 2023-05-30 22:45:41 +03:00
Veaceslav Doina 9bd896633b Update network policies 2023-05-30 22:41:34 +03:00
Slava 1b490c475e Fix Docker repository link (#6) 2023-05-29 11:46:26 +03:00
Slava 7b13d46352 Merge pull request #5 from codex-storage/add-docker-builds
Add Docker builds
2023-05-29 11:31:19 +03:00
benbierens 816cd1728b wires up testfile size in download helper 2023-05-29 09:27:25 +02:00
benbierens 8c85cd22bb Adds clear logging for peer helper invocations. 2023-05-29 09:25:03 +02:00
benbierens 1016f568b8 Cleanup of peer connection helpers. Adds peer download helpers. 2023-05-29 09:13:38 +02:00
benbierens ab7a334987 Adds assert that nodes have correct addresses in their table of peers. 2023-05-29 08:35:46 +02:00
Veaceslav Doina 2e7bdc94ac Add Docker builds 2023-05-28 23:10:17 +03:00
benbierens 025b03e678 Retain for a while in staged variable nodes peer discovery tests 2023-05-18 10:42:04 +02:00
benbierens 830d74a5ae Faster peer-pair checking 2023-05-12 10:48:12 +02:00
benbierens 90a5aafa1c Better peer discovery result logging. Staged discovery test. 2023-05-12 09:11:05 +02:00
benbierens f7e7849460 Improves retrying in peer test helper 2023-05-11 13:59:53 +02:00
benbierens 38d5b172f4 successful peer discovery tests 2023-05-11 12:44:53 +02:00
benbierens e9679e18c0 logs all checks before failing 2023-05-10 10:47:10 +02:00
benbierens d58cb38c79 Cleanup duplicated retry logic. Adds layered and chain tests 2023-05-10 09:55:36 +02:00
benbierens 2a55328d11 Debugs AutoBootstrapDistTest 2023-05-10 09:09:31 +02:00
benbierens 1d224cf2d3 Sets up working peer-discovery tests 2023-05-10 08:53:57 +02:00
benbierens 9c6f00dbce Disable net-isolation tests 2023-05-05 08:47:20 +02:00
benbierens 3c9cb9b7da Turns off test cases for two-client tests 2023-05-05 08:35:13 +02:00
benbierens 3aba6d5082 Merge branch 'feature/parallel-tests' 2023-05-05 08:33:24 +02:00
benbierens 87f3b9ec00 Turns off debug logging 2023-05-05 08:33:10 +02:00
benbierens e96c8a2a13 Enables checking for engine peers in peer tests 2023-05-05 07:58:48 +02:00
benbierens d0b4e31167 Turns off parallel tests for now. 2023-05-04 15:22:11 +02:00
benbierens 3db2217c32 oops forgot to update calls to log 2023-05-04 15:21:36 +02:00
benbierens eae138f7fd Adds policy and test for network isolation. This will not work on docker-desktop clusters. 2023-05-04 14:55:39 +02:00
benbierens 533bf32577 Better logging in case codex node fails to respond to debug/info request. 2023-05-04 11:34:43 +02:00
benbierens ab07ac0389 Fixes issue where multiple instances of ApplicaitonLifecycle are created. 2023-05-04 09:16:15 +02:00
benbierens 5a4a5795b2 Makes timings not static and ties them to test lifecycle 2023-05-04 08:55:20 +02:00
benbierens 2ed6993b58 wip debugging anomalous test failures 2023-05-04 08:25:48 +02:00
benbierens 532eb3d4f9 Fixes single-instance DistTest class being used to run multiple tests in parallel 2023-05-03 14:55:26 +02:00
benbierens 79a40904e4 setup 2023-05-03 14:18:37 +02:00
benbierens 01c8238311 Applies faster geth image 2023-05-03 10:21:15 +02:00
benbierens c1f1daa6fe Fixed some typos 2023-05-02 07:46:17 +02:00
Eric Mastro ea0a690862 clean up 2023-05-02 15:29:05 +10:00
Eric Mastro 050bb85d27 add arch preprocessor directive
Add preprocessor directive that checks if the current platform architecture is ARM64.

The preprocessor directive checks for ARM64 architecture and changes which docker image to load in the recipes.

# Conflicts:
#	DistTestCore/Codex/CodexContainerRecipe.cs
2023-05-02 15:19:28 +10:00
Eric Mastro c16055d7e4 bump to .net 7 2023-05-02 15:17:43 +10:00
benbierens 085c4431a2 Setup and contributing instructions 2023-05-01 16:26:26 +02:00
benbierens e61cc7c0c4 Merge branch 'feature/marketplace-contracts' 2023-05-01 11:15:18 +02:00
benbierens 5c00205f62 Cleanup and support for automatic bootstrap tests 2023-05-01 11:14:42 +02:00
benbierens 2d5554fadf wip test, but passes. 2023-04-30 11:10:38 +02:00
benbierens db4364d336 Much nicer logging by means of container name-override and long-id log replacements 2023-04-30 10:56:19 +02:00
benbierens 4dd02a96e9 Isolating issue with peer tests 2023-04-30 10:08:32 +02:00
benbierens 1da481daa1 Restores original test numbers 2023-04-28 07:22:17 +02:00
benbierens 96c2ade7ff Allows for the use of UseLongTimeouts in combination with test-case attribute 2023-04-27 15:55:33 +02:00
benbierens d236fe6cea Merge parallel-upload-download tests 2023-04-26 15:32:27 +02:00
benbierens e2586ddd26 Merge branch 'master' into feature/marketplace-contracts 2023-04-26 15:31:26 +02:00
benbierens 2ba5a1491a Updates to latest image 2023-04-26 15:31:01 +02:00
Shaun Orssaud 75b8d39cc1 Merge pull request #2 from Shorssaud/master
Adding Multi Download / Upload tests
2023-04-26 15:01:10 +02:00
Corbo12 ec311f4802 MDF change upload tests file name 2023-04-26 15:00:09 +02:00
Corbo12 aed3edaf6a MDF split tests into different files 2023-04-26 14:59:26 +02:00
benbierens 1c9e59a6ea Bonus: sets up peer-tests 2023-04-26 14:40:54 +02:00
benbierens ef546a435b Supports multiple codex nodes from a single geth companion node. 2023-04-26 11:12:33 +02:00
benbierens ff68b8f0f1 Trying to reduce resource usage of geth 2023-04-25 14:58:19 +02:00
Corbo12 5d5b08e084 ADD multithreading 2023-04-25 13:43:51 +02:00
benbierens 52a3a3014d Turns off debug logging 2023-04-25 13:38:26 +02:00
benbierens 62f4eed221 Removes BringOnline method 2023-04-25 12:52:11 +02:00
benbierens f94f4a6d22 Adds debug logging support 2023-04-25 11:31:15 +02:00
benbierens 72bb0132bf Implements waiting for correct sync metric of geth nodes 2023-04-25 07:46:09 +02:00
benbierens 929fdb1157 wip: marketplace test is starting to work with annoying sleep and unused code in NethereumInteraction.cs 2023-04-24 16:07:32 +02:00
benbierens 9db35be2ec Allows for nethereum interactions with companion nodes. Verifies that marketplace contract is available before proceeding with codex-node setup. 2023-04-24 14:09:23 +02:00
benbierens f5a1be34c6 Puts geth websocket and http on same port. 2023-04-21 09:57:52 +02:00
benbierens dc9577613f Debugging geth bootnode setup 2023-04-21 09:11:45 +02:00
benbierens 4efc0d9fa4 Merge branch 'master' into feature/marketplace-contracts 2023-04-21 09:11:11 +02:00
Corbo12 b70e8033f3 WIP merge with main 2023-04-20 15:58:47 +02:00
Corbo12 4492b4f0d2 Merge remote-tracking branch 'upstream/master' 2023-04-20 14:16:53 +02:00
Corbo12 71b061cac7 MDF threaded functions not running correctly 2023-04-20 14:16:04 +02:00
Corbo12 a7e024002f ADD upload tests 2023-04-20 12:43:25 +02:00
benbierens 6c8b2047d9 Makes log path relative 2023-04-19 20:31:50 +02:00
benbierens cdd51f6dc7 Sets up some durability tests. 2023-04-19 14:57:00 +02:00
benbierens baf605464c Merge branch 'feature/marketplace-contracts' 2023-04-19 12:58:43 +02:00
Corbo12 18e59f3490 ADD Parallel download Tests 2023-04-19 12:34:46 +02:00
benbierens 5b132bcb54 Fixes connection from codex node to its companion 2023-04-19 11:23:11 +02:00
benbierens e9d84a5cf7 Better logging for json errors and node actions. 2023-04-19 10:42:08 +02:00
benbierens e4e7afd580 Better logging 2023-04-19 09:57:37 +02:00
benbierens 7e6de4146e Better logging and much faster initial balance transactions 2023-04-19 09:19:06 +02:00
benbierens 48ca6f41fa Removes genesis json. 2023-04-19 07:59:28 +02:00
benbierens 12d122ad83 Finishes implementation of marketplace support 2023-04-18 15:33:12 +02:00
benbierens e36d910f2f Applies BaseStarter type 2023-04-18 13:45:48 +02:00
benbierens 98f5e481d1 We mint test tokens! 2023-04-18 13:22:41 +02:00
benbierens 9b38447dbe Setting up access to marketplace address 2023-04-18 10:22:11 +02:00
benbierens f06216b931 Deploys codex-contracts along with geth bootstrap node. 2023-04-17 16:28:07 +02:00
benbierens a12a4fb154 Merge branch 'feature/refactor' into feature/marketplace-contracts 2023-04-17 13:01:03 +02:00
425 changed files with 26610 additions and 3527 deletions
+26
View File
@@ -0,0 +1,26 @@
name: Docker - AutoClient
on:
push:
branches:
- master
tags:
- 'v*.*.*'
paths:
- 'Tools/AutoClient/**'
- '!Tools/AutoClient/docker/docker-compose.yaml'
- 'Framework/**'
- 'ProjectPlugins/**'
- .github/workflows/docker-autoclient.yml
- .github/workflows/docker-reusable.yml
workflow_dispatch:
jobs:
build-and-push:
name: Build and Push
uses: ./.github/workflows/docker-reusable.yml
with:
docker_file: Tools/AutoClient/docker/Dockerfile
docker_repo: codexstorage/codex-autoclient
secrets: inherit
+28
View File
@@ -0,0 +1,28 @@
name: Docker - Discord Bot
on:
push:
branches:
- master
tags:
- 'v*.*.*'
paths:
- 'Tools/BiblioTech/**'
- '!Tools/BiblioTech/docker/docker-compose.yaml'
- 'Framework/**'
- 'ProjectPlugins/**'
- .github/workflows/docker-discordbot.yml
- .github/workflows/docker-reusable.yml
workflow_dispatch:
jobs:
build-and-push:
name: Build and Push
uses: ./.github/workflows/docker-reusable.yml
with:
docker_file: Tools/BiblioTech/docker/Dockerfile
docker_repo: codexstorage/codex-discordbot
secrets: inherit
+27
View File
@@ -0,0 +1,27 @@
name: Docker - KeyMaker
on:
push:
branches:
- master
tags:
- 'v*.*.*'
paths:
- 'Tools/KeyMaker/**'
- 'Framework/**'
- 'ProjectPlugins/**'
- .github/workflows/docker-KeyMaker.yml
- .github/workflows/docker-reusable.yml
workflow_dispatch:
jobs:
build-and-push:
name: Build and Push
uses: ./.github/workflows/docker-reusable.yml
with:
docker_file: Tools/KeyMaker/docker/Dockerfile
docker_repo: codexstorage/codex-keymaker
secrets: inherit
+178
View File
@@ -0,0 +1,178 @@
name: Reusable - Docker
on:
workflow_call:
inputs:
docker_file:
default: docker/Dockerfile
description: Dockerfile
required: false
type: string
docker_repo:
default: codexstorage/cs-codex-dist-tests
description: DockerHub repository
required: false
type: string
tag_latest:
default: true
description: Set latest tag for Docker images
required: false
type: boolean
tag_sha:
default: true
description: Set Git short commit as Docker tag
required: false
type: boolean
tag_suffix:
default: ''
description: Suffix for Docker images tag
required: false
type: string
env:
DOCKER_FILE: ${{ inputs.docker_file }}
DOCKER_REPO: ${{ inputs.docker_repo }}
TAG_LATEST: ${{ inputs.tag_latest }}
TAG_SHA: ${{ inputs.tag_sha }}
TAG_SUFFIX: ${{ inputs.tag_suffix }}
jobs:
# Build platform specific image
build:
strategy:
fail-fast: true
matrix:
target:
- os: linux
arch: amd64
- os: linux
arch: arm64
include:
- target:
os: linux
arch: amd64
builder: ubuntu-22.04
- target:
os: linux
arch: arm64
builder: buildjet-4vcpu-ubuntu-2204-arm
name: Build ${{ matrix.target.os }}/${{ matrix.target.arch }}
runs-on: ${{ matrix.builder }}
env:
PLATFORM: ${{ format('{0}/{1}', 'linux', matrix.target.arch) }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Docker - Meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.DOCKER_REPO }}
- name: Docker - Set up Buildx
uses: docker/setup-buildx-action@v3
- name: Docker - Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker - Build and Push by digest
id: build
uses: docker/build-push-action@v5
with:
context: .
file: ${{ env.DOCKER_FILE }}
platforms: ${{ env.PLATFORM }}
push: true
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.DOCKER_REPO }},push-by-digest=true,name-canonical=true,push=true
- name: Docker - Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Docker - Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ matrix.target.arch }}
path: /tmp/digests
if-no-files-found: error
retention-days: 1
# Publish multi-platform image
publish:
name: Publish multi-platform image
runs-on: ubuntu-latest
needs: build
steps:
- name: Docker - Variables
run: |
# Adjust custom suffix when set and
if [[ -n "${{ env.TAG_SUFFIX }}" ]]; then
echo "TAG_SUFFIX=-${{ env.TAG_SUFFIX }}" >>$GITHUB_ENV
fi
# Disable SHA tags on tagged release
if [[ ${{ startsWith(github.ref, 'refs/tags/') }} == "true" ]]; then
echo "TAG_SHA=false" >>$GITHUB_ENV
fi
# Handle latest and latest-custom using raw
if [[ ${{ env.TAG_SHA }} == "false" ]]; then
echo "TAG_LATEST=false" >>$GITHUB_ENV
echo "TAG_RAW=true" >>$GITHUB_ENV
if [[ -z "${{ env.TAG_SUFFIX }}" ]]; then
echo "TAG_RAW_VALUE=latest" >>$GITHUB_ENV
else
echo "TAG_RAW_VALUE=latest-{{ env.TAG_SUFFIX }}" >>$GITHUB_ENV
fi
else
echo "TAG_RAW=false" >>$GITHUB_ENV
fi
- name: Docker - Download digests
uses: actions/download-artifact@v4
with:
pattern: digests-*
merge-multiple: true
path: /tmp/digests
- name: Docker - Set up Buildx
uses: docker/setup-buildx-action@v3
- name: Docker - Meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.DOCKER_REPO }}
flavor: |
latest=${{ env.TAG_LATEST }}
suffix=${{ env.TAG_SUFFIX }},onlatest=true
tags: |
type=semver,pattern={{version}}
type=raw,enable=${{ env.TAG_RAW }},value=latest
type=sha,enable=${{ env.TAG_SHA }}
- name: Docker - Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker - Create manifest list and push
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.DOCKER_REPO }}@sha256:%s ' *)
- name: Docker - Inspect image
run: |
docker buildx imagetools inspect ${{ env.DOCKER_REPO }}:${{ steps.meta.outputs.version }}
+26
View File
@@ -0,0 +1,26 @@
name: Docker - Rewarder Bot
on:
push:
branches:
- master
tags:
- 'v*.*.*'
paths:
- 'Tools/TestNetRewarder/**'
- '!Tools/TestNetRewarder/docker/docker-compose.yaml'
- 'Framework/**'
- 'ProjectPlugins/**'
- .github/workflows/docker-rewarder.yml
- .github/workflows/docker-reusable.yml
workflow_dispatch:
jobs:
build-and-push:
name: Build and Push
uses: ./.github/workflows/docker-reusable.yml
with:
docker_file: Tools/TestNetRewarder/docker/Dockerfile
docker_repo: codexstorage/codex-rewarderbot
secrets: inherit
+22
View File
@@ -0,0 +1,22 @@
name: Docker - Runner
on:
push:
branches:
- master
tags:
- 'v*.*.*'
paths:
- docker/Dockerfile
- docker/docker-entrypoint.sh
- .github/workflows/docker-runner.yml
- .github/workflows/docker-reusable.yml
workflow_dispatch:
jobs:
build-and-push:
name: Build and Push
uses: ./.github/workflows/docker-reusable.yml
secrets: inherit
@@ -0,0 +1,20 @@
name: Report - HoldMyBeerTest
on:
# schedule:
# - cron: '30 1 */2 * *'
workflow_dispatch:
jobs:
run_tests:
name: Call runner
uses: ./.github/workflows/run-continuous-tests.yaml
with:
source: ${{ format('{0}/{1}', github.server_url, github.repository) }}
branch: master
nameprefix: c-tests-report-holdmybeertest-48h
tests_target_duration: 48h
tests_filter: HoldMyBeerTest
tests_cleanup: true
secrets: inherit
+20
View File
@@ -0,0 +1,20 @@
name: Report - PeersTest
on:
# schedule:
# - cron: '30 1 */2 * *'
workflow_dispatch:
jobs:
run_tests:
name: Call runner
uses: ./.github/workflows/run-continuous-tests.yaml
with:
source: ${{ format('{0}/{1}', github.server_url, github.repository) }}
branch: master
nameprefix: c-tests-report-peerstest-48h
tests_target_duration: 48h
tests_filter: PeersTest
tests_cleanup: true
secrets: inherit
+170
View File
@@ -0,0 +1,170 @@
name: Run Continuous Tests
on:
workflow_dispatch:
inputs:
source:
description: Repository with tests (current)
required: false
type: string
branch:
description: Branch with tests (master)
required: false
type: string
codexdockerimage:
description: Codex Docker image (codexstorage/nim-codex:latest-dist-tests)
required: false
type: string
nameprefix:
description: Resources prefix (c-tests)
required: false
type: string
tests_target_duration:
description: Runner target duration (2d)
required: false
type: string
tests_filter:
description: Runner tests filter ("")
required: false
type: string
tests_cleanup:
description: Runner tests cleanup
required: false
type: boolean
default: true
workflow_call:
inputs:
source:
description: Repository with tests (current)
required: false
type: string
branch:
description: Branch with tests (master)
required: false
type: string
codexdockerimage:
description: Codex Docker image (codexstorage/nim-codex:latest-dist-tests)
required: false
type: string
nameprefix:
description: Resources prefix (c-tests)
required: false
type: string
tests_target_duration:
description: Runner target duration (2d)
required: false
type: string
tests_filter:
description: Runner tests filter ("")
required: false
type: string
tests_cleanup:
description: Runner tests cleanup
required: false
type: boolean
default: true
workflow_source:
description: Workflow source
required: false
type: string
default: ''
env:
SOURCE: ${{ format('{0}/{1}', github.server_url, github.repository) }}
BRANCH: ${{ github.ref_name }}
CODEXDOCKERIMAGE: codexstorage/nim-codex:latest-dist-tests
NAMEPREFIX: c-tests
NAMESPACE: default
TESTS_TARGET_DURATION: 2d
TESTS_FILTER: ""
TESTS_CLEANUP: true
JOB_MANIFEST: docker/continuous-tests-job.yaml
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
KUBE_VERSION: v1.28.2
jobs:
run_tests:
name: Run Continuous Tests ${{ inputs.tests_filter }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
repository: ${{ inputs.workflow_source }}
- name: Variables
run: |
RUNID=$(date +%Y%m%d-%H%M%S)
echo "RUNID=${RUNID}" >> $GITHUB_ENV
echo "TESTID=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
[[ -n "${{ inputs.source }}" ]] && echo "SOURCE=${{ inputs.source }}" >>"$GITHUB_ENV" || echo "SOURCE=${{ env.SOURCE }}" >>"$GITHUB_ENV"
[[ -n "${{ inputs.branch }}" ]] && echo "BRANCH=${{ inputs.branch }}" >>"$GITHUB_ENV" || echo "BRANCH=${{ env.BRANCH }}" >>"$GITHUB_ENV"
[[ -n "${{ inputs.codexdockerimage }}" ]] && echo "CODEXDOCKERIMAGE=${{ inputs.codexdockerimage }}" >>"$GITHUB_ENV" || echo "CODEXDOCKERIMAGE=${{ env.CODEXDOCKERIMAGE }}" >>"$GITHUB_ENV"
[[ -n "${{ inputs.nameprefix }}" ]] && NAMEPREFIX="`awk '{ print tolower($0) }' <<< ${{ inputs.nameprefix }}`" || NAMEPREFIX="`awk '{ print tolower($0) }' <<< ${{ env.NAMEPREFIX }}`"
echo "NAMEPREFIX=${NAMEPREFIX}-${RUNID}" >>"$GITHUB_ENV"
echo "DEPLOYMENT_NAMESPACE=${NAMEPREFIX}-${RUNID}" >>"$GITHUB_ENV"
[[ -n "${{ inputs.namespace }}" ]] && echo "NAMESPACE=${{ inputs.namespace }}" >>"$GITHUB_ENV" || echo "NAMESPACE=${{ env.NAMESPACE }}" >>"$GITHUB_ENV"
[[ -n "${{ inputs.tests_target_duration }}" ]] && echo "TESTS_TARGET_DURATION=${{ inputs.tests_target_duration }}" >>"$GITHUB_ENV" || echo "TESTS_TARGET_DURATION=${{ env.TESTS_TARGET_DURATION }}" >>"$GITHUB_ENV"
[[ -n "${{ inputs.tests_filter }}" ]] && echo "TESTS_FILTER=${{ inputs.tests_filter }}" >>"$GITHUB_ENV" || echo "TESTS_FILTERS=${{ env.TESTS_FILTERS }}" >>"$GITHUB_ENV"
[[ -n "${{ inputs.tests_cleanup }}" ]] && echo "TESTS_CLEANUP=${{ inputs.tests_cleanup }}" >>"$GITHUB_ENV" || echo "TESTS_CLEANUP=${{ env.TESTS_CLEANUP }}" >>"$GITHUB_ENV"
- name: Kubectl - Install ${{ env.KUBE_VERSION }}
uses: azure/setup-kubectl@v4
with:
version: ${{ env.KUBE_VERSION }}
- name: Kubectl - Kubeconfig
run: |
mkdir -p "${HOME}"/.kube
echo "${{ env.KUBE_CONFIG }}" | base64 -d > "${HOME}"/.kube/config
- name: Kubectl - Create Job
run: |
envsubst < ${{ env.JOB_MANIFEST }} | kubectl apply -f -
- name: Tests Identification
run: |
echo "----"
echo "Repository: ${{ env.SOURCE }}"
echo "Branch: ${{ env.BRANCH }}"
echo "Runner job: ${{ env.NAMEPREFIX }}"
echo "Runner pod: `kubectl get pod --selector job-name=${{ env.NAMEPREFIX }} -ojsonpath='{.items[0].metadata.name}'`"
echo "Runner namespace: ${{ env.NAMESPACE }}"
echo "----"
echo "Tests runid: ${{ env.RUNID }}"
echo "Tests codexdockerimage: ${{ env.CODEXDOCKERIMAGE }}"
echo "Tests namespace: ${{ env.DEPLOYMENT_NAMESPACE }}"
echo "Tests duration: ${{ env.TESTS_TARGET_DURATION }}"
echo "Tests filter: ${{ env.TESTS_FILTER }}"
echo "Tests cleanup: ${{ env.TESTS_CLEANUP }}"
echo "----"
- name: Show Runner logs
if: false
run: |
# Variables
# We need more than 300 seconds because Auto Scaler may take 3 minutes to tun a node
duration=600
namespace="${{ env.NAMESPACE }}"
pod=$(kubectl get pod --selector job-name=${{ env.NAMEPREFIX }} -o jsonpath="{.items[0].metadata.name}")
# Check Pod status
WAIT=120
SECONDS=0
sleep=1
while (( SECONDS < WAIT )); do
phase=$(kubectl get pod ${pod} -n ${namespace} -o jsonpath="{.status.phase}")
[[ "${phase}" == "Running" ]] && { echo "Pod $pod is in $phase state - Get the logs"; break; } || { echo "Pod $pod is in $phase state - Retry in $sleep seconds / $((WAIT - SECONDS))"; }
sleep $sleep
done
# Get logs
timeout $duration \
kubectl logs $pod \
-n $namespace \
-f \
--tail=-1 \
--timestamps || true
+83
View File
@@ -0,0 +1,83 @@
name: Run Dist Tests
on:
workflow_dispatch:
inputs:
source:
description: Repository with tests (current)
required: false
type: string
branch:
description: Branch with tests (master)
required: false
type: string
nameprefix:
description: Runner name prefix (d-tests-runner)
required: false
type: string
namespace:
description: Runner namespace (default)
required: false
type: string
command:
description: Command (dotnet test Tests/CodexTests)
required: false
type: string
env:
SOURCE: ${{ format('{0}/{1}', github.server_url, github.repository) }}
BRANCH: ${{ github.ref_name }}
NAMEPREFIX: d-tests-runner
NAMESPACE: default
COMMAND: dotnet test Tests/CodexTests
JOB_MANIFEST: docker/dist-tests-job.yaml
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
KUBE_VERSION: v1.28.2
jobs:
run_tests:
name: Run Tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Variables
run: |
echo "COMMAND=$(jq -c 'split(" ")' <<< '"'$COMMAND'"')" >>"$GITHUB_ENV"
echo "RUNID=$(date +%Y%m%d-%H%M%S)" >> $GITHUB_ENV
echo "TESTID=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
[[ -n "${{ inputs.branch }}" ]] && echo "BRANCH=${{ inputs.branch }}" >>"$GITHUB_ENV" || echo "BRANCH=${{ env.BRANCH }}" >>"$GITHUB_ENV"
[[ -n "${{ inputs.source }}" ]] && echo "SOURCE=${{ inputs.source }}" >>"$GITHUB_ENV" || echo "SOURCE=${{ env.SOURCE }}" >>"$GITHUB_ENV"
[[ -n "${{ inputs.nameprefix }}" ]] && echo "NAMEPREFIX=${{ inputs.nameprefix }}" >>"$GITHUB_ENV" || echo "NAMEPREFIX=${{ env.NAMEPREFIX }}" >>"$GITHUB_ENV"
[[ -n "${{ inputs.namespace }}" ]] && echo "NAMESPACE=${{ inputs.namespace }}" >>"$GITHUB_ENV" || echo "NAMESPACE=${{ env.NAMESPACE }}" >>"$GITHUB_ENV"
[[ -n "${{ inputs.command }}" ]] && COMMAND="${{ inputs.command }}" || COMMAND="${{ env.COMMAND }}"
- name: Kubectl - Install ${{ env.KUBE_VERSION }}
uses: azure/setup-kubectl@v4
with:
version: ${{ env.KUBE_VERSION }}
- name: Kubectl - Kubeconfig
run: |
mkdir -p "${HOME}"/.kube
echo "${{ env.KUBE_CONFIG }}" | base64 -d > "${HOME}"/.kube/config
- name: Kubectl - Create Job
run: |
envsubst < ${{ env.JOB_MANIFEST }} | kubectl apply -f -
- name: Tests Identification
run: |
echo "----"
echo "Repository: ${{ env.SOURCE }}"
echo "Branch: ${{ env.BRANCH }}"
echo "Runner job: ${{ env.NAMEPREFIX }}-${{ env.RUNID }}"
echo "Runner namespace: ${{ env.NAMESPACE }}"
echo "----"
echo "Tests runid: ${{ env.RUNID }}"
echo "Tests command: `jq -r '. | join(" ")' <<< '${{ env.COMMAND }}'`"
echo "----"
+2
View File
@@ -1,3 +1,5 @@
.vs
obj
bin
.vscode
Tools/AutoClient/datapath
+158
View File
@@ -0,0 +1,158 @@
# Distributed System Tests for Nim-Codex
## Contributing plugins
The testing framework was created for testing Codex. However, it's been designed such that other containerized projects can 'easily' be added.
In this file, you'll see 'users' (in quote) mentioned once or twice. This refers to code/projects/tests which end up making use of your plugin. 'Users' come in many shapes and sizes and tend to have many differen use-cases in mind. Please consider this when reading this document and writing your plugin.
## Checklist
Your application must pass this checklist to be compatible with the framework:
- It runs in a docker container.
- It can be configured via environment variables. (You may need to create a docker image which contains a shell script, to pass some env-vars as CLI arguments to your application. Container command overrides do work, but are not equally reliable across container platforms. When in doubt: use env-var!)
- It has network interaction:
- It exposes one or more APIs via one or more ports, OR
- It makes calls to other services. (OR both.)
If your application's use-cases rely primarily on shell interaction, this framework might not be for you. The framework allows you to execute commands in containers AND read stdout/stderr responses. However, its focus during development has always been webservice API interactions.
## Steps
In order to add your project to the framework you must:
1. Create a library assembly in the project plugins folder.
1. It must contain a type that implements the `IProjectPlugin` interface from the `Core` assembly.
1. If your plugin wants to expose any specific methods or objects to 'users', it must implement extensions for the `CoreInterface` type.
1. If your plugin wants to run containers of its own project, it must provide a recipe.
## Constructors & Tools
Your implementation of `IProjectPlugin` must have a public constructor with a single argument of type `IPluginTools`, for example:
```C#
public class MyPlugin : IProjectPlugin
{
public MyPlugin(IPluginTools tools)
{
...
}
...
}
```
## Plugin Interfaces
The `IProjectPlugin` interface requires the implementation of two methods.
1. `Announce` - It is considered polite to use the logging functionality provided by the `IPluginTools` to announce that your plugin has been loaded. You may also want to log some manner of version and/or configuration information at this time if applicable.
1. `Decommission` - Should your plugin have any active system resources, free them in this method. Please note that resources managed by the framework (such as running containers and tracked data files) do *not* need to be manually disposed in this method. `Decommission` is to be used for resources not managed by the framework.
There are a few optional interfaces your plugin may choose to implement. The framework will automatically use these interfaces.
1. `IHasLogPrefix` - Implementing this interface allows you to provide a string which will be prepended to all log statements made by your plugin. A polite thing to do.
1. `IHasMetadata` - This allows you to provide metadata in the form of key/value pairs. This metadata can be accessed by 'users' of your plugin. Often this data finds its way into log files and container-descriptors in order to help track versions/tests/deployments, etc.
## IPluginTools
`IPluginTools` provides your plugin access to all framework functionality, such as logging, tracked file management, container lifecycle management, and a means to create HTTP clients to make calls to containers. (Figure out addresses and ports for containers is handled by the framework.)
It is possible and allowed for your plugin to depend on and use other plugins. (For example, maybe your project wants to interact with Ethereum and wants to use the GethPlugin to talk to a Geth node.) `IPluginTools` is *not* what is used for accessing functionality of other plugins. See 'Core Interface' section.
ILog GetLog();
IHttp CreateHttp(Action<HttpClient> onClientCreated);
IHttp CreateHttp(Action<HttpClient> onClientCreated, ITimeSet timeSet);
IHttp CreateHttp();
IFileManager GetFileManager();
The plugin tools provide:
1. `Workflow` - This tool allows you to start and stop containers using "container recipes". (More on those below.) It also allows you to execute commands inside a container, access stdout/stderr, detect crashes, and access pod deployment information. The workflow tool also lets you inspect the locations available in the cluster, and decide where you want to run containers. (More on that below as well.)
1. `Log` - Good logging is priceless. Use this tool to get a log object handle, and write useful debug/info/error statements.
1. `Http` - This tool gives you a convenient way to access a standard dotnet HttpClient, and takes care of timeouts and retries (in accordance with the config). Additionally, it combos nicely with container objects created by `Workflow`, such that you never have to spend any time figuring out the addresses and ports of your containers.
1. `FileManager` - Lets you use tracked temporary files. Even if the 'user' tests/application start crashing, the framework will make sure these are cleaned up.
## Core Interface
Any functionality your plugin wants to expose to 'users' will have to be added on to the `CoreInterface` type. You can accomplish this by using C# extension methods. The framework provides a `GetPlugin` method to access your plugin instance from the `CoreInterface` type:
```C#
public static class CoreInterfaceExtensions
{
public static MyPluginReturnType DoSomethingCool(this CoreInterface ci, string someArgument)
{
return Plugin(ci).SomethingCool(someArgument);
}
private static MyPlugin Plugin(CoreInterface ci)
{
return ci.GetPlugin<MyPlugin>();
}
}
```
If your plugin wants to access the functionality exposed by other plugins, then you can pass the argument `CoreInterface ci` to your plugin code in order to do so. (For example, if you want to start a Geth node, the Geth plugin adds `IGethNode StartGethNode(this CoreInterface ci, Action<IGethSetup> setup)` to the core interface.) Don't forget you'll need to add a project reference to each plugin project you wish to use.
While technically you can build whatever you like on top of the `CoreInterface` and your own plugin types, I recommend that you follow the approach explained below.
## Deploying, Wrapping, and Starting
When building a plugin, it is important to make as few assumptions as possible about how it will be used by whoever is going to use the framework. For this reason, I recommend you expose three kinds of methods using your `CoreInterface` extensions:
1. Deploy - This kind of method should deploy your project, creating and configuring containers as needed and returning container objects as a result. If your project requires additional information, you can create a new class type to contain both it and the container objects created.
1. Wrap - This kind of method should, when given the previously mentioned container information, create some kind of convenient accessor or interactor object. This object should abstract away for example details of a REST API of your project, allowing users of your plugin to write their code using a set of methods and types that nicely model your project's domain. (For example, if my project has a REST API call that allows users to fetch some state information, the object returned by Wrap should have a convenient method to call that API and receive that state information.)
1. Start - This kind of method does both, simply calling a Deploy method first, then a Wrap method, and returns the result.
Here's an example:
```C#
public static class CoreInterfaceExtensions
{
public static RunningContainers DeployMyProject(this CoreInterface ci, string someArgument)
{
// `RunningContainers` is a framework type. It contains all necessary information about a deployed container. It is serializable.
// Should you need to return any additional information, create a new type that contains it as well as the container information. Make sure it is serializable.
return Plugin(ci).DeployMyProjectContainer(someArgument); // <-- This method should use the `PluginTools.CreateWorkflow()` tool to deploy a container with a configuration that matches someArguments.
}
public static IMyProjectNode WrapMyProjectContainer(this CoreInterface ci, RunningContainers container)
{
return Plugin(ci).WrapMyContainerProject(container); // <-- This method probably will use the 'PluginTools.CreateHttp()` to create an HTTP client for the container, then wrap it in an object that
// represents the API of your project, in this case 'IMyProjectNode'.
}
public static IMyProjectNode StartMyProject(this CoreInterface ci, string someArgument)
{
// Start is now nothing more than a convenience method, combining the previous two.
var rc = ci.DeployMyProject(someArgument);
return WrapMyProjectContainer(ci, rc);
}
}
```
Should your deploy methods not return framework-types like RunningContainers, please make sure that your custom times are serializable. (Decorate them with the `[Serializable]` attribute.) Tools have been built using this framework which rely on the ability to serialize and store deployment information for later use. Please don't break this possibility. (Consider using the `SerializeGate` type to help ensure compatibility.)
The primary reason to decouple deploying and wrapping functionalities is that some use cases require these steps to be performed by separate applications, and different moments in time. For this reason, whatever is returned by the deploy methods should be serializable. After deserialization at some later time, it should then be valid input for the wrap method. The Codex continuous tests system is a clear example of this use case: The `CodexNetDeployer` tool uses deploy methods to create Codex nodes. Then it writes the returned objects to a JSON file. Some time later, the `CodexContinuousTests` application uses this JSON file to reconstruct the objects created by the deploy methods. It then uses the wrap methods to create accessors and interactors, which are used for testing.
## Container Recipes
In order to run a container of your application, the framework needs to know how to create that container. Think of a container recipe as being similar to a docker-compose.yaml file: You specify the docker image, ports, environment variables, persistent volumes, and secrets. However, container recipes are code. This allows you to add conditional behaviour to how your container is constructed. For example: The 'user' of your plugin specifies in their call input that they want to run your application in a certain mode. This would cause your container recipe to set certain environment variables, which cause the application to behave in the requested way.
### Addresses and ports
In a docker-compose.yaml file, it is perfectly normal to specify which ports should be exposed on your container. However, in the framework there's more to consider. When your application container starts, who knows on what kind of machine it runs, and what other processes it's sharing space with? Well, Kubernetes knows. Therefore, it is recommended that container recipes *do not* specify exact port numbers. The framework allows container recipes to declare "a port" without specifying its port number. This allows the framework and Kubernetes to figure out which ports are available when it's time to deploy. In order to find out which port numbers were assigned post-deployment, you can look up the port by tag (which is just an identifying string). When you specify a port to be mapped in your container recipe, you must specify:
1. `Tag` - An identifier.
1. `Internal` or `External` - Whether this port should be accessible only inside the cluster (for other containers (k8s: "ClusterIP")) or outside the cluster as well (for external tools/applications (k8s: "NodePort")).
1. `Protocol` - TCP or UDP. Both protocols on the same port is not universally supported by all container engines, and is therefore not supported by the framework.
If your application wants to listen for incoming traffic from inside its container, be sure to bind it to address "0.0.0.0".
Reminder: If you don't want to worry about addresses, and internal or external ports, you don't have to! The container objects returned by the `workflow` plugin tool have a method called `GetAddress`. Given a port tag, it returns and address object. The `Http` plugin tool can use that address object to set up connections.
## Locations
The framework is designed to allow you to control instances of your application in multiple (physical) locations. It accomplishes this by using kubernetes, and the ability to deploy containers to specific hosts (nodes) inside a kubernetes cluster. Since Kubernetes allows you to build clusters cross-site, this framework in theory enables you to deploy and interact with containers running anywhere.
The `workflow` plugin tool provides you a list of all available locations in the cluster. When starting a container, you are able to pick one of those locations. If no location is selected, one will be chosen by kubernetes. Locations can be chosen explicitly by kubernetes node name, or, they can be picked from the array of available locations.
Example:
```C#
{
var location = Ci.GetKnownLocations().Get("kbnode_euwest_paris1");
var codex = Ci.StartCodexNode(s => s.At(location));
}
```
In this example, 'Ci' is an instance of the core interface. The CodexPlugin exposes a function 'StartCodexNode', which allows its user to specify a location. This location is then passed to the `workflow` tool when the Codex plugin starts its container.
The available locations array guarantees that each entry corresponds to a different kubernetes host.
```C#
{
var knownLocations = Ci.GetKnownLocations();
// I don't care where exactly, as long as they are different locations.
var codexAtZero = Ci.StartCodexNode(s => s.At(knownLocations.Get(0)));
var codexAtOne = Ci.StartCodexNode(s => s.At(knownLocations.Get(1)));
}
```
+40
View File
@@ -0,0 +1,40 @@
# Distributed System Tests for Nim-Codex
## Contributing tests
Do you want to write some tests using this distributed test setup? Great! Here's what you do.
1. Create a branch. Name it something descriptive, but start it with `tests/` please. [Example: `tests/data-redundancy`.]
1. Checkout your branch.
1. Create a new assembly in the `/Tests` folder. This can be an NUnit test assembly or simply a console app.
1. Add Project references to `Core`, as well as any project plugin you'll be using.
1. Write tests! Use existing tests for inspiration.
## Tips for writing tests for Codex
### Transient tests
1. Add new code files to `Tests/CodexTests`
1. Inherrit from `CodexDistTest` or `AutoBootstrapDistTest`.
1. When using `CodexDistTest`:
1. You must start your own Codex bootstrap node. You can use `AddCodex(...)` for this.
1. When you start other Codex nodes with `AddCodex(...)` you can pass the bootstrap node by adding the `.WithBootstrapNode(...)` option.
1. When using `AutoBootstrapDistTest`:
1. The test-infra creates the bootstrap node for you, and automatically passes it to each Codex node you create in your tests. Handy for keeping your tests clean and to-the-point.
1. When using the auto-bootstrap, you have no control over the bootstrap node from your tests. You can't (for example) shut it down during the course of the test. If you need this level of control for your scenario, use the `CodexDistTest` instead.
1. If your test needs a long time to run, add the `[UseLongTimeouts]` function attribute. This will greatly increase maximum time-out values for operations like for example uploading and downloading files.
### Continuous tests
1. Add new code files to `Tests/CodexContinuousTests/Tests`
1. Inherrit from `ContinuousTest`
1. Define one or more methods and decorate them with the `[TestMoment(...)]` attribute.
1. The TestMoment takes a number of seconds as argument. Each moment will be executed by the continuous test runner applying the given seconds as delay. (Non-cumulative. So two moments at T:10 will be executed one after another without delay, in this case the order of execution should not be depended upon.)
1. Continuous tests automatically receive access to the Codex nodes that the tests are being run against.
1. Additionally, Continuous tests can start their own transient Codex nodes and bootstrap them against the persistent nodes.
### Tips for either type of test
1. You can generate files of random test data by calling `GenerateTestFile(...)`.
1. You can enable access to the Codex node metrics by adding the option `.EnableMetrics()`. Enabling metrics will make the test-infra download and save all Codex metrics in case of a test failure. (The metrics are stored as CSV, in the same location as the test log file.)
1. You can enable access to the blockchain marketplace by adding the option `.EnableMarketplace(...)`.
1. Enabling metrics and/or enabling the marketplace takes extra resources from the test-infra and increases the time needed during Codex node setup. Please don't enable these features unless your tests need them.
1. Tip: Codex nodes can be named. Use the option `WithName(...)` and make reading your test logs a little nicer!
1. Tip: Commit often.
## Don't forget
1. Once you're happy with your tests, please create a pull-request and ask a Codex core contributor to review your changes.
-57
View File
@@ -1,57 +0,0 @@
namespace DistTestCore
{
public class ByteSize
{
public ByteSize(long sizeInBytes)
{
SizeInBytes = sizeInBytes;
}
public long SizeInBytes { get; }
}
public static class IntExtensions
{
private const long Kilo = 1024;
public static ByteSize KB(this long i)
{
return new ByteSize(i * Kilo);
}
public static ByteSize MB(this long i)
{
return (i * Kilo).KB();
}
public static ByteSize GB(this long i)
{
return (i * Kilo).MB();
}
public static ByteSize TB(this long i)
{
return (i * Kilo).GB();
}
public static ByteSize KB(this int i)
{
return Convert.ToInt64(i).KB();
}
public static ByteSize MB(this int i)
{
return Convert.ToInt64(i).MB();
}
public static ByteSize GB(this int i)
{
return Convert.ToInt64(i).GB();
}
public static ByteSize TB(this int i)
{
return Convert.ToInt64(i).TB();
}
}
}
-56
View File
@@ -1,56 +0,0 @@
using KubernetesWorkflow;
namespace DistTestCore.Codex
{
public class CodexAccess
{
public CodexAccess(RunningContainer runningContainer)
{
Container = runningContainer;
}
public RunningContainer Container { get; }
public CodexDebugResponse GetDebugInfo()
{
return Http().HttpGetJson<CodexDebugResponse>("debug/info");
}
public string UploadFile(FileStream fileStream)
{
return Http().HttpPostStream("upload", fileStream);
}
public Stream DownloadFile(string contentId)
{
return Http().HttpGetStream("download/" + contentId);
}
private Http Http()
{
var ip = Container.Pod.Cluster.IP;
var port = Container.ServicePorts[0].Number;
return new Http(ip, port, baseUrl: "/api/codex/v1");
}
public string ConnectToPeer(string peerId, string peerMultiAddress)
{
return Http().HttpGetString($"connect/{peerId}?addrs={peerMultiAddress}");
}
}
public class CodexDebugResponse
{
public string id { get; set; } = string.Empty;
public string[] addrs { get; set; } = new string[0];
public string repo { get; set; } = string.Empty;
public string spr { get; set; } = string.Empty;
public CodexDebugVersionResponse codex { get; set; } = new();
}
public class CodexDebugVersionResponse
{
public string version { get; set; } = string.Empty;
public string revision { get; set; } = string.Empty;
}
}
@@ -1,54 +0,0 @@
using DistTestCore.Marketplace;
using KubernetesWorkflow;
namespace DistTestCore.Codex
{
public class CodexContainerRecipe : ContainerRecipeFactory
{
public const string DockerImage = "thatbenbierens/nim-codex:sha-b204837";
public const string MetricsPortTag = "metrics_port";
protected override string Image => DockerImage;
protected override void Initialize(StartupConfig startupConfig)
{
var config = startupConfig.Get<CodexStartupConfig>();
AddExposedPortAndVar("API_PORT");
AddEnvVar("DATA_DIR", $"datadir{ContainerNumber}");
AddInternalPortAndVar("DISC_PORT");
var listenPort = AddInternalPort();
AddEnvVar("LISTEN_ADDRS", $"/ip4/0.0.0.0/tcp/{listenPort.Number}");
if (config.LogLevel != null)
{
AddEnvVar("LOG_LEVEL", config.LogLevel.ToString()!.ToUpperInvariant());
}
if (config.StorageQuota != null)
{
AddEnvVar("STORAGE_QUOTA", config.StorageQuota.SizeInBytes.ToString()!);
}
if (config.MetricsEnabled)
{
AddEnvVar("METRICS_ADDR", "0.0.0.0");
AddInternalPortAndVar("METRICS_PORT", tag: MetricsPortTag);
}
if (config.MarketplaceConfig != null)
{
var gethConfig = startupConfig.Get<GethStartResult>();
var companionNode = gethConfig.CompanionNodes[Index];
Additional(companionNode);
// Bootstrap node access from within the cluster:
//var ip = gethConfig.BootstrapNode.RunningContainers.RunningPod.Ip;
//var port = gethConfig.BootstrapNode.RunningContainers.Containers[0].Recipe.GetPortByTag(GethContainerRecipe.HttpPortTag);
//AddEnvVar("ETH_PROVIDER", "todo");
//AddEnvVar("ETH_ACCOUNT", companionNode.Account);
//AddEnvVar("ETH_DEPLOYMENT", "todo");
}
}
}
}
-16
View File
@@ -1,16 +0,0 @@
using DistTestCore.Marketplace;
using KubernetesWorkflow;
namespace DistTestCore.Codex
{
public class CodexStartupConfig
{
public Location Location { get; set; }
public CodexLogLevel? LogLevel { get; set; }
public ByteSize? StorageQuota { get; set; }
public bool MetricsEnabled { get; set; }
public MarketplaceInitialConfig? MarketplaceConfig { get; set; }
//public IOnlineCodexNode? BootstrapNode { get; private set; }
}
}
-32
View File
@@ -1,32 +0,0 @@
using DistTestCore.Codex;
using DistTestCore.Marketplace;
using DistTestCore.Metrics;
namespace DistTestCore
{
public interface ICodexNodeFactory
{
OnlineCodexNode CreateOnlineCodexNode(CodexAccess access, CodexNodeGroup group);
}
public class CodexNodeFactory : ICodexNodeFactory
{
private readonly TestLifecycle lifecycle;
private readonly IMetricsAccessFactory metricsAccessFactory;
private readonly IMarketplaceAccessFactory marketplaceAccessFactory;
public CodexNodeFactory(TestLifecycle lifecycle, IMetricsAccessFactory metricsAccessFactory, IMarketplaceAccessFactory marketplaceAccessFactory)
{
this.lifecycle = lifecycle;
this.metricsAccessFactory = metricsAccessFactory;
this.marketplaceAccessFactory = marketplaceAccessFactory;
}
public OnlineCodexNode CreateOnlineCodexNode(CodexAccess access, CodexNodeGroup group)
{
var metricsAccess = metricsAccessFactory.CreateMetricsAccess(access.Container);
var marketplaceAccess = marketplaceAccessFactory.CreateMarketplaceAccess(access);
return new OnlineCodexNode(lifecycle, access, group, metricsAccess, marketplaceAccess);
}
}
}
-86
View File
@@ -1,86 +0,0 @@
using DistTestCore.Codex;
using KubernetesWorkflow;
using System.Collections;
namespace DistTestCore
{
public interface ICodexNodeGroup : IEnumerable<IOnlineCodexNode>
{
ICodexSetup BringOffline();
IOnlineCodexNode this[int index] { get; }
}
public class CodexNodeGroup : ICodexNodeGroup
{
private readonly TestLifecycle lifecycle;
public CodexNodeGroup(TestLifecycle lifecycle, CodexSetup setup, RunningContainers containers, ICodexNodeFactory codexNodeFactory)
{
this.lifecycle = lifecycle;
Setup = setup;
Containers = containers;
Nodes = containers.Containers.Select(c => CreateOnlineCodexNode(c, codexNodeFactory)).ToArray();
}
public IOnlineCodexNode this[int index]
{
get
{
return Nodes[index];
}
}
public ICodexSetup BringOffline()
{
lifecycle.CodexStarter.BringOffline(this);
var result = Setup;
// Clear everything. Prevent accidental use.
Setup = null!;
Nodes = Array.Empty<OnlineCodexNode>();
Containers = null!;
return result;
}
public CodexSetup Setup { get; private set; }
public RunningContainers Containers { get; private set; }
public OnlineCodexNode[] Nodes { get; private set; }
public IEnumerator<IOnlineCodexNode> GetEnumerator()
{
return Nodes.Cast<IOnlineCodexNode>().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return Nodes.GetEnumerator();
}
public string Describe()
{
return $"<CodexNodeGroup@{Containers.Describe()}-{Setup.Describe()}>";
}
private OnlineCodexNode CreateOnlineCodexNode(RunningContainer c, ICodexNodeFactory factory)
{
var access = new CodexAccess(c);
EnsureOnline(access);
return factory.CreateOnlineCodexNode(access, this);
}
private void EnsureOnline(CodexAccess access)
{
try
{
var debugInfo = access.GetDebugInfo();
if (debugInfo == null || string.IsNullOrEmpty(debugInfo.id)) throw new InvalidOperationException("Unable to get debug-info from codex node at startup.");
}
catch (Exception e)
{
lifecycle.Log.Error($"Failed to start codex node: {e}");
throw;
}
}
}
}
-84
View File
@@ -1,84 +0,0 @@
using DistTestCore.Codex;
using DistTestCore.Marketplace;
using KubernetesWorkflow;
namespace DistTestCore
{
public interface ICodexSetup
{
ICodexSetup At(Location location);
ICodexSetup WithLogLevel(CodexLogLevel level);
//ICodexStartupConfig WithBootstrapNode(IOnlineCodexNode node);
ICodexSetup WithStorageQuota(ByteSize storageQuota);
ICodexSetup EnableMetrics();
ICodexSetup EnableMarketplace(int initialBalance);
ICodexNodeGroup BringOnline();
}
public class CodexSetup : CodexStartupConfig, ICodexSetup
{
private readonly CodexStarter starter;
public int NumberOfNodes { get; }
public CodexSetup(CodexStarter starter, int numberOfNodes)
{
this.starter = starter;
NumberOfNodes = numberOfNodes;
}
public ICodexNodeGroup BringOnline()
{
return starter.BringOnline(this);
}
public ICodexSetup At(Location location)
{
Location = location;
return this;
}
//public ICodexSetupConfig WithBootstrapNode(IOnlineCodexNode node)
//{
// BootstrapNode = node;
// return this;
//}
public ICodexSetup WithLogLevel(CodexLogLevel level)
{
LogLevel = level;
return this;
}
public ICodexSetup WithStorageQuota(ByteSize storageQuota)
{
StorageQuota = storageQuota;
return this;
}
public ICodexSetup EnableMetrics()
{
MetricsEnabled = true;
return this;
}
public ICodexSetup EnableMarketplace(int initialBalance)
{
MarketplaceConfig = new MarketplaceInitialConfig(initialBalance);
return this;
}
public string Describe()
{
var args = string.Join(',', DescribeArgs());
return $"({NumberOfNodes} CodexNodes with [{args}])";
}
private IEnumerable<string> DescribeArgs()
{
if (LogLevel != null) yield return $"LogLevel={LogLevel}";
//if (BootstrapNode != null) yield return "BootstrapNode=set-not-shown-here";
if (StorageQuota != null) yield return $"StorageQuote={StorageQuota.SizeInBytes}";
}
}
}
-85
View File
@@ -1,85 +0,0 @@
using DistTestCore.Codex;
using KubernetesWorkflow;
namespace DistTestCore
{
public class CodexStarter
{
private readonly TestLifecycle lifecycle;
private readonly WorkflowCreator workflowCreator;
public CodexStarter(TestLifecycle lifecycle, WorkflowCreator workflowCreator)
{
this.lifecycle = lifecycle;
this.workflowCreator = workflowCreator;
}
public List<CodexNodeGroup> RunningGroups { get; } = new List<CodexNodeGroup>();
public ICodexNodeGroup BringOnline(CodexSetup codexSetup)
{
Log($"Starting {codexSetup.Describe()}...");
var gethStartResult = lifecycle.GethStarter.BringOnlineMarketplaceFor(codexSetup);
var startupConfig = new StartupConfig();
startupConfig.Add(codexSetup);
startupConfig.Add(gethStartResult);
var containers = StartCodexContainers(startupConfig, codexSetup.NumberOfNodes, codexSetup.Location);
var metricAccessFactory = lifecycle.PrometheusStarter.CollectMetricsFor(codexSetup, containers);
var codexNodeFactory = new CodexNodeFactory(lifecycle, metricAccessFactory, gethStartResult.MarketplaceAccessFactory);
var group = CreateCodexGroup(codexSetup, containers, codexNodeFactory);
Log($"Started at '{group.Containers.RunningPod.Ip}'");
return group;
}
public void BringOffline(CodexNodeGroup group)
{
Log($"Stopping {group.Describe()}...");
var workflow = CreateWorkflow();
workflow.Stop(group.Containers);
RunningGroups.Remove(group);
Log("Stopped.");
}
public void DeleteAllResources()
{
var workflow = CreateWorkflow();
workflow.DeleteAllResources();
RunningGroups.Clear();
}
public void DownloadLog(RunningContainer container, ILogHandler logHandler)
{
var workflow = CreateWorkflow();
workflow.DownloadContainerLog(container, logHandler);
}
private RunningContainers StartCodexContainers(StartupConfig startupConfig, int numberOfNodes, Location location)
{
var workflow = CreateWorkflow();
return workflow.Start(numberOfNodes, location, new CodexContainerRecipe(), startupConfig);
}
private CodexNodeGroup CreateCodexGroup(CodexSetup codexSetup, RunningContainers runningContainers, CodexNodeFactory codexNodeFactory)
{
var group = new CodexNodeGroup(lifecycle, codexSetup, runningContainers, codexNodeFactory);
RunningGroups.Add(group);
return group;
}
private StartupWorkflow CreateWorkflow()
{
return workflowCreator.CreateWorkflow();
}
private void Log(string msg)
{
lifecycle.Log.Log(msg);
}
}
}
-32
View File
@@ -1,32 +0,0 @@
using KubernetesWorkflow;
namespace DistTestCore
{
public class Configuration
{
public KubernetesWorkflow.Configuration GetK8sConfiguration()
{
return new KubernetesWorkflow.Configuration(
k8sNamespace: "codex-test-ns",
kubeConfigFile: null,
operationTimeout: Timing.K8sOperationTimeout(),
retryDelay: Timing.K8sServiceDelay(),
locationMap: new[]
{
new ConfigurationLocationEntry(Location.BensOldGamingMachine, "worker01"),
new ConfigurationLocationEntry(Location.BensLaptop, "worker02"),
}
);
}
public Logging.LogConfig GetLogConfig()
{
return new Logging.LogConfig("D:/CodexTestLogs");
}
public string GetFileManagerFolder()
{
return "TestDataFiles";
}
}
}
-194
View File
@@ -1,194 +0,0 @@
using DistTestCore.Codex;
using DistTestCore.Logs;
using DistTestCore.Marketplace;
using DistTestCore.Metrics;
using KubernetesWorkflow;
using Logging;
using NUnit.Framework;
using Utils;
namespace DistTestCore
{
[SetUpFixture]
public abstract class DistTest
{
private readonly Configuration configuration = new Configuration();
private FixtureLog fixtureLog = null!;
private TestLifecycle lifecycle = null!;
private DateTime testStart = DateTime.MinValue;
[OneTimeSetUp]
public void GlobalSetup()
{
// Previous test run may have been interrupted.
// Begin by cleaning everything up.
fixtureLog = new FixtureLog(configuration.GetLogConfig());
try
{
Stopwatch.Measure(fixtureLog, "Global setup", () =>
{
var wc = new WorkflowCreator(configuration.GetK8sConfiguration());
wc.CreateWorkflow().DeleteAllResources();
});
}
catch (Exception ex)
{
GlobalTestFailure.HasFailed = true;
Error($"Global setup cleanup failed with: {ex}");
throw;
}
fixtureLog.Log("Global setup cleanup successful");
fixtureLog.Log($"Codex image: {CodexContainerRecipe.DockerImage}");
fixtureLog.Log($"Prometheus image: {PrometheusContainerRecipe.DockerImage}");
fixtureLog.Log($"Geth image: {GethContainerRecipe.DockerImage}");
}
[SetUp]
public void SetUpDistTest()
{
if (GlobalTestFailure.HasFailed)
{
Assert.Inconclusive("Skip test: Previous test failed during clean up.");
}
else
{
CreateNewTestLifecycle();
}
}
[TearDown]
public void TearDownDistTest()
{
try
{
DisposeTestLifecycle();
}
catch (Exception ex)
{
Error("Cleanup failed: " + ex.Message);
GlobalTestFailure.HasFailed = true;
}
}
public TestFile GenerateTestFile(ByteSize size)
{
return lifecycle.FileManager.GenerateTestFile(size);
}
public ICodexSetup SetupCodexNodes(int numberOfNodes)
{
return new CodexSetup(lifecycle.CodexStarter, numberOfNodes);
}
private void IncludeLogsAndMetricsOnTestFailure()
{
var result = TestContext.CurrentContext.Result;
if (result.Outcome.Status == NUnit.Framework.Interfaces.TestStatus.Failed)
{
fixtureLog.MarkAsFailed();
if (IsDownloadingLogsAndMetricsEnabled())
{
Log("Downloading all CodexNode logs and metrics because of test failure...");
DownloadAllLogs();
DownloadAllMetrics();
}
else
{
Log("Skipping download of all CodexNode logs and metrics due to [DontDownloadLogsAndMetricsOnFailure] attribute.");
}
}
}
private void Log(string msg)
{
lifecycle.Log.Log(msg);
}
private void Error(string msg)
{
lifecycle.Log.Error(msg);
}
private void CreateNewTestLifecycle()
{
Stopwatch.Measure(fixtureLog, $"Setup for {GetCurrentTestName()}", () =>
{
lifecycle = new TestLifecycle(fixtureLog.CreateTestLog(), configuration);
testStart = DateTime.UtcNow;
});
}
private void DisposeTestLifecycle()
{
fixtureLog.Log($"{GetCurrentTestName()} = {GetTestResult()} ({GetTestDuration()})");
Stopwatch.Measure(fixtureLog, $"Teardown for {GetCurrentTestName()}", () =>
{
lifecycle.Log.EndTest();
IncludeLogsAndMetricsOnTestFailure();
lifecycle.DeleteAllResources();
lifecycle = null!;
});
}
private string GetTestDuration()
{
var testDuration = DateTime.UtcNow - testStart;
return Time.FormatDuration(testDuration);
}
private void DownloadAllLogs()
{
OnEachCodexNode(node =>
{
lifecycle.DownloadLog(node);
});
}
private void DownloadAllMetrics()
{
var metricsDownloader = new MetricsDownloader(lifecycle.Log);
OnEachCodexNode(node =>
{
var m = node.Metrics as MetricsAccess;
if (m != null)
{
metricsDownloader.DownloadAllMetricsForNode(node.GetName(), m);
}
});
}
private void OnEachCodexNode(Action<OnlineCodexNode> action)
{
var allNodes = lifecycle.CodexStarter.RunningGroups.SelectMany(g => g.Nodes);
foreach (var node in allNodes)
{
action(node);
}
}
private string GetCurrentTestName()
{
return $"[{TestContext.CurrentContext.Test.Name}]";
}
private string GetTestResult()
{
return TestContext.CurrentContext.Result.Outcome.Status.ToString();
}
private bool IsDownloadingLogsAndMetricsEnabled()
{
var testProperties = TestContext.CurrentContext.Test.Properties;
return !testProperties.ContainsKey(DontDownloadLogsAndMetricsOnFailureAttribute.DontDownloadKey);
}
}
public static class GlobalTestFailure
{
public static bool HasFailed { get; set; } = false;
}
}
-120
View File
@@ -1,120 +0,0 @@
using Logging;
using NUnit.Framework;
namespace DistTestCore
{
public interface IFileManager
{
TestFile CreateEmptyTestFile();
TestFile GenerateTestFile(ByteSize size);
void DeleteAllTestFiles();
}
public class FileManager : IFileManager
{
public const int ChunkSize = 1024 * 1024;
private readonly Random random = new Random();
private readonly TestLog log;
private readonly string folder;
public FileManager(TestLog log, Configuration configuration)
{
folder = configuration.GetFileManagerFolder();
EnsureDirectory();
this.log = log;
}
public TestFile CreateEmptyTestFile()
{
var result = new TestFile(Path.Combine(folder, Guid.NewGuid().ToString() + "_test.bin"));
File.Create(result.Filename).Close();
return result;
}
public TestFile GenerateTestFile(ByteSize size)
{
var result = CreateEmptyTestFile();
GenerateFileBytes(result, size);
log.Log($"Generated {size.SizeInBytes} bytes of content for file '{result.Filename}'.");
return result;
}
public void DeleteAllTestFiles()
{
DeleteDirectory();
}
private void GenerateFileBytes(TestFile result, ByteSize size)
{
long bytesLeft = size.SizeInBytes;
while (bytesLeft > 0)
{
var length = Math.Min(bytesLeft, ChunkSize);
AppendRandomBytesToFile(result, length);
bytesLeft -= length;
}
}
private void AppendRandomBytesToFile(TestFile result, long length)
{
var bytes = new byte[length];
random.NextBytes(bytes);
using var stream = new FileStream(result.Filename, FileMode.Append);
stream.Write(bytes, 0, bytes.Length);
}
private void EnsureDirectory()
{
if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
}
private void DeleteDirectory()
{
Directory.Delete(folder, true);
}
}
public class TestFile
{
public TestFile(string filename)
{
Filename = filename;
}
public string Filename { get; }
public long GetFileSize()
{
var info = new FileInfo(Filename);
return info.Length;
}
public void AssertIsEqual(TestFile? actual)
{
if (actual == null) Assert.Fail("TestFile is null.");
if (actual == this || actual!.Filename == Filename) Assert.Fail("TestFile is compared to itself.");
Assert.That(actual.GetFileSize(), Is.EqualTo(GetFileSize()), "Files are not of equal length.");
using var streamExpected = new FileStream(Filename, FileMode.Open, FileAccess.Read);
using var streamActual = new FileStream(actual.Filename, FileMode.Open, FileAccess.Read);
var bytesExpected = new byte[FileManager.ChunkSize];
var bytesActual = new byte[FileManager.ChunkSize];
var readExpected = 0;
var readActual = 0;
while (true)
{
readExpected = streamExpected.Read(bytesExpected, 0, FileManager.ChunkSize);
readActual = streamActual.Read(bytesActual, 0, FileManager.ChunkSize);
if (readExpected == 0 && readActual == 0) return;
Assert.That(readActual, Is.EqualTo(readExpected), "Unable to read buffers of equal length.");
CollectionAssert.AreEqual(bytesExpected, bytesActual, "Files are not binary-equal.");
}
}
}
}
-80
View File
@@ -1,80 +0,0 @@
using DistTestCore.Marketplace;
using KubernetesWorkflow;
namespace DistTestCore
{
public class GethStarter
{
private readonly GethBootstrapNodeCache bootstrapNodeCache;
private readonly GethCompanionNodeStarter companionNodeStarter;
private readonly TestLifecycle lifecycle;
public GethStarter(TestLifecycle lifecycle, WorkflowCreator workflowCreator)
{
bootstrapNodeCache = new GethBootstrapNodeCache(new GethBootstrapNodeStarter(lifecycle, workflowCreator));
companionNodeStarter = new GethCompanionNodeStarter(lifecycle, workflowCreator);
this.lifecycle = lifecycle;
}
public GethStartResult BringOnlineMarketplaceFor(CodexSetup codexSetup)
{
if (codexSetup.MarketplaceConfig == null) return CreateMarketplaceUnavailableResult();
var bootstrapNode = bootstrapNodeCache.Get();
var companionNodes = StartCompanionNodes(codexSetup, bootstrapNode);
TransferInitialBalance(bootstrapNode, codexSetup.MarketplaceConfig.InitialBalance, companionNodes);
return CreateGethStartResult(bootstrapNode, companionNodes);
}
private void TransferInitialBalance(GethBootstrapNodeInfo bootstrapNode, int initialBalance, GethCompanionNodeInfo[] companionNodes)
{
var interaction = bootstrapNode.StartInteraction(lifecycle.Log);
foreach (var node in companionNodes)
{
interaction.TransferTo(node.Account, initialBalance);
}
}
private GethStartResult CreateGethStartResult(GethBootstrapNodeInfo bootstrapNode, GethCompanionNodeInfo[] companionNodes)
{
return new GethStartResult(CreateMarketplaceAccessFactory(bootstrapNode), bootstrapNode, companionNodes);
}
private GethStartResult CreateMarketplaceUnavailableResult()
{
return new GethStartResult(new MarketplaceUnavailableAccessFactory(), null!, Array.Empty<GethCompanionNodeInfo>());
}
private IMarketplaceAccessFactory CreateMarketplaceAccessFactory(GethBootstrapNodeInfo bootstrapNode)
{
return new GethMarketplaceAccessFactory(lifecycle.Log, bootstrapNode!);
}
private GethCompanionNodeInfo[] StartCompanionNodes(CodexSetup codexSetup, GethBootstrapNodeInfo bootstrapNode)
{
return companionNodeStarter.StartCompanionNodesFor(codexSetup, bootstrapNode);
}
}
public class GethBootstrapNodeCache
{
private readonly GethBootstrapNodeStarter bootstrapNodeStarter;
private GethBootstrapNodeInfo? bootstrapNode;
public GethBootstrapNodeCache(GethBootstrapNodeStarter bootstrapNodeStarter)
{
this.bootstrapNodeStarter = bootstrapNodeStarter;
}
public GethBootstrapNodeInfo Get()
{
if (bootstrapNode == null)
{
bootstrapNode = bootstrapNodeStarter.StartGethBootstrapNode();
}
return bootstrapNode;
}
}
}
-101
View File
@@ -1,101 +0,0 @@
using Newtonsoft.Json;
using NUnit.Framework;
using System.Net.Http.Headers;
using Utils;
namespace DistTestCore
{
public class Http
{
private readonly string ip;
private readonly int port;
private readonly string baseUrl;
public Http(string ip, int port, string baseUrl)
{
this.ip = ip;
this.port = port;
this.baseUrl = baseUrl;
if (!this.baseUrl.StartsWith("/")) this.baseUrl = "/" + this.baseUrl;
if (!this.baseUrl.EndsWith("/")) this.baseUrl += "/";
}
public string HttpGetString(string route)
{
return Retry(() =>
{
using var client = GetClient();
var url = GetUrl() + route;
var result = Time.Wait(client.GetAsync(url));
return Time.Wait(result.Content.ReadAsStringAsync());
});
}
public T HttpGetJson<T>(string route)
{
return JsonConvert.DeserializeObject<T>(HttpGetString(route))!;
}
public string HttpPostStream(string route, Stream stream)
{
return Retry(() =>
{
using var client = GetClient();
var url = GetUrl() + route;
var content = new StreamContent(stream);
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
var response = Time.Wait(client.PostAsync(url, content));
return Time.Wait(response.Content.ReadAsStringAsync());
});
}
public Stream HttpGetStream(string route)
{
return Retry(() =>
{
var client = GetClient();
var url = GetUrl() + route;
return Time.Wait(client.GetStreamAsync(url));
});
}
private string GetUrl()
{
return $"http://{ip}:{port}{baseUrl}";
}
private static T Retry<T>(Func<T> operation)
{
var retryCounter = 0;
while (true)
{
try
{
return operation();
}
catch (Exception exception)
{
Timing.HttpCallRetryDelay();
retryCounter++;
if (retryCounter > Timing.HttpCallRetryCount())
{
Assert.Fail(exception.Message);
throw;
}
}
}
}
private static HttpClient GetClient()
{
var client = new HttpClient();
client.Timeout = Timing.HttpCallTimeout();
return client;
}
}
}
-35
View File
@@ -1,35 +0,0 @@
using Logging;
using NUnit.Framework;
namespace DistTestCore.Logs
{
public interface ICodexNodeLog
{
void AssertLogContains(string expectedString);
}
public class CodexNodeLog : ICodexNodeLog
{
private readonly LogFile logFile;
public CodexNodeLog(LogFile logFile)
{
this.logFile = logFile;
}
public void AssertLogContains(string expectedString)
{
using var file = File.OpenRead(logFile.FullFilename);
using var streamReader = new StreamReader(file);
var line = streamReader.ReadLine();
while (line != null)
{
if (line.Contains(expectedString)) return;
line = streamReader.ReadLine();
}
Assert.Fail($"Unable to find string '{expectedString}' in CodexNode log file {logFile.FullFilename}");
}
}
}
@@ -1,15 +0,0 @@
using NUnit.Framework;
namespace DistTestCore.Logs
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class DontDownloadLogsAndMetricsOnFailureAttribute : PropertyAttribute
{
public const string DontDownloadKey = "DontDownloadLogsAndMetrics";
public DontDownloadLogsAndMetricsOnFailureAttribute()
: base(DontDownloadKey)
{
}
}
}
-28
View File
@@ -1,28 +0,0 @@
using KubernetesWorkflow;
using Logging;
namespace DistTestCore.Logs
{
public class LogDownloadHandler : LogHandler, ILogHandler
{
private readonly LogFile log;
public LogDownloadHandler(string description, LogFile log)
{
this.log = log;
log.Write($"{description} -->> {log.FullFilename}");
log.WriteRaw(description);
}
public CodexNodeLog CreateCodexNodeLog()
{
return new CodexNodeLog(log);
}
protected override void ProcessLine(string line)
{
log.WriteRaw(line);
}
}
}
@@ -1,33 +0,0 @@
using KubernetesWorkflow;
using Logging;
using NethereumWorkflow;
namespace DistTestCore.Marketplace
{
public class GethBootstrapNodeInfo
{
public GethBootstrapNodeInfo(RunningContainers runningContainers, string account, string genesisJsonBase64, string pubKey, Port discoveryPort)
{
RunningContainers = runningContainers;
Account = account;
GenesisJsonBase64 = genesisJsonBase64;
PubKey = pubKey;
DiscoveryPort = discoveryPort;
}
public RunningContainers RunningContainers { get; }
public string Account { get; }
public string GenesisJsonBase64 { get; }
public string PubKey { get; }
public Port DiscoveryPort { get; }
public NethereumInteraction StartInteraction(TestLog log)
{
var ip = RunningContainers.RunningPod.Cluster.IP;
var port = RunningContainers.Containers[0].ServicePorts[0].Number;
var creator = new NethereumInteractionCreator(log, ip, port, Account);
return creator.CreateWorkflow();
}
}
}
@@ -1,49 +0,0 @@
using KubernetesWorkflow;
namespace DistTestCore.Marketplace
{
public class GethBootstrapNodeStarter
{
private const string bootstrapGenesisJsonBase64 = "ewogICAgImNvbmZpZyI6IHsKICAgICAgImNoYWluSWQiOiA3ODk5ODgsCiAgICAgICJob21lc3RlYWRCbG9jayI6IDAsCiAgICAgICJlaXAxNTBCbG9jayI6IDAsCiAgICAgICJlaXAxNTVCbG9jayI6IDAsCiAgICAgICJlaXAxNThCbG9jayI6IDAsCiAgICAgICJieXphbnRpdW1CbG9jayI6IDAsCiAgICAgICJjb25zdGFudGlub3BsZUJsb2NrIjogMCwKICAgICAgInBldGVyc2J1cmdCbG9jayI6IDAsCiAgICAgICJpc3RhbmJ1bEJsb2NrIjogMCwKICAgICAgIm11aXJHbGFjaWVyQmxvY2siOiAwLAogICAgICAiYmVybGluQmxvY2siOiAwLAogICAgICAibG9uZG9uQmxvY2siOiAwLAogICAgICAiYXJyb3dHbGFjaWVyQmxvY2siOiAwLAogICAgICAiZ3JheUdsYWNpZXJCbG9jayI6IDAsCiAgICAgICJjbGlxdWUiOiB7CiAgICAgICAgInBlcmlvZCI6IDUsCiAgICAgICAgImVwb2NoIjogMzAwMDAKICAgICAgfQogICAgfSwKICAgICJkaWZmaWN1bHR5IjogIjEiLAogICAgImdhc0xpbWl0IjogIjgwMDAwMDAwMCIsCiAgICAiZXh0cmFkYXRhIjogIjB4MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMEFDQ09VTlRfSEVSRTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAiLAogICAgImFsbG9jIjogewogICAgICAiMHhBQ0NPVU5UX0hFUkUiOiB7ICJiYWxhbmNlIjogIjUwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAiIH0KICAgIH0KICB9";
private readonly TestLifecycle lifecycle;
private readonly WorkflowCreator workflowCreator;
public GethBootstrapNodeStarter(TestLifecycle lifecycle, WorkflowCreator workflowCreator)
{
this.lifecycle = lifecycle;
this.workflowCreator = workflowCreator;
}
public GethBootstrapNodeInfo StartGethBootstrapNode()
{
Log("Starting Geth bootstrap node...");
var startupConfig = CreateBootstrapStartupConfig();
var workflow = workflowCreator.CreateWorkflow();
var containers = workflow.Start(1, Location.Unspecified, new GethContainerRecipe(), startupConfig);
if (containers.Containers.Length != 1) throw new InvalidOperationException("Expected 1 Geth bootstrap node to be created. Test infra failure.");
var extractor = new GethInfoExtractor(workflow, containers.Containers[0]);
var account = extractor.ExtractAccount();
var genesisJsonBase64 = extractor.ExtractGenesisJsonBase64();
var pubKey = extractor.ExtractPubKey();
var discoveryPort = containers.Containers[0].Recipe.GetPortByTag(GethContainerRecipe.DiscoveryPortTag);
Log($"Geth bootstrap node started with account '{account}'");
return new GethBootstrapNodeInfo(containers, account, genesisJsonBase64, pubKey, discoveryPort);
}
private StartupConfig CreateBootstrapStartupConfig()
{
var config = new StartupConfig();
config.Add(new GethStartupConfig(true, bootstrapGenesisJsonBase64, null!));
return config;
}
private void Log(string msg)
{
lifecycle.Log.Log(msg);
}
}
}
@@ -1,16 +0,0 @@
using KubernetesWorkflow;
namespace DistTestCore.Marketplace
{
public class GethCompanionNodeInfo
{
public GethCompanionNodeInfo(RunningContainer runningContainer, string account)
{
RunningContainer = runningContainer;
Account = account;
}
public RunningContainer RunningContainer { get; }
public string Account { get; }
}
}
@@ -1,50 +0,0 @@
using KubernetesWorkflow;
namespace DistTestCore.Marketplace
{
public class GethCompanionNodeStarter
{
private readonly TestLifecycle lifecycle;
private readonly WorkflowCreator workflowCreator;
public GethCompanionNodeStarter(TestLifecycle lifecycle, WorkflowCreator workflowCreator)
{
this.lifecycle = lifecycle;
this.workflowCreator = workflowCreator;
}
public GethCompanionNodeInfo[] StartCompanionNodesFor(CodexSetup codexSetup, GethBootstrapNodeInfo bootstrapNode)
{
Log($"Initializing companions for {codexSetup.NumberOfNodes} Codex nodes.");
var startupConfig = CreateCompanionNodeStartupConfig(bootstrapNode);
var workflow = workflowCreator.CreateWorkflow();
var containers = workflow.Start(codexSetup.NumberOfNodes, Location.Unspecified, new GethContainerRecipe(), startupConfig);
if (containers.Containers.Length != codexSetup.NumberOfNodes) throw new InvalidOperationException("Expected a Geth companion node to be created for each Codex node. Test infra failure.");
Log("Initialized companion nodes.");
return containers.Containers.Select(c => CreateCompanionInfo(workflow, c)).ToArray();
}
private GethCompanionNodeInfo CreateCompanionInfo(StartupWorkflow workflow, RunningContainer container)
{
var extractor = new GethInfoExtractor(workflow, container);
var account = extractor.ExtractAccount();
return new GethCompanionNodeInfo(container, account);
}
private StartupConfig CreateCompanionNodeStartupConfig(GethBootstrapNodeInfo bootstrapNode)
{
var config = new StartupConfig();
config.Add(new GethStartupConfig(false, bootstrapNode.GenesisJsonBase64, bootstrapNode));
return config;
}
private void Log(string msg)
{
lifecycle.Log.Log(msg);
}
}
}
@@ -1,49 +0,0 @@
using KubernetesWorkflow;
namespace DistTestCore.Marketplace
{
public class GethContainerRecipe : ContainerRecipeFactory
{
public const string DockerImage = "thatbenbierens/geth-confenv:latest";
public const string HttpPortTag = "http_port";
public const string DiscoveryPortTag = "disc_port";
public const string AccountFilename = "account_string.txt";
public const string GenesisFilename = "genesis.json";
protected override string Image => DockerImage;
protected override void Initialize(StartupConfig startupConfig)
{
var config = startupConfig.Get<GethStartupConfig>();
var args = CreateArgs(config);
AddEnvVar("GETH_ARGS", args);
AddEnvVar("GENESIS_JSON", config.GenesisJsonBase64);
}
private string CreateArgs(GethStartupConfig config)
{
var discovery = AddInternalPort(tag: DiscoveryPortTag);
if (config.IsBootstrapNode)
{
AddEnvVar("IS_BOOTSTRAP", "1");
var exposedPort = AddExposedPort();
return $"--http.port {exposedPort.Number} --discovery.port {discovery.Number} --nodiscover";
}
var port = AddInternalPort();
var authRpc = AddInternalPort();
var httpPort = AddInternalPort(tag: HttpPortTag);
var bootPubKey = config.BootstrapNode.PubKey;
var bootIp = config.BootstrapNode.RunningContainers.Containers[0].Pod.Ip;
var bootPort = config.BootstrapNode.DiscoveryPort.Number;
var bootstrapArg = $"--bootnodes enode://{bootPubKey}@{bootIp}:{bootPort}";
// geth --bootnodes enode://pubkey1@ip1:port1
return $"--port {port.Number} --discovery.port {discovery.Number} --authrpc.port {authRpc.Number} --http.port {httpPort.Number} --nodiscover {bootstrapArg}";
}
}
}
@@ -1,110 +0,0 @@
using KubernetesWorkflow;
using System.Text;
namespace DistTestCore.Marketplace
{
public class GethInfoExtractor
{
private readonly StartupWorkflow workflow;
private readonly RunningContainer container;
public GethInfoExtractor(StartupWorkflow workflow, RunningContainer container)
{
this.workflow = workflow;
this.container = container;
}
public string ExtractAccount()
{
var account = Retry(FetchAccount);
if (string.IsNullOrEmpty(account)) throw new InvalidOperationException("Unable to fetch account for geth node. Test infra failure.");
return account;
}
public string ExtractGenesisJsonBase64()
{
var genesisJson = Retry(FetchGenesisJson);
if (string.IsNullOrEmpty(genesisJson)) throw new InvalidOperationException("Unable to fetch genesis-json for geth node. Test infra failure.");
return Convert.ToBase64String(Encoding.ASCII.GetBytes(genesisJson));
}
public string ExtractPubKey()
{
var pubKey = Retry(FetchPubKey);
if (string.IsNullOrEmpty(pubKey)) throw new InvalidOperationException("Unable to fetch enode from geth node. Test infra failure.");
return pubKey;
}
private string Retry(Func<string> fetch)
{
var result = Catch(fetch);
if (string.IsNullOrEmpty(result))
{
Thread.Sleep(TimeSpan.FromSeconds(5));
result = fetch();
}
return result;
}
private string Catch(Func<string> fetch)
{
try
{
return fetch();
}
catch
{
return string.Empty;
}
}
private string FetchGenesisJson()
{
return workflow.ExecuteCommand(container, "cat", GethContainerRecipe.GenesisFilename);
}
private string FetchAccount()
{
return workflow.ExecuteCommand(container, "cat", GethContainerRecipe.AccountFilename);
}
private string FetchPubKey()
{
var enodeFinder = new PubKeyFinder();
workflow.DownloadContainerLog(container, enodeFinder);
return enodeFinder.GetPubKey();
}
}
public class PubKeyFinder : LogHandler, ILogHandler
{
private const string openTag = "self=\"enode://";
private string pubKey = string.Empty;
public string GetPubKey()
{
return pubKey;
}
protected override void ProcessLine(string line)
{
if (line.Contains(openTag))
{
ExtractPubKey(line);
}
}
private void ExtractPubKey(string line)
{
var openIndex = line.IndexOf(openTag) + openTag.Length;
var closeIndex = line.IndexOf("@");
pubKey = line.Substring(
startIndex: openIndex,
length: closeIndex - openIndex);
}
}
}
@@ -1,16 +0,0 @@
namespace DistTestCore.Marketplace
{
public class GethStartResult
{
public GethStartResult(IMarketplaceAccessFactory marketplaceAccessFactory, GethBootstrapNodeInfo bootstrapNode, GethCompanionNodeInfo[] companionNodes)
{
MarketplaceAccessFactory = marketplaceAccessFactory;
BootstrapNode = bootstrapNode;
CompanionNodes = companionNodes;
}
public IMarketplaceAccessFactory MarketplaceAccessFactory { get; }
public GethBootstrapNodeInfo BootstrapNode { get; }
public GethCompanionNodeInfo[] CompanionNodes { get; }
}
}
@@ -1,16 +0,0 @@
namespace DistTestCore.Marketplace
{
public class GethStartupConfig
{
public GethStartupConfig(bool isBootstrapNode, string genesisJsonBase64, GethBootstrapNodeInfo bootstrapNode)
{
IsBootstrapNode = isBootstrapNode;
GenesisJsonBase64 = genesisJsonBase64;
BootstrapNode = bootstrapNode;
}
public bool IsBootstrapNode { get; }
public string GenesisJsonBase64 { get; }
public GethBootstrapNodeInfo BootstrapNode { get; }
}
}
@@ -1,79 +0,0 @@
using Logging;
using NUnit.Framework;
using NUnit.Framework.Constraints;
namespace DistTestCore.Marketplace
{
public interface IMarketplaceAccess
{
void MakeStorageAvailable(ByteSize size, int minPricePerBytePerSecond, float maxCollateral);
void RequestStorage(ContentId contentId, int pricePerBytePerSecond, float requiredCollateral, float minRequiredNumberOfNodes);
void AssertThatBalance(IResolveConstraint constraint, string message = "");
decimal GetBalance();
}
public class MarketplaceAccess : IMarketplaceAccess
{
private readonly TestLog log;
private readonly GethBootstrapNodeInfo bootstrapNode;
private readonly GethCompanionNodeInfo companionNode;
public MarketplaceAccess(TestLog log, GethBootstrapNodeInfo bootstrapNode, GethCompanionNodeInfo companionNode)
{
this.log = log;
this.bootstrapNode = bootstrapNode;
this.companionNode = companionNode;
}
public void RequestStorage(ContentId contentId, int pricePerBytePerSecond, float requiredCollateral, float minRequiredNumberOfNodes)
{
throw new NotImplementedException();
}
public void MakeStorageAvailable(ByteSize size, int minPricePerBytePerSecond, float maxCollateral)
{
throw new NotImplementedException();
}
public void AssertThatBalance(IResolveConstraint constraint, string message = "")
{
Assert.That(GetBalance(), constraint, message);
}
public decimal GetBalance()
{
var interaction = bootstrapNode.StartInteraction(log);
return interaction.GetBalance(companionNode.Account);
}
}
public class MarketplaceUnavailable : IMarketplaceAccess
{
public void RequestStorage(ContentId contentId, int pricePerBytePerSecond, float requiredCollateral, float minRequiredNumberOfNodes)
{
Unavailable();
}
public void MakeStorageAvailable(ByteSize size, int minPricePerBytePerSecond, float maxCollateral)
{
Unavailable();
}
public void AssertThatBalance(IResolveConstraint constraint, string message = "")
{
Unavailable();
}
public decimal GetBalance()
{
Unavailable();
return 0;
}
private void Unavailable()
{
Assert.Fail("Incorrect test setup: Marketplace was not enabled for this group of Codex nodes. Add 'EnableMarketplace(...)' after 'SetupCodexNodes()' to enable it.");
throw new InvalidOperationException();
}
}
}
@@ -1,42 +0,0 @@
using DistTestCore.Codex;
using Logging;
namespace DistTestCore.Marketplace
{
public interface IMarketplaceAccessFactory
{
IMarketplaceAccess CreateMarketplaceAccess(CodexAccess access);
}
public class MarketplaceUnavailableAccessFactory : IMarketplaceAccessFactory
{
public IMarketplaceAccess CreateMarketplaceAccess(CodexAccess access)
{
return new MarketplaceUnavailable();
}
}
public class GethMarketplaceAccessFactory : IMarketplaceAccessFactory
{
private readonly TestLog log;
private readonly GethBootstrapNodeInfo bootstrapNode;
public GethMarketplaceAccessFactory(TestLog log, GethBootstrapNodeInfo bootstrapNode)
{
this.log = log;
this.bootstrapNode = bootstrapNode;
}
public IMarketplaceAccess CreateMarketplaceAccess(CodexAccess access)
{
var companionNode = GetGethCompanionNode(access);
return new MarketplaceAccess(log, bootstrapNode, companionNode);
}
private GethCompanionNodeInfo GetGethCompanionNode(CodexAccess access)
{
var node = access.Container.Recipe.Additionals.Single(a => a is GethCompanionNodeInfo);
return (GethCompanionNodeInfo)node;
}
}
}
@@ -1,12 +0,0 @@
namespace DistTestCore.Marketplace
{
public class MarketplaceInitialConfig
{
public MarketplaceInitialConfig(int initialBalance)
{
InitialBalance = initialBalance;
}
public int InitialBalance { get; }
}
}
-70
View File
@@ -1,70 +0,0 @@
using KubernetesWorkflow;
using NUnit.Framework;
using NUnit.Framework.Constraints;
using Utils;
namespace DistTestCore.Metrics
{
public interface IMetricsAccess
{
void AssertThat(string metricName, IResolveConstraint constraint, string message = "");
}
public class MetricsUnavailable : IMetricsAccess
{
public void AssertThat(string metricName, IResolveConstraint constraint, string message = "")
{
Assert.Fail("Incorrect test setup: Metrics were not enabled for this group of Codex nodes. Add 'EnableMetrics()' after 'SetupCodexNodes()' to enable it.");
throw new InvalidOperationException();
}
}
public class MetricsAccess : IMetricsAccess
{
private readonly MetricsQuery query;
private readonly RunningContainer node;
public MetricsAccess(MetricsQuery query, RunningContainer node)
{
this.query = query;
this.node = node;
}
public void AssertThat(string metricName, IResolveConstraint constraint, string message = "")
{
var metricSet = GetMetricWithTimeout(metricName);
var metricValue = metricSet.Values[0].Value;
Assert.That(metricValue, constraint, message);
}
public Metrics? GetAllMetrics()
{
return query.GetAllMetricsForNode(node);
}
private MetricsSet GetMetricWithTimeout(string metricName)
{
var start = DateTime.UtcNow;
while (true)
{
var mostRecent = GetMostRecent(metricName);
if (mostRecent != null) return mostRecent;
if (DateTime.UtcNow - start > Timing.WaitForMetricTimeout())
{
Assert.Fail($"Timeout: Unable to get metric '{metricName}'.");
throw new TimeoutException();
}
Time.Sleep(TimeSpan.FromSeconds(2));
}
}
private MetricsSet? GetMostRecent(string metricName)
{
var result = query.GetMostRecent(metricName, node);
if (result == null) return null;
return result.Sets.LastOrDefault();
}
}
}
@@ -1,33 +0,0 @@
using KubernetesWorkflow;
namespace DistTestCore.Metrics
{
public interface IMetricsAccessFactory
{
IMetricsAccess CreateMetricsAccess(RunningContainer codexContainer);
}
public class MetricsUnavailableAccessFactory : IMetricsAccessFactory
{
public IMetricsAccess CreateMetricsAccess(RunningContainer codexContainer)
{
return new MetricsUnavailable();
}
}
public class CodexNodeMetricsAccessFactory : IMetricsAccessFactory
{
private readonly RunningContainers prometheusContainer;
public CodexNodeMetricsAccessFactory(RunningContainers prometheusContainer)
{
this.prometheusContainer = prometheusContainer;
}
public IMetricsAccess CreateMetricsAccess(RunningContainer codexContainer)
{
var query = new MetricsQuery(prometheusContainer);
return new MetricsAccess(query, codexContainer);
}
}
}
@@ -1,19 +0,0 @@
using KubernetesWorkflow;
namespace DistTestCore.Metrics
{
public class PrometheusContainerRecipe : ContainerRecipeFactory
{
public const string DockerImage = "thatbenbierens/prometheus-envconf:latest";
protected override string Image => DockerImage;
protected override void Initialize(StartupConfig startupConfig)
{
var config = startupConfig.Get<PrometheusStartupConfig>();
AddExposedPortAndVar("PROM_PORT");
AddEnvVar("PROM_CONFIG", config.PrometheusConfigBase64);
}
}
}
-133
View File
@@ -1,133 +0,0 @@
using DistTestCore.Codex;
using DistTestCore.Logs;
using DistTestCore.Marketplace;
using DistTestCore.Metrics;
using NUnit.Framework;
namespace DistTestCore
{
public interface IOnlineCodexNode
{
CodexDebugResponse GetDebugInfo();
ContentId UploadFile(TestFile file);
TestFile? DownloadContent(ContentId contentId);
void ConnectToPeer(IOnlineCodexNode node);
ICodexNodeLog DownloadLog();
IMetricsAccess Metrics { get; }
IMarketplaceAccess Marketplace { get; }
}
public class OnlineCodexNode : IOnlineCodexNode
{
private const string SuccessfullyConnectedMessage = "Successfully connected to peer";
private const string UploadFailedMessage = "Unable to store block";
private readonly TestLifecycle lifecycle;
public OnlineCodexNode(TestLifecycle lifecycle, CodexAccess codexAccess, CodexNodeGroup group, IMetricsAccess metricsAccess, IMarketplaceAccess marketplaceAccess)
{
this.lifecycle = lifecycle;
CodexAccess = codexAccess;
Group = group;
Metrics = metricsAccess;
Marketplace = marketplaceAccess;
}
public CodexAccess CodexAccess { get; }
public CodexNodeGroup Group { get; }
public IMetricsAccess Metrics { get; }
public IMarketplaceAccess Marketplace { get; }
public string GetName()
{
return $"<{CodexAccess.Container.Recipe.Name}>";
}
public CodexDebugResponse GetDebugInfo()
{
var response = CodexAccess.GetDebugInfo();
Log($"Got DebugInfo with id: '{response.id}'.");
return response;
}
public ContentId UploadFile(TestFile file)
{
Log($"Uploading file of size {file.GetFileSize()}...");
using var fileStream = File.OpenRead(file.Filename);
var response = CodexAccess.UploadFile(fileStream);
if (response.StartsWith(UploadFailedMessage))
{
Assert.Fail("Node failed to store block.");
}
Log($"Uploaded file. Received contentId: '{response}'.");
return new ContentId(response);
}
public TestFile? DownloadContent(ContentId contentId)
{
Log($"Downloading for contentId: '{contentId.Id}'...");
var file = lifecycle.FileManager.CreateEmptyTestFile();
DownloadToFile(contentId.Id, file);
Log($"Downloaded file of size {file.GetFileSize()} to '{file.Filename}'.");
return file;
}
public void ConnectToPeer(IOnlineCodexNode node)
{
var peer = (OnlineCodexNode)node;
Log($"Connecting to peer {peer.GetName()}...");
var peerInfo = node.GetDebugInfo();
var response = CodexAccess.ConnectToPeer(peerInfo.id, GetPeerMultiAddress(peer, peerInfo));
Assert.That(response, Is.EqualTo(SuccessfullyConnectedMessage), "Unable to connect codex nodes.");
Log($"Successfully connected to peer {peer.GetName()}.");
}
public ICodexNodeLog DownloadLog()
{
return lifecycle.DownloadLog(this);
}
public string Describe()
{
return $"({Group.Describe()} contains {GetName()})";
}
private string GetPeerMultiAddress(OnlineCodexNode peer, CodexDebugResponse peerInfo)
{
var multiAddress = peerInfo.addrs.First();
// Todo: Is there a case where First address in list is not the way?
if (Group == peer.Group)
{
return multiAddress;
}
// The peer we want to connect is in a different pod.
// We must replace the default IP with the pod IP in the multiAddress.
return multiAddress.Replace("0.0.0.0", peer.Group.Containers.RunningPod.Ip);
}
private void DownloadToFile(string contentId, TestFile file)
{
using var fileStream = File.OpenWrite(file.Filename);
using var downloadStream = CodexAccess.DownloadFile(contentId);
downloadStream.CopyTo(fileStream);
}
private void Log(string msg)
{
lifecycle.Log.Log($"{GetName()}: {msg}");
}
}
public class ContentId
{
public ContentId(string id)
{
Id = id;
}
public string Id { get; }
}
}
-65
View File
@@ -1,65 +0,0 @@
using DistTestCore.Codex;
using DistTestCore.Metrics;
using KubernetesWorkflow;
using System.Text;
namespace DistTestCore
{
public class PrometheusStarter
{
private readonly TestLifecycle lifecycle;
private readonly WorkflowCreator workflowCreator;
public PrometheusStarter(TestLifecycle lifecycle, WorkflowCreator workflowCreator)
{
this.lifecycle = lifecycle;
this.workflowCreator = workflowCreator;
}
public IMetricsAccessFactory CollectMetricsFor(CodexSetup codexSetup, RunningContainers containers)
{
if (!codexSetup.MetricsEnabled) return new MetricsUnavailableAccessFactory();
Log($"Starting metrics server for {containers.Describe()}");
var startupConfig = new StartupConfig();
startupConfig.Add(new PrometheusStartupConfig(GeneratePrometheusConfig(containers.Containers)));
var workflow = workflowCreator.CreateWorkflow();
var runningContainers = workflow.Start(1, Location.Unspecified, new PrometheusContainerRecipe(), startupConfig);
if (runningContainers.Containers.Length != 1) throw new InvalidOperationException("Expected only 1 Prometheus container to be created.");
Log("Metrics server started.");
return new CodexNodeMetricsAccessFactory(runningContainers);
}
private string GeneratePrometheusConfig(RunningContainer[] nodes)
{
var config = "";
config += "global:\n";
config += " scrape_interval: 30s\n";
config += " scrape_timeout: 10s\n";
config += "\n";
config += "scrape_configs:\n";
config += " - job_name: services\n";
config += " metrics_path: /metrics\n";
config += " static_configs:\n";
config += " - targets:\n";
foreach (var node in nodes)
{
var ip = node.Pod.Ip;
var port = node.Recipe.GetPortByTag(CodexContainerRecipe.MetricsPortTag).Number;
config += $" - '{ip}:{port}'\n";
}
var bytes = Encoding.ASCII.GetBytes(config);
return Convert.ToBase64String(bytes);
}
private void Log(string msg)
{
lifecycle.Log.Log(msg);
}
}
}
-19
View File
@@ -1,19 +0,0 @@
using Logging;
using Utils;
namespace DistTestCore
{
public class Stopwatch
{
public static void Measure(BaseLog log, string name, Action action)
{
var start = DateTime.UtcNow;
action();
var duration = DateTime.UtcNow - start;
log.Log($"{name} ({Time.FormatDuration(duration)})");
}
}
}
-46
View File
@@ -1,46 +0,0 @@
using DistTestCore.Logs;
using KubernetesWorkflow;
using Logging;
namespace DistTestCore
{
public class TestLifecycle
{
private readonly WorkflowCreator workflowCreator;
public TestLifecycle(TestLog log, Configuration configuration)
{
Log = log;
workflowCreator = new WorkflowCreator(configuration.GetK8sConfiguration());
FileManager = new FileManager(Log, configuration);
CodexStarter = new CodexStarter(this, workflowCreator);
PrometheusStarter = new PrometheusStarter(this, workflowCreator);
GethStarter = new GethStarter(this, workflowCreator);
}
public TestLog Log { get; }
public FileManager FileManager { get; }
public CodexStarter CodexStarter { get; }
public PrometheusStarter PrometheusStarter { get; }
public GethStarter GethStarter { get; }
public void DeleteAllResources()
{
CodexStarter.DeleteAllResources();
FileManager.DeleteAllTestFiles();
}
public ICodexNodeLog DownloadLog(OnlineCodexNode node)
{
var subFile = Log.CreateSubfile();
var description = node.Describe();
var handler = new LogDownloadHandler(description, subFile);
Log.Log($"Downloading logs for {description} to file '{subFile.FullFilename}'");
CodexStarter.DownloadLog(node.CodexAccess.Container, handler);
return new CodexNodeLog(subFile);
}
}
}
-132
View File
@@ -1,132 +0,0 @@
using NUnit.Framework;
using Utils;
namespace DistTestCore
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class UseLongTimeoutsAttribute : PropertyAttribute
{
public UseLongTimeoutsAttribute()
: base(Timing.UseLongTimeoutsKey)
{
}
}
public static class Timing
{
public const string UseLongTimeoutsKey = "UseLongTimeouts";
public static TimeSpan HttpCallTimeout()
{
return GetTimes().HttpCallTimeout();
}
public static int HttpCallRetryCount()
{
return GetTimes().HttpCallRetryCount();
}
public static void HttpCallRetryDelay()
{
Time.Sleep(GetTimes().HttpCallRetryDelay());
}
public static TimeSpan K8sServiceDelay()
{
return GetTimes().WaitForK8sServiceDelay();
}
public static TimeSpan K8sOperationTimeout()
{
return GetTimes().K8sOperationTimeout();
}
public static TimeSpan WaitForMetricTimeout()
{
return GetTimes().WaitForMetricTimeout();
}
private static ITimeSet GetTimes()
{
var testProperties = TestContext.CurrentContext.Test.Properties;
if (testProperties.ContainsKey(UseLongTimeoutsKey)) return new LongTimeSet();
return new DefaultTimeSet();
}
}
public interface ITimeSet
{
TimeSpan HttpCallTimeout();
int HttpCallRetryCount();
TimeSpan HttpCallRetryDelay();
TimeSpan WaitForK8sServiceDelay();
TimeSpan K8sOperationTimeout();
TimeSpan WaitForMetricTimeout();
}
public class DefaultTimeSet : ITimeSet
{
public TimeSpan HttpCallTimeout()
{
return TimeSpan.FromSeconds(10);
}
public int HttpCallRetryCount()
{
return 5;
}
public TimeSpan HttpCallRetryDelay()
{
return TimeSpan.FromSeconds(3);
}
public TimeSpan WaitForK8sServiceDelay()
{
return TimeSpan.FromSeconds(1);
}
public TimeSpan K8sOperationTimeout()
{
return TimeSpan.FromMinutes(5);
}
public TimeSpan WaitForMetricTimeout()
{
return TimeSpan.FromSeconds(30);
}
}
public class LongTimeSet : ITimeSet
{
public TimeSpan HttpCallTimeout()
{
return TimeSpan.FromHours(2);
}
public int HttpCallRetryCount()
{
return 2;
}
public TimeSpan HttpCallRetryDelay()
{
return TimeSpan.FromMinutes(5);
}
public TimeSpan WaitForK8sServiceDelay()
{
return TimeSpan.FromSeconds(10);
}
public TimeSpan K8sOperationTimeout()
{
return TimeSpan.FromMinutes(15);
}
public TimeSpan WaitForMetricTimeout()
{
return TimeSpan.FromMinutes(5);
}
}
}
+130
View File
@@ -0,0 +1,130 @@
using System.Reflection;
namespace ArgsUniform
{
public class ArgsUniform<T>
{
private readonly Assigner<T> assigner;
private readonly Action printAppInfo;
private readonly string[] args;
private const int cliStart = 8;
private const int shortStart = 38;
private const int envStart = 48;
private const int descStart = 80;
public ArgsUniform(Action printAppInfo, params string[] args)
: this(printAppInfo, new IEnv.Env(), args)
{
}
public ArgsUniform(Action printAppInfo, object defaultsProvider, params string[] args)
: this(printAppInfo, defaultsProvider, new IEnv.Env(), args)
{
}
public ArgsUniform(Action printAppInfo, IEnv.IEnv env, params string[] args)
: this(printAppInfo, null!, env, args)
{
}
public ArgsUniform(Action printAppInfo, object defaultsProvider, IEnv.IEnv env, params string[] args)
{
this.printAppInfo = printAppInfo;
this.args = args;
assigner = new Assigner<T>(env, args, defaultsProvider);
}
public T Parse(bool printResult = false)
{
if (args.Any(a => a == "-h" || a == "--help" || a == "-?"))
{
printAppInfo();
PrintHelp();
Environment.Exit(0);
}
var result = Activator.CreateInstance<T>();
var uniformProperties = typeof(T).GetProperties().Where(m => m.GetCustomAttributes(typeof(UniformAttribute), false).Length == 1).ToArray();
var missingRequired = new List<PropertyInfo>();
foreach (var uniformProperty in uniformProperties)
{
var attr = uniformProperty.GetCustomAttribute<UniformAttribute>();
if (attr != null)
{
if (!assigner.UniformAssign(result, attr, uniformProperty) && attr.Required)
{
missingRequired.Add(uniformProperty);
}
}
}
if (missingRequired.Any())
{
PrintResults(printResult,result, uniformProperties);
Print("");
foreach (var missing in missingRequired)
{
var attr = missing.GetCustomAttribute<UniformAttribute>()!;
var exampleArg = $"--{attr.Arg}=...";
var exampleEnvVar = $"{attr.EnvVar}=...";
Print($" ! Missing required input. Use argument: '{exampleArg}' or environment variable: '{exampleEnvVar}'.");
}
PrintHelp();
Environment.Exit(1);
}
PrintResults(printResult, result, uniformProperties);
return result;
}
public void PrintHelp()
{
Print("");
PrintAligned("CLI option:", "(short)", "Environment variable:", "Description", "(default)");
var props = typeof(T).GetProperties().Where(m => m.GetCustomAttributes(typeof(UniformAttribute), false).Length == 1).ToArray();
foreach (var prop in props)
{
var a = prop.GetCustomAttribute<UniformAttribute>();
if (a != null)
{
var optional = !a.Required ? " (optional)" : "";
var def = assigner.DescribeDefaultFor(prop);
PrintAligned($"--{a.Arg}=...", $"({a.ArgShort})", a.EnvVar, a.Description + optional, $"({def})");
}
}
Print("");
}
private void PrintResults(bool printResult, T result, PropertyInfo[] uniformProperties)
{
if (!printResult) return;
Print("");
foreach (var p in uniformProperties)
{
Print($"\t{p.Name} = {p.GetValue(result)}");
}
Print("");
}
private void Print(string msg)
{
Console.WriteLine(msg);
}
private void PrintAligned(string cli, string s, string env, string desc, string def)
{
Console.CursorLeft = cliStart;
Console.Write(cli);
Console.CursorLeft = shortStart;
Console.Write(s);
Console.CursorLeft = envStart;
Console.Write(env);
Console.CursorLeft = descStart;
Console.Write(desc + " ");
Console.Write(def + Environment.NewLine);
}
}
}
+13
View File
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="I-Env" Version="1.0.0" />
</ItemGroup>
</Project>
+186
View File
@@ -0,0 +1,186 @@
using System.Globalization;
using System.Numerics;
using System.Reflection;
namespace ArgsUniform
{
public class Assigner<T>
{
private readonly IEnv.IEnv env;
private readonly string[] args;
private readonly object? defaultsProvider;
public Assigner(IEnv.IEnv env, string[] args, object? defaultsProvider)
{
this.env = env;
this.args = args;
this.defaultsProvider = defaultsProvider;
}
public bool UniformAssign(T result, UniformAttribute attr, PropertyInfo uniformProperty)
{
if (AssignFromArgsIfAble(result, attr, uniformProperty)) return true;
if (AssignFromEnvVarIfAble(result, attr, uniformProperty)) return true;
if (AssignFromDefaultsIfAble(result, uniformProperty)) return true;
return false;
}
public string DescribeDefaultFor(PropertyInfo property)
{
var obj = Activator.CreateInstance<T>();
var defaultValue = GetDefaultValue(obj, property);
if (defaultValue == null) return "";
if (defaultValue is string str)
{
return "\"" + str + "\"";
}
return defaultValue.ToString() ?? string.Empty;
}
private object? GetDefaultValue(T result, PropertyInfo uniformProperty)
{
// Get value from object's static initializer if it's there.
var currentValue = uniformProperty.GetValue(result);
if (currentValue != null) return currentValue;
// Get value from defaults-provider object if it's there.
if (defaultsProvider == null) return null;
var defaultProperty = defaultsProvider.GetType().GetProperties().SingleOrDefault(p => p.Name == uniformProperty.Name);
if (defaultProperty == null) return null;
return defaultProperty.GetValue(defaultsProvider);
}
private bool AssignFromDefaultsIfAble(T result, PropertyInfo uniformProperty)
{
var defaultValue = GetDefaultValue(result, uniformProperty);
var isEmptryString = (defaultValue as string) == string.Empty;
if (defaultValue != null && defaultValue != GetDefaultValueForType(uniformProperty.PropertyType) && !isEmptryString)
{
return Assign(result, uniformProperty, defaultValue);
}
return false;
}
private bool AssignFromEnvVarIfAble(T result, UniformAttribute attr, PropertyInfo uniformProperty)
{
var e = env.GetEnvVarOrDefault(attr.EnvVar, string.Empty);
if (!string.IsNullOrEmpty(e))
{
return Assign(result, uniformProperty, e);
}
return false;
}
private bool AssignFromArgsIfAble(T result, UniformAttribute attr, PropertyInfo uniformProperty)
{
var fromArg = GetFromArgs(attr.Arg);
if (fromArg != null)
{
return Assign(result, uniformProperty, fromArg);
}
var fromShort = GetFromArgs(attr.ArgShort);
if (fromShort != null)
{
return Assign(result, uniformProperty, fromShort);
}
return false;
}
private bool Assign(T result, PropertyInfo uniformProperty, object value)
{
if (uniformProperty.PropertyType == value.GetType())
{
uniformProperty.SetValue(result, value);
return true;
}
else
{
if (uniformProperty.PropertyType == typeof(string) || uniformProperty.PropertyType == typeof(int))
{
uniformProperty.SetValue(result, Convert.ChangeType(value, uniformProperty.PropertyType));
return true;
}
else
{
if (uniformProperty.PropertyType == typeof(int?)) return AssignOptionalInt(result, uniformProperty, value);
if (uniformProperty.PropertyType.IsEnum) return AssignEnum(result, uniformProperty, value);
if (uniformProperty.PropertyType == typeof(bool)) return AssignBool(result, uniformProperty, value);
if (uniformProperty.PropertyType == typeof(ulong)) return AssignUlong(result, uniformProperty, value);
if (uniformProperty.PropertyType == typeof(BigInteger)) return AssignBigInt(result, uniformProperty, value);
throw new NotSupportedException(
$"Unsupported property type '${uniformProperty.PropertyType}' " +
$"for property '${uniformProperty.Name}'.");
}
}
}
private static bool AssignEnum(T result, PropertyInfo uniformProperty, object value)
{
var s = value.ToString();
if (Enum.TryParse(uniformProperty.PropertyType, s, out var e))
{
uniformProperty.SetValue(result, e);
return true;
}
return false;
}
private static bool AssignOptionalInt(T result, PropertyInfo uniformProperty, object value)
{
if (int.TryParse(value.ToString(), CultureInfo.InvariantCulture, out int i))
{
uniformProperty.SetValue(result, i);
return true;
}
return false;
}
private bool AssignUlong(T? result, PropertyInfo uniformProperty, object value)
{
if (ulong.TryParse(value.ToString(), CultureInfo.InvariantCulture, out ulong i))
{
uniformProperty.SetValue(result, i);
return true;
}
return false;
}
private bool AssignBigInt(T result, PropertyInfo uniformProperty, object value)
{
if (BigInteger.TryParse(value.ToString(), CultureInfo.InvariantCulture, out BigInteger i))
{
uniformProperty.SetValue(result, i);
return true;
}
return false;
}
private static bool AssignBool(T result, PropertyInfo uniformProperty, object value)
{
var s = value.ToString();
if (s == "1" || (s != null && s.ToLowerInvariant() == "true"))
{
uniformProperty.SetValue(result, true);
}
return true;
}
private string? GetFromArgs(string key)
{
var argKey = $"--{key}=";
var arg = args.FirstOrDefault(a => a.StartsWith(argKey));
if (arg != null)
{
return arg.Substring(argKey.Length);
}
return null;
}
private static object GetDefaultValueForType(Type t)
{
if (t.IsValueType) return Activator.CreateInstance(t)!;
return null!;
}
}
}
+34
View File
@@ -0,0 +1,34 @@
namespace ArgsUniform
{
public class ExampleUser
{
public class Args
{
[Uniform("aaa", "a", "AAA", false, "Sets the AAA!")]
public string Aaa { get; set; } = string.Empty;
[Uniform("bbb", "b", "BBB", true, "Sets that BBB")]
public string Bbb { get; set; } = string.Empty;
}
public class DefaultsProvider
{
public string Aaa { get { return "non-static operation"; } }
}
public void Example()
{
// env var: "AAA=BBB"
var args = "--ccc=ddd";
var uniform = new ArgsUniform<Args>(PrintHelp, new DefaultsProvider(), args);
var aaa = uniform.Parse();
}
private static void PrintHelp()
{
Console.WriteLine("Help text!");
}
}
}
+20
View File
@@ -0,0 +1,20 @@
namespace ArgsUniform
{
public class UniformAttribute : Attribute
{
public UniformAttribute(string arg, string argShort, string envVar, bool required, string description)
{
Arg = arg;
ArgShort = argShort;
EnvVar = envVar;
Required = required;
Description = description;
}
public string Arg { get; }
public string ArgShort { get; }
public string EnvVar { get; }
public bool Required { get; }
public string Description { get; }
}
}
+14
View File
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\FileUtils\FileUtils.csproj" />
<ProjectReference Include="..\KubernetesWorkflow\KubernetesWorkflow.csproj" />
</ItemGroup>
</Project>
+56
View File
@@ -0,0 +1,56 @@
using KubernetesWorkflow;
using KubernetesWorkflow.Types;
namespace Core
{
public sealed class CoreInterface
{
private readonly EntryPoint entryPoint;
internal CoreInterface(EntryPoint entryPoint)
{
this.entryPoint = entryPoint;
}
public T GetPlugin<T>() where T : IProjectPlugin
{
return entryPoint.GetPlugin<T>();
}
public IKnownLocations GetKnownLocations()
{
return entryPoint.Tools.CreateWorkflow().GetAvailableLocations();
}
public IDownloadedLog DownloadLog(IHasContainer containerSource, int? tailLines = null)
{
return DownloadLog(containerSource.Container, tailLines);
}
public IDownloadedLog DownloadLog(RunningContainer container, int? tailLines = null)
{
var workflow = entryPoint.Tools.CreateWorkflow();
var msg = $"Downloading container log for '{container.Name}'";
entryPoint.Tools.GetLog().Log(msg);
var logHandler = new WriteToFileLogHandler(entryPoint.Tools.GetLog(), msg);
workflow.DownloadContainerLog(container, logHandler, tailLines);
return new DownloadedLog(logHandler);
}
public string ExecuteContainerCommand(IHasContainer containerSource, string command, params string[] args)
{
return ExecuteContainerCommand(containerSource.Container, command, args);
}
public string ExecuteContainerCommand(RunningContainer container, string command, params string[] args)
{
var workflow = entryPoint.Tools.CreateWorkflow();
return workflow.ExecuteCommand(container, command, args);
}
}
public interface IHasContainer
{
RunningContainer Container { get; }
}
}
+80
View File
@@ -0,0 +1,80 @@
using KubernetesWorkflow;
using Logging;
namespace Core
{
public interface IDownloadedLog
{
void IterateLines(Action<string> action);
string[] GetLinesContaining(string expectedString);
string[] FindLinesThatContain(params string[] tags);
void DeleteFile();
}
internal class DownloadedLog : IDownloadedLog
{
private readonly LogFile logFile;
internal DownloadedLog(WriteToFileLogHandler logHandler)
{
logFile = logHandler.LogFile;
}
public void IterateLines(Action<string> action)
{
using var file = File.OpenRead(logFile.FullFilename);
using var streamReader = new StreamReader(file);
var line = streamReader.ReadLine();
while (line != null)
{
action(line);
line = streamReader.ReadLine();
}
}
public string[] GetLinesContaining(string expectedString)
{
using var file = File.OpenRead(logFile.FullFilename);
using var streamReader = new StreamReader(file);
var lines = new List<string>();
var line = streamReader.ReadLine();
while (line != null)
{
if (line.Contains(expectedString))
{
lines.Add(line);
}
line = streamReader.ReadLine();
}
return lines.ToArray(); ;
}
public string[] FindLinesThatContain(params string[] tags)
{
var result = new List<string>();
using var file = File.OpenRead(logFile.FullFilename);
using var streamReader = new StreamReader(file);
var line = streamReader.ReadLine();
while (line != null)
{
if (tags.All(line.Contains))
{
result.Add(line);
}
line = streamReader.ReadLine();
}
return result.ToArray();
}
public void DeleteFile()
{
File.Delete(logFile.FullFilename);
}
}
}
+193
View File
@@ -0,0 +1,193 @@
using Logging;
using Newtonsoft.Json;
using Serialization = Newtonsoft.Json.Serialization;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using Utils;
namespace Core
{
public interface IEndpoint
{
string HttpGetString(string route);
T HttpGetJson<T>(string route);
TResponse HttpPostJson<TRequest, TResponse>(string route, TRequest body);
string HttpPostJson<TRequest>(string route, TRequest body);
TResponse HttpPostString<TResponse>(string route, string body);
string HttpPostStream(string route, Stream stream);
Stream HttpGetStream(string route);
T Deserialize<T>(string json);
}
internal class Endpoint : IEndpoint
{
private readonly ILog log;
private readonly IHttp http;
private readonly Address address;
private readonly string baseUrl;
private readonly string? logAlias;
public Endpoint(ILog log, IHttp http, Address address, string baseUrl, string? logAlias)
{
this.log = log;
this.http = http;
this.address = address;
this.baseUrl = baseUrl;
this.logAlias = logAlias;
}
public string HttpGetString(string route)
{
return http.OnClient(client =>
{
return GetString(client, route);
}, $"HTTP-GET:{route}");
}
public T HttpGetJson<T>(string route)
{
return http.OnClient(client =>
{
var json = GetString(client, route);
return Deserialize<T>(json);
}, $"HTTP-GET:{route}");
}
public TResponse HttpPostJson<TRequest, TResponse>(string route, TRequest body)
{
return http.OnClient(client =>
{
var response = PostJson(client, route, body);
var json = Time.Wait(response.Content.ReadAsStringAsync());
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(json);
}
Log(GetUrl() + route, json);
return Deserialize<TResponse>(json);
}, $"HTTP-POST-JSON: {route}");
}
public string HttpPostJson<TRequest>(string route, TRequest body)
{
return http.OnClient(client =>
{
var response = PostJson(client, route, body);
return Time.Wait(response.Content.ReadAsStringAsync());
}, $"HTTP-POST-JSON: {route}");
}
public TResponse HttpPostString<TResponse>(string route, string body)
{
return http.OnClient(client =>
{
var response = PostJsonString(client, route, body);
if (response == null) throw new Exception("Received no response.");
var result = Deserialize<TResponse>(response);
if (result == null) throw new Exception("Failed to deserialize response");
return result;
}, $"HTTO-POST-JSON: {route}");
}
public string HttpPostStream(string route, Stream stream)
{
return http.OnClient(client =>
{
var url = GetUrl() + route;
Log(url, "~ STREAM ~");
var content = new StreamContent(stream);
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
var response = Time.Wait(client.PostAsync(url, content));
var str = Time.Wait(response.Content.ReadAsStringAsync());
Log(url, str);
return str;
}, $"HTTP-POST-STREAM: {route}");
}
public Stream HttpGetStream(string route)
{
return http.OnClient(client =>
{
var url = GetUrl() + route;
Log(url, "~ STREAM ~");
return Time.Wait(client.GetStreamAsync(url));
}, $"HTTP-GET-STREAM: {route}");
}
public T Deserialize<T>(string json)
{
var errors = new List<string>();
var deserialized = JsonConvert.DeserializeObject<T>(json, new JsonSerializerSettings()
{
Error = delegate (object? sender, Serialization.ErrorEventArgs args)
{
if (args.CurrentObject == args.ErrorContext.OriginalObject)
{
errors.Add($"""
Member: '{args.ErrorContext.Member?.ToString() ?? "<null>"}'
Path: {args.ErrorContext.Path}
Error: {args.ErrorContext.Error.Message}
""");
args.ErrorContext.Handled = true;
}
}
});
if (errors.Count > 0)
{
throw new JsonSerializationException($"Failed to deserialize JSON '{json}' with exception(s): \n{string.Join("\n", errors)}");
}
else if (deserialized == null)
{
throw new JsonSerializationException($"Failed to deserialize JSON '{json}': resulting deserialized object is null");
}
return deserialized;
}
private string GetString(HttpClient client, string route)
{
var url = GetUrl() + route;
Log(url, "");
var result = Time.Wait(client.GetAsync(url));
var str = Time.Wait(result.Content.ReadAsStringAsync());
Log(url, str);
return str;
}
private HttpResponseMessage PostJson<TRequest>(HttpClient client, string route, TRequest body)
{
var url = GetUrl() + route;
using var content = JsonContent.Create(body);
Log(url, JsonConvert.SerializeObject(body));
return Time.Wait(client.PostAsync(url, content));
}
private string PostJsonString(HttpClient client, string route, string body)
{
var url = GetUrl() + route;
Log(url, body);
var content = new StringContent(body);
content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
var result = Time.Wait(client.PostAsync(url, content));
var str = Time.Wait(result.Content.ReadAsStringAsync());
Log(url, str);
return str;
}
private string GetUrl()
{
return $"{address.Host}:{address.Port}{baseUrl}";
}
private void Log(string url, string message)
{
if (logAlias != null)
{
log.Debug($"({logAlias})({url}) = '{message}'", 3);
}
else
{
log.Debug($"({url}) = '{message}'", 3);
}
}
}
}
+56
View File
@@ -0,0 +1,56 @@
using KubernetesWorkflow;
using Logging;
namespace Core
{
public class EntryPoint
{
private readonly IToolsFactory toolsFactory;
private readonly PluginManager manager = new PluginManager();
public EntryPoint(ILog log, Configuration configuration, string fileManagerRootFolder, ITimeSet timeSet)
{
toolsFactory = new ToolsFactory(log, configuration, fileManagerRootFolder, timeSet);
Tools = toolsFactory.CreateTools();
manager.InstantiatePlugins(PluginFinder.GetPluginTypes(), toolsFactory);
}
public EntryPoint(ILog log, Configuration configuration, string fileManagerRootFolder)
: this(log, configuration, fileManagerRootFolder, new DefaultTimeSet())
{
}
public IPluginTools Tools { get; }
public void Announce()
{
manager.AnnouncePlugins();
}
public Dictionary<string, string> GetPluginMetadata()
{
return manager.GatherPluginMetadata().Get();
}
public CoreInterface CreateInterface()
{
return new CoreInterface(this);
}
/// <summary>
/// Deletes kubernetes and tracked file resources.
/// when `waitTillDone` is true, this function will block until resources are deleted.
/// </summary>
public void Decommission(bool deleteKubernetesResources, bool deleteTrackedFiles, bool waitTillDone)
{
manager.DecommissionPlugins(deleteKubernetesResources, deleteTrackedFiles, waitTillDone);
Tools.Decommission(deleteKubernetesResources, deleteTrackedFiles, waitTillDone);
}
internal T GetPlugin<T>() where T : IProjectPlugin
{
return manager.GetPlugin<T>();
}
}
}
+84
View File
@@ -0,0 +1,84 @@
using Logging;
using Utils;
namespace Core
{
public interface IHttp
{
T OnClient<T>(Func<HttpClient, T> action);
T OnClient<T>(Func<HttpClient, T> action, string description);
T OnClient<T>(Func<HttpClient, T> action, Retry retry);
IEndpoint CreateEndpoint(Address address, string baseUrl, string? logAlias = null);
}
internal class Http : IHttp
{
private static readonly object httpLock = new object();
private readonly ILog log;
private readonly ITimeSet timeSet;
private readonly Action<HttpClient> onClientCreated;
internal Http(ILog log, ITimeSet timeSet)
: this(log, timeSet, DoNothing)
{
}
internal Http(ILog log, ITimeSet timeSet, Action<HttpClient> onClientCreated)
{
this.log = log;
this.timeSet = timeSet;
this.onClientCreated = onClientCreated;
}
public T OnClient<T>(Func<HttpClient, T> action)
{
return OnClient(action, GetDescription());
}
public T OnClient<T>(Func<HttpClient, T> action, string description)
{
var retry = new Retry(description, timeSet.HttpRetryTimeout(), timeSet.HttpCallRetryDelay(), f => { });
return OnClient(action, retry);
}
public T OnClient<T>(Func<HttpClient, T> action, Retry retry)
{
var client = GetClient();
return LockRetry(() =>
{
return action(client);
}, retry);
}
public IEndpoint CreateEndpoint(Address address, string baseUrl, string? logAlias = null)
{
return new Endpoint(log, this, address, baseUrl, logAlias);
}
private string GetDescription()
{
return DebugStack.GetCallerName(skipFrames: 2);
}
private T LockRetry<T>(Func<T> operation, Retry retry)
{
lock (httpLock)
{
return retry.Run(operation);
}
}
private HttpClient GetClient()
{
var client = new HttpClient();
client.Timeout = timeSet.HttpCallTimeout();
onClientCreated(client);
return client;
}
private static void DoNothing(HttpClient client)
{
}
}
}
+46
View File
@@ -0,0 +1,46 @@
using System.Reflection;
namespace Core
{
internal static class PluginFinder
{
private static Type[]? pluginTypes = null;
internal static Type[] GetPluginTypes()
{
if (pluginTypes != null) return pluginTypes;
// Reflection can be costly. Do this only once.
FindAndLoadPluginAssemblies();
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
pluginTypes = assemblies.SelectMany(a => a.GetTypes().Where(t =>
typeof(IProjectPlugin).IsAssignableFrom(t) &&
!t.IsAbstract)
).ToArray();
return pluginTypes;
}
private static void FindAndLoadPluginAssemblies()
{
var files = Directory.GetFiles(".");
foreach (var file in files)
{
var f = file.ToLowerInvariant();
if (f.Contains("plugin") && f.EndsWith("dll"))
{
var name = Path.GetFileNameWithoutExtension(file);
try
{
Assembly.Load(name);
}
catch (Exception ex)
{
throw new Exception($"Failed to load plugin from file '{name}'.", ex);
}
}
}
}
}
}
+78
View File
@@ -0,0 +1,78 @@
namespace Core
{
internal class PluginManager
{
private readonly List<PluginToolsPair> pairs = new List<PluginToolsPair>();
internal void InstantiatePlugins(Type[] pluginTypes, IToolsFactory provider)
{
pairs.Clear();
foreach (var pluginType in pluginTypes)
{
var tools = provider.CreateTools();
var plugin = InstantiatePlugins(pluginType, tools);
ApplyLogPrefix(plugin, tools);
}
}
internal void AnnouncePlugins()
{
foreach (var pair in pairs) pair.Plugin.Announce();
}
internal PluginMetadata GatherPluginMetadata()
{
var metadata = new PluginMetadata();
foreach (var pair in pairs)
{
if (pair.Plugin is IHasMetadata m)
{
m.AddMetadata(metadata);
}
}
return metadata;
}
internal void DecommissionPlugins(bool deleteKubernetesResources, bool deleteTrackedFiles, bool waitTillDone)
{
foreach (var pair in pairs)
{
pair.Plugin.Decommission();
pair.Tools.Decommission(deleteKubernetesResources, deleteTrackedFiles, waitTillDone);
}
}
internal T GetPlugin<T>() where T : IProjectPlugin
{
return (T)pairs.Single(p => p.Plugin.GetType() == typeof(T)).Plugin;
}
private IProjectPlugin InstantiatePlugins(Type pluginType, PluginTools tools)
{
var plugin = (IProjectPlugin)Activator.CreateInstance(pluginType, args: tools)!;
pairs.Add(new PluginToolsPair(plugin, tools));
return plugin;
}
private void ApplyLogPrefix(IProjectPlugin plugin, PluginTools tools)
{
if (plugin is IHasLogPrefix hasLogPrefix)
{
tools.ApplyLogPrefix(hasLogPrefix.LogPrefix);
}
}
private class PluginToolsPair
{
public PluginToolsPair(IProjectPlugin plugin, IPluginTools tools)
{
Plugin = plugin;
Tools = tools;
}
public IProjectPlugin Plugin { get; }
public IPluginTools Tools { get; }
}
}
}
+27
View File
@@ -0,0 +1,27 @@
namespace Core
{
internal interface IPluginMetadata
{
Dictionary<string, string> Get();
}
public interface IAddMetadata
{
void Add(string key, string value);
}
internal class PluginMetadata : IPluginMetadata, IAddMetadata
{
private readonly Dictionary<string, string> metadata = new Dictionary<string, string>();
public void Add(string key, string value)
{
metadata.Add(key, value);
}
public Dictionary<string, string> Get()
{
return new Dictionary<string, string>(metadata);
}
}
}
+97
View File
@@ -0,0 +1,97 @@
using FileUtils;
using KubernetesWorkflow;
using Logging;
namespace Core
{
public interface IPluginTools : IWorkflowTool, ILogTool, IHttpFactoryTool, IFileTool
{
ITimeSet TimeSet { get; }
/// <summary>
/// Deletes kubernetes and tracked file resources.
/// when `waitTillDone` is true, this function will block until resources are deleted.
/// </summary>
void Decommission(bool deleteKubernetesResources, bool deleteTrackedFiles, bool waitTillDone);
}
public interface IWorkflowTool
{
IStartupWorkflow CreateWorkflow(string? namespaceOverride = null);
}
public interface ILogTool
{
ILog GetLog();
}
public interface IHttpFactoryTool
{
IHttp CreateHttp(Action<HttpClient> onClientCreated);
IHttp CreateHttp(Action<HttpClient> onClientCreated, ITimeSet timeSet);
IHttp CreateHttp();
}
public interface IFileTool
{
IFileManager GetFileManager();
}
internal class PluginTools : IPluginTools
{
private readonly WorkflowCreator workflowCreator;
private readonly IFileManager fileManager;
private readonly LogPrefixer log;
internal PluginTools(ILog log, WorkflowCreator workflowCreator, string fileManagerRootFolder, ITimeSet timeSet)
{
this.log = new LogPrefixer(log);
this.workflowCreator = workflowCreator;
TimeSet = timeSet;
fileManager = new FileManager(log, fileManagerRootFolder);
}
public ITimeSet TimeSet { get; }
public void ApplyLogPrefix(string prefix)
{
log.Prefix = prefix;
}
public IHttp CreateHttp(Action<HttpClient> onClientCreated)
{
return CreateHttp(onClientCreated, TimeSet);
}
public IHttp CreateHttp(Action<HttpClient> onClientCreated, ITimeSet ts)
{
return new Http(log, ts, onClientCreated);
}
public IHttp CreateHttp()
{
return new Http(log, TimeSet);
}
public IStartupWorkflow CreateWorkflow(string? namespaceOverride = null)
{
return workflowCreator.CreateWorkflow(namespaceOverride);
}
public void Decommission(bool deleteKubernetesResources, bool deleteTrackedFiles, bool waitTillDone)
{
if (deleteKubernetesResources) CreateWorkflow().DeleteNamespace(waitTillDone);
if (deleteTrackedFiles) fileManager.DeleteAllFiles();
}
public IFileManager GetFileManager()
{
return fileManager;
}
public ILog GetLog()
{
return log;
}
}
}
+34
View File
@@ -0,0 +1,34 @@
using Utils;
namespace Core
{
public interface IProjectPlugin
{
void Announce();
void Decommission();
}
public interface IHasLogPrefix
{
string LogPrefix { get; }
}
public interface IHasMetadata
{
void AddMetadata(IAddMetadata metadata);
}
public static class ProjectPlugin
{
/// <summary>
/// On some platforms and in some cases, not all required plugin assemblies are automatically loaded into the app domain.
/// In this case, the runtime needs a slight push to load it before the EntryPoint class is instantiated.
/// Used ProjectPlugin.Load<>() before you create an EntryPoint to ensure all plugins you want to use are loaded.
/// </summary>
public static void Load<T>() where T : IProjectPlugin
{
var type = typeof(T);
FrameworkAssert.That(type != null, $"Unable to load plugin.");
}
}
}
+18
View File
@@ -0,0 +1,18 @@
using Newtonsoft.Json;
namespace Core
{
public static class SerializeGate
{
/// <summary>
/// SerializeGate was added to help ensure deployment objects are serializable and remain viable after deserialization.
/// Tools can be built on top of the core interface that rely on deployment objects being serializable.
/// Insert the serialization gate after deployment but before wrapping to ensure any future changes don't break this requirement.
/// </summary>
public static T Gate<T>(T anything)
{
var json = JsonConvert.SerializeObject(anything);
return JsonConvert.DeserializeObject<T>(json)!;
}
}
}
+88
View File
@@ -0,0 +1,88 @@
namespace Core
{
public interface ITimeSet
{
/// <summary>
/// Timeout for a single HTTP call.
/// </summary>
TimeSpan HttpCallTimeout();
/// <summary>
/// Maximum total time to attempt to make a successful HTTP call to a service.
/// When HTTP calls time out during this timespan, retries will be made.
/// </summary>
TimeSpan HttpRetryTimeout();
/// <summary>
/// After a failed HTTP call, wait this long before trying again.
/// </summary>
TimeSpan HttpCallRetryDelay();
/// <summary>
/// After a failed K8s operation, wait this long before trying again.
/// </summary>
TimeSpan K8sOperationRetryDelay();
/// <summary>
/// Maximum total time to attempt to perform a successful k8s operation.
/// If k8s operations fail during this timespan, retries will be made.
/// </summary>
TimeSpan K8sOperationTimeout();
}
public class DefaultTimeSet : ITimeSet
{
public TimeSpan HttpCallTimeout()
{
return TimeSpan.FromMinutes(3);
}
public TimeSpan HttpRetryTimeout()
{
return TimeSpan.FromMinutes(10);
}
public TimeSpan HttpCallRetryDelay()
{
return TimeSpan.FromSeconds(1);
}
public TimeSpan K8sOperationRetryDelay()
{
return TimeSpan.FromSeconds(10);
}
public TimeSpan K8sOperationTimeout()
{
return TimeSpan.FromMinutes(30);
}
}
public class LongTimeSet : ITimeSet
{
public TimeSpan HttpCallTimeout()
{
return TimeSpan.FromMinutes(30);
}
public TimeSpan HttpRetryTimeout()
{
return TimeSpan.FromHours(2.2);
}
public TimeSpan HttpCallRetryDelay()
{
return TimeSpan.FromSeconds(20);
}
public TimeSpan K8sOperationRetryDelay()
{
return TimeSpan.FromSeconds(30);
}
public TimeSpan K8sOperationTimeout()
{
return TimeSpan.FromHours(1);
}
}
}
+31
View File
@@ -0,0 +1,31 @@
using KubernetesWorkflow;
using Logging;
namespace Core
{
internal interface IToolsFactory
{
PluginTools CreateTools();
}
internal class ToolsFactory : IToolsFactory
{
private readonly ILog log;
private readonly WorkflowCreator workflowCreator;
private readonly string fileManagerRootFolder;
private readonly ITimeSet timeSet;
public ToolsFactory(ILog log, Configuration configuration, string fileManagerRootFolder, ITimeSet timeSet)
{
this.log = log;
workflowCreator = new WorkflowCreator(log, configuration);
this.fileManagerRootFolder = fileManagerRootFolder;
this.timeSet = timeSet;
}
public PluginTools CreateTools()
{
return new PluginTools(log, workflowCreator, fileManagerRootFolder, timeSet);
}
}
}
+21
View File
@@ -0,0 +1,21 @@
using Utils;
namespace DiscordRewards
{
public class CheckConfig
{
public CheckType Type { get; set; }
public ulong MinNumberOfHosts { get; set; }
public ByteSize MinSlotSize { get; set; } = 0.Bytes();
public TimeSpan MinDuration { get; set; } = TimeSpan.Zero;
}
public enum CheckType
{
Uninitialized,
HostFilledSlot,
HostFinishedSlot,
ClientPostedContract,
ClientStartedContract,
}
}
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Framework\Utils\Utils.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,31 @@
namespace DiscordRewards
{
public class GiveRewardsCommand
{
public RewardUsersCommand[] Rewards { get; set; } = Array.Empty<RewardUsersCommand>();
public MarketAverage[] Averages { get; set; } = Array.Empty<MarketAverage>();
public string[] EventsOverview { get; set; } = Array.Empty<string>();
public bool HasAny()
{
return Rewards.Any() || Averages.Any() || EventsOverview.Any();
}
}
public class RewardUsersCommand
{
public ulong RewardId { get; set; }
public string[] UserAddresses { get; set; } = Array.Empty<string>();
}
public class MarketAverage
{
public int NumberOfFinished { get; set; }
public int TimeRangeSeconds { get; set; }
public float Price { get; set; }
public float Size { get; set; }
public float Duration { get; set; }
public float Collateral { get; set; }
public float ProofProbability { get; set; }
}
}
+18
View File
@@ -0,0 +1,18 @@
namespace DiscordRewards
{
public class RewardConfig
{
public const string UsernameTag = "<USER>";
public RewardConfig(ulong roleId, string message, CheckConfig checkConfig)
{
RoleId = roleId;
Message = message;
CheckConfig = checkConfig;
}
public ulong RoleId { get; }
public string Message { get; }
public CheckConfig CheckConfig { get; }
}
}
+53
View File
@@ -0,0 +1,53 @@
namespace DiscordRewards
{
public class RewardRepo
{
private static string Tag => RewardConfig.UsernameTag;
public RewardConfig[] Rewards { get; } = new RewardConfig[0];
// Example configuration, from test server:
//{
// // Filled any slot
// new RewardConfig(1187039439558541498, $"{Tag} successfully filled their first slot!", new CheckConfig
// {
// Type = CheckType.HostFilledSlot
// }),
// // Finished any slot
// new RewardConfig(1202286165630390339, $"{Tag} successfully finished their first slot!", new CheckConfig
// {
// Type = CheckType.HostFinishedSlot
// }),
// // Finished a sizable slot
// new RewardConfig(1202286218738405418, $"{Tag} finished their first 1GB-24h slot! (10mb/5mins for test)", new CheckConfig
// {
// Type = CheckType.HostFinishedSlot,
// MinSlotSize = 10.MB(),
// MinDuration = TimeSpan.FromMinutes(5.0),
// }),
// // Posted any contract
// new RewardConfig(1202286258370383913, $"{Tag} posted their first contract!", new CheckConfig
// {
// Type = CheckType.ClientPostedContract
// }),
// // Started any contract
// new RewardConfig(1202286330873126992, $"A contract created by {Tag} reached Started state for the first time!", new CheckConfig
// {
// Type = CheckType.ClientStartedContract
// }),
// // Started a sizable contract
// new RewardConfig(1202286381670608909, $"A large contract created by {Tag} reached Started state for the first time! (10mb/5mins for test)", new CheckConfig
// {
// Type = CheckType.ClientStartedContract,
// MinNumberOfHosts = 4,
// MinSlotSize = 10.MB(),
// MinDuration = TimeSpan.FromMinutes(5.0),
// })
//};
}
}
+168
View File
@@ -0,0 +1,168 @@
using Logging;
using Utils;
namespace FileUtils
{
public interface IFileManager
{
TrackedFile CreateEmptyFile(string label = "");
TrackedFile GenerateFile(ByteSize size, string label = "");
void DeleteAllFiles();
void ScopedFiles(Action action);
T ScopedFiles<T>(Func<T> action);
}
public class FileManager : IFileManager
{
public const int ChunkSize = 1024 * 1024 * 100;
private static NumberSource folderNumberSource = new NumberSource(0);
private readonly Random random = new Random();
private readonly ILog log;
private readonly string rootFolder;
private readonly string folder;
private readonly List<List<TrackedFile>> fileSetStack = new List<List<TrackedFile>>();
public FileManager(ILog log, string rootFolder)
{
folder = Path.Combine(rootFolder, folderNumberSource.GetNextNumber().ToString("D5"));
this.log = log;
this.rootFolder = rootFolder;
}
public TrackedFile CreateEmptyFile(string label = "")
{
var path = Path.Combine(folder, Guid.NewGuid().ToString() + ".bin");
EnsureDirectory();
var result = new TrackedFile(log, path, label);
File.Create(result.Filename).Close();
if (fileSetStack.Any()) fileSetStack.Last().Add(result);
return result;
}
public TrackedFile GenerateFile(ByteSize size, string label)
{
var sw = Stopwatch.Begin(log);
var result = GenerateRandomFile(size, label);
sw.End($"Generated file {result.Describe()}.");
return result;
}
public void DeleteAllFiles()
{
DeleteDirectory();
}
public void ScopedFiles(Action action)
{
PushFileSet();
try
{
action();
}
finally
{
PopFileSet();
}
}
public T ScopedFiles<T>(Func<T> action)
{
PushFileSet();
try
{
return action();
}
finally
{
PopFileSet();
}
}
private void PushFileSet()
{
fileSetStack.Add(new List<TrackedFile>());
}
private void PopFileSet()
{
if (!fileSetStack.Any()) return;
var pop = fileSetStack.Last();
fileSetStack.Remove(pop);
foreach (var file in pop)
{
File.Delete(file.Filename);
}
// If the folder is now empty, delete it too.
if (!Directory.GetFiles(folder).Any()) DeleteDirectory();
}
private TrackedFile GenerateRandomFile(ByteSize size, string label)
{
var result = CreateEmptyFile(label);
CheckSpaceAvailable(result, size);
GenerateFileBytes(result, size);
return result;
}
private void CheckSpaceAvailable(TrackedFile testFile, ByteSize size)
{
var file = new FileInfo(testFile.Filename);
var drive = new DriveInfo(file.Directory!.Root.FullName);
var spaceAvailable = drive.TotalFreeSpace;
if (spaceAvailable < size.SizeInBytes)
{
var msg = $"Not enough disk space. " +
$"{Formatter.FormatByteSize(size.SizeInBytes)} required. " +
$"{Formatter.FormatByteSize(spaceAvailable)} available.";
log.Log(msg);
throw new Exception(msg);
}
}
private void GenerateFileBytes(TrackedFile result, ByteSize size)
{
long bytesLeft = size.SizeInBytes;
int chunkSize = ChunkSize;
while (bytesLeft > 0)
{
try
{
var length = Math.Min(bytesLeft, chunkSize);
AppendRandomBytesToFile(result, length);
bytesLeft -= length;
}
catch
{
chunkSize = chunkSize / 2;
if (chunkSize < 1024) throw;
}
}
}
private void AppendRandomBytesToFile(TrackedFile result, long length)
{
var bytes = new byte[length];
random.NextBytes(bytes);
using var stream = new FileStream(result.Filename, FileMode.Append);
stream.Write(bytes, 0, bytes.Length);
}
private void EnsureDirectory()
{
Directory.CreateDirectory(folder);
}
private void DeleteDirectory()
{
if (Directory.Exists(folder)) Directory.Delete(folder, true);
}
}
}
+14
View File
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Logging\Logging.csproj" />
<ProjectReference Include="..\Utils\Utils.csproj" />
</ItemGroup>
</Project>
+87
View File
@@ -0,0 +1,87 @@
using Logging;
using Utils;
namespace FileUtils
{
public class TrackedFile
{
private readonly ILog log;
public TrackedFile(ILog log, string filename, string label)
{
this.log = log;
Filename = filename;
Label = label;
}
public string Filename { get; }
public string Label { get; }
public void AssertIsEqual(TrackedFile? actual)
{
var sw = Stopwatch.Begin(log);
try
{
AssertEqual(actual);
}
finally
{
sw.End($"{nameof(TrackedFile)}.{nameof(AssertIsEqual)}");
}
}
public string Describe()
{
var sizePostfix = $" ({Formatter.FormatByteSize(GetFileSize())})";
if (!string.IsNullOrEmpty(Label)) return Label + sizePostfix;
return $"'{Filename}'{sizePostfix}";
}
public ByteSize GetFilesize()
{
return new ByteSize(GetFileSize());
}
private void AssertEqual(TrackedFile? actual)
{
if (actual == null) FrameworkAssert.Fail("TestFile is null.");
if (actual == this || actual!.Filename == Filename) FrameworkAssert.Fail("TestFile is compared to itself.");
FrameworkAssert.That(actual.GetFileSize() == GetFileSize(), "Files are not of equal length.");
using var streamExpected = new FileStream(Filename, FileMode.Open, FileAccess.Read);
using var streamActual = new FileStream(actual.Filename, FileMode.Open, FileAccess.Read);
var bytesExpected = new byte[FileManager.ChunkSize];
var bytesActual = new byte[FileManager.ChunkSize];
var readExpected = 0;
var readActual = 0;
while (true)
{
readExpected = streamExpected.Read(bytesExpected, 0, FileManager.ChunkSize);
readActual = streamActual.Read(bytesActual, 0, FileManager.ChunkSize);
if (readExpected == 0 && readActual == 0)
{
log.Log($"OK: {Describe()} is equal to {actual.Describe()}.");
return;
}
FrameworkAssert.That(readActual == readExpected, "Unable to read buffers of equal length.");
for (var i = 0; i < readActual; i++)
{
if (bytesExpected[i] != bytesActual[i]) FrameworkAssert.Fail("File contents not equal.");
}
}
}
private long GetFileSize()
{
var info = new FileInfo(Filename);
return info.Length;
}
}
}
+39
View File
@@ -0,0 +1,39 @@
using CodexContractsPlugin;
using GethPlugin;
using Logging;
namespace GethConnector
{
public class GethConnector
{
public IGethNode GethNode { get; }
public ICodexContracts CodexContracts { get; }
public static GethConnector? Initialize(ILog log)
{
if (!string.IsNullOrEmpty(GethInput.LoadError))
{
var msg = "Geth input incorrect: " + GethInput.LoadError;
log.Error(msg);
return null;
}
var contractsDeployment = new CodexContractsDeployment(
marketplaceAddress: GethInput.MarketplaceAddress,
abi: GethInput.ABI,
tokenAddress: GethInput.TokenAddress
);
var gethNode = new CustomGethNode(log, GethInput.GethHost, GethInput.GethPort, GethInput.PrivateKey);
var contracts = new CodexContractsAccess(log, gethNode, contractsDeployment);
return new GethConnector(gethNode, contracts);
}
private GethConnector(IGethNode gethNode, ICodexContracts codexContracts)
{
GethNode = gethNode;
CodexContracts = codexContracts;
}
}
}
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Framework\Logging\Logging.csproj" />
<ProjectReference Include="..\..\ProjectPlugins\CodexContractsPlugin\CodexContractsPlugin.csproj" />
<ProjectReference Include="..\..\ProjectPlugins\GethPlugin\GethPlugin.csproj" />
</ItemGroup>
</Project>
+52
View File
@@ -0,0 +1,52 @@
namespace GethConnector
{
public static class GethInput
{
private const string GethHostVar = "GETH_HOST";
private const string GethPortVar = "GETH_HTTP_PORT";
private const string GethPrivKeyVar = "GETH_PRIVATE_KEY";
private const string MarketplaceAddressVar = "CODEXCONTRACTS_MARKETPLACEADDRESS";
private const string TokenAddressVar = "CODEXCONTRACTS_TOKENADDRESS";
private const string AbiVar = "CODEXCONTRACTS_ABI";
static GethInput()
{
var error = new List<string>();
var gethHost = GetEnvVar(error, GethHostVar);
var gethPort = Convert.ToInt32(GetEnvVar(error, GethPortVar));
var privateKey = GetEnvVar(error, GethPrivKeyVar);
var marketplaceAddress = GetEnvVar(error, MarketplaceAddressVar);
var tokenAddress = GetEnvVar(error, TokenAddressVar);
var abi = GetEnvVar(error, AbiVar);
if (error.Any())
{
LoadError = string.Join(", ", error);
}
else
{
GethHost = gethHost!;
GethPort = gethPort;
PrivateKey = privateKey!;
MarketplaceAddress = marketplaceAddress!;
TokenAddress = tokenAddress!;
ABI = abi!;
}
}
public static string GethHost { get; } = string.Empty;
public static int GethPort { get; }
public static string PrivateKey { get; } = string.Empty;
public static string MarketplaceAddress { get; } = string.Empty;
public static string TokenAddress { get; } = string.Empty;
public static string ABI { get; } = string.Empty;
public static string LoadError { get; } = string.Empty;
private static string? GetEnvVar(List<string> error, string name)
{
var result = Environment.GetEnvironmentVariable(name);
if (string.IsNullOrEmpty(result)) error.Add($"'{name}' is not set.");
return result;
}
}
}
@@ -1,23 +1,22 @@
using k8s;
using Utils;
using Utils;
namespace KubernetesWorkflow
{
public class CommandRunner
{
private readonly Kubernetes client;
private readonly K8sClient client;
private readonly string k8sNamespace;
private readonly RunningPod pod;
private readonly string podName;
private readonly string containerName;
private readonly string command;
private readonly string[] arguments;
private readonly List<string> lines = new List<string>();
public CommandRunner(Kubernetes client, string k8sNamespace, RunningPod pod, string containerName, string command, string[] arguments)
public CommandRunner(K8sClient client, string k8sNamespace, string podName, string containerName, string command, string[] arguments)
{
this.client = client;
this.k8sNamespace = k8sNamespace;
this.pod = pod;
this.podName = podName;
this.containerName = containerName;
this.command = command;
this.arguments = arguments;
@@ -27,8 +26,8 @@ namespace KubernetesWorkflow
{
var input = new[] { command }.Concat(arguments).ToArray();
Time.Wait(client.NamespacedPodExecAsync(
pod.Name, k8sNamespace, containerName, input, false, Callback, new CancellationToken()));
Time.Wait(client.Run(c => c.NamespacedPodExecAsync(
podName, k8sNamespace, containerName, input, false, Callback, new CancellationToken())));
}
public string GetStdOut()
@@ -0,0 +1,25 @@
using Newtonsoft.Json;
namespace KubernetesWorkflow
{
public class Configuration
{
public Configuration(string? kubeConfigFile, TimeSpan operationTimeout, TimeSpan retryDelay, string kubernetesNamespace)
{
KubeConfigFile = kubeConfigFile;
OperationTimeout = operationTimeout;
RetryDelay = retryDelay;
KubernetesNamespace = kubernetesNamespace;
}
public string? KubeConfigFile { get; }
public TimeSpan OperationTimeout { get; }
public TimeSpan RetryDelay { get; }
public string KubernetesNamespace { get; }
public bool AllowNamespaceOverride { get; set; } = true;
public bool AddAppPodLabel { get; set; } = true;
[JsonIgnore]
public IK8sHooks Hooks { get; set; } = new DoNothingK8sHooks();
}
}
@@ -0,0 +1,98 @@
using k8s;
using Logging;
namespace KubernetesWorkflow
{
public class CrashWatcher
{
private readonly ILog log;
private readonly KubernetesClientConfiguration config;
private readonly string containerName;
private readonly string podName;
private readonly string recipeName;
private readonly string k8sNamespace;
private CancellationTokenSource cts;
private Task? worker;
private Exception? workerException;
public CrashWatcher(ILog log, KubernetesClientConfiguration config, string containerName, string podName, string recipeName, string k8sNamespace)
{
this.log = log;
this.config = config;
this.containerName = containerName;
this.podName = podName;
this.recipeName = recipeName;
this.k8sNamespace = k8sNamespace;
cts = new CancellationTokenSource();
}
public void Start()
{
if (worker != null) throw new InvalidOperationException();
cts = new CancellationTokenSource();
worker = Task.Run(Worker);
}
public void Stop()
{
if (worker == null) throw new InvalidOperationException();
cts.Cancel();
worker.Wait();
worker = null;
if (workerException != null) throw new Exception("Exception occurred in CrashWatcher worker thread.", workerException);
}
public bool HasContainerCrashed()
{
using var client = new Kubernetes(config);
var result = HasContainerBeenRestarted(client);
if (result) DownloadCrashedContainerLogs(client);
return result;
}
private void Worker()
{
try
{
MonitorContainer(cts.Token);
}
catch (Exception ex)
{
workerException = ex;
}
}
private void MonitorContainer(CancellationToken token)
{
using var client = new Kubernetes(config);
while (!token.IsCancellationRequested)
{
token.WaitHandle.WaitOne(TimeSpan.FromSeconds(10));
if (HasContainerBeenRestarted(client))
{
DownloadCrashedContainerLogs(client);
return;
}
}
}
private bool HasContainerBeenRestarted(Kubernetes client)
{
var podInfo = client.ReadNamespacedPod(podName, k8sNamespace);
var result = podInfo.Status.ContainerStatuses.Any(c => c.RestartCount > 0);
if (result) log.Log("Pod crash detected for " + containerName);
return result;
}
private void DownloadCrashedContainerLogs(Kubernetes client)
{
using var stream = client.ReadNamespacedPodLog(podName, k8sNamespace, recipeName, previous: true);
var handler = new WriteToFileLogHandler(log, "Crash detected for " + containerName);
handler.Log(stream);
}
}
}
+36
View File
@@ -0,0 +1,36 @@
using k8s;
namespace KubernetesWorkflow
{
public class K8sClient
{
private readonly Kubernetes client;
private static readonly object clientLock = new object();
public K8sClient(KubernetesClientConfiguration config)
{
client = new Kubernetes(config);
}
public void Run(Action<Kubernetes> action)
{
lock (clientLock)
{
action(client);
}
}
public T Run<T>(Func<Kubernetes, T> action)
{
lock (clientLock)
{
return action(client);
}
}
public void Dispose()
{
client.Dispose();
}
}
}
@@ -8,29 +8,24 @@ namespace KubernetesWorkflow
{
Configuration = configuration;
}
public Configuration Configuration { get; }
public string IP { get; private set; } = string.Empty;
public string HostAddress { get; private set; } = string.Empty;
public KubernetesClientConfiguration GetK8sClientConfig()
{
var config = GetConfig();
UpdateIp(config);
UpdateHostAddress(config);
config.SkipTlsVerify = true; // Required for operation on Wings cluster.
return config;
}
public string GetNodeLabelForLocation(Location location)
{
if (location == Location.Unspecified) return string.Empty;
return Configuration.LocationMap.Single(l => l.Location == location).WorkerName;
}
public TimeSpan K8sOperationTimeout()
{
return Configuration.OperationTimeout;
}
public TimeSpan WaitForK8sServiceDelay()
public TimeSpan K8sOperationRetryDelay()
{
return Configuration.RetryDelay;
}
@@ -47,10 +42,17 @@ namespace KubernetesWorkflow
}
}
private void UpdateIp(KubernetesClientConfiguration config)
private void UpdateHostAddress(KubernetesClientConfiguration config)
{
var host = config.Host.Replace("https://", "");
IP = host.Substring(0, host.IndexOf(':'));
if (host.Contains(":"))
{
HostAddress = "http://" + host.Substring(0, host.IndexOf(':'));
}
else
{
HostAddress = config.Host;
}
}
}
}
@@ -0,0 +1,976 @@
using k8s;
using k8s.Models;
using KubernetesWorkflow.Recipe;
using KubernetesWorkflow.Types;
using Logging;
using Utils;
namespace KubernetesWorkflow
{
public class K8sController
{
private readonly ILog log;
private readonly K8sCluster cluster;
private readonly WorkflowNumberSource workflowNumberSource;
private readonly K8sClient client;
public const string PodLabelKey = "pod-uuid";
public K8sController(ILog log, K8sCluster cluster, WorkflowNumberSource workflowNumberSource, string k8sNamespace)
{
this.log = log;
this.cluster = cluster;
this.workflowNumberSource = workflowNumberSource;
client = new K8sClient(cluster.GetK8sClientConfig());
K8sNamespace = k8sNamespace;
}
public void Dispose()
{
client.Dispose();
}
public StartResult BringOnline(ContainerRecipe[] containerRecipes, ILocation location)
{
log.Debug();
EnsureNamespace();
var podLabel = K8sNameUtils.Format(Guid.NewGuid().ToString());
var deployment = CreateDeployment(containerRecipes, location, podLabel);
var internalService = CreateInternalService(containerRecipes);
var externalService = CreateExternalService(containerRecipes);
return new StartResult(cluster, containerRecipes, deployment, internalService, externalService);
}
public void WaitUntilOnline(RunningContainer container)
{
WaitUntilDeploymentOnline(container);
}
public PodInfo GetPodInfo(RunningDeployment deployment)
{
var pod = GetPodForDeployment(deployment);
return CreatePodInfo(pod);
}
public void Stop(StartResult startResult, bool waitTillStopped)
{
log.Debug();
if (startResult.InternalService != null) DeleteService(startResult.InternalService);
if (startResult.ExternalService != null) DeleteService(startResult.ExternalService);
DeleteDeployment(startResult.Deployment);
if (waitTillStopped) WaitUntilPodsForDeploymentAreOffline(startResult.Deployment);
}
public void DownloadPodLog(RunningContainer container, ILogHandler logHandler, int? tailLines, bool? previous)
{
log.Debug();
var podName = GetPodName(container);
var recipeName = container.Recipe.Name;
using var stream = client.Run(c => c.ReadNamespacedPodLog(podName, K8sNamespace, recipeName, tailLines: tailLines, previous: previous));
logHandler.Log(stream);
}
public string ExecuteCommand(RunningContainer container, string command, params string[] args)
{
var containerName = container.Recipe.Name;
var cmdAndArgs = $"{containerName}: {command} ({string.Join(",", args)})";
log.Debug(cmdAndArgs);
var podName = GetPodName(container);
var runner = new CommandRunner(client, K8sNamespace, podName, containerName, command, args);
runner.Run();
var result = runner.GetStdOut();
log.Debug($"{cmdAndArgs} = '{result}'");
return result;
}
public int[] GetUsedExternalPorts()
{
return client.Run(c =>
{
var result = new List<int>();
var services = c.ListServiceForAllNamespaces();
var nodePorts = services.Items.Where(s => s.Spec.Type == "NodePort").ToArray();
if (!nodePorts.Any()) return result.ToArray();
foreach (var service in nodePorts)
{
foreach (var port in service.Spec.Ports)
{
if (port.NodePort.HasValue)
{
result.Add(port.NodePort.Value);
}
}
}
return result.ToArray();
});
}
public void DeleteAllNamespacesStartingWith(string prefix, bool wait)
{
log.Debug();
var all = client.Run(c => c.ListNamespace().Items);
var namespaces = all.Select(n => n.Name()).Where(n => n.StartsWith(prefix));
foreach (var ns in namespaces)
{
DeleteNamespace(ns, wait);
}
}
public void DeleteNamespace(bool wait)
{
log.Debug();
if (IsNamespaceOnline(K8sNamespace))
{
client.Run(c => c.DeleteNamespace(K8sNamespace, null, null, gracePeriodSeconds: 0));
if (wait) WaitUntilNamespaceDeleted(K8sNamespace);
}
}
public void DeleteNamespace(string ns, bool wait)
{
log.Debug();
if (IsNamespaceOnline(ns))
{
client.Run(c => c.DeleteNamespace(ns, null, null, gracePeriodSeconds: 0));
if (wait) WaitUntilNamespaceDeleted(ns);
}
}
#region Discover K8s Nodes
public K8sNodeLabel[] GetAvailableK8sNodes()
{
var nodes = client.Run(c => c.ListNode());
var optionals = nodes.Items.Select(i => CreateNodeLabel(i));
return optionals.Where(n => n != null).Select(n => n!).ToArray();
}
private K8sNodeLabel? CreateNodeLabel(V1Node i)
{
var keys = i.Metadata.Labels.Keys;
var hostnameKey = keys.SingleOrDefault(k => k.ToLowerInvariant().Contains("hostname"));
if (hostnameKey != null)
{
var hostnameValue = i.Metadata.Labels[hostnameKey];
return new K8sNodeLabel(hostnameKey, hostnameValue);
}
return null;
}
#endregion
#region Namespace management
private string K8sNamespace { get; }
private void EnsureNamespace()
{
if (IsNamespaceOnline(K8sNamespace)) return;
var namespaceSpec = new V1Namespace
{
ApiVersion = "v1",
Metadata = new V1ObjectMeta
{
Name = K8sNamespace,
Labels = new Dictionary<string, string> { { "name", K8sNamespace } }
}
};
client.Run(c => c.CreateNamespace(namespaceSpec));
WaitUntilNamespaceCreated();
CreatePolicy();
}
private bool IsNamespaceOnline(string name)
{
return client.Run(c => c.ListNamespace().Items.Any(n => n.Metadata.Name == name));
}
private void CreatePolicy()
{
client.Run(c =>
{
var body = new V1NetworkPolicy
{
Metadata = new V1ObjectMeta
{
Name = "isolate-policy",
NamespaceProperty = K8sNamespace
},
Spec = new V1NetworkPolicySpec
{
PodSelector = new V1LabelSelector {},
PolicyTypes = new[]
{
"Ingress",
"Egress"
},
Ingress = new List<V1NetworkPolicyIngressRule>
{
new V1NetworkPolicyIngressRule
{
FromProperty = new List<V1NetworkPolicyPeer>
{
new V1NetworkPolicyPeer
{
PodSelector = new V1LabelSelector {}
}
}
},
new V1NetworkPolicyIngressRule
{
FromProperty = new List<V1NetworkPolicyPeer>
{
new V1NetworkPolicyPeer
{
NamespaceSelector = new V1LabelSelector
{
MatchLabels = GetRunnerNamespaceSelector()
}
}
}
},
new V1NetworkPolicyIngressRule
{
FromProperty = new List<V1NetworkPolicyPeer>
{
new V1NetworkPolicyPeer
{
NamespaceSelector = new V1LabelSelector
{
MatchLabels = GetPrometheusNamespaceSelector()
}
}
}
}
},
Egress = new List<V1NetworkPolicyEgressRule>
{
new V1NetworkPolicyEgressRule
{
To = new List<V1NetworkPolicyPeer>
{
new V1NetworkPolicyPeer
{
PodSelector = new V1LabelSelector {}
}
}
},
new V1NetworkPolicyEgressRule
{
To = new List<V1NetworkPolicyPeer>
{
new V1NetworkPolicyPeer
{
NamespaceSelector = new V1LabelSelector
{
MatchLabels = new Dictionary<string, string> { { "kubernetes.io/metadata.name", "kube-system" } }
}
},
new V1NetworkPolicyPeer
{
PodSelector = new V1LabelSelector
{
MatchLabels = new Dictionary<string, string> { { "k8s-app", "kube-dns" } }
}
}
},
Ports = new List<V1NetworkPolicyPort>
{
new V1NetworkPolicyPort
{
Port = new IntstrIntOrString
{
Value = "53"
},
Protocol = "UDP"
}
}
},
new V1NetworkPolicyEgressRule
{
To = new List<V1NetworkPolicyPeer>
{
new V1NetworkPolicyPeer
{
IpBlock = new V1IPBlock
{
Cidr = "0.0.0.0/0"
}
}
},
Ports = new List<V1NetworkPolicyPort>
{
new V1NetworkPolicyPort
{
Port = new IntstrIntOrString
{
Value = "80"
},
Protocol = "TCP"
},
new V1NetworkPolicyPort
{
Port = new IntstrIntOrString
{
Value = "443"
},
Protocol = "TCP"
}
}
}
}
}
};
c.CreateNamespacedNetworkPolicy(body, K8sNamespace);
});
}
#endregion
#region Deployment management
private RunningDeployment CreateDeployment(ContainerRecipe[] containerRecipes, ILocation location, string podLabel)
{
var deploymentSpec = new V1Deployment
{
ApiVersion = "apps/v1",
Metadata = CreateDeploymentMetadata(containerRecipes),
Spec = new V1DeploymentSpec
{
Replicas = 1,
Selector = new V1LabelSelector
{
MatchLabels = GetSelector(containerRecipes)
},
Template = new V1PodTemplateSpec
{
Metadata = new V1ObjectMeta
{
Labels = GetSelector(containerRecipes, podLabel),
Annotations = GetAnnotations(containerRecipes)
},
Spec = new V1PodSpec
{
PriorityClassName = GetPriorityClassName(containerRecipes),
Affinity = CreatePodAffinity(containerRecipes),
NodeSelector = CreateNodeSelector(location),
Containers = CreateDeploymentContainers(containerRecipes),
Volumes = CreateVolumes(containerRecipes)
}
}
}
};
client.Run(c => c.CreateNamespacedDeployment(deploymentSpec, K8sNamespace));
var name = deploymentSpec.Metadata.Name;
return new RunningDeployment(name, podLabel);
}
private void DeleteDeployment(RunningDeployment deployment)
{
client.Run(c => c.DeleteNamespacedDeployment(deployment.Name, K8sNamespace));
WaitUntilDeploymentOffline(deployment.Name);
}
private IDictionary<string, string> CreateNodeSelector(ILocation location)
{
var nodeLabel = GetNodeLabelForLocation(location);
if (nodeLabel == null) return new Dictionary<string, string>();
return new Dictionary<string, string>
{
{ nodeLabel.Key, nodeLabel.Value }
};
}
private V1Affinity? CreatePodAffinity(ContainerRecipe[] recipes)
{
var notIns = recipes
.Select(r => r.SchedulingAffinity.NotIn)
.Where(n => !string.IsNullOrEmpty(n))
.Distinct()
.ToList();
if (!notIns.Any()) return null;
return new V1Affinity
{
NodeAffinity = new V1NodeAffinity
{
RequiredDuringSchedulingIgnoredDuringExecution = new V1NodeSelector
{
NodeSelectorTerms = new List<V1NodeSelectorTerm>
{
new V1NodeSelectorTerm
{
MatchExpressions = new List<V1NodeSelectorRequirement>
{
new V1NodeSelectorRequirement
{
Key = "allow-tests-pods",
OperatorProperty = "NotIn",
Values = notIns
}
}
}
}
}
}
};
}
private K8sNodeLabel? GetNodeLabelForLocation(ILocation location)
{
var l = (Location)location;
return l.NodeLabel;
}
private string GetPriorityClassName(ContainerRecipe[] containerRecipes)
{
if (containerRecipes.Any(c => c.SetCriticalPriority))
{
return "system-node-critical";
}
return null!;
}
private IDictionary<string, string> GetSelector(ContainerRecipe[] containerRecipes)
{
return containerRecipes.First().PodLabels.GetLabels();
}
private IDictionary<string, string> GetSelector(ContainerRecipe[] containerRecipes, string podLabel)
{
var labels = containerRecipes.First().PodLabels.Clone();
labels.Add(PodLabelKey, podLabel);
return labels.GetLabels();
}
private IDictionary<string, string> GetRunnerNamespaceSelector()
{
return new Dictionary<string, string> { { "kubernetes.io/metadata.name", "default" } };
}
private IDictionary<string, string> GetPrometheusNamespaceSelector()
{
return new Dictionary<string, string> { { "kubernetes.io/metadata.name", "monitoring" } };
}
private IDictionary<string, string> GetAnnotations(ContainerRecipe[] containerRecipes)
{
return containerRecipes.First().PodAnnotations.GetAnnotations();
}
private V1ObjectMeta CreateDeploymentMetadata(ContainerRecipe[] containerRecipes)
{
return new V1ObjectMeta
{
Name = string.Join('-',containerRecipes.Select(r => r.Name)),
NamespaceProperty = K8sNamespace,
Labels = GetSelector(containerRecipes),
Annotations = GetAnnotations(containerRecipes)
};
}
private List<V1Container> CreateDeploymentContainers(ContainerRecipe[] containerRecipes)
{
return containerRecipes.Select(CreateDeploymentContainer).ToList();
}
private V1Container CreateDeploymentContainer(ContainerRecipe recipe)
{
return new V1Container
{
Name = recipe.Name,
Image = recipe.Image,
ImagePullPolicy = "Always",
Ports = CreateContainerPorts(recipe),
Env = CreateEnv(recipe),
VolumeMounts = CreateContainerVolumeMounts(recipe),
Resources = CreateResourceLimits(recipe),
Command = CreateCommandList(recipe)
};
}
private IList<string> CreateCommandList(ContainerRecipe recipe)
{
if (recipe.CommandOverride == null || !recipe.CommandOverride.Command.Any()) return null!;
return recipe.CommandOverride.Command.ToList();
}
private V1ResourceRequirements CreateResourceLimits(ContainerRecipe recipe)
{
return new V1ResourceRequirements
{
Requests = CreateResourceQuantities(recipe.Resources.Requests),
Limits = CreateResourceQuantities(recipe.Resources.Limits)
};
}
private Dictionary<string, ResourceQuantity> CreateResourceQuantities(ContainerResourceSet set)
{
var result = new Dictionary<string, ResourceQuantity>();
if (set.MilliCPUs != 0)
{
result.Add("cpu", new ResourceQuantity($"{set.MilliCPUs}m"));
}
if (set.Memory.SizeInBytes != 0)
{
result.Add("memory", new ResourceQuantity(set.Memory.SizeInBytes.ToString()));
}
return result;
}
private List<V1VolumeMount> CreateContainerVolumeMounts(ContainerRecipe recipe)
{
return recipe.Volumes.Select(CreateContainerVolumeMount).ToList();
}
private V1VolumeMount CreateContainerVolumeMount(VolumeMount v)
{
return new V1VolumeMount
{
Name = v.VolumeName,
MountPath = v.MountPath,
SubPath = v.SubPath,
};
}
private List<V1Volume> CreateVolumes(ContainerRecipe[] containerRecipes)
{
return containerRecipes.Where(c => c.Volumes.Any()).SelectMany(CreateVolumes).ToList();
}
private List<V1Volume> CreateVolumes(ContainerRecipe recipe)
{
return recipe.Volumes.Select(CreateVolume).ToList();
}
private V1Volume CreateVolume(VolumeMount v)
{
CreatePersistentVolumeClaimIfNeeded(v);
if (!string.IsNullOrEmpty(v.HostPath))
{
return new V1Volume
{
Name = v.VolumeName,
HostPath = new V1HostPathVolumeSource
{
Path = v.HostPath
}
};
}
if (!string.IsNullOrEmpty(v.Secret))
{
return new V1Volume
{
Name = v.VolumeName,
Secret = CreateVolumeSecret(v)
};
}
return new V1Volume
{
Name = v.VolumeName,
PersistentVolumeClaim = new V1PersistentVolumeClaimVolumeSource
{
ClaimName = v.VolumeName
}
};
}
private void CreatePersistentVolumeClaimIfNeeded(VolumeMount v)
{
var pvcs = client.Run(c => c.ListNamespacedPersistentVolumeClaim(K8sNamespace));
if (pvcs != null && pvcs.Items.Any(i => i.Name() == v.VolumeName)) return;
client.Run(c => c.CreateNamespacedPersistentVolumeClaim(new V1PersistentVolumeClaim
{
ApiVersion = "v1",
Metadata = new V1ObjectMeta
{
Name = v.VolumeName,
},
Spec = new V1PersistentVolumeClaimSpec
{
AccessModes = new List<string>
{
"ReadWriteOnce"
},
Resources = CreateVolumeResourceRequirements(v),
},
}, K8sNamespace));
}
private V1SecretVolumeSource CreateVolumeSecret(VolumeMount v)
{
if (string.IsNullOrWhiteSpace(v.Secret)) return null!;
return new V1SecretVolumeSource
{
SecretName = v.Secret
};
}
private V1ResourceRequirements CreateVolumeResourceRequirements(VolumeMount v)
{
if (v.ResourceQuantity == null) return null!;
return new V1ResourceRequirements
{
Requests = new Dictionary<string, ResourceQuantity>()
{
{"storage", new ResourceQuantity(v.ResourceQuantity) }
}
};
}
private List<V1EnvVar> CreateEnv(ContainerRecipe recipe)
{
return recipe.EnvVars.Select(CreateEnvVar).ToList();
}
private V1EnvVar CreateEnvVar(EnvVar envVar)
{
return new V1EnvVar
{
Name = envVar.Name,
Value = envVar.Value,
};
}
private List<V1ContainerPort> CreateContainerPorts(ContainerRecipe recipe)
{
var exposedPorts = recipe.ExposedPorts.SelectMany(p => CreateContainerPort(recipe, p));
var internalPorts = recipe.InternalPorts.SelectMany(p => CreateContainerPort(recipe, p));
return exposedPorts.Concat(internalPorts).ToList();
}
private List<V1ContainerPort> CreateContainerPort(ContainerRecipe recipe, Port port)
{
var result = new List<V1ContainerPort>();
if (port.IsTcp()) CreateTcpContainerPort(result, recipe, port);
if (port.IsUdp()) CreateUdpContainerPort(result, recipe, port);
return result;
}
private void CreateUdpContainerPort(List<V1ContainerPort> result, ContainerRecipe recipe, Port port)
{
result.Add(CreateContainerPort(recipe, port, "UDP"));
}
private void CreateTcpContainerPort(List<V1ContainerPort> result, ContainerRecipe recipe, Port port)
{
result.Add(CreateContainerPort(recipe, port, "TCP"));
}
private V1ContainerPort CreateContainerPort(ContainerRecipe recipe, Port port, string protocol)
{
return new V1ContainerPort
{
Name = GetNameForPort(recipe, port),
ContainerPort = port.Number,
Protocol = protocol
};
}
private string GetNameForPort(ContainerRecipe recipe, Port port)
{
var inputs = new[]
{
$"p{workflowNumberSource.WorkflowNumber}",
recipe.Number.ToString(),
port.Number.ToString(),
port.Protocol.ToString().ToLowerInvariant()
};
return K8sNameUtils.FormatPortName(string.Join(",", inputs));
}
private string GetPodName(RunningContainer container)
{
return GetPodForDeployment(container.RunningPod.StartResult.Deployment).Metadata.Name;
}
private V1Pod GetPodForDeployment(RunningDeployment deployment)
{
return Time.Retry(() => GetPodForDeplomentInternal(deployment),
// We will wait up to 1 minute, k8s might be moving pods around.
maxTimeout: TimeSpan.FromMinutes(1),
retryTime: TimeSpan.FromSeconds(10),
description: "Find pod by label for deployment.");
}
private V1Pod GetPodForDeplomentInternal(RunningDeployment deployment)
{
var allPods = client.Run(c => c.ListNamespacedPod(K8sNamespace));
var pods = allPods.Items.Where(p => p.GetLabel(PodLabelKey) == deployment.PodLabel).ToArray();
if (pods.Length != 1)
{
var allLabels = allPods.Items.Select(p =>
{
var labels = string.Join(",", p.Labels().Select(l => $"{l.Key}={l.Value}"));
return $"pod:'{p.Name()}' has labels: [{labels}]";
});
throw new Exception($"Expected to find 1 pod by podLabel '{deployment.PodLabel}'. Found: {pods.Length}. " +
$"Total number of pods: {allPods.Items.Count}. Their labels: {string.Join(Environment.NewLine, allLabels)}");
}
return pods[0];
}
#endregion
#region Service management
private RunningService? CreateInternalService(ContainerRecipe[] recipes)
{
return CreateService(recipes, r => r.InternalPorts.Concat(r.ExposedPorts).ToArray(), "ClusterIP", "int", false);
}
private RunningService? CreateExternalService(ContainerRecipe[] recipes)
{
return CreateService(recipes, r => r.ExposedPorts, "NodePort", "ext", true);
}
private RunningService? CreateService(ContainerRecipe[] recipes, Func<ContainerRecipe, Port[]> portSelector, string serviceType, string namePostfix, bool isNodePort)
{
var ports = CreateServicePorts(recipes, portSelector, isNodePort);
if (!ports.Any()) return null;
var serviceSpec = new V1Service
{
ApiVersion = "v1",
Metadata = CreateServiceMetadata(recipes, namePostfix),
Spec = new V1ServiceSpec
{
Type = serviceType,
Selector = GetSelector(recipes),
Ports = ports,
}
};
client.Run(c => c.CreateNamespacedService(serviceSpec, K8sNamespace));
var result = ReadBackServiceAndMapPorts(serviceSpec, recipes);
var name = serviceSpec.Metadata.Name;
return new RunningService(name, result);
}
private List<ContainerRecipePortMapEntry> ReadBackServiceAndMapPorts(V1Service serviceSpec, ContainerRecipe[] containerRecipes)
{
var result = new List<ContainerRecipePortMapEntry>();
// For each container-recipe-port, we need to figure out which service-ports it was assigned by K8s.
var readback = client.Run(c => c.ReadNamespacedService(serviceSpec.Metadata.Name, K8sNamespace));
foreach (var r in containerRecipes)
{
var recipePorts = r.ExposedPorts.Concat(r.InternalPorts).ToArray();
foreach (var port in recipePorts)
{
var portName = GetNameForPort(r, port);
var matchingServicePorts = readback.Spec.Ports.Where(p => p.Name == portName);
var ports = matchingServicePorts.Select(p => MapPortIfAble(p, port.Tag, port.Protocol)).ToArray();
if (ports.Any())
{
result.Add(new ContainerRecipePortMapEntry(r.Number, ports));
log.Debug($"Service Readback: {portName} found: {string.Join(",", ports.Select(p => p.ToString()))}");
}
}
}
return result;
}
private Port MapPortIfAble(V1ServicePort p, string tag, PortProtocol protocol)
{
if (p.NodePort != null) return new Port(p.NodePort.Value, tag, protocol);
if (p.Port > 0) return new Port(p.Port, tag, protocol);
throw new Exception("Unable to map port.");
}
private void DeleteService(RunningService service)
{
client.Run(c => c.DeleteNamespacedService(service.Name, K8sNamespace));
}
private V1ObjectMeta CreateServiceMetadata(ContainerRecipe[] containerRecipes, string namePostfix)
{
var recipeName = containerRecipes.First().Name;
var name = K8sNameUtils.Format($"{recipeName}-{workflowNumberSource.WorkflowNumber}-{namePostfix}");
log.Debug("Creating service: " + name);
return new V1ObjectMeta
{
Name = name,
NamespaceProperty = K8sNamespace,
};
}
private List<V1ServicePort> CreateServicePorts(ContainerRecipe[] recipes, Func<ContainerRecipe, Port[]> portSelector, bool isNodePort)
{
var result = new List<V1ServicePort>();
foreach (var recipe in recipes)
{
var ports = portSelector(recipe);
foreach (var port in ports)
{
result.AddRange(CreateServicePorts(recipe, port, isNodePort));
}
}
return result;
}
private List<V1ServicePort> CreateServicePorts(ContainerRecipe recipe, Port recipePort, bool isNodePort)
{
var result = new List<V1ServicePort>();
if (recipePort.IsTcp()) CreateServicePort(result, recipe, recipePort, "TCP", isNodePort);
if (recipePort.IsUdp()) CreateServicePort(result, recipe, recipePort, "UDP", isNodePort);
return result;
}
private void CreateServicePort(List<V1ServicePort> result, ContainerRecipe recipe, Port port, string protocol, bool isNodePort)
{
var p = new V1ServicePort
{
Name = GetNameForPort(recipe, port),
Protocol = protocol,
Port = port.Number,
TargetPort = GetNameForPort(recipe, port)
};
if (isNodePort) p.NodePort = port.Number;
result.Add(p);
}
#endregion
#region Waiting
private void WaitUntilNamespaceCreated()
{
WaitUntil(() => IsNamespaceOnline(K8sNamespace), nameof(WaitUntilNamespaceCreated));
}
private void WaitUntilNamespaceDeleted(string @namespace)
{
WaitUntil(() => !IsNamespaceOnline(@namespace), nameof(WaitUntilNamespaceDeleted));
}
private void WaitUntilDeploymentOnline(RunningContainer container)
{
WaitUntil(() =>
{
CheckForCrash(container);
var deployment = client.Run(c => c.ReadNamespacedDeployment(container.Recipe.Name, K8sNamespace));
return deployment?.Status.AvailableReplicas != null && deployment.Status.AvailableReplicas > 0;
}, nameof(WaitUntilDeploymentOnline));
}
private void CheckForCrash(RunningContainer container)
{
var deploymentName = container.Recipe.Name;
var podName = GetPodName(container);
var podInfo = client.Run(c => c.ReadNamespacedPod(podName, K8sNamespace));
if (podInfo == null) return;
if (podInfo.Status == null) return;
if (podInfo.Status.ContainerStatuses == null) return;
var result = podInfo.Status.ContainerStatuses.Any(c => c.RestartCount > 0);
if (result)
{
var msg = $"Pod crash detected for deployment {deploymentName} (pod:{podName})";
log.Error(msg);
DownloadPodLog(container, new WriteToFileLogHandler(log, msg), tailLines: null, previous: true);
throw new Exception(msg);
}
}
private void WaitUntilDeploymentOffline(string deploymentName)
{
WaitUntil(() =>
{
var deployments = client.Run(c => c.ListNamespacedDeployment(K8sNamespace));
var deployment = deployments.Items.SingleOrDefault(d => d.Metadata.Name == deploymentName);
return deployment == null || deployment.Status.AvailableReplicas == 0;
}, nameof(WaitUntilDeploymentOffline));
}
private void WaitUntilPodsForDeploymentAreOffline(RunningDeployment deployment)
{
WaitUntil(() =>
{
var pods = FindPodsByLabel(deployment.PodLabel);
return !pods.Any();
}, nameof(WaitUntilPodsForDeploymentAreOffline));
}
private void WaitUntil(Func<bool> predicate, string msg)
{
var sw = Stopwatch.Begin(log, true);
try
{
Time.WaitUntil(predicate, cluster.K8sOperationTimeout(), cluster.K8sOperationRetryDelay(), msg);
}
finally
{
sw.End(msg, 1);
}
}
#endregion
public CrashWatcher CreateCrashWatcher(RunningContainer container)
{
var containerName = container.Name;
var podName = GetPodName(container);
var recipeName = container.Recipe.Name;
return new CrashWatcher(log, cluster.GetK8sClientConfig(), containerName, podName, recipeName, K8sNamespace);
}
private V1Pod[] FindPodsByLabel(string podLabel)
{
var pods = client.Run(c => c.ListNamespacedPod(K8sNamespace));
return pods.Items.Where(p => p.GetLabel(PodLabelKey) == podLabel).ToArray();
}
private PodInfo CreatePodInfo(V1Pod pod)
{
var name = pod.Name();
var ip = pod.Status.PodIP;
var k8sNodeName = pod.Spec.NodeName;
if (string.IsNullOrEmpty(name)) throw new InvalidOperationException("Invalid pod name received. Test infra failure.");
if (string.IsNullOrEmpty(ip)) throw new InvalidOperationException("Invalid pod IP received. Test infra failure.");
return new PodInfo(name, ip, k8sNodeName);
}
}
}
+27
View File
@@ -0,0 +1,27 @@
using KubernetesWorkflow.Recipe;
using KubernetesWorkflow.Types;
namespace KubernetesWorkflow
{
public interface IK8sHooks
{
void OnContainersStarted(RunningPod runningPod);
void OnContainersStopped(RunningPod runningPod);
void OnContainerRecipeCreated(ContainerRecipe recipe);
}
public class DoNothingK8sHooks : IK8sHooks
{
public void OnContainersStarted(RunningPod runningPod)
{
}
public void OnContainersStopped(RunningPod runningPod)
{
}
public void OnContainerRecipeCreated(ContainerRecipe recipe)
{
}
}
}
@@ -0,0 +1,33 @@
namespace KubernetesWorkflow
{
public static class K8sNameUtils
{
public static string Format(string s)
{
return Format(s, 62);
}
public static string FormatPortName(string s)
{
return Format(s, 15);
}
private static string Format(string s, int maxLength)
{
var result = s.ToLowerInvariant()
.Replace("_", "-")
.Replace(" ", "-")
.Replace(":", "-")
.Replace("/", "-")
.Replace("\\", "-")
.Replace("[", "-")
.Replace("]", "-")
.Replace(",", "-");
result = result.Trim('-');
if (result.Length > maxLength) result = result.Substring(0, maxLength);
return result;
}
}
}
@@ -0,0 +1,47 @@
namespace KubernetesWorkflow
{
public interface IKnownLocations
{
/// <summary>
/// Returns a known location given an index.
/// Each index guarantees a different location.
/// </summary>
ILocation Get(int index);
int NumberOfLocations { get; }
/// <summary>
/// Returns the location object for a specific kubernetes node. Throws if it doesn't exist.
/// </summary>
ILocation Get(string kubeNodeName, bool allowPartialMatches = false);
}
public class KnownLocations : IKnownLocations
{
private readonly Location[] locations;
public KnownLocations(Location[] locations)
{
this.locations = locations;
if (locations.Any(l => l.NodeLabel == null)) throw new Exception("Must not contain unspecified location");
}
public static ILocation UnspecifiedLocation { get; } = new Location();
public int NumberOfLocations => locations.Length;
public ILocation Get(int index)
{
return locations[index];
}
public ILocation Get(string kubeNodeName, bool allowPartialMatches = false)
{
if (allowPartialMatches)
{
return locations.Single(l => l.NodeLabel != null && l.NodeLabel.Value.Contains(kubeNodeName));
}
return locations.Single(l => l.NodeLabel != null && l.NodeLabel.Value == kubeNodeName);
}
}
}
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>KubernetesWorkflow</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
@@ -9,9 +9,11 @@
<ItemGroup>
<PackageReference Include="KubernetesClient" Version="10.1.4" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Logging\Logging.csproj" />
<ProjectReference Include="..\Utils\Utils.csproj" />
</ItemGroup>
+24
View File
@@ -0,0 +1,24 @@
using KubernetesWorkflow.Types;
namespace KubernetesWorkflow
{
public interface ILocation
{
}
public class Location : ILocation
{
internal Location(K8sNodeLabel? nodeLabel = null)
{
NodeLabel = nodeLabel;
}
internal K8sNodeLabel? NodeLabel { get; }
public override string ToString()
{
if (NodeLabel == null) return "Location:Unspecified";
return $"Location:KubeNode-'{NodeLabel.Key}:{NodeLabel.Value}'";
}
}
}
@@ -0,0 +1,50 @@
using KubernetesWorkflow.Types;
using Logging;
namespace KubernetesWorkflow
{
public class LocationProvider
{
private readonly TimeSpan locationsExpirationTime = TimeSpan.FromMinutes(10);
private readonly ILog log;
private readonly Action<Action<K8sController>> onController;
private Location[] knownLocations = Array.Empty<Location>();
private DateTime lastUpdate = DateTime.UtcNow;
public LocationProvider(ILog log, Action<Action<K8sController>> onController)
{
this.log = log;
this.onController = onController;
}
public IKnownLocations GetAvailableLocations()
{
if (ShouldUpdateKnownLocations())
{
onController(UpdateKnownLocations);
}
return new KnownLocations(knownLocations);
}
private void UpdateKnownLocations(K8sController controller)
{
knownLocations = controller.GetAvailableK8sNodes().Select(CreateLocation).ToArray();
lastUpdate = DateTime.UtcNow;
log.Log($"Detected {knownLocations.Length} available locations: '{string.Join(",", knownLocations.Select(l => l.ToString()))}'");
}
private Location CreateLocation(K8sNodeLabel k8sNode)
{
return new Location(k8sNode);
}
private bool ShouldUpdateKnownLocations()
{
if (!knownLocations.Any()) return true;
if (DateTime.UtcNow - lastUpdate > locationsExpirationTime) return true;
return false;
}
}
}
@@ -0,0 +1,46 @@
using Logging;
namespace KubernetesWorkflow
{
public interface ILogHandler
{
void Log(Stream log);
}
public abstract class LogHandler : ILogHandler
{
public void Log(Stream log)
{
using var reader = new StreamReader(log);
var line = reader.ReadLine();
while (line != null)
{
ProcessLine(line);
line = reader.ReadLine();
}
}
protected abstract void ProcessLine(string line);
}
public class WriteToFileLogHandler : LogHandler, ILogHandler
{
public WriteToFileLogHandler(ILog sourceLog, string description)
{
LogFile = sourceLog.CreateSubfile();
var msg = $"{description} -->> {LogFile.FullFilename}";
sourceLog.Log(msg);
LogFile.Write(msg);
LogFile.WriteRaw(description);
}
public LogFile LogFile { get; }
protected override void ProcessLine(string line)
{
LogFile.WriteRaw(line);
}
}
}
@@ -0,0 +1,7 @@
namespace KubernetesWorkflow
{
public static class PublicIpService
{
public static string Address { get; } = "ip.codex.storage";
}
}
@@ -0,0 +1,12 @@
namespace KubernetesWorkflow.Recipe
{
public class CommandOverride
{
public CommandOverride(params string[] command)
{
Command = command;
}
public string[] Command { get; }
}
}
@@ -0,0 +1,53 @@
using Newtonsoft.Json.Linq;
namespace KubernetesWorkflow.Recipe
{
public class ContainerAdditionals
{
public ContainerAdditionals(Additional[] additionals)
{
Additionals = additionals;
}
public static ContainerAdditionals CreateFromUserData(IEnumerable<object> userData)
{
return new ContainerAdditionals(userData.Select(ConvertToAdditional).ToArray());
}
public Additional[] Additionals { get; }
public T? Get<T>()
{
var typeName = GetTypeName(typeof(T));
var userData = Additionals.SingleOrDefault(a => a.Type == typeName);
if (userData == null) return default;
var jobject = (JObject)userData.UserData;
return jobject.ToObject<T>();
}
private static Additional ConvertToAdditional(object userData)
{
var typeName = GetTypeName(userData.GetType());
return new Additional(typeName, userData);
}
private static string GetTypeName(Type type)
{
var typeName = type.FullName;
if (string.IsNullOrEmpty(typeName)) throw new Exception("Object type fullname is null or empty: " + type);
return typeName;
}
}
public class Additional
{
public Additional(string type, object userData)
{
Type = type;
UserData = userData;
}
public string Type { get; }
public object UserData { get; }
}
}
@@ -0,0 +1,144 @@
namespace KubernetesWorkflow.Recipe
{
public class ContainerRecipe
{
public ContainerRecipe(int number, string? nameOverride, string image, ContainerResources resources, SchedulingAffinity schedulingAffinity, CommandOverride commandOverride, bool setCriticalPriority, Port[] exposedPorts, Port[] internalPorts, EnvVar[] envVars, PodLabels podLabels, PodAnnotations podAnnotations, VolumeMount[] volumes, ContainerAdditionals additionals)
{
Number = number;
NameOverride = nameOverride;
Image = image;
Resources = resources;
SchedulingAffinity = schedulingAffinity;
CommandOverride = commandOverride;
SetCriticalPriority = setCriticalPriority;
ExposedPorts = exposedPorts;
InternalPorts = internalPorts;
EnvVars = envVars;
PodLabels = podLabels;
PodAnnotations = podAnnotations;
Volumes = volumes;
Additionals = additionals;
if (NameOverride != null)
{
Name = $"{K8sNameUtils.Format(NameOverride)}-{Number}";
}
else
{
Name = $"ctnr{Number}";
}
if (exposedPorts.Any(p => string.IsNullOrEmpty(p.Tag))) throw new Exception("Port tags are required for all exposed ports.");
}
public string Name { get; }
public int Number { get; }
public string? NameOverride { get; }
public ContainerResources Resources { get; }
public SchedulingAffinity SchedulingAffinity { get; }
public CommandOverride CommandOverride { get; }
public bool SetCriticalPriority { get; }
public string Image { get; }
public Port[] ExposedPorts { get; }
public Port[] InternalPorts { get; }
public EnvVar[] EnvVars { get; }
public PodLabels PodLabels { get; }
public PodAnnotations PodAnnotations { get; }
public VolumeMount[] Volumes { get; }
public ContainerAdditionals Additionals { get; }
public Port? GetPortByTag(string tag)
{
return ExposedPorts.Concat(InternalPorts).SingleOrDefault(p => p.Tag == tag);
}
public override string ToString()
{
return $"(container-recipe: {Name}, image: {Image}, " +
$"exposedPorts: {string.Join(",", ExposedPorts.Select(p => p.Number))}, " +
$"internalPorts: {string.Join(",", InternalPorts.Select(p => p.Number))}, " +
$"envVars: {string.Join(",", EnvVars.Select(v => v.ToString()))}, " +
$"limits: {Resources}, " +
$"affinity: {SchedulingAffinity}, " +
$"volumes: {string.Join(",", Volumes.Select(v => $"'{v.MountPath}'"))}";
}
}
public class Port
{
public Port(int number, string tag, PortProtocol protocol)
{
Number = number;
Tag = tag;
Protocol = protocol;
if (string.IsNullOrWhiteSpace(Tag))
{
throw new Exception("A unique port tag is required");
}
}
public int Number { get; }
public string Tag { get; }
public PortProtocol Protocol { get; }
public bool IsTcp()
{
return Protocol == PortProtocol.TCP;
}
public bool IsUdp()
{
return Protocol == PortProtocol.UDP;
}
public override string ToString()
{
if (string.IsNullOrEmpty(Tag)) return $"untagged-port={Number}/{Protocol}";
return $"{Tag}={Number}/{Protocol}";
}
}
public enum PortProtocol
{
TCP,
UDP
}
public class EnvVar
{
public EnvVar(string name, string value)
{
Name = name;
Value = value;
}
public string Name { get; }
public string Value { get; }
public override string ToString()
{
return $"'{Name}' = '{Value}'";
}
}
public class VolumeMount
{
public VolumeMount(string volumeName, string mountPath, string? subPath = null, string? resourceQuantity = null, string? secret = null, string? hostPath = null)
{
VolumeName = volumeName;
MountPath = mountPath;
SubPath = subPath;
ResourceQuantity = resourceQuantity;
Secret = secret;
HostPath = hostPath;
}
public string VolumeName { get; }
public string MountPath { get; }
public string? SubPath { get; }
public string? ResourceQuantity { get; }
public string? Secret { get; }
public string? HostPath { get; }
}
}
@@ -0,0 +1,168 @@
using Utils;
namespace KubernetesWorkflow.Recipe
{
public abstract class ContainerRecipeFactory
{
private readonly List<Port> exposedPorts = new List<Port>();
private readonly List<Port> internalPorts = new List<Port>();
private readonly List<EnvVar> envVars = new List<EnvVar>();
private readonly PodLabels podLabels = new PodLabels();
private readonly PodAnnotations podAnnotations = new PodAnnotations();
private readonly List<VolumeMount> volumeMounts = new List<VolumeMount>();
private readonly List<object> additionals = new List<object>();
private RecipeComponentFactory factory = null!;
private ContainerResources resources = new ContainerResources();
private SchedulingAffinity schedulingAffinity = new SchedulingAffinity();
private CommandOverride commandOverride = new CommandOverride();
private bool setCriticalPriority;
public ContainerRecipe CreateRecipe(int index, int containerNumber, RecipeComponentFactory factory, StartupConfig config)
{
this.factory = factory;
ContainerNumber = containerNumber;
Index = index;
Initialize(config);
var recipe = new ContainerRecipe(containerNumber, config.NameOverride, Image, resources, schedulingAffinity, commandOverride, setCriticalPriority,
exposedPorts.ToArray(),
internalPorts.ToArray(),
envVars.ToArray(),
podLabels.Clone(),
podAnnotations.Clone(),
volumeMounts.ToArray(),
ContainerAdditionals.CreateFromUserData(additionals));
exposedPorts.Clear();
internalPorts.Clear();
envVars.Clear();
podLabels.Clear();
podAnnotations.Clear();
volumeMounts.Clear();
additionals.Clear();
this.factory = null!;
resources = new ContainerResources();
schedulingAffinity = new SchedulingAffinity();
commandOverride = new CommandOverride();
setCriticalPriority = false;
return recipe;
}
public abstract string AppName { get; }
public abstract string Image { get; }
protected int ContainerNumber { get; private set; } = 0;
protected int Index { get; private set; } = 0;
protected abstract void Initialize(StartupConfig config);
protected Port AddExposedPort(string tag, PortProtocol protocol = PortProtocol.TCP)
{
return AddExposedPort(factory.CreateExternalPort(tag, protocol));
}
protected Port AddExposedPort(int number, string tag, PortProtocol protocol = PortProtocol.TCP)
{
return AddExposedPort(factory.CreateExternalPort(number, tag, protocol));
}
protected Port AddInternalPort(string tag = "", PortProtocol protocol = PortProtocol.TCP)
{
var p = factory.CreateInternalPort(tag, protocol);
internalPorts.Add(p);
return p;
}
protected void AddExposedPortAndVar(string name, string tag, PortProtocol protocol = PortProtocol.TCP)
{
AddEnvVar(name, AddExposedPort(tag, protocol));
}
protected void AddInternalPortAndVar(string name, string tag = "", PortProtocol protocol = PortProtocol.TCP)
{
AddEnvVar(name, AddInternalPort(tag, protocol));
}
protected void AddEnvVar(string name, string value)
{
envVars.Add(factory.CreateEnvVar(name, value));
}
protected void AddEnvVar(string name, Port value)
{
envVars.Add(factory.CreateEnvVar(name, value.Number));
}
protected void AddPodLabel(string name, string value)
{
podLabels.Add(name, value);
}
protected void AddPodAnnotation(string name, string value)
{
podAnnotations.Add(name, value);
}
protected void AddVolume(string name, string mountPath, string? subPath = null, string? secret = null, string? hostPath = null)
{
var size = 10.MB().SizeInBytes.ToString();
volumeMounts.Add(new VolumeMount(name, mountPath, subPath, size, secret, hostPath));
}
protected void AddVolume(string mountPath, ByteSize volumeSize)
{
volumeMounts.Add(new VolumeMount(
$"autovolume-{Guid.NewGuid().ToString().ToLowerInvariant()}",
mountPath,
resourceQuantity: volumeSize.SizeInBytes.ToString()));
}
protected void Additional(object userData)
{
additionals.Add(userData);
}
protected void SetResourcesRequest(int milliCPUs, ByteSize memory)
{
SetResourcesRequest(new ContainerResourceSet(milliCPUs, memory));
}
protected void SetSchedulingAffinity(string notIn)
{
schedulingAffinity = new SchedulingAffinity(notIn);
}
protected void OverrideCommand(params string[] command)
{
commandOverride = new CommandOverride(command);
}
protected void SetSystemCriticalPriority()
{
setCriticalPriority = true;
}
// Disabled following a possible bug in the k8s cluster that will throttle containers much more than is
// called for if they have resource limits defined.
//protected void SetResourceLimits(int milliCPUs, ByteSize memory)
//{
// SetResourceLimits(new ContainerResourceSet(milliCPUs, memory));
//}
protected void SetResourcesRequest(ContainerResourceSet requests)
{
resources.Requests = requests;
}
protected void SetResourceLimits(ContainerResourceSet limits)
{
resources.Limits = limits;
}
private Port AddExposedPort(Port port)
{
exposedPorts.Add(port);
return port;
}
}
}
@@ -0,0 +1,52 @@
using Utils;
namespace KubernetesWorkflow.Recipe
{
public class ContainerResources
{
public ContainerResourceSet Requests { get; set; } = new ContainerResourceSet();
public ContainerResourceSet Limits { get; set; } = new ContainerResourceSet();
public override string ToString()
{
return $"requests:{Requests}, limits:{Limits}";
}
}
public class ContainerResourceSet
{
public ContainerResourceSet(int milliCPUs, ByteSize memory)
{
MilliCPUs = milliCPUs;
Memory = memory;
}
public ContainerResourceSet(int milliCPUs)
: this(milliCPUs, new ByteSize(0))
{
}
public ContainerResourceSet(ByteSize memory)
: this(0, memory)
{
}
public ContainerResourceSet()
: this(0)
{
}
public int MilliCPUs { get; }
public ByteSize Memory { get; }
public override string ToString()
{
var result = new List<string>();
if (MilliCPUs == 0) result.Add("cpu: unlimited");
else result.Add($"cpu: {MilliCPUs} milliCPUs");
if (Memory.SizeInBytes == 0) result.Add("memory: unlimited");
else result.Add($"memory: {Memory}");
return string.Join(", ", result);
}
}
}
@@ -0,0 +1,29 @@
namespace KubernetesWorkflow.Recipe
{
public class PodAnnotations
{
private readonly Dictionary<string, string> annotations = new Dictionary<string, string>();
public void Add(string key, string value)
{
annotations.Add(key, value);
}
public PodAnnotations Clone()
{
var result = new PodAnnotations();
foreach (var entry in annotations) result.Add(entry.Key, entry.Value);
return result;
}
public void Clear()
{
annotations.Clear();
}
internal Dictionary<string, string> GetAnnotations()
{
return annotations;
}
}
}
@@ -0,0 +1,29 @@
namespace KubernetesWorkflow.Recipe
{
public class PodLabels
{
private readonly Dictionary<string, string> labels = new Dictionary<string, string>();
public void Add(string key, string value)
{
labels.Add(key, K8sNameUtils.Format(value));
}
public PodLabels Clone()
{
var result = new PodLabels();
foreach (var entry in labels) result.Add(entry.Key, entry.Value);
return result;
}
public void Clear()
{
labels.Clear();
}
internal Dictionary<string, string> GetLabels()
{
return labels;
}
}
}
@@ -0,0 +1,50 @@
using System.Globalization;
using Utils;
namespace KubernetesWorkflow.Recipe
{
public class RecipeComponentFactory
{
private readonly NumberSource internalNumberSource = new NumberSource(8080);
private static readonly NumberSource externalNumberSource = new NumberSource(30000);
private static int[] usedExternalPorts = Array.Empty<int>();
public void Update(K8sController controller)
{
usedExternalPorts = controller.GetUsedExternalPorts();
}
public Port CreateInternalPort(string tag, PortProtocol protocol)
{
return new Port(internalNumberSource.GetNextNumber(), tag, protocol);
}
public Port CreateExternalPort(int number, string tag, PortProtocol protocol)
{
if (usedExternalPorts.Contains(number)) throw new Exception($"External port number {number} is already in use by the cluster.");
return new Port(number, tag, protocol);
}
public Port CreateExternalPort(string tag, PortProtocol protocol)
{
while (true)
{
var number = externalNumberSource.GetNextNumber();
if (!usedExternalPorts.Contains(number))
{
return new Port(number, tag, protocol);
}
}
}
public EnvVar CreateEnvVar(string name, int value)
{
return CreateEnvVar(name, value.ToString(CultureInfo.InvariantCulture));
}
public EnvVar CreateEnvVar(string name, string value)
{
return new EnvVar(name, value);
}
}
}

Some files were not shown because too many files have changed in this diff Show More