diff --git a/.gitignore b/.gitignore index 61b1c211..1a7759c2 100644 --- a/.gitignore +++ b/.gitignore @@ -235,3 +235,4 @@ _Pvt_Extensions # FAKE - F# Make .fake/ +tools/ diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 00000000..05e9ac41 --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1,4 @@ +mode: ContinuousDelivery +branches: {} +ignore: + sha: [] diff --git a/Ocelot.sln b/Ocelot.sln index 0165beb0..6c0006b0 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -9,18 +9,24 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .gitignore = .gitignore appveyor.yml = appveyor.yml - build-and-run-tests.bat = build-and-run-tests.bat - build.bat = build.bat + build-and-release-unstable.ps1 = build-and-release-unstable.ps1 + build-and-run-tests.ps1 = build-and-run-tests.ps1 + build.cake = build.cake + build.ps1 = build.ps1 + build.readme.md = build.readme.md configuration-explanation.txt = configuration-explanation.txt + configuration.yaml = configuration.yaml + GitVersion.yml = GitVersion.yml global.json = global.json LICENSE.md = LICENSE.md Ocelot.nuspec = Ocelot.nuspec - push-to-nuget.bat = push-to-nuget.bat README.md = README.md - run-acceptance-tests.bat = run-acceptance-tests.bat - run-benchmarks.bat = run-benchmarks.bat - run-tests.bat = run-tests.bat - run-unit-tests.bat = run-unit-tests.bat + release.ps1 = release.ps1 + ReleaseNotes.md = ReleaseNotes.md + run-acceptance-tests.ps1 = run-acceptance-tests.ps1 + run-benchmarks.ps1 = run-benchmarks.ps1 + run-unit-tests.ps1 = run-unit-tests.ps1 + version.ps1 = version.ps1 EndProjectSection EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot", "src\Ocelot\Ocelot.xproj", "{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}" diff --git a/ReleaseNotes.md b/ReleaseNotes.md new file mode 100644 index 00000000..a647c6c3 --- /dev/null +++ b/ReleaseNotes.md @@ -0,0 +1 @@ +No issues closed since last release \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 83110722..6e8b16f6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,10 +3,6 @@ configuration: - Release platform: Any CPU build_script: -- build.bat -test_script: -- run-tests.bat -after_test: -- push-to-nuget.bat %appveyor_build_version% %nugetApiKey% +- build.ps1 cache: - '%USERPROFILE%\.nuget\packages' \ No newline at end of file diff --git a/build-and-release-unstable.ps1 b/build-and-release-unstable.ps1 new file mode 100644 index 00000000..9a29f95f --- /dev/null +++ b/build-and-release-unstable.ps1 @@ -0,0 +1 @@ +./build.ps1 -target build-full \ No newline at end of file diff --git a/build-and-run-tests.bat b/build-and-run-tests.bat deleted file mode 100755 index 764682b6..00000000 --- a/build-and-run-tests.bat +++ /dev/null @@ -1,2 +0,0 @@ -./run-tests.bat -./build.bat \ No newline at end of file diff --git a/build-and-run-tests.ps1 b/build-and-run-tests.ps1 new file mode 100644 index 00000000..f82502e5 --- /dev/null +++ b/build-and-run-tests.ps1 @@ -0,0 +1 @@ +./build.ps1 -target RunTests \ No newline at end of file diff --git a/build.bat b/build.bat deleted file mode 100755 index 656515d4..00000000 --- a/build.bat +++ /dev/null @@ -1,8 +0,0 @@ -echo ------------------------- - -echo Building Ocelot -dotnet restore src/Ocelot -dotnet build src/Ocelot -c Release - - - diff --git a/build.cake b/build.cake new file mode 100644 index 00000000..1d798d74 --- /dev/null +++ b/build.cake @@ -0,0 +1,347 @@ +#tool "nuget:?package=GitVersion.CommandLine" +#tool "nuget:?package=OpenCover" +#tool "nuget:?package=ReportGenerator" +#tool "nuget:?package=GitReleaseNotes" +#addin "nuget:?package=Cake.DoInDirectory" +#addin "nuget:?package=Cake.Json" + +// compile +var compileConfig = Argument("configuration", "Release"); +var projectJson = "./src/Ocelot/project.json"; + +// build artifacts +var artifactsDir = Directory("artifacts"); + +// unit testing +var artifactsForUnitTestsDir = artifactsDir + Directory("UnitTests"); +var unitTestAssemblies = @"./test/Ocelot.UnitTests"; + +// acceptance testing +var artifactsForAcceptanceTestsDir = artifactsDir + Directory("AcceptanceTests"); +var acceptanceTestAssemblies = @"./test/Ocelot.AcceptanceTests"; + +// benchmark testing +var artifactsForBenchmarkTestsDir = artifactsDir + Directory("BenchmarkTests"); +var benchmarkTestAssemblies = @"./test/Ocelot.Benchmarks"; + +// packaging +var packagesDir = artifactsDir + Directory("Packages"); +var releaseNotesFile = packagesDir + File("releasenotes.md"); +var artifactsFile = packagesDir + File("artifacts.txt"); + +// unstable releases +var nugetFeedUnstableKey = EnvironmentVariable("nuget-apikey-unstable"); +var nugetFeedUnstableUploadUrl = "https://www.nuget.org/api/v2/package"; +var nugetFeedUnstableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package"; + +// stable releases +var tagsUrl = "https://api.github.com/repos/tompallister/ocelot/releases/tags/"; +var nugetFeedStableKey = EnvironmentVariable("nuget-apikey-stable"); +var nugetFeedStableUploadUrl = "https://www.nuget.org/api/v2/package"; +var nugetFeedStableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package"; + +// internal build variables - don't change these. +var releaseTag = ""; +var buildVersion = committedVersion; +var committedVersion = "0.0.0-dev"; + +var target = Argument("target", "Default"); + +Information("target is " +target); +Information("Build configuration is " + compileConfig); + +Task("Default") + .IsDependentOn("Build"); + +Task("Build") + .IsDependentOn("RunTests") + .IsDependentOn("CreatePackages"); + +Task("BuildAndReleaseUnstable") + .IsDependentOn("Build") + .IsDependentOn("ReleasePackagesToUnstableFeed"); + +Task("Clean") + .Does(() => + { + if (DirectoryExists(artifactsDir)) + { + DeleteDirectory(artifactsDir, recursive:true); + } + CreateDirectory(artifactsDir); + }); + +Task("Version") + .Does(() => + { + var nugetVersion = GetNuGetVersionForCommit(); + Information("SemVer version number: " + nugetVersion); + + if (AppVeyor.IsRunningOnAppVeyor) + { + Information("Persisting version number..."); + PersistVersion(nugetVersion); + buildVersion = nugetVersion; + } + else + { + Information("We are not running on build server, so we won't persist the version number."); + } + }); + +Task("Restore") + .IsDependentOn("Clean") + .IsDependentOn("Version") + .Does(() => + { + DotNetCoreRestore("./src"); + DotNetCoreRestore("./test"); + }); + +Task("RunUnitTests") + .IsDependentOn("Restore") + .Does(() => + { + var buildSettings = new DotNetCoreTestSettings + { + Configuration = compileConfig, + }; + + EnsureDirectoryExists(artifactsForUnitTestsDir); + DotNetCoreTest(unitTestAssemblies, buildSettings); + }); + +Task("RunAcceptanceTests") + .IsDependentOn("Restore") + .Does(() => + { + var buildSettings = new DotNetCoreTestSettings + { + Configuration = "Debug", //acceptance test config is hard-coded for debug + }; + + EnsureDirectoryExists(artifactsForAcceptanceTestsDir); + + DoInDirectory("test/Ocelot.AcceptanceTests", () => + { + DotNetCoreTest(".", buildSettings); + }); + + }); + +Task("RunBenchmarkTests") + .IsDependentOn("Restore") + .Does(() => + { + var buildSettings = new DotNetCoreRunSettings + { + Configuration = compileConfig, + }; + + EnsureDirectoryExists(artifactsForBenchmarkTestsDir); + + DoInDirectory(benchmarkTestAssemblies, () => + { + DotNetCoreRun(".", "", buildSettings); + }); + }); + +Task("RunTests") + .IsDependentOn("RunUnitTests") + .IsDependentOn("RunAcceptanceTests") + .Does(() => + { + }); + +Task("CreatePackages") + .Does(() => + { + EnsureDirectoryExists(packagesDir); + + GenerateReleaseNotes(); + + var settings = new DotNetCorePackSettings + { + OutputDirectory = packagesDir, + NoBuild = true + }; + + DotNetCorePack(projectJson, settings); + + System.IO.File.WriteAllLines(artifactsFile, new[]{ + "nuget:Ocelot." + buildVersion + ".nupkg", + "nugetSymbols:Ocelot." + buildVersion + ".symbols.nupkg", + "releaseNotes:releasenotes.md" + }); + + if (AppVeyor.IsRunningOnAppVeyor) + { + var path = packagesDir.ToString() + @"/**/*"; + + foreach (var file in GetFiles(path)) + { + AppVeyor.UploadArtifact(file.FullPath); + } + } + }); + +Task("ReleasePackagesToUnstableFeed") + .IsDependentOn("CreatePackages") + .Does(() => + { + PublishPackages(nugetFeedUnstableKey, nugetFeedUnstableUploadUrl, nugetFeedUnstableSymbolsUploadUrl); + }); + +Task("EnsureStableReleaseRequirements") + .Does(() => + { + if (!AppVeyor.IsRunningOnAppVeyor) + { + throw new Exception("Stable release should happen via appveyor"); + } + + var isTag = + AppVeyor.Environment.Repository.Tag.IsTag && + !string.IsNullOrWhiteSpace(AppVeyor.Environment.Repository.Tag.Name); + + if (!isTag) + { + throw new Exception("Stable release should happen from a published GitHub release"); + } + }); + +Task("UpdateVersionInfo") + .IsDependentOn("EnsureStableReleaseRequirements") + .Does(() => + { + releaseTag = AppVeyor.Environment.Repository.Tag.Name; + AppVeyor.UpdateBuildVersion(releaseTag); + }); + +Task("DownloadGitHubReleaseArtifacts") + .IsDependentOn("UpdateVersionInfo") + .Does(() => + { + EnsureDirectoryExists(packagesDir); + + var releaseUrl = tagsUrl + releaseTag; + var assets_url = ParseJson(GetResource(releaseUrl)) + .GetValue("assets_url") + .Value(); + + foreach(var asset in DeserializeJson(GetResource(assets_url))) + { + var file = packagesDir + File(asset.Value("name")); + Information("Downloading " + file); + DownloadFile(asset.Value("browser_download_url"), file); + } + }); + +Task("ReleasePackagesToStableFeed") + .IsDependentOn("DownloadGitHubReleaseArtifacts") + .Does(() => + { + PublishPackages(nugetFeedStableKey, nugetFeedStableUploadUrl, nugetFeedStableSymbolsUploadUrl); + }); + +Task("Release") + .IsDependentOn("ReleasePackagesToStableFeed"); + +RunTarget(target); + +/// Gets nuique nuget version for this commit +private string GetNuGetVersionForCommit() +{ + GitVersion(new GitVersionSettings{ + UpdateAssemblyInfo = false, + OutputType = GitVersionOutput.BuildServer + }); + + var versionInfo = GitVersion(new GitVersionSettings{ OutputType = GitVersionOutput.Json }); + return versionInfo.NuGetVersion; +} + +/// Updates project version in all of our projects +private void PersistVersion(string version) +{ + Information(string.Format("We'll search all project.json files for {0} and replace with {1}...", committedVersion, version)); + + var projectJsonFiles = GetFiles("./**/project.json"); + + foreach(var projectJsonFile in projectJsonFiles) + { + var file = projectJsonFile.ToString(); + + Information(string.Format("Updating {0}...", file)); + + var updatedProjectJson = System.IO.File.ReadAllText(file) + .Replace(committedVersion, version); + + System.IO.File.WriteAllText(file, updatedProjectJson); + } +} + +/// generates release notes based on issues closed in GitHub since the last release +private void GenerateReleaseNotes() +{ + Information("Generating release notes at " + releaseNotesFile); + + var releaseNotesExitCode = StartProcess( + @"tools/GitReleaseNotes/tools/gitreleasenotes.exe", + new ProcessSettings { Arguments = ". /o " + releaseNotesFile }); + + if (string.IsNullOrEmpty(System.IO.File.ReadAllText(releaseNotesFile))) + { + System.IO.File.WriteAllText(releaseNotesFile, "No issues closed since last release"); + } + + if (releaseNotesExitCode != 0) + { + throw new Exception("Failed to generate release notes"); + } +} + +/// Publishes code and symbols packages to nuget feed, based on contents of artifacts file +private void PublishPackages(string feedApiKey, string codeFeedUrl, string symbolFeedUrl) +{ + var artifacts = System.IO.File + .ReadAllLines(artifactsFile) + .Select(l => l.Split(':')) + .ToDictionary(v => v[0], v => v[1]); + + var codePackage = packagesDir + File(artifacts["nuget"]); + var symbolsPackage = packagesDir + File(artifacts["nugetSymbols"]); + + NuGetPush( + codePackage, + new NuGetPushSettings { + ApiKey = feedApiKey, + Source = codeFeedUrl + }); + + NuGetPush( + symbolsPackage, + new NuGetPushSettings { + ApiKey = feedApiKey, + Source = symbolFeedUrl + }); + +} + +/// gets the resource from the specified url +private string GetResource(string url) +{ + Information("Getting resource from " + url); + + var assetsRequest = System.Net.WebRequest.CreateHttp(url); + assetsRequest.Method = "GET"; + assetsRequest.Accept = "application/vnd.github.v3+json"; + assetsRequest.UserAgent = "BuildScript"; + + using (var assetsResponse = assetsRequest.GetResponse()) + { + var assetsStream = assetsResponse.GetResponseStream(); + var assetsReader = new StreamReader(assetsStream); + return assetsReader.ReadToEnd(); + } +} \ No newline at end of file diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 00000000..44de5793 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,189 @@ +########################################################################## +# This is the Cake bootstrapper script for PowerShell. +# This file was downloaded from https://github.com/cake-build/resources +# Feel free to change this file to fit your needs. +########################################################################## + +<# + +.SYNOPSIS +This is a Powershell script to bootstrap a Cake build. + +.DESCRIPTION +This Powershell script will download NuGet if missing, restore NuGet tools (including Cake) +and execute your Cake build script with the parameters you provide. + +.PARAMETER Script +The build script to execute. +.PARAMETER Target +The build script target to run. +.PARAMETER Configuration +The build configuration to use. +.PARAMETER Verbosity +Specifies the amount of information to be displayed. +.PARAMETER Experimental +Tells Cake to use the latest Roslyn release. +.PARAMETER WhatIf +Performs a dry run of the build script. +No tasks will be executed. +.PARAMETER Mono +Tells Cake to use the Mono scripting engine. +.PARAMETER SkipToolPackageRestore +Skips restoring of packages. +.PARAMETER ScriptArgs +Remaining arguments are added here. + +.LINK +http://cakebuild.net + +#> + +[CmdletBinding()] +Param( + [string]$Script = "build.cake", + [string]$Target = "Default", + [ValidateSet("Release", "Debug")] + [string]$Configuration = "Release", + [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] + [string]$Verbosity = "Verbose", + [switch]$Experimental, + [Alias("DryRun","Noop")] + [switch]$WhatIf, + [switch]$Mono, + [switch]$SkipToolPackageRestore, + [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] + [string[]]$ScriptArgs +) + +[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null +function MD5HashFile([string] $filePath) +{ + if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf)) + { + return $null + } + + [System.IO.Stream] $file = $null; + [System.Security.Cryptography.MD5] $md5 = $null; + try + { + $md5 = [System.Security.Cryptography.MD5]::Create() + $file = [System.IO.File]::OpenRead($filePath) + return [System.BitConverter]::ToString($md5.ComputeHash($file)) + } + finally + { + if ($file -ne $null) + { + $file.Dispose() + } + } +} + +Write-Host "Preparing to run build script..." + +if(!$PSScriptRoot){ + $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent +} + +$TOOLS_DIR = Join-Path $PSScriptRoot "tools" +$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" +$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" +$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" +$PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" +$PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum" + +# Should we use mono? +$UseMono = ""; +if($Mono.IsPresent) { + Write-Verbose -Message "Using the Mono based scripting engine." + $UseMono = "-mono" +} + +# Should we use the new Roslyn? +$UseExperimental = ""; +if($Experimental.IsPresent -and !($Mono.IsPresent)) { + Write-Verbose -Message "Using experimental version of Roslyn." + $UseExperimental = "-experimental" +} + +# Is this a dry run? +$UseDryRun = ""; +if($WhatIf.IsPresent) { + $UseDryRun = "-dryrun" +} + +# Make sure tools folder exists +if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { + Write-Verbose -Message "Creating tools directory..." + New-Item -Path $TOOLS_DIR -Type directory | out-null +} + +# Make sure that packages.config exist. +if (!(Test-Path $PACKAGES_CONFIG)) { + Write-Verbose -Message "Downloading packages.config..." + try { (New-Object System.Net.WebClient).DownloadFile("http://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch { + Throw "Could not download packages.config." + } +} + +# Try find NuGet.exe in path if not exists +if (!(Test-Path $NUGET_EXE)) { + Write-Verbose -Message "Trying to find nuget.exe in PATH..." + $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_) } + $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1 + if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) { + Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)." + $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName + } +} + +# Try download NuGet.exe if not exists +if (!(Test-Path $NUGET_EXE)) { + Write-Verbose -Message "Downloading NuGet.exe..." + try { + (New-Object System.Net.WebClient).DownloadFile($NUGET_URL, $NUGET_EXE) + } catch { + Throw "Could not download NuGet.exe." + } +} + +# Save nuget.exe path to environment to be available to child processed +$ENV:NUGET_EXE = $NUGET_EXE + +# Restore tools from NuGet? +if(-Not $SkipToolPackageRestore.IsPresent) { + Push-Location + Set-Location $TOOLS_DIR + + # Check for changes in packages.config and remove installed tools if true. + [string] $md5Hash = MD5HashFile($PACKAGES_CONFIG) + if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or + ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) { + Write-Verbose -Message "Missing or changed package.config hash..." + Remove-Item * -Recurse -Exclude packages.config,nuget.exe + } + + Write-Verbose -Message "Restoring tools from NuGet..." + $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`"" + + if ($LASTEXITCODE -ne 0) { + Throw "An error occured while restoring NuGet tools." + } + else + { + $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII" + } + Write-Verbose -Message ($NuGetOutput | out-string) + Pop-Location +} + +# Make sure that Cake has been installed. +if (!(Test-Path $CAKE_EXE)) { + Throw "Could not find Cake.exe at $CAKE_EXE" +} + +# Start Cake +Write-Host "Running build script..." +Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental $ScriptArgs" +exit $LASTEXITCODE \ No newline at end of file diff --git a/build.readme.md b/build.readme.md new file mode 100644 index 00000000..fccff4d5 --- /dev/null +++ b/build.readme.md @@ -0,0 +1,22 @@ +#1. Overview + +This document summarises the build and release process for the project. The build scripts are written using [Cake](http://cakebuild.net/), and are defined in `./build.cake`. The scripts have been designed to be run by either developers locally or by a build server (currently [AppVeyor](https://www.appveyor.com/)), with minimal logic defined in the build server itself. + +#2. Building + * You'll generally want to run the `./build.ps1` script. This will compile, run unit and acceptance tests and build the output packages locally. Output will got to the `./artifacts` directory. + * You can view the current commit's [SemVer](http://semver.org/) build information by running `./version.ps1`. + * The other `./*.ps1` scripts perform subsets of the build process, if you don't want to run the full build. + * The release process works best with GitFlow branching; this allows us to publish every development commit to an unstable feed with a unique SemVer version, and then choose when to release to a stable feed. + +#3. Release process +This section defines the release process for the maintainers of the project. + * Merge pull requests to the `release` branch. + * Every commit pushed to the Origin repo will kick off the [ocelot-build](https://ci.appveyor.com/project/binarymash/ocelot) project in AppVeyor. This performs the same tasks as the command line build, and in addition pushes the packages to the unstable nuget feed. + * When you're ready for a release, create a release branch. You'll probably want to update the committed `./ReleaseNotes.md` based on the contents of the equivalent file in the `./artifacts` directory. + * When the `release` branch has built successfully in Appveyor, select the build and then Deploy to the `GitHub Release` environment. This will create a new release in GitHub. + * In Github, navigate to the [release](https://github.com/binarymash/Ocelot/releases). Modify the release name and tag as desired. + * When you're ready, publish the release. This will tag the commit with the specified release number. + * The [ocelot-release](https://ci.appveyor.com/project/binarymash/ocelot-wtaj9) project will detect the newly created tag and kick off the release process. This will download the artifacts from GitHub, and publish the packages to the stable nuget feed. + * When you have a final stable release build, merge the `release` branch into `master` and `develop`. Deploy the master branch to github and following the full release process as described above. Don't forget to uncheck the "This is a pre-release" checkbox in GitHub before publishing. + * Note - because the release builds are initiated by tagging a commit, if for some reason a release build fails in AppVeyor you'll need to delete the tag from the repo and republish the release in GitHub. + diff --git a/push-to-nuget.bat b/push-to-nuget.bat deleted file mode 100644 index 73d3bf7e..00000000 --- a/push-to-nuget.bat +++ /dev/null @@ -1,11 +0,0 @@ -echo ------------------------- - -echo Packing Ocelot Version %1 -nuget pack .\Ocelot.nuspec -version %1 - -echo Publishing Ocelot -nuget push Ocelot.%1.nupkg -ApiKey %2 -Source https://www.nuget.org/api/v2/package - - - - diff --git a/release.ps1 b/release.ps1 new file mode 100644 index 00000000..6cf4c66b --- /dev/null +++ b/release.ps1 @@ -0,0 +1 @@ +./build.ps1 -target Release \ No newline at end of file diff --git a/run-acceptance-tests.bat b/run-acceptance-tests.bat deleted file mode 100755 index ba8a3489..00000000 --- a/run-acceptance-tests.bat +++ /dev/null @@ -1,8 +0,0 @@ -echo Running Ocelot.AcceptanceTests -cd test/Ocelot.AcceptanceTests/ -dotnet restore -dotnet test -cd ../../ - -echo Restoring Ocelot.ManualTest -dotnet restore test/Ocelot.ManualTest/ \ No newline at end of file diff --git a/run-acceptance-tests.ps1 b/run-acceptance-tests.ps1 new file mode 100644 index 00000000..480e1d4c --- /dev/null +++ b/run-acceptance-tests.ps1 @@ -0,0 +1 @@ +./build -target RunAcceptanceTests \ No newline at end of file diff --git a/run-benchmarks.bat b/run-benchmarks.bat deleted file mode 100644 index 1376f17a..00000000 --- a/run-benchmarks.bat +++ /dev/null @@ -1,15 +0,0 @@ -echo ------------------------- - -echo Running Ocelot.Benchmarks - -cd test/Ocelot.Benchmarks - -dotnet restore - -dotnet run - -cd ../../ - - - - diff --git a/run-benchmarks.ps1 b/run-benchmarks.ps1 new file mode 100644 index 00000000..e05490fd --- /dev/null +++ b/run-benchmarks.ps1 @@ -0,0 +1 @@ +./build.ps1 -target RunBenchmarkTests \ No newline at end of file diff --git a/run-tests.bat b/run-tests.bat deleted file mode 100755 index 39532229..00000000 --- a/run-tests.bat +++ /dev/null @@ -1,2 +0,0 @@ -./run-unit-tests.bat -./run-acceptance-tests.bat \ No newline at end of file diff --git a/run-unit-tests.bat b/run-unit-tests.bat deleted file mode 100755 index 9ad6a4f2..00000000 --- a/run-unit-tests.bat +++ /dev/null @@ -1,8 +0,0 @@ -echo ------------------------- - -echo Restoring Ocelot -dotnet restore src/Ocelot - -echo Running Ocelot.UnitTests -dotnet restore test/Ocelot.UnitTests/ -dotnet test test/Ocelot.UnitTests/ diff --git a/run-unit-tests.ps1 b/run-unit-tests.ps1 new file mode 100644 index 00000000..0e6a91bd --- /dev/null +++ b/run-unit-tests.ps1 @@ -0,0 +1 @@ +./build.ps1 -target RunUnitTests \ No newline at end of file diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index ecc0b67f..9579330c 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -201,13 +201,12 @@ namespace Ocelot.Configuration.Builder public ReRoute Build() { - Func downstreamHostFunc = () => new HostAndPort(_downstreamHost, _dsPort); - return new ReRoute(new DownstreamPathTemplate(_downstreamPathTemplate), _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _serviceName, - _useServiceDiscovery, _serviceDiscoveryAddress, _serviceDiscoveryProvider, downstreamHostFunc, _downstreamScheme, _loadBalancer); + _useServiceDiscovery, _serviceDiscoveryAddress, _serviceDiscoveryProvider, _downstreamScheme, _loadBalancer, + _downstreamHost, _dsPort); } } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 24c4db72..827dcbad 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Options; using Ocelot.Configuration.File; using Ocelot.Configuration.Parser; using Ocelot.Configuration.Validator; +using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Responses; using Ocelot.ServiceDiscovery; using Ocelot.Utilities; @@ -97,31 +98,6 @@ namespace Ocelot.Configuration.Creator && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Address) && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider); - - //can we do the logic in this func to get the host and port from the load balancer? - //lBfactory.GetLbForThisDownstreamTemplate - //use it in the func to get the next host and port? - //how do we release it? cant callback, could access the lb and release later? - - //ideal world we would get the host and port, then make the request using it, then release the connection to the lb - - Func downstreamHostAndPortFunc = () => { - - //service provider factory takes the reRoute - //can return no service provider (just use ocelot config) - //can return consol service provider - //returns a service provider - //we call get on the service provider - //could reutrn services from consol or just configuration.json - //this returns a list of services and we take the first one - var hostAndPort = new HostAndPort(reRoute.DownstreamHost.Trim('/'), reRoute.DownstreamPort); - var services = new List(); - var serviceProvider = new NoServiceProvider(services); - var service = serviceProvider.Get(); - var firstHostAndPort = service[0].HostAndPort; - return firstHostAndPort; - }; - if (isAuthenticated) { var authOptionsForRoute = new AuthenticationOptions(reRoute.AuthenticationOptions.Provider, @@ -139,8 +115,8 @@ namespace Ocelot.Configuration.Creator reRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds), reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme, - reRoute.LoadBalancer); + globalConfiguration?.ServiceDiscoveryProvider?.Address, reRoute.DownstreamScheme, + reRoute.LoadBalancer, reRoute.DownstreamHost, reRoute.DownstreamPort); } return new ReRoute(new DownstreamPathTemplate(reRoute.DownstreamPathTemplate), reRoute.UpstreamTemplate, @@ -149,8 +125,8 @@ namespace Ocelot.Configuration.Creator reRoute.RouteClaimsRequirement, isAuthorised, new List(), requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds), reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme, - reRoute.LoadBalancer); + globalConfiguration?.ServiceDiscoveryProvider?.Address, reRoute.DownstreamScheme, + reRoute.LoadBalancer, reRoute.DownstreamHost, reRoute.DownstreamPort); } private string BuildUpstreamTemplate(FileReRoute reRoute) diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 9faab4ab..5cbaaebb 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -10,10 +10,12 @@ namespace Ocelot.Configuration bool isAuthenticated, AuthenticationOptions authenticationOptions, List configurationHeaderExtractorProperties, List claimsToClaims, Dictionary routeClaimsRequirement, bool isAuthorised, List claimsToQueries, string requestIdKey, bool isCached, CacheOptions fileCacheOptions, string serviceName, bool useServiceDiscovery, - string serviceDiscoveryProvider, string serviceDiscoveryAddress, Func downstreamHostAndPort, - string downstreamScheme, string loadBalancer) + string serviceDiscoveryProvider, string serviceDiscoveryAddress, + string downstreamScheme, string loadBalancer, string downstreamHost, int downstreamPort) { LoadBalancer = loadBalancer; + DownstreamHost = downstreamHost; + DownstreamPort = downstreamPort; DownstreamPathTemplate = downstreamPathTemplate; UpstreamTemplate = upstreamTemplate; UpstreamHttpMethod = upstreamHttpMethod; @@ -35,7 +37,6 @@ namespace Ocelot.Configuration UseServiceDiscovery = useServiceDiscovery; ServiceDiscoveryProvider = serviceDiscoveryProvider; ServiceDiscoveryAddress = serviceDiscoveryAddress; - DownstreamHostAndPort = downstreamHostAndPort; DownstreamScheme = downstreamScheme; } public DownstreamPathTemplate DownstreamPathTemplate { get; private set; } @@ -56,8 +57,9 @@ namespace Ocelot.Configuration public bool UseServiceDiscovery { get; private set;} public string ServiceDiscoveryProvider { get; private set;} public string ServiceDiscoveryAddress { get; private set;} - public Func DownstreamHostAndPort {get;private set;} public string DownstreamScheme {get;private set;} public string LoadBalancer {get;private set;} + public string DownstreamHost { get; private set; } + public int DownstreamPort { get; private set; } } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index 957acb8e..bca20466 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -6,6 +6,7 @@ using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; +using Ocelot.Values; namespace Ocelot.DownstreamUrlCreator.Middleware { @@ -45,15 +46,10 @@ namespace Ocelot.DownstreamUrlCreator.Middleware } var dsScheme = DownstreamRoute.ReRoute.DownstreamScheme; - - //here we could have a lb factory that takes stuff or we could just get the load balancer from the reRoute? - //returns the lb for this request - //lease the next address from the lb - - //this could return the load balancer which you call next on, that gives you the host and port then you can call release in a try catch - //and if the call works? - var dsHostAndPort = DownstreamRoute.ReRoute.DownstreamHostAndPort(); + //todo - get this out of scoped data repo? + var dsHostAndPort = new HostAndPort(DownstreamRoute.ReRoute.DownstreamHost, + DownstreamRoute.ReRoute.DownstreamPort); var dsUrl = _urlBuilder.Build(dsPath.Data.Value, dsScheme, dsHostAndPort); diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs new file mode 100644 index 00000000..fe4e5baf --- /dev/null +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs @@ -0,0 +1,11 @@ +using Ocelot.Responses; +using Ocelot.Values; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public interface ILoadBalancer + { + Response Lease(); + Response Release(HostAndPort hostAndPort); + } +} \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs new file mode 100644 index 00000000..930a5211 --- /dev/null +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs @@ -0,0 +1,7 @@ +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public interface ILoadBalancerFactory + { + ILoadBalancer Get(string serviceName, string loadBalancer); + } +} \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/Lease.cs b/src/Ocelot/LoadBalancer/LoadBalancers/Lease.cs new file mode 100644 index 00000000..bd2e1b88 --- /dev/null +++ b/src/Ocelot/LoadBalancer/LoadBalancers/Lease.cs @@ -0,0 +1,15 @@ +using Ocelot.Values; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public class Lease + { + public Lease(HostAndPort hostAndPort, int connections) + { + HostAndPort = hostAndPort; + Connections = connections; + } + public HostAndPort HostAndPort { get; private set; } + public int Connections { get; private set; } + } +} \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs new file mode 100644 index 00000000..4799ab12 --- /dev/null +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Ocelot.Errors; +using Ocelot.Responses; +using Ocelot.Values; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public class LeastConnectionLoadBalancer : ILoadBalancer + { + private Func> _services; + private List _leases; + private string _serviceName; + + public LeastConnectionLoadBalancer(Func> services, string serviceName) + { + _services = services; + _serviceName = serviceName; + _leases = new List(); + } + + public Response Lease() + { + var services = _services(); + + if (services == null) + { + return new ErrorResponse(new List() { new ServicesAreNullError($"services were null for {_serviceName}") }); + } + + if (!services.Any()) + { + return new ErrorResponse(new List() { new ServicesAreEmptyError($"services were empty for {_serviceName}") }); + } + + //todo - maybe this should be moved somewhere else...? Maybe on a repeater on seperate thread? loop every second and update or something? + UpdateServices(services); + + var leaseWithLeastConnections = GetLeaseWithLeastConnections(); + + _leases.Remove(leaseWithLeastConnections); + + leaseWithLeastConnections = AddConnection(leaseWithLeastConnections); + + _leases.Add(leaseWithLeastConnections); + + return new OkResponse(new HostAndPort(leaseWithLeastConnections.HostAndPort.DownstreamHost, leaseWithLeastConnections.HostAndPort.DownstreamPort)); + } + + public Response Release(HostAndPort hostAndPort) + { + var matchingLease = _leases.FirstOrDefault(l => l.HostAndPort.DownstreamHost == hostAndPort.DownstreamHost + && l.HostAndPort.DownstreamPort == hostAndPort.DownstreamPort); + + if (matchingLease != null) + { + var replacementLease = new Lease(hostAndPort, matchingLease.Connections - 1); + + _leases.Remove(matchingLease); + + _leases.Add(replacementLease); + } + + return new OkResponse(); + } + + private Lease AddConnection(Lease lease) + { + return new Lease(lease.HostAndPort, lease.Connections + 1); + } + + private Lease GetLeaseWithLeastConnections() + { + //now get the service with the least connections? + Lease leaseWithLeastConnections = null; + + for (var i = 0; i < _leases.Count; i++) + { + if (i == 0) + { + leaseWithLeastConnections = _leases[i]; + } + else + { + if (_leases[i].Connections < leaseWithLeastConnections.Connections) + { + leaseWithLeastConnections = _leases[i]; + } + } + } + + return leaseWithLeastConnections; + } + + private Response UpdateServices(List services) + { + if (_leases.Count > 0) + { + var leasesToRemove = new List(); + + foreach (var lease in _leases) + { + var match = services.FirstOrDefault(s => s.HostAndPort.DownstreamHost == lease.HostAndPort.DownstreamHost + && s.HostAndPort.DownstreamPort == lease.HostAndPort.DownstreamPort); + + if (match == null) + { + leasesToRemove.Add(lease); + } + } + + foreach (var lease in leasesToRemove) + { + _leases.Remove(lease); + } + + foreach (var service in services) + { + var exists = _leases.FirstOrDefault(l => l.HostAndPort.ToString() == service.HostAndPort.ToString()); + + if (exists == null) + { + _leases.Add(new Lease(service.HostAndPort, 0)); + } + } + } + else + { + foreach (var service in services) + { + _leases.Add(new Lease(service.HostAndPort, 0)); + } + } + + return new OkResponse(); + } + } +} diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs new file mode 100644 index 00000000..d5203062 --- /dev/null +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs @@ -0,0 +1,27 @@ +using Ocelot.ServiceDiscovery; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public class LoadBalancerFactory : ILoadBalancerFactory + { + private readonly IServiceProvider _serviceProvider; + + public LoadBalancerFactory(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public ILoadBalancer Get(string serviceName, string loadBalancer) + { + switch (loadBalancer) + { + case "RoundRobin": + return new RoundRobinLoadBalancer(_serviceProvider.Get()); + case "LeastConnection": + return new LeastConnectionLoadBalancer(() => _serviceProvider.Get(), serviceName); + default: + return new NoLoadBalancer(_serviceProvider.Get()); + } + } + } +} diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs new file mode 100644 index 00000000..2788656a --- /dev/null +++ b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Linq; +using Ocelot.Responses; +using Ocelot.Values; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public class NoLoadBalancer : ILoadBalancer + { + private readonly List _services; + + public NoLoadBalancer(List services) + { + _services = services; + } + + public Response Lease() + { + var service = _services.FirstOrDefault(); + return new OkResponse(service.HostAndPort); + } + + public Response Release(HostAndPort hostAndPort) + { + return new OkResponse(); + } + } +} diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinLoadBalancer.cs new file mode 100644 index 00000000..1ffb46ce --- /dev/null +++ b/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinLoadBalancer.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using Ocelot.Responses; +using Ocelot.Values; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public class RoundRobinLoadBalancer : ILoadBalancer + { + private readonly List _services; + private int _last; + + public RoundRobinLoadBalancer(List services) + { + _services = services; + } + + public Response Lease() + { + if (_last >= _services.Count) + { + _last = 0; + } + + var next = _services[_last]; + _last++; + return new OkResponse(next.HostAndPort); + } + + public Response Release(HostAndPort hostAndPort) + { + return new OkResponse(); + } + } +} diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ServicesAreEmptyError.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ServicesAreEmptyError.cs new file mode 100644 index 00000000..2fc4d953 --- /dev/null +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ServicesAreEmptyError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public class ServicesAreEmptyError : Error + { + public ServicesAreEmptyError(string message) + : base(message, OcelotErrorCode.ServicesAreEmptyError) + { + } + } +} \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ServicesAreNullError.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ServicesAreNullError.cs new file mode 100644 index 00000000..8e1bb700 --- /dev/null +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ServicesAreNullError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public class ServicesAreNullError : Error + { + public ServicesAreNullError(string message) + : base(message, OcelotErrorCode.ServicesAreNullError) + { + } + } +} \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs new file mode 100644 index 00000000..5c66e98a --- /dev/null +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -0,0 +1,67 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Ocelot.Infrastructure.RequestData; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.Logging; +using Ocelot.Middleware; +using Ocelot.QueryStrings.Middleware; +using Ocelot.ServiceDiscovery; + +namespace Ocelot.LoadBalancer.Middleware +{ + public class LoadBalancingMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IOcelotLogger _logger; + + public LoadBalancingMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IRequestScopedDataRepository requestScopedDataRepository) + : base(requestScopedDataRepository) + { + _next = next; + _logger = loggerFactory.CreateLogger(); + } + + public async Task Invoke(HttpContext context) + { + _logger.LogDebug("started calling query string builder middleware"); + + //todo - get out of di? or do this when we bootstrap? + var serviceProviderFactory = new ServiceProviderFactory(); + var serviceConfig = new ServiceConfiguraion( + DownstreamRoute.ReRoute.ServiceName, + DownstreamRoute.ReRoute.DownstreamHost, + DownstreamRoute.ReRoute.DownstreamPort, + DownstreamRoute.ReRoute.UseServiceDiscovery); + //todo - get this out of some kind of service provider house? + var serviceProvider = serviceProviderFactory.Get(serviceConfig); + + //todo - get out of di? or do this when we bootstrap? + var loadBalancerFactory = new LoadBalancerFactory(serviceProvider); + //todo - currently instanciates a load balancer per request which is wrong, + //need some kind of load balance house! :) + var loadBalancer = loadBalancerFactory.Get(DownstreamRoute.ReRoute.ServiceName, DownstreamRoute.ReRoute.LoadBalancer); + var response = loadBalancer.Lease(); + + _logger.LogDebug("calling next middleware"); + + //todo - try next middleware if we get an exception make sure we release + //the host and port? Not sure if this is the way to go but we shall see! + try + { + await _next.Invoke(context); + + loadBalancer.Release(response.Data); + } + catch (Exception exception) + { + loadBalancer.Release(response.Data); + throw; + } + + _logger.LogDebug("succesfully called next middleware"); + } + } +} diff --git a/src/Ocelot/ServiceDiscovery/NoServiceProvider.cs b/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs similarity index 68% rename from src/Ocelot/ServiceDiscovery/NoServiceProvider.cs rename to src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs index 9f8b0239..b207f772 100644 --- a/src/Ocelot/ServiceDiscovery/NoServiceProvider.cs +++ b/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs @@ -3,11 +3,11 @@ using Ocelot.Values; namespace Ocelot.ServiceDiscovery { - public class NoServiceProvider : IServiceProvider + public class ConfigurationServiceProvider : IServiceProvider { private List _services; - public NoServiceProvider(List services) + public ConfigurationServiceProvider(List services) { _services = services; } diff --git a/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs b/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs index 0043edaa..67b84c69 100644 --- a/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs @@ -1,9 +1,7 @@ -using Ocelot.Configuration; - namespace Ocelot.ServiceDiscovery { public interface IServiceProviderFactory { - Ocelot.ServiceDiscovery.IServiceProvider Get(ReRoute reRoute); + IServiceProvider Get(ServiceConfiguraion serviceConfig); } } \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/ServiceProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceProviderFactory.cs index 583774e7..be3b8b8c 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/ServiceProviderFactory.cs @@ -1,13 +1,34 @@ -using System; -using Ocelot.Configuration; +using System.Collections.Generic; +using Ocelot.Values; namespace Ocelot.ServiceDiscovery { public class ServiceProviderFactory : IServiceProviderFactory { - public Ocelot.ServiceDiscovery.IServiceProvider Get(ReRoute reRoute) + public IServiceProvider Get(ServiceConfiguraion serviceConfig) { - throw new NotImplementedException(); + var services = new List() + { + new Service(serviceConfig.ServiceName, new HostAndPort(serviceConfig.DownstreamHost, serviceConfig.DownstreamPort)) + }; + + return new ConfigurationServiceProvider(services); } } + + public class ServiceConfiguraion + { + public ServiceConfiguraion(string serviceName, string downstreamHost, int downstreamPort, bool useServiceDiscovery) + { + ServiceName = serviceName; + DownstreamHost = downstreamHost; + DownstreamPort = downstreamPort; + UseServiceDiscovery = useServiceDiscovery; + } + + public string ServiceName { get; } + public string DownstreamHost { get; } + public int DownstreamPort { get; } + public bool UseServiceDiscovery { get; } + } } \ No newline at end of file diff --git a/src/Ocelot/Values/HostAndPort.cs b/src/Ocelot/Values/HostAndPort.cs index f8769743..c1219527 100644 --- a/src/Ocelot/Values/HostAndPort.cs +++ b/src/Ocelot/Values/HostAndPort.cs @@ -4,16 +4,11 @@ { public HostAndPort(string downstreamHost, int downstreamPort) { - DownstreamHost = downstreamHost; + DownstreamHost = downstreamHost?.Trim('/'); DownstreamPort = downstreamPort; } public string DownstreamHost { get; private set; } public int DownstreamPort { get; private set; } - - public override string ToString() - { - return $"{DownstreamHost}:{DownstreamPort}"; - } } } \ No newline at end of file diff --git a/src/Ocelot/project.json b/src/Ocelot/project.json index 85008568..8d259469 100644 --- a/src/Ocelot/project.json +++ b/src/Ocelot/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-*", + "version": "0.0.0-dev", "dependencies": { "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index f28abefe..713b93f1 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Address":null}}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Address":null}}} \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/project.json b/test/Ocelot.AcceptanceTests/project.json index 4b364510..17f35a3c 100644 --- a/test/Ocelot.AcceptanceTests/project.json +++ b/test/Ocelot.AcceptanceTests/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-*", + "version": "0.0.0-dev", "buildOptions": { "copyToOutput": { @@ -22,10 +22,10 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", "Microsoft.AspNetCore.Http": "1.1.0", "Microsoft.DotNet.InternalAbstractions": "1.0.0", - "Ocelot": "1.0.0-*", + "Ocelot": "0.0.0-dev", "xunit": "2.2.0-beta2-build3300", "dotnet-test-xunit": "2.2.0-preview2-build1029", - "Ocelot.ManualTest": "1.0.0-*", + "Ocelot.ManualTest": "0.0.0-dev", "Microsoft.AspNetCore.TestHost": "1.1.0", "IdentityServer4": "1.0.1", "Microsoft.AspNetCore.Mvc": "1.1.0", @@ -36,7 +36,7 @@ }, "runtimes": { "win10-x64": {}, - "osx.10.11-x64":{}, + "osx.10.11-x64": {}, "win7-x64": {} }, "frameworks": { diff --git a/test/Ocelot.Benchmarks/project.json b/test/Ocelot.Benchmarks/project.json index da310ddd..5f7a4987 100644 --- a/test/Ocelot.Benchmarks/project.json +++ b/test/Ocelot.Benchmarks/project.json @@ -1,11 +1,11 @@ { - "version": "1.0.0-*", + "version": "0.0.0-dev", "buildOptions": { "emitEntryPoint": true }, "dependencies": { - "Ocelot": "1.0.0-*", + "Ocelot": "0.0.0-dev", "BenchmarkDotNet": "0.10.1" }, "runtimes": { diff --git a/test/Ocelot.ManualTest/project.json b/test/Ocelot.ManualTest/project.json index 181bdb07..3ae09ccb 100644 --- a/test/Ocelot.ManualTest/project.json +++ b/test/Ocelot.ManualTest/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-*", + "version": "0.0.0-dev", "dependencies": { "Microsoft.AspNetCore.Http": "1.1.0", @@ -10,7 +10,7 @@ "Microsoft.Extensions.Logging.Console": "1.1.0", "Microsoft.Extensions.Logging.Debug": "1.1.0", "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", - "Ocelot": "1.0.0-*", + "Ocelot": "0.0.0-dev", "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", "Microsoft.NETCore.App": "1.1.0" }, diff --git a/test/Ocelot.UnitTests/LeastConnectionTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs similarity index 56% rename from test/Ocelot.UnitTests/LeastConnectionTests.cs rename to test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs index ede2105f..a8617b22 100644 --- a/test/Ocelot.UnitTests/LeastConnectionTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs @@ -1,14 +1,12 @@ -using System; using System.Collections.Generic; -using System.Linq; -using Ocelot.Errors; +using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Responses; using Ocelot.Values; using Shouldly; using TestStack.BDDfy; using Xunit; -namespace Ocelot.UnitTests +namespace Ocelot.UnitTests.LoadBalancer { public class LeastConnectionTests { @@ -17,10 +15,6 @@ namespace Ocelot.UnitTests private LeastConnectionLoadBalancer _leastConnection; private List _services; - public LeastConnectionTests() - { - } - [Fact] public void should_get_next_url() { @@ -202,161 +196,4 @@ namespace Ocelot.UnitTests _result.Data.DownstreamPort.ShouldBe(_hostAndPort.DownstreamPort); } } - - public class LeastConnectionLoadBalancer : ILoadBalancer - { - private Func> _services; - private List _leases; - private string _serviceName; - - public LeastConnectionLoadBalancer(Func> services, string serviceName) - { - _services = services; - _serviceName = serviceName; - _leases = new List(); - } - - public Response Lease() - { - var services = _services(); - - if(services == null) - { - return new ErrorResponse(new List(){ new ServicesAreNullError($"services were null for {_serviceName}")}); - } - - if(!services.Any()) - { - return new ErrorResponse(new List(){ new ServicesAreEmptyError($"services were empty for {_serviceName}")}); - } - - //todo - maybe this should be moved somewhere else...? Maybe on a repeater on seperate thread? loop every second and update or something? - UpdateServices(services); - - var leaseWithLeastConnections = GetLeaseWithLeastConnections(); - - _leases.Remove(leaseWithLeastConnections); - - leaseWithLeastConnections = AddConnection(leaseWithLeastConnections); - - _leases.Add(leaseWithLeastConnections); - - return new OkResponse(new HostAndPort(leaseWithLeastConnections.HostAndPort.DownstreamHost, leaseWithLeastConnections.HostAndPort.DownstreamPort)); - } - - public Response Release(HostAndPort hostAndPort) - { - var matchingLease = _leases.FirstOrDefault(l => l.HostAndPort.DownstreamHost == hostAndPort.DownstreamHost - && l.HostAndPort.DownstreamPort == hostAndPort.DownstreamPort); - - if(matchingLease != null) - { - var replacementLease = new Lease(hostAndPort, matchingLease.Connections - 1); - - _leases.Remove(matchingLease); - - _leases.Add(replacementLease); - } - - return new OkResponse(); - } - - private Lease AddConnection(Lease lease) - { - return new Lease(lease.HostAndPort, lease.Connections + 1); - } - - private Lease GetLeaseWithLeastConnections() - { - //now get the service with the least connections? - Lease leaseWithLeastConnections = null; - - for(var i = 0; i < _leases.Count; i++) - { - if(i == 0) - { - leaseWithLeastConnections = _leases[i]; - } - else - { - if(_leases[i].Connections < leaseWithLeastConnections.Connections) - { - leaseWithLeastConnections = _leases[i]; - } - } - } - - return leaseWithLeastConnections; - } - - private Response UpdateServices(List services) - { - if(_leases.Count > 0) - { - var leasesToRemove = new List(); - - foreach(var lease in _leases) - { - var match = services.FirstOrDefault(s => s.HostAndPort.DownstreamHost == lease.HostAndPort.DownstreamHost - && s.HostAndPort.DownstreamPort == lease.HostAndPort.DownstreamPort); - - if(match == null) - { - leasesToRemove.Add(lease); - } - } - - foreach(var lease in leasesToRemove) - { - _leases.Remove(lease); - } - - foreach(var service in services) - { - var exists = _leases.FirstOrDefault(l => l.HostAndPort.ToString() == service.HostAndPort.ToString()); - - if(exists == null) - { - _leases.Add(new Lease(service.HostAndPort, 0)); - } - } - } - else - { - foreach(var service in services) - { - _leases.Add(new Lease(service.HostAndPort, 0)); - } - } - - return new OkResponse(); - } - } - - public class Lease - { - public Lease(HostAndPort hostAndPort, int connections) - { - HostAndPort = hostAndPort; - Connections = connections; - } - public HostAndPort HostAndPort {get;private set;} - public int Connections {get;private set;} - } - - public class ServicesAreNullError : Error - { - public ServicesAreNullError(string message) - : base(message, OcelotErrorCode.ServicesAreNullError) - { - } - } - - public class ServicesAreEmptyError : Error - { - public ServicesAreEmptyError(string message) - : base(message, OcelotErrorCode.ServicesAreEmptyError) - { - } - } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs new file mode 100644 index 00000000..c4eb736b --- /dev/null +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -0,0 +1,97 @@ +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.ServiceDiscovery; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.LoadBalancer +{ + public class LoadBalancerFactoryTests + { + private ReRoute _reRoute; + private LoadBalancerFactory _factory; + private ILoadBalancer _result; + private Mock _serviceProvider; + + public LoadBalancerFactoryTests() + { + _serviceProvider = new Mock(); + _factory = new LoadBalancerFactory(_serviceProvider.Object); + } + + [Fact] + public void should_return_no_load_balancer() + { + var reRoute = new ReRouteBuilder() + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .When(x => x.WhenIGetTheLoadBalancer()) + .Then(x => x.ThenTheLoadBalancerIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_return_round_robin_load_balancer() + { + var reRoute = new ReRouteBuilder() + .WithLoadBalancer("RoundRobin") + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .When(x => x.WhenIGetTheLoadBalancer()) + .Then(x => x.ThenTheLoadBalancerIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_return_round_least_connection_balancer() + { + var reRoute = new ReRouteBuilder() + .WithLoadBalancer("LeastConnection") + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .When(x => x.WhenIGetTheLoadBalancer()) + .Then(x => x.ThenTheLoadBalancerIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_call_service_provider() + { + var reRoute = new ReRouteBuilder() + .WithLoadBalancer("RoundRobin") + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .When(x => x.WhenIGetTheLoadBalancer()) + .Then(x => x.ThenTheServiceProviderIsCalledCorrectly()) + .BDDfy(); + } + + private void ThenTheServiceProviderIsCalledCorrectly() + { + _serviceProvider + .Verify(x => x.Get(), Times.Once); + } + + private void GivenAReRoute(ReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenIGetTheLoadBalancer() + { + _result = _factory.Get(_reRoute.ServiceName, _reRoute.LoadBalancer); + } + + private void ThenTheLoadBalancerIsReturned() + { + _result.ShouldBeOfType(); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs b/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs new file mode 100644 index 00000000..a2fd2be8 --- /dev/null +++ b/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.Responses; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.LoadBalancer +{ + public class NoLoadBalancerTests + { + private List _services; + private NoLoadBalancer _loadBalancer; + private Response _result; + + [Fact] + public void should_return_host_and_port() + { + var hostAndPort = new HostAndPort("127.0.0.1", 80); + + var services = new List + { + new Service("product", hostAndPort) + }; + this.Given(x => x.GivenServices(services)) + .When(x => x.WhenIGetTheNextHostAndPort()) + .Then(x => x.ThenTheHostAndPortIs(hostAndPort)) + .BDDfy(); + } + + private void GivenServices(List services) + { + _services = services; + } + + private void WhenIGetTheNextHostAndPort() + { + _loadBalancer = new NoLoadBalancer(_services); + _result = _loadBalancer.Lease(); + } + + private void ThenTheHostAndPortIs(HostAndPort expected) + { + _result.Data.ShouldBe(expected); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/RoundRobinTests.cs b/test/Ocelot.UnitTests/LoadBalancer/RoundRobinTests.cs similarity index 71% rename from test/Ocelot.UnitTests/RoundRobinTests.cs rename to test/Ocelot.UnitTests/LoadBalancer/RoundRobinTests.cs index 39d33441..cb934af8 100644 --- a/test/Ocelot.UnitTests/RoundRobinTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/RoundRobinTests.cs @@ -1,12 +1,13 @@ using System.Collections.Generic; using System.Diagnostics; +using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Responses; using Ocelot.Values; using Shouldly; using TestStack.BDDfy; using Xunit; -namespace Ocelot.UnitTests +namespace Ocelot.UnitTests.LoadBalancer { public class RoundRobinTests { @@ -64,38 +65,4 @@ namespace Ocelot.UnitTests _hostAndPort.Data.ShouldBe(_services[index].HostAndPort); } } - - public interface ILoadBalancer - { - Response Lease(); - Response Release(HostAndPort hostAndPort); - } - - public class RoundRobinLoadBalancer : ILoadBalancer - { - private readonly List _services; - private int _last; - - public RoundRobinLoadBalancer(List services) - { - _services = services; - } - - public Response Lease() - { - if (_last >= _services.Count) - { - _last = 0; - } - - var next = _services[_last]; - _last++; - return new OkResponse(next.HostAndPort); - } - - public Response Release(HostAndPort hostAndPort) - { - return new OkResponse(); - } - } } diff --git a/test/Ocelot.UnitTests/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancerFactoryTests.cs deleted file mode 100644 index 12f59ceb..00000000 --- a/test/Ocelot.UnitTests/LoadBalancerFactoryTests.cs +++ /dev/null @@ -1,187 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Responses; -using Ocelot.Values; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests -{ - public class LoadBalancerFactoryTests - { - private ReRoute _reRoute; - private LoadBalancerFactory _factory; - private ILoadBalancer _result; - private Mock _serviceProvider; - - public LoadBalancerFactoryTests() - { - _serviceProvider = new Mock(); - _factory = new LoadBalancerFactory(_serviceProvider.Object); - } - - [Fact] - public void should_return_no_load_balancer() - { - var reRoute = new ReRouteBuilder() - .Build(); - - this.Given(x => x.GivenAReRoute(reRoute)) - .When(x => x.WhenIGetTheLoadBalancer()) - .Then(x => x.ThenTheLoadBalancerIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_return_round_robin_load_balancer() - { - var reRoute = new ReRouteBuilder() - .WithLoadBalancer("RoundRobin") - .Build(); - - this.Given(x => x.GivenAReRoute(reRoute)) - .When(x => x.WhenIGetTheLoadBalancer()) - .Then(x => x.ThenTheLoadBalancerIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_return_round_least_connection_balancer() - { - var reRoute = new ReRouteBuilder() - .WithLoadBalancer("LeastConnection") - .Build(); - - this.Given(x => x.GivenAReRoute(reRoute)) - .When(x => x.WhenIGetTheLoadBalancer()) - .Then(x => x.ThenTheLoadBalancerIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_call_service_provider() - { - var reRoute = new ReRouteBuilder() - .WithLoadBalancer("RoundRobin") - .Build(); - - this.Given(x => x.GivenAReRoute(reRoute)) - .When(x => x.WhenIGetTheLoadBalancer()) - .Then(x => x.ThenTheServiceProviderIsCalledCorrectly(reRoute)) - .BDDfy(); - } - - private void ThenTheServiceProviderIsCalledCorrectly(ReRoute reRoute) - { - _serviceProvider - .Verify(x => x.Get(), Times.Once); - } - - private void GivenAReRoute(ReRoute reRoute) - { - _reRoute = reRoute; - } - - private void WhenIGetTheLoadBalancer() - { - _result = _factory.Get(_reRoute); - } - - private void ThenTheLoadBalancerIsReturned() - { - _result.ShouldBeOfType(); - } - } - - public class NoLoadBalancerTests - { - private List _services; - private NoLoadBalancer _loadBalancer; - private Response _result; - - [Fact] - public void should_return_host_and_port() - { - var hostAndPort = new HostAndPort("127.0.0.1", 80); - - var services = new List - { - new Service("product", hostAndPort) - }; - this.Given(x => x.GivenServices(services)) - .When(x => x.WhenIGetTheNextHostAndPort()) - .Then(x => x.ThenTheHostAndPortIs(hostAndPort)) - .BDDfy(); - } - - private void GivenServices(List services) - { - _services = services; - } - - private void WhenIGetTheNextHostAndPort() - { - _loadBalancer = new NoLoadBalancer(_services); - _result = _loadBalancer.Lease(); - } - - private void ThenTheHostAndPortIs(HostAndPort expected) - { - _result.Data.ShouldBe(expected); - } - } - - public class NoLoadBalancer : ILoadBalancer - { - private List _services; - - public NoLoadBalancer(List services) - { - _services = services; - } - - public Response Lease() - { - var service = _services.FirstOrDefault(); - return new OkResponse(service.HostAndPort); - } - - public Response Release(HostAndPort hostAndPort) - { - return new OkResponse(); - } - } - - public interface ILoadBalancerFactory - { - ILoadBalancer Get(ReRoute reRoute); - } - - public class LoadBalancerFactory : ILoadBalancerFactory - { - private Ocelot.ServiceDiscovery.IServiceProvider _serviceProvider; - - public LoadBalancerFactory(Ocelot.ServiceDiscovery.IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - } - - public ILoadBalancer Get(ReRoute reRoute) - { - switch (reRoute.LoadBalancer) - { - case "RoundRobin": - return new RoundRobinLoadBalancer(_serviceProvider.Get()); - case "LeastConnection": - return new LeastConnectionLoadBalancer(() => _serviceProvider.Get(), reRoute.ServiceName); - default: - return new NoLoadBalancer(_serviceProvider.Get()); - } - } - } -} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/NoServiceProviderTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs similarity index 77% rename from test/Ocelot.UnitTests/NoServiceProviderTests.cs rename to test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs index f85ef13c..182dd514 100644 --- a/test/Ocelot.UnitTests/NoServiceProviderTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs @@ -1,17 +1,15 @@ -using System; using System.Collections.Generic; -using Ocelot.Configuration; using Ocelot.ServiceDiscovery; using Ocelot.Values; using Shouldly; using TestStack.BDDfy; using Xunit; -namespace Ocelot.UnitTests +namespace Ocelot.UnitTests.ServiceDiscovery { - public class NoServiceProviderTests + public class ConfigurationServiceProviderTests { - private NoServiceProvider _serviceProvider; + private ConfigurationServiceProvider _serviceProvider; private HostAndPort _hostAndPort; private List _result; private List _expected; @@ -26,20 +24,20 @@ namespace Ocelot.UnitTests new Service("product", hostAndPort) }; - this.Given(x => x.GivenAHostAndPort(services)) + this.Given(x => x.GivenServices(services)) .When(x => x.WhenIGetTheService()) .Then(x => x.ThenTheFollowingIsReturned(services)) .BDDfy(); } - private void GivenAHostAndPort(List services) + private void GivenServices(List services) { _expected = services; } private void WhenIGetTheService() { - _serviceProvider = new NoServiceProvider(_expected); + _serviceProvider = new ConfigurationServiceProvider(_expected); _result = _serviceProvider.Get(); } diff --git a/test/Ocelot.UnitTests/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs similarity index 54% rename from test/Ocelot.UnitTests/ServiceProviderFactoryTests.cs rename to test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs index 05609816..9bcc2672 100644 --- a/test/Ocelot.UnitTests/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs @@ -1,17 +1,15 @@ -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; using Ocelot.ServiceDiscovery; using Shouldly; using TestStack.BDDfy; using Xunit; -namespace Ocelot.UnitTests +namespace Ocelot.UnitTests.ServiceDiscovery { public class ServiceProviderFactoryTests { - private ReRoute _reRote; + private ServiceConfiguraion _serviceConfig; private IServiceProvider _result; - private ServiceProviderFactory _factory; + private readonly ServiceProviderFactory _factory; public ServiceProviderFactoryTests() { @@ -21,25 +19,22 @@ namespace Ocelot.UnitTests [Fact] public void should_return_no_service_provider() { - var reRoute = new ReRouteBuilder() - .WithDownstreamHost("127.0.0.1") - .WithDownstreamPort(80) - .Build(); + var serviceConfig = new ServiceConfiguraion("product", "127.0.0.1", 80, false); - this.Given(x => x.GivenTheReRoute(reRoute)) + this.Given(x => x.GivenTheReRoute(serviceConfig)) .When(x => x.WhenIGetTheServiceProvider()) - .Then(x => x.ThenTheServiceProviderIs()) + .Then(x => x.ThenTheServiceProviderIs()) .BDDfy(); } - private void GivenTheReRoute(ReRoute reRoute) + private void GivenTheReRoute(ServiceConfiguraion serviceConfig) { - _reRote = reRoute; + _serviceConfig = serviceConfig; } private void WhenIGetTheServiceProvider() { - _result = _factory.Get(_reRote); + _result = _factory.Get(_serviceConfig); } private void ThenTheServiceProviderIs() diff --git a/test/Ocelot.UnitTests/ServiceRegistryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceRegistryTests.cs similarity index 98% rename from test/Ocelot.UnitTests/ServiceRegistryTests.cs rename to test/Ocelot.UnitTests/ServiceDiscovery/ServiceRegistryTests.cs index 09012213..61f7c975 100644 --- a/test/Ocelot.UnitTests/ServiceRegistryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceRegistryTests.cs @@ -4,7 +4,7 @@ using Shouldly; using TestStack.BDDfy; using Xunit; -namespace Ocelot.UnitTests +namespace Ocelot.UnitTests.ServiceDiscovery { public class ServiceRegistryTests { diff --git a/test/Ocelot.UnitTests/project.json b/test/Ocelot.UnitTests/project.json index 605b25d6..ab3e6cb1 100644 --- a/test/Ocelot.UnitTests/project.json +++ b/test/Ocelot.UnitTests/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-*", + "version": "0.0.0-dev", "testRunner": "xunit", @@ -13,7 +13,7 @@ "Microsoft.Extensions.Logging.Debug": "1.1.0", "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", "Microsoft.AspNetCore.Http": "1.1.0", - "Ocelot": "1.0.0-*", + "Ocelot": "0.0.0-dev", "xunit": "2.2.0-beta2-build3300", "dotnet-test-xunit": "2.2.0-preview2-build1029", "Moq": "4.6.38-alpha", diff --git a/version.ps1 b/version.ps1 new file mode 100644 index 00000000..621201b6 --- /dev/null +++ b/version.ps1 @@ -0,0 +1 @@ +.\tools\GitVersion.CommandLine\tools\GitVersion.exe \ No newline at end of file