├── .gitattributes
├── nuspecs
├── Dhaf.Core.readme.md
└── Dhaf.Core.nuspec
├── project_identity
├── icon.png
├── github.png
└── small.pdf
├── src
├── Dhaf.Switchers.Exec
│ ├── extension.json
│ ├── InternalConfig.cs
│ ├── Config.cs
│ ├── Dhaf.Switchers.Exec.csproj
│ └── ExecSwitcher.cs
├── Dhaf.HealthCheckers.Exec
│ ├── extension.json
│ ├── InternalConfig.cs
│ ├── Config.cs
│ ├── Dhaf.HealthCheckers.Exec.csproj
│ └── ExecHealthChecker.cs
├── Dhaf.Switchers.GoogleCloud
│ ├── extension.json
│ ├── InternalConfig.cs
│ ├── Config.cs
│ ├── Dhaf.Switchers.GoogleCloud.csproj
│ └── GoogleCloudSwitcher.cs
├── Dhaf.Core
│ ├── Interfaces
│ │ ├── Config
│ │ │ ├── ISwitcherConfig.cs
│ │ │ ├── IHealthCheckerConfig.cs
│ │ │ ├── INotifierInternalConfig.cs
│ │ │ ├── ISwitcherInternalConfig.cs
│ │ │ ├── IHealthCheckerInternalConfig.cs
│ │ │ └── INotifierConfig.cs
│ │ ├── INotifier
│ │ │ ├── Enums
│ │ │ │ ├── NotifierRole.cs
│ │ │ │ ├── NotifierLevel.cs
│ │ │ │ └── NotifierEvent.cs
│ │ │ ├── INotifier.cs
│ │ │ ├── INotifierEventData.cs
│ │ │ ├── NotifierPushOptions.cs
│ │ │ ├── NotifierInitOptions.cs
│ │ │ └── NotifierEventData.cs
│ │ ├── IExtensionConfig.cs
│ │ ├── IExtensionInitOptions.cs
│ │ ├── IHealthChecker
│ │ │ ├── HealthStatus.cs
│ │ │ ├── HealthCheckerCheckOptions.cs
│ │ │ ├── IHealthChecker.cs
│ │ │ └── HealthCheckerInitOptions.cs
│ │ ├── ISwitcher
│ │ │ ├── SwitcherSwitchOptions.cs
│ │ │ ├── ISwitcher.cs
│ │ │ └── SwitcherInitOptions.cs
│ │ ├── IExtension.cs
│ │ └── IExtensionStorageProvider.cs
│ ├── Enums
│ │ └── DhafNodeRole.cs
│ ├── ClusterConfig
│ │ ├── Parts
│ │ │ ├── SwitcherDefaultConfig.cs
│ │ │ ├── HealthCheckerDefaultConfig.cs
│ │ │ ├── ClusterEtcdConfig.cs
│ │ │ ├── NotifierDefaultConfig.cs
│ │ │ ├── ClusterServiceEntryPoint.cs
│ │ │ ├── ClusterServiceConfig.cs
│ │ │ └── ClusterDhafConfig.cs
│ │ ├── ClusterConfig.cs
│ │ └── YamlResolving.cs
│ ├── Shell
│ │ ├── ExecResults.cs
│ │ └── Shell.cs
│ ├── Exceptions
│ │ ├── ConfigParsingException.cs
│ │ ├── SwitchFailedException.cs
│ │ └── ExtensionInitFailedException.cs
│ ├── Dhaf.Core.csproj
│ ├── ExtensionsScope
│ │ ├── ExtensionLoadContext.cs
│ │ └── ExtensionsScope.cs
│ └── DhafInternalConfig.cs
├── Dhaf.CLI
│ ├── Interfaces
│ │ └── IConfigPath.cs
│ ├── Definitions.cs
│ ├── VerbOptions
│ │ ├── StatusDhafOptions.cs
│ │ ├── StatusServicesOptions.cs
│ │ ├── NodeDecommissionOptions.cs
│ │ ├── StatusServiceOptions.cs
│ │ ├── SwitchoverPurgeOptions.cs
│ │ ├── SwitchoverCandidatesOptions.cs
│ │ └── SwitchoverToOptions.cs
│ ├── Actions
│ │ ├── SwitchoverTo.cs
│ │ ├── SwitchoverPurge.cs
│ │ ├── NodeDecommission.cs
│ │ ├── SwitchoverCandidates.cs
│ │ ├── StatusDhaf.cs
│ │ ├── ActionBase.cs
│ │ ├── StatusService.cs
│ │ └── StatusServices.cs
│ ├── appsettings.json
│ ├── Program.cs
│ └── Dhaf.CLI.csproj
├── Dhaf.Switchers.Cloudflare
│ ├── extension.json
│ ├── DataTransferObjects
│ │ ├── ErrorDto.cs
│ │ ├── ZoneDto.cs
│ │ ├── DnsRecordDto.cs
│ │ └── ApiResultDtos.cs
│ ├── InternalConfig.cs
│ ├── Config.cs
│ └── Dhaf.Switchers.Cloudflare.csproj
├── Dhaf.Node.DataTransferObjects
│ ├── Node
│ │ ├── DhafNodeStatusRaw.cs
│ │ ├── UpdatePanicModeRelevanceResult.cs
│ │ ├── DhafNodeStatus.cs
│ │ ├── EtcdServiceHealth.cs
│ │ ├── EtcdManualSwitching.cs
│ │ ├── EntryPointStatus.cs
│ │ ├── DecisionOfEntryPointSwitching.cs
│ │ ├── DemotionStatus.cs
│ │ └── PromotionStatus.cs
│ ├── RestApi
│ │ ├── RestApiError.cs
│ │ ├── SwitchoverCandidate.cs
│ │ ├── DhafStatus.cs
│ │ ├── RestApiResponse.cs
│ │ └── ServiceStatus.cs
│ └── Dhaf.Node.DataTransferObjects.csproj
├── Dhaf.Notifiers.Telegram
│ ├── extension.json
│ ├── Config.cs
│ ├── InternalConfig.cs
│ ├── Dhaf.Notifiers.Telegram.csproj
│ ├── StorageActions.cs
│ └── UpdatesProcessing.cs
├── Dhaf.Notifiers.Email
│ ├── extension.json
│ ├── DataTransferObjects
│ │ └── MessageData.cs
│ ├── InternalConfig.cs
│ ├── Config.cs
│ └── Dhaf.Notifiers.Email.csproj
├── Dhaf.HealthCheckers.Tcp
│ ├── extension.json
│ ├── Config.cs
│ ├── InternalConfig.cs
│ ├── Dhaf.HealthCheckers.Tcp.csproj
│ └── TcpHealthChecker.cs
├── Dhaf.Node
│ ├── ArgsOptions.cs
│ ├── RestApi
│ │ ├── RestApiException.cs
│ │ ├── RestApiFactory.cs
│ │ └── RestApiController.cs
│ ├── appsettings.json
│ ├── DhafService.cs
│ ├── nlog.config
│ ├── Dhaf.Node.csproj
│ ├── ExtensionStorageProvider.cs
│ └── Program.cs
├── Dhaf.HealthCheckers.Web
│ ├── extension.json
│ ├── Config.cs
│ ├── DownReasonResolver.cs
│ ├── InternalConfig.cs
│ └── Dhaf.HealthCheckers.Web.csproj
└── Dhaf.sln
├── templates
├── agents
│ └── google-cloud
│ │ ├── services.yaml
│ │ ├── nginx
│ │ ├── dhaf_serv1_ip
│ │ └── nginx_template.conf
│ │ ├── systemd
│ │ └── gc-nginx-agent.service
│ │ ├── gc-nginx-agent.py
│ │ └── README.md
├── dhaf_extensions
│ ├── Dhaf.Templates.Extensions.FooSwitcher
│ │ ├── extension.json
│ │ ├── Config.cs
│ │ ├── InternalConfig.cs
│ │ ├── Dhaf.Templates.Extensions.FooSwitcher.csproj
│ │ └── FooSwitcher.cs
│ ├── Dhaf.Templates.Extensions.FooHealthChecker
│ │ ├── extension.json
│ │ ├── Config.cs
│ │ ├── InternalConfig.cs
│ │ ├── Dhaf.Templates.Extensions.FooHealthChecker.csproj
│ │ └── FooHealthChecker.cs
│ └── Dhaf.Templates.Extensions.sln
├── systemd
│ └── README.md
└── build_example.sh
├── tests
└── Dhaf.Core.Tests
│ ├── Data
│ ├── test_config_issue_25.dhaf
│ └── test_config_1.dhaf
│ ├── Mocks
│ └── ExtensionConfigsMock.cs
│ ├── appsettings.json
│ ├── Dhaf.Core.Tests.csproj
│ └── ClusterConfigParserTest.cs
├── .github
├── _README.md
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
└── workflows
│ ├── dhaf-core-nuget.yml
│ ├── dhaf-release.yml
│ ├── osx-x64.yml
│ ├── win-x64.yml
│ └── linux-x64.yml
├── LICENSE
└── .gitignore
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/nuspecs/Dhaf.Core.readme.md:
--------------------------------------------------------------------------------
1 | # Dhaf.Core
2 | The core package for developing **dhaf** extensions.
--------------------------------------------------------------------------------
/project_identity/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperion-cs/dhaf/HEAD/project_identity/icon.png
--------------------------------------------------------------------------------
/project_identity/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperion-cs/dhaf/HEAD/project_identity/github.png
--------------------------------------------------------------------------------
/src/Dhaf.Switchers.Exec/extension.json:
--------------------------------------------------------------------------------
1 | {
2 | "entryPoint": "Dhaf.Switchers.Exec.dll",
3 | "internalConfiguration": {}
4 | }
--------------------------------------------------------------------------------
/src/Dhaf.HealthCheckers.Exec/extension.json:
--------------------------------------------------------------------------------
1 | {
2 | "entryPoint": "Dhaf.HealthCheckers.Exec.dll",
3 | "internalConfiguration": {}
4 | }
--------------------------------------------------------------------------------
/src/Dhaf.Switchers.GoogleCloud/extension.json:
--------------------------------------------------------------------------------
1 | {
2 | "entryPoint": "Dhaf.Switchers.GoogleCloud.dll",
3 | "internalConfiguration": {}
4 | }
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/Config/ISwitcherConfig.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Core
2 | {
3 | public interface ISwitcherConfig : IExtensionConfig { }
4 | }
5 |
--------------------------------------------------------------------------------
/templates/agents/google-cloud/services.yaml:
--------------------------------------------------------------------------------
1 | - name: serv1
2 | gcmd_key: dhaf_serv1_ip
3 | nginx_subconfig_path: /etc/nginx/dhaf/dhaf_serv1_ip
4 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/Config/IHealthCheckerConfig.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Core
2 | {
3 | public interface IHealthCheckerConfig : IExtensionConfig { }
4 | }
5 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/Config/INotifierInternalConfig.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Core
2 | {
3 | public interface INotifierInternalConfig : IExtensionConfig { }
4 | }
5 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/Config/ISwitcherInternalConfig.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Core
2 | {
3 | public interface ISwitcherInternalConfig : IExtensionConfig { }
4 | }
5 |
--------------------------------------------------------------------------------
/src/Dhaf.CLI/Interfaces/IConfigPath.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.CLI
2 | {
3 | public interface IConfigPath
4 | {
5 | public string Config { get; set; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Enums/DhafNodeRole.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Core
2 | {
3 | public enum DhafNodeRole
4 | {
5 | Leader = 1, Follower = 2, Candidate = 3
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/Config/IHealthCheckerInternalConfig.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Core
2 | {
3 | public interface IHealthCheckerInternalConfig : IExtensionConfig { }
4 | }
5 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/INotifier/Enums/NotifierRole.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Core
2 | {
3 | public enum NotifierRole
4 | {
5 | Leader, Follower
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/Dhaf.CLI/Definitions.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.CLI
2 | {
3 | static class Definitions
4 | {
5 | public const string APPLICATION_ALIAS = "dhaf.cli";
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/INotifier/Enums/NotifierLevel.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Core
2 | {
3 | public enum NotifierLevel
4 | {
5 | Info, Warning, Critical
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/templates/dhaf_extensions/Dhaf.Templates.Extensions.FooSwitcher/extension.json:
--------------------------------------------------------------------------------
1 | {
2 | "entryPoint": "Dhaf.Templates.Extensions.FooSwitcher.dll",
3 | "internalConfiguration": {}
4 | }
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/IExtensionConfig.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Core
2 | {
3 | public interface IExtensionConfig
4 | {
5 | public string ExtensionName { get; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/templates/dhaf_extensions/Dhaf.Templates.Extensions.FooHealthChecker/extension.json:
--------------------------------------------------------------------------------
1 | {
2 | "entryPoint": "Dhaf.Templates.Extensions.FooHealthChecker.dll",
3 | "internalConfiguration": {}
4 | }
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/Config/INotifierConfig.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Core
2 | {
3 | public interface INotifierConfig : IExtensionConfig
4 | {
5 | string Name { get; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/IExtensionInitOptions.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Core
2 | {
3 | public interface IExtensionInitOptions
4 | {
5 | IExtensionStorageProvider Storage { get; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/Dhaf.Switchers.Cloudflare/extension.json:
--------------------------------------------------------------------------------
1 | {
2 | "entryPoint": "Dhaf.Switchers.Cloudflare.dll",
3 | "internalConfiguration": {
4 | "baseUrl": "https://api.cloudflare.com/client/v4/"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/Dhaf.Node.DataTransferObjects/Node/DhafNodeStatusRaw.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Node
2 | {
3 | public class DhafNodeStatusRaw
4 | {
5 | public long LastHeartbeatTimestamp { get; set; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/templates/agents/google-cloud/nginx/dhaf_serv1_ip:
--------------------------------------------------------------------------------
1 | # If necessary, it will be overridden automatically via agent.
2 | # Typically, this file is located in /etc/nginx/dhaf/dhaf_serv1_ip
3 |
4 | set $dhaf_ip "100.10.10.10";
--------------------------------------------------------------------------------
/src/Dhaf.Notifiers.Telegram/extension.json:
--------------------------------------------------------------------------------
1 | {
2 | "entryPoint": "Dhaf.Notifiers.Telegram.dll",
3 | "internalConfiguration": {
4 | "updatesPollingInterval": 2,
5 | "storageSubscribersPath": "subs"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/Dhaf.Node.DataTransferObjects/Node/UpdatePanicModeRelevanceResult.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Node
2 | {
3 | public class UpdatePanicModeRelevanceResult
4 | {
5 | public bool HasStatusChanged { get; set; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/Dhaf.Node.DataTransferObjects/RestApi/RestApiError.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Node
2 | {
3 | public class RestApiError
4 | {
5 | public int Code { get; set; }
6 | public string Message { get; set; }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Dhaf.Notifiers.Email/extension.json:
--------------------------------------------------------------------------------
1 | {
2 | "entryPoint": "Dhaf.Notifiers.Email.dll",
3 | "internalConfiguration": {
4 | "senderName": "Dhaf Notifier",
5 | "securitySslFlag": "ssl",
6 | "timeout": 10000
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/IHealthChecker/HealthStatus.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Core
2 | {
3 | public class HealthStatus
4 | {
5 | public bool Healthy { get; set; }
6 | public int? ReasonCode { get; set; }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Dhaf.HealthCheckers.Tcp/extension.json:
--------------------------------------------------------------------------------
1 | {
2 | "entryPoint": "Dhaf.HealthCheckers.Tcp.dll",
3 | "internalConfiguration": {
4 | "defReceiveTimeout": 5,
5 | "minReceiveTimeout": 1,
6 | "maxReceiveTimeout": 86400
7 | }
8 | }
--------------------------------------------------------------------------------
/src/Dhaf.Node.DataTransferObjects/Node/DhafNodeStatus.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Node
2 | {
3 | public class DhafNodeStatus
4 | {
5 | public string Name { get; set; }
6 | public bool Healthy { get; set; }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Dhaf.Node.DataTransferObjects/RestApi/SwitchoverCandidate.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Node
2 | {
3 | public class SwitchoverCandidate
4 | {
5 | public string Name { get; set; }
6 | public int Priority { get; set; }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Dhaf.Notifiers.Email/DataTransferObjects/MessageData.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Notifiers.Email
2 | {
3 | public class MessageData
4 | {
5 | public string Subject { get; set; }
6 | public string Body { get; set; }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Dhaf.Switchers.Cloudflare/DataTransferObjects/ErrorDto.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Switchers.Cloudflare
2 | {
3 | public class ErrorDto
4 | {
5 | public int Code { get; set; }
6 | public string Message { get; set; }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/ISwitcher/SwitcherSwitchOptions.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Core
2 | {
3 | public class SwitcherSwitchOptions
4 | {
5 | public string EntryPointId { get; set; }
6 | public bool Failover { get; set; }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Dhaf.Switchers.Exec/InternalConfig.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 |
3 | namespace Dhaf.Switchers.Exec
4 | {
5 | public class InternalConfig : ISwitcherInternalConfig
6 | {
7 | public string ExtensionName => "exec";
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Dhaf.HealthCheckers.Exec/InternalConfig.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 |
3 | namespace Dhaf.HealthCheckers.Exec
4 | {
5 | public class InternalConfig : IHealthCheckerInternalConfig
6 | {
7 | public string ExtensionName => "exec";
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/templates/dhaf_extensions/Dhaf.Templates.Extensions.FooSwitcher/Config.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 |
3 | namespace Dhaf.Switchers.Foo
4 | {
5 | public class Config : ISwitcherConfig
6 | {
7 | public string ExtensionName => "foo";
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/IHealthChecker/HealthCheckerCheckOptions.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Core
2 | {
3 | public class HealthCheckerCheckOptions
4 | {
5 | /// Entry point ID.
6 | public string EntryPointId { get; set; }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Dhaf.Switchers.GoogleCloud/InternalConfig.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 |
3 | namespace Dhaf.Switchers.GoogleCloud
4 | {
5 | public class InternalConfig : ISwitcherInternalConfig
6 | {
7 | public string ExtensionName => "google-cloud";
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/templates/dhaf_extensions/Dhaf.Templates.Extensions.FooHealthChecker/Config.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 |
3 | namespace Dhaf.HealthCheckers.Foo
4 | {
5 | public class Config : IHealthCheckerConfig
6 | {
7 | public string ExtensionName => "foo";
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Dhaf.Node/ArgsOptions.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 |
3 | namespace Dhaf.Node
4 | {
5 | public class ArgsOptions
6 | {
7 | [Option('c', "config", Required = true, HelpText = "Configuration file.")]
8 | public string ConfigPath { get; set; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/INotifier/INotifier.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 |
3 | namespace Dhaf.Core
4 | {
5 | public interface INotifier : IExtension
6 | {
7 | Task Init(NotifierInitOptions options);
8 | Task Push(NotifierPushOptions options);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Dhaf.Node.DataTransferObjects/Node/EtcdServiceHealth.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Node
2 | {
3 | public class EtcdServiceHealth
4 | {
5 | public bool Healthy { get; set; }
6 | public long Timestamp { get; set; }
7 | public int? ReasonCode { get; set; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Dhaf.Switchers.Cloudflare/DataTransferObjects/ZoneDto.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Switchers.Cloudflare
2 | {
3 | public class ZoneDto
4 | {
5 | public string Id { get; set; }
6 | public string Status { get; set; }
7 | public bool Paused { get; set; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/INotifier/INotifierEventData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Dhaf.Core
4 | {
5 | public interface INotifierEventData
6 | {
7 | string Service { get; }
8 | string DhafCluster { get; }
9 | DateTime UtcTimestamp { get; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Dhaf.Node.DataTransferObjects/RestApi/DhafStatus.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Dhaf.Node
4 | {
5 | public class DhafStatus
6 | {
7 | public string Leader { get; set; }
8 | public IEnumerable Nodes { get; set; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Dhaf.Switchers.Cloudflare/DataTransferObjects/DnsRecordDto.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Switchers.Cloudflare
2 | {
3 | public class DnsRecordDto
4 | {
5 | public string Id { get; set; }
6 | public string Content { get; set; }
7 | public bool Proxied { get; set; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Dhaf.Switchers.Cloudflare/InternalConfig.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 | namespace Dhaf.Switchers.Cloudflare
3 | {
4 | public class InternalConfig : ISwitcherInternalConfig
5 | {
6 | public string ExtensionName => "cloudflare";
7 | public string BaseUrl { get; set; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/ClusterConfig/Parts/SwitcherDefaultConfig.cs:
--------------------------------------------------------------------------------
1 | using YamlDotNet.Serialization;
2 |
3 | namespace Dhaf.Core
4 | {
5 | public class SwitcherDefaultConfig : ISwitcherConfig
6 | {
7 | [YamlMember(Alias = "type")]
8 | public string ExtensionName { get; set; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Dhaf.Node.DataTransferObjects/Node/EtcdManualSwitching.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Node
2 | {
3 | public class EtcdManualSwitching
4 | {
5 | public string DhafNode { get; set; }
6 |
7 | /// Entry point ID.
8 | public string EpId { get; set; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Dhaf.Switchers.Exec/Config.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 |
3 | namespace Dhaf.Switchers.Exec
4 | {
5 | public class Config : ISwitcherConfig
6 | {
7 | public string ExtensionName => "exec";
8 |
9 | public string Init { get; set; }
10 | public string Switch { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/templates/agents/google-cloud/systemd/gc-nginx-agent.service:
--------------------------------------------------------------------------------
1 | Description=Google Cloud nginx agent
2 | After=network.target
3 |
4 | [Service]
5 | Type=simple
6 | WorkingDirectory=/opt/dhaf/
7 | ExecStart=/usr/bin/python3 /opt/dhaf/gc-nginx-agent.py
8 | Restart=on-failure
9 |
10 | [Install]
11 | WantedBy=multi-user.target
--------------------------------------------------------------------------------
/src/Dhaf.Core/ClusterConfig/Parts/HealthCheckerDefaultConfig.cs:
--------------------------------------------------------------------------------
1 | using YamlDotNet.Serialization;
2 |
3 | namespace Dhaf.Core
4 | {
5 | public class HealthCheckerDefaultConfig : IHealthCheckerConfig
6 | {
7 | [YamlMember(Alias = "type")]
8 | public string ExtensionName { get; set; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Shell/ExecResults.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Core
2 | {
3 | public class ExecResults
4 | {
5 | public string Output { get; set; }
6 | public double TotalExecuteTime { get; set; }
7 | public int ExitCode { get; set; }
8 |
9 | public bool Success { get; set; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/INotifier/NotifierPushOptions.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Core
2 | {
3 | public class NotifierPushOptions
4 | {
5 | public NotifierLevel Level { get; set; }
6 |
7 | public NotifierEvent Event { get; set; }
8 | public INotifierEventData EventData { get; set; }
9 |
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Dhaf.HealthCheckers.Exec/Config.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 |
3 | namespace Dhaf.HealthCheckers.Exec
4 | {
5 | public class Config : IHealthCheckerConfig
6 | {
7 | public string ExtensionName => "exec";
8 |
9 | public string Init { get; set; }
10 | public string Check { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Dhaf.HealthCheckers.Tcp/Config.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 |
3 | namespace Dhaf.HealthCheckers.Tcp
4 | {
5 | public class Config : IHealthCheckerConfig
6 | {
7 | public string ExtensionName => "tcp";
8 |
9 | public int? Port { get; set; }
10 | public int? ReceiveTimeout { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Dhaf.Switchers.Cloudflare/Config.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 |
3 | namespace Dhaf.Switchers.Cloudflare
4 | {
5 | public class Config : ISwitcherConfig
6 | {
7 | public string ExtensionName => "cloudflare";
8 | public string ApiToken { get; set; }
9 | public string Zone { get; set; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tests/Dhaf.Core.Tests/Data/test_config_issue_25.dhaf:
--------------------------------------------------------------------------------
1 | dhaf:
2 | cluster-name: test-cr
3 | node-name: node-1
4 |
5 | services:
6 | - name: serv1
7 | domain: site.com
8 | entry-points:
9 | - name: nc1
10 | ip: 100.1.1.1
11 | switcher:
12 | type: cloudflare
13 | health-checker:
14 | type: web
15 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/ClusterConfig/Parts/ClusterEtcdConfig.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Core
2 | {
3 | public class ClusterEtcdConfig
4 | {
5 | public string Hosts { get; set; }
6 |
7 | public string Username { get; set; }
8 | public string Password { get; set; }
9 |
10 | public int? LeaderKeyTtl { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/ISwitcher/ISwitcher.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 |
3 | namespace Dhaf.Core
4 | {
5 | public interface ISwitcher : IExtension
6 | {
7 | Task Init(SwitcherInitOptions options);
8 | Task Switch(SwitcherSwitchOptions options);
9 |
10 | Task GetCurrentEntryPointId();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/ClusterConfig/Parts/NotifierDefaultConfig.cs:
--------------------------------------------------------------------------------
1 | using YamlDotNet.Serialization;
2 |
3 | namespace Dhaf.Core
4 | {
5 | public class NotifierDefaultConfig : INotifierConfig
6 | {
7 | [YamlMember(Alias = "type")]
8 | public string ExtensionName { get; set; }
9 | public string Name { get => "aa"; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Dhaf.Node.DataTransferObjects/Node/EntryPointStatus.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Dhaf.Node
4 | {
5 | public class EntryPointStatus
6 | {
7 | public string EntryPointId { get; set; }
8 | public bool Healthy { get; set; }
9 |
10 | public IEnumerable Reasons { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Dhaf.Notifiers.Telegram/Config.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 |
3 | namespace Dhaf.Notifiers.Telegram
4 | {
5 | public class Config : INotifierConfig
6 | {
7 | public string ExtensionName => "tg";
8 | public string Name { get; set; }
9 | public string JoinCode { get; set; }
10 | public string Token { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Dhaf.Notifiers.Telegram/InternalConfig.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 |
3 | namespace Dhaf.Notifiers.Telegram
4 | {
5 | public class InternalConfig : INotifierInternalConfig
6 | {
7 | public string ExtensionName => "tg";
8 | public int UpdatesPollingInterval { get; set; }
9 | public string StorageSubscribersPath { get; set; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/ClusterConfig/Parts/ClusterServiceEntryPoint.cs:
--------------------------------------------------------------------------------
1 | using YamlDotNet.Serialization;
2 |
3 | namespace Dhaf.Core
4 | {
5 | public class ClusterServiceEntryPoint
6 | {
7 | [YamlMember(Alias = "name")]
8 | public string Id { get; set; }
9 |
10 | [YamlMember(Alias = "ip")]
11 | public string IP { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Dhaf.Node.DataTransferObjects/Dhaf.Node.DataTransferObjects.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | linux-x64;osx-x64;win-x64
6 | true
7 | 1.2.5
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/Dhaf.Notifiers.Email/InternalConfig.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 |
3 | namespace Dhaf.Notifiers.Email
4 | {
5 | public class InternalConfig : INotifierInternalConfig
6 | {
7 | public string ExtensionName => "email";
8 | public string SenderName { get; set; }
9 | public string SecuritySslFlag { get; set; }
10 | public int Timeout { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/IHealthChecker/IHealthChecker.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 |
3 | namespace Dhaf.Core
4 | {
5 | public interface IHealthChecker : IExtension
6 | {
7 | Task Init(HealthCheckerInitOptions config);
8 | Task Check(HealthCheckerCheckOptions options);
9 |
10 | Task ResolveUnhealthinessReasonCode(int code);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Dhaf.Switchers.GoogleCloud/Config.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 |
3 | namespace Dhaf.Switchers.GoogleCloud
4 | {
5 | public class Config : ISwitcherConfig
6 | {
7 | public string ExtensionName => "google-cloud";
8 |
9 | public string Project { get; set; }
10 | public string MetadataKey { get; set; }
11 | public string CredentialsPath { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/IExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace Dhaf.Core
5 | {
6 | public interface IExtension
7 | {
8 | string ExtensionName { get; }
9 | string Sign { get; }
10 | Type ConfigType { get; }
11 | Type InternalConfigType { get; }
12 |
13 | Task DhafNodeRoleChangedEventHandler(DhafNodeRole role);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Dhaf.Node.DataTransferObjects/Node/DecisionOfEntryPointSwitching.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Node
2 | {
3 | public class DecisionOfEntryPointSwitching
4 | {
5 | public bool IsRequired { get; set; }
6 | public bool Failover { get; set; }
7 |
8 | ///
9 | /// Host ID for switching.
10 | ///
11 | public string SwitchTo { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Dhaf.Node/RestApi/RestApiException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Dhaf.Node
4 | {
5 | public class RestApiException : Exception
6 | {
7 | public int Code { get; }
8 | public override string Message { get; }
9 |
10 | public RestApiException(int code, string message)
11 | {
12 | Code = code;
13 | Message = message;
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Dhaf.HealthCheckers.Tcp/InternalConfig.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 |
3 | namespace Dhaf.HealthCheckers.Tcp
4 | {
5 | public class InternalConfig : IHealthCheckerInternalConfig
6 | {
7 | public string ExtensionName => "tcp";
8 |
9 | public int DefReceiveTimeout { get; set; }
10 | public int MinReceiveTimeout { get; set; }
11 | public int MaxReceiveTimeout { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Dhaf.Node.DataTransferObjects/Node/DemotionStatus.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Node
2 | {
3 | public class DemotionStatus
4 | {
5 | /// Flag the success of the demotion of the current node to cluster follower.
6 | public bool Success { get; set; }
7 |
8 | /// The node name of the current cluster leader.
9 | public string Leader { get; set; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Exceptions/ConfigParsingException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Dhaf.Core
4 | {
5 | public class ConfigParsingException : Exception
6 | {
7 | public int Code { get; }
8 | public override string Message { get; }
9 |
10 | public ConfigParsingException(int code, string message)
11 | {
12 | Code = code;
13 | Message = message;
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.github/_README.md:
--------------------------------------------------------------------------------
1 | Tags serve as the trigger for actions.
2 | Actions (tag assignment) must be running in a way that ensures the release occurs last, e.g.:
3 | ```
4 | vX.Y.Z-core-nuget
5 | vX.Y.Z-linux-x64
6 | vX.Y.Z-osx-x64
7 | vX.Y.Z-win-x64
8 | vX.Y.Z // github release tag
9 | ```
10 |
11 | If a tag is deleted on the origin, it will remain locally (which might cause issues). To remove it, use:
12 | ```sh
13 | git tag -d
14 | ```
15 |
--------------------------------------------------------------------------------
/templates/dhaf_extensions/Dhaf.Templates.Extensions.FooSwitcher/InternalConfig.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 |
3 | namespace Dhaf.Switchers.Foo
4 | {
5 | // The dhaf extension system will automatically populate an
6 | // instance of this class from the internalConfiguration member of the extension.json file.
7 | public class InternalConfig : ISwitcherInternalConfig
8 | {
9 | public string ExtensionName => "foo";
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/ClusterConfig/ClusterConfig.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Dhaf.Core
4 | {
5 | public class ClusterConfig
6 | {
7 | public ClusterDhafConfig Dhaf { get; set; } = new();
8 | public ClusterEtcdConfig Etcd { get; set; } = new();
9 | public List Services { get; set; } = new();
10 | public List Notifiers { get; set; } = new();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Dhaf.Node.DataTransferObjects/RestApi/RestApiResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Dhaf.Node
4 | {
5 | public class RestApiResponse
6 | {
7 | public bool Success { get; set; }
8 |
9 | public IEnumerable Errors { get; set; } = new List();
10 | }
11 |
12 | public class RestApiResponse : RestApiResponse
13 | {
14 | public T Data { get; set; }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/templates/dhaf_extensions/Dhaf.Templates.Extensions.FooHealthChecker/InternalConfig.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 |
3 | namespace Dhaf.HealthCheckers.Foo
4 | {
5 | // The dhaf extension system will automatically populate an
6 | // instance of this class from the internalConfiguration member of the extension.json file.
7 | public class InternalConfig : IHealthCheckerInternalConfig
8 | {
9 | public string ExtensionName => "foo";
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/INotifier/NotifierInitOptions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 |
3 | namespace Dhaf.Core
4 | {
5 | public class NotifierInitOptions : IExtensionInitOptions
6 | {
7 | public ILogger Logger { get; set; }
8 | public INotifierConfig Config { get; set; }
9 | public INotifierInternalConfig InternalConfig { get; set; }
10 | public IExtensionStorageProvider Storage { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/templates/agents/google-cloud/nginx/nginx_template.conf:
--------------------------------------------------------------------------------
1 | server {
2 | # You can add your own changes to the configuration. The main thing is to have reverse proxying to $dhaf_ip.
3 |
4 | server_name site.com;
5 | listen 80;
6 |
7 | include "dhaf/dhaf_serv1_ip"; # Sets $dhaf_ip for the current virtual nginx server.
8 | location / {
9 | proxy_pass http://$dhaf_ip;
10 | }
11 | }
12 |
13 | # ... You can add several virtual servers in the same way.
--------------------------------------------------------------------------------
/src/Dhaf.Core/Exceptions/SwitchFailedException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Dhaf.Core
4 | {
5 | public class SwitchFailedException : Exception
6 | {
7 | public string ExtensionSign { get; }
8 | public override string Message { get => $"Failed to switch with {ExtensionSign}. See log for details."; }
9 |
10 | public SwitchFailedException(string extensionSign)
11 | {
12 | ExtensionSign = extensionSign;
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Exceptions/ExtensionInitFailedException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Dhaf.Core
4 | {
5 | public class ExtensionInitFailedException : Exception
6 | {
7 | public string ExtensionSign { get; }
8 | public override string Message { get => $"Failed to initialize extension {ExtensionSign}. See log for details."; }
9 |
10 | public ExtensionInitFailedException(string extensionSign)
11 | {
12 | ExtensionSign = extensionSign;
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/IExtensionStorageProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 |
4 | namespace Dhaf.Core
5 | {
6 | public interface IExtensionStorageProvider
7 | {
8 | Task PutAsync(string key, string value);
9 |
10 | Task DeleteAsync(string key);
11 | Task DeleteRangeAsync(string keyPrefix);
12 |
13 | Task GetAsyncOfDefault(string key);
14 | Task> GetRangeAsync(string keyPrefix);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/ClusterConfig/Parts/ClusterServiceConfig.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using YamlDotNet.Serialization;
3 |
4 | namespace Dhaf.Core
5 | {
6 | public class ClusterServiceConfig
7 | {
8 | public string Name { get; set; }
9 | public string Domain { get; set; }
10 |
11 | public List EntryPoints { get; set; } = new();
12 | public ISwitcherConfig Switcher { get; set; }
13 | public IHealthCheckerConfig HealthChecker { get; set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Dhaf.Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | linux-x64;osx-x64;win-x64
6 | true
7 | 1.2.5
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/ISwitcher/SwitcherInitOptions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 |
3 | namespace Dhaf.Core
4 | {
5 | public class SwitcherInitOptions : IExtensionInitOptions
6 | {
7 | public ILogger Logger { get; set; }
8 | public ISwitcherConfig Config { get; set; }
9 | public ISwitcherInternalConfig InternalConfig { get; set; }
10 | public ClusterServiceConfig ClusterServiceConfig { get; set; }
11 | public IExtensionStorageProvider Storage { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/IHealthChecker/HealthCheckerInitOptions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 |
3 | namespace Dhaf.Core
4 | {
5 | public class HealthCheckerInitOptions : IExtensionInitOptions
6 | {
7 | public ILogger Logger { get; set; }
8 | public IHealthCheckerConfig Config { get; set; }
9 | public IHealthCheckerInternalConfig InternalConfig { get; set; }
10 | public ClusterServiceConfig ClusterServiceConfig { get; set; }
11 | public IExtensionStorageProvider Storage { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/INotifier/Enums/NotifierEvent.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Core
2 | {
3 | public enum NotifierEvent
4 | {
5 | // Note: "Ep" is "Entry point".
6 |
7 | EpDown, EpUp, // EpHealthChanged
8 | Failover, Switchover, Switching, // CurrentEpChanged
9 | SwitchoverPurged, // SwitchoverPurged
10 | ServiceUp, ServiceDown, // ServiceHealthChanged
11 | DhafNodeUp, DhafNodeDown, // DhafNodeHealthChanged
12 | DhafNewLeader // DhafNewLeader
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Dhaf.Node.DataTransferObjects/Node/PromotionStatus.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Node
2 | {
3 | public class PromotionStatus
4 | {
5 | /// Flag the success of the promotion of the current node to cluster leader.
6 | public bool Success { get; set; }
7 |
8 | /// The node name of the current cluster leader.
9 | public string Leader { get; set; }
10 |
11 | /// If it was successful to become a leader, it contains the lease id of the leader's key.
12 | public long? LeaderLeaseId { get; set; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Dhaf.Node.DataTransferObjects/RestApi/ServiceStatus.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Dhaf.Node
4 | {
5 | public class ServiceStatus
6 | {
7 | public string Name { get; set; }
8 | public string Domain { get; set; }
9 | public string CurrentEntryPointName { get; set; }
10 | public IEnumerable EntryPoints { get; set; }
11 | public string SwitchoverRequirement { get; set; }
12 | }
13 |
14 | public class ServiceEntryPointStatus
15 | {
16 | public string Name { get; set; }
17 | public bool Healthy { get; set; }
18 | public int Priority { get; set; }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Dhaf.Notifiers.Email/Config.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 |
3 | namespace Dhaf.Notifiers.Email
4 | {
5 | public class Config : INotifierConfig
6 | {
7 | public string ExtensionName => "email";
8 | public string Name { get; set; }
9 | public SmtpConfig Smtp { get; set; }
10 |
11 | public string From { get; set; }
12 | public string To { get; set; }
13 | }
14 |
15 | public class SmtpConfig
16 | {
17 | public string Server { get; set; }
18 | public int Port { get; set; }
19 | public string Security { get; set; }
20 | public string Username { get; set; }
21 | public string Password { get; set; }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/ClusterConfig/Parts/ClusterDhafConfig.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Core
2 | {
3 | public class ClusterDhafConfig
4 | {
5 | public string ClusterName { get; set; }
6 | public string NodeName { get; set; }
7 |
8 | public int? HealthyNodeStatusTtl { get; set; }
9 | public int? HeartbeatInterval { get; set; }
10 | public int? TactInterval { get; set; }
11 | public int? TactPostSwitchDelay { get; set; }
12 |
13 | public ClusterDhafWebApiConfig WebApi { get; set; } = new();
14 | }
15 |
16 | public class ClusterDhafWebApiConfig
17 | {
18 | public string Host { get; set; }
19 | public int? Port { get; set; }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/src/Dhaf.HealthCheckers.Web/extension.json:
--------------------------------------------------------------------------------
1 | {
2 | "entryPoint": "Dhaf.HealthCheckers.Web.dll",
3 | "internalConfiguration": {
4 | "defHttpPort": 80,
5 | "defHttpsPort": 443,
6 | "httpSchema": "http",
7 | "httpsSchema": "https",
8 |
9 | "defMethod": "GET",
10 | "defPath": "",
11 | "defHeaders": {},
12 | "defFollowRedirects": false,
13 | "defTimeout": 5,
14 | "defRetries": 2,
15 | "defExpectedCodes": "200",
16 | "expectedCodesSeparator": ",",
17 | "expectedCodesWildcard": "X",
18 | "defExpectedResponseBody": "",
19 |
20 | "minTimeout": 1,
21 | "maxTimeout": 86400,
22 |
23 | "defDomainForwarding": true,
24 | "defIgnoreSslErrors": false,
25 |
26 | "hostHeader": "Host"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/templates/dhaf_extensions/Dhaf.Templates.Extensions.FooSwitcher/Dhaf.Templates.Extensions.FooSwitcher.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net7.0
4 | linux-x64;osx-x64;win-x64
5 | true
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Always
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/templates/dhaf_extensions/Dhaf.Templates.Extensions.FooHealthChecker/Dhaf.Templates.Extensions.FooHealthChecker.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net7.0
4 | linux-x64;osx-x64;win-x64
5 | true
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Always
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.github/workflows/dhaf-core-nuget.yml:
--------------------------------------------------------------------------------
1 | name: Dhaf.Core Nuget (Dev)
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*.*.*-core-nuget'
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v2
14 | - name: setup .net
15 | uses: actions/setup-dotnet@v1
16 | with:
17 | dotnet-version: 9.0.x
18 |
19 | - name: prepare nuget package
20 | run: dotnet pack src/Dhaf.Core/Dhaf.Core.csproj --configuration Release -p:NuspecFile=../../nuspecs/Dhaf.Core.nuspec --output nupkgs
21 |
22 | - name: get the package file path
23 | run: echo "PKG_FILE=$(ls nupkgs | head -n 1)" >> $GITHUB_ENV
24 |
25 | - name: push package
26 | run: dotnet nuget push "nupkgs/${{ env.PKG_FILE }}" --api-key "${{ secrets.NUGET_TOKEN }}" --source https://api.nuget.org/v3/index.json
--------------------------------------------------------------------------------
/src/Dhaf.CLI/VerbOptions/StatusDhafOptions.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 | using CommandLine.Text;
3 | using System.Collections.Generic;
4 |
5 | namespace Dhaf.CLI
6 | {
7 | [Verb("status-dhaf", HelpText = "Show dhaf cluster status information.")]
8 | public class StatusDhafOptions : IConfigPath
9 | {
10 | [Option('c', "config", Required = true, HelpText = "Configuration file.")]
11 | public string Config { get; set; }
12 |
13 | [Usage(ApplicationAlias = Definitions.APPLICATION_ALIAS)]
14 | public static IEnumerable Examples
15 | {
16 | get
17 | {
18 | yield return new Example("Show dhaf cluster status information using configuration file ",
19 | new StatusDhafOptions { Config = "" });
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '...'
17 | 3. See error
18 |
19 | **Expected behavior**
20 | A clear and concise description of what you expected to happen.
21 |
22 | **Code/Config**
23 | ```
24 | If applicable, add code to help explain your problem.
25 | ```
26 |
27 | **Screenshots**
28 | If applicable, add screenshots to help explain your problem.
29 |
30 | **Stand (please complete the following information):**
31 | - OS: [e.g. Ubuntu 20.04]
32 | - Dhaf version: [e.g. 1.0.0]
33 |
34 | **Additional context**
35 | Add any other context about the problem here.
36 |
--------------------------------------------------------------------------------
/src/Dhaf.CLI/VerbOptions/StatusServicesOptions.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 | using CommandLine.Text;
3 | using System.Collections.Generic;
4 |
5 | namespace Dhaf.CLI
6 | {
7 | [Verb("status-services", HelpText = "Show all services status information.")]
8 | public class StatusServicesOptions : IConfigPath
9 | {
10 | [Option('c', "config", Required = true, HelpText = "Configuration file.")]
11 | public string Config { get; set; }
12 |
13 | [Usage(ApplicationAlias = Definitions.APPLICATION_ALIAS)]
14 | public static IEnumerable Examples
15 | {
16 | get
17 | {
18 | yield return new Example("Show all services status information using configuration file ",
19 | new StatusServiceOptions { Config = "" });
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Dhaf.HealthCheckers.Web/Config.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 | using System.Collections.Generic;
3 |
4 | namespace Dhaf.HealthCheckers.Web
5 | {
6 | public class Config : IHealthCheckerConfig
7 | {
8 | public string ExtensionName => "web";
9 | public string Schema { get; set; }
10 | public string Method { get; set; }
11 | public int? Port { get; set; }
12 | public string Path { get; set; }
13 | public Dictionary Headers { get; set; }
14 | public bool? FollowRedirects { get; set; }
15 | public int? Timeout { get; set; }
16 | public int? Retries { get; set; }
17 | public string ExpectedCodes { get; set; }
18 | public string ExpectedResponseBody { get; set; }
19 | public bool? DomainForwarding { get; set; }
20 | public bool? IgnoreSslErrors { get; set; }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Dhaf.CLI/Actions/SwitchoverTo.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Node;
2 | using RestSharp;
3 | using System;
4 | using System.Threading.Tasks;
5 |
6 | namespace Dhaf.CLI
7 | {
8 | public static partial class Actions
9 | {
10 | public static async Task ExecuteSwitchoverToAndReturnExitCode(SwitchoverToOptions opt)
11 | {
12 | await PrepareRestClient(opt);
13 | var request = new RestRequest($"switchover?entryPointId={opt.EpName}&serviceName={opt.ServiceName}");
14 |
15 | var response = await _restClient.GetAsync(request);
16 | if (!response.Success)
17 | {
18 | PrintErrors(response.Errors);
19 | return -1;
20 | }
21 |
22 | Console.WriteLine($"OK. A request for a switchover to has been sent.");
23 |
24 | return 0;
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Dhaf.CLI/Actions/SwitchoverPurge.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Node;
2 | using RestSharp;
3 | using System;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace Dhaf.CLI
8 | {
9 | public static partial class Actions
10 | {
11 | public static async Task ExecuteSwitchoverPurgeAndReturnExitCode(SwitchoverPurgeOptions opt)
12 | {
13 | await PrepareRestClient(opt);
14 | var request = new RestRequest($"switchover/purge?serviceName={opt.ServiceName}");
15 |
16 | var response = await _restClient.GetAsync(request);
17 | if (!response.Success)
18 | {
19 | PrintErrors(response.Errors);
20 | return -1;
21 | }
22 |
23 | Console.WriteLine("OK. The switchover requirement has been purged.");
24 |
25 | return 0;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Dhaf.CLI/Actions/NodeDecommission.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Node;
2 | using RestSharp;
3 | using System;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace Dhaf.CLI
8 | {
9 | public static partial class Actions
10 | {
11 | public static async Task ExecuteNodeDecommissionAndReturnExitCode(NodeDecommissionOptions opt)
12 | {
13 | await PrepareRestClient(opt);
14 | var request = new RestRequest($"dhaf/node/decommission?name={opt.NodeName}");
15 |
16 | var response = await _restClient.GetAsync(request);
17 | if (!response.Success)
18 | {
19 | PrintErrors(response.Errors);
20 | return -1;
21 | }
22 |
23 | Console.WriteLine($"OK. The dhaf node has been successfully decommissioned.");
24 |
25 | return 0;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/templates/systemd/README.md:
--------------------------------------------------------------------------------
1 | # Systemd Template
2 |
3 | 1. Create systemd service file (it's standard path for the most Linux distros, but you should check it before):
4 | ```shell
5 | nano /etc/systemd/system/dhaf.service
6 |
7 | ```
8 | 2. Edit this basic service (especially paths and params):
9 | ```shell
10 | [Unit]
11 | Description=Dhaf
12 | After=network.target
13 |
14 | [Service]
15 | Type=simple
16 | WorkingDirectory=/opt/dhaf
17 | ExecStart=/opt/dhaf/dhaf.node -c config-n1.dhaf
18 | Restart=on-failure
19 |
20 | [Install]
21 | WantedBy=multi-user.target
22 |
23 | ```
24 | 3. Reload daemons:
25 | ```shell
26 | systemctl daemon-reload
27 |
28 | ```
29 | 4. Test dhaf service:
30 | ```shell
31 | systemctl restart dhaf.service
32 | # Check status, it should be active
33 | systemctl status dhaf.service
34 |
35 | ```
36 | 5. Enable it, to autostart service after reboot:
37 | ```shell
38 | systemctl enable dhaf.service
39 |
40 | ```
41 |
--------------------------------------------------------------------------------
/src/Dhaf.CLI/VerbOptions/NodeDecommissionOptions.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 | using CommandLine.Text;
3 | using System.Collections.Generic;
4 |
5 | namespace Dhaf.CLI
6 | {
7 | [Verb("node-decommission", HelpText = "Decommission the dhaf node.")]
8 | public class NodeDecommissionOptions : IConfigPath
9 | {
10 | [Option('c', "config", Required = true, HelpText = "Configuration file.")]
11 | public string Config { get; set; }
12 |
13 | [Value(0, Required = true, HelpText = "Node name.")]
14 | public string NodeName { get; set; }
15 |
16 |
17 | [Usage(ApplicationAlias = Definitions.APPLICATION_ALIAS)]
18 | public static IEnumerable Examples
19 | {
20 | get
21 | {
22 | yield return new Example("Decommission the dhaf node using configuration file ",
23 | new NodeDecommissionOptions { Config = "", NodeName = "n1" });
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/nuspecs/Dhaf.Core.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Dhaf.Core
5 | 1.2.5
6 | The core package for developing dhaf extensions.
7 | Petr Osetrov
8 | dhaf core extension cluster high availability fault tolerance
9 |
10 | false
11 | MIT
12 | Copyright © 2024 Petr Osetrov
13 | docs/Dhaf.Core.readme.md
14 | images\icon.png
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/Dhaf.Switchers.Cloudflare/DataTransferObjects/ApiResultDtos.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace Dhaf.Switchers.Cloudflare
5 | {
6 | public abstract class ResultBaseDto where T : class
7 | {
8 | public bool Success { get; set; }
9 | public List Errors { get; set; }
10 |
11 | public string PrettyErrors(string action)
12 | {
13 | var separator = "\n";
14 | var intro = $"Errors occurred ({Errors.Count} pcs.) in the action \"{action}\":";
15 | var errors = string.Join(separator, Errors.Select(x => $"Error {x.Code}: {x.Message}"));
16 |
17 | return $"{intro}\n{errors}";
18 | }
19 | }
20 |
21 | public class ResultDto : ResultBaseDto where T : class
22 | {
23 | public T Result { get; set; }
24 | }
25 |
26 | public class ResultCollectionDto : ResultBaseDto where T : class
27 | {
28 | public List Result { get; set; }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/Dhaf.Core.Tests/Mocks/ExtensionConfigsMock.cs:
--------------------------------------------------------------------------------
1 | namespace Dhaf.Core.Tests
2 | {
3 | public class SwitcherConfigMock : ISwitcherConfig
4 | {
5 | public string ExtensionName => "a";
6 | }
7 |
8 | public class SwitcherInternalConfigMock : ISwitcherInternalConfig
9 | {
10 | public string ExtensionName => "a";
11 | }
12 |
13 | public class HealthCheckerConfigMock : IHealthCheckerConfig
14 | {
15 | public string ExtensionName => "a";
16 | }
17 |
18 | public class HealthCheckerInternalConfigMock : IHealthCheckerInternalConfig
19 | {
20 | public string ExtensionName => "a";
21 | }
22 |
23 | public class NotifierConfigMock : INotifierConfig
24 | {
25 | public string ExtensionName => "a";
26 |
27 | public string Name => "a";
28 | }
29 | public class NotifierInternalConfigMock : INotifierInternalConfig
30 | {
31 | public string ExtensionName => "a";
32 |
33 | public string DefName => "a";
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Dhaf.CLI/VerbOptions/StatusServiceOptions.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 | using CommandLine.Text;
3 | using System.Collections.Generic;
4 |
5 | namespace Dhaf.CLI
6 | {
7 | [Verb("status-service", HelpText = "Show service status information.")]
8 | public class StatusServiceOptions : IConfigPath
9 | {
10 | [Option('c', "config", Required = true, HelpText = "Configuration file.")]
11 | public string Config { get; set; }
12 |
13 | [Option('s', "service", Required = true, HelpText = "Service name.")]
14 | public string ServiceName { get; set; }
15 |
16 | [Usage(ApplicationAlias = Definitions.APPLICATION_ALIAS)]
17 | public static IEnumerable Examples
18 | {
19 | get
20 | {
21 | yield return new Example("Show service status information using configuration file ",
22 | new StatusServiceOptions { Config = "", ServiceName = "" });
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Dhaf.CLI/VerbOptions/SwitchoverPurgeOptions.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 | using CommandLine.Text;
3 | using System.Collections.Generic;
4 |
5 | namespace Dhaf.CLI
6 | {
7 | [Verb("switchover-purge", HelpText = "Purge the switchover requirement.")]
8 | public class SwitchoverPurgeOptions : IConfigPath
9 | {
10 | [Option('c', "config", Required = true, HelpText = "Configuration file.")]
11 | public string Config { get; set; }
12 |
13 | [Option('s', "service", Required = true, HelpText = "Service name.")]
14 | public string ServiceName { get; set; }
15 |
16 | [Usage(ApplicationAlias = Definitions.APPLICATION_ALIAS)]
17 | public static IEnumerable Examples
18 | {
19 | get
20 | {
21 | yield return new Example("Purge the switchover requirement in service using configuration file ",
22 | new SwitchoverPurgeOptions { Config = "", ServiceName = "" });
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/Dhaf.Core.Tests/Data/test_config_1.dhaf:
--------------------------------------------------------------------------------
1 | dhaf:
2 | cluster-name: test-cr
3 | node-name: node-1
4 | web-api:
5 | host: localhost
6 | port: 8128
7 |
8 | etcd:
9 | hosts: http://11.22.33.44:2379
10 |
11 | services:
12 | - name: serv1
13 | domain: site.com
14 | entry-points:
15 | - name: nc1
16 | ip: 100.1.1.1
17 | - name: nc2
18 | ip: 100.1.1.2
19 | - name: nc3
20 | ip: 100.1.1.3
21 | switcher:
22 | type: cloudflare
23 | api-token: aaa
24 | zone: site.com
25 | health-checker:
26 | type: web
27 | schema: http
28 |
29 | - name: serv2
30 | domain: foo.site.com
31 | entry-points:
32 | - name: nc1
33 | ip: 120.1.1.1
34 | - name: nc2
35 | ip: 120.1.1.2
36 | switcher:
37 | type: cloudflare
38 | api-token: bbb
39 | zone: site.com
40 | health-checker:
41 | type: web
42 | schema: http
43 |
44 | notifiers:
45 | - type: tg
46 | token: tg-token-aaa
47 | join-code: w2r6KPSgf2SnD6yM
48 | name: ntf-tg-1
49 |
--------------------------------------------------------------------------------
/src/Dhaf.CLI/VerbOptions/SwitchoverCandidatesOptions.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 | using CommandLine.Text;
3 | using System.Collections.Generic;
4 |
5 | namespace Dhaf.CLI
6 | {
7 | [Verb("switchover-candidates", HelpText = "Show suitable entry points for switchover.")]
8 | public class SwitchoverCandidatesOptions : IConfigPath
9 | {
10 | [Option('c', "config", Required = true, HelpText = "Configuration file.")]
11 | public string Config { get; set; }
12 |
13 | [Option('s', "service", Required = true, HelpText = "Service name.")]
14 | public string ServiceName { get; set; }
15 |
16 | [Usage(ApplicationAlias = Definitions.APPLICATION_ALIAS)]
17 | public static IEnumerable Examples
18 | {
19 | get
20 | {
21 | yield return new Example("Show suitable entry points in service for switchover using configuration file ",
22 | new SwitchoverCandidatesOptions { Config = "", ServiceName = "" });
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Shell/Shell.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | namespace Dhaf.Core
4 | {
5 | public static class Shell
6 | {
7 | public static ExecResults Exec(string fileName, string args = "")
8 | {
9 | var psi = new ProcessStartInfo(fileName, args)
10 | {
11 | RedirectStandardOutput = true,
12 | };
13 |
14 | var proc = Process.Start(psi);
15 | if (proc == null)
16 | {
17 | return new ExecResults { Success = false };
18 | }
19 |
20 | var output = proc.StandardOutput
21 | .ReadToEnd()
22 | .TrimEnd('\r', '\n');
23 |
24 | proc.WaitForExit();
25 |
26 | var totalExecTime = (proc.ExitTime - proc.StartTime).TotalMilliseconds;
27 |
28 |
29 | return new ExecResults
30 | {
31 | Success = true,
32 | ExitCode = proc.ExitCode,
33 | TotalExecuteTime = totalExecTime,
34 | Output = output
35 | };
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Petr Osetrov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/Dhaf.CLI/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "defHeartbeatInterval": 5,
3 | "defTactInterval": 10,
4 | "tactIntervalMin": 1,
5 | "tactIntervalMax": 3600,
6 | "defTactPostSwitchDelay": 0,
7 | "tactPostSwitchDelayMin": 0,
8 | "tactPostSwitchDelayMax": 3600,
9 | "defHealthyNodeStatusTtl": 30,
10 | "nameMaxLength": 64,
11 | "extensions": [
12 | "health-checkers/web",
13 | "health-checkers/exec",
14 | "health-checkers/tcp",
15 | "switchers/cloudflare",
16 | "switchers/google-cloud",
17 | "switchers/exec",
18 | "notifiers/email",
19 | "notifiers/tg"
20 | ],
21 | "etcd": {
22 | "leaderPath": "leader",
23 | "defLeaderKeyTtl": 15,
24 | "nodesPath": "nodes/",
25 | "healthPath": "health/",
26 | "switchoverPath": "switchover/",
27 | "extensionStoragePath": "extensions",
28 | "extensionStorageHcPrefix": "hc/",
29 | "extensionStorageSwPrefix": "sw/",
30 | "extensionStorageNtfPrefix": "ntf/"
31 | },
32 | "webApi": {
33 | "DefHost": "localhost",
34 | "DefPort": 8128
35 | },
36 | "Logging": {
37 | "LogLevel": {
38 | "Default": "Trace",
39 | "Microsoft": "Warning"
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Dhaf.Node/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "defHeartbeatInterval": 5,
3 | "defTactInterval": 10,
4 | "tactIntervalMin": 1,
5 | "tactIntervalMax": 3600,
6 | "defTactPostSwitchDelay": 0,
7 | "tactPostSwitchDelayMin": 0,
8 | "tactPostSwitchDelayMax": 3600,
9 | "defHealthyNodeStatusTtl": 30,
10 | "nameMaxLength": 64,
11 | "extensions": [
12 | "health-checkers/web",
13 | "health-checkers/exec",
14 | "health-checkers/tcp",
15 | "switchers/cloudflare",
16 | "switchers/google-cloud",
17 | "switchers/exec",
18 | "notifiers/email",
19 | "notifiers/tg"
20 | ],
21 | "etcd": {
22 | "leaderPath": "leader",
23 | "defLeaderKeyTtl": 15,
24 | "nodesPath": "nodes/",
25 | "healthPath": "health/",
26 | "switchoverPath": "switchover/",
27 | "extensionStoragePath": "extensions",
28 | "extensionStorageHcPrefix": "hc/",
29 | "extensionStorageSwPrefix": "sw/",
30 | "extensionStorageNtfPrefix": "ntf/"
31 | },
32 | "webApi": {
33 | "DefHost": "localhost",
34 | "DefPort": 8128
35 | },
36 | "Logging": {
37 | "LogLevel": {
38 | "Default": "Trace",
39 | "Microsoft": "Warning"
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Dhaf.Switchers.Exec/Dhaf.Switchers.Exec.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | linux-x64;osx-x64;win-x64
6 | true
7 | 1.2.5
8 | true
9 |
10 |
11 |
12 |
13 | false
14 | runtime
15 |
16 |
17 |
18 |
19 |
20 | Always
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/tests/Dhaf.Core.Tests/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "defHeartbeatInterval": 5,
3 | "defTactInterval": 10,
4 | "tactIntervalMin": 1,
5 | "tactIntervalMax": 3600,
6 | "defTactPostSwitchDelay": 0,
7 | "tactPostSwitchDelayMin": 0,
8 | "tactPostSwitchDelayMax": 3600,
9 | "defHealthyNodeStatusTtl": 30,
10 | "nameMaxLength": 64,
11 | "extensions": [
12 | "health-checkers/web",
13 | "health-checkers/exec",
14 | "health-checkers/tcp",
15 | "switchers/cloudflare",
16 | "switchers/google-cloud",
17 | "switchers/exec",
18 | "notifiers/email",
19 | "notifiers/tg"
20 | ],
21 | "etcd": {
22 | "leaderPath": "leader",
23 | "defLeaderKeyTtl": 15,
24 | "nodesPath": "nodes/",
25 | "healthPath": "health/",
26 | "switchoverPath": "switchover/",
27 | "extensionStoragePath": "extensions",
28 | "extensionStorageHcPrefix": "hc/",
29 | "extensionStorageSwPrefix": "sw/",
30 | "extensionStorageNtfPrefix": "ntf/"
31 | },
32 | "webApi": {
33 | "DefHost": "localhost",
34 | "DefPort": 8128
35 | },
36 | "Logging": {
37 | "LogLevel": {
38 | "Default": "Trace",
39 | "Microsoft": "Warning"
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Dhaf.HealthCheckers.Exec/Dhaf.HealthCheckers.Exec.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | linux-x64;osx-x64;win-x64
6 | true
7 | 1.2.5
8 | true
9 |
10 |
11 |
12 |
13 | false
14 | runtime
15 |
16 |
17 |
18 |
19 |
20 | Always
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/Dhaf.HealthCheckers.Tcp/Dhaf.HealthCheckers.Tcp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | linux-x64;osx-x64;win-x64
6 | true
7 | 1.2.5
8 | true
9 |
10 |
11 |
12 |
13 | false
14 | runtime
15 |
16 |
17 |
18 |
19 |
20 | Always
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/Dhaf.CLI/VerbOptions/SwitchoverToOptions.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 | using CommandLine.Text;
3 | using System.Collections.Generic;
4 |
5 | namespace Dhaf.CLI
6 | {
7 | [Verb("switchover-to", HelpText = "Switchover to the specified entry point.")]
8 | public class SwitchoverToOptions : IConfigPath
9 | {
10 | [Option('c', "config", Required = true, HelpText = "Configuration file.")]
11 | public string Config { get; set; }
12 |
13 | [Option('s', "service", Required = true, HelpText = "Service name.")]
14 | public string ServiceName { get; set; }
15 |
16 | [Value(0, Required = true, HelpText = "Entry point name.")]
17 | public string EpName { get; set; }
18 |
19 | [Usage(ApplicationAlias = Definitions.APPLICATION_ALIAS)]
20 | public static IEnumerable Examples
21 | {
22 | get
23 | {
24 | yield return new Example("Switchover to the specified entry point in service using configuration file ",
25 | new SwitchoverToOptions { EpName = "ep-1", Config = "", ServiceName = "" });
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/Interfaces/INotifier/NotifierEventData.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 | using System;
3 | using System.Collections.Generic;
4 |
5 | namespace Dhaf.NotifierEventData
6 | {
7 | public class Base : INotifierEventData
8 | {
9 | public DateTime UtcTimestamp { get; set; }
10 |
11 | public string DhafCluster { get; set; }
12 | public string Service { get; set; }
13 | }
14 |
15 | // Note: "Ep" is "Entry point".
16 |
17 | public class EpHealthChanged : Base
18 | {
19 | public string EpName { get; set; }
20 | public IEnumerable Reasons { get; set; }
21 | }
22 |
23 | public class CurrentEpChanged : Base
24 | {
25 | public string FromEp { get; set; }
26 | public string ToEp { get; set; }
27 | }
28 |
29 | public class SwitchoverPurged : Base
30 | {
31 | public string SwitchoverEp { get; set; }
32 | }
33 |
34 | public class DhafNewLeader : Base
35 | {
36 | public string Leader { get; set; }
37 | }
38 |
39 | public class DhafNodeHealthChanged : Base
40 | {
41 | public string NodeName { get; set; }
42 | }
43 |
44 | public class ServiceHealthChanged : Base { }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Dhaf.Notifiers.Email/Dhaf.Notifiers.Email.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | linux-x64;osx-x64;win-x64
6 | true
7 | 1.2.5
8 | true
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | false
18 | runtime
19 |
20 |
21 |
22 |
23 |
24 | Always
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/Dhaf.HealthCheckers.Web/DownReasonResolver.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Dhaf.HealthCheckers.Web
4 | {
5 | public enum DownReason
6 | {
7 | Timeout = 0,
8 | UnexpectedHttpCode = 1,
9 | UnexpectedResponseBody = 2,
10 | NotCompleted = 3,
11 | SslPolicyErrors = 4,
12 | NetworkOrHttpFrameworkException = 5
13 | }
14 |
15 | public static class DownReasonResolver
16 | {
17 | public static string Resolve(int code)
18 | {
19 | var x = (DownReason)code;
20 |
21 | if (!Map.ContainsKey(x))
22 | {
23 | return "Unexpected reason";
24 | }
25 |
26 | return Map[x];
27 | }
28 |
29 | private static Dictionary Map { get; set; } = new()
30 | {
31 | { DownReason.Timeout, "HTTP timeout occurred" },
32 | { DownReason.UnexpectedHttpCode, "Unexpected http code" },
33 | { DownReason.UnexpectedResponseBody, "Unexpected response body" },
34 | { DownReason.NotCompleted, "Not completed" },
35 | { DownReason.SslPolicyErrors, "Ssl policy errors" },
36 | { DownReason.NetworkOrHttpFrameworkException, "Network or http framework exception" }
37 | };
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Dhaf.Switchers.GoogleCloud/Dhaf.Switchers.GoogleCloud.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | linux-x64;osx-x64;win-x64
6 | true
7 | 1.2.5
8 | true
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | false
18 | runtime
19 |
20 |
21 |
22 |
23 |
24 | Always
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/Dhaf.HealthCheckers.Web/InternalConfig.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 | using System.Collections.Generic;
3 |
4 | namespace Dhaf.HealthCheckers.Web
5 | {
6 | public class InternalConfig : IHealthCheckerInternalConfig
7 | {
8 | public string ExtensionName => "web";
9 | public int DefHttpPort { get; set; }
10 | public int DefHttpsPort { get; set; }
11 | public string HttpSchema { get; set; }
12 | public string HttpsSchema { get; set; }
13 |
14 | public string DefMethod { get; set; }
15 | public string DefPath { get; set; }
16 | public Dictionary DefHeaders { get; set; }
17 | public bool DefFollowRedirects { get; set; }
18 | public int DefTimeout { get; set; }
19 | public int DefRetries { get; set; }
20 |
21 | public string DefExpectedCodes { get; set; }
22 | public string ExpectedCodesSeparator { get; set; }
23 | public string ExpectedCodesWildcard { get; set; }
24 |
25 | public string DefExpectedResponseBody { get; set; }
26 |
27 | public int MinTimeout { get; set; }
28 | public int MaxTimeout { get; set; }
29 |
30 | public bool DefDomainForwarding { get; set; }
31 | public bool DefIgnoreSslErrors { get; set; }
32 |
33 | public string HostHeader { get; set; }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Dhaf.Notifiers.Telegram/Dhaf.Notifiers.Telegram.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | linux-x64;osx-x64;win-x64
6 | true
7 | 1.2.5
8 | true
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | false
19 | runtime
20 |
21 |
22 |
23 |
24 |
25 | Always
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/Dhaf.CLI/Program.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 | using System.Threading.Tasks;
3 |
4 | namespace Dhaf.CLI
5 | {
6 | class Program
7 | {
8 | static async Task Main(string[] args)
9 | {
10 | await Parser.Default.ParseArguments(args)
14 | .MapResult
15 | (
16 | (StatusDhafOptions opts) => Actions.ExecuteStatusDhafAndReturnExitCode(opts),
17 | (StatusServiceOptions opts) => Actions.ExecuteStatusServiceAndReturnExitCode(opts),
18 | (StatusServicesOptions opts) => Actions.ExecuteStatusServicesAndReturnExitCode(opts),
19 | (SwitchoverCandidatesOptions opts) => Actions.ExecuteSwitchoverCandidatesAndReturnExitCode(opts),
20 | (SwitchoverToOptions opts) => Actions.ExecuteSwitchoverToAndReturnExitCode(opts),
21 | (SwitchoverPurgeOptions opts) => Actions.ExecuteSwitchoverPurgeAndReturnExitCode(opts),
22 | (NodeDecommissionOptions opts) => Actions.ExecuteNodeDecommissionAndReturnExitCode(opts),
23 | errs => Task.FromResult(0)
24 | );
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Dhaf.Switchers.Cloudflare/Dhaf.Switchers.Cloudflare.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | linux-x64;osx-x64;win-x64
6 | true
7 | 1.2.5
8 | true
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | false
19 | runtime
20 |
21 |
22 |
23 |
24 |
25 | Always
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/Dhaf.CLI/Dhaf.CLI.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net9.0
6 | linux-x64;osx-x64;win-x64
7 | true
8 | 1.2.5
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | PreserveNewest
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/Dhaf.Notifiers.Telegram/StorageActions.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace Dhaf.Notifiers.Telegram
8 | {
9 | public partial class TelegramNotifier : INotifier
10 | {
11 | protected async Task PutSubscriber(long id, string type)
12 | {
13 | var key = $"{_internalConfig.StorageSubscribersPath}/{id}";
14 | await _storage.PutAsync(key, type);
15 | }
16 |
17 | protected async Task DeleteSubscriber(long id)
18 | {
19 | var key = $"{_internalConfig.StorageSubscribersPath}/{id}";
20 | await _storage.DeleteAsync(key);
21 | }
22 |
23 | protected async Task GetSubscriberOfDefault(long id)
24 | {
25 | var key = $"{_internalConfig.StorageSubscribersPath}/{id}";
26 | var value = await _storage.GetAsyncOfDefault(key);
27 |
28 | if (value is null)
29 | {
30 | return null;
31 | }
32 |
33 | return id;
34 | }
35 |
36 | protected async Task> GetSubscribers()
37 | {
38 | var subs = await _storage.GetRangeAsync(_internalConfig.StorageSubscribersPath);
39 |
40 | var subIds = subs.Select(x => long.Parse(Path.GetFileName(x.Key)));
41 | return subIds;
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Dhaf.HealthCheckers.Web/Dhaf.HealthCheckers.Web.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | linux-x64;osx-x64;win-x64
6 | true
7 | 1.2.5
8 | true
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | false
20 | runtime
21 |
22 |
23 |
24 |
25 |
26 | Always
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/Dhaf.Node/DhafService.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 | using System.Collections.Generic;
3 |
4 | namespace Dhaf.Node
5 | {
6 | public class DhafService
7 | {
8 | public string Name { get; set; }
9 | public string Domain { get; set; }
10 | public List EntryPoints { get; set; }
11 | public ISwitcher Switcher { get; set; }
12 | public IHealthChecker HealthChecker { get; set; }
13 |
14 | ///
15 | /// The flag shows that all entry points is unhealthy.
16 | /// The service is DOWN and dhaf is physically unable to fix the situation on its own.
17 | ///
18 | public bool PanicMode { get; set; } = false;
19 |
20 | public string SwitchoverLastRequirementInStorage { get; set; } = null;
21 |
22 | public string CurrentEntryPointId { get; set; }
23 |
24 | public IEnumerable EntryPointStatuses { get; set; }
25 | public IEnumerable PreviousEntryPointStatuses { get; set; } // For tracking changes.
26 |
27 | public DhafService(string name, string domain, List entryPoints,
28 | ISwitcher switcher, IHealthChecker healthChecker)
29 | {
30 | Name = name;
31 | Domain = domain;
32 | EntryPoints = entryPoints;
33 | Switcher = switcher;
34 | HealthChecker = healthChecker;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Dhaf.Node/nlog.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/Dhaf.CLI/Actions/SwitchoverCandidates.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Node;
2 | using RestSharp;
3 | using Spectre.Console;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Threading.Tasks;
8 |
9 | namespace Dhaf.CLI
10 | {
11 | public static partial class Actions
12 | {
13 | public static async Task ExecuteSwitchoverCandidatesAndReturnExitCode(SwitchoverCandidatesOptions opt)
14 | {
15 | await PrepareRestClient(opt);
16 | var request = new RestRequest($"switchover/candidates?serviceName={opt.ServiceName}");
17 |
18 | var response = await _restClient.GetAsync>>(request);
19 | if (!response.Success)
20 | {
21 | PrintErrors(response.Errors);
22 | return -1;
23 | }
24 |
25 | var table = new Table();
26 | table.Border(TableBorder.Ascii2);
27 | table.Width = 50;
28 | table.Title = new TableTitle("Switchover candidates");
29 | table.AddColumns("Priority", "Name");
30 |
31 | table.Columns[0].Centered();
32 | table.Columns[1].Centered();
33 |
34 | var candidates = response.Data;
35 | foreach (var c in candidates)
36 | {
37 | table.AddRow(c.Priority.ToString(), c.Name);
38 | }
39 |
40 | AnsiConsole.Write(table);
41 | return 0;
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Dhaf.CLI/Actions/StatusDhaf.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Node;
2 | using RestSharp;
3 | using Spectre.Console;
4 | using System.Threading.Tasks;
5 |
6 | namespace Dhaf.CLI
7 | {
8 | public static partial class Actions
9 | {
10 | public static async Task ExecuteStatusDhafAndReturnExitCode(StatusDhafOptions opt)
11 | {
12 | await PrepareRestClient(opt);
13 | var request = new RestRequest($"dhaf/status");
14 |
15 | var response = await _restClient.GetAsync>(request);
16 | if (!response.Success)
17 | {
18 | PrintErrors(response.Errors);
19 | return -1;
20 | }
21 |
22 | var dhafStatus = response.Data;
23 |
24 | var table = new Table();
25 | table.Border(TableBorder.Ascii2);
26 | table.Width = 50;
27 | table.Title = new TableTitle("Dhaf cluster status");
28 | table.AddColumns("Node name", "Healthy", "Role");
29 |
30 | table.Columns[0].Centered();
31 | table.Columns[1].Centered();
32 | table.Columns[2].Centered();
33 |
34 | foreach (var node in dhafStatus.Nodes)
35 | {
36 | table.AddRow(node.Name,
37 | node.Healthy ? "[green]Yes[/]" : "[red]No[/]",
38 | dhafStatus.Leader == node.Name ? "[white]Leader[/]" : "Follower");
39 | }
40 |
41 | AnsiConsole.Write(table);
42 | return 0;
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/ExtensionsScope/ExtensionLoadContext.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using System.Runtime.Loader;
4 |
5 | namespace Dhaf.Core
6 | {
7 | class ExtensionLoadContext : AssemblyLoadContext
8 | {
9 | private readonly AssemblyDependencyResolver _resolver;
10 |
11 | public ExtensionLoadContext(string extensionPath)
12 | {
13 | _resolver = new AssemblyDependencyResolver(extensionPath);
14 | }
15 |
16 | protected override Assembly Load(AssemblyName assemblyName)
17 | {
18 | // See more about this check here:
19 | // https://github.com/dotnet/runtime/issues/87578
20 | try
21 | {
22 | var asm = AssemblyLoadContext.Default.LoadFromAssemblyName(assemblyName);
23 | if (asm != null)
24 | {
25 | return asm;
26 | }
27 | }
28 | catch
29 | {
30 | // Assembly is not part of the host - load it into the plugin.
31 | var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
32 | if (assemblyPath != null)
33 | {
34 | return LoadFromAssemblyPath(assemblyPath);
35 | }
36 | }
37 |
38 | return null;
39 | }
40 |
41 | protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
42 | {
43 | var libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
44 | if (libraryPath != null)
45 | {
46 | return LoadUnmanagedDllFromPath(libraryPath);
47 | }
48 |
49 | return IntPtr.Zero;
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Dhaf.Node/Dhaf.Node.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net9.0
6 | linux-x64;osx-x64;win-x64
7 | true
8 | 1.2.5
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | Always
36 |
37 |
38 | Always
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/tests/Dhaf.Core.Tests/Dhaf.Core.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 |
6 | false
7 | 1.2.5
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | runtime; build; native; contentfiles; analyzers; buildtransitive
16 | all
17 |
18 |
19 | runtime; build; native; contentfiles; analyzers; buildtransitive
20 | all
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | PreserveNewest
34 |
35 |
36 | PreserveNewest
37 |
38 |
39 | PreserveNewest
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/templates/dhaf_extensions/Dhaf.Templates.Extensions.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.31702.278
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dhaf.Templates.Extensions.FooSwitcher", "Dhaf.Templates.Extensions.FooSwitcher\Dhaf.Templates.Extensions.FooSwitcher.csproj", "{8387F31C-97B0-4903-B858-639358AE84A9}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dhaf.Templates.Extensions.FooHealthChecker", "Dhaf.Templates.Extensions.FooHealthChecker\Dhaf.Templates.Extensions.FooHealthChecker.csproj", "{59B10033-CCA2-49CE-BCD7-1C9A32D37069}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {8387F31C-97B0-4903-B858-639358AE84A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {8387F31C-97B0-4903-B858-639358AE84A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {8387F31C-97B0-4903-B858-639358AE84A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {8387F31C-97B0-4903-B858-639358AE84A9}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {59B10033-CCA2-49CE-BCD7-1C9A32D37069}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {59B10033-CCA2-49CE-BCD7-1C9A32D37069}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {59B10033-CCA2-49CE-BCD7-1C9A32D37069}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {59B10033-CCA2-49CE-BCD7-1C9A32D37069}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {69BF9CF9-877A-4DA9-8283-9AC292E23173}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/src/Dhaf.CLI/Actions/ActionBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Dhaf.Core;
6 | using Dhaf.Node;
7 | using Microsoft.Extensions.Configuration;
8 | using RestSharp;
9 | using RestSharp.Serializers.NewtonsoftJson;
10 |
11 | namespace Dhaf.CLI
12 | {
13 | public static partial class Actions
14 | {
15 | private static IRestClient _restClient;
16 |
17 | private static async Task GetClusterConfig(IConfigPath opt)
18 | {
19 | var _configuration = GetConfiguration();
20 |
21 | var internalConfig = new DhafInternalConfig();
22 | _configuration.Bind(internalConfig);
23 |
24 | var clusterConfigParser = new ClusterConfigParser(opt.Config, internalConfig);
25 | var config = await clusterConfigParser.Parse();
26 |
27 | return config;
28 | }
29 |
30 | private static async Task PrepareRestClient(IConfigPath opt)
31 | {
32 | var config = await GetClusterConfig(opt);
33 | var webApiEndpoint = config.Dhaf.WebApi;
34 | var uri = new Uri($"http://{webApiEndpoint.Host}:{webApiEndpoint.Port}/");
35 |
36 | var options = new RestClientOptions { BaseUrl = uri };
37 | _restClient = new RestClient(options, configureSerialization: s => s.UseNewtonsoftJson());
38 | }
39 |
40 | private static void PrintErrors(IEnumerable errors)
41 | {
42 | var error = errors.First();
43 | Console.WriteLine($"Error {error.Code}: {error.Message}");
44 | }
45 |
46 | private static IConfigurationRoot GetConfiguration()
47 | {
48 | return new ConfigurationBuilder()
49 | .AddJsonFile("appsettings.json")
50 | .Build();
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/.github/workflows/dhaf-release.yml:
--------------------------------------------------------------------------------
1 | name: Dhaf Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*.*.[0-9]+'
7 |
8 | jobs:
9 | release:
10 | if: startsWith(github.ref, 'refs/tags')
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: download linux-x64 artifacts
15 | uses: dawidd6/action-download-artifact@v6
16 | with:
17 | workflow: linux-x64.yml
18 | workflow_conclusion: success
19 | path: linux-x64
20 |
21 | - name: download win-x64 artifacts
22 | uses: dawidd6/action-download-artifact@v6
23 | with:
24 | workflow: win-x64.yml
25 | workflow_conclusion: success
26 | path: win-x64
27 |
28 | - name: download osx-x64 artifacts
29 | uses: dawidd6/action-download-artifact@v6
30 | with:
31 | workflow: osx-x64.yml
32 | workflow_conclusion: success
33 | path: osx-x64
34 |
35 | - name: set the release version from tag
36 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
37 |
38 | - name: zip linux-x64 artifacts
39 | uses: papeloto/action-zip@v1
40 | with:
41 | files: linux-x64
42 | recursive: true
43 | dest: dhaf-${{ env.RELEASE_VERSION }}-linux-x64.zip
44 |
45 | - name: zip win-x64 artifacts
46 | uses: papeloto/action-zip@v1
47 | with:
48 | files: win-x64
49 | recursive: true
50 | dest: dhaf-${{ env.RELEASE_VERSION }}-win-x64.zip
51 |
52 | - name: zip osx-x64 artifacts
53 | uses: papeloto/action-zip@v1
54 | with:
55 | files: osx-x64
56 | recursive: true
57 | dest: dhaf-${{ env.RELEASE_VERSION }}-osx-x64.zip
58 |
59 | - name: release
60 | uses: softprops/action-gh-release@v1
61 | with:
62 | files: |
63 | dhaf-${{ env.RELEASE_VERSION }}-linux-x64.zip
64 | dhaf-${{ env.RELEASE_VERSION }}-win-x64.zip
65 | dhaf-${{ env.RELEASE_VERSION }}-osx-x64.zip
66 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/DhafInternalConfig.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Text;
3 | using System.Text.Json;
4 |
5 | namespace Dhaf.Core
6 | {
7 | public class DhafInternalConfig
8 | {
9 | public DhafInternalConfigEtcd Etcd { get; set; }
10 | public DhafInternalConfigWebApi WebApi { get; set; }
11 |
12 | public int DefHeartbeatInterval { get; set; }
13 |
14 | public int DefTactInterval { get; set; }
15 | public int TactIntervalMin { get; set; }
16 | public int TactIntervalMax { get; set; }
17 |
18 | public int DefTactPostSwitchDelay { get; set; }
19 | public int TactPostSwitchDelayMin { get; set; }
20 | public int TactPostSwitchDelayMax { get; set; }
21 |
22 | public int DefHealthyNodeStatusTtl { get; set; }
23 |
24 | public List Extensions { get; set; }
25 |
26 | public static Encoding ConfigsEncoding { get; set; } = Encoding.UTF8;
27 |
28 | public int NameMaxLength { get; set; }
29 |
30 | public static JsonSerializerOptions JsonSerializerOptions { get; set; }
31 | = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
32 | }
33 |
34 | public class DhafInternalConfigEtcd
35 | {
36 | public string LeaderPath { get; set; }
37 | public int DefLeaderKeyTtl { get; set; }
38 |
39 | public string NodesPath { get; set; }
40 | public string HealthPath { get; set; }
41 | public string SwitchoverPath { get; set; }
42 | public string ExtensionStoragePath { get; set; }
43 |
44 | public string ExtensionStorageHcPrefix { get; set; }
45 | public string ExtensionStorageSwPrefix { get; set; }
46 | public string ExtensionStorageNtfPrefix { get; set; }
47 | }
48 |
49 | public class DhafInternalConfigWebApi
50 | {
51 | public string DefHost { get; set; }
52 | public int DefPort { get; set; }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/templates/agents/google-cloud/gc-nginx-agent.py:
--------------------------------------------------------------------------------
1 | import os
2 | import requests
3 | import asyncio
4 | import functools
5 | import logging
6 | import yaml
7 |
8 | with open('services.yaml') as f:
9 | services = yaml.safe_load(f)
10 |
11 | TACT_TIMEOUT = 30
12 | APPLY_CHANGES_COMMAND = "systemctl reload nginx"
13 | EVENT_LOOP = asyncio.get_event_loop()
14 |
15 | logging.basicConfig(
16 | format='%(asctime)s %(levelname)s: %(message)s', level=logging.INFO)
17 |
18 |
19 | def GET_NGINX_SUBCONFIG_CONTENT(ip): return f"set $dhaf_ip \"{str(ip)}\";"
20 |
21 |
22 | async def get_gc_metadata(md_key, timeout=0):
23 | path = f"http://metadata.google.internal/computeMetadata/v1/project/attributes/{md_key}"
24 |
25 | if timeout > 0:
26 | path = path + f"?wait_for_change=true&timeout_sec={TACT_TIMEOUT}"
27 |
28 | request_task = EVENT_LOOP.run_in_executor(None, functools.partial(
29 | requests.get, path, headers={"Metadata-Flavor": "Google"}))
30 | resp = await request_task
31 |
32 | if resp.status_code == 200:
33 | return resp.content.decode('UTF-8')
34 | else:
35 | return None
36 |
37 |
38 | async def watch_gc_metadata(service_name, gcmd_key, nginx_subconfig_path):
39 | prev_value = await get_gc_metadata(gcmd_key)
40 | logging.info(f"Start watch [{service_name}] (init value: {prev_value})")
41 | while True:
42 | curr_value = await get_gc_metadata(gcmd_key, TACT_TIMEOUT)
43 |
44 | if curr_value is not None and curr_value != prev_value:
45 | logging.warning(
46 | f"[{service_name}] Changed the value <{prev_value}> → <{curr_value}>.")
47 |
48 | prev_value = curr_value
49 | curr_content = GET_NGINX_SUBCONFIG_CONTENT(prev_value)
50 |
51 | try:
52 | with open(nginx_subconfig_path, "w") as f:
53 | f.write(curr_content)
54 |
55 | os.system(APPLY_CHANGES_COMMAND)
56 | logging.info(f"[{service_name}] Changes applied.")
57 |
58 | except Exception as e:
59 | logging.error(str(e))
60 |
61 | if os.geteuid() != 0:
62 | logging.critical("The \"google cloud nginx agent\" must be run as root.")
63 | exit()
64 |
65 | logging.info("The \"google cloud nginx agent\" started...")
66 | tasks = [watch_gc_metadata(
67 | i["name"], i["gcmd_key"], i["nginx_subconfig_path"]) for i in services]
68 |
69 | EVENT_LOOP.run_until_complete(asyncio.wait(tasks))
70 | EVENT_LOOP.close()
71 |
--------------------------------------------------------------------------------
/src/Dhaf.Node/ExtensionStorageProvider.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 | using dotnet_etcd;
3 | using System.Collections.Generic;
4 | using System.Threading.Tasks;
5 |
6 | namespace Dhaf.Node
7 | {
8 | public class ExtensionStorageProvider : IExtensionStorageProvider
9 | {
10 | private readonly EtcdClient _etcdClient;
11 | private readonly Grpc.Core.Metadata _etcdHeaders;
12 | private readonly ClusterConfig _clusterConfig;
13 | protected DhafInternalConfig _dhafInternalConfig;
14 | private readonly string _extensionSign;
15 |
16 | protected string _etcdRootPath
17 | {
18 | get => $"/{_clusterConfig.Dhaf.ClusterName}/{_dhafInternalConfig.Etcd.ExtensionStoragePath}/{_extensionSign}/";
19 | }
20 |
21 | public ExtensionStorageProvider(EtcdClient etcdClient, Grpc.Core.Metadata etcdHeaders,
22 | ClusterConfig clusterConfig, DhafInternalConfig dhafInternalConfig,
23 | string extensionPrefix)
24 | {
25 | _etcdClient = etcdClient;
26 | _etcdHeaders = etcdHeaders;
27 | _clusterConfig = clusterConfig;
28 | _dhafInternalConfig = dhafInternalConfig;
29 | _extensionSign = extensionPrefix;
30 | }
31 |
32 | public async Task DeleteAsync(string key)
33 | {
34 | var realKey = _etcdRootPath + key;
35 | await _etcdClient.DeleteAsync(realKey, _etcdHeaders);
36 | }
37 |
38 | public async Task DeleteRangeAsync(string keyPrefix)
39 | {
40 | var realKeyPrefix = _etcdRootPath + keyPrefix;
41 | await _etcdClient.DeleteRangeAsync(realKeyPrefix, _etcdHeaders);
42 | }
43 |
44 | public async Task GetAsyncOfDefault(string key)
45 | {
46 | var realKey = _etcdRootPath + key;
47 | var value = await _etcdClient.GetValAsync(realKey, _etcdHeaders);
48 |
49 | if (string.IsNullOrEmpty(value))
50 | {
51 | return null;
52 | }
53 |
54 | return value;
55 | }
56 |
57 | public async Task> GetRangeAsync(string keyPrefix)
58 | {
59 | var realKeyPrefix = _etcdRootPath + keyPrefix;
60 |
61 | var values = await _etcdClient.GetRangeValAsync(realKeyPrefix, _etcdHeaders);
62 | return values;
63 | }
64 |
65 | public async Task PutAsync(string key, string value)
66 | {
67 | var realKey = _etcdRootPath + key;
68 | await _etcdClient.PutAsync(realKey, value, _etcdHeaders);
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/templates/dhaf_extensions/Dhaf.Templates.Extensions.FooSwitcher/FooSwitcher.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 | using Microsoft.Extensions.Logging;
3 | using System;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace Dhaf.Switchers.Foo
8 | {
9 | public class FooSwitcher : ISwitcher
10 | {
11 | private ILogger _logger;
12 |
13 | private Config _config;
14 | private InternalConfig _internalConfig;
15 | private ClusterServiceConfig _serviceConfig;
16 |
17 | protected string _currentEntryPointId = string.Empty;
18 |
19 | public string ExtensionName => "foo";
20 | public string Sign => $"[{_serviceConfig.Name}/{ExtensionName} sw]";
21 |
22 | public Type ConfigType => typeof(Config);
23 | public Type InternalConfigType => typeof(InternalConfig);
24 |
25 | public async Task Init(SwitcherInitOptions options)
26 | {
27 | _serviceConfig = options.ClusterServiceConfig;
28 | _logger = options.Logger; // The dhaf extension system provides logging out of the box.
29 |
30 | _logger.LogTrace($"{Sign} Init process...");
31 |
32 | // The dhaf extension system will automatically populate instances of the configuration classes.
33 | _config = (Config)options.Config;
34 | _internalConfig = (InternalConfig)options.InternalConfig;
35 |
36 | // <- Other code to initialize your extension HERE.
37 | // <- Can be empty if you have nothing else to initialize.
38 |
39 | _logger.LogInformation($"{Sign} Init OK.");
40 | }
41 |
42 | public async Task Switch(SwitcherSwitchOptions options)
43 | {
44 | var entryPoint = _serviceConfig.EntryPoints.FirstOrDefault(x => x.Id == options.EntryPointId);
45 | _logger.LogInformation($"{Sign} Switch to entry point <{entryPoint.Id}> requested...");
46 |
47 | // <- Your code for the switch is HERE.
48 |
49 | _currentEntryPointId = entryPoint.Id;
50 | _logger.LogInformation($"{Sign} Successfully switched to entry point <{entryPoint.Id}>.");
51 | }
52 |
53 | public async Task DhafNodeRoleChangedEventHandler(DhafNodeRole role)
54 | {
55 | // <- Your code to react to the event of the current
56 | // <- dhaf cluster node role change (can be empty in most cases).
57 | }
58 |
59 | public async Task GetCurrentEntryPointId()
60 | {
61 | // In some cases can be replaced by more complex logic.
62 | return _currentEntryPointId;
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Dhaf.Node/Program.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 | using Dhaf.Core;
3 | using Microsoft.Extensions.Configuration;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using Microsoft.Extensions.Hosting;
6 | using Microsoft.Extensions.Logging;
7 | using NLog;
8 | using NLog.Extensions.Logging;
9 | using System;
10 | using System.Threading.Tasks;
11 |
12 | namespace Dhaf.Node
13 | {
14 | class Program
15 | {
16 | static async Task Main(string[] args)
17 | {
18 | var logger = LogManager.GetCurrentClassLogger();
19 |
20 | try
21 | {
22 | await CreateHostBuilder(args).Build().RunAsync();
23 | }
24 | catch (ConfigParsingException ex)
25 | {
26 | logger.Fatal($"Config parsing error {ex.Code}: {ex.Message}");
27 | }
28 | catch (YamlDotNet.Core.YamlException ex)
29 | {
30 | logger.Fatal($"Config YAML deserialize error:\n{ex.Message}");
31 | }
32 | /*catch (Exception ex)
33 | {
34 | logger.Fatal($"Further work of the node is impossible because of a fatal error:\n{ex.Message}");
35 | }*/
36 | finally
37 | {
38 | logger.Info("* Dhaf node exit...");
39 | LogManager.Shutdown();
40 | }
41 | }
42 |
43 | public static IHostBuilder CreateHostBuilder(string[] args) =>
44 | Host.CreateDefaultBuilder(args)
45 | .ConfigureAppConfiguration((hostingContext, config) =>
46 | {
47 | config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
48 | config.AddEnvironmentVariables();
49 | })
50 | .ConfigureServices(services =>
51 | {
52 |
53 | services.AddSingleton(servicesProvider =>
54 | {
55 | ArgsOptions argsOptions = null;
56 | Parser.Default.ParseArguments(args)
57 | .WithParsed(p => argsOptions = p);
58 |
59 | return argsOptions;
60 | })
61 | .AddHostedService()
62 | .AddLogging(loggingBuilder =>
63 | {
64 | loggingBuilder.ClearProviders();
65 | loggingBuilder.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
66 | loggingBuilder.AddNLog();
67 | });
68 | });
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/templates/agents/google-cloud/README.md:
--------------------------------------------------------------------------------
1 | # Google Cloud Agents
2 | ⚠️ Before reading the current document, read the main [README.md](../../README.md#with-google-cloud-switcher-provider) (in the "Quick Start/With Google Cloud switcher provider" section).
3 |
4 | So, here are the agents that allow you to make a Google Cloud VM instance a reverse proxy that proxies to the server specified in the GC project metadata.
5 | In general, you can implement this task in any way you want. However, we offer ready-made templates below.
6 |
7 | ## Nginx agent
8 | Look at the nginx [subdirectory](nginx). Two files can be found there:
9 | 1. `nginx_template.conf` - a basic configuration for nginx that makes it a reverse proxy to the server specified in the static variable `$dhaf_ip`;
10 | 2. `dhaf_serv1_ip` - the file that contains the definition of the variable `$dhaf_ip`. It will be statically included to the main configuration file above.
11 |
12 | Then look at the `gc-nginx-agent.py` file in the current directory.
13 | It is a simple python (v3) script that monitors changes in Google Cloud project metadata, and,
14 | if necessary, passes those changes to nginx (and also reloads its configuration). In other words, it fills the `dhaf_serv1_ip` file described above (and similar).
15 | This does not require you to prepare any credentials, because inside the VM instance, project metadata is available by default
16 | Aslo, it supports working for multiple services in dhaf at once.
17 |
18 | In the directory with the script you must place the file `services.yaml`, inside which is a collection where each item contains:
19 | 1. Service name. Does not have a strong importance, implemented simply for usability;
20 | 2. Goolge Cloud Metadata key. The same key must be specified in the dhaf service configuration;
21 | 3. The path to the nginx subconfig (similar to `dhaf_serv1_ip` described above) which will contain the current ip address variable (`$dhaf_ip`) definition for proxying.
22 |
23 | For example:
24 | ```yaml
25 | - name: serv1
26 | gcmd_key: dhaf_serv1_ip
27 | nginx_subconfig_path: /etc/nginx/dhaf/dhaf_serv1_ip
28 | ```
29 |
30 | That's all. Now you can make this script run with the OS, for example using `systemd`. You can find a ready-made template [here](systemd).
31 |
32 | Oh yes, if you want to use the autoscaling feature of the [managed instance group](https://cloud.google.com/compute/docs/instance-groups#managed_instance_groups) (it's generally not necessary,
33 | you can leave a static number of instances and disallow automatic changes),
34 | don't forget to describe in the Google Cloud instance template the [startup-script](https://cloud.google.com/compute/docs/instance-templates/deterministic-instance-templates)
35 | for installation of nginx and the corresponding agent (and so on). You can also use a container (e.g. a [docker](https://www.docker.com/)) for this. Good luck ;)
36 |
--------------------------------------------------------------------------------
/src/Dhaf.HealthCheckers.Exec/ExecHealthChecker.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 | using Microsoft.Extensions.Logging;
3 | using System;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace Dhaf.HealthCheckers.Exec
8 | {
9 | ///
10 | /// The exec health checker can run any command, and expects an zero-value exit code on success.
11 | /// Non-zero exit codes are considered errors.
12 | ///
13 | public class ExecHealthChecker : IHealthChecker
14 | {
15 | private ILogger _logger;
16 |
17 | private Config _config;
18 | private InternalConfig _internalConfig;
19 | private ClusterServiceConfig _serviceConfig;
20 |
21 | public string ExtensionName => "exec";
22 |
23 | public Type ConfigType => typeof(Config);
24 | public Type InternalConfigType => typeof(InternalConfig);
25 |
26 | public string Sign => $"[{_serviceConfig.Name}/{ExtensionName} hc]";
27 |
28 | public async Task Init(HealthCheckerInitOptions options)
29 | {
30 | _serviceConfig = options.ClusterServiceConfig;
31 | _logger = options.Logger;
32 |
33 | _logger.LogTrace($"{Sign} Init process...");
34 |
35 | _config = (Config)options.Config;
36 | _internalConfig = (InternalConfig)options.InternalConfig;
37 |
38 | var execResults = Shell.Exec(_config.Init);
39 |
40 | if (!execResults.Success || execResults.ExitCode != 0)
41 | {
42 | _logger.LogCritical($"{Sign} The executable initialization file returned a non-zero return code.");
43 | throw new ExtensionInitFailedException(Sign);
44 | }
45 |
46 | _logger.LogTrace($"{Sign} Init output: <{execResults.Output}>");
47 | _logger.LogTrace($"{Sign} Init total exec time: {execResults.TotalExecuteTime} ms.");
48 | _logger.LogInformation($"{Sign} Init OK.");
49 | }
50 |
51 | public async Task Check(HealthCheckerCheckOptions options)
52 | {
53 | var entryPoint = _serviceConfig.EntryPoints.FirstOrDefault(x => x.Id == options.EntryPointId);
54 |
55 | var args = $"{entryPoint.Id} {entryPoint.IP}";
56 | var execResults = Shell.Exec(_config.Check);
57 |
58 | if (execResults.Success && execResults.ExitCode == 0)
59 | {
60 | return new HealthStatus { Healthy = true };
61 | }
62 |
63 | return new HealthStatus { Healthy = false };
64 | }
65 |
66 | public async Task DhafNodeRoleChangedEventHandler(DhafNodeRole role) { }
67 |
68 | public async Task ResolveUnhealthinessReasonCode(int code)
69 | {
70 | return "The return code was different from <0>.";
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Dhaf.CLI/Actions/StatusService.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Node;
2 | using RestSharp;
3 | using Spectre.Console;
4 | using System;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 |
8 | namespace Dhaf.CLI
9 | {
10 | public static partial class Actions
11 | {
12 | public static async Task ExecuteStatusServiceAndReturnExitCode(StatusServiceOptions opt)
13 | {
14 | const int TABLE_WIDTH = 80;
15 |
16 | await PrepareRestClient(opt);
17 | var request = new RestRequest($"service/status?serviceName={opt.ServiceName}");
18 |
19 | var response = await _restClient.GetAsync>(request);
20 | if (!response.Success)
21 | {
22 | PrintErrors(response.Errors);
23 | return -1;
24 | }
25 |
26 | var serviceStatus = response.Data;
27 | var isUp = serviceStatus.EntryPoints
28 | .Any(x => x.Healthy) ? "[green]UP[/]" : "[red]DOWN[/]";
29 |
30 | var sw = serviceStatus.SwitchoverRequirement is null ? "NO" : $"YES (to <{serviceStatus.SwitchoverRequirement}>)";
31 |
32 | var summaryTable = new Table
33 | {
34 | Width = TABLE_WIDTH
35 | };
36 |
37 | summaryTable.Border(TableBorder.Ascii2);
38 | summaryTable.Title = new TableTitle($"Service \"{serviceStatus.Name}\" -> Summary");
39 | summaryTable.AddColumns("Key", "Value");
40 | summaryTable.AddRow("Service status", isUp);
41 | summaryTable.AddRow("Domain", serviceStatus.Domain);
42 | summaryTable.AddRow("Switchover is required", sw);
43 | AnsiConsole.Write(summaryTable);
44 |
45 | Console.WriteLine();
46 | var epTable = new Table
47 | {
48 | Width = TABLE_WIDTH
49 | };
50 |
51 | epTable.Border(TableBorder.Ascii2);
52 | epTable.Title = new TableTitle($"Service \"{serviceStatus.Name}\" -> Entry points");
53 | epTable.AddColumns("Priority", "Name", "Healthy", "Status");
54 | epTable.Columns[0].Centered();
55 | epTable.Columns[1].Centered();
56 | epTable.Columns[2].Centered();
57 | epTable.Columns[3].Centered();
58 |
59 | foreach (var ep in serviceStatus.EntryPoints)
60 | {
61 | var status = ep.Healthy ? "READY" : "DISABLED";
62 | if (ep.Name == serviceStatus.CurrentEntryPointName)
63 | {
64 | status = "[white]CURRENT[/]";
65 | }
66 |
67 | epTable.AddRow(ep.Priority.ToString(), ep.Name,
68 | ep.Healthy ? "[green]Yes[/]" : "[red]No[/]", status);
69 | }
70 |
71 | AnsiConsole.Write(epTable);
72 | return 0;
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/templates/dhaf_extensions/Dhaf.Templates.Extensions.FooHealthChecker/FooHealthChecker.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 | using Microsoft.Extensions.Logging;
3 | using System;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace Dhaf.HealthCheckers.Foo
8 | {
9 | public class FooHealthChecker : IHealthChecker
10 | {
11 | private ILogger _logger;
12 |
13 | private Config _config;
14 | private InternalConfig _internalConfig;
15 | private ClusterServiceConfig _serviceConfig;
16 |
17 | public string ExtensionName => "foo";
18 | public string Sign => $"[{_serviceConfig.Name}/{ExtensionName} hc]";
19 |
20 | public Type ConfigType => typeof(Config);
21 | public Type InternalConfigType => typeof(InternalConfig);
22 |
23 | public async Task Init(HealthCheckerInitOptions options)
24 | {
25 | _serviceConfig = options.ClusterServiceConfig;
26 | _logger = options.Logger; // The dhaf extension system provides logging out of the box.
27 |
28 | _logger.LogTrace($"{Sign} Init process...");
29 |
30 | // The dhaf extension system will automatically populate instances of the configuration classes.
31 | _config = (Config)options.Config;
32 | _internalConfig = (InternalConfig)options.InternalConfig;
33 |
34 | // <- Other code to initialize your extension HERE.
35 | // <- Can be empty if you have nothing else to initialize.
36 |
37 | _logger.LogInformation($"{Sign} Init OK.");
38 | }
39 |
40 | public async Task Check(HealthCheckerCheckOptions options)
41 | {
42 | var entryPoint = _serviceConfig.EntryPoints.FirstOrDefault(x => x.Id == options.EntryPointId);
43 |
44 | // <- Your health check code is HERE.
45 |
46 | // This is the test data for the return. It must be replaced with real data.
47 | var reasonCode = 1; // The code for the cause of unhealthiness (for illustration purposes only).
48 | return new HealthStatus { Healthy = false, ReasonCode = reasonCode };
49 | }
50 |
51 | public async Task DhafNodeRoleChangedEventHandler(DhafNodeRole role)
52 | {
53 | // <- Your code to react to the event of the current
54 | // <- dhaf cluster node role change (can be empty in most cases).
55 | }
56 |
57 |
58 | public async Task ResolveUnhealthinessReasonCode(int code)
59 | {
60 | // The dhaf cluster state store stores the reason of unhealthiness as an integer code.
61 | // This method serves to resolve such codes into human-readable text.
62 | // How to do it is up to you.
63 | // For example, you can store a Dictionary that resolves codes into text.
64 | // And you can also use enums.
65 |
66 | return "Test reason";
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Dhaf.Node/RestApi/RestApiFactory.cs:
--------------------------------------------------------------------------------
1 | using EmbedIO;
2 | using EmbedIO.Utilities;
3 | using EmbedIO.WebApi;
4 | using Microsoft.Extensions.Logging;
5 | using Swan.Logging;
6 | using System.Collections.Generic;
7 | using System.Text;
8 | using System.Text.Json;
9 | using System.Threading.Tasks;
10 |
11 | namespace Dhaf.Node
12 |
13 | {
14 | public class RestApiFactory
15 | {
16 | protected static async Task SerializationCallback(IHttpContext context, object data)
17 | {
18 | Validate.NotNull(nameof(context), context).Response.ContentType = MimeType.Json;
19 | using var text = context.OpenResponseText(new UTF8Encoding(false));
20 | await text.WriteAsync(JsonSerializer.Serialize(data, new JsonSerializerOptions()
21 | {
22 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase
23 | })).ConfigureAwait(false);
24 | }
25 |
26 | public WebServer CreateWebServer(string url, IDhafNode dhafNode, ILogger logger)
27 | {
28 | Logger.NoLogging();
29 |
30 | var server = new WebServer(o => o
31 | .WithUrlPrefix(url)
32 | .WithMode(HttpListenerMode.EmbedIO))
33 | .WithLocalSessionManager()
34 | .WithWebApi("/", SerializationCallback,
35 | m => m.WithController(() => new RestApiController(dhafNode, logger))
36 | );
37 |
38 | server.HandleUnhandledException(async (context, exception) =>
39 | {
40 | context.Response.StatusCode = 500;
41 | var errors = new List()
42 | {
43 | new RestApiError { Code = -1, Message = "Internal error." }
44 | };
45 |
46 | if (exception is RestApiException restApiExp)
47 | {
48 | context.Response.StatusCode = 400;
49 | errors = new List()
50 | {
51 | new RestApiError { Code = restApiExp.Code, Message = restApiExp.Message }
52 | };
53 | }
54 |
55 | await context.SendDataAsync(SerializationCallback, new RestApiResponse
56 | {
57 | Success = false,
58 | Errors = errors
59 | });
60 | });
61 |
62 | server.HandleHttpException(async (context, exception) =>
63 | {
64 | context.Response.StatusCode = 400;
65 |
66 | var msg = exception.StatusCode == 404 ? "Endpoint not found." : exception.Message;
67 | var errors = new List()
68 | {
69 | new RestApiError { Code = exception.StatusCode, Message = msg }
70 | };
71 |
72 | await context.SendDataAsync(SerializationCallback, new RestApiResponse
73 | {
74 | Success = false,
75 | Errors = errors
76 | });
77 | });
78 |
79 | return server;
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/Dhaf.CLI/Actions/StatusServices.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Node;
2 | using RestSharp;
3 | using Spectre.Console;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Threading.Tasks;
8 |
9 | namespace Dhaf.CLI
10 | {
11 | public static partial class Actions
12 | {
13 | public static async Task ExecuteStatusServicesAndReturnExitCode(StatusServicesOptions opt)
14 | {
15 | const int TABLE_WIDTH = 80;
16 |
17 | await PrepareRestClient(opt);
18 | var request = new RestRequest($"services/status");
19 |
20 | var response = await _restClient.GetAsync>>(request);
21 | if (!response.Success)
22 | {
23 | PrintErrors(response.Errors);
24 | return -1;
25 | }
26 |
27 | foreach (var serviceStatus in response.Data)
28 | {
29 | var isUp = serviceStatus.EntryPoints
30 | .Any(x => x.Healthy) ? "[green]UP[/]" : "[red]DOWN[/]";
31 |
32 | var sw = serviceStatus.SwitchoverRequirement is null ? "NO" : $"YES (to <{serviceStatus.SwitchoverRequirement}>)";
33 |
34 | var summaryTable = new Table
35 | {
36 | Width = TABLE_WIDTH
37 | };
38 |
39 | summaryTable.Border(TableBorder.Ascii2);
40 | summaryTable.Title = new TableTitle($"Service \"{serviceStatus.Name}\" -> Summary");
41 | summaryTable.AddColumns("Key", "Value");
42 | summaryTable.AddRow("Service status", isUp);
43 | summaryTable.AddRow("Domain", serviceStatus.Domain);
44 | summaryTable.AddRow("Switchover is required", sw);
45 | AnsiConsole.Write(summaryTable);
46 |
47 | Console.WriteLine();
48 | var epTable = new Table
49 | {
50 | Width = TABLE_WIDTH
51 | };
52 |
53 | epTable.Border(TableBorder.Ascii2);
54 | epTable.Title = new TableTitle($"Service \"{serviceStatus.Name}\" -> Entry points");
55 | epTable.AddColumns("Priority", "Name", "Healthy", "Status");
56 | epTable.Columns[0].Centered();
57 | epTable.Columns[1].Centered();
58 | epTable.Columns[2].Centered();
59 | epTable.Columns[3].Centered();
60 |
61 | foreach (var nc in serviceStatus.EntryPoints)
62 | {
63 | var status = nc.Healthy ? "READY" : "DISABLED";
64 | if (nc.Name == serviceStatus.CurrentEntryPointName)
65 | {
66 | status = "[white]CURRENT[/]";
67 | }
68 |
69 | epTable.AddRow(nc.Priority.ToString(), nc.Name,
70 | nc.Healthy ? "[green]Yes[/]" : "[red]No[/]", status);
71 | }
72 |
73 | AnsiConsole.Write(epTable);
74 |
75 | const char SEP_CHAR = '=';
76 | Console.WriteLine(new string(SEP_CHAR, TABLE_WIDTH) + "\n");
77 | }
78 |
79 | return 0;
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/Dhaf.Node/RestApi/RestApiController.cs:
--------------------------------------------------------------------------------
1 | using EmbedIO;
2 | using EmbedIO.Routing;
3 | using EmbedIO.WebApi;
4 | using Microsoft.Extensions.Logging;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Threading.Tasks;
8 |
9 | namespace Dhaf.Node
10 | {
11 | public class RestApiController : WebApiController
12 | {
13 | protected readonly IDhafNode _dhafNode;
14 | protected readonly ILogger _logger;
15 |
16 | public RestApiController()
17 | {
18 | throw new Exception("The default constructor is not possible.");
19 | }
20 |
21 | public RestApiController(IDhafNode dhafNode, ILogger logger)
22 | {
23 | _dhafNode = dhafNode;
24 | _logger = logger;
25 | }
26 |
27 |
28 | [Route(HttpVerbs.Get, "/ping")]
29 | public async Task Ping()
30 | {
31 | return "pong";
32 | }
33 |
34 | [Route(HttpVerbs.Get, "/switchover")]
35 | public async Task Switchover([QueryField(true)] string serviceName, [QueryField(true)] string entryPointId)
36 | {
37 | await _dhafNode.Switchover(serviceName, entryPointId);
38 | return new RestApiResponse { Success = true };
39 | }
40 |
41 | [Route(HttpVerbs.Get, "/switchover/purge")]
42 | public async Task SwitchoverPurge([QueryField(true)] string serviceName)
43 | {
44 | await _dhafNode.PurgeSwitchover(serviceName);
45 | return new RestApiResponse { Success = true };
46 | }
47 |
48 | [Route(HttpVerbs.Get, "/switchover/candidates")]
49 | public async Task SwitchoverСandidates([QueryField(true)] string serviceName)
50 | {
51 | var candidates = await _dhafNode.GetSwitchoverCandidates(serviceName);
52 | return new RestApiResponse> { Success = true, Data = candidates };
53 | }
54 |
55 | [Route(HttpVerbs.Get, "/service/status")]
56 | public async Task ServiceStatus([QueryField(true)] string serviceName)
57 | {
58 | var status = await _dhafNode.GetServiceStatus(serviceName);
59 | return new RestApiResponse { Success = true, Data = status };
60 | }
61 |
62 | [Route(HttpVerbs.Get, "/services/status")]
63 | public async Task ServicesStatus()
64 | {
65 | var statuses = await _dhafNode.GetServicesStatus();
66 | return new RestApiResponse> { Success = true, Data = statuses };
67 | }
68 |
69 | [Route(HttpVerbs.Get, "/dhaf/status")]
70 | public async Task DhafStatus()
71 | {
72 | var status = await _dhafNode.GetDhafClusterStatus();
73 | return new RestApiResponse { Success = true, Data = status };
74 | }
75 |
76 | [Route(HttpVerbs.Get, "/dhaf/node/decommission")]
77 | public async Task DhafNodeDecommission([QueryField(true)] string name)
78 | {
79 | await _dhafNode.DecommissionDhafNode(name);
80 | return new RestApiResponse { Success = true };
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Dhaf.Switchers.Exec/ExecSwitcher.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 | using Microsoft.Extensions.Logging;
3 | using System;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace Dhaf.Switchers.Exec
8 | {
9 | public class ExecSwitcher : ISwitcher
10 | {
11 | private ILogger _logger;
12 |
13 | private Config _config;
14 | private InternalConfig _internalConfig;
15 | private ClusterServiceConfig _serviceConfig;
16 |
17 | protected string _currentEntryPointId = string.Empty;
18 |
19 | public string ExtensionName => "exec";
20 |
21 | public Type ConfigType => typeof(Config);
22 | public Type InternalConfigType => typeof(InternalConfig);
23 |
24 | public string Sign => $"[{_serviceConfig.Name}/{ExtensionName} sw]";
25 |
26 | public async Task GetCurrentEntryPointId()
27 | {
28 | return _currentEntryPointId;
29 | }
30 |
31 | public async Task Init(SwitcherInitOptions options)
32 | {
33 | _serviceConfig = options.ClusterServiceConfig;
34 | _logger = options.Logger;
35 |
36 | _logger.LogTrace($"{Sign} Init process...");
37 |
38 | _config = (Config)options.Config;
39 | _internalConfig = (InternalConfig)options.InternalConfig;
40 |
41 | var execResults = Shell.Exec(_config.Init);
42 |
43 | if (!execResults.Success || execResults.ExitCode != 0)
44 | {
45 | _logger.LogCritical($"{Sign} The executable initialization file returned a non-zero return code.");
46 | throw new ExtensionInitFailedException(Sign);
47 | }
48 |
49 | _logger.LogTrace($"{Sign} Init output: <{execResults.Output}>");
50 | _logger.LogTrace($"{Sign} Init total exec time: {execResults.TotalExecuteTime} ms.");
51 |
52 | // The exec switcher MUST return the ID of the current entry point
53 | // if it initializes successfully.
54 | var currentEpId = execResults.Output.Trim();
55 | _currentEntryPointId = currentEpId;
56 |
57 | _logger.LogInformation($"{Sign} Init OK.");
58 | }
59 |
60 | public async Task Switch(SwitcherSwitchOptions options)
61 | {
62 | var entryPoint = _serviceConfig.EntryPoints.FirstOrDefault(x => x.Id == options.EntryPointId);
63 | _logger.LogInformation($"{Sign} Switch to entry point <{entryPoint.Id}> requested...");
64 |
65 | var args = $"{entryPoint.Id} {entryPoint.IP}";
66 | var execResults = Shell.Exec(_config.Switch, args);
67 |
68 | if (!execResults.Success || execResults.ExitCode != 0)
69 | {
70 | _logger.LogCritical($"{Sign} The executable for the switch returned a non-zero return code.");
71 | throw new SwitchFailedException(Sign);
72 | }
73 |
74 | _logger.LogTrace($"{Sign} Switch output: <{execResults.Output}>");
75 | _logger.LogTrace($"{Sign} Switch total exec time: {execResults.TotalExecuteTime} ms.");
76 |
77 | _currentEntryPointId = entryPoint.Id;
78 | _logger.LogInformation($"{Sign} Successfully switched to entry point <{entryPoint.Id}>.");
79 | }
80 |
81 | public async Task DhafNodeRoleChangedEventHandler(DhafNodeRole role) { }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Dhaf.HealthCheckers.Tcp/TcpHealthChecker.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 | using Microsoft.Extensions.Logging;
3 | using System;
4 | using System.Linq;
5 | using System.Net.Sockets;
6 | using System.Threading.Tasks;
7 |
8 | namespace Dhaf.HealthCheckers.Tcp
9 | {
10 | public class TcpHealthChecker : IHealthChecker
11 | {
12 | private ILogger _logger;
13 |
14 | private Config _config;
15 | private InternalConfig _internalConfig;
16 | private ClusterServiceConfig _serviceConfig;
17 |
18 | public string ExtensionName => "tcp";
19 |
20 | public Type ConfigType => typeof(Config);
21 | public Type InternalConfigType => typeof(InternalConfig);
22 |
23 | public string Sign => $"[{_serviceConfig.Name}/{ExtensionName} hc]";
24 |
25 | public async Task Init(HealthCheckerInitOptions options)
26 | {
27 | _serviceConfig = options.ClusterServiceConfig;
28 | _logger = options.Logger;
29 |
30 | _logger.LogTrace($"{Sign} Init process...");
31 |
32 | _config = (Config)options.Config;
33 | _internalConfig = (InternalConfig)options.InternalConfig;
34 |
35 | await ConfigCheck();
36 | _logger.LogInformation($"{Sign} Init OK.");
37 | }
38 |
39 | public async Task Check(HealthCheckerCheckOptions options)
40 | {
41 | var entryPoint = _serviceConfig.EntryPoints.FirstOrDefault(x => x.Id == options.EntryPointId);
42 |
43 | try
44 | {
45 | const int MS_IN_SEC = 1000;
46 |
47 | using var tcpClient = new TcpClient();
48 | tcpClient.ReceiveTimeout = (_config.ReceiveTimeout ?? _internalConfig.DefReceiveTimeout) * MS_IN_SEC;
49 |
50 | await tcpClient.ConnectAsync(entryPoint.IP, _config.Port.Value);
51 |
52 | return new HealthStatus { Healthy = true };
53 | }
54 | catch (SocketException ex)
55 | {
56 | return new HealthStatus
57 | {
58 | Healthy = false,
59 | ReasonCode = (int)ex.SocketErrorCode
60 | };
61 | }
62 | }
63 |
64 | public async Task DhafNodeRoleChangedEventHandler(DhafNodeRole role) { }
65 |
66 | public async Task ResolveUnhealthinessReasonCode(int code)
67 | {
68 | if (Enum.IsDefined(typeof(SocketError), code))
69 | {
70 | var socketError = (SocketError)code;
71 | return socketError.ToString();
72 | }
73 |
74 | return "Unexpected reason";
75 | }
76 |
77 | protected async Task ConfigCheck()
78 | {
79 | if (_config.Port is null)
80 | {
81 | throw new ConfigParsingException(1901, $"{Sign} The port is not specified.");
82 | }
83 |
84 | if (_config.ReceiveTimeout is not null
85 | && (_config.ReceiveTimeout < _internalConfig.MinReceiveTimeout
86 | || _config.ReceiveTimeout > _internalConfig.MaxReceiveTimeout))
87 | {
88 | throw new ConfigParsingException(1902, $"{Sign} Receive timeout must be in the " +
89 | $"range {_internalConfig.MinReceiveTimeout}-{_internalConfig.MaxReceiveTimeout} seconds.");
90 | }
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/templates/build_example.sh:
--------------------------------------------------------------------------------
1 | # runtime identifier: win-x64, linux-x64, osx-x64 (see https://docs.microsoft.com/en-us/dotnet/core/rid-catalog for details)
2 | RID=
3 | DOTNET_CLI_TELEMETRY_OPTOUT=1
4 |
5 | # clone .git repo
6 | git clone https://github.com/hyperion-cs/dhaf.git
7 | cd dhaf
8 |
9 | # restore dependencies
10 | dotnet restore src/Dhaf.sln
11 |
12 | # build dhaf core, node, cli
13 | dotnet publish src/Dhaf.Core/Dhaf.Core.csproj --configuration Release --no-restore -nowarn:CS1998 -r "$RID" -o "bin/$RID/core" /p:DebugType=None /p:DebugSymbols=false
14 | dotnet publish src/Dhaf.Node/Dhaf.Node.csproj --configuration Release --no-dependencies --no-restore -nowarn:CS1998 -r "$RID" -o "bin/$RID" -p:PublishSingleFile=true --self-contained false /p:DebugType=None /p:DebugSymbols=false
15 | dotnet publish src/Dhaf.CLI/Dhaf.CLI.csproj --configuration Release --no-dependencies --no-restore -nowarn:CS1998 -r "$RID" -o "bin/$RID" -p:PublishSingleFile=true --self-contained false /p:DebugType=None /p:DebugSymbols=false
16 |
17 | # build core extensions
18 |
19 | dotnet publish src/Dhaf.HealthCheckers.Web/Dhaf.HealthCheckers.Web.csproj --configuration Release --no-restore -nowarn:CS1998 -r "$RID" -o "bin/$RID/ext/hc/web" /p:DebugType=None /p:DebugSymbols=false
20 | dotnet publish src/Dhaf.HealthCheckers.Exec/Dhaf.HealthCheckers.Exec.csproj --configuration Release --no-restore -nowarn:CS1998 -r $RID -o "bin/$RID/ext/hc/exec" /p:DebugType=None /p:DebugSymbols=false
21 |
22 | dotnet publish src/Dhaf.Switchers.Cloudflare/Dhaf.Switchers.Cloudflare.csproj --configuration Release --no-restore -nowarn:CS1998 -r "$RID" -o "bin/$RID/ext/sw/cloudflare" /p:DebugType=None /p:DebugSymbols=false
23 | dotnet publish src/Dhaf.Switchers.GoogleCloud/Dhaf.Switchers.GoogleCloud.csproj --configuration Release --no-restore -nowarn:CS1998 -r "$RID" -o "bin/$RID/ext/sw/google-cloud" /p:DebugType=None /p:DebugSymbols=false
24 | dotnet publish src/Dhaf.Switchers.Exec/Dhaf.Switchers.Exec.csproj --configuration Release --no-restore -nowarn:CS1998 -r "$RID" -o "bin/$RID/ext/sw/exec" /p:DebugType=None /p:DebugSymbols=false
25 |
26 | dotnet publish src/Dhaf.Notifiers.Email/Dhaf.Notifiers.Email.csproj --configuration Release --no-restore -nowarn:CS1998 -r "$RID" -o "bin/$RID/ext/ntf/email" /p:DebugType=None /p:DebugSymbols=false
27 | dotnet publish src/Dhaf.Notifiers.Telegram/Dhaf.Notifiers.Telegram.csproj --configuration Release --no-restore -nowarn:CS1998 -r "$RID" -o "bin/$RID/ext/ntf/tg" /p:DebugType=None /p:DebugSymbols=false
28 |
29 | # "$RID-artifacts" — the build destination folder.
30 |
31 | mkdir -p $RID-artifacts
32 | mkdir -p $RID-artifacts/libs
33 | mkdir -p $RID-artifacts/extensions/health-checkers/web
34 | mkdir -p $RID-artifacts/extensions/health-checkers/exec
35 | mkdir -p $RID-artifacts/extensions/switchers/cloudflare
36 | mkdir -p $RID-artifacts/extensions/switchers/google-cloud
37 | mkdir -p $RID-artifacts/extensions/switchers/exec
38 | mkdir -p $RID-artifacts/extensions/notifiers/email
39 | mkdir -p $RID-artifacts/extensions/notifiers/tg
40 |
41 | mv ./bin/$RID/Dhaf.Node $RID-artifacts/dhaf.node
42 | mv ./bin/$RID/Dhaf.CLI $RID-artifacts/dhaf.cli
43 | mv ./bin/$RID/appsettings.json ./bin/$RID/nlog.config $RID-artifacts
44 | mv ./bin/$RID/core/* $RID-artifacts/libs
45 | mv ./bin/$RID/ext/hc/web/* $RID-artifacts/extensions/health-checkers/web
46 | mv ./bin/$RID/ext/hc/exec/* $RID-artifacts/extensions/health-checkers/exec
47 | mv ./bin/$RID/ext/sw/cloudflare/* $RID-artifacts/extensions/switchers/cloudflare
48 | mv ./bin/$RID/ext/sw/google-cloud/* $RID-artifacts/extensions/switchers/google-cloud
49 | mv ./bin/$RID/ext/sw/exec/* $RID-artifacts/extensions/switchers/exec
50 | mv ./bin/$RID/ext/ntf/email/* $RID-artifacts/extensions/notifiers/email
51 | mv ./bin/$RID/ext/ntf/tg/* $RID-artifacts/extensions/notifiers/tg
52 |
--------------------------------------------------------------------------------
/src/Dhaf.Switchers.GoogleCloud/GoogleCloudSwitcher.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 | using Google.Apis.Auth.OAuth2;
3 | using Google.Apis.Compute.v1;
4 | using Google.Apis.Services;
5 | using Microsoft.Extensions.Logging;
6 | using System;
7 | using System.Linq;
8 | using System.Threading.Tasks;
9 |
10 | namespace Dhaf.Switchers.GoogleCloud
11 | {
12 | public class GoogleCloudSwitcher : ISwitcher
13 | {
14 | private ILogger _logger;
15 |
16 | private Config _config;
17 | private InternalConfig _internalConfig;
18 | private ClusterServiceConfig _serviceConfig;
19 |
20 | protected string _currentEntryPointId = string.Empty;
21 |
22 | protected ComputeService _gcComputeService;
23 | protected ProjectsResource _gcProjectsResource;
24 |
25 | public string ExtensionName => "google-cloud";
26 |
27 | public Type ConfigType => typeof(Config);
28 | public Type InternalConfigType => typeof(InternalConfig);
29 |
30 | public string Sign => $"[{_serviceConfig.Name}/{ExtensionName} sw]";
31 |
32 | public async Task Init(SwitcherInitOptions options)
33 | {
34 | _serviceConfig = options.ClusterServiceConfig;
35 | _logger = options.Logger;
36 |
37 | _logger.LogTrace($"{Sign} Init process...");
38 |
39 | _config = (Config)options.Config;
40 | _internalConfig = (InternalConfig)options.InternalConfig;
41 |
42 | var credential = GoogleCredential.FromFile(_config.CredentialsPath);
43 | _gcComputeService = new ComputeService(new BaseClientService.Initializer
44 | {
45 | HttpClientInitializer = credential
46 | });
47 |
48 | _gcProjectsResource = new ProjectsResource(_gcComputeService);
49 |
50 | var gcProject = await _gcProjectsResource
51 | .Get(_config.Project)
52 | .ExecuteAsync();
53 |
54 | var gcMetadataItem = gcProject.CommonInstanceMetadata.Items
55 | .FirstOrDefault(x => x.Key == _config.MetadataKey);
56 |
57 | if (gcMetadataItem is null)
58 | {
59 | _logger.LogCritical($"Project <{_config.Project}> does not have an initialization value in the metadata for key <{_config.MetadataKey}>.");
60 | throw new ExtensionInitFailedException(Sign);
61 | }
62 |
63 | var currentEntryPoint = _serviceConfig.EntryPoints.FirstOrDefault(x => x.IP == gcMetadataItem.Value);
64 | if (currentEntryPoint is null)
65 | {
66 | _logger.LogCritical($"In project <{_config.Project}>, the initialization value in the metadata for key <{_config.MetadataKey}> " +
67 | $"is incorrect because it does not match any of the entry points in the dhaf configuration." +
68 | $"On the other hand, it may be a mistake in the dhaf configuration.");
69 |
70 | throw new ExtensionInitFailedException(Sign);
71 | }
72 |
73 | _currentEntryPointId = currentEntryPoint.Id;
74 | _logger.LogInformation($"{Sign} Init OK.");
75 | }
76 |
77 | public async Task Switch(SwitcherSwitchOptions options)
78 | {
79 | var entryPoint = _serviceConfig.EntryPoints.FirstOrDefault(x => x.Id == options.EntryPointId);
80 | _logger.LogInformation($"{Sign} Switch to entry point <{entryPoint.Id}> requested...");
81 |
82 | var gcProject = await _gcProjectsResource
83 | .Get(_config.Project)
84 | .ExecuteAsync();
85 |
86 | var gcMetadata = gcProject.CommonInstanceMetadata;
87 | var currValue = gcMetadata.Items.FirstOrDefault(x => x.Key == _config.MetadataKey);
88 |
89 | if (currValue is null)
90 | {
91 | gcMetadata.Items.Add(new Google.Apis.Compute.v1.Data.Metadata.ItemsData
92 | {
93 | Key = _config.MetadataKey,
94 | Value = entryPoint.IP
95 | });
96 | }
97 | else
98 | {
99 | currValue.Value = entryPoint.IP;
100 | }
101 |
102 | await _gcProjectsResource
103 | .SetCommonInstanceMetadata(gcMetadata, _config.Project)
104 | .ExecuteAsync();
105 |
106 | _currentEntryPointId = entryPoint.Id;
107 | _logger.LogInformation($"{Sign} Successfully switched to entry point <{entryPoint.Id}>.");
108 | }
109 |
110 | public async Task GetCurrentEntryPointId()
111 | {
112 | return _currentEntryPointId;
113 | }
114 |
115 | public async Task DhafNodeRoleChangedEventHandler(DhafNodeRole role) { }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/ClusterConfig/YamlResolving.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using YamlDotNet.Core;
5 | using YamlDotNet.Core.Events;
6 | using YamlDotNet.Serialization;
7 | using YamlDotNet.Serialization.NamingConventions;
8 |
9 | namespace Dhaf.Core
10 | {
11 | public class ExtensionConfigTypeMap
12 | {
13 | public string ExtensionName { get; set; }
14 | public Type ConfigTypeInterface { get; set; }
15 | public Type ImplType { get; set; }
16 |
17 | public ExtensionConfigTypeMap(Type configTypeInterface, Type implType)
18 | {
19 | ConfigTypeInterface = configTypeInterface;
20 | ImplType = implType;
21 | }
22 |
23 | public ExtensionConfigTypeMap(string extensionName, Type configTypeInterface, Type implType)
24 | {
25 | ExtensionName = extensionName;
26 | ConfigTypeInterface = configTypeInterface;
27 | ImplType = implType;
28 | }
29 | }
30 |
31 | public class ClusterConfigInterfacesResolver : INodeTypeResolver
32 | {
33 | protected Dictionary _map;
34 |
35 | public ClusterConfigInterfacesResolver(Dictionary map)
36 | {
37 | _map = map;
38 | }
39 |
40 | public bool Resolve(NodeEvent nodeEvent, ref Type type)
41 | {
42 | if (_map.TryGetValue(type, out var implementationType))
43 | {
44 | type = implementationType;
45 | return true;
46 | }
47 |
48 | return false;
49 | }
50 | }
51 |
52 | public class ExtensionConfigYamlMark
53 | {
54 | public int AbsoluteOffset { get; set; }
55 | public Type InterfaceType { get; set; }
56 | public string ExtensionName { get; set; }
57 | }
58 |
59 | public enum EctcMode
60 | {
61 | CollectYamlMarks, ConvertWithMap
62 | }
63 |
64 | public class ExtensionConfigTypeConverter : IYamlTypeConverter
65 | {
66 | private readonly IDeserializer _deserializer;
67 |
68 | public List Map { get; set; } = new();
69 | public EctcMode Mode { get; set; } = EctcMode.CollectYamlMarks;
70 | public List YamlMarks { get; set; } = new();
71 |
72 | public List CatchTypes { get; set; } = new List()
73 | {
74 | typeof(SwitcherDefaultConfig),
75 | typeof(HealthCheckerDefaultConfig),
76 | typeof(NotifierDefaultConfig),
77 | };
78 |
79 | public List AvailableInterfaces { get; set; } = new List()
80 | {
81 | typeof(ISwitcherConfig),
82 | typeof(IHealthCheckerConfig),
83 | typeof(INotifierConfig),
84 | };
85 |
86 | public bool Accepts(Type type)
87 | {
88 | return CatchTypes.Contains(type);
89 | }
90 |
91 | public ExtensionConfigTypeConverter()
92 | {
93 | var deserializer = new DeserializerBuilder()
94 | .WithNamingConvention(HyphenatedNamingConvention.Instance)
95 | .IgnoreUnmatchedProperties()
96 | .Build();
97 |
98 | _deserializer = deserializer;
99 | }
100 |
101 | public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
102 | {
103 | var offset = parser.Current.Start.Index;
104 | var typeInterfaces = type.GetInterfaces();
105 |
106 | var configTypeInterface = AvailableInterfaces
107 | .FirstOrDefault(x => typeInterfaces.Contains(x));
108 |
109 | object obj = null;
110 |
111 | if (Mode == EctcMode.ConvertWithMap)
112 | {
113 | var yamlMark = YamlMarks.FirstOrDefault(x => x.AbsoluteOffset == offset
114 | && x.InterfaceType == configTypeInterface);
115 |
116 | var realConfigType = Map
117 | .FirstOrDefault(x => x.ConfigTypeInterface == yamlMark.InterfaceType
118 | && (x.ExtensionName is null || x.ExtensionName == yamlMark.ExtensionName))
119 | .ImplType;
120 |
121 | obj = _deserializer.Deserialize(parser, realConfigType);
122 | }
123 |
124 | if (Mode == EctcMode.CollectYamlMarks)
125 | {
126 | obj = _deserializer.Deserialize(parser, type);
127 | var yamlMark = new ExtensionConfigYamlMark
128 | {
129 | ExtensionName = ((IExtensionConfig)obj).ExtensionName,
130 | AbsoluteOffset = (int)offset,
131 | InterfaceType = configTypeInterface
132 | };
133 |
134 | YamlMarks.Add(yamlMark);
135 | }
136 |
137 | return obj;
138 | }
139 |
140 | public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerializer serializer)
141 | {
142 | throw new NotImplementedException();
143 | }
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/.github/workflows/osx-x64.yml:
--------------------------------------------------------------------------------
1 | name: macOS-x64
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*.*.*-osx-x64'
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v2
14 | - name: setup .net
15 | uses: actions/setup-dotnet@v1
16 | with:
17 | dotnet-version: 9.0.x
18 |
19 | - name: restore dependencies
20 | run: dotnet restore src/Dhaf.sln
21 |
22 | # TODO: `--no-dependencies` using (this is difficult because extensions have their own directory, and they cannot detect the root `refs` directory)
23 |
24 | - name: build core
25 | run: dotnet publish src/Dhaf.Core/Dhaf.Core.csproj --configuration Release --no-restore -nowarn:CS1998 -r osx-x64 -o bin/osx-x64/core /p:DebugType=None /p:DebugSymbols=false
26 |
27 | - name: build dhaf.node
28 | run: dotnet publish src/Dhaf.Node/Dhaf.Node.csproj --configuration Release --no-dependencies --no-restore -nowarn:CS1998 -r osx-x64 -o bin/osx-x64 -p:PublishSingleFile=true --self-contained false /p:DebugType=None /p:DebugSymbols=false
29 |
30 | - name: build dhaf.cli
31 | run: dotnet publish src/Dhaf.CLI/Dhaf.CLI.csproj --configuration Release --no-dependencies --no-restore -nowarn:CS1998 -r osx-x64 -o bin/osx-x64 -p:PublishSingleFile=true --self-contained false /p:DebugType=None /p:DebugSymbols=false
32 |
33 | - name: build core extensions
34 | run: |
35 | dotnet publish src/Dhaf.HealthCheckers.Web/Dhaf.HealthCheckers.Web.csproj --configuration Release --no-restore -nowarn:CS1998 -r osx-x64 -o bin/osx-x64/ext/hc/web /p:DebugType=None /p:DebugSymbols=false
36 | dotnet publish src/Dhaf.HealthCheckers.Exec/Dhaf.HealthCheckers.Exec.csproj --configuration Release --no-restore -nowarn:CS1998 -r osx-x64 -o bin/osx-x64/ext/hc/exec /p:DebugType=None /p:DebugSymbols=false
37 | dotnet publish src/Dhaf.HealthCheckers.Tcp/Dhaf.HealthCheckers.Tcp.csproj --configuration Release --no-restore -nowarn:CS1998 -r osx-x64 -o bin/osx-x64/ext/hc/tcp /p:DebugType=None /p:DebugSymbols=false
38 |
39 | dotnet publish src/Dhaf.Switchers.Cloudflare/Dhaf.Switchers.Cloudflare.csproj --configuration Release --no-restore -nowarn:CS1998 -r osx-x64 -o bin/osx-x64/ext/sw/cloudflare /p:DebugType=None /p:DebugSymbols=false
40 | dotnet publish src/Dhaf.Switchers.GoogleCloud/Dhaf.Switchers.GoogleCloud.csproj --configuration Release --no-restore -nowarn:CS1998 -r osx-x64 -o bin/osx-x64/ext/sw/google-cloud /p:DebugType=None /p:DebugSymbols=false
41 | dotnet publish src/Dhaf.Switchers.Exec/Dhaf.Switchers.Exec.csproj --configuration Release --no-restore -nowarn:CS1998 -r osx-x64 -o bin/osx-x64/ext/sw/exec /p:DebugType=None /p:DebugSymbols=false
42 |
43 | dotnet publish src/Dhaf.Notifiers.Email/Dhaf.Notifiers.Email.csproj --configuration Release --no-restore -nowarn:CS1998 -r osx-x64 -o bin/osx-x64/ext/ntf/email /p:DebugType=None /p:DebugSymbols=false
44 | dotnet publish src/Dhaf.Notifiers.Telegram/Dhaf.Notifiers.Telegram.csproj --configuration Release --no-restore -nowarn:CS1998 -r osx-x64 -o bin/osx-x64/ext/ntf/tg /p:DebugType=None /p:DebugSymbols=false
45 |
46 | - name: set the release version from tag
47 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
48 |
49 | - name: prepare artifacts
50 | run: |
51 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}
52 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/libs
53 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/health-checkers/web
54 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/health-checkers/exec
55 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/health-checkers/tcp
56 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/switchers/cloudflare
57 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/switchers/google-cloud
58 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/switchers/exec
59 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/notifiers/email
60 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/notifiers/tg
61 |
62 | mv ./bin/osx-x64/Dhaf.Node dhaf-${{ env.RELEASE_VERSION }}/dhaf.node
63 | mv ./bin/osx-x64/Dhaf.CLI dhaf-${{ env.RELEASE_VERSION }}/dhaf.cli
64 | mv ./bin/osx-x64/appsettings.json ./bin/osx-x64/nlog.config dhaf-${{ env.RELEASE_VERSION }}
65 | mv ./bin/osx-x64/core/* dhaf-${{ env.RELEASE_VERSION }}/libs
66 |
67 | mv ./bin/osx-x64/ext/hc/web/* dhaf-${{ env.RELEASE_VERSION }}/extensions/health-checkers/web
68 | mv ./bin/osx-x64/ext/hc/exec/* dhaf-${{ env.RELEASE_VERSION }}/extensions/health-checkers/exec
69 | mv ./bin/osx-x64/ext/hc/tcp/* dhaf-${{ env.RELEASE_VERSION }}/extensions/health-checkers/tcp
70 | mv ./bin/osx-x64/ext/sw/cloudflare/* dhaf-${{ env.RELEASE_VERSION }}/extensions/switchers/cloudflare
71 | mv ./bin/osx-x64/ext/sw/google-cloud/* dhaf-${{ env.RELEASE_VERSION }}/extensions/switchers/google-cloud
72 | mv ./bin/osx-x64/ext/sw/exec/* dhaf-${{ env.RELEASE_VERSION }}/extensions/switchers/exec
73 | mv ./bin/osx-x64/ext/ntf/email/* dhaf-${{ env.RELEASE_VERSION }}/extensions/notifiers/email
74 | mv ./bin/osx-x64/ext/ntf/tg/* dhaf-${{ env.RELEASE_VERSION }}/extensions/notifiers/tg
75 |
76 | - name: upload artifacts
77 | uses: actions/upload-artifact@master
78 | with:
79 | name: dhaf-${{ env.RELEASE_VERSION }}
80 | path: dhaf-${{ env.RELEASE_VERSION }}
81 |
--------------------------------------------------------------------------------
/.github/workflows/win-x64.yml:
--------------------------------------------------------------------------------
1 | name: Windows-x64
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*.*.*-win-x64'
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v2
14 | - name: setup .net
15 | uses: actions/setup-dotnet@v1
16 | with:
17 | dotnet-version: 9.0.x
18 |
19 | - name: restore dependencies
20 | run: dotnet restore src/Dhaf.sln
21 |
22 | # TODO: `--no-dependencies` using (this is difficult because extensions have their own directory, and they cannot detect the root `refs` directory)
23 |
24 | - name: build core
25 | run: dotnet publish src/Dhaf.Core/Dhaf.Core.csproj --configuration Release --no-restore -nowarn:CS1998 -r win-x64 -o bin/win-x64/core /p:DebugType=None /p:DebugSymbols=false
26 |
27 | - name: build dhaf.node
28 | run: dotnet publish src/Dhaf.Node/Dhaf.Node.csproj --configuration Release --no-dependencies --no-restore -nowarn:CS1998 -r win-x64 -o bin/win-x64 -p:PublishSingleFile=true --self-contained false /p:DebugType=None /p:DebugSymbols=false
29 |
30 | - name: build dhaf.cli
31 | run: dotnet publish src/Dhaf.CLI/Dhaf.CLI.csproj --configuration Release --no-dependencies --no-restore -nowarn:CS1998 -r win-x64 -o bin/win-x64 -p:PublishSingleFile=true --self-contained false /p:DebugType=None /p:DebugSymbols=false
32 |
33 | - name: build core extensions
34 | run: |
35 | dotnet publish src/Dhaf.HealthCheckers.Web/Dhaf.HealthCheckers.Web.csproj --configuration Release --no-restore -nowarn:CS1998 -r win-x64 -o bin/win-x64/ext/hc/web /p:DebugType=None /p:DebugSymbols=false
36 | dotnet publish src/Dhaf.HealthCheckers.Exec/Dhaf.HealthCheckers.Exec.csproj --configuration Release --no-restore -nowarn:CS1998 -r win-x64 -o bin/win-x64/ext/hc/exec /p:DebugType=None /p:DebugSymbols=false
37 | dotnet publish src/Dhaf.HealthCheckers.Tcp/Dhaf.HealthCheckers.Tcp.csproj --configuration Release --no-restore -nowarn:CS1998 -r win-x64 -o bin/win-x64/ext/hc/tcp /p:DebugType=None /p:DebugSymbols=false
38 |
39 | dotnet publish src/Dhaf.Switchers.Cloudflare/Dhaf.Switchers.Cloudflare.csproj --configuration Release --no-restore -nowarn:CS1998 -r win-x64 -o bin/win-x64/ext/sw/cloudflare /p:DebugType=None /p:DebugSymbols=false
40 | dotnet publish src/Dhaf.Switchers.GoogleCloud/Dhaf.Switchers.GoogleCloud.csproj --configuration Release --no-restore -nowarn:CS1998 -r win-x64 -o bin/win-x64/ext/sw/google-cloud /p:DebugType=None /p:DebugSymbols=false
41 | dotnet publish src/Dhaf.Switchers.Exec/Dhaf.Switchers.Exec.csproj --configuration Release --no-restore -nowarn:CS1998 -r win-x64 -o bin/win-x64/ext/sw/exec /p:DebugType=None /p:DebugSymbols=false
42 |
43 | dotnet publish src/Dhaf.Notifiers.Email/Dhaf.Notifiers.Email.csproj --configuration Release --no-restore -nowarn:CS1998 -r win-x64 -o bin/win-x64/ext/ntf/email /p:DebugType=None /p:DebugSymbols=false
44 | dotnet publish src/Dhaf.Notifiers.Telegram/Dhaf.Notifiers.Telegram.csproj --configuration Release --no-restore -nowarn:CS1998 -r win-x64 -o bin/win-x64/ext/ntf/tg /p:DebugType=None /p:DebugSymbols=false
45 |
46 | - name: set the release version from tag
47 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
48 |
49 | - name: prepare artifacts
50 | run: |
51 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}
52 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/libs
53 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/health-checkers/web
54 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/health-checkers/exec
55 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/health-checkers/tcp
56 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/switchers/cloudflare
57 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/switchers/google-cloud
58 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/switchers/exec
59 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/notifiers/email
60 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/notifiers/tg
61 |
62 | mv ./bin/win-x64/Dhaf.Node.exe dhaf-${{ env.RELEASE_VERSION }}/Dhaf.Node.exe
63 | mv ./bin/win-x64/Dhaf.CLI.exe dhaf-${{ env.RELEASE_VERSION }}/Dhaf.CLI.exe
64 | mv ./bin/win-x64/appsettings.json ./bin/win-x64/nlog.config dhaf-${{ env.RELEASE_VERSION }}
65 | mv ./bin/win-x64/core/* dhaf-${{ env.RELEASE_VERSION }}/libs
66 |
67 | mv ./bin/win-x64/ext/hc/web/* dhaf-${{ env.RELEASE_VERSION }}/extensions/health-checkers/web
68 | mv ./bin/win-x64/ext/hc/exec/* dhaf-${{ env.RELEASE_VERSION }}/extensions/health-checkers/exec
69 | mv ./bin/win-x64/ext/hc/tcp/* dhaf-${{ env.RELEASE_VERSION }}/extensions/health-checkers/tcp
70 | mv ./bin/win-x64/ext/sw/cloudflare/* dhaf-${{ env.RELEASE_VERSION }}/extensions/switchers/cloudflare
71 | mv ./bin/win-x64/ext/sw/google-cloud/* dhaf-${{ env.RELEASE_VERSION }}/extensions/switchers/google-cloud
72 | mv ./bin/win-x64/ext/sw/exec/* dhaf-${{ env.RELEASE_VERSION }}/extensions/switchers/exec
73 | mv ./bin/win-x64/ext/ntf/email/* dhaf-${{ env.RELEASE_VERSION }}/extensions/notifiers/email
74 | mv ./bin/win-x64/ext/ntf/tg/* dhaf-${{ env.RELEASE_VERSION }}/extensions/notifiers/tg
75 |
76 | - name: upload artifacts
77 | uses: actions/upload-artifact@master
78 | with:
79 | name: dhaf-${{ env.RELEASE_VERSION }}
80 | path: dhaf-${{ env.RELEASE_VERSION }}
81 |
--------------------------------------------------------------------------------
/.github/workflows/linux-x64.yml:
--------------------------------------------------------------------------------
1 | name: Linux-x64
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*.*.*-linux-x64'
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v2
14 | - name: setup .net
15 | uses: actions/setup-dotnet@v1
16 | with:
17 | dotnet-version: 9.0.x
18 |
19 | - name: restore dependencies
20 | run: dotnet restore src/Dhaf.sln
21 |
22 | # TODO: `--no-dependencies` using (this is difficult because extensions have their own directory, and they cannot detect the root `refs` directory)
23 |
24 | - name: build core
25 | run: dotnet publish src/Dhaf.Core/Dhaf.Core.csproj --configuration Release --no-restore -nowarn:CS1998 -r linux-x64 -o bin/linux-x64/core /p:DebugType=None /p:DebugSymbols=false
26 |
27 | - name: build dhaf.node
28 | run: dotnet publish src/Dhaf.Node/Dhaf.Node.csproj --configuration Release --no-dependencies --no-restore -nowarn:CS1998 -r linux-x64 -o bin/linux-x64 -p:PublishSingleFile=true --self-contained false /p:DebugType=None /p:DebugSymbols=false
29 |
30 | - name: build dhaf.cli
31 | run: dotnet publish src/Dhaf.CLI/Dhaf.CLI.csproj --configuration Release --no-dependencies --no-restore -nowarn:CS1998 -r linux-x64 -o bin/linux-x64 -p:PublishSingleFile=true --self-contained false /p:DebugType=None /p:DebugSymbols=false
32 |
33 | - name: build core extensions
34 | run: |
35 | dotnet publish src/Dhaf.HealthCheckers.Web/Dhaf.HealthCheckers.Web.csproj --configuration Release --no-restore -nowarn:CS1998 -r linux-x64 -o bin/linux-x64/ext/hc/web /p:DebugType=None /p:DebugSymbols=false
36 | dotnet publish src/Dhaf.HealthCheckers.Exec/Dhaf.HealthCheckers.Exec.csproj --configuration Release --no-restore -nowarn:CS1998 -r linux-x64 -o bin/linux-x64/ext/hc/exec /p:DebugType=None /p:DebugSymbols=false
37 | dotnet publish src/Dhaf.HealthCheckers.Tcp/Dhaf.HealthCheckers.Tcp.csproj --configuration Release --no-restore -nowarn:CS1998 -r linux-x64 -o bin/linux-x64/ext/hc/tcp /p:DebugType=None /p:DebugSymbols=false
38 |
39 | dotnet publish src/Dhaf.Switchers.Cloudflare/Dhaf.Switchers.Cloudflare.csproj --configuration Release --no-restore -nowarn:CS1998 -r linux-x64 -o bin/linux-x64/ext/sw/cloudflare /p:DebugType=None /p:DebugSymbols=false
40 | dotnet publish src/Dhaf.Switchers.GoogleCloud/Dhaf.Switchers.GoogleCloud.csproj --configuration Release --no-restore -nowarn:CS1998 -r linux-x64 -o bin/linux-x64/ext/sw/google-cloud /p:DebugType=None /p:DebugSymbols=false
41 | dotnet publish src/Dhaf.Switchers.Exec/Dhaf.Switchers.Exec.csproj --configuration Release --no-restore -nowarn:CS1998 -r linux-x64 -o bin/linux-x64/ext/sw/exec /p:DebugType=None /p:DebugSymbols=false
42 |
43 | dotnet publish src/Dhaf.Notifiers.Email/Dhaf.Notifiers.Email.csproj --configuration Release --no-restore -nowarn:CS1998 -r linux-x64 -o bin/linux-x64/ext/ntf/email /p:DebugType=None /p:DebugSymbols=false
44 | dotnet publish src/Dhaf.Notifiers.Telegram/Dhaf.Notifiers.Telegram.csproj --configuration Release --no-restore -nowarn:CS1998 -r linux-x64 -o bin/linux-x64/ext/ntf/tg /p:DebugType=None /p:DebugSymbols=false
45 |
46 | - name: set the release version from tag
47 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
48 |
49 | - name: prepare artifacts
50 | run: |
51 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}
52 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/libs
53 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/health-checkers/web
54 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/health-checkers/exec
55 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/health-checkers/tcp
56 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/switchers/cloudflare
57 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/switchers/google-cloud
58 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/switchers/exec
59 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/notifiers/email
60 | mkdir -p dhaf-${{ env.RELEASE_VERSION }}/extensions/notifiers/tg
61 |
62 | mv ./bin/linux-x64/Dhaf.Node dhaf-${{ env.RELEASE_VERSION }}/dhaf.node
63 | mv ./bin/linux-x64/Dhaf.CLI dhaf-${{ env.RELEASE_VERSION }}/dhaf.cli
64 | mv ./bin/linux-x64/appsettings.json ./bin/linux-x64/nlog.config dhaf-${{ env.RELEASE_VERSION }}
65 | mv ./bin/linux-x64/core/* dhaf-${{ env.RELEASE_VERSION }}/libs
66 |
67 | mv ./bin/linux-x64/ext/hc/web/* dhaf-${{ env.RELEASE_VERSION }}/extensions/health-checkers/web
68 | mv ./bin/linux-x64/ext/hc/exec/* dhaf-${{ env.RELEASE_VERSION }}/extensions/health-checkers/exec
69 | mv ./bin/linux-x64/ext/hc/tcp/* dhaf-${{ env.RELEASE_VERSION }}/extensions/health-checkers/tcp
70 | mv ./bin/linux-x64/ext/sw/cloudflare/* dhaf-${{ env.RELEASE_VERSION }}/extensions/switchers/cloudflare
71 | mv ./bin/linux-x64/ext/sw/google-cloud/* dhaf-${{ env.RELEASE_VERSION }}/extensions/switchers/google-cloud
72 | mv ./bin/linux-x64/ext/sw/exec/* dhaf-${{ env.RELEASE_VERSION }}/extensions/switchers/exec
73 | mv ./bin/linux-x64/ext/ntf/email/* dhaf-${{ env.RELEASE_VERSION }}/extensions/notifiers/email
74 | mv ./bin/linux-x64/ext/ntf/tg/* dhaf-${{ env.RELEASE_VERSION }}/extensions/notifiers/tg
75 |
76 | - name: upload artifacts
77 | uses: actions/upload-artifact@master
78 | with:
79 | name: dhaf-${{ env.RELEASE_VERSION }}
80 | path: dhaf-${{ env.RELEASE_VERSION }}
81 |
--------------------------------------------------------------------------------
/tests/Dhaf.Core.Tests/ClusterConfigParserTest.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 | using Moq;
3 | using System.Collections.Generic;
4 | using System.Threading.Tasks;
5 | using Xunit;
6 |
7 | namespace Dhaf.Core.Tests
8 | {
9 | public class ClusterConfigParserTest
10 | {
11 | [Fact]
12 | public async Task ParseTest()
13 | {
14 | var extensionsScope = GetExtensionsScope();
15 | var _configuration = GetConfiguration();
16 |
17 | var internalConfig = new DhafInternalConfig();
18 | _configuration.Bind(internalConfig);
19 |
20 | var configParser = new ClusterConfigParser("Data/test_config_1.dhaf", extensionsScope, internalConfig);
21 | var parsedConfig = await configParser.Parse();
22 |
23 | Assert.NotNull(parsedConfig.Dhaf);
24 | Assert.Equal("test-cr", parsedConfig.Dhaf.ClusterName);
25 | Assert.Equal("node-1", parsedConfig.Dhaf.NodeName);
26 |
27 | Assert.NotNull(parsedConfig.Dhaf.WebApi);
28 | Assert.Equal("localhost", parsedConfig.Dhaf.WebApi.Host);
29 | Assert.Equal(8128, parsedConfig.Dhaf.WebApi.Port);
30 |
31 | Assert.NotNull(parsedConfig.Etcd);
32 | Assert.Equal("http://11.22.33.44:2379", parsedConfig.Etcd.Hosts);
33 |
34 | Assert.NotNull(parsedConfig.Services);
35 | Assert.Equal(2, parsedConfig.Services.Count);
36 |
37 | var serv1 = parsedConfig.Services[0];
38 | Assert.NotNull(serv1);
39 | Assert.Equal("serv1", serv1.Name);
40 | Assert.Equal("site.com", serv1.Domain);
41 | Assert.NotNull(serv1.EntryPoints);
42 | Assert.Equal(3, serv1.EntryPoints.Count);
43 |
44 | var serv1nc1 = serv1.EntryPoints[0];
45 | Assert.Equal("nc1", serv1nc1.Id);
46 | Assert.Equal("100.1.1.1", serv1nc1.IP);
47 |
48 | var serv1nc2 = serv1.EntryPoints[1];
49 | Assert.Equal("nc2", serv1nc2.Id);
50 | Assert.Equal("100.1.1.2", serv1nc2.IP);
51 |
52 | Assert.NotNull(serv1.Switcher);
53 | Assert.Equal("a", serv1.Switcher.ExtensionName);
54 |
55 | Assert.NotNull(serv1.HealthChecker);
56 | Assert.Equal("a", serv1.HealthChecker.ExtensionName);
57 |
58 | var serv2 = parsedConfig.Services[1];
59 | Assert.NotNull(serv2);
60 | Assert.Equal("serv2", serv2.Name);
61 | Assert.Equal("foo.site.com", serv2.Domain);
62 | Assert.NotNull(serv2.EntryPoints);
63 | Assert.Equal(2, serv2.EntryPoints.Count);
64 |
65 | Assert.NotNull(parsedConfig.Notifiers);
66 | Assert.Single(parsedConfig.Notifiers);
67 |
68 | var ntf1 = parsedConfig.Notifiers[0];
69 | Assert.NotNull(ntf1);
70 | Assert.Equal("a", ntf1.ExtensionName);
71 | Assert.Equal("a", ntf1.Name);
72 | }
73 |
74 | [Fact]
75 | public async Task ParseTest_Issue_25()
76 | {
77 | var extensionsScope = GetExtensionsScope();
78 | var _configuration = GetConfiguration();
79 |
80 | var internalConfig = new DhafInternalConfig();
81 | _configuration.Bind(internalConfig);
82 |
83 | var configParser = new ClusterConfigParser("Data/test_config_issue_25.dhaf", extensionsScope, internalConfig);
84 | var parsedConfig = await configParser.Parse();
85 |
86 | Assert.NotNull(parsedConfig.Dhaf.WebApi);
87 | Assert.Equal("localhost", parsedConfig.Dhaf.WebApi.Host);
88 | Assert.Equal(8128, parsedConfig.Dhaf.WebApi.Port);
89 | }
90 |
91 | private ExtensionsScope GetExtensionsScope()
92 | {
93 | var switcher = new Mock();
94 | switcher.Setup(x => x.ExtensionName).Returns("cloudflare");
95 | switcher.Setup(x => x.ConfigType).Returns(typeof(SwitcherConfigMock));
96 | switcher.Setup(x => x.InternalConfigType).Returns(typeof(SwitcherInternalConfigMock));
97 |
98 | var healthChecker = new Mock();
99 | healthChecker.Setup(x => x.ExtensionName).Returns("web");
100 | healthChecker.Setup(x => x.ConfigType).Returns(typeof(HealthCheckerConfigMock));
101 | healthChecker.Setup(x => x.InternalConfigType).Returns(typeof(HealthCheckerInternalConfigMock));
102 |
103 | var tgNotifier = new Mock();
104 | tgNotifier.Setup(x => x.ExtensionName).Returns("tg");
105 | tgNotifier.Setup(x => x.ConfigType).Returns(typeof(NotifierConfigMock));
106 | tgNotifier.Setup(x => x.InternalConfigType).Returns(typeof(NotifierInternalConfigMock));
107 |
108 | var extensionsScope = new ExtensionsScope
109 | {
110 | Switchers = new List>()
111 | {
112 | new DhafExtension
113 | {
114 | ExtensionPath = "sw/cloudflare", Instance = switcher.Object
115 | }
116 | },
117 | HealthCheckers = new List>()
118 | {
119 | new DhafExtension
120 | {
121 | ExtensionPath = "hc/web", Instance = healthChecker.Object
122 | }
123 | },
124 | Notifiers = new List>()
125 | {
126 | new DhafExtension
127 | {
128 | ExtensionPath = "ntf/tg", Instance = tgNotifier.Object
129 | }
130 | }
131 | };
132 |
133 | return extensionsScope;
134 | }
135 |
136 | private IConfigurationRoot GetConfiguration()
137 | {
138 | // TODO: For performance reasons, it is better to do the configuration reading once before all the tests.
139 | // However, flexibility is lost in this way.
140 |
141 | return new ConfigurationBuilder()
142 | .AddJsonFile("appsettings.json")
143 | .Build();
144 | }
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/Dhaf.Notifiers.Telegram/UpdatesProcessing.cs:
--------------------------------------------------------------------------------
1 | using Dhaf.Core;
2 | using Microsoft.Extensions.Logging;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 | using Telegram.Bot;
9 | using Telegram.Bot.Exceptions;
10 | using Telegram.Bot.Requests;
11 | using Telegram.Bot.Types;
12 | using Telegram.Bot.Types.Enums;
13 |
14 | namespace Dhaf.Notifiers.Telegram
15 | {
16 | public partial class TelegramNotifier : INotifier
17 | {
18 | private static readonly Update[] EmptyUpdates = Array.Empty();
19 | private int? _messageOffset = null;
20 | private CancellationTokenSource _handleUpdatesWithIntervalCts = new();
21 | private Task _handleUpdatesWithIntervalTask;
22 |
23 | protected async Task HandleUpdatesWithInterval(CancellationToken cancellationToken)
24 | {
25 | var interval = _internalConfig.UpdatesPollingInterval;
26 |
27 | while (!cancellationToken.IsCancellationRequested)
28 | {
29 | var updates = await ReceiveUpdates();
30 | await HandleUpdates(updates);
31 | await Task.Delay(TimeSpan.FromSeconds(interval));
32 | }
33 | }
34 |
35 | protected async Task HandleUpdates(Update[] updates)
36 | {
37 | foreach (var update in updates)
38 | {
39 | if (update.Message.Type == MessageType.LeftChatMember)
40 | {
41 | if (update.Message.LeftChatMember.Id == _botClient.BotId)
42 | {
43 | var chatId = update.Message.Chat.Id;
44 | await DeleteSubscriber(chatId);
45 |
46 | _logger.LogTrace($"{Sign} The bot was removed from chat with id <{chatId}>.");
47 |
48 | continue;
49 | }
50 | }
51 |
52 | if (update.Message.Type == MessageType.Text)
53 | {
54 | var chatId = update.Message.Chat.Id;
55 |
56 | try
57 | {
58 | if (update.Message.Text.StartsWith("/start"))
59 | {
60 | var subInStore = await GetSubscriberOfDefault(chatId);
61 | if (subInStore.HasValue)
62 | {
63 | await _botClient.SendTextMessageAsync(
64 | chatId: chatId,
65 | text: "You are already subscribed to notifications."
66 | );
67 |
68 | continue;
69 | }
70 |
71 | var parts = update.Message.Text.Split(' ');
72 | if (parts.Length == 2)
73 | {
74 | var joinCode = parts[1];
75 | if (joinCode == _config.JoinCode)
76 | {
77 | await PutSubscriber(chatId, update.Message.Chat.Type.ToString());
78 |
79 | await _botClient.SendTextMessageAsync(
80 | chatId: chatId,
81 | text: "The join code is correct. You will now receive notifications from me."
82 | );
83 |
84 | _logger.LogInformation($"{Sign} Chat with id <{chatId}> has been added to receive notifications.");
85 | }
86 | else
87 | {
88 | await _botClient.SendTextMessageAsync(
89 | chatId: chatId,
90 | text: "The join code is incorrect. Try again."
91 | );
92 | }
93 | }
94 | else
95 | {
96 | await _botClient.SendTextMessageAsync(
97 | chatId: chatId,
98 | text: "Please use the following command format:\n```\n/start \n```",
99 | parseMode: ParseMode.MarkdownV2
100 | );
101 | }
102 | }
103 | else
104 | {
105 | var subInStore = await GetSubscriberOfDefault(chatId);
106 | if (subInStore.HasValue)
107 | {
108 | await _botClient.SendTextMessageAsync(
109 | chatId: chatId,
110 | text: "I don't understand you. You're subscribed to notifications from me, so you can relax for now."
111 | );
112 | }
113 | else
114 | {
115 | await _botClient.SendTextMessageAsync(
116 | chatId: chatId,
117 | text: "I don't understand you\\. By the way, you're not subscribed to notifications from me\\. I recommend that you do so using the following command:\n```\n/start \n```",
118 | parseMode: ParseMode.MarkdownV2
119 | );
120 | }
121 | }
122 | }
123 | catch (ApiRequestException e)
124 | {
125 | await ProcessPossibleUnavailableSubscriber(e, chatId);
126 | }
127 | catch { }
128 | }
129 | }
130 | }
131 |
132 | protected async Task ReceiveUpdates()
133 | {
134 | var allowedUpdates = new List()
135 | {
136 | UpdateType.Message
137 | };
138 |
139 | var updates = EmptyUpdates;
140 |
141 | var request = new GetUpdatesRequest
142 | {
143 | Offset = _messageOffset ?? 0,
144 | AllowedUpdates = allowedUpdates,
145 | };
146 |
147 | try
148 | {
149 | updates = await _botClient.MakeRequestAsync(request);
150 | }
151 | catch { }
152 |
153 | if (updates.Any())
154 | {
155 | var lastUpdateId = updates.Max(x => x.Id);
156 | _messageOffset = lastUpdateId + 1;
157 | }
158 |
159 | return updates;
160 | }
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/Dhaf.Core/ExtensionsScope/ExtensionsScope.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Reflection;
5 | using System.Text.Json;
6 |
7 | namespace Dhaf.Core
8 | {
9 | public class DhafExtension
10 | {
11 | public string ExtensionPath { get; set; }
12 | public T Instance { get; set; }
13 | }
14 |
15 | public class ExtensionsScope
16 | {
17 | public IEnumerable> HealthCheckers { get; set; }
18 | public IEnumerable> Switchers { get; set; }
19 | public IEnumerable> Notifiers { get; set; }
20 | }
21 |
22 | public class ExtensionMeta
23 | {
24 | public string EntryPoint { get; set; }
25 |
26 | public T InternalConfiguration { get; set; }
27 | }
28 |
29 | public class ExtensionsScopeFactory
30 | {
31 | public static T CreateSuchAs(T source)
32 | {
33 | var type = source.GetType();
34 | var destination = Activator.CreateInstance(type);
35 |
36 | return (T)destination;
37 | }
38 |
39 | public static ExtensionsScope GetExtensionsScope(IEnumerable extensionsPath)
40 | {
41 | var healthCheckers = new List>();
42 | var switchers = new List>();
43 | var notifiers = new List>();
44 |
45 | foreach (var path in extensionsPath)
46 | {
47 | var extAssembly = LoadExtension(path);
48 | var impls = GetImplementationsFromAssembly(extAssembly, path);
49 |
50 | healthCheckers.AddRange(impls.HealthCheckers);
51 | switchers.AddRange(impls.Switchers);
52 | notifiers.AddRange(impls.Notifiers);
53 | }
54 |
55 | return new ExtensionsScope
56 | {
57 | HealthCheckers = healthCheckers,
58 | Switchers = switchers,
59 | Notifiers = notifiers,
60 | };
61 | }
62 |
63 | protected static Assembly LoadExtension(string path)
64 | {
65 | var extensionDir = $"extensions/{path}/";
66 | var metaRaw = File.ReadAllText(extensionDir + "extension.json");
67 | var meta = JsonSerializer.Deserialize>(metaRaw, DhafInternalConfig.JsonSerializerOptions);
68 |
69 | var loadContext = new ExtensionLoadContext(extensionDir + meta.EntryPoint);
70 | return loadContext.LoadFromAssemblyName(
71 | new AssemblyName(Path.GetFileNameWithoutExtension(extensionDir + meta.EntryPoint))
72 | );
73 | }
74 |
75 | protected static ExtensionsScope GetImplementationsFromAssembly(Assembly assembly, string extensionPath)
76 | {
77 | var healthCheckers = new List>();
78 | var switchers = new List>();
79 | var notifiers = new List>();
80 |
81 | var types = assembly.GetTypes();
82 | foreach (var type in types)
83 | {
84 | var healthChecker = GetImplementationOrDefault(type);
85 | if (healthChecker != null)
86 | {
87 | if (!typeof(IHealthCheckerConfig).IsAssignableFrom(healthChecker.ConfigType))
88 | {
89 | throw new Exception($"The health checker config type <{healthChecker.ConfigType}> does not implement the dhaf health checker config interface.");
90 | }
91 |
92 | if (!typeof(IHealthCheckerInternalConfig).IsAssignableFrom(healthChecker.InternalConfigType))
93 | {
94 | throw new Exception($"The health checker internal config type <{healthChecker.InternalConfigType}> does not implement the dhaf health checker internal interface.");
95 | }
96 |
97 | healthCheckers.Add(new DhafExtension
98 | {
99 | Instance = healthChecker,
100 | ExtensionPath = extensionPath
101 | });
102 |
103 | continue;
104 | }
105 |
106 | var switcher = GetImplementationOrDefault(type);
107 | if (switcher != null)
108 | {
109 | if (!typeof(ISwitcherConfig).IsAssignableFrom(switcher.ConfigType))
110 | {
111 | throw new Exception($"The switch config type <{switcher.ConfigType}> does not implement the dhaf switch config interface.");
112 | }
113 |
114 | if (!typeof(ISwitcherInternalConfig).IsAssignableFrom(switcher.InternalConfigType))
115 | {
116 | throw new Exception($"The switch internal config type <{switcher.InternalConfigType}> does not implement the dhaf switch internal config interface.");
117 | }
118 |
119 | switchers.Add(new DhafExtension
120 | {
121 | Instance = switcher,
122 | ExtensionPath = extensionPath
123 | });
124 |
125 | continue;
126 | }
127 |
128 | var notifier = GetImplementationOrDefault(type);
129 | if (notifier != null)
130 | {
131 | if (!typeof(INotifierConfig).IsAssignableFrom(notifier.ConfigType))
132 | {
133 | throw new Exception($"The notifier config type <{notifier.ConfigType}> does not implement the dhaf notifier config interface.");
134 | }
135 |
136 | if (!typeof(INotifierInternalConfig).IsAssignableFrom(notifier.InternalConfigType))
137 | {
138 | throw new Exception($"The notifier internal config type <{notifier.InternalConfigType}> does not implement the dhaf notifier internal config interface.");
139 | }
140 |
141 | notifiers.Add(new DhafExtension
142 | {
143 | Instance = notifier,
144 | ExtensionPath = extensionPath
145 | });
146 | }
147 | }
148 |
149 | return new ExtensionsScope
150 | {
151 | HealthCheckers = healthCheckers,
152 | Switchers = switchers,
153 | Notifiers = notifiers
154 | };
155 | }
156 |
157 | protected static T GetImplementationOrDefault(Type type)
158 | where T : class
159 | {
160 | if (typeof(T).IsAssignableFrom(type)
161 | && Activator.CreateInstance(type) is T impl)
162 | {
163 | return impl;
164 | }
165 |
166 | return null;
167 | }
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/project_identity/small.pdf:
--------------------------------------------------------------------------------
1 | %PDF-1.7
2 |
3 | 1 0 obj
4 | << /Length 2 0 R >>
5 | stream
6 | 0.234000 0 0.000000 0.000000 0.234000 1.000000 d1
7 |
8 | endstream
9 | endobj
10 |
11 | 2 0 obj
12 | 50
13 | endobj
14 |
15 | 3 0 obj
16 | << /Length 4 0 R >>
17 | stream
18 | 0.493000 0 0.038000 0.000000 0.463000 0.897000 d1
19 |
20 | endstream
21 | endobj
22 |
23 | 4 0 obj
24 | 50
25 | endobj
26 |
27 | 5 0 obj
28 | << /Length 6 0 R >>
29 | stream
30 | 0.499000 0 0.038000 0.000000 0.461000 0.897000 d1
31 |
32 | endstream
33 | endobj
34 |
35 | 6 0 obj
36 | 50
37 | endobj
38 |
39 | 7 0 obj
40 | << /Length 8 0 R >>
41 | stream
42 | 0.399000 0 0.038000 0.000000 0.382000 0.897000 d1
43 |
44 | endstream
45 | endobj
46 |
47 | 8 0 obj
48 | 50
49 | endobj
50 |
51 | 9 0 obj
52 | << /Length 10 0 R >>
53 | stream
54 | 0.485000 0 0.015000 0.000000 0.471000 0.874000 d1
55 |
56 | endstream
57 | endobj
58 |
59 | 10 0 obj
60 | 50
61 | endobj
62 |
63 | 11 0 obj
64 | [ 0.234000 0.499000 0.399000 0.493000 0.485000 ]
65 | endobj
66 |
67 | 12 0 obj
68 | << /Length 13 0 R >>
69 | stream
70 | /CIDInit /ProcSet findresource begin
71 | 12 dict begin
72 | begincmap
73 | /CIDSystemInfo
74 | << /Registry (FigmaPDF)
75 | /Ordering (FigmaPDF)
76 | /Supplement 0
77 | >> def
78 | /CMapName /A-B-C def
79 | /CMapType 2 def
80 | 1 begincodespacerange
81 | <00>
82 | endcodespacerange
83 | 1 beginbfchar
84 | <00> <0020>
85 | endbfchar
86 | 1 beginbfchar
87 | <01> <0048>
88 | endbfchar
89 | 1 beginbfchar
90 | <02> <0046>
91 | endbfchar
92 | 1 beginbfchar
93 | <03> <0044>
94 | endbfchar
95 | 1 beginbfchar
96 | <04> <0041>
97 | endbfchar
98 | endcmap
99 | CMapName currentdict /CMap defineresource pop
100 | end
101 | end
102 | endstream
103 | endobj
104 |
105 | 13 0 obj
106 | 476
107 | endobj
108 |
109 | 14 0 obj
110 | << /Subtype /Type3
111 | /CharProcs << /C0 1 0 R
112 | /C3 3 0 R
113 | /C1 5 0 R
114 | /C2 7 0 R
115 | /C4 9 0 R
116 | >>
117 | /Encoding << /Type /Encoding
118 | /Differences [ 0 /C0 /C1 /C2 /C3 /C4 ]
119 | >>
120 | /Widths 11 0 R
121 | /FontBBox [ 0.000000 0.000000 0.000000 0.000000 ]
122 | /FontMatrix [ 1.000000 0.000000 0.000000 1.000000 0.000000 0.000000 ]
123 | /Type /Font
124 | /ToUnicode 12 0 R
125 | /FirstChar 0
126 | /LastChar 4
127 | /Resources << >>
128 | >>
129 | endobj
130 |
131 | 15 0 obj
132 | << /Font << /F1 14 0 R >> >>
133 | endobj
134 |
135 | 16 0 obj
136 | << /Length 17 0 R >>
137 | stream
138 | /DeviceRGB CS
139 | /DeviceRGB cs
140 | q
141 | 1.000000 0.000000 -0.000000 1.000000 1282.177979 1781.162109 cm
142 | 0.145833 0.145833 0.145833 scn
143 | 123.656822 224.682129 m
144 | 146.705200 0.000061 l
145 | 0.000000 0.000061 l
146 | 123.656822 224.682129 l
147 | h
148 | f
149 | n
150 | Q
151 | q
152 | 1.000000 0.000000 -0.000000 1.000000 1282.177979 1781.162109 cm
153 | 0.145833 0.145833 0.145833 scn
154 | 283.193970 224.682114 m
155 | 260.145630 0.000061 l
156 | 410.399628 0.000061 l
157 | 283.193970 224.682114 l
158 | h
159 | f
160 | n
161 | Q
162 | q
163 | 1.000000 0.000000 -0.000000 1.000000 181.972412 1093.675293 cm
164 | 0.145833 0.145833 0.145833 scn
165 | 196.375854 -250.899902 m
166 | h
167 | 230.575851 522.200134 m
168 | 443.875854 522.200134 l
169 | 499.075836 522.200134 540.475830 506.900116 568.075867 476.300110 c
170 | 595.675903 445.700104 609.775879 400.400116 610.375854 340.400116 c
171 | 612.175842 -21.399902 l
172 | 612.775879 -97.599915 599.575867 -154.899902 572.575867 -193.299927 c
173 | 545.575867 -231.699890 500.875854 -250.899902 438.475861 -250.899902 c
174 | 230.575851 -250.899902 l
175 | 230.575851 522.200134 l
176 | h
177 | 413.275879 -114.099915 m
178 | 441.475861 -114.099915 455.575867 -100.299927 455.575867 -72.699890 c
179 | 455.575867 326.000122 l
180 | 455.575867 343.400116 454.075867 356.300110 451.075867 364.700104 c
181 | 448.675873 373.700134 443.875854 379.700165 436.675873 382.700134 c
182 | 429.475891 385.700134 418.375885 387.200134 403.375854 387.200134 c
183 | 386.275879 387.200134 l
184 | 386.275879 -114.099915 l
185 | 413.275879 -114.099915 l
186 | h
187 | 640.223511 -250.899902 m
188 | h
189 | 674.423523 -250.899902 m
190 | 674.423523 522.200134 l
191 | 828.323486 522.200134 l
192 | 828.323486 245.900146 l
193 | 901.223511 245.900146 l
194 | 901.223511 522.200134 l
195 | 1055.123535 522.200134 l
196 | 1055.123535 -250.899902 l
197 | 901.223511 -250.899902 l
198 | 901.223511 100.100098 l
199 | 828.323486 100.100098 l
200 | 828.323486 -250.899902 l
201 | 674.423523 -250.899902 l
202 | h
203 | 1511.219604 -250.899902 m
204 | h
205 | 1545.419556 -250.899902 m
206 | 1545.419556 522.200134 l
207 | 1855.019653 522.200134 l
208 | 1855.019653 371.900116 l
209 | 1701.119629 371.900116 l
210 | 1701.119629 248.600098 l
211 | 1846.919678 248.600098 l
212 | 1846.919678 100.100098 l
213 | 1701.119629 100.100098 l
214 | 1701.119629 -250.899902 l
215 | 1545.419556 -250.899902 l
216 | h
217 | f
218 | n
219 | Q
220 | q
221 | 1.000000 0.000000 -0.000000 1.000000 181.972412 1093.675293 cm
222 | BT
223 | 900.000000 0.000000 0.000000 900.000000 196.375854 -250.899902 Tm
224 | /F1 1.000000 Tf
225 | [ (\003) (\001) (\000) (\000) (\002) ] TJ
226 | ET
227 | Q
228 | q
229 | -1.000000 0.000000 -0.000000 -1.000000 2198.093994 1489.029785 cm
230 | 0.145833 0.145833 0.145833 scn
231 | 494.591797 -250.899902 m
232 | h
233 | 508.091797 -250.899902 m
234 | 582.791809 522.200134 l
235 | 844.691833 522.200134 l
236 | 918.491821 -250.899902 l
237 | 771.791809 -250.899902 l
238 | 760.991821 -125.799866 l
239 | 667.391785 -125.799866 l
240 | 658.391785 -250.899902 l
241 | 508.091797 -250.899902 l
242 | h
243 | 679.091797 -2.499878 m
244 | 749.291809 -2.499878 l
245 | 715.091797 390.800140 l
246 | 707.891785 390.800140 l
247 | 679.091797 -2.499878 l
248 | h
249 | f
250 | n
251 | Q
252 | q
253 | -1.000000 0.000000 -0.000000 -1.000000 2198.093994 1489.029785 cm
254 | BT
255 | 900.000000 0.000000 0.000000 900.000000 494.591797 -250.899902 Tm
256 | /F1 1.000000 Tf
257 | [ (\004) ] TJ
258 | ET
259 | Q
260 |
261 | endstream
262 | endobj
263 |
264 | 17 0 obj
265 | 2849
266 | endobj
267 |
268 | 18 0 obj
269 | << /Annots []
270 | /Type /Page
271 | /MediaBox [ 0.000000 0.000000 2444.000000 2444.000000 ]
272 | /Resources 15 0 R
273 | /Contents 16 0 R
274 | /Parent 19 0 R
275 | >>
276 | endobj
277 |
278 | 19 0 obj
279 | << /Kids [ 18 0 R ]
280 | /Count 1
281 | /Type /Pages
282 | >>
283 | endobj
284 |
285 | 20 0 obj
286 | << /Type /Catalog
287 | /Pages 19 0 R
288 | >>
289 | endobj
290 |
291 | xref
292 | 0 21
293 | 0000000000 65535 f
294 | 0000000010 00000 n
295 | 0000000116 00000 n
296 | 0000000137 00000 n
297 | 0000000243 00000 n
298 | 0000000264 00000 n
299 | 0000000370 00000 n
300 | 0000000391 00000 n
301 | 0000000497 00000 n
302 | 0000000518 00000 n
303 | 0000000625 00000 n
304 | 0000000647 00000 n
305 | 0000000715 00000 n
306 | 0000001249 00000 n
307 | 0000001272 00000 n
308 | 0000001835 00000 n
309 | 0000001883 00000 n
310 | 0000004790 00000 n
311 | 0000004814 00000 n
312 | 0000004995 00000 n
313 | 0000005071 00000 n
314 | trailer
315 | << /ID [ (some) (id) ]
316 | /Root 20 0 R
317 | /Size 21
318 | >>
319 | startxref
320 | 5132
321 | %%EOF
--------------------------------------------------------------------------------
/src/Dhaf.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.31005.135
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dhaf.Node", "Dhaf.Node\Dhaf.Node.csproj", "{8567AF9C-6406-431F-9C1D-74460B9CBC4A}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dhaf.Switchers.Cloudflare", "Dhaf.Switchers.Cloudflare\Dhaf.Switchers.Cloudflare.csproj", "{53A14176-6048-4CD6-B13E-C46AE119FE7B}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dhaf.CLI", "Dhaf.CLI\Dhaf.CLI.csproj", "{BE053C9E-8668-4746-A283-02CA32EE552E}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dhaf.Switchers.Exec", "Dhaf.Switchers.Exec\Dhaf.Switchers.Exec.csproj", "{AF16D4AF-96D2-4D0A-B057-1FD5BC888E4B}"
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dhaf.HealthCheckers.Web", "Dhaf.HealthCheckers.Web\Dhaf.HealthCheckers.Web.csproj", "{E34EFAB0-FAC0-4444-B981-42C60A2762B4}"
15 | EndProject
16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dhaf.Core", "Dhaf.Core\Dhaf.Core.csproj", "{ACE75F82-05C9-4BE6-A528-12F7CF4ABFDE}"
17 | EndProject
18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dhaf.HealthCheckers.Exec", "Dhaf.HealthCheckers.Exec\Dhaf.HealthCheckers.Exec.csproj", "{F8C98E17-835F-41F5-AB7E-5CE85AD010E6}"
19 | EndProject
20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dhaf.Notifiers.Email", "Dhaf.Notifiers.Email\Dhaf.Notifiers.Email.csproj", "{01665C8E-8D27-4CDB-A965-2756D8030624}"
21 | EndProject
22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dhaf.Notifiers.Telegram", "Dhaf.Notifiers.Telegram\Dhaf.Notifiers.Telegram.csproj", "{010BAE78-C9AD-4F60-A3AD-7588C724E52A}"
23 | EndProject
24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dhaf.Core.Tests", "..\tests\Dhaf.Core.Tests\Dhaf.Core.Tests.csproj", "{B24910B3-1629-4C77-81B6-CED348589E29}"
25 | EndProject
26 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dhaf.Node.DataTransferObjects", "Dhaf.Node.DataTransferObjects\Dhaf.Node.DataTransferObjects.csproj", "{7BCA415F-B775-428C-8903-26FCDC1AFCB3}"
27 | EndProject
28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dhaf.Switchers.GoogleCloud", "Dhaf.Switchers.GoogleCloud\Dhaf.Switchers.GoogleCloud.csproj", "{B90E1B46-8259-4B93-85FB-D7C50F09E582}"
29 | EndProject
30 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dhaf.HealthCheckers.Tcp", "Dhaf.HealthCheckers.Tcp\Dhaf.HealthCheckers.Tcp.csproj", "{60712C16-B67F-485D-A64A-0E446EAD4EFB}"
31 | EndProject
32 | Global
33 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
34 | Debug|Any CPU = Debug|Any CPU
35 | Release|Any CPU = Release|Any CPU
36 | EndGlobalSection
37 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
38 | {8567AF9C-6406-431F-9C1D-74460B9CBC4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39 | {8567AF9C-6406-431F-9C1D-74460B9CBC4A}.Debug|Any CPU.Build.0 = Debug|Any CPU
40 | {8567AF9C-6406-431F-9C1D-74460B9CBC4A}.Release|Any CPU.ActiveCfg = Release|Any CPU
41 | {8567AF9C-6406-431F-9C1D-74460B9CBC4A}.Release|Any CPU.Build.0 = Release|Any CPU
42 | {53A14176-6048-4CD6-B13E-C46AE119FE7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
43 | {53A14176-6048-4CD6-B13E-C46AE119FE7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
44 | {53A14176-6048-4CD6-B13E-C46AE119FE7B}.Release|Any CPU.ActiveCfg = Release|Any CPU
45 | {53A14176-6048-4CD6-B13E-C46AE119FE7B}.Release|Any CPU.Build.0 = Release|Any CPU
46 | {BE053C9E-8668-4746-A283-02CA32EE552E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
47 | {BE053C9E-8668-4746-A283-02CA32EE552E}.Debug|Any CPU.Build.0 = Debug|Any CPU
48 | {BE053C9E-8668-4746-A283-02CA32EE552E}.Release|Any CPU.ActiveCfg = Release|Any CPU
49 | {BE053C9E-8668-4746-A283-02CA32EE552E}.Release|Any CPU.Build.0 = Release|Any CPU
50 | {AF16D4AF-96D2-4D0A-B057-1FD5BC888E4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
51 | {AF16D4AF-96D2-4D0A-B057-1FD5BC888E4B}.Debug|Any CPU.Build.0 = Debug|Any CPU
52 | {AF16D4AF-96D2-4D0A-B057-1FD5BC888E4B}.Release|Any CPU.ActiveCfg = Release|Any CPU
53 | {AF16D4AF-96D2-4D0A-B057-1FD5BC888E4B}.Release|Any CPU.Build.0 = Release|Any CPU
54 | {E34EFAB0-FAC0-4444-B981-42C60A2762B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
55 | {E34EFAB0-FAC0-4444-B981-42C60A2762B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
56 | {E34EFAB0-FAC0-4444-B981-42C60A2762B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
57 | {E34EFAB0-FAC0-4444-B981-42C60A2762B4}.Release|Any CPU.Build.0 = Release|Any CPU
58 | {ACE75F82-05C9-4BE6-A528-12F7CF4ABFDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
59 | {ACE75F82-05C9-4BE6-A528-12F7CF4ABFDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
60 | {ACE75F82-05C9-4BE6-A528-12F7CF4ABFDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
61 | {ACE75F82-05C9-4BE6-A528-12F7CF4ABFDE}.Release|Any CPU.Build.0 = Release|Any CPU
62 | {F8C98E17-835F-41F5-AB7E-5CE85AD010E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
63 | {F8C98E17-835F-41F5-AB7E-5CE85AD010E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
64 | {F8C98E17-835F-41F5-AB7E-5CE85AD010E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
65 | {F8C98E17-835F-41F5-AB7E-5CE85AD010E6}.Release|Any CPU.Build.0 = Release|Any CPU
66 | {01665C8E-8D27-4CDB-A965-2756D8030624}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
67 | {01665C8E-8D27-4CDB-A965-2756D8030624}.Debug|Any CPU.Build.0 = Debug|Any CPU
68 | {01665C8E-8D27-4CDB-A965-2756D8030624}.Release|Any CPU.ActiveCfg = Release|Any CPU
69 | {01665C8E-8D27-4CDB-A965-2756D8030624}.Release|Any CPU.Build.0 = Release|Any CPU
70 | {010BAE78-C9AD-4F60-A3AD-7588C724E52A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
71 | {010BAE78-C9AD-4F60-A3AD-7588C724E52A}.Debug|Any CPU.Build.0 = Debug|Any CPU
72 | {010BAE78-C9AD-4F60-A3AD-7588C724E52A}.Release|Any CPU.ActiveCfg = Release|Any CPU
73 | {010BAE78-C9AD-4F60-A3AD-7588C724E52A}.Release|Any CPU.Build.0 = Release|Any CPU
74 | {B24910B3-1629-4C77-81B6-CED348589E29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
75 | {B24910B3-1629-4C77-81B6-CED348589E29}.Debug|Any CPU.Build.0 = Debug|Any CPU
76 | {B24910B3-1629-4C77-81B6-CED348589E29}.Release|Any CPU.ActiveCfg = Release|Any CPU
77 | {B24910B3-1629-4C77-81B6-CED348589E29}.Release|Any CPU.Build.0 = Release|Any CPU
78 | {7BCA415F-B775-428C-8903-26FCDC1AFCB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
79 | {7BCA415F-B775-428C-8903-26FCDC1AFCB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
80 | {7BCA415F-B775-428C-8903-26FCDC1AFCB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
81 | {7BCA415F-B775-428C-8903-26FCDC1AFCB3}.Release|Any CPU.Build.0 = Release|Any CPU
82 | {B90E1B46-8259-4B93-85FB-D7C50F09E582}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
83 | {B90E1B46-8259-4B93-85FB-D7C50F09E582}.Debug|Any CPU.Build.0 = Debug|Any CPU
84 | {B90E1B46-8259-4B93-85FB-D7C50F09E582}.Release|Any CPU.ActiveCfg = Release|Any CPU
85 | {B90E1B46-8259-4B93-85FB-D7C50F09E582}.Release|Any CPU.Build.0 = Release|Any CPU
86 | {60712C16-B67F-485D-A64A-0E446EAD4EFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
87 | {60712C16-B67F-485D-A64A-0E446EAD4EFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
88 | {60712C16-B67F-485D-A64A-0E446EAD4EFB}.Release|Any CPU.ActiveCfg = Release|Any CPU
89 | {60712C16-B67F-485D-A64A-0E446EAD4EFB}.Release|Any CPU.Build.0 = Release|Any CPU
90 | EndGlobalSection
91 | GlobalSection(SolutionProperties) = preSolution
92 | HideSolutionNode = FALSE
93 | EndGlobalSection
94 | GlobalSection(ExtensibilityGlobals) = postSolution
95 | SolutionGuid = {CB278208-B774-42D8-AD9A-33944C91F157}
96 | EndGlobalSection
97 | EndGlobal
98 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | bld/
24 | [Bb]in/
25 | [Oo]bj/
26 | [Ll]og/
27 |
28 | # Visual Studio 2015/2017 cache/options directory
29 | .vs/
30 | # Uncomment if you have tasks that create the project's static files in wwwroot
31 | #wwwroot/
32 |
33 | # Visual Studio 2017 auto generated files
34 | Generated\ Files/
35 |
36 | # MSTest test Results
37 | [Tt]est[Rr]esult*/
38 | [Bb]uild[Ll]og.*
39 |
40 | # NUNIT
41 | *.VisualState.xml
42 | TestResult.xml
43 |
44 | # Build Results of an ATL Project
45 | [Dd]ebugPS/
46 | [Rr]eleasePS/
47 | dlldata.c
48 |
49 | # Benchmark Results
50 | BenchmarkDotNet.Artifacts/
51 |
52 | # .NET Core
53 | project.lock.json
54 | project.fragment.lock.json
55 | artifacts/
56 |
57 | # StyleCop
58 | StyleCopReport.xml
59 |
60 | # Files built by Visual Studio
61 | *_i.c
62 | *_p.c
63 | *_h.h
64 | *.ilk
65 | *.meta
66 | *.obj
67 | *.iobj
68 | *.pch
69 | *.pdb
70 | *.ipdb
71 | *.pgc
72 | *.pgd
73 | *.rsp
74 | *.sbr
75 | *.tlb
76 | *.tli
77 | *.tlh
78 | *.tmp
79 | *.tmp_proj
80 | *_wpftmp.csproj
81 | *.log
82 | *.vspscc
83 | *.vssscc
84 | .builds
85 | *.pidb
86 | *.svclog
87 | *.scc
88 |
89 | # Chutzpah Test files
90 | _Chutzpah*
91 |
92 | # Visual C++ cache files
93 | ipch/
94 | *.aps
95 | *.ncb
96 | *.opendb
97 | *.opensdf
98 | *.sdf
99 | *.cachefile
100 | *.VC.db
101 | *.VC.VC.opendb
102 |
103 | # Visual Studio profiler
104 | *.psess
105 | *.vsp
106 | *.vspx
107 | *.sap
108 |
109 | # Visual Studio Trace Files
110 | *.e2e
111 |
112 | # TFS 2012 Local Workspace
113 | $tf/
114 |
115 | # Guidance Automation Toolkit
116 | *.gpState
117 |
118 | # ReSharper is a .NET coding add-in
119 | _ReSharper*/
120 | *.[Rr]e[Ss]harper
121 | *.DotSettings.user
122 |
123 | # JustCode is a .NET coding add-in
124 | .JustCode
125 |
126 | # TeamCity is a build add-in
127 | _TeamCity*
128 |
129 | # DotCover is a Code Coverage Tool
130 | *.dotCover
131 |
132 | # AxoCover is a Code Coverage Tool
133 | .axoCover/*
134 | !.axoCover/settings.json
135 |
136 | # Visual Studio code coverage results
137 | *.coverage
138 | *.coveragexml
139 |
140 | # NCrunch
141 | _NCrunch_*
142 | .*crunch*.local.xml
143 | nCrunchTemp_*
144 |
145 | # MightyMoose
146 | *.mm.*
147 | AutoTest.Net/
148 |
149 | # Web workbench (sass)
150 | .sass-cache/
151 |
152 | # Installshield output folder
153 | [Ee]xpress/
154 |
155 | # DocProject is a documentation generator add-in
156 | DocProject/buildhelp/
157 | DocProject/Help/*.HxT
158 | DocProject/Help/*.HxC
159 | DocProject/Help/*.hhc
160 | DocProject/Help/*.hhk
161 | DocProject/Help/*.hhp
162 | DocProject/Help/Html2
163 | DocProject/Help/html
164 |
165 | # Click-Once directory
166 | publish/
167 |
168 | # Publish Web Output
169 | *.[Pp]ublish.xml
170 | *.azurePubxml
171 | # Note: Comment the next line if you want to checkin your web deploy settings,
172 | # but database connection strings (with potential passwords) will be unencrypted
173 | *.pubxml
174 | *.publishproj
175 |
176 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
177 | # checkin your Azure Web App publish settings, but sensitive information contained
178 | # in these scripts will be unencrypted
179 | PublishScripts/
180 |
181 | # NuGet Packages
182 | *.nupkg
183 | # The packages folder can be ignored because of Package Restore
184 | **/[Pp]ackages/*
185 | # except build/, which is used as an MSBuild target.
186 | !**/[Pp]ackages/build/
187 | # Uncomment if necessary however generally it will be regenerated when needed
188 | #!**/[Pp]ackages/repositories.config
189 | # NuGet v3's project.json files produces more ignorable files
190 | *.nuget.props
191 | *.nuget.targets
192 |
193 | # Microsoft Azure Build Output
194 | csx/
195 | *.build.csdef
196 |
197 | # Microsoft Azure Emulator
198 | ecf/
199 | rcf/
200 |
201 | # Windows Store app package directories and files
202 | AppPackages/
203 | BundleArtifacts/
204 | Package.StoreAssociation.xml
205 | _pkginfo.txt
206 | *.appx
207 |
208 | # Visual Studio cache files
209 | # files ending in .cache can be ignored
210 | *.[Cc]ache
211 | # but keep track of directories ending in .cache
212 | !*.[Cc]ache/
213 |
214 | # Others
215 | ClientBin/
216 | ~$*
217 | *~
218 | *.dbmdl
219 | *.dbproj.schemaview
220 | *.jfm
221 | *.pfx
222 | *.publishsettings
223 | orleans.codegen.cs
224 |
225 | # Including strong name files can present a security risk
226 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
227 | #*.snk
228 |
229 | # Since there are multiple workflows, uncomment next line to ignore bower_components
230 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
231 | #bower_components/
232 |
233 | # RIA/Silverlight projects
234 | Generated_Code/
235 |
236 | # Backup & report files from converting an old project file
237 | # to a newer Visual Studio version. Backup files are not needed,
238 | # because we have git ;-)
239 | _UpgradeReport_Files/
240 | Backup*/
241 | UpgradeLog*.XML
242 | UpgradeLog*.htm
243 | ServiceFabricBackup/
244 | *.rptproj.bak
245 |
246 | # SQL Server files
247 | *.mdf
248 | *.ldf
249 | *.ndf
250 |
251 | # Business Intelligence projects
252 | *.rdl.data
253 | *.bim.layout
254 | *.bim_*.settings
255 | *.rptproj.rsuser
256 |
257 | # Microsoft Fakes
258 | FakesAssemblies/
259 |
260 | # GhostDoc plugin setting file
261 | *.GhostDoc.xml
262 |
263 | # Node.js Tools for Visual Studio
264 | .ntvs_analysis.dat
265 | node_modules/
266 |
267 | # Visual Studio 6 build log
268 | *.plg
269 |
270 | # Visual Studio 6 workspace options file
271 | *.opt
272 |
273 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
274 | *.vbw
275 |
276 | # Visual Studio LightSwitch build output
277 | **/*.HTMLClient/GeneratedArtifacts
278 | **/*.DesktopClient/GeneratedArtifacts
279 | **/*.DesktopClient/ModelManifest.xml
280 | **/*.Server/GeneratedArtifacts
281 | **/*.Server/ModelManifest.xml
282 | _Pvt_Extensions
283 |
284 | # Paket dependency manager
285 | .paket/paket.exe
286 | paket-files/
287 |
288 | # FAKE - F# Make
289 | .fake/
290 |
291 | # JetBrains Rider
292 | .idea/
293 | *.sln.iml
294 |
295 | # CodeRush personal settings
296 | .cr/personal
297 |
298 | # Python Tools for Visual Studio (PTVS)
299 | __pycache__/
300 | *.pyc
301 |
302 | # Cake - Uncomment if you are using it
303 | # tools/**
304 | # !tools/packages.config
305 |
306 | # Tabs Studio
307 | *.tss
308 |
309 | # Telerik's JustMock configuration file
310 | *.jmconfig
311 |
312 | # BizTalk build output
313 | *.btp.cs
314 | *.btm.cs
315 | *.odx.cs
316 | *.xsd.cs
317 |
318 | # OpenCover UI analysis results
319 | OpenCover/
320 |
321 | # Azure Stream Analytics local run output
322 | ASALocalRun/
323 |
324 | # MSBuild Binary and Structured Log
325 | *.binlog
326 |
327 | # NVidia Nsight GPU debugger configuration file
328 | *.nvuser
329 |
330 | # MFractors (Xamarin productivity tool) working folder
331 | .mfractor/
332 |
333 | # Local History for Visual Studio
334 | .localhistory/
335 |
336 | # Launch Settings for Visual Studio
337 | **/*launchSettings.json
338 |
339 | # Temp files
340 | /tmp/*
341 |
342 | #
343 | templates/dhaf_extensions/libs/*.*
344 |
345 | # macOS files
346 | .DS_Store
347 |
--------------------------------------------------------------------------------