diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..49a79976 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at tom@threemammals.co.uk. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..e6f89046 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,9 @@ +We love to receive contributions from the community so please keep them coming :) + +Pull requests, issues and commentary welcome! + +Please complete the relavent template for issues and PRs. Sometimes it's worth getting in touch with us to discuss changes +before doing any work incase this is something we are already doing or it might not make sense. We can also give +advice on the easiest way to do things :) + +Finally we mark all existing issues as help wanted, small, medium and large effort. If you want to contriute for the first time I suggest looking at a help wanted & small effort issue :) diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..3f0d0f3a --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,17 @@ +## Expected Behavior / New Feature + + +## Actual Behavior / Motivation for New Feautre + + +## Steps to Reproduce the Problem + + 1. + 1. + 1. + +## Specifications + + - Version: + - Platform: + - Subsystem: 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/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..72a3b6fe --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ +Fixes / New Feature # + +## Proposed Changes + + - + - + - diff --git a/README.md b/README.md index 7145dc38..fc9b67b8 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,17 @@ -# Ocelot +[](http://threemammals.com/ocelot) [![Build status](https://ci.appveyor.com/api/projects/status/r6sv51qx36sis1je?svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb) [![Windows Build history](https://buildstats.info/appveyor/chart/TomPallister/ocelot-fcfpb?branch=develop&includeBuildsFromPullRequest=false)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb/history?branch=develop) - [![Coverage Status](https://coveralls.io/repos/github/TomPallister/Ocelot/badge.svg?branch=develop)](https://coveralls.io/github/TomPallister/Ocelot?branch=develop) +# Ocelot + Ocelot is a .NET Api Gateway. This project is aimed at people using .NET running a micro services / service orientated architecture -that need a unified point of entry into their system. +that need a unified point of entry into their system. However it will worth with anything that +speaks HTTP and run on any platform that asp.net core supports. In particular I want easy integration with IdentityServer reference and bearer tokens. @@ -26,11 +28,28 @@ Ocelot manipulates the HttpRequest object into a state specified by its configur it reaches a request builder middleware where it creates a HttpRequestMessage object which is used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. -The response from the downstream service is stored in a per request scoped repository -and retrieved as the requests goes back up the Ocelot pipeline. There is a piece of middleware -that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. -That is basically it with a bunch of other features. - +The response from the downstream service is retrieved as the requests goes back up the Ocelot pipeline. +There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that +is returned to the client. That is basically it with a bunch of other features! + +## Features + +A quick list of Ocelot's capabilities for more information see the [documentation](http://ocelot.readthedocs.io/en/latest/). + +* Routing +* Request Aggregation +* Service Discovery with Consul +* Authentication +* Authorisation +* Rate Limiting +* Caching +* Retry policies / QoS +* Load Balancing +* Logging / Tracing / Correlation +* Headers / Query String / Claims Transformation +* Custom Middleware / Delegating Handlers +* Configuration / Administration REST API + ## How to install Ocelot is designed to work with ASP.NET core only and is currently @@ -48,20 +67,22 @@ Please click [here](http://ocelot.readthedocs.io/en/latest/) for the Ocleot docu ## Coming up -You can see what we are working on [here](https://github.com/TomPallister/Ocelot/projects/1) +You can see what we are working on [here](https://github.com/ThreeMammals/Ocelot/issues). ## Contributing -Pull requests, issues and commentary welcome! No special process just create a request and get in -touch either via gitter or create an issue. +We love to receive contributions from the community so please keep them coming :) +Pull requests, issues and commentary welcome! + +Please complete the relavent template for issues and PRs. Sometimes it's worth getting in touch with us to discuss changes +before doing any work incase this is something we are already doing or it might not make sense. We can also give +advice on the easiest way to do things :) + +Finally we mark all existing issues as help wanted, small, medium and large effort. If you want to contriute for the first time I suggest looking at a help wanted & small effort issue :) ## Things that are currently annoying me -+ The base OcelotMiddleware lets you access things that are going to be null -and doesnt check the response is OK. I think the fact you can even call stuff -that isnt available is annoying. Let alone it be null. - [![](https://codescene.io/projects/697/status.svg) Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results) 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;