From 454ba3f9a03d79dfd493d67d6a02c0a2eb1d0c4b Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sat, 3 Mar 2018 15:24:05 +0000 Subject: [PATCH] Make Ocelot work with service fabric DNS and naming service for guest exe and stateless (#242) * test for issue * added service fabric sample * working!! * changed sample naming to Ocelot * removed files we dont need * removed files we dont need * updated sample gitignore * updated sample gitignore * getting ocelot to work with service fabric using the reverse proxy * #238 - added support for service fabric discovery provider, proxies requests through naming service, wont work on partioned service fabric services yet * #238 - Manually tested service fabric using sample..all seems OK. Made some changes after testing, added docs * #238 - added docs for servic fabric --- Ocelot.sln | 19 + docs/features/servicediscovery.rst | 14 +- docs/features/servicefabric.rst | 35 + docs/index.rst | 1 + .../ocelot.postman_collection.json | 626 +++++++++--------- samples/OcelotServiceFabric/.gitignore | 269 ++++++++ samples/OcelotServiceFabric/CONTRIBUTING.md | 11 + samples/OcelotServiceFabric/LICENSE.md | 21 + .../OcelotApplication/ApplicationManifest.xml | 39 ++ .../Code/entryPoint.cmd | 2 + .../Code/entryPoint.sh | 17 + .../Config/Settings.xml | 9 + .../Config/_readme.txt | 12 + .../Data/_readme.txt | 5 + .../ServiceManifest-Linux.xml | 35 + .../ServiceManifest-Windows.xml | 35 + .../ServiceManifest.xml | 35 + .../Code/entryPoint.cmd | 2 + .../Code/entryPoint.sh | 15 + .../Config/Settings.xml | 9 + .../Config/_readme.txt | 12 + .../Data/_readme.txt | 5 + .../ServiceManifest-Linux.xml | 32 + .../ServiceManifest-Windows.xml | 32 + .../ServiceManifest.xml | 32 + samples/OcelotServiceFabric/README.md | 51 ++ samples/OcelotServiceFabric/build.bat | 12 + samples/OcelotServiceFabric/build.sh | 15 + samples/OcelotServiceFabric/dotnet-include.sh | 12 + samples/OcelotServiceFabric/install.ps1 | 20 + samples/OcelotServiceFabric/install.sh | 22 + .../OcelotApplicationApiGateway.cs | 31 + .../OcelotApplicationApiGateway.csproj | 22 + .../OcelotApplicationApiGateway/Program.cs | 51 ++ .../Properties/launchSettings.json | 27 + .../ServiceEventListener.cs | 94 +++ .../ServiceEventSource.cs | 86 +++ .../WebCommunicationListener.cs | 125 ++++ .../appsettings.json | 10 + .../configuration.json | 22 + .../OcelotApplicationService/ApiGateway.cs | 59 ++ .../Controllers/ValuesController.cs | 44 ++ .../OcelotApplicationService.csproj | 21 + .../src/OcelotApplicationService/Program.cs | 38 ++ .../ServiceEventSource.cs | 189 ++++++ .../src/OcelotApplicationService/Startup.cs | 40 ++ samples/OcelotServiceFabric/src/global.json | 6 + samples/OcelotServiceFabric/uninstall.ps1 | 2 + samples/OcelotServiceFabric/uninstall.sh | 5 + .../Builder/DownstreamReRouteBuilder.cs | 1 - .../ServiceProviderConfigurationBuilder.cs | 11 +- .../ServiceProviderConfigurationCreator.cs | 10 +- .../File/FileServiceDiscoveryProvider.cs | 3 +- .../ConsulFileConfigurationRepository.cs | 6 +- .../ServiceProviderConfiguraion.cs | 14 +- .../FileConfigurationFluentValidator.cs | 2 +- .../DependencyInjection/OcelotBuilder.cs | 1 - .../DownstreamUrlCreator/IUrlBuilder.cs | 4 +- .../DownstreamUrlCreatorMiddleware.cs | 35 +- src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs | 7 +- .../LoadBalancers/NoLoadBalancer.cs | 1 + .../ServiceDiscoveryProviderFactory.cs | 12 +- .../ServiceFabricConfiguration.cs | 16 + .../ServiceFabricServiceDiscoveryProvider.cs | 29 + .../ServiceFabricTests.cs | 112 ++++ .../ConfigurationFluentValidationTests.cs | 2 +- .../ServiceProviderCreatorTests.cs | 10 +- .../DownstreamUrlCreatorMiddlewareTests.cs | 76 ++- .../DownstreamUrlCreator/UrlBuilderTests.cs | 122 ---- ...viceFabricServiceDiscoveryProviderTests.cs | 58 ++ .../ServiceProviderFactoryTests.cs | 18 + 71 files changed, 2394 insertions(+), 484 deletions(-) create mode 100644 docs/features/servicefabric.rst rename ocelot.postman_collection.json => postman/ocelot.postman_collection.json (96%) create mode 100644 samples/OcelotServiceFabric/.gitignore create mode 100644 samples/OcelotServiceFabric/CONTRIBUTING.md create mode 100644 samples/OcelotServiceFabric/LICENSE.md create mode 100644 samples/OcelotServiceFabric/OcelotApplication/ApplicationManifest.xml create mode 100644 samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Code/entryPoint.cmd create mode 100644 samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Code/entryPoint.sh create mode 100644 samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Config/Settings.xml create mode 100644 samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Config/_readme.txt create mode 100644 samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Data/_readme.txt create mode 100644 samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest-Linux.xml create mode 100644 samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest-Windows.xml create mode 100644 samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest.xml create mode 100644 samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Code/entryPoint.cmd create mode 100644 samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Code/entryPoint.sh create mode 100644 samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Config/Settings.xml create mode 100644 samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Config/_readme.txt create mode 100644 samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Data/_readme.txt create mode 100644 samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest-Linux.xml create mode 100644 samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest-Windows.xml create mode 100644 samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest.xml create mode 100644 samples/OcelotServiceFabric/README.md create mode 100644 samples/OcelotServiceFabric/build.bat create mode 100644 samples/OcelotServiceFabric/build.sh create mode 100644 samples/OcelotServiceFabric/dotnet-include.sh create mode 100644 samples/OcelotServiceFabric/install.ps1 create mode 100644 samples/OcelotServiceFabric/install.sh create mode 100644 samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.cs create mode 100644 samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.csproj create mode 100644 samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/Program.cs create mode 100644 samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/Properties/launchSettings.json create mode 100644 samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ServiceEventListener.cs create mode 100644 samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ServiceEventSource.cs create mode 100644 samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/WebCommunicationListener.cs create mode 100644 samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/appsettings.json create mode 100644 samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/configuration.json create mode 100644 samples/OcelotServiceFabric/src/OcelotApplicationService/ApiGateway.cs create mode 100644 samples/OcelotServiceFabric/src/OcelotApplicationService/Controllers/ValuesController.cs create mode 100644 samples/OcelotServiceFabric/src/OcelotApplicationService/OcelotApplicationService.csproj create mode 100644 samples/OcelotServiceFabric/src/OcelotApplicationService/Program.cs create mode 100644 samples/OcelotServiceFabric/src/OcelotApplicationService/ServiceEventSource.cs create mode 100644 samples/OcelotServiceFabric/src/OcelotApplicationService/Startup.cs create mode 100644 samples/OcelotServiceFabric/src/global.json create mode 100644 samples/OcelotServiceFabric/uninstall.ps1 create mode 100644 samples/OcelotServiceFabric/uninstall.sh create mode 100644 src/Ocelot/ServiceDiscovery/ServiceFabricConfiguration.cs create mode 100644 src/Ocelot/ServiceDiscovery/ServiceFabricServiceDiscoveryProvider.cs create mode 100644 test/Ocelot.AcceptanceTests/ServiceFabricTests.cs delete mode 100644 test/Ocelot.UnitTests/DownstreamUrlCreator/UrlBuilderTests.cs create mode 100644 test/Ocelot.UnitTests/ServiceDiscovery/ServiceFabricServiceDiscoveryProviderTests.cs diff --git a/Ocelot.sln b/Ocelot.sln index 6aa429fe..e966a5ae 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -39,6 +39,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Benchmarks", "test\O EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.IntegrationTests", "test\Ocelot.IntegrationTests\Ocelot.IntegrationTests.csproj", "{D4575572-99CA-4530-8737-C296EDA326F8}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{E78EF991-3401-459A-94FE-EC4F4E5BD702}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OcelotApplicationApiGateway", "samples\OcelotServiceFabric\src\OcelotApplicationApiGateway\OcelotApplicationApiGateway.csproj", "{1A3A3D97-33AB-48FB-8A9F-92905A15DF74}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OcelotApplicationService", "samples\OcelotServiceFabric\src\OcelotApplicationService\OcelotApplicationService.csproj", "{272DD79B-7D04-4DFB-BB64-B1C098CE8050}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OcelotServiceFabric", "OcelotServiceFabric", "{98424512-BCF5-4F42-ACB2-6D7040D92487}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -69,6 +77,14 @@ Global {D4575572-99CA-4530-8737-C296EDA326F8}.Debug|Any CPU.Build.0 = Debug|Any CPU {D4575572-99CA-4530-8737-C296EDA326F8}.Release|Any CPU.ActiveCfg = Release|Any CPU {D4575572-99CA-4530-8737-C296EDA326F8}.Release|Any CPU.Build.0 = Release|Any CPU + {1A3A3D97-33AB-48FB-8A9F-92905A15DF74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A3A3D97-33AB-48FB-8A9F-92905A15DF74}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A3A3D97-33AB-48FB-8A9F-92905A15DF74}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A3A3D97-33AB-48FB-8A9F-92905A15DF74}.Release|Any CPU.Build.0 = Release|Any CPU + {272DD79B-7D04-4DFB-BB64-B1C098CE8050}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {272DD79B-7D04-4DFB-BB64-B1C098CE8050}.Debug|Any CPU.Build.0 = Debug|Any CPU + {272DD79B-7D04-4DFB-BB64-B1C098CE8050}.Release|Any CPU.ActiveCfg = Release|Any CPU + {272DD79B-7D04-4DFB-BB64-B1C098CE8050}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -80,6 +96,9 @@ Global {02BBF4C5-517E-4157-8D21-4B8B9E118B7A} = {5B401523-36DA-4491-B73A-7590A26E420B} {106B49E6-95F6-4A7B-B81C-96BFA74AF035} = {5B401523-36DA-4491-B73A-7590A26E420B} {D4575572-99CA-4530-8737-C296EDA326F8} = {5B401523-36DA-4491-B73A-7590A26E420B} + {1A3A3D97-33AB-48FB-8A9F-92905A15DF74} = {98424512-BCF5-4F42-ACB2-6D7040D92487} + {272DD79B-7D04-4DFB-BB64-B1C098CE8050} = {98424512-BCF5-4F42-ACB2-6D7040D92487} + {98424512-BCF5-4F42-ACB2-6D7040D92487} = {E78EF991-3401-459A-94FE-EC4F4E5BD702} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {21476EFF-778A-4F97-8A56-D1AF1CEC0C48} diff --git a/docs/features/servicediscovery.rst b/docs/features/servicediscovery.rst index 28728db3..f8cadd2e 100644 --- a/docs/features/servicediscovery.rst +++ b/docs/features/servicediscovery.rst @@ -6,16 +6,18 @@ for the downstream service Ocelot is forwarding a request to. At the moment this GlobalConfiguration section which means the same service discovery provider will be used for all ReRoutes you specify a ServiceName for at ReRoute level. -At the moment the only supported service discovery provider is Consul. The following is required in the -GlobalConfiguration. The Provider is required and if you do not specify a host and port the Consul default +Consul +^^^^^^ + +The following is required in the GlobalConfiguration. The Provider is required and if you do not specify a host and port the Consul default will be used. .. code-block:: json "ServiceDiscoveryProvider": { - "Host": "localhost", - "Port": 9500 - } + "Host": "localhost", + "Port": 9500 + } In the future we can add a feature that allows ReRoute specfic configuration. @@ -35,4 +37,4 @@ and LeastConnection algorithm you can use. If no load balancer is specified Ocel "UseServiceDiscovery": true } -When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. \ No newline at end of file +When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. diff --git a/docs/features/servicefabric.rst b/docs/features/servicefabric.rst new file mode 100644 index 00000000..e6f69983 --- /dev/null +++ b/docs/features/servicefabric.rst @@ -0,0 +1,35 @@ +Service Fabric +============== + +If you have services deployed in Service Fabric you will normally use the naming service to access them. + +The following example shows how to set up a ReRoute that will work in Service Fabric. The most important thing is the ServiceName which is made up of the +Service Fabric application name then the specific service name. We also need to set UseServiceDiscovery as true and set up the ServiceDiscoveryProvider in +GlobalConfiguration. The example here shows a typical configuration. It assumes service fabric is running on localhost and that the naming service is on port 19081. + +The example below is taken from the samples folder so please check it if this doesnt make sense! + +.. code-block:: json + + { + "ReRoutes": [ + { + "DownstreamPathTemplate": "/api/values", + "UpstreamPathTemplate": "/EquipmentInterfaces", + "UpstreamHttpMethod": [ + "Get" + ], + "DownstreamScheme": "http", + "ServiceName": "OcelotServiceApplication/OcelotApplicationService", + "UseServiceDiscovery" : true + } + ], + "GlobalConfiguration": { + "RequestIdKey": "OcRequestId", + "ServiceDiscoveryProvider": { + "Host": "localhost", + "Port": 19081, + "Type": "ServiceFabric" + } + } + } diff --git a/docs/index.rst b/docs/index.rst index 258085ef..43a6c436 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -22,6 +22,7 @@ Thanks for taking a look at the Ocelot documentation. Please use the left hand n features/routing features/requestaggregation features/servicediscovery + features/servicefabric features/authentication features/authorisation features/administration diff --git a/ocelot.postman_collection.json b/postman/ocelot.postman_collection.json similarity index 96% rename from ocelot.postman_collection.json rename to postman/ocelot.postman_collection.json index 2dc33d33..28bbeb0c 100644 --- a/ocelot.postman_collection.json +++ b/postman/ocelot.postman_collection.json @@ -1,314 +1,314 @@ -{ - "id": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", - "name": "Ocelot", - "description": "", - "order": [ - "a1c95935-ed18-d5dc-bcb8-a3db8ba1934f", - "ea0ed57a-2cb9-8acc-47dd-006b8db2f1b2", - "c4494401-3985-a5bf-71fb-6e4171384ac6", - "09af8dda-a9cb-20d2-5ee3-0a3023773a1a", - "e8825dc3-4137-99a7-0000-ef5786610dc3", - "fddfc4fa-5114-69e3-4744-203ed71a526b", - "c45d30d7-d9c4-fa05-8110-d6e769bb6ff9", - "4684c2fa-f38c-c193-5f55-bf563a1978c6", - "5f308240-79e3-cf74-7a6b-fe462f0d54f1", - "178f16da-c61b-c881-1c33-9d64a56851a4", - "26a08569-85f6-7f9a-726f-61be419c7a34" - ], - "folders": [], - "timestamp": 0, - "owner": "212120", - "public": false, - "requests": [ - { - "folder": null, - "id": "09af8dda-a9cb-20d2-5ee3-0a3023773a1a", - "name": "GET http://localhost:5000/comments?postId=1", - "dataMode": "params", - "data": null, - "rawModeData": null, - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "GET", - "pathVariables": {}, - "url": "http://localhost:5000/comments?postId=1", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" - }, - { - "id": "178f16da-c61b-c881-1c33-9d64a56851a4", - "headers": "Authorization: Bearer {{AccessToken}}\n", - "url": "http://localhost:5000/administration/configuration", - "preRequestScript": null, - "pathVariables": {}, - "method": "GET", - "data": null, - "dataMode": "params", - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "time": 1508914722969, - "name": "GET http://localhost:5000/admin/configuration", - "description": "", - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" - }, - { - "id": "26a08569-85f6-7f9a-726f-61be419c7a34", - "headers": "", - "url": "http://localhost:5000/administration/connect/token", - "preRequestScript": null, - "pathVariables": {}, - "method": "POST", - "data": [ - { - "key": "client_id", - "value": "raft", - "type": "text", - "enabled": true - }, - { - "key": "client_secret", - "value": "REALLYHARDPASSWORD", - "type": "text", - "enabled": true - }, - { - "key": "scope", - "value": "admin raft ", - "type": "text", - "enabled": true - }, - { - "key": "username", - "value": "admin", - "type": "text", - "enabled": false - }, - { - "key": "password", - "value": "secret", - "type": "text", - "enabled": false - }, - { - "key": "grant_type", - "value": "client_credentials", - "type": "text", - "enabled": true - } - ], - "dataMode": "params", - "tests": "var jsonData = JSON.parse(responseBody);\npostman.setGlobalVariable(\"AccessToken\", jsonData.access_token);\npostman.setGlobalVariable(\"RefreshToken\", jsonData.refresh_token);", - "currentHelper": "normal", - "helperAttributes": {}, - "time": 1513240031907, - "name": "POST http://localhost:5000/admin/connect/token copy copy", - "description": "", - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" - }, - { - "folder": null, - "id": "4684c2fa-f38c-c193-5f55-bf563a1978c6", - "name": "DELETE http://localhost:5000/posts/1", - "dataMode": "params", - "data": null, - "rawModeData": null, - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "DELETE", - "pathVariables": {}, - "url": "http://localhost:5000/posts/1", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" - }, - { - "id": "5f308240-79e3-cf74-7a6b-fe462f0d54f1", - "headers": "Authorization: Bearer {{AccessToken}}\n", - "url": "http://localhost:5000/administration/.well-known/openid-configuration", - "preRequestScript": null, - "pathVariables": {}, - "method": "GET", - "data": null, - "dataMode": "params", - "tests": null, - "currentHelper": "normal", - "helperAttributes": "{}", - "time": 1488038888813, - "name": "GET http://localhost:5000/admin/.well-known/openid-configuration", - "description": "", - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", - "folder": null, - "rawModeData": null, - "descriptionFormat": null, - "queryParams": [], - "headerData": [ - { - "key": "Authorization", - "value": "Bearer {{AccessToken}}", - "description": "", - "enabled": true - } - ], - "pathVariableData": [] - }, - { - "id": "a1c95935-ed18-d5dc-bcb8-a3db8ba1934f", - "folder": null, - "name": "GET http://localhost:5000/posts", - "dataMode": "params", - "data": [ - { - "key": "client_id", - "value": "admin", - "type": "text", - "enabled": true - }, - { - "key": "client_secret", - "value": "secret", - "type": "text", - "enabled": true - }, - { - "key": "scope", - "value": "admin", - "type": "text", - "enabled": true - }, - { - "key": "username", - "value": "admin", - "type": "text", - "enabled": true - }, - { - "key": "password", - "value": "admin", - "type": "text", - "enabled": true - }, - { - "key": "grant_type", - "value": "password", - "type": "text", - "enabled": true - } - ], - "rawModeData": null, - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "POST", - "pathVariables": {}, - "url": "http://localhost:5000/admin/configuration", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": "{}", - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" - }, - { - "folder": null, - "id": "c4494401-3985-a5bf-71fb-6e4171384ac6", - "name": "GET http://localhost:5000/posts/1/comments", - "dataMode": "params", - "data": null, - "rawModeData": null, - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "GET", - "pathVariables": {}, - "url": "http://localhost:5000/posts/1/comments", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" - }, - { - "folder": null, - "id": "c45d30d7-d9c4-fa05-8110-d6e769bb6ff9", - "name": "PATCH http://localhost:5000/posts/1", - "dataMode": "raw", - "data": [], - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "PATCH", - "pathVariables": {}, - "url": "http://localhost:5000/posts/1", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", - "rawModeData": "{\n \"title\": \"gfdgsgsdgsdfgsdfgdfg\",\n}" - }, - { - "folder": null, - "id": "e8825dc3-4137-99a7-0000-ef5786610dc3", - "name": "POST http://localhost:5000/posts/1", - "dataMode": "raw", - "data": [], - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "POST", - "pathVariables": {}, - "url": "http://localhost:5000/posts", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", - "rawModeData": "{\n \"userId\": 1,\n \"title\": \"test\",\n \"body\": \"test\"\n}" - }, - { - "folder": null, - "id": "ea0ed57a-2cb9-8acc-47dd-006b8db2f1b2", - "name": "GET http://localhost:5000/posts/1", - "dataMode": "params", - "data": null, - "rawModeData": null, - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "GET", - "pathVariables": {}, - "url": "http://localhost:5000/posts/1", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" - }, - { - "folder": null, - "id": "fddfc4fa-5114-69e3-4744-203ed71a526b", - "name": "PUT http://localhost:5000/posts/1", - "dataMode": "raw", - "data": [], - "descriptionFormat": "html", - "description": "", - "headers": "", - "method": "PUT", - "pathVariables": {}, - "url": "http://localhost:5000/posts/1", - "preRequestScript": null, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", - "rawModeData": "{\n \"userId\": 1,\n \"title\": \"test\",\n \"body\": \"test\"\n}" - } - ] +{ + "id": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", + "name": "Ocelot", + "description": "", + "order": [ + "a1c95935-ed18-d5dc-bcb8-a3db8ba1934f", + "ea0ed57a-2cb9-8acc-47dd-006b8db2f1b2", + "c4494401-3985-a5bf-71fb-6e4171384ac6", + "09af8dda-a9cb-20d2-5ee3-0a3023773a1a", + "e8825dc3-4137-99a7-0000-ef5786610dc3", + "fddfc4fa-5114-69e3-4744-203ed71a526b", + "c45d30d7-d9c4-fa05-8110-d6e769bb6ff9", + "4684c2fa-f38c-c193-5f55-bf563a1978c6", + "5f308240-79e3-cf74-7a6b-fe462f0d54f1", + "178f16da-c61b-c881-1c33-9d64a56851a4", + "26a08569-85f6-7f9a-726f-61be419c7a34" + ], + "folders": [], + "timestamp": 0, + "owner": "212120", + "public": false, + "requests": [ + { + "folder": null, + "id": "09af8dda-a9cb-20d2-5ee3-0a3023773a1a", + "name": "GET http://localhost:5000/comments?postId=1", + "dataMode": "params", + "data": null, + "rawModeData": null, + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "GET", + "pathVariables": {}, + "url": "http://localhost:5000/comments?postId=1", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "id": "178f16da-c61b-c881-1c33-9d64a56851a4", + "headers": "Authorization: Bearer {{AccessToken}}\n", + "url": "http://localhost:5000/administration/configuration", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1508914722969, + "name": "GET http://localhost:5000/admin/configuration", + "description": "", + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "id": "26a08569-85f6-7f9a-726f-61be419c7a34", + "headers": "", + "url": "http://localhost:5000/administration/connect/token", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [ + { + "key": "client_id", + "value": "raft", + "type": "text", + "enabled": true + }, + { + "key": "client_secret", + "value": "REALLYHARDPASSWORD", + "type": "text", + "enabled": true + }, + { + "key": "scope", + "value": "admin raft ", + "type": "text", + "enabled": true + }, + { + "key": "username", + "value": "admin", + "type": "text", + "enabled": false + }, + { + "key": "password", + "value": "secret", + "type": "text", + "enabled": false + }, + { + "key": "grant_type", + "value": "client_credentials", + "type": "text", + "enabled": true + } + ], + "dataMode": "params", + "tests": "var jsonData = JSON.parse(responseBody);\npostman.setGlobalVariable(\"AccessToken\", jsonData.access_token);\npostman.setGlobalVariable(\"RefreshToken\", jsonData.refresh_token);", + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1513240031907, + "name": "POST http://localhost:5000/admin/connect/token copy copy", + "description": "", + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "folder": null, + "id": "4684c2fa-f38c-c193-5f55-bf563a1978c6", + "name": "DELETE http://localhost:5000/posts/1", + "dataMode": "params", + "data": null, + "rawModeData": null, + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "DELETE", + "pathVariables": {}, + "url": "http://localhost:5000/posts/1", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "id": "5f308240-79e3-cf74-7a6b-fe462f0d54f1", + "headers": "Authorization: Bearer {{AccessToken}}\n", + "url": "http://localhost:5000/administration/.well-known/openid-configuration", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": "{}", + "time": 1488038888813, + "name": "GET http://localhost:5000/admin/.well-known/openid-configuration", + "description": "", + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", + "folder": null, + "rawModeData": null, + "descriptionFormat": null, + "queryParams": [], + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "description": "", + "enabled": true + } + ], + "pathVariableData": [] + }, + { + "id": "a1c95935-ed18-d5dc-bcb8-a3db8ba1934f", + "folder": null, + "name": "GET http://localhost:5000/posts", + "dataMode": "params", + "data": [ + { + "key": "client_id", + "value": "admin", + "type": "text", + "enabled": true + }, + { + "key": "client_secret", + "value": "secret", + "type": "text", + "enabled": true + }, + { + "key": "scope", + "value": "admin", + "type": "text", + "enabled": true + }, + { + "key": "username", + "value": "admin", + "type": "text", + "enabled": true + }, + { + "key": "password", + "value": "admin", + "type": "text", + "enabled": true + }, + { + "key": "grant_type", + "value": "password", + "type": "text", + "enabled": true + } + ], + "rawModeData": null, + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "POST", + "pathVariables": {}, + "url": "http://localhost:5000/admin/configuration", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": "{}", + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "folder": null, + "id": "c4494401-3985-a5bf-71fb-6e4171384ac6", + "name": "GET http://localhost:5000/posts/1/comments", + "dataMode": "params", + "data": null, + "rawModeData": null, + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "GET", + "pathVariables": {}, + "url": "http://localhost:5000/posts/1/comments", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "folder": null, + "id": "c45d30d7-d9c4-fa05-8110-d6e769bb6ff9", + "name": "PATCH http://localhost:5000/posts/1", + "dataMode": "raw", + "data": [], + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "PATCH", + "pathVariables": {}, + "url": "http://localhost:5000/posts/1", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", + "rawModeData": "{\n \"title\": \"gfdgsgsdgsdfgsdfgdfg\",\n}" + }, + { + "folder": null, + "id": "e8825dc3-4137-99a7-0000-ef5786610dc3", + "name": "POST http://localhost:5000/posts/1", + "dataMode": "raw", + "data": [], + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "POST", + "pathVariables": {}, + "url": "http://localhost:5000/posts", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", + "rawModeData": "{\n \"userId\": 1,\n \"title\": \"test\",\n \"body\": \"test\"\n}" + }, + { + "folder": null, + "id": "ea0ed57a-2cb9-8acc-47dd-006b8db2f1b2", + "name": "GET http://localhost:5000/posts/1", + "dataMode": "params", + "data": null, + "rawModeData": null, + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "GET", + "pathVariables": {}, + "url": "http://localhost:5000/posts/1", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "folder": null, + "id": "fddfc4fa-5114-69e3-4744-203ed71a526b", + "name": "PUT http://localhost:5000/posts/1", + "dataMode": "raw", + "data": [], + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "PUT", + "pathVariables": {}, + "url": "http://localhost:5000/posts/1", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", + "rawModeData": "{\n \"userId\": 1,\n \"title\": \"test\",\n \"body\": \"test\"\n}" + } + ] } \ No newline at end of file diff --git a/samples/OcelotServiceFabric/.gitignore b/samples/OcelotServiceFabric/.gitignore new file mode 100644 index 00000000..733dbb07 --- /dev/null +++ b/samples/OcelotServiceFabric/.gitignore @@ -0,0 +1,269 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Service fabric +OcelotApplicationApiGatewayPkg/Code +OcelotApplication/OcelotApplicationApiGatewayPkg/Code/appsettings.json +OcelotApplication/OcelotApplicationApiGatewayPkg/Code/configuration.json +OcelotApplication/OcelotApplicationApiGatewayPkg/Code/runtimes/ +OcelotApplicationServicePkg/Code +OcelotApplication/OcelotApplicationApiGatewayPkg/Code/web.config +OcelotApplication/OcelotApplicationServicePkg/Code/runtimes/ +!entryPoint.cmd +!entryPoint.sh + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# Dotnet generated files +*.dll +*.pdb +*.deps.json +*.runtimeconfig.json diff --git a/samples/OcelotServiceFabric/CONTRIBUTING.md b/samples/OcelotServiceFabric/CONTRIBUTING.md new file mode 100644 index 00000000..5577d2d7 --- /dev/null +++ b/samples/OcelotServiceFabric/CONTRIBUTING.md @@ -0,0 +1,11 @@ +# Contributing to Azure samples + +Thank you for your interest in contributing to Azure samples! + +## Ways to contribute + +You can contribute to [Azure samples](https://azure.microsoft.com/documentation/samples/) in a few different ways: + +- Submit feedback on [this sample page](https://azure.microsoft.com/documentation/samples/service-fabric-dotnet-web-reference-app/) whether it was helpful or not. +- Submit issues through [issue tracker](https://github.com/Azure-Samples/service-fabric-dotnet-web-reference-app/issues) on GitHub. We are actively monitoring the issues and improving our samples. +- If you wish to make code changes to samples, or contribute something new, please follow the [GitHub Forks / Pull requests model](https://help.github.com/articles/fork-a-repo/): Fork the sample repo, make the change and propose it back by submitting a pull request. diff --git a/samples/OcelotServiceFabric/LICENSE.md b/samples/OcelotServiceFabric/LICENSE.md new file mode 100644 index 00000000..70aed34a --- /dev/null +++ b/samples/OcelotServiceFabric/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Azure Samples + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/samples/OcelotServiceFabric/OcelotApplication/ApplicationManifest.xml b/samples/OcelotServiceFabric/OcelotApplication/ApplicationManifest.xml new file mode 100644 index 00000000..d002e811 --- /dev/null +++ b/samples/OcelotServiceFabric/OcelotApplication/ApplicationManifest.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Code/entryPoint.cmd b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Code/entryPoint.cmd new file mode 100644 index 00000000..c93389bd --- /dev/null +++ b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Code/entryPoint.cmd @@ -0,0 +1,2 @@ +dotnet %~dp0\OcelotApplicationApiGateway.dll +exit /b %errorlevel% \ No newline at end of file diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Code/entryPoint.sh b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Code/entryPoint.sh new file mode 100644 index 00000000..9cc67287 --- /dev/null +++ b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Code/entryPoint.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +check_errs() +{ + # Function. Parameter 1 is the return code + if [ "${1}" -ne "0" ]; then + # make our script exit with the right error code. + exit ${1} + fi +} + +DIR=`dirname $0` + +echo 0x3f > /proc/self/coredump_filter + +source $DIR/dotnet-include.sh +dotnet $DIR/OcelotApplicationApiGateway.dll $@ +check_errs $? diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Config/Settings.xml b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Config/Settings.xml new file mode 100644 index 00000000..902c747a --- /dev/null +++ b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Config/Settings.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Config/_readme.txt b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Config/_readme.txt new file mode 100644 index 00000000..f5e5e287 --- /dev/null +++ b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Config/_readme.txt @@ -0,0 +1,12 @@ +contains a Settings.xml file, that can specify parameters for the service + +Configuration packages describe user-defined, application-overridable configuration settings (sections of key-value pairs) +required for running service replicas/instances of service types specified in the ser-vice manifest. The configuration settings +must be stored in Settings.xml in the config package folder. + +The service developer uses Service Fabric APIs to locate the package folders and read applica-tion-overridable configuration settings. +The service developer can also register callbacks that are in-voked when any of the configuration packages specified in the +service manifest are upgraded and re-reads new configuration settings inside that callback. + +This means that Service Fabric will not recycle EXEs and DLLHOSTs specified in the host and support packages when +configuration packages are up-graded. \ No newline at end of file diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Data/_readme.txt b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Data/_readme.txt new file mode 100644 index 00000000..de32b7d3 --- /dev/null +++ b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/Data/_readme.txt @@ -0,0 +1,5 @@ +Data packages contain data files like custom dictionaries, +non-overridable configuration files, custom initialized data files, etc. + +Service Fabric will recycle all EXEs and DLLHOSTs specified in the host and support packages when any of the data packages +specified inside service manifest are upgraded. \ No newline at end of file diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest-Linux.xml b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest-Linux.xml new file mode 100644 index 00000000..c1990112 --- /dev/null +++ b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest-Linux.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + entryPoint.sh + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest-Windows.xml b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest-Windows.xml new file mode 100644 index 00000000..a96730d3 --- /dev/null +++ b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest-Windows.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + entryPoint.cmd + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest.xml b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest.xml new file mode 100644 index 00000000..a96730d3 --- /dev/null +++ b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationApiGatewayPkg/ServiceManifest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + entryPoint.cmd + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Code/entryPoint.cmd b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Code/entryPoint.cmd new file mode 100644 index 00000000..9b71795b --- /dev/null +++ b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Code/entryPoint.cmd @@ -0,0 +1,2 @@ +dotnet %~dp0\OcelotApplicationService.dll +exit /b %errorlevel% \ No newline at end of file diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Code/entryPoint.sh b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Code/entryPoint.sh new file mode 100644 index 00000000..9e187faa --- /dev/null +++ b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Code/entryPoint.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +check_errs() +{ + # Function. Parameter 1 is the return code + if [ "${1}" -ne "0" ]; then + # make our script exit with the right error code. + exit ${1} + fi +} + +DIR=`dirname $0` +source $DIR/dotnet-include.sh + +dotnet $DIR/OcelotApplicationService.dll $@ +check_errs $? diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Config/Settings.xml b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Config/Settings.xml new file mode 100644 index 00000000..902c747a --- /dev/null +++ b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Config/Settings.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Config/_readme.txt b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Config/_readme.txt new file mode 100644 index 00000000..f5e5e287 --- /dev/null +++ b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Config/_readme.txt @@ -0,0 +1,12 @@ +contains a Settings.xml file, that can specify parameters for the service + +Configuration packages describe user-defined, application-overridable configuration settings (sections of key-value pairs) +required for running service replicas/instances of service types specified in the ser-vice manifest. The configuration settings +must be stored in Settings.xml in the config package folder. + +The service developer uses Service Fabric APIs to locate the package folders and read applica-tion-overridable configuration settings. +The service developer can also register callbacks that are in-voked when any of the configuration packages specified in the +service manifest are upgraded and re-reads new configuration settings inside that callback. + +This means that Service Fabric will not recycle EXEs and DLLHOSTs specified in the host and support packages when +configuration packages are up-graded. \ No newline at end of file diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Data/_readme.txt b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Data/_readme.txt new file mode 100644 index 00000000..e51dae93 --- /dev/null +++ b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/Data/_readme.txt @@ -0,0 +1,5 @@ +Data packages contain data files like custom dictionaries, +non-overridable configuration files, custom initialized data files, etc. + +Service Fabric will recycle all EXEs and DLLHOSTs specified in the host and support packages when any of the data packages +specified inside service manifest are upgraded. \ No newline at end of file diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest-Linux.xml b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest-Linux.xml new file mode 100644 index 00000000..e94651cb --- /dev/null +++ b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest-Linux.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + entryPoint.sh + + + + + + + + + + + + + + diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest-Windows.xml b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest-Windows.xml new file mode 100644 index 00000000..61c7ac80 --- /dev/null +++ b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest-Windows.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + entryPoint.cmd + + + + + + + + + + + + + + diff --git a/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest.xml b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest.xml new file mode 100644 index 00000000..61c7ac80 --- /dev/null +++ b/samples/OcelotServiceFabric/OcelotApplication/OcelotApplicationServicePkg/ServiceManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + entryPoint.cmd + + + + + + + + + + + + + + diff --git a/samples/OcelotServiceFabric/README.md b/samples/OcelotServiceFabric/README.md new file mode 100644 index 00000000..099348c6 --- /dev/null +++ b/samples/OcelotServiceFabric/README.md @@ -0,0 +1,51 @@ +--- +services: service-fabric +platforms: dotnet +author: raunakpandya edited by Tom Pallister for Ocelot +--- + +# Ocelot Service Fabric example + +This shows a service fabric cluster with Ocelot exposed over HTTP accessing services in the cluster via the naming service. If you want to try and use Ocelot with +Service Fabric I reccomend using this as a starting point. + +Ocelot does not support partitioned services (stateful & actors) at the moment. + +I have not tested this sample on Service Fabric hosted on Linux just a Windows dev cluster. This sample assumes a good understanding of Service Fabric. + +The rest of this document is from the Microsoft asp.net core service fabric getting started guide. + +# Getting started with Service Fabric with .NET Core + +This repository contains a set of simple sample projects to help you getting started with Service Fabric on Linux with .NET Core as the framework. As a pre requisite ensure you have the Service Fabric C# SDK installed on ubuntu box. Follow these instruction to [prepare your development environment on Linux][service-fabric-Linux-getting-started] + +### Folder Hierarchy +* src/ - Source of the application divided by different modules by sub-folders. +* <application package folder>/ - Service Fabric Application folder heirarchy. After compilation the executables are placed in code subfolders. +* build.sh - Script to build source on Linux shell. +* build.ps1 - PowerShell script to build source on Windows. +* install.sh - Script to install Application from Linux shell. +* install.ps1 - PowerShell script to install application from Windows. Before calling this script run Connect-ServiceFabricCluster localhost:19000 or however you prefer to connect. +* uninstall.sh - Script to uninstall application from Linux shell. +* uninstall.ps1 - PowerShell script to unintall application from Windows. +* dotnet-include.sh - Script to conditionally handle RHEL dotnet cli through scl(software collections) + +# Testing + +Once everything is up and running on your dev cluster visit http://localhost:31002/EquipmentInterfaces and you should see the following returned. + +```json +["value1","value2"] +``` + +If you get any errors please check the service fabric logs and let me know if you need help. + +## More information + +The [Service Fabric documentation][service-fabric-docs] includes a rich set of tutorials and conceptual articles, which serve as a good complement to the samples. + + + +[service-fabric-programming-models]: https://azure.microsoft.com/en-us/documentation/articles/service-fabric-choose-framework/ +[service-fabric-docs]: http://aka.ms/servicefabricdocs +[service-fabric-Linux-getting-started]: https://azure.microsoft.com/en-us/documentation/articles/service-fabric-get-started-linux/ diff --git a/samples/OcelotServiceFabric/build.bat b/samples/OcelotServiceFabric/build.bat new file mode 100644 index 00000000..9905715a --- /dev/null +++ b/samples/OcelotServiceFabric/build.bat @@ -0,0 +1,12 @@ +cd ./src/OcelotApplicationService/ +dotnet restore -s https://api.nuget.org/v3/index.json +dotnet build +dotnet publish -o ../../OcelotApplication/OcelotApplicationServicePkg/Code +cd ../../ + +cd ./src/OcelotApplicationApiGateway/ +dotnet restore -s https://api.nuget.org/v3/index.json +dotnet build +dotnet publish -o ../../OcelotApplication/OcelotApplicationApiGatewayPkg/Code +cd ../../ + diff --git a/samples/OcelotServiceFabric/build.sh b/samples/OcelotServiceFabric/build.sh new file mode 100644 index 00000000..a18f2a4a --- /dev/null +++ b/samples/OcelotServiceFabric/build.sh @@ -0,0 +1,15 @@ +#!/bin/bash +DIR=`dirname $0` +source $DIR/dotnet-include.sh + +cd $DIR/src/OcelotApplicationService/ +dotnet restore -s https://api.nuget.org/v3/index.json +dotnet build +dotnet publish -o ../../OcelotApplication/OcelotApplicationServicePkg/Code +cd - + +cd $DIR/src/OcelotApplicationApiGateway/ +dotnet restore -s https://api.nuget.org/v3/index.json +dotnet build +dotnet publish -o ../../OcelotApplication/OcelotApplicationApiGatewayPkg/Code +cd - diff --git a/samples/OcelotServiceFabric/dotnet-include.sh b/samples/OcelotServiceFabric/dotnet-include.sh new file mode 100644 index 00000000..a716f3d8 --- /dev/null +++ b/samples/OcelotServiceFabric/dotnet-include.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +. /etc/os-release +linuxDistrib=$ID +if [ $linuxDistrib = "rhel" ]; then + source scl_source enable rh-dotnet20 + exitCode=$? + if [ $exitCode != 0 ]; then + echo "Failed: source scl_source enable rh-dotnet20 : ExitCode: $exitCode" + exit $exitCode + fi +fi diff --git a/samples/OcelotServiceFabric/install.ps1 b/samples/OcelotServiceFabric/install.ps1 new file mode 100644 index 00000000..662c59e3 --- /dev/null +++ b/samples/OcelotServiceFabric/install.ps1 @@ -0,0 +1,20 @@ +$AppPath = "$PSScriptRoot\OcelotApplication" +$sdkInstallPath = (Get-ItemProperty 'HKLM:\Software\Microsoft\Service Fabric SDK').FabricSDKInstallPath +$sfSdkPsModulePath = $sdkInstallPath + "Tools\PSModule\ServiceFabricSDK" +Import-Module $sfSdkPsModulePath\ServiceFabricSDK.psm1 + +$StatefulServiceManifestlocation = $AppPath + "\OcelotApplicationServicePkg\" +$StatefulServiceManifestlocationLinux = $StatefulServiceManifestlocation + "\ServiceManifest-Linux.xml" +$StatefulServiceManifestlocationWindows = $StatefulServiceManifestlocation + "\ServiceManifest-Windows.xml" +$StatefulServiceManifestlocationFinal= $StatefulServiceManifestlocation + "ServiceManifest.xml" +Copy-Item -Path $StatefulServiceManifestlocationWindows -Destination $StatefulServiceManifestlocationFinal -Force + +$WebServiceManifestlocation = $AppPath + "\OcelotApplicationApiGatewayPkg\" +$WebServiceManifestlocationLinux = $WebServiceManifestlocation + "\ServiceManifest-Linux.xml" +$WebServiceManifestlocationWindows = $WebServiceManifestlocation + "\ServiceManifest-Windows.xml" +$WebServiceManifestlocationFinal= $WebServiceManifestlocation + "ServiceManifest.xml" +Copy-Item -Path $WebServiceManifestlocationWindows -Destination $WebServiceManifestlocationFinal -Force + +Copy-ServiceFabricApplicationPackage -ApplicationPackagePath $AppPath -ApplicationPackagePathInImageStore OcelotServiceApplicationType -ImageStoreConnectionString (Get-ImageStoreConnectionStringFromClusterManifest(Get-ServiceFabricClusterManifest)) -TimeoutSec 1800 +Register-ServiceFabricApplicationType OcelotServiceApplicationType +New-ServiceFabricApplication fabric:/OcelotServiceApplication OcelotServiceApplicationType 1.0.0 \ No newline at end of file diff --git a/samples/OcelotServiceFabric/install.sh b/samples/OcelotServiceFabric/install.sh new file mode 100644 index 00000000..e8245579 --- /dev/null +++ b/samples/OcelotServiceFabric/install.sh @@ -0,0 +1,22 @@ +#!/bin/bash +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +appPkg="$DIR/OcelotServiceApplication" + +WebServiceManifestlocation="$appPkg/OcelotApplicationApiGatewayPkg" +WebServiceManifestlocationLinux="$WebServiceManifestlocation/ServiceManifest-Linux.xml" +WebServiceManifestlocationWindows="$WebServiceManifestlocation/ServiceManifest-Windows.xml" +WebServiceManifestlocation="$WebServiceManifestlocation/ServiceManifest.xml" +cp $WebServiceManifestlocationLinux $WebServiceManifestlocation + + +StatefulServiceManifestlocation="$appPkg/OcelotApplicationServicePkg" +StatefulServiceManifestlocationLinux="$StatefulServiceManifestlocation/ServiceManifest-Linux.xml" +StatefulServiceManifestlocationWindows="$StatefulServiceManifestlocation/ServiceManifest-Windows.xml" +StatefulServiceManifestlocation="$StatefulServiceManifestlocation/ServiceManifest.xml" +cp $StatefulServiceManifestlocationLinux $StatefulServiceManifestlocation +cp dotnet-include.sh ./OcelotServiceApplication/OcelotApplicationServicePkg/Code +cp dotnet-include.sh ./OcelotServiceApplication/OcelotApplicationApiGatewayPkg/Code +sfctl application upload --path OcelotServiceApplication --show-progress +sfctl application provision --application-type-build-path OcelotServiceApplication +sfctl application create --app-name fabric:/OcelotServiceApplication --app-type OcelotServiceApplicationType --app-version 1.0.0 diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.cs b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.cs new file mode 100644 index 00000000..28ea7abb --- /dev/null +++ b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.cs @@ -0,0 +1,31 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +namespace OcelotApplicationApiGateway +{ + using System.Fabric; + using Microsoft.ServiceFabric.Services.Communication.Runtime; + using Microsoft.ServiceFabric.Services.Runtime; + using System.Collections.Generic; + + /// Service that handles front-end web requests and acts as a proxy to the back-end data for the UI web page. + /// It is a stateless service that hosts a Web API application on OWIN. + internal sealed class OcelotServiceWebService : StatelessService + { + public OcelotServiceWebService(StatelessServiceContext context) + : base(context) + { } + + protected override IEnumerable CreateServiceInstanceListeners() + { + return new[] + { + new ServiceInstanceListener( + initparams => new WebCommunicationListener(string.Empty, initparams), + "OcelotServiceWebListener") + }; + } + } +} \ No newline at end of file diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.csproj b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.csproj new file mode 100644 index 00000000..1ff69c09 --- /dev/null +++ b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.csproj @@ -0,0 +1,22 @@ + + + Stateless Web Service for Stateful OcelotApplicationApiGateway App + + netcoreapp2.0 + OcelotApplicationApiGateway + Exe + OcelotApplicationApiGateway + + + + PreserveNewest + + + + + + + + + + \ No newline at end of file diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/Program.cs b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/Program.cs new file mode 100644 index 00000000..7da30a50 --- /dev/null +++ b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/Program.cs @@ -0,0 +1,51 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +namespace OcelotApplicationApiGateway + +{ + using System; + using System.Fabric; + using System.Threading; + using Microsoft.ServiceFabric.Services.Runtime; + using System.Diagnostics.Tracing; + + + /// + /// The service host is the executable that hosts the Service instances. + /// + public class Program + { + public static void Main(string[] args) + { + // Create Service Fabric runtime and register the service type. + try + { + + //Creating a new event listener to redirect the traces to a file + ServiceEventListener listener = new ServiceEventListener("OcelotApplicationApiGateway"); + listener.EnableEvents(ServiceEventSource.Current, EventLevel.LogAlways, EventKeywords.All); + + // The ServiceManifest.XML file defines one or more service type names. + // Registering a service maps a service type name to a .NET type. + // When Service Fabric creates an instance of this service type, + // an instance of the class is created in this host process. + ServiceRuntime + .RegisterServiceAsync("OcelotApplicationApiGatewayType", context => new OcelotServiceWebService (context)) + .GetAwaiter() + .GetResult(); + + + // Prevents this host process from terminating so services keep running. + Thread.Sleep(Timeout.Infinite); + } + catch (Exception ex) + { + ServiceEventSource.Current.ServiceHostInitializationFailed(ex); + throw ex; + } + } + } +} \ No newline at end of file diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/Properties/launchSettings.json b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/Properties/launchSettings.json new file mode 100644 index 00000000..e28c3315 --- /dev/null +++ b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:36034/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "OcelotApplicationApiGateway": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:36035/" + } + } +} \ No newline at end of file diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ServiceEventListener.cs b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ServiceEventListener.cs new file mode 100644 index 00000000..555f9fde --- /dev/null +++ b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ServiceEventListener.cs @@ -0,0 +1,94 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +namespace OcelotApplicationApiGateway +{ + using System; + using System.IO; + using System.Linq; + using System.Diagnostics.Tracing; + using System.Fabric; + using System.Fabric.Common; + using System.Fabric.Common.Tracing; + using Microsoft.ServiceFabric.Services.Runtime; + using System.Globalization; + using System.Text; + + /// + /// ServiceEventListener is a class which listens to the eventsources registered and redirects the traces to a file + /// Note that this class serves as a template to EventListener class and redirects the logs to /tmp/{appnameyyyyMMddHHmmssffff}. + /// You can extend the functionality by writing your code to implement rolling logs for the logs written through this class. + /// You can also write your custom listener class and handle the registered evestsources accordingly. + /// + internal class ServiceEventListener : EventListener + { + private string fileName; + private string filepath = Path.GetTempPath(); + + public ServiceEventListener(string appName) + { + this.fileName = appName + DateTime.Now.ToString("yyyyMMddHHmmssffff"); + } + + /// + /// We override this method to get a callback on every event we subscribed to with EnableEvents + /// + /// The event arguments that describe the event. + protected override void OnEventWritten(EventWrittenEventArgs eventData) + { + using (StreamWriter writer = new StreamWriter( new FileStream(filepath + fileName, FileMode.Append))) + { + // report all event information + writer.Write(" {0} ", Write(eventData.Task.ToString(), + eventData.EventName, + eventData.EventId.ToString(), + eventData.Level)); + + if (eventData.Message != null) + { + writer.WriteLine(string.Format(CultureInfo.InvariantCulture, eventData.Message, eventData.Payload.ToArray())); + } + } + } + + private static String Write(string taskName, string eventName, string id, EventLevel level) + { + StringBuilder output = new StringBuilder(); + + DateTime now = DateTime.UtcNow; + output.Append(now.ToString("yyyy/MM/dd-HH:mm:ss.fff", CultureInfo.InvariantCulture)); + output.Append(','); + output.Append(ConvertLevelToString(level)); + output.Append(','); + output.Append(taskName); + + if (!string.IsNullOrEmpty(eventName)) + { + output.Append('.'); + output.Append(eventName); + } + + if (!string.IsNullOrEmpty(id)) + { + output.Append('@'); + output.Append(id); + } + + output.Append(','); + return output.ToString(); + } + + private static string ConvertLevelToString(EventLevel level) + { + switch (level) + { + case EventLevel.Informational: + return "Info"; + default: + return level.ToString(); + } + } + } +} \ No newline at end of file diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ServiceEventSource.cs b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ServiceEventSource.cs new file mode 100644 index 00000000..c0579955 --- /dev/null +++ b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ServiceEventSource.cs @@ -0,0 +1,86 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +namespace OcelotApplicationApiGateway +{ + using System; + using System.Diagnostics.Tracing; + using System.Fabric; + using Microsoft.ServiceFabric.Services.Runtime; + + /// + /// Implements methods for logging service related events. + /// + public class ServiceEventSource : EventSource + { + public static ServiceEventSource Current = new ServiceEventSource(); + + // Define an instance method for each event you want to record and apply an [Event] attribute to it. + // The method name is the name of the event. + // Pass any parameters you want to record with the event (only primitive integer types, DateTime, Guid & string are allowed). + // Each event method implementation should check whether the event source is enabled, and if it is, call WriteEvent() method to raise the event. + // The number and types of arguments passed to every event method must exactly match what is passed to WriteEvent(). + // Put [NonEvent] attribute on all methods that do not define an event. + // For more information see https://msdn.microsoft.com/en-us/library/system.diagnostics.tracing.eventsource.aspx + + [NonEvent] + public void Message(string message, params object[] args) + { + if (this.IsEnabled()) + { + var finalMessage = string.Format(message, args); + this.Message(finalMessage); + } + } + + private const int MessageEventId = 1; + + [Event(MessageEventId, Level = EventLevel.Informational, Message = "{0}")] + public void Message(string message) + { + if (this.IsEnabled()) + { + this.WriteEvent(MessageEventId, message); + } + } + + private const int ServiceTypeRegisteredEventId = 3; + + [Event(ServiceTypeRegisteredEventId, Level = EventLevel.Informational, Message = "Service host process {0} registered service type {1}")] + public void ServiceTypeRegistered(int hostProcessId, string serviceType) + { + this.WriteEvent(ServiceTypeRegisteredEventId, hostProcessId, serviceType); + } + + [NonEvent] + public void ServiceHostInitializationFailed(Exception e) + { + this.ServiceHostInitializationFailed(e.ToString()); + } + + private const int ServiceHostInitializationFailedEventId = 4; + + [Event(ServiceHostInitializationFailedEventId, Level = EventLevel.Error, Message = "Service host initialization failed: {0}")] + private void ServiceHostInitializationFailed(string exception) + { + this.WriteEvent(ServiceHostInitializationFailedEventId, exception); + } + + [NonEvent] + public void ServiceWebHostBuilderFailed(Exception e) + { + this.ServiceWebHostBuilderFailed(e.ToString()); + } + + private const int ServiceWebHostBuilderFailedEventId = 5; + + [Event(ServiceWebHostBuilderFailedEventId, Level = EventLevel.Error, Message = "Service Owin Web Host Builder Failed: {0}")] + private void ServiceWebHostBuilderFailed(string exception) + { + this.WriteEvent(ServiceWebHostBuilderFailedEventId, exception); + } + + } +} diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/WebCommunicationListener.cs b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/WebCommunicationListener.cs new file mode 100644 index 00000000..6685e11f --- /dev/null +++ b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/WebCommunicationListener.cs @@ -0,0 +1,125 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +namespace OcelotApplicationApiGateway +{ + using System; + using System.Fabric; + using System.Fabric.Description; + using System.Globalization; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Hosting; + using Microsoft.ServiceFabric.Services.Communication.Runtime; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Configuration; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using Ocelot.DependencyInjection; + using Ocelot.Middleware; + + public class WebCommunicationListener : ICommunicationListener + { + private readonly string appRoot; + private readonly ServiceContext serviceInitializationParameters; + private string listeningAddress; + private string publishAddress; + + // OWIN server handle. + private IWebHost webHost; + + public WebCommunicationListener(string appRoot, ServiceContext serviceInitializationParameters) + { + this.appRoot = appRoot; + this.serviceInitializationParameters = serviceInitializationParameters; + } + + public Task OpenAsync(CancellationToken cancellationToken) + { + ServiceEventSource.Current.Message("Initialize"); + + EndpointResourceDescription serviceEndpoint = this.serviceInitializationParameters.CodePackageActivationContext.GetEndpoint("WebEndpoint"); + int port = serviceEndpoint.Port; + + this.listeningAddress = string.Format( + CultureInfo.InvariantCulture, + "http://+:{0}/{1}", + port, + string.IsNullOrWhiteSpace(this.appRoot) + ? string.Empty + : this.appRoot.TrimEnd('/') + '/'); + + this.publishAddress = this.listeningAddress.Replace("+", FabricRuntime.GetNodeContext().IPAddressOrFQDN); + + ServiceEventSource.Current.Message("Starting web server on {0}", this.listeningAddress); + + try + { + this.webHost = new WebHostBuilder() + .UseKestrel() + //.UseStartup() + .UseUrls(this.listeningAddress) + .ConfigureAppConfiguration((hostingContext, config) => + { + config + .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) + .AddJsonFile("appsettings.json", true, true) + .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) + .AddJsonFile("configuration.json") + .AddEnvironmentVariables(); + }) + .ConfigureLogging((hostingContext, logging) => + { + logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + logging.AddConsole(); + }) + .ConfigureServices(s => { + s.AddOcelot(); + }) + .Configure(a => { + a.UseOcelot().Wait(); + }) + .Build(); + + this.webHost.Start(); + } + catch (Exception ex) + { + ServiceEventSource.Current.ServiceWebHostBuilderFailed(ex); + } + + return Task.FromResult(this.publishAddress); + } + + public Task CloseAsync(CancellationToken cancellationToken) + { + this.StopAll(); + return Task.FromResult(true); + } + + public void Abort() + { + this.StopAll(); + } + + /// + /// Stops, cancels, and disposes everything. + /// + private void StopAll() + { + try + { + if (this.webHost != null) + { + ServiceEventSource.Current.Message("Stopping web server."); + this.webHost.Dispose(); + } + } + catch (ObjectDisposedException) + { + } + } + } +} diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/appsettings.json b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/appsettings.json new file mode 100644 index 00000000..51d92491 --- /dev/null +++ b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Trace", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/configuration.json b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/configuration.json new file mode 100644 index 00000000..dce3d55e --- /dev/null +++ b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/configuration.json @@ -0,0 +1,22 @@ +{ + "ReRoutes": [ + { + "DownstreamPathTemplate": "/api/values", + "UpstreamPathTemplate": "/EquipmentInterfaces", + "UpstreamHttpMethod": [ + "Get" + ], + "DownstreamScheme": "http", + "ServiceName": "OcelotServiceApplication/OcelotApplicationService", + "UseServiceDiscovery" : true + } + ], + "GlobalConfiguration": { + "RequestIdKey": "OcRequestId", + "ServiceDiscoveryProvider": { + "Host": "localhost", + "Port": 19081, + "Type": "ServiceFabric" + } + } +} diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationService/ApiGateway.cs b/samples/OcelotServiceFabric/src/OcelotApplicationService/ApiGateway.cs new file mode 100644 index 00000000..8c090885 --- /dev/null +++ b/samples/OcelotServiceFabric/src/OcelotApplicationService/ApiGateway.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Fabric; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.ServiceFabric.Services.Communication.AspNetCore; +using Microsoft.ServiceFabric.Services.Communication.Runtime; +using Microsoft.ServiceFabric.Services.Runtime; +using Microsoft.Extensions.Logging; + +namespace OcelotApplicationService +{ + /// + /// The FabricRuntime creates an instance of this class for each service type instance. + /// + internal sealed class ApiGateway : StatelessService + { + public ApiGateway(StatelessServiceContext context) + : base(context) + { } + + /// + /// Optional override to create listeners (like tcp, http) for this service instance. + /// + /// The collection of listeners. + protected override IEnumerable CreateServiceInstanceListeners() + { + return new ServiceInstanceListener[] + { + new ServiceInstanceListener(serviceContext => + new KestrelCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) => + { + Console.WriteLine($"Starting Kestrel on {url}"); + ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting Kestrel on {url}"); + + return new WebHostBuilder() + .UseKestrel() + .ConfigureServices( + services => services + .AddSingleton(serviceContext)) + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.UseUniqueServiceUrl) + .UseStartup() + .UseUrls(url) + .ConfigureLogging((hostingContext, logging) => + { + logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + logging.AddConsole(); + }) + .Build(); + })) + }; + } + } +} diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationService/Controllers/ValuesController.cs b/samples/OcelotServiceFabric/src/OcelotApplicationService/Controllers/ValuesController.cs new file mode 100644 index 00000000..13e56999 --- /dev/null +++ b/samples/OcelotServiceFabric/src/OcelotApplicationService/Controllers/ValuesController.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace OcelotApplicationService.Controllers +{ + [Route("api/[controller]")] + public class ValuesController : Controller + { + // GET api/values + [HttpGet] + public IEnumerable Get() + { + return new string[] { "value1", "value2" }; + } + + // GET api/values/5 + [HttpGet("{id}")] + public string Get(int id) + { + return "value"; + } + + // POST api/values + [HttpPost] + public void Post([FromBody]string value) + { + } + + // PUT api/values/5 + [HttpPut("{id}")] + public void Put(int id, [FromBody]string value) + { + } + + // DELETE api/values/5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } + } +} diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationService/OcelotApplicationService.csproj b/samples/OcelotServiceFabric/src/OcelotApplicationService/OcelotApplicationService.csproj new file mode 100644 index 00000000..34991440 --- /dev/null +++ b/samples/OcelotServiceFabric/src/OcelotApplicationService/OcelotApplicationService.csproj @@ -0,0 +1,21 @@ + + + Stateless Service Application + + Exe + netcoreapp2.0 + OcelotApplicationService + OcelotApplicationService + $(PackageTargetFallback) + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationService/Program.cs b/samples/OcelotServiceFabric/src/OcelotApplicationService/Program.cs new file mode 100644 index 00000000..b9d7d72e --- /dev/null +++ b/samples/OcelotServiceFabric/src/OcelotApplicationService/Program.cs @@ -0,0 +1,38 @@ +using Microsoft.ServiceFabric.Services.Runtime; +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +namespace OcelotApplicationService +{ + internal static class Program + { + /// + /// This is the entry point of the service host process. + /// + private static void Main() + { + try + { + // The ServiceManifest.XML file defines one or more service type names. + // Registering a service maps a service type name to a .NET type. + // When Service Fabric creates an instance of this service type, + // an instance of the class is created in this host process. + + ServiceRuntime.RegisterServiceAsync("OcelotApplicationServiceType", + context => new ApiGateway(context)).GetAwaiter().GetResult(); + + ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(ApiGateway).Name); + + // Prevents this host process from terminating so services keeps running. + Thread.Sleep(Timeout.Infinite); + } + catch (Exception e) + { + ServiceEventSource.Current.ServiceHostInitializationFailed(e.ToString()); + throw; + } + } + } +} diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationService/ServiceEventSource.cs b/samples/OcelotServiceFabric/src/OcelotApplicationService/ServiceEventSource.cs new file mode 100644 index 00000000..13e2df12 --- /dev/null +++ b/samples/OcelotServiceFabric/src/OcelotApplicationService/ServiceEventSource.cs @@ -0,0 +1,189 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Tracing; +using System.Fabric; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OcelotApplicationService +{ + [EventSource(Name = "MyCompany-ServiceOcelotApplication-OcelotService")] + internal sealed class ServiceEventSource : EventSource + { + public static readonly ServiceEventSource Current = new ServiceEventSource(); + + static ServiceEventSource() + { + // A workaround for the problem where ETW activities do not get tracked until Tasks infrastructure is initialized. + // This problem will be fixed in .NET Framework 4.6.2. + Task.Run(() => { }); + } + + // Instance constructor is private to enforce singleton semantics + private ServiceEventSource() : base() { } + + #region Keywords + // Event keywords can be used to categorize events. + // Each keyword is a bit flag. A single event can be associated with multiple keywords (via EventAttribute.Keywords property). + // Keywords must be defined as a public class named 'Keywords' inside EventSource that uses them. + public static class Keywords + { + public const EventKeywords Requests = (EventKeywords)0x1L; + public const EventKeywords ServiceInitialization = (EventKeywords)0x2L; + } + #endregion + + #region Events + // Define an instance method for each event you want to record and apply an [Event] attribute to it. + // The method name is the name of the event. + // Pass any parameters you want to record with the event (only primitive integer types, DateTime, Guid & string are allowed). + // Each event method implementation should check whether the event source is enabled, and if it is, call WriteEvent() method to raise the event. + // The number and types of arguments passed to every event method must exactly match what is passed to WriteEvent(). + // Put [NonEvent] attribute on all methods that do not define an event. + // For more information see https://msdn.microsoft.com/en-us/library/system.diagnostics.tracing.eventsource.aspx + + [NonEvent] + public void Message(string message, params object[] args) + { + if (this.IsEnabled()) + { + string finalMessage = string.Format(message, args); + Message(finalMessage); + } + } + + private const int MessageEventId = 1; + [Event(MessageEventId, Level = EventLevel.Informational, Message = "{0}")] + public void Message(string message) + { + if (this.IsEnabled()) + { + WriteEvent(MessageEventId, message); + } + } + + [NonEvent] + public void ServiceMessage(ServiceContext serviceContext, string message, params object[] args) + { + if (this.IsEnabled()) + { + + string finalMessage = string.Format(message, args); + ServiceMessage( + serviceContext.ServiceName.ToString(), + serviceContext.ServiceTypeName, + GetReplicaOrInstanceId(serviceContext), + serviceContext.PartitionId, + serviceContext.CodePackageActivationContext.ApplicationName, + serviceContext.CodePackageActivationContext.ApplicationTypeName, + serviceContext.NodeContext.NodeName, + finalMessage); + } + } + + // For very high-frequency events it might be advantageous to raise events using WriteEventCore API. + // This results in more efficient parameter handling, but requires explicit allocation of EventData structure and unsafe code. + // To enable this code path, define UNSAFE conditional compilation symbol and turn on unsafe code support in project properties. + private const int ServiceMessageEventId = 2; + [Event(ServiceMessageEventId, Level = EventLevel.Informational, Message = "{7}")] + private +#if UNSAFE + unsafe +#endif + void ServiceMessage( + string serviceName, + string serviceTypeName, + long replicaOrInstanceId, + Guid partitionId, + string applicationName, + string applicationTypeName, + string nodeName, + string message) + { +#if !UNSAFE + WriteEvent(ServiceMessageEventId, serviceName, serviceTypeName, replicaOrInstanceId, partitionId, applicationName, applicationTypeName, nodeName, message); +#else + const int numArgs = 8; + fixed (char* pServiceName = serviceName, pServiceTypeName = serviceTypeName, pApplicationName = applicationName, pApplicationTypeName = applicationTypeName, pNodeName = nodeName, pMessage = message) + { + EventData* eventData = stackalloc EventData[numArgs]; + eventData[0] = new EventData { DataPointer = (IntPtr) pServiceName, Size = SizeInBytes(serviceName) }; + eventData[1] = new EventData { DataPointer = (IntPtr) pServiceTypeName, Size = SizeInBytes(serviceTypeName) }; + eventData[2] = new EventData { DataPointer = (IntPtr) (&replicaOrInstanceId), Size = sizeof(long) }; + eventData[3] = new EventData { DataPointer = (IntPtr) (&partitionId), Size = sizeof(Guid) }; + eventData[4] = new EventData { DataPointer = (IntPtr) pApplicationName, Size = SizeInBytes(applicationName) }; + eventData[5] = new EventData { DataPointer = (IntPtr) pApplicationTypeName, Size = SizeInBytes(applicationTypeName) }; + eventData[6] = new EventData { DataPointer = (IntPtr) pNodeName, Size = SizeInBytes(nodeName) }; + eventData[7] = new EventData { DataPointer = (IntPtr) pMessage, Size = SizeInBytes(message) }; + + WriteEventCore(ServiceMessageEventId, numArgs, eventData); + } +#endif + } + + private const int ServiceTypeRegisteredEventId = 3; + [Event(ServiceTypeRegisteredEventId, Level = EventLevel.Informational, Message = "Service host process {0} registered service type {1}", Keywords = Keywords.ServiceInitialization)] + public void ServiceTypeRegistered(int hostProcessId, string serviceType) + { + WriteEvent(ServiceTypeRegisteredEventId, hostProcessId, serviceType); + } + + private const int ServiceHostInitializationFailedEventId = 4; + [Event(ServiceHostInitializationFailedEventId, Level = EventLevel.Error, Message = "Service host initialization failed", Keywords = Keywords.ServiceInitialization)] + public void ServiceHostInitializationFailed(string exception) + { + WriteEvent(ServiceHostInitializationFailedEventId, exception); + } + + // A pair of events sharing the same name prefix with a "Start"/"Stop" suffix implicitly marks boundaries of an event tracing activity. + // These activities can be automatically picked up by debugging and profiling tools, which can compute their execution time, child activities, + // and other statistics. + private const int ServiceRequestStartEventId = 5; + [Event(ServiceRequestStartEventId, Level = EventLevel.Informational, Message = "Service request '{0}' started", Keywords = Keywords.Requests)] + public void ServiceRequestStart(string requestTypeName) + { + WriteEvent(ServiceRequestStartEventId, requestTypeName); + } + + private const int ServiceRequestStopEventId = 6; + [Event(ServiceRequestStopEventId, Level = EventLevel.Informational, Message = "Service request '{0}' finished", Keywords = Keywords.Requests)] + public void ServiceRequestStop(string requestTypeName, string exception = "") + { + WriteEvent(ServiceRequestStopEventId, requestTypeName, exception); + } + #endregion + + #region Private methods + private static long GetReplicaOrInstanceId(ServiceContext context) + { + StatelessServiceContext stateless = context as StatelessServiceContext; + if (stateless != null) + { + return stateless.InstanceId; + } + + StatefulServiceContext stateful = context as StatefulServiceContext; + if (stateful != null) + { + return stateful.ReplicaId; + } + + throw new NotSupportedException("Context type not supported."); + } +#if UNSAFE + private int SizeInBytes(string s) + { + if (s == null) + { + return 0; + } + else + { + return (s.Length + 1) * sizeof(char); + } + } +#endif + #endregion + } +} diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationService/Startup.cs b/samples/OcelotServiceFabric/src/OcelotApplicationService/Startup.cs new file mode 100644 index 00000000..022fcb0e --- /dev/null +++ b/samples/OcelotServiceFabric/src/OcelotApplicationService/Startup.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace OcelotApplicationService +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseMvc(); + } + } +} diff --git a/samples/OcelotServiceFabric/src/global.json b/samples/OcelotServiceFabric/src/global.json new file mode 100644 index 00000000..8288598a --- /dev/null +++ b/samples/OcelotServiceFabric/src/global.json @@ -0,0 +1,6 @@ +{ + "projects": [ "../"], + "sdk": { + "version": "2.1.4" + } +} \ No newline at end of file diff --git a/samples/OcelotServiceFabric/uninstall.ps1 b/samples/OcelotServiceFabric/uninstall.ps1 new file mode 100644 index 00000000..032ee0e8 --- /dev/null +++ b/samples/OcelotServiceFabric/uninstall.ps1 @@ -0,0 +1,2 @@ +Remove-ServiceFabricApplication fabric:/OcelotServiceApplication +Unregister-ServiceFabricApplicationType OcelotServiceApplicationType 1.0.0 \ No newline at end of file diff --git a/samples/OcelotServiceFabric/uninstall.sh b/samples/OcelotServiceFabric/uninstall.sh new file mode 100644 index 00000000..45fc561c --- /dev/null +++ b/samples/OcelotServiceFabric/uninstall.sh @@ -0,0 +1,5 @@ +#!/bin/bash -x + +sfctl application delete --application-id OcelotServiceApplication +sfctl application unprovision --application-type-name OcelotServiceApplicationType --application-type-version 1.0.0 +sfctl store delete --content-path OcelotServiceApplication \ No newline at end of file diff --git a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs index bc860424..0e7509b9 100644 --- a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs @@ -36,7 +36,6 @@ namespace Ocelot.Configuration.Builder private readonly List _downstreamAddresses; private string _upstreamHost; private string _key; - public DownstreamReRouteBuilder() { _downstreamAddresses = new List(); diff --git a/src/Ocelot/Configuration/Builder/ServiceProviderConfigurationBuilder.cs b/src/Ocelot/Configuration/Builder/ServiceProviderConfigurationBuilder.cs index 0999a47c..b1f4e832 100644 --- a/src/Ocelot/Configuration/Builder/ServiceProviderConfigurationBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ServiceProviderConfigurationBuilder.cs @@ -4,6 +4,7 @@ namespace Ocelot.Configuration.Builder { private string _serviceDiscoveryProviderHost; private int _serviceDiscoveryProviderPort; + private string _type; public ServiceProviderConfigurationBuilder WithServiceDiscoveryProviderHost(string serviceDiscoveryProviderHost) { @@ -17,9 +18,15 @@ namespace Ocelot.Configuration.Builder return this; } + public ServiceProviderConfigurationBuilder WithServiceDiscoveryProviderType(string type) + { + _type = type; + return this; + } + public ServiceProviderConfiguration Build() { - return new ServiceProviderConfiguration(_serviceDiscoveryProviderHost,_serviceDiscoveryProviderPort); + return new ServiceProviderConfiguration(_type, _serviceDiscoveryProviderHost, _serviceDiscoveryProviderPort); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs index cfc3e7c7..c104385a 100644 --- a/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs @@ -7,12 +7,14 @@ namespace Ocelot.Configuration.Creator { public ServiceProviderConfiguration Create(FileGlobalConfiguration globalConfiguration) { + //todo log or return error here dont just default to something that wont work.. var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; return new ServiceProviderConfigurationBuilder() - .WithServiceDiscoveryProviderHost(globalConfiguration?.ServiceDiscoveryProvider?.Host) - .WithServiceDiscoveryProviderPort(serviceProviderPort) - .Build(); + .WithServiceDiscoveryProviderHost(globalConfiguration?.ServiceDiscoveryProvider?.Host) + .WithServiceDiscoveryProviderPort(serviceProviderPort) + .WithServiceDiscoveryProviderType(globalConfiguration?.ServiceDiscoveryProvider?.Type) + .Build(); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs b/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs index f9c370b1..203cc675 100644 --- a/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs +++ b/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs @@ -4,5 +4,6 @@ namespace Ocelot.Configuration.File { public string Host {get;set;} public int Port { get; set; } + public string Type { get; set; } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs index 91b4bd9d..13c128b9 100644 --- a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs @@ -18,8 +18,8 @@ namespace Ocelot.Configuration.Repository public ConsulFileConfigurationRepository(Cache.IOcelotCache cache, ServiceProviderConfiguration serviceProviderConfig) { - var consulHost = string.IsNullOrEmpty(serviceProviderConfig?.ServiceProviderHost) ? "localhost" : serviceProviderConfig?.ServiceProviderHost; - var consulPort = serviceProviderConfig?.ServiceProviderPort ?? 8500; + var consulHost = string.IsNullOrEmpty(serviceProviderConfig?.Host) ? "localhost" : serviceProviderConfig?.Host; + var consulPort = serviceProviderConfig?.Port ?? 8500; var configuration = new ConsulRegistryConfiguration(consulHost, consulPort, _ocelotConfiguration); _cache = cache; _consul = new ConsulClient(c => @@ -76,4 +76,4 @@ namespace Ocelot.Configuration.Repository return new ErrorResponse(new UnableToSetConfigInConsulError($"Unable to set FileConfiguration in consul, response status code from consul was {result.StatusCode}")); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Configuration/ServiceProviderConfiguraion.cs b/src/Ocelot/Configuration/ServiceProviderConfiguraion.cs index 272d2205..9df16ea2 100644 --- a/src/Ocelot/Configuration/ServiceProviderConfiguraion.cs +++ b/src/Ocelot/Configuration/ServiceProviderConfiguraion.cs @@ -2,13 +2,15 @@ { public class ServiceProviderConfiguration { - public ServiceProviderConfiguration(string serviceProviderHost, int serviceProviderPort) + public ServiceProviderConfiguration(string type, string host, int port) { - ServiceProviderHost = serviceProviderHost; - ServiceProviderPort = serviceProviderPort; + Host = host; + Port = port; + Type = type; } - public string ServiceProviderHost { get; private set; } - public int ServiceProviderPort { get; private set; } + public string Host { get; private set; } + public int Port { get; private set; } + public string Type { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs index 58da7c4f..d0c6de0e 100644 --- a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs +++ b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs @@ -30,7 +30,7 @@ namespace Ocelot.Configuration.Validator RuleForEach(configuration => configuration.Aggregates) .Must((config, aggregateReRoute) => AllReRoutesForAggregateExist(aggregateReRoute, config.ReRoutes)) - .WithMessage((config, aggregateReRoute) => $"ReRoutes for {nameof(aggregateReRoute)} {aggregateReRoute.UpstreamPathTemplate} either do not exist or do not have correct Key property"); + .WithMessage((config, aggregateReRoute) => $"ReRoutes for {nameof(aggregateReRoute)} {aggregateReRoute.UpstreamPathTemplate} either do not exist or do not have correct ServiceName property"); RuleForEach(configuration => configuration.Aggregates) .Must((config, aggregateReRoute) => DoesNotContainReRoutesWithSpecificRequestIdKeys(aggregateReRoute, config.ReRoutes)) diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index f4192713..ccd2a9be 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -99,7 +99,6 @@ namespace Ocelot.DependencyInjection _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); - _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); diff --git a/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs b/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs index 19fa55a1..9975cec2 100644 --- a/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs +++ b/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs @@ -1,4 +1,5 @@ -using Ocelot.Responses; +/* +using Ocelot.Responses; using Ocelot.Values; namespace Ocelot.DownstreamUrlCreator @@ -8,3 +9,4 @@ namespace Ocelot.DownstreamUrlCreator Response Build(string downstreamPath, string downstreamScheme, ServiceHostAndPort downstreamHostAndPort); } } +*/ diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index 5b5e8346..dc02a20e 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -5,6 +5,7 @@ using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; using System; +using System.Linq; using Ocelot.DownstreamRouteFinder.Middleware; namespace Ocelot.DownstreamUrlCreator.Middleware @@ -14,16 +15,13 @@ namespace Ocelot.DownstreamUrlCreator.Middleware private readonly OcelotRequestDelegate _next; private readonly IDownstreamPathPlaceholderReplacer _replacer; private readonly IOcelotLogger _logger; - private readonly IUrlBuilder _urlBuilder; public DownstreamUrlCreatorMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, - IDownstreamPathPlaceholderReplacer replacer, - IUrlBuilder urlBuilder) + IDownstreamPathPlaceholderReplacer replacer) { _next = next; _replacer = replacer; - _urlBuilder = urlBuilder; _logger = loggerFactory.CreateLogger(); } @@ -40,11 +38,32 @@ namespace Ocelot.DownstreamUrlCreator.Middleware return; } - var uriBuilder = new UriBuilder(context.DownstreamRequest.RequestUri) + UriBuilder uriBuilder; + + //todo - feel this is a bit crap the way we build the url dont see why we need this builder thing..maybe i blew my own brains out + // when i originally wrote it.. + if (context.ServiceProviderConfiguration.Type == "ServiceFabric" && context.DownstreamReRoute.UseServiceDiscovery) { - Path = dsPath.Data.Value, - Scheme = context.DownstreamReRoute.DownstreamScheme - }; + _logger.LogInformation("DownstreamUrlCreatorMiddleware - going to try set service fabric path"); + + var scheme = context.DownstreamReRoute.DownstreamScheme; + var host = context.DownstreamRequest.RequestUri.Host; + var port = context.DownstreamRequest.RequestUri.Port; + var serviceFabricPath = $"/{context.DownstreamReRoute.ServiceName + dsPath.Data.Value}"; + + _logger.LogInformation("DownstreamUrlCreatorMiddleware - service fabric path is {proxyUrl}", serviceFabricPath); + + var uri = new Uri($"{scheme}://{host}:{port}{serviceFabricPath}?cmd=instance"); + uriBuilder = new UriBuilder(uri); + } + else + { + uriBuilder = new UriBuilder(context.DownstreamRequest.RequestUri) + { + Path = dsPath.Data.Value, + Scheme = context.DownstreamReRoute.DownstreamScheme + }; + } context.DownstreamRequest.RequestUri = uriBuilder.Uri; diff --git a/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs b/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs index 80f2507d..1c5f284c 100644 --- a/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs +++ b/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs @@ -1,4 +1,5 @@ -using System; +/* +using System; using System.Collections.Generic; using Ocelot.Errors; using Ocelot.Responses; @@ -25,12 +26,11 @@ namespace Ocelot.DownstreamUrlCreator return new ErrorResponse(new List { new DownstreamHostNullOrEmptyError() }); } - var builder = new UriBuilder { Host = downstreamHostAndPort.DownstreamHost, Path = downstreamPath, - Scheme = downstreamScheme + Scheme = downstreamScheme, }; if (downstreamHostAndPort.DownstreamPort > 0) @@ -44,3 +44,4 @@ namespace Ocelot.DownstreamUrlCreator } } } +*/ diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs index 172909d7..203d1b9d 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs @@ -17,6 +17,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers public async Task> Lease() { + //todo no point spinning a task up here, also first or default could be null.. var service = await Task.FromResult(_services.FirstOrDefault()); return new OkResponse(service.HostAndPort); } diff --git a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs index 0e75cb1a..de93a19d 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs @@ -18,7 +18,7 @@ namespace Ocelot.ServiceDiscovery { if (reRoute.UseServiceDiscovery) { - return GetServiceDiscoveryProvider(reRoute.ServiceName, serviceConfig.ServiceProviderHost, serviceConfig.ServiceProviderPort); + return GetServiceDiscoveryProvider(serviceConfig, reRoute.ServiceName); } var services = new List(); @@ -33,9 +33,15 @@ namespace Ocelot.ServiceDiscovery return new ConfigurationServiceProvider(services); } - private IServiceDiscoveryProvider GetServiceDiscoveryProvider(string keyOfServiceInConsul, string providerHostName, int providerPort) + private IServiceDiscoveryProvider GetServiceDiscoveryProvider(ServiceProviderConfiguration serviceConfig, string serviceName) { - var consulRegistryConfiguration = new ConsulRegistryConfiguration(providerHostName, providerPort, keyOfServiceInConsul); + if (serviceConfig.Type == "ServiceFabric") + { + var config = new ServiceFabricConfiguration(serviceConfig.Host, serviceConfig.Port, serviceName); + return new ServiceFabricServiceDiscoveryProvider(config); + } + + var consulRegistryConfiguration = new ConsulRegistryConfiguration(serviceConfig.Host, serviceConfig.Port, serviceName); return new ConsulServiceDiscoveryProvider(consulRegistryConfiguration, _factory); } } diff --git a/src/Ocelot/ServiceDiscovery/ServiceFabricConfiguration.cs b/src/Ocelot/ServiceDiscovery/ServiceFabricConfiguration.cs new file mode 100644 index 00000000..7522a1e0 --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/ServiceFabricConfiguration.cs @@ -0,0 +1,16 @@ +namespace Ocelot.ServiceDiscovery +{ + public class ServiceFabricConfiguration + { + public ServiceFabricConfiguration(string hostName, int port, string serviceName) + { + HostName = hostName; + Port = port; + ServiceName = serviceName; + } + + public string ServiceName { get; private set; } + public string HostName { get; private set; } + public int Port { get; private set; } + } +} diff --git a/src/Ocelot/ServiceDiscovery/ServiceFabricServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/ServiceFabricServiceDiscoveryProvider.cs new file mode 100644 index 00000000..28f9bb65 --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/ServiceFabricServiceDiscoveryProvider.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Ocelot.Values; + +namespace Ocelot.ServiceDiscovery +{ + public class ServiceFabricServiceDiscoveryProvider : IServiceDiscoveryProvider + { + private readonly ServiceFabricConfiguration _configuration; + + public ServiceFabricServiceDiscoveryProvider(ServiceFabricConfiguration configuration) + { + _configuration = configuration; + } + + public async Task> Get() + { + return new List + { + new Service(_configuration.ServiceName, + new ServiceHostAndPort(_configuration.HostName, _configuration.Port), + "doesnt matter with service fabric", + "doesnt matter with service fabric", + new List()) + }; + } + } +} diff --git a/test/Ocelot.AcceptanceTests/ServiceFabricTests.cs b/test/Ocelot.AcceptanceTests/ServiceFabricTests.cs new file mode 100644 index 00000000..bfe14103 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/ServiceFabricTests.cs @@ -0,0 +1,112 @@ +using System.Linq; +using Microsoft.Extensions.Primitives; + +namespace Ocelot.AcceptanceTests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Net; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Http; + using Ocelot.Configuration.File; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class ServiceFabricTests : IDisposable + { + private IWebHost _builder; + private readonly Steps _steps; + private string _downstreamPath; + + public ServiceFabricTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_support_service_fabric_naming_and_dns_service() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/values", + DownstreamScheme = "http", + UpstreamPathTemplate = "/EquipmentInterfaces", + UpstreamHttpMethod = new List { "Get" }, + UseServiceDiscovery = true, + ServiceName = "OcelotServiceApplication/OcelotApplicationService" + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = 19081, + Type = "ServiceFabric" + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:19081", "/OcelotServiceApplication/OcelotApplicationService/api/values", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/EquipmentInterfaces")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) + { + _builder = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(async context => + { + _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; + + if(_downstreamPath != basePath) + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + if (context.Request.Query.TryGetValue("cmd", out var values)) + { + values.First().ShouldBe("instance"); + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + } + }); + }) + .Build(); + + _builder.Start(); + } + + public void Dispose() + { + _builder?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/ConfigurationFluentValidationTests.cs b/test/Ocelot.UnitTests/Configuration/ConfigurationFluentValidationTests.cs index e7e56178..ba7bfd01 100644 --- a/test/Ocelot.UnitTests/Configuration/ConfigurationFluentValidationTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ConfigurationFluentValidationTests.cs @@ -328,7 +328,7 @@ namespace Ocelot.UnitTests.Configuration this.Given(x => x.GivenAConfiguration(configuration)) .When(x => x.WhenIValidateTheConfiguration()) .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "ReRoutes for aggregateReRoute / either do not exist or do not have correct Key property")) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "ReRoutes for aggregateReRoute / either do not exist or do not have correct ServiceName property")) .BDDfy(); } diff --git a/test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs index c0a35696..8b0cc078 100644 --- a/test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs @@ -30,13 +30,15 @@ namespace Ocelot.UnitTests.Configuration ServiceDiscoveryProvider = new FileServiceDiscoveryProvider { Host = "127.0.0.1", - Port = 1234 + Port = 1234, + Type = "ServiceFabric" } }; var expected = new ServiceProviderConfigurationBuilder() .WithServiceDiscoveryProviderHost("127.0.0.1") .WithServiceDiscoveryProviderPort(1234) + .WithServiceDiscoveryProviderType("ServiceFabric") .Build(); this.Given(x => x.GivenTheFollowingReRoute(reRoute)) @@ -63,8 +65,8 @@ namespace Ocelot.UnitTests.Configuration private void ThenTheConfigIs(ServiceProviderConfiguration expected) { - _result.ServiceProviderHost.ShouldBe(expected.ServiceProviderHost); - _result.ServiceProviderPort.ShouldBe(expected.ServiceProviderPort); + _result.Host.ShouldBe(expected.Host); + _result.Port.ShouldBe(expected.Port); } } -} \ No newline at end of file +} diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index ec4829a7..d0926fc9 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -1,4 +1,5 @@ -using Ocelot.Middleware; +using Ocelot.Configuration; +using Ocelot.Middleware; namespace Ocelot.UnitTests.DownstreamUrlCreator { @@ -27,7 +28,6 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator public class DownstreamUrlCreatorMiddlewareTests { private readonly Mock _downstreamUrlTemplateVariableReplacer; - private readonly Mock _urlBuilder; private OkResponse _downstreamPath; private Mock _loggerFactory; private Mock _logger; @@ -42,7 +42,6 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _downstreamUrlTemplateVariableReplacer = new Mock(); - _urlBuilder = new Mock(); _downstreamContext.DownstreamRequest = new HttpRequestMessage(HttpMethod.Get, "https://my.url/abc/?q=123"); _next = async context => { //do nothing @@ -58,6 +57,9 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator .WithDownstreamScheme("https") .Build(); + var config = new ServiceProviderConfigurationBuilder() + .Build(); + this.Given(x => x.GivenTheDownStreamRouteIs( new DownstreamRoute( new List(), @@ -66,15 +68,81 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator .WithUpstreamHttpMethod(new List { "Get" }) .Build()))) .And(x => x.GivenTheDownstreamRequestUriIs("http://my.url/abc?q=123")) + .And(x => GivenTheServiceProviderConfigIs(config)) .And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1")) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheDownstreamRequestUriIs("https://my.url:80/api/products/1?q=123")) .BDDfy(); } + [Fact] + public void should_not_create_service_fabric_url() + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithDownstreamScheme("https") + .Build(); + + var config = new ServiceProviderConfigurationBuilder() + .WithServiceDiscoveryProviderType("ServiceFabric") + .WithServiceDiscoveryProviderHost("localhost") + .WithServiceDiscoveryProviderPort(19081) + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs( + new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .And(x => x.GivenTheDownstreamRequestUriIs("http://my.url/abc?q=123")) + .And(x => GivenTheServiceProviderConfigIs(config)) + .And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1")) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamRequestUriIs("https://my.url:80/api/products/1?q=123")) + .BDDfy(); + } + + [Fact] + public void should_create_service_fabric_url() + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamScheme("http") + .WithServiceName("Ocelot/OcelotApp") + .WithUseServiceDiscovery(true) + .Build(); + + var downstreamRoute = new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .Build()); + + var config = new ServiceProviderConfigurationBuilder() + .WithServiceDiscoveryProviderType("ServiceFabric") + .WithServiceDiscoveryProviderHost("localhost") + .WithServiceDiscoveryProviderPort(19081) + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => GivenTheServiceProviderConfigIs(config)) + .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:19081")) + .And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1")) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamRequestUriIs("http://localhost:19081/Ocelot/OcelotApp/api/products/1?cmd=instance")) + .BDDfy(); + } + + private void GivenTheServiceProviderConfigIs(ServiceProviderConfiguration config) + { + _downstreamContext.ServiceProviderConfiguration = config; + } + private void WhenICallTheMiddleware() { - _middleware = new DownstreamUrlCreatorMiddleware(_next, _loggerFactory.Object, _downstreamUrlTemplateVariableReplacer.Object, _urlBuilder.Object); + _middleware = new DownstreamUrlCreatorMiddleware(_next, _loggerFactory.Object, _downstreamUrlTemplateVariableReplacer.Object); _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); } diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlBuilderTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlBuilderTests.cs deleted file mode 100644 index 20dfa1a1..00000000 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlBuilderTests.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System; -using Ocelot.DownstreamUrlCreator; -using Ocelot.Responses; -using Ocelot.Values; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.DownstreamUrlCreator -{ - public class UrlBuilderTests - { - private readonly IUrlBuilder _urlBuilder; - private string _dsPath; - private string _dsScheme; - private string _dsHost; - private int _dsPort; - - private Response _result; - - public UrlBuilderTests() - { - _urlBuilder = new UrlBuilder(); - } - - [Fact] - public void should_return_error_when_downstream_path_is_null() - { - this.Given(x => x.GivenADownstreamPath(null)) - .When(x => x.WhenIBuildTheUrl()) - .Then(x => x.ThenThereIsAnErrorOfType()) - .BDDfy(); - } - - [Fact] - public void should_return_error_when_downstream_scheme_is_null() - { - this.Given(x => x.GivenADownstreamScheme(null)) - .And(x => x.GivenADownstreamPath("test")) - .When(x => x.WhenIBuildTheUrl()) - .Then(x => x.ThenThereIsAnErrorOfType()) - .BDDfy(); - } - - [Fact] - public void should_return_error_when_downstream_host_is_null() - { - this.Given(x => x.GivenADownstreamScheme(null)) - .And(x => x.GivenADownstreamPath("test")) - .And(x => x.GivenADownstreamScheme("test")) - .When(x => x.WhenIBuildTheUrl()) - .Then(x => x.ThenThereIsAnErrorOfType()) - .BDDfy(); - } - - [Fact] - public void should_not_use_port_if_zero() - { - this.Given(x => x.GivenADownstreamPath("/api/products/1")) - .And(x => x.GivenADownstreamScheme("http")) - .And(x => x.GivenADownstreamHost("127.0.0.1")) - .And(x => x.GivenADownstreamPort(0)) - .When(x => x.WhenIBuildTheUrl()) - .Then(x => x.ThenTheUrlIsReturned("http://127.0.0.1/api/products/1")) - .And(x => x.ThenTheUrlIsWellFormed()) - .BDDfy(); - } - - [Fact] - public void should_build_well_formed_uri() - { - this.Given(x => x.GivenADownstreamPath("/api/products/1")) - .And(x => x.GivenADownstreamScheme("http")) - .And(x => x.GivenADownstreamHost("127.0.0.1")) - .And(x => x.GivenADownstreamPort(5000)) - .When(x => x.WhenIBuildTheUrl()) - .Then(x => x.ThenTheUrlIsReturned("http://127.0.0.1:5000/api/products/1")) - .And(x => x.ThenTheUrlIsWellFormed()) - .BDDfy(); - } - - private void ThenThereIsAnErrorOfType() - { - _result.Errors[0].ShouldBeOfType(); - } - - private void GivenADownstreamPath(string dsPath) - { - _dsPath = dsPath; - } - - private void GivenADownstreamScheme(string dsScheme) - { - _dsScheme = dsScheme; - } - - private void GivenADownstreamHost(string dsHost) - { - _dsHost = dsHost; - } - - private void GivenADownstreamPort(int dsPort) - { - _dsPort = dsPort; - } - - private void WhenIBuildTheUrl() - { - _result = _urlBuilder.Build(_dsPath, _dsScheme, new ServiceHostAndPort(_dsHost, _dsPort)); - } - - private void ThenTheUrlIsReturned(string expected) - { - _result.Data.Value.ShouldBe(expected); - } - - private void ThenTheUrlIsWellFormed() - { - Uri.IsWellFormedUriString(_result.Data.Value, UriKind.Absolute).ShouldBeTrue(); - } - } -} diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceFabricServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceFabricServiceDiscoveryProviderTests.cs new file mode 100644 index 00000000..39deb681 --- /dev/null +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceFabricServiceDiscoveryProviderTests.cs @@ -0,0 +1,58 @@ +namespace Ocelot.UnitTests.ServiceDiscovery +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using Consul; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Http; + using Moq; + using Ocelot.Logging; + using Ocelot.ServiceDiscovery; + using Ocelot.Values; + using Xunit; + using TestStack.BDDfy; + using Shouldly; + + public class ServiceFabricServiceDiscoveryProviderTests + { + private ServiceFabricServiceDiscoveryProvider _provider; + private ServiceFabricConfiguration _config; + private string _host; + private string _serviceName; + private int _port; + private List _services; + + [Fact] + public void should_return_service_fabric_naming_service() + { + this.Given(x => GivenTheFollowing()) + .When(x => WhenIGet()) + .Then(x => ThenTheServiceFabricNamingServiceIsRetured()) + .BDDfy(); + } + + private void GivenTheFollowing() + { + _host = "localhost"; + _serviceName = "OcelotServiceApplication/OcelotApplicationService"; + _port = 19081; + } + + private void WhenIGet() + { + _config = new ServiceFabricConfiguration(_host, _port, _serviceName); + _provider = new ServiceFabricServiceDiscoveryProvider(_config); + _services = _provider.Get().GetAwaiter().GetResult(); + } + + private void ThenTheServiceFabricNamingServiceIsRetured() + { + _services.Count.ShouldBe(1); + _services[0].HostAndPort.DownstreamHost.ShouldBe(_host); + _services[0].HostAndPort.DownstreamPort.ShouldBe(_port); + } + } +} diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs index f8c60bb7..021b9efb 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs @@ -77,6 +77,24 @@ namespace Ocelot.UnitTests.ServiceDiscovery .BDDfy(); } + [Fact] + public void should_return_service_fabric_provider() + { + var reRoute = new DownstreamReRouteBuilder() + .WithServiceName("product") + .WithUseServiceDiscovery(true) + .Build(); + + var serviceConfig = new ServiceProviderConfigurationBuilder() + .WithServiceDiscoveryProviderType("ServiceFabric") + .Build(); + + this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute)) + .When(x => x.WhenIGetTheServiceProvider()) + .Then(x => x.ThenTheServiceProviderIs()) + .BDDfy(); + } + private void ThenTheFollowingServicesAreReturned(List downstreamAddresses) { var result = (ConfigurationServiceProvider)_result;