├── src ├── LogWatcher │ ├── wwwroot │ │ ├── js │ │ │ ├── site.min.js │ │ │ └── site.js │ │ ├── favicon.ico │ │ ├── _references.js │ │ └── css │ │ │ ├── site.min.css │ │ │ └── site.css │ ├── .bowerrc │ ├── Views │ │ ├── _ViewStart.cshtml │ │ ├── _ViewImports.cshtml │ │ ├── Shared │ │ │ ├── Error.cshtml │ │ │ └── _Layout.cshtml │ │ └── Home │ │ │ ├── Index.cshtml │ │ │ ├── Stats.cshtml │ │ │ └── Search.cshtml │ ├── bower.json │ ├── appsettings.json │ ├── web.config │ ├── Program.cs │ ├── bundleconfig.json │ ├── Properties │ │ └── launchSettings.json │ ├── LogWatcher.xproj │ ├── Controllers │ │ └── HomeController.cs │ ├── project.json │ ├── Hubs │ │ ├── LogChangeHandler.cs │ │ ├── RethinkDbKeepAlive.cs │ │ ├── BackgroundTaskManager.cs │ │ └── LogHub.cs │ └── Startup.cs ├── RethinkDbLogProvider │ ├── IRethinkDbConnectionFactory.cs │ ├── project.json │ ├── IRethinkDbLoggerService.cs │ ├── RethinkDbOptions.cs │ ├── LogEntry.cs │ ├── LoggerFactoryExtensions.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── RethinkDbLoggerProvider.cs │ ├── RethinkDbLogProvider.xproj │ ├── RethinkDbConnectionFactory.cs │ ├── RethinkDbLogger.cs │ └── RethinkDbLoggerService.cs └── TokenGen │ ├── Models │ ├── Token.cs │ ├── Issuer.cs │ ├── IssuerStatus.cs │ └── TokenStatus.cs │ ├── Controllers │ ├── HealthcheckController.cs │ ├── IssuerController.cs │ ├── RdbController.cs │ └── TokenController.cs │ ├── Store │ ├── IRethinkDbStore.cs │ └── RethinkDbStore.cs │ ├── web.config │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── appsettings.json │ ├── TokenGen.xproj │ ├── project.json │ └── Startup.cs ├── docker ├── nginx │ ├── hosts │ ├── nginx.dockerfile │ ├── nginx-swarm-down.ps1 │ ├── nginx-swarm-up.ps1 │ └── nginx.conf ├── rethinkdb │ ├── rethinkdb-container-up.ps1 │ ├── rethinkdb-container-down.ps1 │ ├── rethinkdb-swarm-down.ps1 │ └── rethinkdb-swarm-up.ps1 ├── swarm-setup.ps1 ├── swarm-cleanup.ps1 └── install-docker-ubuntu-xenial.sh ├── res ├── rdb-1.png ├── rdb-2.png ├── rdb-3.png ├── rdb-4.png └── diagram.png ├── logwatcher-swarm-down.ps1 ├── container-test.ps1 ├── global.json ├── swarm-test.ps1 ├── container-down.ps1 ├── swarm-down.ps1 ├── container-up.ps1 ├── NuGet.Config ├── .dockerignore ├── .gitignore ├── logwatcher-swarm-up.ps1 ├── swarm-loadtest.ps1 ├── swarm-up.ps1 ├── README.md ├── swarm-ver-update.ps1 ├── swarm-ver-deploy.ps1 ├── TokenGen.dockerfile ├── LogWatcher.dockerfile ├── LICENSE ├── .gitattributes └── aspnetcore-dockerswarm.sln /src/LogWatcher/wwwroot/js/site.min.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/LogWatcher/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "wwwroot/lib" 3 | } 4 | -------------------------------------------------------------------------------- /docker/nginx/hosts: -------------------------------------------------------------------------------- 1 | 127.0.0.1 token.api 2 | 127.0.0.1 token.app 3 | -------------------------------------------------------------------------------- /docker/nginx/nginx.dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx 2 | COPY ./nginx.conf /etc/nginx/nginx.conf -------------------------------------------------------------------------------- /src/LogWatcher/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /res/rdb-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanprodan/aspnetcore-dockerswarm/HEAD/res/rdb-1.png -------------------------------------------------------------------------------- /res/rdb-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanprodan/aspnetcore-dockerswarm/HEAD/res/rdb-2.png -------------------------------------------------------------------------------- /res/rdb-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanprodan/aspnetcore-dockerswarm/HEAD/res/rdb-3.png -------------------------------------------------------------------------------- /res/rdb-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanprodan/aspnetcore-dockerswarm/HEAD/res/rdb-4.png -------------------------------------------------------------------------------- /res/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanprodan/aspnetcore-dockerswarm/HEAD/res/diagram.png -------------------------------------------------------------------------------- /src/LogWatcher/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asp.net", 3 | "private": true, 4 | "dependencies": {} 5 | } 6 | -------------------------------------------------------------------------------- /docker/nginx/nginx-swarm-down.ps1: -------------------------------------------------------------------------------- 1 | docker service rm nginx 2 | 3 | Start-Sleep 5 4 | 5 | docker rmi -f nginx-img 6 | -------------------------------------------------------------------------------- /logwatcher-swarm-down.ps1: -------------------------------------------------------------------------------- 1 | docker service rm logwatcher 2 | 3 | Start-Sleep 5 4 | 5 | docker rmi -f logwatcher-img -------------------------------------------------------------------------------- /src/LogWatcher/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using LogWatcher 2 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 3 | -------------------------------------------------------------------------------- /container-test.ps1: -------------------------------------------------------------------------------- 1 | Invoke-RestMethod -Method Get -Headers @{'accept'='application/json'} -Uri http://localhost:5000/api/token -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "projects": [ "src", "test" ], 3 | "sdk": { 4 | "version": "1.0.0-preview2-003121" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/LogWatcher/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanprodan/aspnetcore-dockerswarm/HEAD/src/LogWatcher/wwwroot/favicon.ico -------------------------------------------------------------------------------- /swarm-test.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | for($i=1; $i -le 5; $i++){ 4 | Invoke-RestMethod http://10.0.75.2:5000/api/token 5 | } 6 | -------------------------------------------------------------------------------- /docker/rethinkdb/rethinkdb-container-up.ps1: -------------------------------------------------------------------------------- 1 | docker run --name rethinkdev -d -p 8080:8080 -p 28015:28015 rethinkdb:latest rethinkdb --bind all --server-name DEV -------------------------------------------------------------------------------- /src/LogWatcher/wwwroot/_references.js: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | -------------------------------------------------------------------------------- /docker/rethinkdb/rethinkdb-container-down.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | # stop container if running 4 | docker stop rethinkdev 5 | 6 | # remove container 7 | docker rm rethinkdev 8 | -------------------------------------------------------------------------------- /container-down.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | # stop container if running 4 | docker stop tokengen 5 | 6 | # remove container 7 | docker rm tokengen 8 | 9 | # remove image 10 | docker rmi -f tokengen-img -------------------------------------------------------------------------------- /swarm-down.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | # stop all services by scaling to 0 4 | docker service scale tokengen=0 5 | 6 | # remove serive 7 | docker service rm tokengen 8 | 9 | Start-Sleep 2 10 | 11 | # remove image 12 | docker rmi -f tokengen-img -------------------------------------------------------------------------------- /container-up.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | # build image 4 | if(docker images -q tokengen-img){ 5 | "using existing tokengen image" 6 | }else{ 7 | docker build -t tokengen-img -f TokenGen.dockerfile . 8 | } 9 | 10 | # run container 11 | docker run --name tokengen -d -p 5000:5000 -t tokengen-img -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/LogWatcher/wwwroot/css/site.min.css: -------------------------------------------------------------------------------- 1 | body{padding-top:50px;padding-bottom:20px}.body-content{padding-left:15px;padding-right:15px}input,select,textarea{max-width:280px}.carousel-caption p{font-size:20px;line-height:1.4}.carousel-inner .item img[src$=".svg"]{width:100%}@media screen and (max-width:767px){.carousel-caption{display:none}} -------------------------------------------------------------------------------- /src/RethinkDbLogProvider/IRethinkDbConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | using RethinkDb.Driver.Net; 2 | 3 | namespace RethinkDbLogProvider 4 | { 5 | public interface IRethinkDbConnectionFactory 6 | { 7 | Connection CreateConnection(); 8 | void CloseConnection(); 9 | RethinkDbOptions GetOptions(); 10 | } 11 | } -------------------------------------------------------------------------------- /docker/swarm-setup.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | # initialize swarm 4 | if(!(docker info).contains("Swarm: active")){ 5 | docker swarm init 6 | } 7 | 8 | # create network 9 | $network = "backend-net" 10 | if(!(docker network ls --filter name=$network -q)){ 11 | docker network create --driver overlay $network 12 | } 13 | -------------------------------------------------------------------------------- /docker/swarm-cleanup.ps1: -------------------------------------------------------------------------------- 1 | # Remove all stopped containers 2 | docker rm $(docker ps -a -q -f "status=exited") 3 | 4 | Start-Sleep 5 5 | 6 | # Remove untagged images 7 | docker rmi $(docker images -q -f "dangling=true") 8 | 9 | Start-Sleep 5 10 | 11 | # Remove orphaned volumes 12 | docker volume rm $(docker volume ls -q -f "dangling=true") -------------------------------------------------------------------------------- /docker/nginx/nginx-swarm-up.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | # build image 4 | if(docker images -q nginx-img){ 5 | "using existing nginx image" 6 | }else{ 7 | docker build -t nginx-img -f nginx.dockerfile . 8 | } 9 | 10 | # create and start nginx service 11 | docker service create --publish 80:80 --name nginx --network backend-net nginx-img 12 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | [Oo]bj/ 2 | [Bb]in/ 3 | TestResults/ 4 | .nuget/ 5 | _ReSharper.*/ 6 | packages/ 7 | artifacts/ 8 | PublishProfiles/ 9 | *.user 10 | *.suo 11 | *.cache 12 | *.docstates 13 | _ReSharper.* 14 | nuget.exe 15 | *net45.csproj 16 | *k10.csproj 17 | *.psess 18 | *.vsp 19 | *.pidb 20 | *.userprefs 21 | *DS_Store 22 | *.ncrunchsolution 23 | *.*sdf 24 | *.ipch 25 | .vs/ 26 | project.lock.json 27 | 28 | bower_components/ 29 | node_modules/ -------------------------------------------------------------------------------- /src/RethinkDbLogProvider/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "description": "RethinkDB Logging Provider for .NET Core", 4 | "authors": [ "Stefan Prodan" ], 5 | "dependencies": { 6 | "Microsoft.Extensions.Options": "1.0.0", 7 | "NETStandard.Library": "1.6.0", 8 | "RethinkDb.Driver": "2.3.15" 9 | }, 10 | 11 | "frameworks": { 12 | "netstandard1.6": { 13 | "imports": "dnxcore50" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docker/rethinkdb/rethinkdb-swarm-down.ps1: -------------------------------------------------------------------------------- 1 | # remove RethinkDB cluster 2 | docker service rm rdb-proxy 3 | docker service rm rdb-primary 4 | docker service rm rdb-secondary 5 | 6 | Start-Sleep -s 5 7 | 8 | # remove all containers 9 | docker rm -f $(docker ps -a -q) 10 | 11 | # remove all stopped containers 12 | # docker rm -v $(docker ps -a -q -f status=exited) 13 | 14 | # remove all unused images 15 | # docker rmi $(docker images -q -f dangling=true) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [Oo]bj/ 2 | [Bb]in/ 3 | TestResults/ 4 | .nuget/ 5 | _ReSharper.*/ 6 | packages/ 7 | artifacts/ 8 | PublishProfiles/ 9 | *.user 10 | *.suo 11 | *.cache 12 | *.docstates 13 | _ReSharper.* 14 | nuget.exe 15 | *net45.csproj 16 | *k10.csproj 17 | *.psess 18 | *.vsp 19 | *.pidb 20 | *.userprefs 21 | *DS_Store 22 | *.ncrunchsolution 23 | *.*sdf 24 | *.ipch 25 | .vs/ 26 | project.lock.json 27 | 28 | bower_components/ 29 | node_modules/ 30 | **/wwwroot/lib -------------------------------------------------------------------------------- /src/TokenGen/Models/Token.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace TokenGen 8 | { 9 | public class Token 10 | { 11 | [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)] 12 | public string Id { get; set; } 13 | public DateTime Expires { get; set; } 14 | public string Issuer { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /logwatcher-swarm-up.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | # build image 4 | if(docker images -q logwatcher-img){ 5 | "using existing logwatcher image" 6 | }else{ 7 | docker build -t logwatcher-img -f LogWatcher.dockerfile . 8 | } 9 | 10 | # create and start logwatcher service 11 | docker service create --publish 5005:5005 --mount type=bind,src=/c/users/docker/logwatcher,dst=/root/.aspnet/DataProtection-Keys --name logwatcher --network backend-net logwatcher-img 12 | -------------------------------------------------------------------------------- /src/RethinkDbLogProvider/IRethinkDbLoggerService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace RethinkDbLogProvider 7 | { 8 | public interface IRethinkDbLoggerService 9 | { 10 | void InitializeDatabase(); 11 | void CloseConnection(); 12 | void Log(string categoryName, string logLevel, int eventId, string eventName, string message, Exception exception); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/RethinkDbLogProvider/RethinkDbOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace RethinkDbLogProvider 7 | { 8 | public class RethinkDbOptions 9 | { 10 | public string Application { get; set; } 11 | public string Host { get; set; } 12 | public int Port { get; set; } 13 | public string Database { get; set; } 14 | public int Timeout { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /swarm-loadtest.ps1: -------------------------------------------------------------------------------- 1 | workflow loadtest{ 2 | Param($Iterations) 3 | 4 | $array = 1..$Iterations 5 | 6 | $startTime = get-date 7 | 8 | foreach -Parallel -ThrottleLimit 10 ($i in $array){ 9 | $null = Invoke-RestMethod http://localhost:5000/api/token 10 | } 11 | 12 | "elapsed time " + ((get-date) - $startTime).TotalSeconds + "sec" 13 | 14 | # display load 15 | Invoke-RestMethod http://localhost:5000/api/issuer 16 | } 17 | 18 | # run load test 19 | loadtest 1000 20 | -------------------------------------------------------------------------------- /src/TokenGen/Models/Issuer.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace TokenGen 8 | { 9 | public class Issuer 10 | { 11 | [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)] 12 | public string Id { get; set; } 13 | public string Name { get; set; } 14 | public string Version { get; set; } 15 | public DateTime Timestamp { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/TokenGen/Controllers/HealthcheckController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.PlatformAbstractions; 7 | 8 | namespace TokenGen.Controllers 9 | { 10 | [Route("api/[controller]")] 11 | public class Healthcheck : Controller 12 | { 13 | 14 | [HttpGet] 15 | public dynamic Get() 16 | { 17 | return StatusCode(200); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/TokenGen/Store/IRethinkDbStore.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace TokenGen 4 | { 5 | public interface IRethinkDbStore 6 | { 7 | void InitializeDatabase(); 8 | List GetIssuerStatus(); 9 | List GetTokensCountByIssuer(); 10 | TokenStatus GetTokenStatus(string tokenId); 11 | void InserToken(Token token); 12 | string InsertOrUpdateIssuer(Issuer issuer); 13 | void Reconfigure(int shards, int replicas); 14 | List SearchToken(string querystring); 15 | } 16 | } -------------------------------------------------------------------------------- /src/LogWatcher/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Information", 6 | "System": "Warning", 7 | "Microsoft": "Warning" 8 | } 9 | }, 10 | "RethinkDbDev": { 11 | "Application": "LogWatcher.Debug", 12 | "Host": "localhost", 13 | "Port": 28015, 14 | "Timeout": 10, 15 | "Database": "TokenStore" 16 | }, 17 | "RethinkDbStaging": { 18 | "Application": "LogWatcher.Staging", 19 | "Host": "rdb-proxy", 20 | "Port": 28015, 21 | "Timeout": 10, 22 | "Database": "TokenStore" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/TokenGen/Models/IssuerStatus.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace TokenGen 8 | { 9 | public class IssuerStatus 10 | { 11 | public string Name { get; set; } 12 | [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] 13 | public string Version { get; set; } 14 | [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] 15 | public DateTime? RegisterDate { get; set; } 16 | public long TotalTokensIssued { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/TokenGen/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/LogWatcher/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /swarm-up.ps1: -------------------------------------------------------------------------------- 1 | #$ErrorActionPreference = "Stop" 2 | 3 | # build tokengen image 4 | if(docker images -q tokengen-img){ 5 | "using existing tokengen image" 6 | }else{ 7 | docker build -t tokengen-img -f TokenGen.dockerfile . 8 | } 9 | 10 | # initialize swarm 11 | #docker swarm init 12 | 13 | # create network 14 | #docker network create --driver overlay backend-net 15 | 16 | # create and start tokengen service 17 | docker service create --publish 5000:5000 --name tokengen --network backend-net tokengen-img 18 | 19 | # wait for the database initialization to finish 20 | Start-Sleep -s 10 21 | 22 | # scale x3 23 | docker service scale tokengen=3 24 | -------------------------------------------------------------------------------- /src/LogWatcher/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Hosting; 7 | 8 | namespace LogWatcher 9 | { 10 | public class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | var host = new WebHostBuilder() 15 | .UseKestrel() 16 | .UseContentRoot(Directory.GetCurrentDirectory()) 17 | .UseIISIntegration() 18 | .UseStartup() 19 | .Build(); 20 | 21 | host.Run(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/TokenGen/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.Builder; 8 | 9 | namespace TokenGen 10 | { 11 | public class Program 12 | { 13 | public static void Main(string[] args) 14 | { 15 | var host = new WebHostBuilder() 16 | .UseKestrel() 17 | .UseContentRoot(Directory.GetCurrentDirectory()) 18 | .UseIISIntegration() 19 | .UseStartup() 20 | .Build(); 21 | 22 | host.Run(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/TokenGen/Controllers/IssuerController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.PlatformAbstractions; 7 | 8 | namespace TokenGen.Controllers 9 | { 10 | [Route("api/[controller]")] 11 | public class IssuerController : Controller 12 | { 13 | private IRethinkDbStore _store; 14 | 15 | public IssuerController(IRethinkDbStore store) 16 | { 17 | _store = store; 18 | } 19 | 20 | [HttpGet] 21 | public dynamic Get() 22 | { 23 | return _store.GetTokensCountByIssuer(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/LogWatcher/bundleconfig.json: -------------------------------------------------------------------------------- 1 | // Configure bundling and minification for the project. 2 | // More info at https://go.microsoft.com/fwlink/?LinkId=808241 3 | [ 4 | { 5 | "outputFileName": "wwwroot/css/site.min.css", 6 | // An array of relative input file paths. Globbing patterns supported 7 | "inputFiles": [ 8 | "wwwroot/css/site.css" 9 | ] 10 | }, 11 | { 12 | "outputFileName": "wwwroot/js/site.min.js", 13 | "inputFiles": [ 14 | "wwwroot/js/site.js" 15 | ], 16 | // Optionally specify minification options 17 | "minify": { 18 | "enabled": true, 19 | "renameLocals": true 20 | }, 21 | // Optinally generate .map file 22 | "sourceMap": false 23 | } 24 | ] 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ASP.NET Core orchestration scenarios with Docker 2 | 3 | This repository contains a series of experiments on ASP.NET Core application orchestration with Docker Swarm. 4 | 5 | **Scenarios** 6 | * [Scale an ASP.NET Core stateless microservice with Docker Swarm Mode](https://github.com/stefanprodan/aspnetcore-dockerswarm/wiki/Stateless-microservice-scaling) 7 | * [Build a scalable, fault tolerant system with ASP.NET Core and RethinkDB on Docker Swarm mode](https://github.com/stefanprodan/aspnetcore-dockerswarm/wiki/ASP.NET-Core-RethinkDB) 8 | * [Configure NGINX as a reverse proxy with web sockets support, compression and caching for ASP.NET Core containers on Docker Swarm Mode](https://stefanprodan.com/2016/nginx-reverse-proxy-aspnetcore-docker-swarm/) 9 | -------------------------------------------------------------------------------- /swarm-ver-update.ps1: -------------------------------------------------------------------------------- 1 | 2 | $serviceName = "tokengen" 3 | 4 | # parse project.json and extract app version 5 | $rootPath = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent 6 | $projectPath = $rootPath + "\src\TokenGen\project.json" 7 | $json = Get-Content -Raw -Path $projectPath | ConvertFrom-Json 8 | $version = $json.version.Split("-")[0] 9 | 10 | # tag docker image with app version 11 | $imageName = "$serviceName-img:$version" 12 | 13 | # build image 14 | if(docker images -q $imageName){ 15 | "Image $imageName exists!" 16 | return 17 | }else{ 18 | docker build -t $imageName -f "$rootPath\TokenGen.dockerfile" $rootPath 19 | } 20 | 21 | # apply update 22 | docker service update --image $imageName $serviceName 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/LogWatcher/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:41589/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "LogWatcher": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "launchUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/LogWatcher/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | padding-bottom: 20px; 4 | } 5 | 6 | /* Wrapping element */ 7 | /* Set some basic padding to keep content from hitting the edges */ 8 | .body-content { 9 | padding-left: 15px; 10 | padding-right: 15px; 11 | } 12 | 13 | /* Set widths on the form inputs since otherwise they're 100% wide */ 14 | input, 15 | select, 16 | textarea { 17 | max-width: 1500px; 18 | } 19 | 20 | .table-borderless tbody tr td, .table-borderless tbody tr th, .table-borderless thead tr th { 21 | border: none; 22 | } 23 | 24 | /* Hide/rearrange for smaller screens */ 25 | @media screen and (max-width: 767px) { 26 | /* Hide captions */ 27 | .carousel-caption { 28 | display: none 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/LogWatcher/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Error"; 3 | } 4 | 5 |

Error.

6 |

An error occurred while processing your request.

7 | 8 |

Development Mode

9 |

10 | Swapping to Development environment will display more detailed information about the error that occurred. 11 |

12 |

13 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. 14 |

15 | -------------------------------------------------------------------------------- /swarm-ver-deploy.ps1: -------------------------------------------------------------------------------- 1 | 2 | $serviceName = "tokengen" 3 | 4 | # parse project.json and extract app version 5 | $rootPath = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent 6 | $projectPath = "$rootPath\src\TokenGen\project.json" 7 | $json = Get-Content -Raw -Path $projectPath | ConvertFrom-Json 8 | $version = $json.version.Split("-")[0] 9 | 10 | # tag docker image with app version 11 | $imageName = "$serviceName-img:$version" 12 | 13 | # build image 14 | if(docker images -q $imageName){ 15 | "Image $imageName exists!" 16 | return 17 | }else{ 18 | docker build -t $imageName -f "$rootPath\TokenGen.dockerfile" $rootPath 19 | } 20 | 21 | # create service 22 | docker service create --publish 5000:5000 --name $serviceName --replicas 3 --update-delay 5s $imageName 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/TokenGen/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:8452/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "launchUrl": "api/token", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "TokenGen": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "launchUrl": "http://localhost:5000/api/token", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /TokenGen.dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:latest 2 | 3 | # Set environment variables 4 | ENV ASPNETCORE_URLS="http://*:5000" 5 | ENV ASPNETCORE_ENVIRONMENT="Staging" 6 | 7 | # Copy files to app directory 8 | COPY . /app 9 | 10 | # RethinkDbLogProvider 11 | WORKDIR /app/src/RethinkDbLogProvider 12 | RUN ["dotnet", "restore"] 13 | 14 | # Set working directory 15 | WORKDIR /app/src/TokenGen 16 | 17 | # Restore NuGet packages 18 | RUN ["dotnet", "restore"] 19 | 20 | # Build the app 21 | RUN mkdir release && dotnet publish -c Release -o /app/src/TokenGen/release 22 | 23 | # Open port 24 | EXPOSE 5000/tcp 25 | 26 | HEALTHCHECK CMD curl --fail http://localhost:5000/api/healthcheck || exit 1 27 | 28 | # Set working directory to release 29 | WORKDIR /app/src/TokenGen/release 30 | 31 | # Run the app 32 | ENTRYPOINT ["dotnet", "TokenGen.dll"] -------------------------------------------------------------------------------- /src/RethinkDbLogProvider/LogEntry.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace RethinkDbLogProvider 8 | { 9 | public class LogEntry 10 | { 11 | [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)] 12 | public string Id { get; set; } 13 | public string Application { get; set; } 14 | public string Host { get; set; } 15 | public string Category { get; set; } 16 | public string Level { get; set; } 17 | public int EventId { get; set; } 18 | public string Event { get; set; } 19 | public string Message { get; set; } 20 | public string ExceptionId { get; set; } 21 | public DateTime Timestamp { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/TokenGen/Models/TokenStatus.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace TokenGen 8 | { 9 | public class TokenStatus 10 | { 11 | public string Status { get; set; } 12 | 13 | [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] 14 | public DateTime? Expires { get; set; } 15 | 16 | [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] 17 | public string Issuer { get; set; } 18 | 19 | [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] 20 | public string IssuerVersion { get; set; } 21 | 22 | [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] 23 | public DateTime? IssuerTimestamp { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/LogWatcher/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | function showAlert(alert, message) { 2 | alert.find("p").text(message); 3 | alert.show(); 4 | } 5 | 6 | $(function () { 7 | 8 | var alert = $('#alert'); 9 | 10 | // enable SignalR logging 11 | $.connection.hub.logging = true; 12 | 13 | // alert on slow connection 14 | $.connection.hub.connectionSlow(function () { 15 | showAlert(alert, 'We are currently experiencing difficulties with the SignalR connection'); 16 | }); 17 | 18 | // alert on connection error 19 | $.connection.hub.error(function (error) { 20 | showAlert(alert, 'SignalR error: ' + error); 21 | }); 22 | 23 | // alert on reconnected 24 | $.connection.hub.reconnected(function () { 25 | showAlert(alert, 'Reconnected to SignalR hub, transport ' + $.connection.hub.transport.name); 26 | }); 27 | }); -------------------------------------------------------------------------------- /src/RethinkDbLogProvider/LoggerFactoryExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using System; 3 | 4 | namespace RethinkDbLogProvider 5 | { 6 | public static class LoggerFactoryExtensions 7 | { 8 | public static ILoggerFactory AddRethinkDb(this ILoggerFactory factory, 9 | IRethinkDbLoggerService service, 10 | Func filter = null) 11 | { 12 | factory.AddProvider(new RethinkDbLoggerProvider(filter, service)); 13 | return factory; 14 | } 15 | 16 | public static ILoggerFactory AddRethinkDb(this ILoggerFactory factory, 17 | IRethinkDbLoggerService service, 18 | LogLevel minLevel) 19 | { 20 | return AddRethinkDb( 21 | factory, 22 | service, 23 | (_, logLevel) => logLevel >= minLevel); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/RethinkDbLogProvider/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("RethinkDbLogProvider")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("35e8a145-382e-4d59-936b-2834a9ced1db")] 20 | -------------------------------------------------------------------------------- /src/RethinkDbLogProvider/RethinkDbLoggerProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace RethinkDbLogProvider 8 | { 9 | public class RethinkDbLoggerProvider : ILoggerProvider 10 | { 11 | private readonly Func _filter; 12 | private readonly IRethinkDbLoggerService _service; 13 | 14 | public RethinkDbLoggerProvider(Func filter, IRethinkDbLoggerService service) 15 | { 16 | _service = service; 17 | _filter = filter; 18 | } 19 | 20 | public ILogger CreateLogger(string categoryName) 21 | { 22 | return new RethinkDbLogger(categoryName, _filter, _service); 23 | } 24 | 25 | public void Dispose() 26 | { 27 | _service.CloseConnection(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LogWatcher.dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:latest 2 | 3 | # Set environment variables 4 | ENV ASPNETCORE_URLS="http://*:5005" 5 | ENV ASPNETCORE_ENVIRONMENT="Staging" 6 | 7 | # Copy files to app directory 8 | COPY /src/RethinkDbLogProvider /app/src/RethinkDbLogProvider 9 | COPY /src/LogWatcher /app/src/LogWatcher 10 | COPY NuGet.Config /app/src/LogWatcher/NuGet.Config 11 | 12 | # RethinkDbLogProvider 13 | WORKDIR /app/src/RethinkDbLogProvider 14 | RUN ["dotnet", "restore"] 15 | 16 | # Set working directory 17 | WORKDIR /app/src/LogWatcher 18 | 19 | # Restore NuGet packages 20 | RUN ["dotnet", "restore"] 21 | 22 | # Build the app 23 | RUN mkdir release && dotnet publish -c Release -o /app/src/LogWatcher/release 24 | 25 | # Open port 26 | EXPOSE 5005/tcp 27 | 28 | HEALTHCHECK CMD curl --fail http://localhost:5005/home/healthcheck || exit 1 29 | 30 | # Set working directory to release 31 | WORKDIR /app/src/LogWatcher/release 32 | 33 | # Run the app 34 | ENTRYPOINT ["dotnet", "LogWatcher.dll"] -------------------------------------------------------------------------------- /src/TokenGen/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | }, 10 | "RethinkDbDev": { 11 | "Application": "TokenGen.Debug", 12 | "Host": "localhost", 13 | "Port": 28015, 14 | "Timeout": 10, 15 | "Database": "TokenStore" 16 | }, 17 | "RethinkDbStaging": { 18 | "Application": "TokenGen.Staging", 19 | "Host": "rdb-proxy", 20 | "Port": 28015, 21 | "Timeout": 10, 22 | "Database": "TokenStore" 23 | }, 24 | "IpRateLimiting": { 25 | "EnableEndpointRateLimiting": true, 26 | "IpWhitelist": [ "192.168.0.0/24" ], 27 | "GeneralRules": [ 28 | { 29 | "Endpoint": "*", 30 | "Period": "1s", 31 | "Limit": 10 32 | }, 33 | { 34 | "Endpoint": "*", 35 | "Period": "15m", 36 | "Limit": 1000 37 | } 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/TokenGen/Controllers/RdbController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.PlatformAbstractions; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace TokenGen.Controllers 10 | { 11 | [Route("api/[controller]")] 12 | public class RdbController : Controller 13 | { 14 | private IRethinkDbStore _store; 15 | private ILogger _logger; 16 | 17 | public RdbController(IRethinkDbStore store, ILogger logger) 18 | { 19 | _store = store; 20 | _logger = logger; 21 | } 22 | 23 | [Route("[action]/{shards:int}/{replicas:int}")] 24 | public void Reconfigure(int shards, int replicas) 25 | { 26 | _logger.LogInformation($"Reconfigure database tables: set shards to {shards} and replicas to {replicas}"); 27 | _store.Reconfigure(shards, replicas); 28 | 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Stefan Prodan 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 | -------------------------------------------------------------------------------- /docker/rethinkdb/rethinkdb-swarm-up.ps1: -------------------------------------------------------------------------------- 1 | # create and start rethinkdb primary 2 | docker service create --name rdb-primary --network backend-net rethinkdb:latest rethinkdb --bind all --no-http-admin 3 | 4 | Start-Sleep -s 5 5 | 6 | # create and start rethinkdb secondary 7 | docker service create --name rdb-secondary --network backend-net --replicas 1 rethinkdb:latest rethinkdb --bind all --no-http-admin --join rdb-primary 8 | 9 | Start-Sleep -s 5 10 | 11 | # up 3 nodes (primary + 2 secondary) to enable automatic failover 12 | docker service scale rdb-secondary=2 13 | 14 | Start-Sleep -s 5 15 | 16 | # remove primary 17 | docker service rm rdb-primary 18 | 19 | # recreate primary with --join flag 20 | docker service create --name rdb-primary --network backend-net rethinkdb:latest rethinkdb --bind all --no-http-admin --join rdb-secondary 21 | 22 | Start-Sleep -s 5 23 | 24 | # start 2 rdb-primary instances 25 | docker service scale rdb-primary=2 26 | 27 | Start-Sleep -s 5 28 | 29 | # create and start rethinkdb proxy 30 | docker service create --name rdb-proxy --network backend-net --publish 8080:8080 --publish 28015:28015 rethinkdb:latest rethinkdb proxy --bind all --join rdb-primary -------------------------------------------------------------------------------- /src/TokenGen/TokenGen.xproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 494be48b-eade-4791-b380-9a611eec5ad8 10 | TokenGen 11 | .\obj 12 | .\bin\ 13 | v4.5.2 14 | 15 | 16 | 2.0 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/RethinkDbLogProvider/RethinkDbLogProvider.xproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 10 | 35e8a145-382e-4d59-936b-2834a9ced1db 11 | RethinkDbLogProvider 12 | .\obj 13 | .\bin\ 14 | v4.6 15 | 16 | 17 | 18 | 2.0 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/LogWatcher/LogWatcher.xproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | daa4b110-ba2f-49e5-9d11-909432c6c828 10 | LogWatcher 11 | .\obj 12 | .\bin\ 13 | v4.6 14 | 15 | 16 | 2.0 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /docker/install-docker-ubuntu-xenial.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | sudo true 6 | 7 | sudo apt-get update 8 | sudo apt-get install apt-transport-https ca-certificates 9 | sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D 10 | sudo sh -c 'echo "deb https://apt.dockerproject.org/repo ubuntu-xenial main" > /etc/apt/sources.list.d/docker.list' 11 | sudo apt-get update 12 | sudo apt-get purge lxc-docker 13 | sudo apt-cache policy docker-engine 14 | sudo apt-get install -y linux-image-extra-$(uname -r) 15 | sudo apt-get install -y docker-engine 16 | sudo apt-get install -y python-pip 17 | sudo service docker start 18 | sudo systemctl enable docker 19 | sudo groupadd docker 20 | sudo usermod -aG docker $(whoami) 21 | 22 | echo "Docker engine installed" 23 | 24 | COMPOSE_VERSION=`git ls-remote https://github.com/docker/compose | grep refs/tags | grep -oP "[0-9]+\.[0-9]+\.[0-9]+$" | tail -n 1` 25 | sudo sh -c "curl -L https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose" 26 | sudo chmod +x /usr/local/bin/docker-compose 27 | sudo sh -c "curl -L https://raw.githubusercontent.com/docker/compose/${COMPOSE_VERSION}/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose" 28 | 29 | echo "Docker compose installed" -------------------------------------------------------------------------------- /src/LogWatcher/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace LogWatcher.Controllers 8 | { 9 | public class HomeController : Controller 10 | { 11 | private readonly BackgroundTaskManager _backgroundTaskManager; 12 | public HomeController(BackgroundTaskManager backgroundTaskManager) 13 | { 14 | _backgroundTaskManager = backgroundTaskManager; 15 | backgroundTaskManager.StartKeepAlive(); 16 | backgroundTaskManager.StartLogChangeFeed(); 17 | } 18 | 19 | public IActionResult Index() 20 | { 21 | return View(); 22 | } 23 | 24 | public IActionResult Error() 25 | { 26 | return View(); 27 | } 28 | 29 | public IActionResult Search() 30 | { 31 | return View(); 32 | } 33 | 34 | public IActionResult Stats() 35 | { 36 | return View(); 37 | } 38 | 39 | public IActionResult Healthcheck() 40 | { 41 | if (_backgroundTaskManager.IsConnected()) 42 | { 43 | return new EmptyResult(); 44 | } 45 | 46 | return StatusCode(500); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/TokenGen/Controllers/TokenController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.PlatformAbstractions; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace TokenGen.Controllers 10 | { 11 | [Route("api/[controller]")] 12 | public class TokenController : Controller 13 | { 14 | private IRethinkDbStore _store; 15 | private ILogger _logger; 16 | 17 | public TokenController(IRethinkDbStore store, ILogger logger) 18 | { 19 | _store = store; 20 | _logger = logger; 21 | } 22 | 23 | [HttpGet] 24 | public Token Get() 25 | { 26 | var token = new Token 27 | { 28 | Id = Guid.NewGuid().ToString(), 29 | Expires = DateTime.UtcNow.AddHours(1), 30 | Issuer = Environment.MachineName 31 | }; 32 | 33 | _store.InserToken(token); 34 | 35 | return token; 36 | } 37 | 38 | [HttpGet("{id}")] 39 | public TokenStatus Get(string id) 40 | { 41 | return _store.GetTokenStatus(id); 42 | } 43 | 44 | [Route("[action]/{term}")] 45 | public dynamic Search(string term) 46 | { 47 | return _store.SearchToken(term); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/RethinkDbLogProvider/RethinkDbConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | using RethinkDb.Driver; 2 | using RethinkDb.Driver.Net; 3 | using Microsoft.Extensions.Options; 4 | 5 | namespace RethinkDbLogProvider 6 | { 7 | /// 8 | /// To be used as singleton, RethinkDB connection is thread safe 9 | /// 10 | public class RethinkDbConnectionFactory : IRethinkDbConnectionFactory 11 | { 12 | private static RethinkDB R = RethinkDB.R; 13 | private Connection conn; 14 | private RethinkDbOptions _options; 15 | 16 | public RethinkDbConnectionFactory(IOptions options) 17 | { 18 | _options = options.Value; 19 | } 20 | 21 | public Connection CreateConnection() 22 | { 23 | if (conn == null) 24 | { 25 | conn = R.Connection() 26 | .Hostname(_options.Host) 27 | .Port(_options.Port) 28 | .Timeout(_options.Timeout) 29 | .Connect(); 30 | } 31 | 32 | if(!conn.Open) 33 | { 34 | conn.Reconnect(); 35 | } 36 | 37 | return conn; 38 | } 39 | 40 | public void CloseConnection() 41 | { 42 | if (conn != null && conn.Open) 43 | { 44 | conn.Close(false); 45 | } 46 | } 47 | 48 | public RethinkDbOptions GetOptions() 49 | { 50 | return _options; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/TokenGen/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.1", 3 | "dependencies": { 4 | "Microsoft.NETCore.App": { 5 | "version": "1.0.1", 6 | "type": "platform" 7 | }, 8 | "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", 9 | "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", 10 | "Microsoft.Extensions.Configuration.FileExtensions": "1.0.0", 11 | "Microsoft.Extensions.Configuration.Json": "1.0.0", 12 | "Microsoft.Extensions.Logging": "1.0.0", 13 | "Microsoft.Extensions.Logging.Console": "1.0.0", 14 | "Microsoft.Extensions.Logging.Debug": "1.0.0", 15 | "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0", 16 | "AspNetCoreRateLimit": "1.0.0", 17 | "RethinkDbLogProvider": "1.0.0-*", 18 | "Microsoft.AspNetCore.Mvc": "1.0.1", 19 | "Microsoft.AspNetCore.Server.Kestrel": "1.0.1" 20 | }, 21 | 22 | "tools": { 23 | "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" 24 | }, 25 | 26 | "frameworks": { 27 | "netcoreapp1.0": { 28 | "imports": [ 29 | "dotnet5.6", 30 | "portable-net45+win8" 31 | ] 32 | } 33 | }, 34 | 35 | "buildOptions": { 36 | "emitEntryPoint": true, 37 | "preserveCompilationContext": true 38 | }, 39 | 40 | "runtimeOptions": { 41 | "configProperties": { 42 | "System.GC.Server": true 43 | } 44 | }, 45 | 46 | "publishOptions": { 47 | "include": [ 48 | "wwwroot", 49 | "Views", 50 | "Areas/**/Views", 51 | "appsettings.json", 52 | "web.config" 53 | ] 54 | }, 55 | 56 | "scripts": { 57 | "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/RethinkDbLogProvider/RethinkDbLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace RethinkDbLogProvider 8 | { 9 | public class RethinkDbLogger: ILogger 10 | { 11 | private string _categoryName; 12 | private Func _filter; 13 | private readonly IRethinkDbLoggerService _service; 14 | 15 | public RethinkDbLogger(string categoryName, Func filter, IRethinkDbLoggerService service) 16 | { 17 | _categoryName = categoryName; 18 | _filter = filter; 19 | _service = service; 20 | } 21 | 22 | public IDisposable BeginScope(TState state) 23 | { 24 | return null; 25 | } 26 | 27 | public bool IsEnabled(LogLevel logLevel) 28 | { 29 | return (_filter == null || _filter(_categoryName, logLevel)); 30 | } 31 | 32 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 33 | { 34 | if (!IsEnabled(logLevel)) 35 | { 36 | return; 37 | } 38 | 39 | if (formatter == null) 40 | { 41 | throw new ArgumentNullException(nameof(formatter)); 42 | } 43 | 44 | var message = formatter(state, exception); 45 | 46 | if (string.IsNullOrEmpty(message)) 47 | { 48 | return; 49 | } 50 | 51 | _service.Log(_categoryName, logLevel.ToString(), eventId.Id, eventId.Name, message, exception); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/LogWatcher/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Log feed"; 3 | } 4 | 5 |

Real time log feed

6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
TimeLevelApplicationHostCategoryEventId
25 | 26 | @section scripts { 27 | 28 | 57 | } 58 | -------------------------------------------------------------------------------- /src/LogWatcher/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-beta1", 3 | "dependencies": { 4 | "Microsoft.NETCore.App": { 5 | "version": "1.0.1", 6 | "type": "platform" 7 | }, 8 | "Microsoft.AspNetCore.Diagnostics": "1.0.0", 9 | "Microsoft.AspNetCore.Razor.Tools": { 10 | "version": "1.0.0-preview2-final", 11 | "type": "build" 12 | }, 13 | "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", 14 | "Microsoft.AspNetCore.StaticFiles": "1.0.0", 15 | "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", 16 | "Microsoft.Extensions.Configuration.Json": "1.0.0", 17 | "Microsoft.Extensions.Logging": "1.0.0", 18 | "Microsoft.Extensions.Logging.Console": "1.0.0", 19 | "Microsoft.Extensions.Logging.Debug": "1.0.0", 20 | "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0", 21 | "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0", 22 | "Microsoft.AspNetCore.SignalR.Server": "0.1.0-rtm-21431", 23 | "RethinkDbLogProvider": "1.0.0-*", 24 | "Microsoft.AspNetCore.WebSockets.Server": "0.1.0", 25 | "BundlerMinifier.Core": "2.2.281", 26 | "Microsoft.AspNetCore.Mvc": "1.0.1", 27 | "Microsoft.AspNetCore.Server.Kestrel": "1.0.1" 28 | }, 29 | 30 | "tools": { 31 | "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final", 32 | "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" 33 | }, 34 | 35 | "frameworks": { 36 | "netcoreapp1.0": { 37 | "imports": [ 38 | "dnxcore50" 39 | ] 40 | } 41 | }, 42 | 43 | "buildOptions": { 44 | "emitEntryPoint": true, 45 | "preserveCompilationContext": true 46 | }, 47 | 48 | "runtimeOptions": { 49 | "configProperties": { 50 | "System.GC.Server": true 51 | } 52 | }, 53 | 54 | "publishOptions": { 55 | "include": [ 56 | "wwwroot", 57 | "Views", 58 | "Areas/**/Views", 59 | "appsettings.json", 60 | "web.config" 61 | ] 62 | }, 63 | 64 | "scripts": { 65 | "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/LogWatcher/Hubs/LogChangeHandler.cs: -------------------------------------------------------------------------------- 1 | using RethinkDb.Driver; 2 | using RethinkDbLogProvider; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using Microsoft.Extensions.Logging; 8 | using Microsoft.Extensions.Options; 9 | using RethinkDb.Driver.Net; 10 | 11 | namespace LogWatcher 12 | { 13 | public class LogChangeHandler 14 | { 15 | private static RethinkDB R = RethinkDB.R; 16 | private readonly Microsoft.AspNetCore.SignalR.Infrastructure.IConnectionManager _signalManager; 17 | private readonly IRethinkDbConnectionFactory _rethinkDbFactory; 18 | private RethinkDbOptions _options; 19 | private Connection _conn; 20 | private DateTime _lastLogTimestamp = DateTime.UtcNow.AddSeconds(-1); 21 | private int _retyCount = 0; 22 | 23 | public LogChangeHandler(IRethinkDbConnectionFactory rethinkDbFactory, 24 | IOptions options, 25 | Microsoft.AspNetCore.SignalR.Infrastructure.IConnectionManager signalManager) 26 | { 27 | _rethinkDbFactory = rethinkDbFactory; 28 | _options = options.Value; 29 | _signalManager = signalManager; 30 | } 31 | 32 | public void HandleUpdates() 33 | { 34 | try 35 | { 36 | _conn = _rethinkDbFactory.CreateConnection(); 37 | RunChangefeed(); 38 | } 39 | catch (Exception) 40 | { 41 | _retyCount++; 42 | 43 | //TODO: retry limit 44 | HandleUpdates(); 45 | } 46 | } 47 | 48 | private void RunChangefeed() 49 | { 50 | var hubContext = _signalManager.GetHubContext(); 51 | var feed = R.Db(_options.Database).Table("Logs") 52 | .Between(_lastLogTimestamp, R.Maxval())[new { index = nameof(LogEntry.Timestamp) }] 53 | .Changes().RunChanges(_conn); 54 | 55 | foreach (var log in feed) 56 | { 57 | // push new value to SignalR hub 58 | hubContext.Clients.All.OnLog(log.NewValue); 59 | 60 | // start point on reconnect 61 | _lastLogTimestamp = log.NewValue.Timestamp; 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/LogWatcher/Hubs/RethinkDbKeepAlive.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using RethinkDb.Driver; 3 | using RethinkDbLogProvider; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace LogWatcher 11 | { 12 | public class RethinkDbKeepAlive 13 | { 14 | private static RethinkDB R = RethinkDB.R; 15 | private readonly IRethinkDbConnectionFactory _rethinkDbFactory; 16 | private readonly ILogger _logger; 17 | 18 | public RethinkDbKeepAlive(IRethinkDbConnectionFactory rethinkDbFactory, 19 | ILogger logger) 20 | { 21 | _rethinkDbFactory = rethinkDbFactory; 22 | _logger = logger; 23 | } 24 | 25 | public void Start() 26 | { 27 | bool firstCall = true; 28 | while (true) 29 | { 30 | var conn = _rethinkDbFactory.CreateConnection(); 31 | 32 | try 33 | { 34 | //var srv = conn.Server(); 35 | //_logger.LogDebug(902, $"Connected to RethinkDB server {srv.Name}"); 36 | 37 | var result = R.Db(_rethinkDbFactory.GetOptions().Database).TableList().RunAtom>(conn); 38 | if (firstCall) 39 | { 40 | _logger.LogDebug(902, $"Connected to RethinkDB server {_rethinkDbFactory.GetOptions().Host}"); 41 | } 42 | firstCall = false; 43 | } 44 | catch (Exception ex) 45 | { 46 | _logger.LogDebug(1001, ex, $"RethinkDbKeepAlive error {ex.Message}. Connection open {conn.Open}."); 47 | 48 | conn.Reconnect(); 49 | } 50 | 51 | Thread.Sleep(TimeSpan.FromSeconds(60)); 52 | } 53 | } 54 | 55 | public bool IsConnected() 56 | { 57 | try 58 | { 59 | var conn = _rethinkDbFactory.CreateConnection(); 60 | var result = R.Db(_rethinkDbFactory.GetOptions().Database).TableList().RunAtom>(conn); 61 | 62 | return true; 63 | } 64 | catch (Exception) 65 | { 66 | return false; 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/LogWatcher/Hubs/BackgroundTaskManager.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace LogWatcher 8 | { 9 | public class BackgroundTaskManager 10 | { 11 | private Task _keepAlive = null; 12 | private Task _changeFeed = null; 13 | private RethinkDbKeepAlive _keepAliveService; 14 | private LogChangeHandler _logChangeHandlerService; 15 | private readonly ILogger _logger; 16 | 17 | public BackgroundTaskManager(RethinkDbKeepAlive keepAliveService, LogChangeHandler logChangeHandlerService, ILogger logger) 18 | { 19 | _keepAliveService = keepAliveService; 20 | _logChangeHandlerService = logChangeHandlerService; 21 | _logger = logger; 22 | } 23 | 24 | public void StartKeepAlive() 25 | { 26 | if(_keepAlive == null || _keepAlive.IsCompleted) 27 | { 28 | _logger.LogInformation("KeepAlive Task started"); 29 | 30 | _keepAlive = Task.Factory.StartNew(_keepAliveService.Start, TaskCreationOptions.LongRunning).ContinueWith(task => 31 | { 32 | if (task.Exception != null) 33 | { 34 | var flattened = task.Exception.Flatten(); 35 | _logger.LogError(1001, flattened, $"KeepAlive error: {flattened.Message}"); 36 | } 37 | 38 | _logger.LogCritical("KeepAlive Task exited"); 39 | }); 40 | } 41 | } 42 | 43 | public void StartLogChangeFeed() 44 | { 45 | if (_changeFeed == null || _changeFeed.IsCompleted) 46 | { 47 | _logger.LogInformation("LogChangeFeed Task started"); 48 | 49 | _changeFeed = Task.Factory.StartNew(_logChangeHandlerService.HandleUpdates, TaskCreationOptions.LongRunning).ContinueWith(task => 50 | { 51 | if (task.Exception != null) 52 | { 53 | var flattened = task.Exception.Flatten(); 54 | _logger.LogError(1001, flattened, $"LogChangeFeed error: {flattened.Message}"); 55 | } 56 | 57 | _logger.LogCritical("LogChangeFeed Task exited"); 58 | }); 59 | } 60 | } 61 | 62 | public bool IsConnected() 63 | { 64 | return _keepAliveService.IsConnected(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/LogWatcher/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - LogWatcher 7 | 8 | 9 | 10 | 11 | 12 | 32 |
33 | @RenderBody() 34 |
35 |
36 |

Running on @Environment.MachineName | Client IP @GetClientIp()

37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | @RenderSection("scripts", required: false) 47 | 48 | 49 | 50 | @functions 51 | { 52 | public string GetClientIp() 53 | { 54 | if(Context.Request.Headers.Keys.Contains("X-Forwarded-For", StringComparer.CurrentCultureIgnoreCase)) 55 | { 56 | 57 | return Context.Request.Headers["X-Forwarded-For"].First(); 58 | } 59 | 60 | return Context.Connection.RemoteIpAddress.ToString(); 61 | } 62 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /aspnetcore-dockerswarm.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4DFB6013-EC7C-49D7-BB08-DCF4237D580D}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{458B447A-F71F-40E0-BB29-60CFE3E5A79D}" 9 | ProjectSection(SolutionItems) = preProject 10 | global.json = global.json 11 | EndProjectSection 12 | EndProject 13 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "TokenGen", "src\TokenGen\TokenGen.xproj", "{494BE48B-EADE-4791-B380-9A611EEC5AD8}" 14 | EndProject 15 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RethinkDbLogProvider", "src\RethinkDbLogProvider\RethinkDbLogProvider.xproj", "{35E8A145-382E-4D59-936B-2834A9CED1DB}" 16 | EndProject 17 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LogWatcher", "src\LogWatcher\LogWatcher.xproj", "{DAA4B110-BA2F-49E5-9D11-909432C6C828}" 18 | EndProject 19 | Global 20 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 21 | Debug|Any CPU = Debug|Any CPU 22 | Release|Any CPU = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {494BE48B-EADE-4791-B380-9A611EEC5AD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {494BE48B-EADE-4791-B380-9A611EEC5AD8}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {494BE48B-EADE-4791-B380-9A611EEC5AD8}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {494BE48B-EADE-4791-B380-9A611EEC5AD8}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {35E8A145-382E-4D59-936B-2834A9CED1DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {35E8A145-382E-4D59-936B-2834A9CED1DB}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {35E8A145-382E-4D59-936B-2834A9CED1DB}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {35E8A145-382E-4D59-936B-2834A9CED1DB}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {DAA4B110-BA2F-49E5-9D11-909432C6C828}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {DAA4B110-BA2F-49E5-9D11-909432C6C828}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {DAA4B110-BA2F-49E5-9D11-909432C6C828}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {DAA4B110-BA2F-49E5-9D11-909432C6C828}.Release|Any CPU.Build.0 = Release|Any CPU 37 | EndGlobalSection 38 | GlobalSection(SolutionProperties) = preSolution 39 | HideSolutionNode = FALSE 40 | EndGlobalSection 41 | GlobalSection(NestedProjects) = preSolution 42 | {494BE48B-EADE-4791-B380-9A611EEC5AD8} = {4DFB6013-EC7C-49D7-BB08-DCF4237D580D} 43 | {35E8A145-382E-4D59-936B-2834A9CED1DB} = {4DFB6013-EC7C-49D7-BB08-DCF4237D580D} 44 | {DAA4B110-BA2F-49E5-9D11-909432C6C828} = {4DFB6013-EC7C-49D7-BB08-DCF4237D580D} 45 | EndGlobalSection 46 | EndGlobal 47 | -------------------------------------------------------------------------------- /docker/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes 2; # 2 * Number of CPUs 2 | 3 | # max connections = worker_processes * worker_connections * (K / average $request_time) 4 | events { 5 | worker_connections 2048; 6 | } 7 | 8 | http { 9 | 10 | # enable web sockets protocol 11 | map $http_upgrade $connection_upgrade { 12 | default upgrade; 13 | '' close; 14 | } 15 | 16 | # enable compression 17 | gzip on; 18 | gzip_http_version 1.0; 19 | gzip_proxied any; 20 | gzip_min_length 256; 21 | gzip_types text/plain text/css application/json application/x-javascript 22 | text/xml application/xml application/xml+rss text/javascript 23 | application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon; 24 | 25 | # hide server version 26 | server_tokens off; 27 | 28 | # log only warn | error | crit 29 | error_log /var/log/nginx/error.log warn; 30 | 31 | # disable access log 32 | access_log /dev/null; 33 | 34 | # let upstream handle 404 and 50x errors 35 | proxy_intercept_errors off; 36 | 37 | # api 38 | upstream tokengen { 39 | server tokengen:5000; 40 | } 41 | 42 | server { 43 | listen 80; 44 | server_name token.api; 45 | 46 | location / { 47 | proxy_pass http://tokengen; 48 | proxy_http_version 1.1; 49 | proxy_set_header Upgrade $http_upgrade; 50 | proxy_set_header Connection $connection_upgrade; 51 | proxy_set_header Host $host; 52 | proxy_cache_bypass $http_upgrade; 53 | proxy_set_header X-Real-IP $remote_addr; 54 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 55 | } 56 | } 57 | 58 | # enable disk caching 59 | proxy_cache_path /tmp/nginx levels=1:2 keys_zone=STATIC:10m inactive=60m max_size=1g; 60 | proxy_cache_key "$scheme$request_method$host$request_uri"; 61 | 62 | # app 63 | upstream logwatcher { 64 | server logwatcher:5005; 65 | } 66 | 67 | server { 68 | listen 80; 69 | server_name token.app; 70 | 71 | location / { 72 | proxy_pass http://logwatcher; 73 | proxy_http_version 1.1; 74 | proxy_set_header Upgrade $http_upgrade; 75 | proxy_set_header Connection $connection_upgrade; 76 | proxy_set_header Host $host; 77 | proxy_cache_bypass $http_upgrade; 78 | proxy_set_header X-Real-IP $remote_addr; 79 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 80 | 81 | location ~ \.(svg|jpg|jpeg|gif|png|ico|css|js|woff2)$ { 82 | # browser caching 83 | expires 30d; 84 | add_header Cache-Control "public"; 85 | # server caching 86 | proxy_cache STATIC; 87 | proxy_cache_valid 200 301 302 30m; 88 | proxy_cache_bypass $http_cache_control; 89 | add_header X-Proxy-Cache $upstream_cache_status; 90 | proxy_pass http://logwatcher; 91 | } 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /src/LogWatcher/Views/Home/Stats.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Stats"; 3 | } 4 | 5 |

Statistics

6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
ApplicationTraceDebugInformationWarningErrorCritical
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
HostTraceDebugInformationWarningErrorCritical
41 | 42 | @section scripts { 43 | 44 | 87 | } 88 | -------------------------------------------------------------------------------- /src/LogWatcher/Views/Home/Search.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Search"; 3 | } 4 | 5 |

Search in logs

6 | 7 | 11 | 12 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
TimeLevelApplicationHostCategoryEventId
32 | 33 | 34 | @section scripts { 35 | 89 | } 90 | -------------------------------------------------------------------------------- /src/TokenGen/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Logging; 7 | using RethinkDb.Driver; 8 | using Microsoft.Extensions.PlatformAbstractions; 9 | using RethinkDbLogProvider; 10 | 11 | namespace TokenGen 12 | { 13 | public class Startup 14 | { 15 | private IHostingEnvironment _env; 16 | 17 | public Startup(IHostingEnvironment env) 18 | { 19 | _env = env; 20 | 21 | var builder = new ConfigurationBuilder() 22 | .SetBasePath(env.ContentRootPath) 23 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 24 | .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) 25 | .AddEnvironmentVariables(); 26 | Configuration = builder.Build(); 27 | } 28 | 29 | public IConfigurationRoot Configuration { get; } 30 | 31 | // This method gets called by the runtime. Use this method to add services to the container. 32 | public void ConfigureServices(IServiceCollection services) 33 | { 34 | services.AddOptions(); 35 | services.AddMemoryCache(); 36 | 37 | services.AddMvc(); 38 | 39 | // add RethinkDB logger service 40 | if (_env.IsDevelopment()) 41 | { 42 | services.Configure(Configuration.GetSection("RethinkDbDev")); 43 | } 44 | else 45 | { 46 | services.Configure(Configuration.GetSection("RethinkDbStaging")); 47 | } 48 | services.AddSingleton(); 49 | services.AddSingleton(); 50 | 51 | // add RethinkDB store service 52 | services.AddSingleton(); 53 | } 54 | 55 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 56 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, 57 | IRethinkDbLoggerService rethinkDbLoggerService, 58 | IRethinkDbStore store) 59 | { 60 | loggerFactory.AddConsole(Configuration.GetSection("Logging")); 61 | loggerFactory.AddDebug(); 62 | 63 | // create log database, tables and indexes if not exists 64 | rethinkDbLoggerService.InitializeDatabase(); 65 | 66 | // enable RethinkDb logging 67 | loggerFactory.AddRethinkDb(rethinkDbLoggerService, LogLevel.Information); 68 | 69 | app.UseMvc(); 70 | 71 | // create TokenStore database, tables and indexes if not exists 72 | store.InitializeDatabase(); 73 | 74 | // register issuer 75 | store.InsertOrUpdateIssuer(new Issuer 76 | { 77 | Name = Environment.MachineName, 78 | Version = PlatformServices.Default.Application.ApplicationVersion, 79 | Timestamp = DateTime.UtcNow 80 | }); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/LogWatcher/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Logging; 10 | using RethinkDbLogProvider; 11 | using RethinkDb.Driver; 12 | 13 | namespace LogWatcher 14 | { 15 | public class Startup 16 | { 17 | private IHostingEnvironment _env; 18 | 19 | public Startup(IHostingEnvironment env) 20 | { 21 | _env = env; 22 | 23 | var builder = new ConfigurationBuilder() 24 | .SetBasePath(env.ContentRootPath) 25 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 26 | .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) 27 | .AddEnvironmentVariables(); 28 | Configuration = builder.Build(); 29 | } 30 | 31 | public IConfigurationRoot Configuration { get; } 32 | 33 | // This method gets called by the runtime. Use this method to add services to the container. 34 | public void ConfigureServices(IServiceCollection services) 35 | { 36 | // Add framework services. 37 | services.AddMvc(); 38 | 39 | services.AddSignalR(options => 40 | { 41 | options.Hubs.EnableDetailedErrors = true; 42 | }); 43 | 44 | if (_env.IsDevelopment()) 45 | { 46 | services.Configure(Configuration.GetSection("RethinkDbDev")); 47 | } 48 | else 49 | { 50 | services.Configure(Configuration.GetSection("RethinkDbStaging")); 51 | } 52 | services.AddSingleton(); 53 | services.AddSingleton(); 54 | 55 | // register keep alive and change feed services 56 | services.AddSingleton(); 57 | services.AddSingleton(); 58 | services.AddSingleton(); 59 | } 60 | 61 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 62 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, 63 | ILoggerFactory loggerFactory, 64 | IRethinkDbLoggerService rethinkDbLoggerService, 65 | BackgroundTaskManager backgroundTaskManager) 66 | { 67 | loggerFactory.AddConsole(Configuration.GetSection("Logging")); 68 | loggerFactory.AddDebug(); 69 | 70 | if (env.IsDevelopment()) 71 | { 72 | // enable RethinkDb driver logging 73 | loggerFactory.EnableRethinkDbLogging(); 74 | } 75 | 76 | // enable RethinkDb log provider 77 | loggerFactory.AddRethinkDb(rethinkDbLoggerService, LogLevel.Warning); 78 | 79 | if (env.IsDevelopment()) 80 | { 81 | app.UseDeveloperExceptionPage(); 82 | app.UseBrowserLink(); 83 | } 84 | else 85 | { 86 | app.UseExceptionHandler("/Home/Error"); 87 | } 88 | 89 | app.UseStaticFiles(); 90 | 91 | app.UseMvc(routes => 92 | { 93 | routes.MapRoute( 94 | name: "default", 95 | template: "{controller=Home}/{action=Index}/{id?}"); 96 | }); 97 | 98 | // try to avoid connection being dropped due to inactivity 99 | backgroundTaskManager.StartKeepAlive(); 100 | 101 | // run log watcher on a background thread 102 | backgroundTaskManager.StartLogChangeFeed(); 103 | 104 | app.UseWebSockets(); 105 | app.UseSignalR(); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/RethinkDbLogProvider/RethinkDbLoggerService.cs: -------------------------------------------------------------------------------- 1 | using RethinkDb.Driver; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace RethinkDbLogProvider 8 | { 9 | public class RethinkDbLoggerService: IRethinkDbLoggerService 10 | { 11 | private static RethinkDB R = RethinkDB.R; 12 | private static IRethinkDbConnectionFactory _connectionFactory; 13 | private string _dbName; 14 | 15 | public string LogTable { get; set; } = "Logs"; 16 | public string ExceptionTable { get; set; } = "Exceptions"; 17 | 18 | public RethinkDbLoggerService(IRethinkDbConnectionFactory connectionFactory) 19 | { 20 | _connectionFactory = connectionFactory; 21 | _dbName = _connectionFactory.GetOptions().Database; 22 | } 23 | 24 | public void Log(string categoryName, string logLevel, int eventId, string eventName, string message, Exception exception) 25 | { 26 | var conn = _connectionFactory.CreateConnection(); 27 | 28 | try 29 | { 30 | InsertLog(conn, categoryName, logLevel, eventId, eventName, message, exception); 31 | } 32 | catch (Exception) 33 | { 34 | if(!conn.Open) 35 | { 36 | conn.Reconnect(); 37 | } 38 | else 39 | { 40 | conn.Close(); 41 | conn.Reconnect(); 42 | } 43 | 44 | InsertLog(conn, categoryName, logLevel, eventId, eventName, message, exception); 45 | } 46 | } 47 | 48 | private void InsertLog(RethinkDb.Driver.Net.Connection conn, string categoryName, string logLevel, int eventId, string eventName, string message, Exception exception) 49 | { 50 | string exceptionId = null; 51 | 52 | if (exception != null) 53 | { 54 | // insert exception 55 | var result = R.Db(_dbName).Table(ExceptionTable) 56 | .Insert(exception) 57 | .RunResult(conn); 58 | 59 | exceptionId = result.GeneratedKeys.First().ToString(); 60 | } 61 | 62 | var logEntry = new LogEntry 63 | { 64 | Application = _connectionFactory.GetOptions().Application, 65 | Category = categoryName, 66 | Event = eventName, 67 | EventId = eventId, 68 | ExceptionId = exceptionId, 69 | Host = Environment.MachineName, 70 | Level = logLevel, 71 | Message = message, 72 | Timestamp = DateTime.UtcNow 73 | }; 74 | 75 | R.Db(_dbName).Table(LogTable) 76 | .Insert(logEntry) 77 | .RunResult(conn); 78 | } 79 | 80 | public void InitializeDatabase() 81 | { 82 | // database 83 | CreateDb(_dbName); 84 | 85 | // tables 86 | CreateTable(_dbName, LogTable); 87 | CreateTable(_dbName, ExceptionTable); 88 | 89 | // indexes 90 | CreateIndex(_dbName, LogTable, nameof(LogEntry.EventId)); 91 | CreateIndex(_dbName, LogTable, nameof(LogEntry.Application)); 92 | CreateIndex(_dbName, LogTable, nameof(LogEntry.Timestamp)); 93 | CreateIndex(_dbName, LogTable, nameof(LogEntry.Host)); 94 | } 95 | 96 | protected void CreateDb(string dbName) 97 | { 98 | var conn = _connectionFactory.CreateConnection(); 99 | var exists = R.DbList().Contains(db => db == dbName).Run(conn); 100 | 101 | if (!exists) 102 | { 103 | R.DbCreate(dbName).Run(conn); 104 | R.Db(dbName).Wait_().Run(conn); 105 | } 106 | } 107 | 108 | protected void CreateTable(string dbName, string tableName) 109 | { 110 | var conn = _connectionFactory.CreateConnection(); 111 | var exists = R.Db(dbName).TableList().Contains(t => t == tableName).Run(conn); 112 | if (!exists) 113 | { 114 | R.Db(dbName).TableCreate(tableName).Run(conn); 115 | R.Db(dbName).Table(tableName).Wait_().Run(conn); 116 | } 117 | } 118 | 119 | protected void CreateIndex(string dbName, string tableName, string indexName) 120 | { 121 | var conn = _connectionFactory.CreateConnection(); 122 | var exists = R.Db(dbName).Table(tableName).IndexList().Contains(t => t == indexName).Run(conn); 123 | if (!exists) 124 | { 125 | R.Db(dbName).Table(tableName).IndexCreate(indexName).Run(conn); 126 | R.Db(dbName).Table(tableName).IndexWait(indexName).Run(conn); 127 | } 128 | } 129 | 130 | public void CloseConnection() 131 | { 132 | _connectionFactory.CloseConnection(); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/LogWatcher/Hubs/LogHub.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.SignalR; 2 | using Microsoft.Extensions.Logging; 3 | using Newtonsoft.Json.Linq; 4 | using RethinkDb.Driver; 5 | using RethinkDb.Driver.Net; 6 | using RethinkDbLogProvider; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | 12 | namespace LogWatcher 13 | { 14 | public class LogHub : Hub 15 | { 16 | private static RethinkDB R = RethinkDB.R; 17 | private readonly IRethinkDbConnectionFactory _rethinkDbFactory; 18 | private readonly ILogger _logger; 19 | 20 | public LogHub(IRethinkDbConnectionFactory rethinkDbFactory, 21 | ILogger logger) 22 | { 23 | _rethinkDbFactory = rethinkDbFactory; 24 | _logger = logger; 25 | } 26 | 27 | public dynamic Load(int limit) 28 | { 29 | var conn = _rethinkDbFactory.CreateConnection(); 30 | var logs = R.Db(_rethinkDbFactory.GetOptions().Database) 31 | .Table("Logs") 32 | .OrderBy()[new { index = R.Desc(nameof(LogEntry.Timestamp)) }] 33 | .Limit(limit) 34 | .RunCursor(conn); 35 | 36 | var result = logs.ToList(); 37 | logs.Close(); 38 | 39 | return result; 40 | } 41 | 42 | public dynamic Search(string query, int limit) 43 | { 44 | var conn = _rethinkDbFactory.CreateConnection(); 45 | 46 | try 47 | { 48 | return DoSearch(query, limit, conn); 49 | } 50 | catch (Exception ex) 51 | { 52 | _logger.LogError(1001, ex, $"DoSearch error {ex.Message}. Connection open {conn.Open}."); 53 | 54 | conn.Close(); 55 | conn.Reconnect(); 56 | 57 | return DoSearch(query, limit, conn); 58 | } 59 | } 60 | 61 | private List DoSearch(string query, int limit, RethinkDb.Driver.Net.Connection conn) 62 | { 63 | var date = DateTime.UtcNow.AddDays(-1); 64 | var logs = R.Db(_rethinkDbFactory.GetOptions().Database) 65 | .Table("Logs") 66 | .OrderBy()[new { index = R.Desc(nameof(LogEntry.Timestamp)) }] 67 | .Filter(t => t.CoerceTo("string").Match($"(?i){query}")) 68 | .Limit(limit) 69 | .RunCursor(conn); 70 | 71 | var result = logs.ToList(); 72 | logs.Close(); 73 | 74 | return result; 75 | } 76 | 77 | public dynamic HostLogStats() 78 | { 79 | var conn = _rethinkDbFactory.CreateConnection(); 80 | var result = R.Db(_rethinkDbFactory.GetOptions().Database) 81 | .Table("Logs") 82 | .Group("Host", "Level") 83 | .Count() 84 | .RunGrouping, long>(conn); 85 | 86 | var stats = new List(); 87 | 88 | foreach (var item in result.ToList()) 89 | { 90 | var host = item.Key[0]; 91 | var level = item.Key[1]; 92 | 93 | if(!stats.Exists(s => s.Name == host)) 94 | { 95 | stats.Add(new LevelStats { Name = host }); 96 | } 97 | 98 | var entry = stats.Find(s => s.Name == host); 99 | 100 | switch (level) 101 | { 102 | case "Trace": 103 | entry.Trace = item.Items.First(); 104 | break; 105 | case "Debug": 106 | entry.Debug = item.Items.First(); 107 | break; 108 | case "Information": 109 | entry.Information = item.Items.First(); 110 | break; 111 | case "Warning": 112 | entry.Warning = item.Items.First(); 113 | break; 114 | case "Error": 115 | entry.Error = item.Items.First(); 116 | break; 117 | case "Critical": 118 | entry.Critical = item.Items.First(); 119 | break; 120 | default: 121 | break; 122 | } 123 | } 124 | 125 | return stats; 126 | } 127 | 128 | public dynamic AppLogStats() 129 | { 130 | var conn = _rethinkDbFactory.CreateConnection(); 131 | var result = R.Db(_rethinkDbFactory.GetOptions().Database) 132 | .Table("Logs") 133 | .Group("Application", "Level") 134 | .Count() 135 | .RunGrouping, long>(conn); 136 | 137 | var stats = new List(); 138 | 139 | foreach (var item in result.ToList()) 140 | { 141 | var host = item.Key[0]; 142 | var level = item.Key[1]; 143 | 144 | if (!stats.Exists(s => s.Name == host)) 145 | { 146 | stats.Add(new LevelStats { Name = host }); 147 | } 148 | 149 | var entry = stats.Find(s => s.Name == host); 150 | 151 | switch (level) 152 | { 153 | case "Trace": 154 | entry.Trace = item.Items.First(); 155 | break; 156 | case "Debug": 157 | entry.Debug = item.Items.First(); 158 | break; 159 | case "Information": 160 | entry.Information = item.Items.First(); 161 | break; 162 | case "Warning": 163 | entry.Warning = item.Items.First(); 164 | break; 165 | case "Error": 166 | entry.Error = item.Items.First(); 167 | break; 168 | case "Critical": 169 | entry.Critical = item.Items.First(); 170 | break; 171 | default: 172 | break; 173 | } 174 | } 175 | 176 | return stats; 177 | } 178 | } 179 | 180 | public class LevelStats 181 | { 182 | public string Name { get; set; } 183 | public long Trace { get; set; } 184 | public long Debug { get; set; } 185 | public long Information { get; set; } 186 | public long Warning { get; set; } 187 | public long Error { get; set; } 188 | public long Critical { get; set; } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/TokenGen/Store/RethinkDbStore.cs: -------------------------------------------------------------------------------- 1 | using RethinkDb.Driver; 2 | using RethinkDb.Driver.Ast; 3 | using RethinkDb.Driver.Model; 4 | using RethinkDb.Driver.Net; 5 | using RethinkDb.Driver.Net.Clustering; 6 | using RethinkDbLogProvider; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | 12 | namespace TokenGen 13 | { 14 | public class RethinkDbStore : IRethinkDbStore 15 | { 16 | private static IRethinkDbConnectionFactory _connectionFactory; 17 | private static RethinkDB R = RethinkDB.R; 18 | private string _dbName; 19 | 20 | public RethinkDbStore(IRethinkDbConnectionFactory connectionFactory) 21 | { 22 | _connectionFactory = connectionFactory; 23 | _dbName = connectionFactory.GetOptions().Database; 24 | } 25 | 26 | public string InsertOrUpdateIssuer(Issuer issuer) 27 | { 28 | var conn = _connectionFactory.CreateConnection(); 29 | Cursor all = R.Db(_dbName).Table(nameof(Issuer)) 30 | .GetAll(issuer.Name)[new { index = nameof(Issuer.Name) }] 31 | .Run(conn); 32 | 33 | var issuers = all.ToList(); 34 | 35 | if (issuers.Count > 0) 36 | { 37 | // update 38 | R.Db(_dbName).Table(nameof(Issuer)).Get(issuers.First().Id).Update(issuer).RunResult(conn); 39 | 40 | return issuers.First().Id; 41 | } 42 | else 43 | { 44 | // insert 45 | var result = R.Db(_dbName).Table(nameof(Issuer)) 46 | .Insert(issuer) 47 | .RunResult(conn); 48 | 49 | return result.GeneratedKeys.First().ToString(); 50 | } 51 | } 52 | 53 | public List SearchToken(string querystring) 54 | { 55 | var conn = _connectionFactory.CreateConnection(); 56 | 57 | // case-insensitive search in all Token fields 58 | Cursor all = R.Db(_dbName).Table(nameof(Token)).Filter(t => t.CoerceTo("string").Match($"(?i){querystring}")).RunCursor(conn); 59 | return all.OrderByDescending(f => f.Expires).ToList(); 60 | } 61 | 62 | public List GetTokensCountByIssuer() 63 | { 64 | var conn = _connectionFactory.CreateConnection(); 65 | 66 | var list = R.Db(_dbName).Table(nameof(Token)) 67 | .Group()[new { index = nameof(Token.Issuer) }] 68 | .Count() 69 | .RunGrouping(conn); 70 | 71 | var issuerReport = list.Select(f => new IssuerStatus 72 | { 73 | Name = f.Key, 74 | TotalTokensIssued = f.Items.First() 75 | }).ToList(); 76 | 77 | return issuerReport; 78 | } 79 | 80 | public List GetIssuerStatus() 81 | { 82 | var conn = _connectionFactory.CreateConnection(); 83 | Cursor all = R.Db(_dbName).Table(nameof(Issuer)).RunCursor(conn); 84 | var list = all.OrderByDescending(f => f.Timestamp) 85 | .Select(f => new IssuerStatus 86 | { 87 | Name = f.Name, 88 | RegisterDate = f.Timestamp, 89 | Version = f.Version, 90 | TotalTokensIssued = R.Db(_dbName).Table(nameof(Token)).GetAll(f.Name)[new { index = nameof(Token.Issuer) }].Count().Run(conn) 91 | }).ToList(); 92 | 93 | return list; 94 | } 95 | 96 | public void InserToken(Token token) 97 | { 98 | var conn = _connectionFactory.CreateConnection(); 99 | var result = R.Db(_dbName).Table(nameof(Token)) 100 | .Insert(token) 101 | .RunResult(conn); 102 | } 103 | 104 | public TokenStatus GetTokenStatus(string tokenId) 105 | { 106 | var conn = _connectionFactory.CreateConnection(); 107 | var tokenStatus = new TokenStatus(); 108 | 109 | Token token = R.Db(_dbName).Table(nameof(Token)).Get(tokenId).Run(conn); 110 | 111 | if(token == null) 112 | { 113 | tokenStatus.Status = "Not found"; 114 | return tokenStatus; 115 | } 116 | else 117 | { 118 | tokenStatus.Issuer = token.Issuer; 119 | tokenStatus.Expires = token.Expires; 120 | tokenStatus.Status = DateTime.UtcNow > token.Expires ? "Expired" : "Valid"; 121 | } 122 | 123 | Cursor all = R.Db(_dbName).Table(nameof(Issuer)) 124 | .GetAll(token.Issuer)[new { index = nameof(Issuer.Name) }] 125 | .Run(conn); 126 | 127 | var issuers = all.ToList(); 128 | 129 | if (issuers.Count > 0) 130 | { 131 | tokenStatus.IssuerVersion = issuers.First().Version; 132 | tokenStatus.IssuerTimestamp = issuers.First().Timestamp; 133 | } 134 | 135 | return tokenStatus; 136 | } 137 | 138 | public void InitializeDatabase() 139 | { 140 | // database 141 | CreateDb(_dbName); 142 | 143 | // tables 144 | CreateTable(_dbName, nameof(Token)); 145 | CreateTable(_dbName, nameof(Issuer)); 146 | 147 | // indexes 148 | CreateIndex(_dbName, nameof(Token), nameof(Token.Issuer)); 149 | CreateIndex(_dbName, nameof(Issuer), nameof(Issuer.Name)); 150 | 151 | // configure shards and replicas for each table 152 | //R.Db(dbName).Table(nameof(Token)).Reconfigure().OptArg("shards", 1).OptArg("replicas", 2).Run(conn); 153 | //R.Db(dbName).Table(nameof(Issuer)).Reconfigure().OptArg("shards", 1).OptArg("replicas", 2).Run(conn); 154 | } 155 | 156 | protected void CreateDb(string dbName) 157 | { 158 | var conn = _connectionFactory.CreateConnection(); 159 | var exists = R.DbList().Contains(db => db == dbName).Run(conn); 160 | 161 | if (!exists) 162 | { 163 | R.DbCreate(dbName).Run(conn); 164 | R.Db(dbName).Wait_().Run(conn); 165 | } 166 | } 167 | 168 | protected void DropDb(string dbName) 169 | { 170 | var conn = _connectionFactory.CreateConnection(); 171 | var exists = R.DbList().Contains(db => db == dbName).Run(conn); 172 | 173 | if (exists) 174 | { 175 | R.DbDrop(dbName).Run(conn); 176 | } 177 | } 178 | 179 | protected void CreateTable(string dbName, string tableName) 180 | { 181 | var conn = _connectionFactory.CreateConnection(); 182 | var exists = R.Db(dbName).TableList().Contains(t => t == tableName).Run(conn); 183 | if (!exists) 184 | { 185 | R.Db(dbName).TableCreate(tableName).Run(conn); 186 | R.Db(dbName).Table(tableName).Wait_().Run(conn); 187 | } 188 | } 189 | 190 | protected void CreateIndex(string dbName, string tableName, string indexName) 191 | { 192 | var conn = _connectionFactory.CreateConnection(); 193 | var exists = R.Db(dbName).Table(tableName).IndexList().Contains(t => t == indexName).Run(conn); 194 | if (!exists) 195 | { 196 | R.Db(dbName).Table(tableName).IndexCreate(indexName).Run(conn); 197 | R.Db(dbName).Table(tableName).IndexWait(indexName).Run(conn); 198 | } 199 | } 200 | 201 | protected void DropTable(string dbName, string tableName) 202 | { 203 | var conn = _connectionFactory.CreateConnection(); 204 | var exists = R.Db(dbName).TableList().Contains(t => t == tableName).Run(conn); 205 | if (exists) 206 | { 207 | R.Db(dbName).TableDrop(tableName).Run(conn); 208 | } 209 | } 210 | 211 | public void Reconfigure(int shards, int replicas) 212 | { 213 | var conn = _connectionFactory.CreateConnection(); 214 | var tables = R.Db(_dbName).TableList().Run(conn); 215 | foreach (string table in tables) 216 | { 217 | R.Db(_dbName).Table(table).Reconfigure().OptArg("shards", shards).OptArg("replicas", replicas).Run(conn); 218 | R.Db(_dbName).Table(table).Wait_().Run(conn); 219 | } 220 | } 221 | 222 | } 223 | } 224 | --------------------------------------------------------------------------------