diff --git a/.gitignore b/.gitignore index 3c690a758..6bc27551d 100644 --- a/.gitignore +++ b/.gitignore @@ -263,3 +263,6 @@ pub/ /src/Web/WebMVC/wwwroot/css/site.min.css **/.kube/** .mfractor + +# Ignore HealthCheckdb +*healthchecksdb* \ No newline at end of file diff --git a/NuGet.config b/NuGet.config index da29646ef..0a3819dd2 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,6 +4,9 @@ + + + diff --git a/README.md b/README.md index cbae071f8..037d532a3 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,15 @@ Sample .NET Core reference application, powered by Microsoft, based on a simplified microservices architecture and Docker containers. ## IMPORTANT NOTES! -**The current supported Visual Studio version for eShopOnContainers is Visual Studio 2017 15.7** ([GA/RTM since May 8th 2018](https://docs.microsoft.com/en-us/visualstudio/releasenotes/vs2017-relnotes)) or later version. +**You can use either the latest version of Visual Studio or simply Docker CLI and .NET CLI for Windows, Mac and Linux**. **Note for Pull Requests (PRs)**: We accept pull request from the community. When doing it, please do it onto the **DEV branch** which is the consolidated work-in-progress branch. Do not request it onto Master branch, if possible. **NEWS / ANNOUNCEMENTS** Do you want to be up-to-date on .NET Architecture guidance and reference apps like eShopOnContainers? --> Subscribe by "WATCHING" this new GitHub repo: https://github.com/dotnet-architecture/News -## Updated for .NET Core 2.0 and 2.1 "wave" of technologies -eShopOnContainers is updated to .NET Core 2.0 and 2.1 "wave". Not just compilation but also new recommended code in EF Core, ASP.NET Core, and other new related versions. +## Updated for .NET Core 2.2 "wave" of technologies +eShopOnContainers is updated to .NET Core 2.x (currently updated to 2.2) "wave" of technologies. Not just compilation but also new recommended code in EF Core, ASP.NET Core, and other new related versions. The **dockerfiles** in the solution have also been updated and now support [**Docker Multi-Stage**](https://blogs.msdn.microsoft.com/stevelasker/2017/09/11/net-and-multistage-dockerfiles/) since mid-December 2017. @@ -70,7 +70,7 @@ You can download them and start reviewing these Guides/eBooks here: | Architecting & Developing | Containers Lifecycle & CI/CD | App patterns with Xamarin.Forms | | ------------ | ------------| ------------| | | | | -| **Download .PDF** (v2.1 Edition) | **Download** | **Download** | +| **Download .PDF** (v2.2 Edition) | **Download** | **Download** | Download in other formats (**eReaders** like **MOBI**, **EPUB**) and other eBooks at the [.NET Architecture center](http://dot.net/architecture). diff --git a/docker-compose-tests.override.yml b/docker-compose-tests.override.yml new file mode 100644 index 000000000..8705e703f --- /dev/null +++ b/docker-compose-tests.override.yml @@ -0,0 +1,247 @@ +version: '3.4' + +services: + rabbitmq-test: + ports: + - "15672:15672" + - "5672:5672" + + sql-data-test: + environment: + - SA_PASSWORD=Pass@word + - ACCEPT_EULA=Y + ports: + - "5433:1433" + + nosql-data-test: + ports: + - "27017:27017" + + basket-data-test: + ports: + - "6379:6379" + + identity-api-test: + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=http://0.0.0.0:80 + - SpaClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5104 + - XamarinCallback=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5105/xamarincallback + - ConnectionString=${ESHOP_AZURE_IDENTITY_DB:-Server=sql-data-test;Database=Microsoft.eShopOnContainers.Service.IdentityDb;User Id=sa;Password=Pass@word} + - MvcClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5100 + - LocationApiClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5109 + - MarketingApiClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5110 + - BasketApiClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5103 + - OrderingApiClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5102 + - MobileShoppingAggClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5120 + - WebShoppingAggClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5121 + - UseCustomizationData=True + - ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY} + - OrchestratorType=${ORCHESTRATOR_TYPE} + ports: + - "5105:80" + + basket-api-test: + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=http://0.0.0.0:80 + - ConnectionString=${ESHOP_AZURE_REDIS_BASKET_DB:-basket-data-test} + - identityUrl=http://identity-api + - IdentityUrlExternal=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 + - EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq-test} + - EventBusUserName=${ESHOP_SERVICE_BUS_USERNAME} + - EventBusPassword=${ESHOP_SERVICE_BUS_PASSWORD} + - AzureServiceBusEnabled=False + - ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY} + - OrchestratorType=${ORCHESTRATOR_TYPE} + - UseLoadTest=${USE_LOADTEST:-False} + ports: + - "5103:80" + entrypoint: + - dotnet + - test + - --logger + - trx;LogFileName=/tests/basket-test-results.xml + + basket-api-unit-test: + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=http://0.0.0.0:80 + - ConnectionString=${ESHOP_AZURE_REDIS_BASKET_DB:-basket-data-test} + - identityUrl=http://identity-api + - IdentityUrlExternal=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 + - EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq-test} + - EventBusUserName=${ESHOP_SERVICE_BUS_USERNAME} + - EventBusPassword=${ESHOP_SERVICE_BUS_PASSWORD} + - AzureServiceBusEnabled=False + - ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY} + - OrchestratorType=${ORCHESTRATOR_TYPE} + - UseLoadTest=${USE_LOADTEST:-False} + ports: + - "5113:80" + entrypoint: + - dotnet + - test + - --logger + - trx;LogFileName=/tests/basket-unit-test-results.xml + + catalog-api-test: + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=http://0.0.0.0:80 + - ConnectionString=${ESHOP_AZURE_CATALOG_DB:-Server=sql-data-test;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word} + - PicBaseUrl=${ESHOP_AZURE_STORAGE_CATALOG_URL:-http://localhost:5202/api/v1/c/catalog/items/[0]/pic/} + - EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq-test} + - EventBusUserName=${ESHOP_SERVICE_BUS_USERNAME} + - EventBusPassword=${ESHOP_SERVICE_BUS_PASSWORD} + - AzureStorageAccountName=${ESHOP_AZURE_STORAGE_CATALOG_NAME} + - AzureStorageAccountKey=${ESHOP_AZURE_STORAGE_CATALOG_KEY} + - UseCustomizationData=True + - AzureServiceBusEnabled=False + - AzureStorageEnabled=False + - ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY} + - OrchestratorType=${ORCHESTRATOR_TYPE} + ports: + - "5101:80" + entrypoint: + - dotnet + - test + - --logger + - trx;LogFileName=/tests/catalog-test-results.xml + + catalog-api-unit-test: + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=http://0.0.0.0:80 + - ConnectionString=${ESHOP_AZURE_CATALOG_DB:-Server=sql-data-test;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word} + - PicBaseUrl=${ESHOP_AZURE_STORAGE_CATALOG_URL:-http://localhost:5202/api/v1/c/catalog/items/[0]/pic/} + - EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq-test} + - EventBusUserName=${ESHOP_SERVICE_BUS_USERNAME} + - EventBusPassword=${ESHOP_SERVICE_BUS_PASSWORD} + - AzureStorageAccountName=${ESHOP_AZURE_STORAGE_CATALOG_NAME} + - AzureStorageAccountKey=${ESHOP_AZURE_STORAGE_CATALOG_KEY} + - UseCustomizationData=True + - AzureServiceBusEnabled=False + - AzureStorageEnabled=False + - ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY} + - OrchestratorType=${ORCHESTRATOR_TYPE} + ports: + - "5191:80" + entrypoint: + - dotnet + - test + - --logger + - trx;LogFileName=/tests/catalog-unit-test-results.xml + + ordering-api-test: + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=http://0.0.0.0:80 + - ConnectionString=${ESHOP_AZURE_ORDERING_DB:-Server=sql-data-test;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word} + - identityUrl=http://identity-api + - IdentityUrlExternal=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 + - EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq-test} + - EventBusUserName=${ESHOP_SERVICE_BUS_USERNAME} + - EventBusPassword=${ESHOP_SERVICE_BUS_PASSWORD} + - UseCustomizationData=True + - AzureServiceBusEnabled=False + - CheckUpdateTime=30000 + - ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY} + - OrchestratorType=${ORCHESTRATOR_TYPE} + - UseLoadTest=${USE_LOADTEST:-False} + ports: + - "5102:80" + entrypoint: + - dotnet + - test + - --logger + - trx;LogFileName=/tests/ordering-test-results.xml + + ordering-api-unit-test: + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=http://0.0.0.0:80 + - ConnectionString=${ESHOP_AZURE_ORDERING_DB:-Server=sql-data-test;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word} + - identityUrl=http://identity-api + - IdentityUrlExternal=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 + - EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq-test} + - EventBusUserName=${ESHOP_SERVICE_BUS_USERNAME} + - EventBusPassword=${ESHOP_SERVICE_BUS_PASSWORD} + - UseCustomizationData=True + - AzureServiceBusEnabled=False + - CheckUpdateTime=30000 + - ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY} + - OrchestratorType=${ORCHESTRATOR_TYPE} + - UseLoadTest=${USE_LOADTEST:-False} + ports: + - "5112:80" + entrypoint: + - dotnet + - test + - --logger + - trx;LogFileName=/tests/ordering-unit-test-results.xml + + marketing-api-test: + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=http://0.0.0.0:80 + - ConnectionString=${ESHOP_AZURE_MARKETING_DB:-Server=sql-data-test;Database=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word} + - MongoConnectionString=${ESHOP_AZURE_COSMOSDB:-mongodb://nosql-data-test} + - MongoDatabase=MarketingDb + - EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq-test} + - EventBusUserName=${ESHOP_SERVICE_BUS_USERNAME} + - EventBusPassword=${ESHOP_SERVICE_BUS_PASSWORD} + - identityUrl=http://identity-api + - IdentityUrlExternal=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 + - CampaignDetailFunctionUri=${ESHOP_AZUREFUNC_CAMPAIGN_DETAILS_URI} + - PicBaseUrl=${ESHOP_AZURE_STORAGE_MARKETING_URL:-http://localhost:5110/api/v1/campaigns/[0]/pic/} + - AzureStorageAccountName=${ESHOP_AZURE_STORAGE_MARKETING_NAME} + - AzureStorageAccountKey=${ESHOP_AZURE_STORAGE_MARKETING_KEY} + - AzureServiceBusEnabled=False + - AzureStorageEnabled=False + - ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY} + - OrchestratorType=${ORCHESTRATOR_TYPE} + - UseLoadTest=${USE_LOADTEST:-False} + ports: + - "5110:80" + entrypoint: + - dotnet + - test + - --logger + - trx;LogFileName=/tests/marketing-test-results.xml + + payment-api-test: + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=http://0.0.0.0:80 + - EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq-test} + - EventBusUserName=${ESHOP_SERVICE_BUS_USERNAME} + - EventBusPassword=${ESHOP_SERVICE_BUS_PASSWORD} + - AzureServiceBusEnabled=False + - ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY} + - OrchestratorType=${ORCHESTRATOR_TYPE} + ports: + - "5108:80" + + locations-api-test: + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=http://0.0.0.0:80 + - ConnectionString=${ESHOP_AZURE_COSMOSDB:-mongodb://nosql-data-test} + - Database=LocationsDb + - identityUrl=http://identity-api + - IdentityUrlExternal=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 + - EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq-test} + - EventBusUserName=${ESHOP_SERVICE_BUS_USERNAME} + - EventBusPassword=${ESHOP_SERVICE_BUS_PASSWORD} + - AzureServiceBusEnabled=False + - ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY} + - OrchestratorType=${ORCHESTRATOR_TYPE} + - UseLoadTest=${USE_LOADTEST:-False} + ports: + - "5109:80" + entrypoint: + - dotnet + - test + - --logger + - trx;LogFileName=/tests/locations-test-results.xml \ No newline at end of file diff --git a/docker-compose-tests.yml b/docker-compose-tests.yml new file mode 100644 index 000000000..d05f7e27e --- /dev/null +++ b/docker-compose-tests.yml @@ -0,0 +1,130 @@ +version: '3.4' + +services: + rabbitmq-test: + image: rabbitmq:3-management-alpine + + basket-data-test: + image: redis:alpine + + sql-data-test: + image: microsoft/mssql-server-linux:2017-latest + + nosql-data-test: + image: mongo + + identity-api-test: + image: eshop/identity-api-test:${TAG:-latest} + build: + context: . + dockerfile: src/Services/Identity/Identity.API/Dockerfile + depends_on: + - sql-data-test + + basket-api-test: + image: eshop/basket-api-test:${TAG:-latest} + build: + context: . + dockerfile: src/Services/Basket/Basket.API/Dockerfile + target: functionaltest + depends_on: + - basket-data-test + - identity-api-test + - rabbitmq-test + volumes: + - ${BUILD_ARTIFACTSTAGINGDIRECTORY:-./tests-results/}:/tests + + basket-api-unit-test: + image: eshop/basket-api-test:${TAG:-latest} + build: + context: . + dockerfile: src/Services/Basket/Basket.API/Dockerfile + target: unittest + depends_on: + - basket-data-test + - identity-api-test + - rabbitmq-test + volumes: + - ${BUILD_ARTIFACTSTAGINGDIRECTORY:-./tests-results/}:/tests + + catalog-api-test: + image: eshop/catalog-api-test:${TAG:-latest} + build: + context: . + dockerfile: src/Services/Catalog/Catalog.API/Dockerfile + target: functionaltest + depends_on: + - sql-data-test + - rabbitmq-test + volumes: + - ${BUILD_ARTIFACTSTAGINGDIRECTORY:-./tests-results/}:/tests + + catalog-api-unit-test: + image: eshop/catalog-api-test:${TAG:-latest} + build: + context: . + dockerfile: src/Services/Catalog/Catalog.API/Dockerfile + target: unittest + depends_on: + - sql-data-test + - rabbitmq-test + volumes: + - ${BUILD_ARTIFACTSTAGINGDIRECTORY:-./tests-results/}:/tests + + ordering-api-test: + image: eshop/ordering-api-test:${TAG:-latest} + build: + context: . + dockerfile: src/Services/Ordering/Ordering.API/Dockerfile + target: functionaltest + depends_on: + - sql-data-test + - rabbitmq-test + volumes: + - ${BUILD_ARTIFACTSTAGINGDIRECTORY:-./tests-results/}:/tests + + ordering-api-unit-test: + image: eshop/ordering-api-test:${TAG:-latest} + build: + context: . + dockerfile: src/Services/Ordering/Ordering.API/Dockerfile + target: unittest + depends_on: + - sql-data-test + - rabbitmq-test + volumes: + - ${BUILD_ARTIFACTSTAGINGDIRECTORY:-./tests-results/}:/tests + + marketing-api-test: + image: eshop/marketing-api-test:${TAG:-latest} + build: + context: . + dockerfile: src/Services/Marketing/Marketing.API/Dockerfile + target: functionaltest + depends_on: + - sql-data-test + - nosql-data-test + - identity-api-test + - rabbitmq-test + volumes: + - ${BUILD_ARTIFACTSTAGINGDIRECTORY:-./tests-results/}:/tests + + payment-api-test: + image: eshop/payment-api-test:${TAG:-latest} + build: + context: . + dockerfile: src/Services/Payment/Payment.API/Dockerfile + depends_on: + - rabbitmq-test + + locations-api-test: + image: eshop/locations-api-test:${TAG:-latest} + build: + context: . + dockerfile: src/Services/Location/Locations.API/Dockerfile + target: functionaltest + depends_on: + - nosql-data-test + - rabbitmq-test + volumes: + - ${BUILD_ARTIFACTSTAGINGDIRECTORY:-./tests-results/}:/tests \ No newline at end of file diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 969cfb922..a0a3b35b7 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -187,6 +187,17 @@ services: environment: - ASPNETCORE_ENVIRONMENT=Development - IdentityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5110. + - urls__basket=http://basket.api + - urls__catalog=http://catalog.api + - urls__orders=http://ordering.api + - urls__identity=http://identity.api + - CatalogUrlHC=http://catalog.api/hc + - OrderingUrlHC=http://ordering.api/hc + - IdentityUrlHC=http://identity.api/hc + - BasketUrlHC=http://basket.api/hc + - MarketingUrlHC=http://marketing.api/hc + - PaymentUrlHC=http://payment.api/hc + - LocationUrlHC=http://locations.api/hc ports: - "5200:80" volumes: @@ -196,6 +207,13 @@ services: environment: - ASPNETCORE_ENVIRONMENT=Development - IdentityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5110. + - CatalogUrlHC=http://catalog.api/hc + - OrderingUrlHC=http://ordering.api/hc + - IdentityUrlHC=http://identity.api/hc + - BasketUrlHC=http://basket.api/hc + - MarketingUrlHC=http://marketing.api/hc + - PaymentUrlHC=http://payment.api/hc + - LocationUrlHC=http://locations.api/hc ports: - "5201:80" volumes: @@ -205,6 +223,13 @@ services: environment: - ASPNETCORE_ENVIRONMENT=Development - IdentityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5110. + - CatalogUrlHC=http://catalog.api/hc + - OrderingUrlHC=http://ordering.api/hc + - IdentityUrlHC=http://identity.api/hc + - BasketUrlHC=http://basket.api/hc + - MarketingUrlHC=http://marketing.api/hc + - PaymentUrlHC=http://payment.api/hc + - LocationUrlHC=http://locations.api/hc ports: - "5202:80" volumes: @@ -214,6 +239,13 @@ services: environment: - ASPNETCORE_ENVIRONMENT=Development - IdentityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5110. + - CatalogUrlHC=http://catalog.api/hc + - OrderingUrlHC=http://ordering.api/hc + - IdentityUrlHC=http://identity.api/hc + - BasketUrlHC=http://basket.api/hc + - MarketingUrlHC=http://marketing.api/hc + - PaymentUrlHC=http://payment.api/hc + - LocationUrlHC=http://locations.api/hc ports: - "5203:80" volumes: @@ -226,6 +258,13 @@ services: - urls__catalog=http://catalog.api - urls__orders=http://ordering.api - urls__identity=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5110. + - CatalogUrlHC=http://catalog.api/hc + - OrderingUrlHC=http://ordering.api/hc + - IdentityUrlHC=http://identity.api/hc + - BasketUrlHC=http://basket.api/hc + - MarketingUrlHC=http://marketing.api/hc + - PaymentUrlHC=http://payment.api/hc + - LocationUrlHC=http://locations.api/hc ports: - "5120:80" # Important: In a production environment your should remove the external port (5120) kept here for microservice debugging purposes. # The API Gateway redirects and access through the internal port (80). @@ -237,6 +276,13 @@ services: - urls__catalog=http://catalog.api - urls__orders=http://ordering.api - urls__identity=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5110. + - CatalogUrlHC=http://catalog.api/hc + - OrderingUrlHC=http://ordering.api/hc + - IdentityUrlHC=http://identity.api/hc + - BasketUrlHC=http://basket.api/hc + - MarketingUrlHC=http://marketing.api/hc + - PaymentUrlHC=http://payment.api/hc + - LocationUrlHC=http://locations.api/hc ports: - "5121:80" # Important: In a production environment your should remove the external port (5121) kept here for microservice debugging purposes. # The API Gateway redirects and access through the internal port (80). @@ -259,16 +305,41 @@ services: environment: - ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_URLS=http://0.0.0.0:80 - - CatalogUrl=http://catalog.api/hc - - OrderingUrl=http://ordering.api/hc + - HealthChecks-UI__HealthChecks__1__Name=WebMVC HTTP Check + - HealthChecks-UI__HealthChecks__1__Uri=http://webmvc/hc + - HealthChecks-UI__HealthChecks__2__Name=WebSPA HTTP Check + - HealthChecks-UI__HealthChecks__2__Uri=http://webspa/hc + - HealthChecks-UI__HealthChecks__3__Name=Web Shopping Aggregator GW HTTP Check + - HealthChecks-UI__HealthChecks__3__Uri=http://webshoppingagg/hc + - HealthChecks-UI__HealthChecks__4__Name=Mobile Shopping Aggregator HTTP Check + - HealthChecks-UI__HealthChecks__4__Uri=http://mobileshoppingagg/hc + - HealthChecks-UI__HealthChecks__5__Name=Mobile Shopping API GW HTTP Check + - HealthChecks-UI__HealthChecks__5__Uri=http://mobileshoppingapigw/hc + - HealthChecks-UI__HealthChecks__6__Name=Mobile Marketing API GW HTTP Check + - HealthChecks-UI__HealthChecks__6__Uri=http://mobilemarketingapigw/hc + - HealthChecks-UI__HealthChecks__7__Name=Web Shopping API GW HTTP Check + - HealthChecks-UI__HealthChecks__7__Uri=http://webshoppingapigw/hc + - HealthChecks-UI__HealthChecks__8__Name=Web Marketing API GW HTTP Check + - HealthChecks-UI__HealthChecks__8__Uri=http://webmarketingapigw/hc + - HealthChecks-UI__HealthChecks__9__Name=Ordering HTTP Check + - HealthChecks-UI__HealthChecks__9__Uri=http://ordering.api/hc + - HealthChecks-UI__HealthChecks__10__Name=Ordering HTTP Background Check + - HealthChecks-UI__HealthChecks__10__Uri=http://ordering.backgroundtasks/hc + - HealthChecks-UI__HealthChecks__11__Name=Basket HTTP Check + - HealthChecks-UI__HealthChecks__11__Uri=http://basket.api/hc + - HealthChecks-UI__HealthChecks__12__Name=Catalog HTTP Check + - HealthChecks-UI__HealthChecks__12__Uri=http://catalog.api/hc + - HealthChecks-UI__HealthChecks__13__Name=Identity HTTP Check + - HealthChecks-UI__HealthChecks__13__Uri=http://identity.api/hc + - HealthChecks-UI__HealthChecks__14__Name=Marketing HTTP Check + - HealthChecks-UI__HealthChecks__14__Uri=http://marketing.api/hc + - HealthChecks-UI__HealthChecks__15__Name=Locations HTTP Check + - HealthChecks-UI__HealthChecks__15__Uri=http://locations.api/hc + - HealthChecks-UI__HealthChecks__16__Name=Payments HTTP Check + - HealthChecks-UI__HealthChecks__16__Uri=http://payment.api/hc + - HealthChecks-UI__HealthChecks__17__Name=Ordering SignalRHub HTTP Check + - HealthChecks-UI__HealthChecks__17__Uri=http://ordering.signalrhub/hc - OrderingBackgroundTasksUrl=http://ordering.backgroundtasks/hc - - BasketUrl=http://basket.api/hc - - IdentityUrl=http://identity.api/hc - - LocationsUrl=http://locations.api/hc - - MarketingUrl=http://marketing.api/hc - - PaymentUrl=http://payment.api/hc - - mvc=http://webmvc/hc - - spa=http://webspa/hc - ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY} - OrchestratorType=${ORCHESTRATOR_TYPE} ports: @@ -281,12 +352,9 @@ services: - IdentityUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. - PurchaseUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5202 - MarketingUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5203 - - CatalogUrlHC=http://catalog.api/hc - - OrderingUrlHC=http://ordering.api/hc - - IdentityUrlHC=http://identity.api/hc #Local: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}, if using external IP or DNS name from browser. - - BasketUrlHC=http://basket.api/hc - - MarketingUrlHC=http://marketing.api/hc - - PaymentUrlHC=http://payment.api/hc + - PurchaseUrlHC=http://webshoppingapigw/hc + - MarketingUrlHC=http://webmarketingapigw/hc + - IdentityUrlHC=http://identity.api/hc - UseCustomizationData=True - ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY} - OrchestratorType=${ORCHESTRATOR_TYPE} @@ -300,14 +368,11 @@ services: - ASPNETCORE_URLS=http://0.0.0.0:80 - PurchaseUrl=http://webshoppingapigw - IdentityUrl=http://10.0.75.1:5105 # Local Mac: Use http://docker.for.mac.localhost:5105 || Local Windows: Use 10.0.75.1 in a "Docker for Windows" environment, if using "localhost" from browser. || #Remote access: Use ${ESHOP_EXTERNAL_DNS_NAME_OR_IP} if using external IP or DNS name from browser. - - MarketingUrl=http://webmarketingapigw - - CatalogUrlHC=http://catalog.api/hc - - OrderingUrlHC=http://ordering.api/hc - - IdentityUrlHC=http://identity.api/hc #Local: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}, if using external IP or DNS name from browser. - - BasketUrlHC=http://basket.api/hc - - MarketingUrlHC=http://marketing.api/hc - - PaymentUrlHC=http://payment.api/hc + - MarketingUrl=http://webmarketingapigw - SignalrHubUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5202 + - IdentityUrlHC=http://identity.api/hc + - PurchaseUrlHC=http://webshoppingapigw/hc + - MarketingUrlHC=http://webmarketingapigw/hc - UseCustomizationData=True - ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY} - OrchestratorType=${ORCHESTRATOR_TYPE} diff --git a/docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-eBook)-v2.1.02.pdf b/docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-eBook)-v2.1.02.pdf new file mode 100644 index 000000000..0bd88ca3e Binary files /dev/null and b/docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-eBook)-v2.1.02.pdf differ diff --git a/docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-eBook)-v2.1.03.pdf b/docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-eBook)-v2.1.03.pdf new file mode 100644 index 000000000..0540236c9 Binary files /dev/null and b/docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-eBook)-v2.1.03.pdf differ diff --git a/docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-eBook).pdf b/docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-eBook).pdf index 0bd88ca3e..826a08f3b 100644 Binary files a/docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-eBook).pdf and b/docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-eBook).pdf differ diff --git a/eShopOnContainers-ServicesAndWebApps.sln b/eShopOnContainers-ServicesAndWebApps.sln index 583b4499c..0a152c73b 100644 --- a/eShopOnContainers-ServicesAndWebApps.sln +++ b/eShopOnContainers-ServicesAndWebApps.sln @@ -56,16 +56,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBusRabbitMQ", "src\Bui EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationEventLogEF", "src\BuildingBlocks\EventBus\IntegrationEventLogEF\IntegrationEventLogEF.csproj", "{9EE28E45-1533-472B-8267-56C48855BA0E}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HealthChecks", "HealthChecks", "{A81ECBC2-6B00-4DCD-8388-469174033379}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj", "{942ED6E8-0050-495F-A0EA-01E97F63760C}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebStatus", "src\Web\WebStatus\WebStatus.csproj", "{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HealthChecks", "src\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj", "{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks.SqlServer", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.SqlServer\Microsoft.Extensions.HealthChecks.SqlServer.csproj", "{4BD76717-3102-4969-8C2C-BAAA3F0263B6}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Location", "Location", "{41139F64-4046-4F16-96B7-D941D96FA9C6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Locations.API", "src\Services\Location\Locations.API\Locations.API.csproj", "{E7581357-FC34-474C-B8F5-307EE3CE05EF}" @@ -76,8 +68,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Marketing.API", "src\Servic EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBusServiceBus", "src\BuildingBlocks\EventBus\EventBusServiceBus\EventBusServiceBus.csproj", "{69AF10D3-AA76-4FF7-B187-EC7E8CC5F5B8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks.AzureStorage", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.AzureStorage\Microsoft.Extensions.HealthChecks.AzureStorage.csproj", "{768C887F-C229-4B94-ACD8-0C7F65686524}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebHost", "WebHost", "{1815B651-941C-466B-AE33-D1D7EEB8F77F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebHost.Customization", "src\BuildingBlocks\WebHostCustomization\WebHost.Customization\WebHost.Customization.csproj", "{15F4B3AA-89B6-4A0D-9051-414305974781}" @@ -644,54 +634,6 @@ Global {9EE28E45-1533-472B-8267-56C48855BA0E}.Release|x64.Build.0 = Release|Any CPU {9EE28E45-1533-472B-8267-56C48855BA0E}.Release|x86.ActiveCfg = Release|Any CPU {9EE28E45-1533-472B-8267-56C48855BA0E}.Release|x86.Build.0 = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|x64.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|x86.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|Any CPU.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|ARM.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|ARM.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|iPhone.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|iPhone.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|x64.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|x64.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|x86.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|x86.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|ARM.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|ARM.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|iPhone.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|x64.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|x64.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|x86.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|x86.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|Any CPU.Build.0 = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|ARM.ActiveCfg = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|ARM.Build.0 = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|iPhone.ActiveCfg = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|iPhone.Build.0 = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|x64.ActiveCfg = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|x64.Build.0 = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|x86.ActiveCfg = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|x86.Build.0 = Release|Any CPU {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU @@ -740,102 +682,6 @@ Global {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|x64.Build.0 = Release|Any CPU {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|x86.ActiveCfg = Release|Any CPU {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|x86.Build.0 = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|x64.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|x86.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|Any CPU.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|ARM.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|ARM.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|iPhone.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|iPhone.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|x64.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|x64.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|x86.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|x86.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|ARM.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|ARM.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|iPhone.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|x64.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|x64.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|x86.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|x86.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|Any CPU.Build.0 = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|ARM.ActiveCfg = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|ARM.Build.0 = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|iPhone.ActiveCfg = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|iPhone.Build.0 = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|x64.ActiveCfg = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|x64.Build.0 = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|x86.ActiveCfg = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|x86.Build.0 = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|x64.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|x86.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|Any CPU.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|ARM.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|ARM.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|iPhone.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|iPhone.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|x64.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|x64.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|x86.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|x86.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|ARM.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|ARM.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|iPhone.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|x64.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|x64.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|x86.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|x86.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|Any CPU.Build.0 = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|ARM.ActiveCfg = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|ARM.Build.0 = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|iPhone.ActiveCfg = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|iPhone.Build.0 = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x64.ActiveCfg = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x64.Build.0 = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x86.ActiveCfg = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x86.Build.0 = Release|Any CPU {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU @@ -980,54 +826,6 @@ Global {69AF10D3-AA76-4FF7-B187-EC7E8CC5F5B8}.Release|x64.Build.0 = Release|Any CPU {69AF10D3-AA76-4FF7-B187-EC7E8CC5F5B8}.Release|x86.ActiveCfg = Release|Any CPU {69AF10D3-AA76-4FF7-B187-EC7E8CC5F5B8}.Release|x86.Build.0 = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|x64.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|x86.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|Any CPU.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|ARM.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|ARM.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|iPhone.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|iPhone.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|x64.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|x64.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|x86.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|x86.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|Any CPU.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|ARM.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|ARM.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|iPhone.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|x64.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|x64.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|x86.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|x86.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|Any CPU.ActiveCfg = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|Any CPU.Build.0 = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|ARM.ActiveCfg = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|ARM.Build.0 = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|iPhone.ActiveCfg = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|iPhone.Build.0 = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|x64.ActiveCfg = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|x64.Build.0 = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|x86.ActiveCfg = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|x86.Build.0 = Release|Any CPU {15F4B3AA-89B6-4A0D-9051-414305974781}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {15F4B3AA-89B6-4A0D-9051-414305974781}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {15F4B3AA-89B6-4A0D-9051-414305974781}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU @@ -1869,17 +1667,12 @@ Global {0044B293-1DCC-4224-B948-00CF6DC7F510} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} {8088F3FC-6787-45FA-A924-816EC81CBFAC} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} {9EE28E45-1533-472B-8267-56C48855BA0E} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} - {A81ECBC2-6B00-4DCD-8388-469174033379} = {DB0EFB20-B024-4E5E-A75C-52143C131D25} - {942ED6E8-0050-495F-A0EA-01E97F63760C} = {A81ECBC2-6B00-4DCD-8388-469174033379} {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04} - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5} = {A81ECBC2-6B00-4DCD-8388-469174033379} - {4BD76717-3102-4969-8C2C-BAAA3F0263B6} = {A81ECBC2-6B00-4DCD-8388-469174033379} {41139F64-4046-4F16-96B7-D941D96FA9C6} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8} {E7581357-FC34-474C-B8F5-307EE3CE05EF} = {41139F64-4046-4F16-96B7-D941D96FA9C6} {A5260DE0-1FDD-467E-9CC1-A028AB081CEE} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8} {DF395F85-B010-465D-857A-7EBCC512C0C2} = {A5260DE0-1FDD-467E-9CC1-A028AB081CEE} {69AF10D3-AA76-4FF7-B187-EC7E8CC5F5B8} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} - {768C887F-C229-4B94-ACD8-0C7F65686524} = {A81ECBC2-6B00-4DCD-8388-469174033379} {1815B651-941C-466B-AE33-D1D7EEB8F77F} = {DB0EFB20-B024-4E5E-A75C-52143C131D25} {15F4B3AA-89B6-4A0D-9051-414305974781} = {1815B651-941C-466B-AE33-D1D7EEB8F77F} {77849D35-37D4-4802-81DC-9477B2775A40} = {932D8224-11F6-4D07-B109-DA28AD288A63} diff --git a/global.json b/global.json index 079ebc941..6cb33cee9 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "2.1.401" + "version": "2.2.100" } } diff --git a/k8s/deploy-ingress-azure.ps1 b/k8s/deploy-ingress-azure.ps1 index f93cf437b..d0ff702df 100644 --- a/k8s/deploy-ingress-azure.ps1 +++ b/k8s/deploy-ingress-azure.ps1 @@ -1,3 +1 @@ -kubectl patch deployment -n ingress-nginx nginx-ingress-controller --type=json --patch="$(cat nginx-ingress\publish-service-patch.yaml)" -kubectl apply -f nginx-ingress\azure\service.yaml -kubectl apply -f nginx-ingress\patch-service-without-rbac.yaml \ No newline at end of file +kubectl apply -f nginx-ingress\cloud-generic.yaml \ No newline at end of file diff --git a/k8s/deploy-ingress-dockerlocal.ps1 b/k8s/deploy-ingress-dockerlocal.ps1 new file mode 100644 index 000000000..04ffad763 --- /dev/null +++ b/k8s/deploy-ingress-dockerlocal.ps1 @@ -0,0 +1,2 @@ +kubectl apply -f nginx-ingress\cm.yaml +kubectl apply -f nginx-ingress\cloud-generic.yaml \ No newline at end of file diff --git a/k8s/deploy-ingress.ps1 b/k8s/deploy-ingress.ps1 index 694361bfa..37abcbee2 100644 --- a/k8s/deploy-ingress.ps1 +++ b/k8s/deploy-ingress.ps1 @@ -1,12 +1,5 @@ -kubectl apply -f ingress.yaml - # Deploy nginx-ingress core files -kubectl apply -f nginx-ingress\namespace.yaml -kubectl apply -f nginx-ingress\default-backend.yaml -kubectl apply -f nginx-ingress\configmap.yaml -kubectl apply -f nginx-ingress\tcp-services-configmap.yaml -kubectl apply -f nginx-ingress\udp-services-configmap.yaml -kubectl apply -f nginx-ingress\without-rbac.yaml +kubectl apply -f nginx-ingress\mandatory.yaml diff --git a/k8s/deploy.ps1 b/k8s/deploy.ps1 index f0905096a..abeb12aed 100644 --- a/k8s/deploy.ps1 +++ b/k8s/deploy.ps1 @@ -113,6 +113,7 @@ ExecKube -cmd 'delete configmap internalurls' ExecKube -cmd 'delete configmap urls' ExecKube -cmd 'delete configmap externalcfg' ExecKube -cmd 'delete configmap ocelot' +ExecKube -cmd 'delete -f ingress.yaml' # start sql, rabbitmq, frontend deployments if ($deployInfrastructure) { @@ -204,5 +205,8 @@ ExecKube -cmd 'rollout resume deployments/apigwwm' ExecKube -cmd 'rollout resume deployments/apigwws' ExecKube -cmd 'rollout resume deployments/ordering-signalrhub' +Write-Host "Adding/Updating ingress resource..." -ForegroundColor Yellow +ExecKube -cmd 'apply -f ingress.yaml' + Write-Host "WebSPA is exposed at http://$externalDns, WebMVC at http://$externalDns/webmvc, WebStatus at http://$externalDns/webstatus" -ForegroundColor Yellow diff --git a/k8s/helm-rbac.yaml b/k8s/helm-rbac.yaml new file mode 100644 index 000000000..b6180329a --- /dev/null +++ b/k8s/helm-rbac.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: tiller + namespace: kube-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: tiller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: + - kind: ServiceAccount + name: tiller + namespace: kube-system \ No newline at end of file diff --git a/k8s/helm/apigwmm/templates/configmap.yaml b/k8s/helm/apigwmm/templates/configmap.yaml index 2bd39aff5..fbffcd339 100644 --- a/k8s/helm/apigwmm/templates/configmap.yaml +++ b/k8s/helm/apigwmm/templates/configmap.yaml @@ -11,4 +11,11 @@ metadata: heritage: {{ .Release.Service }} data: internalurls__identity: http://{{ .Values.app.svc.identity }} + internalurls__basket__hc: http://{{ .Values.app.svc.basket }}/hc + internalurls__catalog__hc: http://{{ .Values.app.svc.catalog }}/hc + internalurls__identity__hc: http://{{ .Values.app.svc.identity }}/hc + internalurls__ordering__hc: http://{{ .Values.app.svc.ordering }}/hc + internalurls__marketing__hc: http://{{ .Values.app.svc.marketing }}/hc + internalurls__payment__hc: http://{{ .Values.app.svc.payment }}/hc + internalurls__location__hc: http://{{ .Values.app.svc.locations }}/hc diff --git a/k8s/helm/apigwmm/values.yaml b/k8s/helm/apigwmm/values.yaml index 0bfca0ab5..5bf92838a 100644 --- a/k8s/helm/apigwmm/values.yaml +++ b/k8s/helm/apigwmm/values.yaml @@ -32,6 +32,20 @@ env: configmap: - name: IdentityUrl key: internalurls__identity + - name: CatalogUrlHC + key: internalurls__catalog__hc + - name: BasketUrlHC + key: internalurls__basket__hc + - name: IdentityUrlHC + key: internalurls__identity__hc + - name: OrderingUrlHC + key: internalurls__ordering__hc + - name: MarketingUrlHC + key: internalurls__marketing__hc + - name: PaymentUrlHC + key: internalurls__payment__hc + - name: LocationUrlHC + key: internalurls__location__hc # values define environment variables with a fixed value (no configmap involved) (name is name of var, and value is its value) values: - name: ASPNETCORE_ENVIRONMENT diff --git a/k8s/helm/apigwms/templates/configmap.yaml b/k8s/helm/apigwms/templates/configmap.yaml index 7e93362d8..f3292ce72 100644 --- a/k8s/helm/apigwms/templates/configmap.yaml +++ b/k8s/helm/apigwms/templates/configmap.yaml @@ -11,4 +11,11 @@ metadata: heritage: {{ .Release.Service }} data: internalurls__identity: http://{{ .Values.app.svc.identity }} + internalurls__basket__hc: http://{{ .Values.app.svc.basket }}/hc + internalurls__catalog__hc: http://{{ .Values.app.svc.catalog }}/hc + internalurls__identity__hc: http://{{ .Values.app.svc.identity }}/hc + internalurls__ordering__hc: http://{{ .Values.app.svc.ordering }}/hc + internalurls__marketing__hc: http://{{ .Values.app.svc.marketing }}/hc + internalurls__payment__hc: http://{{ .Values.app.svc.payment }}/hc + internalurls__location__hc: http://{{ .Values.app.svc.locations }}/hc diff --git a/k8s/helm/apigwms/values.yaml b/k8s/helm/apigwms/values.yaml index f432a923a..dc8b50ba1 100644 --- a/k8s/helm/apigwms/values.yaml +++ b/k8s/helm/apigwms/values.yaml @@ -32,6 +32,20 @@ env: configmap: - name: IdentityUrl key: internalurls__identity + - name: CatalogUrlHC + key: internalurls__catalog__hc + - name: BasketUrlHC + key: internalurls__basket__hc + - name: IdentityUrlHC + key: internalurls__identity__hc + - name: OrderingUrlHC + key: internalurls__ordering__hc + - name: MarketingUrlHC + key: internalurls__marketing__hc + - name: PaymentUrlHC + key: internalurls__payment__hc + - name: LocationUrlHC + key: internalurls__location__hc # values define environment variables with a fixed value (no configmap involved) (name is name of var, and value is its value) values: - name: ASPNETCORE_ENVIRONMENT diff --git a/k8s/helm/apigwwm/templates/configmap.yaml b/k8s/helm/apigwwm/templates/configmap.yaml index 2e27194d1..34c0e6979 100644 --- a/k8s/helm/apigwwm/templates/configmap.yaml +++ b/k8s/helm/apigwwm/templates/configmap.yaml @@ -11,4 +11,11 @@ metadata: heritage: {{ .Release.Service }} data: internalurls__identity: http://{{ .Values.app.svc.identity }} + internalurls__basket__hc: http://{{ .Values.app.svc.basket }}/hc + internalurls__catalog__hc: http://{{ .Values.app.svc.catalog }}/hc + internalurls__identity__hc: http://{{ .Values.app.svc.identity }}/hc + internalurls__ordering__hc: http://{{ .Values.app.svc.ordering }}/hc + internalurls__marketing__hc: http://{{ .Values.app.svc.marketing }}/hc + internalurls__payment__hc: http://{{ .Values.app.svc.payment }}/hc + internalurls__location__hc: http://{{ .Values.app.svc.locations }}/hc diff --git a/k8s/helm/apigwwm/values.yaml b/k8s/helm/apigwwm/values.yaml index a00e59deb..5b3b5c97a 100644 --- a/k8s/helm/apigwwm/values.yaml +++ b/k8s/helm/apigwwm/values.yaml @@ -32,6 +32,20 @@ env: configmap: - name: IdentityUrl key: internalurls__identity + - name: CatalogUrlHC + key: internalurls__catalog__hc + - name: BasketUrlHC + key: internalurls__basket__hc + - name: IdentityUrlHC + key: internalurls__identity__hc + - name: OrderingUrlHC + key: internalurls__ordering__hc + - name: MarketingUrlHC + key: internalurls__marketing__hc + - name: PaymentUrlHC + key: internalurls__payment__hc + - name: LocationUrlHC + key: internalurls__location__hc # values define environment variables with a fixed value (no configmap involved) (name is name of var, and value is its value) values: - name: ASPNETCORE_ENVIRONMENT diff --git a/k8s/helm/apigwws/templates/configmap.yaml b/k8s/helm/apigwws/templates/configmap.yaml index e5dc17201..dd5530f61 100644 --- a/k8s/helm/apigwws/templates/configmap.yaml +++ b/k8s/helm/apigwws/templates/configmap.yaml @@ -11,4 +11,11 @@ metadata: heritage: {{ .Release.Service }} data: internalurls__identity: http://{{ .Values.app.svc.identity }} + internalurls__basket__hc: http://{{ .Values.app.svc.basket }}/hc + internalurls__catalog__hc: http://{{ .Values.app.svc.catalog }}/hc + internalurls__identity__hc: http://{{ .Values.app.svc.identity }}/hc + internalurls__ordering__hc: http://{{ .Values.app.svc.ordering }}/hc + internalurls__marketing__hc: http://{{ .Values.app.svc.marketing }}/hc + internalurls__payment__hc: http://{{ .Values.app.svc.payment }}/hc + internalurls__location__hc: http://{{ .Values.app.svc.locations }}/hc diff --git a/k8s/helm/apigwws/values.yaml b/k8s/helm/apigwws/values.yaml index 57ed34dd5..9f37619af 100644 --- a/k8s/helm/apigwws/values.yaml +++ b/k8s/helm/apigwws/values.yaml @@ -32,6 +32,20 @@ env: configmap: - name: IdentityUrl key: internalurls__identity + - name: CatalogUrlHC + key: internalurls__catalog__hc + - name: BasketUrlHC + key: internalurls__basket__hc + - name: IdentityUrlHC + key: internalurls__identity__hc + - name: OrderingUrlHC + key: internalurls__ordering__hc + - name: MarketingUrlHC + key: internalurls__marketing__hc + - name: PaymentUrlHC + key: internalurls__payment__hc + - name: LocationUrlHC + key: internalurls__location__hc # values define environment variables with a fixed value (no configmap involved) (name is name of var, and value is its value) values: - name: ASPNETCORE_ENVIRONMENT diff --git a/k8s/helm/deploy-all.ps1 b/k8s/helm/deploy-all.ps1 index 1239cc7af..fa9d4cfc4 100644 --- a/k8s/helm/deploy-all.ps1 +++ b/k8s/helm/deploy-all.ps1 @@ -8,11 +8,19 @@ Param( [parameter(Mandatory=$false)][bool]$clean=$true, [parameter(Mandatory=$false)][string]$aksName="", [parameter(Mandatory=$false)][string]$aksRg="", - [parameter(Mandatory=$false)][string]$imageTag="latest" -) + [parameter(Mandatory=$false)][string]$imageTag="latest", + [parameter(Mandatory=$false)][bool]$useLocalk8s=$false + ) $dns = $externalDns +$ingressValuesFile="ingress_values.yaml" + +if ($useLocalk8s -eq $true) { + $ingressValuesFile="ingress_values_dockerk8s.yaml" + $dns="localhost" +} + if ($externalDns -eq "aks") { if ([string]::IsNullOrEmpty($aksName) -or [string]::IsNullOrEmpty($aksRg)) { Write-Host "Error: When using -dns aks, MUST set -aksName and -aksRg too." -ForegroundColor Red @@ -58,18 +66,18 @@ $charts = ("eshop-common", "apigwmm", "apigwms", "apigwwm", "apigwws", "basket-a if ($deployInfrastructure) { foreach ($infra in $infras) { Write-Host "Installing infrastructure: $infra" -ForegroundColor Green - helm install --values app.yaml --values inf.yaml --values ingress_values.yaml --set app.name=$appName --set inf.k8s.dns=$dns --name="$appName-$infra" $infra + helm install --values app.yaml --values inf.yaml --values $ingressValuesFile --set app.name=$appName --set inf.k8s.dns=$dns --name="$appName-$infra" $infra } } foreach ($chart in $charts) { Write-Host "Installing: $chart" -ForegroundColor Green if ($useCustomRegistry) { - helm install --set inf.registry.server=$registry --set inf.registry.login=$dockerUser --set inf.registry.pwd=$dockerPassword --set inf.registry.secretName=eshop-docker-scret --values app.yaml --values inf.yaml --values ingress_values.yaml --set app.name=$appName --set inf.k8s.dns=$dns --set image.tag=$imageTag --set image.pullPolicy=Always --name="$appName-$chart" $chart + helm install --set inf.registry.server=$registry --set inf.registry.login=$dockerUser --set inf.registry.pwd=$dockerPassword --set inf.registry.secretName=eshop-docker-scret --values app.yaml --values inf.yaml --values $ingressValuesFile --set app.name=$appName --set inf.k8s.dns=$dns --set image.tag=$imageTag --set image.pullPolicy=Always --name="$appName-$chart" $chart } else { if ($chart -ne "eshop-common") { # eshop-common is ignored when no secret must be deployed - helm install --values app.yaml --values inf.yaml --values ingress_values.yaml --set app.name=$appName --set inf.k8s.dns=$dns --set image.tag=$imageTag --set image.pullPolicy=Always --name="$appName-$chart" $chart + helm install --values app.yaml --values inf.yaml --values $ingressValuesFile --set app.name=$appName --set inf.k8s.dns=$dns --set image.tag=$imageTag --set image.pullPolicy=Always --name="$appName-$chart" $chart } } } diff --git a/k8s/helm/inf.yaml b/k8s/helm/inf.yaml index 4c0180f45..5cc9c0894 100644 --- a/k8s/helm/inf.yaml +++ b/k8s/helm/inf.yaml @@ -14,6 +14,8 @@ inf: db: OrderingDb # Ordering API SQL db name identity: db: IdentityDb # Ordering API SQL db name + marketing: + db: MarketingDb # Marketing API SQL db name mongo: # host: my-nosql-data # Uncomment to use specify custom mongo host. By default nosql-data is used locations: diff --git a/k8s/helm/ingress_values_dockerk8s.yaml b/k8s/helm/ingress_values_dockerk8s.yaml new file mode 100644 index 000000000..75597aac9 --- /dev/null +++ b/k8s/helm/ingress_values_dockerk8s.yaml @@ -0,0 +1,5 @@ +ingress: + annotations: + kubernetes.io/ingress.class: "nginx" + ingress.kubernetes.io/ssl-redirect: "false" + nginx.ingress.kubernetes.io/ssl-redirect: "false" diff --git a/k8s/helm/marketing-api/templates/configmap.yaml b/k8s/helm/marketing-api/templates/configmap.yaml index ea99a1b5a..45f21e57d 100644 --- a/k8s/helm/marketing-api/templates/configmap.yaml +++ b/k8s/helm/marketing-api/templates/configmap.yaml @@ -2,6 +2,7 @@ {{- $identity := include "url-of" (list .Values.app.ingress.entries.identity .) -}} {{- $webshoppingapigw := include "url-of" (list .Values.app.ingress.entries.webshoppingapigw .) -}} {{- $mongo := include "mongo-name" . -}} +{{- $sqlsrv := include "sql-name" . -}} apiVersion: v1 kind: ConfigMap @@ -19,6 +20,7 @@ data: all_EnableLoadTest: "{{ .Values.inf.misc.useLoadTest }}" internalurls__IdentityUrl: http://{{ .Values.app.svc.identity }} urls__IdentityUrl: {{ $identity }} - marketing__ConnectionString: mongodb://{{ $mongo }} - marketing__Database: {{ .Values.inf.mongo.marketing.database }} + marketing__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.marketing.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }}; + marketing__MongoConnectionString: mongodb://{{ $mongo }} + marketing__MongoDatabase: {{ .Values.inf.mongo.marketing.database }} marketing__PicBaseUrl: http://{{ $webshoppingapigw }}/api/v1/c/catalog/items/[0]/pic/ \ No newline at end of file diff --git a/k8s/helm/marketing-api/values.yaml b/k8s/helm/marketing-api/values.yaml index 8779685d6..b898af73c 100644 --- a/k8s/helm/marketing-api/values.yaml +++ b/k8s/helm/marketing-api/values.yaml @@ -44,8 +44,10 @@ env: key: urls__IdentityUrl - name: ConnectionString key: marketing__ConnectionString - - name: Database - key: marketing__Database + - name: MongoConnectionString + key: marketing__MongoConnectionString + - name: MongoDatabase + key: marketing__MongoDatabase - name: PicBaseUrl key: marketing__PicBaseUrl # values define environment variables with a fixed value (no configmap involved) (name is name of var, and value is its value) diff --git a/k8s/helm/mobileshoppingagg/templates/configmap.yaml b/k8s/helm/mobileshoppingagg/templates/configmap.yaml index 10eb543b2..d3e935409 100644 --- a/k8s/helm/mobileshoppingagg/templates/configmap.yaml +++ b/k8s/helm/mobileshoppingagg/templates/configmap.yaml @@ -17,3 +17,10 @@ data: internalurls__catalog: http://{{ .Values.app.svc.catalog }} internalurls__identity: http://{{ .Values.app.svc.identity }} internalurls__ordering: http://{{ .Values.app.svc.ordering }} + internalurls__basket__hc: http://{{ .Values.app.svc.basket }}/hc + internalurls__catalog__hc: http://{{ .Values.app.svc.catalog }}/hc + internalurls__identity__hc: http://{{ .Values.app.svc.identity }}/hc + internalurls__ordering__hc: http://{{ .Values.app.svc.ordering }}/hc + internalurls__marketing__hc: http://{{ .Values.app.svc.marketing }}/hc + internalurls__payment__hc: http://{{ .Values.app.svc.payment }}/hc + internalurls__location__hc: http://{{ .Values.app.svc.locations }}/hc diff --git a/k8s/helm/mobileshoppingagg/values.yaml b/k8s/helm/mobileshoppingagg/values.yaml index 039695024..7773891e7 100644 --- a/k8s/helm/mobileshoppingagg/values.yaml +++ b/k8s/helm/mobileshoppingagg/values.yaml @@ -40,6 +40,20 @@ env: key: internalurls__ordering - name: urls__identity key: internalurls__identity + - name: CatalogUrlHC + key: internalurls__catalog__hc + - name: BasketUrlHC + key: internalurls__basket__hc + - name: IdentityUrlHC + key: internalurls__identity__hc + - name: OrderingUrlHC + key: internalurls__ordering__hc + - name: MarketingUrlHC + key: internalurls__marketing__hc + - name: PaymentUrlHC + key: internalurls__payment__hc + - name: LocationUrlHC + key: internalurls__location__hc # values define environment variables with a fixed value (no configmap involved) (name is name of var, and value is its value) values: - name: ASPNETCORE_ENVIRONMENT diff --git a/k8s/helm/webmvc/templates/configmap.yaml b/k8s/helm/webmvc/templates/configmap.yaml index 9d120fe7b..60dacdadd 100644 --- a/k8s/helm/webmvc/templates/configmap.yaml +++ b/k8s/helm/webmvc/templates/configmap.yaml @@ -21,12 +21,9 @@ data: webmvc__keystore: {{ .Values.inf.redis.keystore.constr }} internalurls__apigwws: http://{{ .Values.app.svc.webshoppingapigw }} internalurls__apigwwm: http://{{ .Values.app.svc.webmarketingapigw }} - internalurls__basket__hc: http://{{ .Values.app.svc.basket }}/hc - internalurls__catalog__hc: http://{{ .Values.app.svc.catalog }}/hc + internalurls__apigwws__hc: http://{{ .Values.app.svc.webshoppingapigw }}/hc internalurls__identity__hc: http://{{ .Values.app.svc.identity }}/hc - internalurls__ordering__hc: http://{{ .Values.app.svc.ordering }}/hc - internalurls__marketing__hc: http://{{ .Values.app.svc.marketing }}/hc - internalurls__payment__hc: http://{{ .Values.app.svc.payment }}/hc + internalurls__apigwwm__hc: http://{{ .Values.app.svc.webmarketingapigw }}/hc urls__apigwws: http://{{ $webshoppingapigw }} urls__mvc: http://{{ $mvc }} urls__IdentityUrl: http://{{ $identity }} diff --git a/k8s/helm/webmvc/values.yaml b/k8s/helm/webmvc/values.yaml index f37dc104e..f4d077fc2 100644 --- a/k8s/helm/webmvc/values.yaml +++ b/k8s/helm/webmvc/values.yaml @@ -46,18 +46,12 @@ env: key: urls__IdentityUrl - name: MarketingUrl key: internalurls__apigwwm - - name: BasketUrlHC - key: internalurls__basket__hc - - name: CatalogUrlHC - key: internalurls__catalog__hc + - name: PurchaseUrlHC + key: internalurls__apigwws__hc - name: IdentityUrlHC key: internalurls__identity__hc - - name: OrderingUrlHC - key: internalurls__ordering__hc - name: MarketingUrlHC - key: internalurls__marketing__hc - - name: PaymentUrlHC - key: internalurls__payment__hc + key: internalurls__apigwwm__hc - name: SignalrHubUrl key: urls__apigwws # values define environment variables with a fixed value (no configmap involved) (name is name of var, and value is its value) diff --git a/k8s/helm/webshoppingagg/templates/configmap.yaml b/k8s/helm/webshoppingagg/templates/configmap.yaml index c03f2272d..b4d3da041 100644 --- a/k8s/helm/webshoppingagg/templates/configmap.yaml +++ b/k8s/helm/webshoppingagg/templates/configmap.yaml @@ -17,3 +17,10 @@ data: internalurls__catalog: http://{{ .Values.app.svc.catalog }} internalurls__identity: http://{{ .Values.app.svc.identity }} internalurls__ordering: http://{{ .Values.app.svc.ordering }} + internalurls__basket__hc: http://{{ .Values.app.svc.basket }}/hc + internalurls__catalog__hc: http://{{ .Values.app.svc.catalog }}/hc + internalurls__identity__hc: http://{{ .Values.app.svc.identity }}/hc + internalurls__ordering__hc: http://{{ .Values.app.svc.ordering }}/hc + internalurls__marketing__hc: http://{{ .Values.app.svc.marketing }}/hc + internalurls__payment__hc: http://{{ .Values.app.svc.payment }}/hc + internalurls__location__hc: http://{{ .Values.app.svc.locations }}/hc diff --git a/k8s/helm/webshoppingagg/values.yaml b/k8s/helm/webshoppingagg/values.yaml index 9f855d796..78c826e42 100644 --- a/k8s/helm/webshoppingagg/values.yaml +++ b/k8s/helm/webshoppingagg/values.yaml @@ -40,6 +40,20 @@ env: key: internalurls__ordering - name: urls__identity key: internalurls__identity + - name: CatalogUrlHC + key: internalurls__catalog__hc + - name: BasketUrlHC + key: internalurls__basket__hc + - name: IdentityUrlHC + key: internalurls__identity__hc + - name: OrderingUrlHC + key: internalurls__ordering__hc + - name: MarketingUrlHC + key: internalurls__marketing__hc + - name: PaymentUrlHC + key: internalurls__payment__hc + - name: LocationUrlHC + key: internalurls__location__hc # values define environment variables with a fixed value (no configmap involved) (name is name of var, and value is its value) values: - name: ASPNETCORE_ENVIRONMENT diff --git a/k8s/helm/webspa/templates/configmap.yaml b/k8s/helm/webspa/templates/configmap.yaml index bf03ff8cd..7d4651a31 100644 --- a/k8s/helm/webspa/templates/configmap.yaml +++ b/k8s/helm/webspa/templates/configmap.yaml @@ -20,12 +20,9 @@ data: all_EnableLoadTest: "{{ .Values.inf.misc.useLoadTest }}" webspa__keystore: {{ .Values.inf.redis.keystore.constr }} internalurls__apigwws: http://{{ .Values.app.svc.webshoppingapigw }} - internalurls__basket__hc: http://{{ .Values.app.svc.basket }}/hc - internalurls__catalog__hc: http://{{ .Values.app.svc.catalog }}/hc + internalurls__apigwws__hc: http://{{ .Values.app.svc.webshoppingapigw }}/hc internalurls__identity__hc: http://{{ .Values.app.svc.identity }}/hc - internalurls__ordering__hc: http://{{ .Values.app.svc.ordering }}/hc - internalurls__marketing__hc: http://{{ .Values.app.svc.marketing }}/hc - internalurls__payment__hc: http://{{ .Values.app.svc.payment }}/hc + internalurls__apigwwm__hc: http://{{ .Values.app.svc.webmarketingapigw }}/hc urls__apigwws: http://{{ $webshoppingapigw }} urls__spa: http://{{ $spa }} urls__IdentityUrl: http://{{ $identity }} diff --git a/k8s/helm/webspa/values.yaml b/k8s/helm/webspa/values.yaml index e07909f6e..056b58a0d 100644 --- a/k8s/helm/webspa/values.yaml +++ b/k8s/helm/webspa/values.yaml @@ -44,18 +44,12 @@ env: key: urls__IdentityUrl - name: MarketingUrl key: urls__apigwwm - - name: BasketUrlHC - key: internalurls__basket__hc - - name: CatalogUrlHC - key: internalurls__catalog__hc + - name: PurchaseUrlHC + key: internalurls__apigwws__hc - name: IdentityUrlHC key: internalurls__identity__hc - - name: OrderingUrlHC - key: internalurls__ordering__hc - name: MarketingUrlHC - key: internalurls__marketing__hc - - name: PaymentUrlHC - key: internalurls__payment__hc + key: internalurls__apigwwm__hc - name: SignalrHubUrl key: urls__apigwws # values define environment variables with a fixed value (no configmap involved) (name is name of var, and value is its value) diff --git a/k8s/helm/webstatus/templates/configmap.yaml b/k8s/helm/webstatus/templates/configmap.yaml index 805e33165..3f32d7adc 100644 --- a/k8s/helm/webstatus/templates/configmap.yaml +++ b/k8s/helm/webstatus/templates/configmap.yaml @@ -19,18 +19,37 @@ data: all__UseAzureServiceBus: "{{ .Values.inf.eventbus.useAzure }}" all_EnableLoadTest: "{{ .Values.inf.misc.useLoadTest }}" webstatus__keystore: {{ .Values.inf.redis.keystore.constr }} - internalurls__apigwws: http://{{ .Values.app.svc.webshoppingapigw }} - internalurls__apigwwm: http://{{ .Values.app.svc.webmarketingapigw }} + name__mvc__hc: WebMVC HTTP Check + internalurls__mvc__hc: http://{{ .Values.app.svc.mvc }}/hc + name__spa__hc: WebSPA HTTP Check + internalurls__spa__hc: http://{{ .Values.app.svc.spa }}/hc + name__apigwws__hc: Web Shopping API GW HTTP Check + internalurls__apigwws__hc: http://{{ .Values.app.svc.webshoppingapigw }}/hc + name__apigwwm__hc: Web Marketing API GW HTTP Check + internalurls__apigwwm__hc: http://{{ .Values.app.svc.webmarketingapigw }}/hc + name__apigwms__hc: Mobile Shopping API GW HTTP Check + internalurls__apigwms__hc: http://{{ .Values.app.svc.mobileshoppingapigw }}/hc + name__apigwmm__hc: Mobile Marketing API GW HTTP Check + internalurls__apigwmm__hc: http://{{ .Values.app.svc.mobilemarketingapigw }}/hc + name__apigwwsagg__hc: Web Shopping Aggregator GW HTTP Check + internalurls__apigwwsagg__hc: http://{{ .Values.app.svc.webshoppingagg }}/hc + name__apigwmsagg__hc: Mobile Shopping Aggregator HTTP Check + internalurls__apigwmsagg__hc: http://{{ .Values.app.svc.mobileshoppingagg }}/hc + name__ordering__hc: Ordering HTTP Check + internalurls__ordering__hc: http://{{ .Values.app.svc.ordering }}/hc + name__orderingbackground__hc: Ordering HTTP Background Check + internalurls__orderingbackground__hc: http://{{ .Values.app.svc.orderingbackgroundtasks }}/hc + name__basket__hc: Basket HTTP Check internalurls__basket__hc: http://{{ .Values.app.svc.basket }}/hc + name__catalog__hc: Catalog HTTP Check internalurls__catalog__hc: http://{{ .Values.app.svc.catalog }}/hc + name__identity__hc: Identity HTTP Check internalurls__identity__hc: http://{{ .Values.app.svc.identity }}/hc - internalurls__ordering__hc: http://{{ .Values.app.svc.ordering }}/hc + name__marketing__hc: Marketing HTTP Check internalurls__marketing__hc: http://{{ .Values.app.svc.marketing }}/hc - internalurls__payment__hc: http://{{ .Values.app.svc.payment }}/hc + name__locations__hc: Locations HTTP Check internalurls__locations__hc: http://{{ .Values.app.svc.locations }}/hc - internalurls__orderingbackground__hc: http://{{ .Values.app.svc.orderingbackgroundtasks }}/hc - internalurls__mvc__hc: http://{{ .Values.app.svc.mvc }}/hc - internalurls__spa__hc: http://{{ .Values.app.svc.spa }}/hc - urls__apigwws: http://{{ $webshoppingapigw }} - urls__mvc: http://{{ $mvc }} - urls__IdentityUrl: http://{{ $identity }} + name__payment__hc: Payment HTTP Check + internalurls__payment__hc: http://{{ .Values.app.svc.payment }}/hc + name__signalrhub__hc: Ordering SignalR Hub HTTP Check + internalurls__signalrhub__hc: http://{{ .Values.app.svc.orderingsignalrhub }}/hc diff --git a/k8s/helm/webstatus/values.yaml b/k8s/helm/webstatus/values.yaml index 8e5912fda..88cae5d7f 100644 --- a/k8s/helm/webstatus/values.yaml +++ b/k8s/helm/webstatus/values.yaml @@ -13,7 +13,9 @@ service: ingress: enabled: true - annotations: {} + annotations: { + + } tls: [] resources: {} @@ -32,27 +34,73 @@ env: configmap: - name: ApplicationInsights__InstrumentationKey key: all__InstrumentationKey - - name: BasketUrl + - name: HealthChecks-UI__HealthChecks__0__Name + key: name__mvc__hc + - name: HealthChecks-UI__HealthChecks__0__Uri + key: internalurls__mvc__hc + - name: HealthChecks-UI__HealthChecks__1__Name + key: name__spa__hc + - name: HealthChecks-UI__HealthChecks__1__Uri + key: internalurls__spa__hc + - name: HealthChecks-UI__HealthChecks__2__Name + key: name__apigwws__hc + - name: HealthChecks-UI__HealthChecks__2__Uri + key: internalurls__apigwws__hc + - name: HealthChecks-UI__HealthChecks__3__Name + key: name__apigwwm__hc + - name: HealthChecks-UI__HealthChecks__3__Uri + key: internalurls__apigwwm__hc + - name: HealthChecks-UI__HealthChecks__4__Name + key: name__apigwms__hc + - name: HealthChecks-UI__HealthChecks__4__Uri + key: internalurls__apigwms__hc + - name: HealthChecks-UI__HealthChecks__5__Name + key: name__apigwmm__hc + - name: HealthChecks-UI__HealthChecks__5__Uri + key: internalurls__apigwmm__hc + - name: HealthChecks-UI__HealthChecks__6__Name + key: name__apigwwsagg__hc + - name: HealthChecks-UI__HealthChecks__6__Uri + key: internalurls__apigwwsagg__hc + - name: HealthChecks-UI__HealthChecks__7__Name + key: name__apigwmsagg__hc + - name: HealthChecks-UI__HealthChecks__7__Uri + key: internalurls__apigwmsagg__hc + - name: HealthChecks-UI__HealthChecks__8__Name + key: name__ordering__hc + - name: HealthChecks-UI__HealthChecks__8__Uri + key: internalurls__ordering__hc + - name: HealthChecks-UI__HealthChecks__9__Name + key: name__orderingbackground__hc + - name: HealthChecks-UI__HealthChecks__9__Uri + key: internalurls__orderingbackground__hc + - name: HealthChecks-UI__HealthChecks__10__Name + key: name__signalrhub__hc + - name: HealthChecks-UI__HealthChecks__10__Uri + key: internalurls__signalrhub__hc + - name: HealthChecks-UI__HealthChecks__11__Name + key: name__basket__hc + - name: HealthChecks-UI__HealthChecks__11__Uri key: internalurls__basket__hc - - name: CatalogUrl + - name: HealthChecks-UI__HealthChecks__12__Name + key: name__catalog__hc + - name: HealthChecks-UI__HealthChecks__12__Uri key: internalurls__catalog__hc - - name: IdentityUrl + - name: HealthChecks-UI__HealthChecks__13__Name + key: name__identity__hc + - name: HealthChecks-UI__HealthChecks__13__Uri key: internalurls__identity__hc - - name: OrderingUrl - key: internalurls__ordering__hc - - name: OrderingBackgroundTasksUrl - key: internalurls__orderingbackground__hc - - name: LocationsUrl - key: internalurls__locations__hc - - name: MarketingUrl + - name: HealthChecks-UI__HealthChecks__14__Name + key: name__marketing__hc + - name: HealthChecks-UI__HealthChecks__14__Uri key: internalurls__marketing__hc - - name: IdentityUrlHC - key: internalurls__identity__hc - - name: mvc - key: internalurls__mvc__hc - - name: spa - key: internalurls__spa__hc - - name: PaymentUrl + - name: HealthChecks-UI__HealthChecks__15__Name + key: name__locations__hc + - name: HealthChecks-UI__HealthChecks__15__Uri + key: internalurls__locations__hc + - name: HealthChecks-UI__HealthChecks__16__Name + key: name__payment__hc + - name: HealthChecks-UI__HealthChecks__16__Uri key: internalurls__payment__hc # values define environment variables with a fixed value (no configmap involved) (name is name of var, and value is its value) values: diff --git a/k8s/nginx-ingress/azure/service.yaml b/k8s/nginx-ingress/azure/service.yaml deleted file mode 100644 index 8d2f71505..000000000 --- a/k8s/nginx-ingress/azure/service.yaml +++ /dev/null @@ -1,19 +0,0 @@ -kind: Service -apiVersion: v1 -metadata: - name: ingress-nginx - namespace: ingress-nginx - labels: - app: ingress-nginx -spec: - externalTrafficPolicy: Local - type: LoadBalancer - selector: - app: ingress-nginx - ports: - - name: http - port: 80 - targetPort: http - - name: https - port: 443 - targetPort: https diff --git a/k8s/nginx-ingress/cloud-generic.yaml b/k8s/nginx-ingress/cloud-generic.yaml new file mode 100644 index 000000000..945441ab8 --- /dev/null +++ b/k8s/nginx-ingress/cloud-generic.yaml @@ -0,0 +1,21 @@ +kind: Service +apiVersion: v1 +metadata: + name: ingress-nginx + namespace: ingress-nginx + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx +spec: + externalTrafficPolicy: Local + type: LoadBalancer + selector: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + ports: + - name: http + port: 80 + targetPort: http + - name: https + port: 443 + targetPort: https \ No newline at end of file diff --git a/k8s/nginx-ingress/cm.yaml b/k8s/nginx-ingress/cm.yaml new file mode 100644 index 000000000..7818fd15b Binary files /dev/null and b/k8s/nginx-ingress/cm.yaml differ diff --git a/k8s/nginx-ingress/configmap.yaml b/k8s/nginx-ingress/configmap.yaml deleted file mode 100644 index 6703fc38e..000000000 --- a/k8s/nginx-ingress/configmap.yaml +++ /dev/null @@ -1,11 +0,0 @@ -kind: ConfigMap -apiVersion: v1 -metadata: - name: nginx-configuration - namespace: ingress-nginx - labels: - app: ingress-nginx -data: - ssl-redirect: "false" - proxy-buffer-size: "128k" - proxy-buffers: "4 256k" diff --git a/k8s/nginx-ingress/default-backend.yaml b/k8s/nginx-ingress/default-backend.yaml deleted file mode 100644 index 64f6f58ad..000000000 --- a/k8s/nginx-ingress/default-backend.yaml +++ /dev/null @@ -1,52 +0,0 @@ -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - name: default-http-backend - labels: - app: default-http-backend - namespace: ingress-nginx -spec: - replicas: 1 - template: - metadata: - labels: - app: default-http-backend - spec: - terminationGracePeriodSeconds: 60 - containers: - - name: default-http-backend - # Any image is permissable as long as: - # 1. It serves a 404 page at / - # 2. It serves 200 on a /healthz endpoint - image: gcr.io/google_containers/defaultbackend:1.4 - livenessProbe: - httpGet: - path: /healthz - port: 8080 - scheme: HTTP - initialDelaySeconds: 30 - timeoutSeconds: 5 - ports: - - containerPort: 8080 - resources: - limits: - cpu: 10m - memory: 20Mi - requests: - cpu: 10m - memory: 20Mi ---- - -apiVersion: v1 -kind: Service -metadata: - name: default-http-backend - namespace: ingress-nginx - labels: - app: default-http-backend -spec: - ports: - - port: 80 - targetPort: 8080 - selector: - app: default-http-backend diff --git a/k8s/nginx-ingress/local-dockerk8s/identityapi-cm-fix.yaml b/k8s/nginx-ingress/local-dockerk8s/identityapi-cm-fix.yaml new file mode 100644 index 000000000..3a3fcf5a5 --- /dev/null +++ b/k8s/nginx-ingress/local-dockerk8s/identityapi-cm-fix.yaml @@ -0,0 +1,3 @@ +data: + mvc_e: http://10.0.75.1/webmvc + \ No newline at end of file diff --git a/k8s/nginx-ingress/local-dockerk8s/mvc-cm-fix.yaml b/k8s/nginx-ingress/local-dockerk8s/mvc-cm-fix.yaml new file mode 100644 index 000000000..1475deec1 --- /dev/null +++ b/k8s/nginx-ingress/local-dockerk8s/mvc-cm-fix.yaml @@ -0,0 +1,3 @@ +data: + urls__IdentityUrl: http://10.0.75.1/identity + urls__mvc: http://10.0.75.1/webmvc diff --git a/k8s/nginx-ingress/local-dockerk8s/mvc-fix.yaml b/k8s/nginx-ingress/local-dockerk8s/mvc-fix.yaml new file mode 100644 index 000000000..b9ecd4cba --- /dev/null +++ b/k8s/nginx-ingress/local-dockerk8s/mvc-fix.yaml @@ -0,0 +1,39 @@ +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + annotations: + ingress.kubernetes.io/ssl-redirect: "false" + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/ssl-redirect: "false" + labels: + app: webmvc + name: eshop-webmvc-loopback + namespace: default +spec: + rules: + - http: + paths: + - backend: + serviceName: webmvc + servicePort: http + path: /webmvc +--- +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + annotations: + ingress.kubernetes.io/ssl-redirect: "false" + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/ssl-redirect: "false" + labels: + app: identity-api + name: eshop-identity-api-loopback + namespace: default +spec: + rules: + - http: + paths: + - backend: + serviceName: identity + servicePort: http + path: /identity \ No newline at end of file diff --git a/k8s/nginx-ingress/mandatory.yaml b/k8s/nginx-ingress/mandatory.yaml new file mode 100644 index 000000000..56b1cc3b5 --- /dev/null +++ b/k8s/nginx-ingress/mandatory.yaml @@ -0,0 +1,238 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: ingress-nginx + +--- + +kind: ConfigMap +apiVersion: v1 +metadata: + name: nginx-configuration + namespace: ingress-nginx + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: nginx-ingress-serviceaccount + namespace: ingress-nginx + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRole +metadata: + name: nginx-ingress-clusterrole + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx +rules: + - apiGroups: + - "" + resources: + - configmaps + - endpoints + - nodes + - pods + - secrets + verbs: + - list + - watch + - apiGroups: + - "" + resources: + - nodes + verbs: + - get + - apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch + - apiGroups: + - "extensions" + resources: + - ingresses + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - apiGroups: + - "extensions" + resources: + - ingresses/status + verbs: + - update + +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: Role +metadata: + name: nginx-ingress-role + namespace: ingress-nginx + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx +rules: + - apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - namespaces + verbs: + - get + - apiGroups: + - "" + resources: + - configmaps + resourceNames: + # Defaults to "-" + # Here: "-" + # This has to be adapted if you change either parameter + # when launching the nginx-ingress-controller. + - "ingress-controller-leader-nginx" + verbs: + - get + - update + - apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - apiGroups: + - "" + resources: + - endpoints + verbs: + - get + +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: RoleBinding +metadata: + name: nginx-ingress-role-nisa-binding + namespace: ingress-nginx + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: nginx-ingress-role +subjects: + - kind: ServiceAccount + name: nginx-ingress-serviceaccount + namespace: ingress-nginx + +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRoleBinding +metadata: + name: nginx-ingress-clusterrole-nisa-binding + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: nginx-ingress-clusterrole +subjects: + - kind: ServiceAccount + name: nginx-ingress-serviceaccount + namespace: ingress-nginx + +--- + +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: nginx-ingress-controller + namespace: ingress-nginx + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + template: + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + annotations: + prometheus.io/port: "10254" + prometheus.io/scrape: "true" + spec: + serviceAccountName: nginx-ingress-serviceaccount + containers: + - name: nginx-ingress-controller + image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.20.0 + args: + - /nginx-ingress-controller + - --configmap=$(POD_NAMESPACE)/nginx-configuration + - --publish-service=$(POD_NAMESPACE)/ingress-nginx + - --annotations-prefix=nginx.ingress.kubernetes.io + securityContext: + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + # www-data -> 33 + runAsUser: 33 + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + ports: + - name: http + containerPort: 80 + - name: https + containerPort: 443 + livenessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + readinessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 diff --git a/k8s/nginx-ingress/namespace.yaml b/k8s/nginx-ingress/namespace.yaml deleted file mode 100644 index 6878f0be8..000000000 --- a/k8s/nginx-ingress/namespace.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: ingress-nginx diff --git a/k8s/nginx-ingress/patch-service-without-rbac.yaml b/k8s/nginx-ingress/patch-service-without-rbac.yaml deleted file mode 100644 index 919efc389..000000000 --- a/k8s/nginx-ingress/patch-service-without-rbac.yaml +++ /dev/null @@ -1,40 +0,0 @@ -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - name: nginx-ingress-controller - namespace: ingress-nginx -spec: - replicas: 1 - selector: - matchLabels: - app: ingress-nginx - template: - metadata: - labels: - app: ingress-nginx - spec: - containers: - - name: nginx-ingress-controller - image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.9.0 - args: - - /nginx-ingress-controller - - --default-backend-service=$(POD_NAMESPACE)/default-http-backend - - --configmap=$(POD_NAMESPACE)/nginx-configuration - - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services - - --udp-services-configmap=$(POD_NAMESPACE)/udp-services - - --publish-service=$(POD_NAMESPACE)/ingress-nginx - - --annotations-prefix=nginx.ingress.kubernetes.io - env: - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - ports: - - name: http - containerPort: 80 - - name: https - containerPort: 443 diff --git a/k8s/nginx-ingress/publish-service-patch.yaml b/k8s/nginx-ingress/publish-service-patch.yaml deleted file mode 100644 index f8f52f772..000000000 --- a/k8s/nginx-ingress/publish-service-patch.yaml +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - 'op': 'add', - 'path': '/spec/template/spec/containers/0/args/-', - 'value': '--publish-service=$(POD_NAMESPACE)/ingress-nginx' - } -] diff --git a/k8s/nginx-ingress/service-nodeport.yaml b/k8s/nginx-ingress/service-nodeport.yaml new file mode 100644 index 000000000..dd82ed3ed --- /dev/null +++ b/k8s/nginx-ingress/service-nodeport.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: Service +metadata: + name: ingress-nginx + namespace: ingress-nginx + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx +spec: + type: NodePort + ports: + - name: http + port: 80 + targetPort: 80 + protocol: TCP + - name: https + port: 443 + targetPort: 443 + protocol: TCP + selector: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx diff --git a/k8s/nginx-ingress/tcp-services-configmap.yaml b/k8s/nginx-ingress/tcp-services-configmap.yaml deleted file mode 100644 index a963085d3..000000000 --- a/k8s/nginx-ingress/tcp-services-configmap.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: ConfigMap -apiVersion: v1 -metadata: - name: tcp-services - namespace: ingress-nginx diff --git a/k8s/nginx-ingress/udp-services-configmap.yaml b/k8s/nginx-ingress/udp-services-configmap.yaml deleted file mode 100644 index 1870931a2..000000000 --- a/k8s/nginx-ingress/udp-services-configmap.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: ConfigMap -apiVersion: v1 -metadata: - name: udp-services - namespace: ingress-nginx diff --git a/k8s/nginx-ingress/without-rbac.yaml b/k8s/nginx-ingress/without-rbac.yaml deleted file mode 100644 index 1c46b73eb..000000000 --- a/k8s/nginx-ingress/without-rbac.yaml +++ /dev/null @@ -1,61 +0,0 @@ -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - name: nginx-ingress-controller - namespace: ingress-nginx -spec: - replicas: 1 - selector: - matchLabels: - app: ingress-nginx - template: - metadata: - labels: - app: ingress-nginx - annotations: - prometheus.io/port: '10254' - prometheus.io/scrape: 'true' - spec: - containers: - - name: nginx-ingress-controller - image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.9.0 - args: - - /nginx-ingress-controller - - --default-backend-service=$(POD_NAMESPACE)/default-http-backend - - --configmap=$(POD_NAMESPACE)/nginx-configuration - - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services - - --udp-services-configmap=$(POD_NAMESPACE)/udp-services - - --annotations-prefix=nginx.ingress.kubernetes.io - env: - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - ports: - - name: http - containerPort: 80 - - name: https - containerPort: 443 - livenessProbe: - failureThreshold: 3 - httpGet: - path: /healthz - port: 10254 - scheme: HTTP - initialDelaySeconds: 10 - periodSeconds: 10 - successThreshold: 1 - timeoutSeconds: 1 - readinessProbe: - failureThreshold: 3 - httpGet: - path: /healthz - port: 10254 - scheme: HTTP - periodSeconds: 10 - successThreshold: 1 - timeoutSeconds: 1 diff --git a/src/ApiGateways/ApiGw-Base/Dockerfile b/src/ApiGateways/ApiGw-Base/Dockerfile index 7fdd5f073..eb94675b9 100644 --- a/src/ApiGateways/ApiGw-Base/Dockerfile +++ b/src/ApiGateways/ApiGw-Base/Dockerfile @@ -1,13 +1,12 @@ -FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +FROM microsoft/dotnet:2.2.0-aspnetcore-runtime AS base WORKDIR /app EXPOSE 80 -FROM microsoft/dotnet:2.1-sdk AS build +FROM microsoft/dotnet:2.2.100-sdk AS build WORKDIR /src -COPY src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj src/ApiGateways/ApiGw-Base/ -RUN dotnet restore src/ApiGateways/ApiGw-Base/ COPY . . WORKDIR /src/src/ApiGateways/ApiGw-Base/ +RUN dotnet restore -nowarn:msb3202,nu1503 RUN dotnet build -c Release -o /app FROM build AS publish diff --git a/src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj b/src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj index d3b1a049b..cf963debc 100644 --- a/src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj +++ b/src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp2.2 @@ -9,7 +9,13 @@ - - + + + + + + + + diff --git a/src/ApiGateways/ApiGw-Base/Program.cs b/src/ApiGateways/ApiGw-Base/Program.cs index effd5684e..c8f8d7993 100644 --- a/src/ApiGateways/ApiGw-Base/Program.cs +++ b/src/ApiGateways/ApiGw-Base/Program.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; +using Serilog; namespace OcelotApiGw { @@ -23,7 +24,14 @@ namespace OcelotApiGw IWebHostBuilder builder = WebHost.CreateDefaultBuilder(args); builder.ConfigureServices(s => s.AddSingleton(builder)) .ConfigureAppConfiguration(ic => ic.AddJsonFile(Path.Combine("configuration", "configuration.json"))) - .UseStartup(); + .UseStartup() + .UseSerilog((builderContext, config) => + { + config + .MinimumLevel.Information() + .Enrich.FromLogContext() + .WriteTo.Console(); + }); IWebHost host = builder.Build(); return host; } diff --git a/src/ApiGateways/ApiGw-Base/Startup.cs b/src/ApiGateways/ApiGw-Base/Startup.cs index f6a36b59e..cd90bd7ca 100644 --- a/src/ApiGateways/ApiGw-Base/Startup.cs +++ b/src/ApiGateways/ApiGw-Base/Startup.cs @@ -1,22 +1,19 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using CacheManager.Core; -using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Ocelot.DependencyInjection; using Ocelot.Middleware; +using System; +using HealthChecks.UI.Client; +using Microsoft.Extensions.Diagnostics.HealthChecks; namespace OcelotApiGw { public class Startup { - private readonly IConfiguration _cfg; public Startup(IConfiguration configuration) @@ -29,12 +26,23 @@ namespace OcelotApiGw var identityUrl = _cfg.GetValue("IdentityUrl"); var authenticationProviderKey = "IdentityApiKey"; + services.AddHealthChecks() + .AddCheck("self", () => HealthCheckResult.Healthy()) + .AddUrlGroup(new Uri(_cfg["CatalogUrlHC"]), name: "catalogapi-check", tags: new string[] { "catalogapi" }) + .AddUrlGroup(new Uri(_cfg["OrderingUrlHC"]), name: "orderingapi-check", tags: new string[] { "orderingapi" }) + .AddUrlGroup(new Uri(_cfg["BasketUrlHC"]), name: "basketapi-check", tags: new string[] { "basketapi" }) + .AddUrlGroup(new Uri(_cfg["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" }) + .AddUrlGroup(new Uri(_cfg["MarketingUrlHC"]), name: "marketingapi-check", tags: new string[] { "marketingapi" }) + .AddUrlGroup(new Uri(_cfg["PaymentUrlHC"]), name: "paymentapi-check", tags: new string[] { "paymentapi" }) + .AddUrlGroup(new Uri(_cfg["LocationUrlHC"]), name: "locationapi-check", tags: new string[] { "locationapi" }); + services.AddCors(options => { options.AddPolicy("CorsPolicy", - builder => builder.AllowAnyOrigin() + builder => builder .AllowAnyMethod() .AllowAnyHeader() + .SetIsOriginAllowed((host) => true) .AllowCredentials()); }); @@ -71,6 +79,7 @@ namespace OcelotApiGw public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { var pathBase = _cfg["PATH_BASE"]; + if (!string.IsNullOrEmpty(pathBase)) { app.UsePathBase(pathBase); @@ -81,6 +90,17 @@ namespace OcelotApiGw app.UseDeveloperExceptionPage(); } + app.UseHealthChecks("/hc", new HealthCheckOptions() + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); + + app.UseHealthChecks("/liveness", new HealthCheckOptions + { + Predicate = r => r.Name.Contains("self") + }); + loggerFactory.AddConsole(_cfg.GetSection("Logging")); app.UseCors("CorsPolicy"); diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Controllers/BasketController.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Controllers/BasketController.cs index 702c805fb..978b1858c 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Controllers/BasketController.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Controllers/BasketController.cs @@ -5,16 +5,19 @@ using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services; using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers { [Route("api/v1/[controller]")] [Authorize] - public class BasketController : Controller + [ApiController] + public class BasketController : ControllerBase { private readonly ICatalogService _catalog; private readonly IBasketService _basket; + public BasketController(ICatalogService catalogService, IBasketService basketService) { _catalog = catalogService; @@ -23,22 +26,24 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers [HttpPost] [HttpPut] - public async Task UpdateAllBasket([FromBody] UpdateBasketRequest data) + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(BasketData), (int)HttpStatusCode.OK)] + public async Task> UpdateAllBasketAsync([FromBody] UpdateBasketRequest data) { - if (data.Items == null || !data.Items.Any()) { return BadRequest("Need to pass at least one basket line"); } // Retrieve the current basket - var currentBasket = await _basket.GetById(data.BuyerId); + var currentBasket = await _basket.GetByIdAsync(data.BuyerId); + if (currentBasket == null) { currentBasket = new BasketData(data.BuyerId); } - var catalogItems = await _catalog.GetCatalogItems(data.Items.Select(x => x.ProductId)); + var catalogItems = await _catalog.GetCatalogItemsAsync(data.Items.Select(x => x.ProductId)); var newBasket = new BasketData(data.BuyerId); foreach (var bitem in data.Items) @@ -60,13 +65,16 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers }); } - await _basket.Update(newBasket); - return Ok(newBasket); + await _basket.UpdateAsync(newBasket); + + return newBasket; } [HttpPut] [Route("items")] - public async Task UpdateQuantities([FromBody] UpdateBasketItemsRequest data) + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(BasketData), (int)HttpStatusCode.OK)] + public async Task> UpdateQuantitiesAsync([FromBody] UpdateBasketItemsRequest data) { if (!data.Updates.Any()) { @@ -74,7 +82,7 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers } // Retrieve the current basket - var currentBasket = await _basket.GetById(data.BasketId); + var currentBasket = await _basket.GetByIdAsync(data.BasketId); if (currentBasket == null) { return BadRequest($"Basket with id {data.BasketId} not found."); @@ -84,21 +92,26 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers foreach (var update in data.Updates) { var basketItem = currentBasket.Items.SingleOrDefault(bitem => bitem.Id == update.BasketItemId); + if (basketItem == null) { return BadRequest($"Basket item with id {update.BasketItemId} not found"); } + basketItem.Quantity = update.NewQty; } // Save the updated basket - await _basket.Update(currentBasket); - return Ok(currentBasket); + await _basket.UpdateAsync(currentBasket); + + return currentBasket; } [HttpPost] [Route("items")] - public async Task AddBasketItem([FromBody] AddBasketItemRequest data) + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + [ProducesResponseType((int)HttpStatusCode.OK)] + public async Task AddBasketItemAsync([FromBody] AddBasketItemRequest data) { if (data == null || data.Quantity == 0) { @@ -106,12 +119,12 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers } // Step 1: Get the item from catalog - var item = await _catalog.GetCatalogItem(data.CatalogItemId); + var item = await _catalog.GetCatalogItemAsync(data.CatalogItemId); //item.PictureUri = // Step 2: Get current basket status - var currentBasket = (await _basket.GetById(data.BasketId)) ?? new BasketData(data.BasketId); + var currentBasket = (await _basket.GetByIdAsync(data.BasketId)) ?? new BasketData(data.BasketId); // Step 3: Merge current status with new product currentBasket.Items.Add(new BasketDataItem() { @@ -124,8 +137,7 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers }); // Step 4: Update basket - await _basket.Update(currentBasket); - + await _basket.UpdateAsync(currentBasket); return Ok(); } diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Controllers/OrderController.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Controllers/OrderController.cs index 4c18d25ae..a4b33c6cb 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Controllers/OrderController.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Controllers/OrderController.cs @@ -1,19 +1,20 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services; -using System; -using System.Collections.Generic; -using System.Linq; +using System.Net; using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers { [Route("api/v1/[controller]")] [Authorize] - public class OrderController : Controller + [ApiController] + public class OrderController : ControllerBase { private readonly IBasketService _basketService; private readonly IOrderApiClient _orderClient; + public OrderController(IBasketService basketService, IOrderApiClient orderClient) { _basketService = basketService; @@ -22,21 +23,23 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers [Route("draft/{basketId}")] [HttpGet] - public async Task GetOrderDraft(string basketId) + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(OrderData), (int)HttpStatusCode.OK)] + public async Task> GetOrderDraftAsync(string basketId) { if (string.IsNullOrEmpty(basketId)) { return BadRequest("Need a valid basketid"); } // Get the basket data and build a order draft based on it - var basket = await _basketService.GetById(basketId); + var basket = await _basketService.GetByIdAsync(basketId); + if (basket == null) { return BadRequest($"No basket found for id {basketId}"); } - var orderDraft = await _orderClient.GetOrderDraftFromBasket(basket); - return Ok(orderDraft); + return await _orderClient.GetOrderDraftFromBasketAsync(basket); } } } diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile index 7787dd159..be89c315f 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile @@ -1,8 +1,8 @@ -FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +FROM microsoft/dotnet:2.2.0-aspnetcore-runtime AS base WORKDIR /app EXPOSE 80 -FROM microsoft/dotnet:2.1-sdk AS build +FROM microsoft/dotnet:2.2.100-sdk AS build WORKDIR /src COPY . . WORKDIR /src/src/ApiGateways/Mobile.Bff.Shopping/aggregator diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj index d13a8a159..6300d6711 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp2.2 Mobile.Shopping.HttpAggregator Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator ..\..\..\docker-compose.dcproj @@ -12,9 +12,15 @@ + + + + + + - + diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Program.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Program.cs index 0c88fcd7d..fc21a70a4 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Program.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Program.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Serilog; namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator { @@ -31,6 +32,13 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator }); }) .UseStartup() + .UseSerilog((builderContext, config) => + { + config + .MinimumLevel.Information() + .Enrich.FromLogContext() + .WriteTo.Console(); + }) .Build(); } } diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/BasketService.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/BasketService.cs index 8339ee44b..5186fe361 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/BasketService.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/BasketService.cs @@ -22,7 +22,7 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services _urls = config.Value; } - public async Task GetById(string id) + public async Task GetByIdAsync(string id) { var data = await _httpClient.GetStringAsync(_urls.Basket + UrlsConfig.BasketOperations.GetItemById(id)); @@ -31,7 +31,7 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services return basket; } - public async Task Update(BasketData currentBasket) + public async Task UpdateAsync(BasketData currentBasket) { var basketContent = new StringContent(JsonConvert.SerializeObject(currentBasket), System.Text.Encoding.UTF8, "application/json"); diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/CatalogService.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/CatalogService.cs index 6c59f0c49..69bca2b39 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/CatalogService.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/CatalogService.cs @@ -22,7 +22,7 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services _urls = config.Value; } - public async Task GetCatalogItem(int id) + public async Task GetCatalogItemAsync(int id) { var stringContent = await _httpClient.GetStringAsync(_urls.Catalog + UrlsConfig.CatalogOperations.GetItemById(id)); var catalogItem = JsonConvert.DeserializeObject(stringContent); @@ -30,7 +30,7 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services return catalogItem; } - public async Task> GetCatalogItems(IEnumerable ids) + public async Task> GetCatalogItemsAsync(IEnumerable ids) { var stringContent = await _httpClient.GetStringAsync(_urls.Catalog + UrlsConfig.CatalogOperations.GetItemsById(ids)); var catalogItems = JsonConvert.DeserializeObject(stringContent); diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/IBasketService.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/IBasketService.cs index 6fd6871c1..ad49e1adb 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/IBasketService.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/IBasketService.cs @@ -1,15 +1,13 @@ using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; -using System; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services { public interface IBasketService { - Task GetById(string id); - Task Update(BasketData currentBasket); + Task GetByIdAsync(string id); + + Task UpdateAsync(BasketData currentBasket); } } diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/ICatalogService.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/ICatalogService.cs index d7e605bfa..832dfc740 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/ICatalogService.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/ICatalogService.cs @@ -1,14 +1,13 @@ using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; -using System; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services { public interface ICatalogService { - Task GetCatalogItem(int id); - Task> GetCatalogItems(IEnumerable ids); + Task GetCatalogItemAsync(int id); + + Task> GetCatalogItemsAsync(IEnumerable ids); } } diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/IOrderApiClient.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/IOrderApiClient.cs index 30ac013b9..1abe545bd 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/IOrderApiClient.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/IOrderApiClient.cs @@ -1,13 +1,10 @@ using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; -using System; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services { public interface IOrderApiClient { - Task GetOrderDraftFromBasket(BasketData basket); + Task GetOrderDraftFromBasketAsync(BasketData basket); } } diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/OrderApiClient.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/OrderApiClient.cs index 03644c110..da39abbff 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/OrderApiClient.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/OrderApiClient.cs @@ -21,7 +21,7 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services _urls = config.Value; } - public async Task GetOrderDraftFromBasket(BasketData basket) + public async Task GetOrderDraftFromBasketAsync(BasketData basket) { var uri = _urls.Orders + UrlsConfig.OrdersOperations.GetOrderDraft(); var content = new StringContent(JsonConvert.SerializeObject(basket), System.Text.Encoding.UTF8, "application/json"); diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs index 3a11975c7..eeb58ac3b 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config; using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Filters.Basket.API.Infrastructure.Filters; using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure; @@ -16,6 +17,9 @@ using Microsoft.Extensions.Logging; using Polly; using Polly.Extensions.Http; using Swashbuckle.AspNetCore.Swagger; +using HealthChecks.UI.Client; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.Diagnostics.HealthChecks; namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator { @@ -31,6 +35,16 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { + services.AddHealthChecks() + .AddCheck("self", () => HealthCheckResult.Healthy()) + .AddUrlGroup(new Uri(Configuration["CatalogUrlHC"]), name: "catalogapi-check", tags: new string[] { "catalogapi" }) + .AddUrlGroup(new Uri(Configuration["OrderingUrlHC"]), name: "orderingapi-check", tags: new string[] { "orderingapi" }) + .AddUrlGroup(new Uri(Configuration["BasketUrlHC"]), name: "basketapi-check", tags: new string[] { "basketapi" }) + .AddUrlGroup(new Uri(Configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" }) + .AddUrlGroup(new Uri(Configuration["MarketingUrlHC"]), name: "marketingapi-check", tags: new string[] { "marketingapi" }) + .AddUrlGroup(new Uri(Configuration["PaymentUrlHC"]), name: "paymentapi-check", tags: new string[] { "paymentapi" }) + .AddUrlGroup(new Uri(Configuration["LocationUrlHC"]), name: "locationapi-check", tags: new string[] { "locationapi" }); + services.AddCustomMvc(Configuration) .AddCustomAuthentication(Configuration) .AddHttpServices(); @@ -47,15 +61,31 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator app.UsePathBase(pathBase); } + app.UseHealthChecks("/hc", new HealthCheckOptions() + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); + + app.UseHealthChecks("/liveness", new HealthCheckOptions + { + Predicate = r => r.Name.Contains("self") + }); + app.UseCors("CorsPolicy"); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } + else + { + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } app.UseAuthentication(); - + app.UseHttpsRedirection(); app.UseMvc(); app.UseSwagger().UseSwaggerUI(c => @@ -77,7 +107,8 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator services.AddOptions(); services.Configure(configuration.GetSection("urls")); - services.AddMvc(); + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddSwaggerGen(options => { @@ -108,9 +139,10 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator services.AddCors(options => { options.AddPolicy("CorsPolicy", - builder => builder.AllowAnyOrigin() + builder => builder .AllowAnyMethod() .AllowAnyHeader() + .SetIsOriginAllowed((host) => true) .AllowCredentials()); }); @@ -120,12 +152,14 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator { JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); var identityUrl = configuration.GetValue("urls:identity"); + services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - }).AddJwtBearer(options => + }) + .AddJwtBearer(options => { options.Authority = identityUrl; options.RequireHttpsMetadata = false; @@ -143,6 +177,7 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator return services; } + public static IServiceCollection AddHttpServices(this IServiceCollection services) { //register delegating handlers @@ -163,8 +198,6 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator .AddPolicyHandler(GetRetryPolicy()) .AddPolicyHandler(GetCircuitBreakerPolicy()); - - return services; } @@ -174,7 +207,6 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator .HandleTransientHttpError() .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound) .WaitAndRetryAsync(6, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); - } private static IAsyncPolicy GetCircuitBreakerPolicy() diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Controllers/BasketController.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Controllers/BasketController.cs index bfef55726..6745ffa02 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Controllers/BasketController.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Controllers/BasketController.cs @@ -5,16 +5,19 @@ using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services; using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers { [Route("api/v1/[controller]")] [Authorize] - public class BasketController : Controller + [ApiController] + public class BasketController : ControllerBase { private readonly ICatalogService _catalog; private readonly IBasketService _basket; + public BasketController(ICatalogService catalogService, IBasketService basketService) { _catalog = catalogService; @@ -23,22 +26,23 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers [HttpPost] [HttpPut] - public async Task UpdateAllBasket([FromBody] UpdateBasketRequest data) + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(BasketData), (int)HttpStatusCode.OK)] + public async Task> UpdateAllBasketAsync([FromBody] UpdateBasketRequest data) { - if (data.Items == null || !data.Items.Any()) { return BadRequest("Need to pass at least one basket line"); } // Retrieve the current basket - var currentBasket = await _basket.GetById(data.BuyerId); + var currentBasket = await _basket.GetByIdAsync(data.BuyerId); if (currentBasket == null) { currentBasket = new BasketData(data.BuyerId); } - var catalogItems = await _catalog.GetCatalogItems(data.Items.Select(x => x.ProductId)); + var catalogItems = await _catalog.GetCatalogItemsAsync(data.Items.Select(x => x.ProductId)); var newBasket = new BasketData(data.BuyerId); foreach (var bitem in data.Items) @@ -60,13 +64,16 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers }); } - await _basket.Update(newBasket); - return Ok(newBasket); + await _basket.UpdateAsync(newBasket); + + return newBasket; } [HttpPut] [Route("items")] - public async Task UpdateQuantities([FromBody] UpdateBasketItemsRequest data) + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(BasketData), (int)HttpStatusCode.OK)] + public async Task> UpdateQuantitiesAsync([FromBody] UpdateBasketItemsRequest data) { if (!data.Updates.Any()) { @@ -74,7 +81,7 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers } // Retrieve the current basket - var currentBasket = await _basket.GetById(data.BasketId); + var currentBasket = await _basket.GetByIdAsync(data.BasketId); if (currentBasket == null) { return BadRequest($"Basket with id {data.BasketId} not found."); @@ -92,13 +99,16 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers } // Save the updated basket - await _basket.Update(currentBasket); - return Ok(currentBasket); + await _basket.UpdateAsync(currentBasket); + + return currentBasket; } [HttpPost] [Route("items")] - public async Task AddBasketItem([FromBody] AddBasketItemRequest data) + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + [ProducesResponseType((int)HttpStatusCode.OK)] + public async Task AddBasketItemAsync([FromBody] AddBasketItemRequest data) { if (data == null || data.Quantity == 0) { @@ -106,12 +116,12 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers } // Step 1: Get the item from catalog - var item = await _catalog.GetCatalogItem(data.CatalogItemId); + var item = await _catalog.GetCatalogItemAsync(data.CatalogItemId); //item.PictureUri = // Step 2: Get current basket status - var currentBasket = (await _basket.GetById(data.BasketId)) ?? new BasketData(data.BasketId); + var currentBasket = (await _basket.GetByIdAsync(data.BasketId)) ?? new BasketData(data.BasketId); // Step 3: Merge current status with new product currentBasket.Items.Add(new BasketDataItem() { @@ -124,8 +134,7 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers }); // Step 4: Update basket - await _basket.Update(currentBasket); - + await _basket.UpdateAsync(currentBasket); return Ok(); } diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Controllers/OrderController.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Controllers/OrderController.cs index fd108ffb8..de3e4cc55 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Controllers/OrderController.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Controllers/OrderController.cs @@ -1,16 +1,19 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models; using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services; using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers { [Route("api/v1/[controller]")] [Authorize] - public class OrderController : Controller + [ApiController] + public class OrderController : ControllerBase { private readonly IBasketService _basketService; private readonly IOrderApiClient _orderClient; @@ -22,21 +25,23 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers [Route("draft/{basketId}")] [HttpGet] - public async Task GetOrderDraft(string basketId) + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(OrderData), (int)HttpStatusCode.OK)] + public async Task> GetOrderDraftAsync(string basketId) { if (string.IsNullOrEmpty(basketId)) { return BadRequest("Need a valid basketid"); } // Get the basket data and build a order draft based on it - var basket = await _basketService.GetById(basketId); + var basket = await _basketService.GetByIdAsync(basketId); + if (basket == null) { return BadRequest($"No basket found for id {basketId}"); } - var orderDraft = await _orderClient.GetOrderDraftFromBasket(basket); - return Ok(orderDraft); + return await _orderClient.GetOrderDraftFromBasketAsync(basket); } } } diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile b/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile index d4940d436..236d3705a 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile @@ -1,8 +1,8 @@ -FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +FROM microsoft/dotnet:2.2.0-aspnetcore-runtime AS base WORKDIR /app EXPOSE 80 -FROM microsoft/dotnet:2.1-sdk AS build +FROM microsoft/dotnet:2.2.100-sdk AS build WORKDIR /src COPY . . WORKDIR /src/src/ApiGateways/Web.Bff.Shopping/aggregator diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Program.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Program.cs index c865a8b3b..4bbac21e6 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Program.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Program.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Serilog; namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator { @@ -31,6 +32,13 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator }); }) .UseStartup() + .UseSerilog((builderContext, config) => + { + config + .MinimumLevel.Information() + .Enrich.FromLogContext() + .WriteTo.Console(); + }) .Build(); } } diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/BasketService.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/BasketService.cs index 291e98fd3..d0fb5c008 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/BasketService.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/BasketService.cs @@ -10,7 +10,6 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services { public class BasketService : IBasketService { - private readonly HttpClient _apiClient; private readonly ILogger _logger; private readonly UrlsConfig _urls; @@ -22,18 +21,19 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services _urls = config.Value; } - public async Task GetById(string id) + public async Task GetByIdAsync(string id) { var data = await _apiClient.GetStringAsync(_urls.Basket + UrlsConfig.BasketOperations.GetItemById(id)); var basket = !string.IsNullOrEmpty(data) ? JsonConvert.DeserializeObject(data) : null; + return basket; } - public async Task Update(BasketData currentBasket) + public async Task UpdateAsync(BasketData currentBasket) { var basketContent = new StringContent(JsonConvert.SerializeObject(currentBasket), System.Text.Encoding.UTF8, "application/json"); - var data = await _apiClient.PostAsync(_urls.Basket + UrlsConfig.BasketOperations.UpdateBasket(), basketContent); + await _apiClient.PostAsync(_urls.Basket + UrlsConfig.BasketOperations.UpdateBasket(), basketContent); } } } diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/CatalogService.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/CatalogService.cs index ba67b7c1e..159e56d85 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/CatalogService.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/CatalogService.cs @@ -11,7 +11,6 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services { public class CatalogService : ICatalogService { - private readonly HttpClient _httpClient; private readonly ILogger _logger; private readonly UrlsConfig _urls; @@ -23,20 +22,18 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services _urls = config.Value; } - public async Task GetCatalogItem(int id) + public async Task GetCatalogItemAsync(int id) { var stringContent = await _httpClient.GetStringAsync(_urls.Catalog + UrlsConfig.CatalogOperations.GetItemById(id)); - var catalogItem = JsonConvert.DeserializeObject(stringContent); - return catalogItem; + return JsonConvert.DeserializeObject(stringContent); } - public async Task> GetCatalogItems(IEnumerable ids) + public async Task> GetCatalogItemsAsync(IEnumerable ids) { var stringContent = await _httpClient.GetStringAsync(_urls.Catalog + UrlsConfig.CatalogOperations.GetItemsById(ids)); - var catalogItems = JsonConvert.DeserializeObject(stringContent); - return catalogItems; + return JsonConvert.DeserializeObject(stringContent); } } } diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/IBasketService.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/IBasketService.cs index f59c31965..046ef753d 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/IBasketService.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/IBasketService.cs @@ -1,15 +1,12 @@ using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models; -using System; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services { public interface IBasketService { - Task GetById(string id); - Task Update(BasketData currentBasket); + Task GetByIdAsync(string id); + Task UpdateAsync(BasketData currentBasket); } } diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/ICatalogService.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/ICatalogService.cs index 7d192f3cc..9f86b84f9 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/ICatalogService.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/ICatalogService.cs @@ -1,14 +1,13 @@ using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models; -using System; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services { public interface ICatalogService { - Task GetCatalogItem(int id); - Task> GetCatalogItems(IEnumerable ids); + Task GetCatalogItemAsync(int id); + + Task> GetCatalogItemsAsync(IEnumerable ids); } } diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/IOrderApiClient.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/IOrderApiClient.cs index c97eccbbd..0e972833c 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/IOrderApiClient.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/IOrderApiClient.cs @@ -1,13 +1,10 @@ using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models; -using System; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services { public interface IOrderApiClient { - Task GetOrderDraftFromBasket(BasketData basket); + Task GetOrderDraftFromBasketAsync(BasketData basket); } } diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/OrderApiClient.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/OrderApiClient.cs index d43e392d3..a26028d69 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/OrderApiClient.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/OrderApiClient.cs @@ -10,7 +10,6 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services { public class OrderApiClient : IOrderApiClient { - private readonly HttpClient _apiClient; private readonly ILogger _logger; private readonly UrlsConfig _urls; @@ -22,7 +21,7 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services _urls = config.Value; } - public async Task GetOrderDraftFromBasket(BasketData basket) + public async Task GetOrderDraftFromBasketAsync(BasketData basket) { var url = _urls.Orders + UrlsConfig.OrdersOperations.GetOrderDraft(); var content = new StringContent(JsonConvert.SerializeObject(basket), System.Text.Encoding.UTF8, "application/json"); diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs index e4a080289..0fcd13a41 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config; using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Filters.Basket.API.Infrastructure.Filters; using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Infrastructure; @@ -17,6 +18,9 @@ using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Net.Http; +using HealthChecks.UI.Client; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.Diagnostics.HealthChecks; namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator { @@ -32,6 +36,16 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { + services.AddHealthChecks() + .AddCheck("self", () => HealthCheckResult.Healthy()) + .AddUrlGroup(new Uri(Configuration["CatalogUrlHC"]), name: "catalogapi-check", tags: new string[] { "catalogapi" }) + .AddUrlGroup(new Uri(Configuration["OrderingUrlHC"]), name: "orderingapi-check", tags: new string[] { "orderingapi" }) + .AddUrlGroup(new Uri(Configuration["BasketUrlHC"]), name: "basketapi-check", tags: new string[] { "basketapi" }) + .AddUrlGroup(new Uri(Configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" }) + .AddUrlGroup(new Uri(Configuration["MarketingUrlHC"]), name: "marketingapi-check", tags: new string[] { "marketingapi" }) + .AddUrlGroup(new Uri(Configuration["PaymentUrlHC"]), name: "paymentapi-check", tags: new string[] { "paymentapi" }) + .AddUrlGroup(new Uri(Configuration["LocationUrlHC"]), name: "locationapi-check", tags: new string[] { "locationapi" }); + services.AddCustomMvc(Configuration) .AddCustomAuthentication(Configuration) .AddApplicationServices(); @@ -47,24 +61,39 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator app.UsePathBase(pathBase); } + app.UseHealthChecks("/hc", new HealthCheckOptions() + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); + + app.UseHealthChecks("/liveness", new HealthCheckOptions + { + Predicate = r => r.Name.Contains("self") + }); + app.UseCors("CorsPolicy"); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } + else + { + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } app.UseAuthentication(); - + app.UseHttpsRedirection(); app.UseMvc(); - app.UseSwagger().UseSwaggerUI(c => - { - c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Purchase BFF V1"); - //c.ConfigureOAuth2("Microsoft.eShopOnContainers.Web.Shopping.HttpAggregatorwaggerui", "", "", "Purchase BFF Swagger UI"); - }); - - + app.UseSwagger() + .UseSwaggerUI(c => + { + c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Purchase BFF V1"); + //c.ConfigureOAuth2("Microsoft.eShopOnContainers.Web.Shopping.HttpAggregatorwaggerui", "", "", "Purchase BFF Swagger UI"); + }); } } @@ -99,12 +128,14 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator return services; } + public static IServiceCollection AddCustomMvc(this IServiceCollection services, IConfiguration configuration) { services.AddOptions(); services.Configure(configuration.GetSection("urls")); - services.AddMvc(); + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddSwaggerGen(options => { @@ -135,7 +166,8 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator services.AddCors(options => { options.AddPolicy("CorsPolicy", - builder => builder.AllowAnyOrigin() + builder => builder + .SetIsOriginAllowed((host) => true) .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials()); @@ -177,6 +209,7 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator .WaitAndRetryAsync(6, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); } + static IAsyncPolicy GetCircuitBreakerPolicy() { return HttpPolicyExtensions diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj b/src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj index 00695556b..de2f55ab8 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp2.2 Web.Shopping.HttpAggregator Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator ..\..\..\docker-compose.dcproj @@ -12,9 +12,14 @@ - + + + + + + - + diff --git a/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs b/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs index e01a7aaa8..ef09911fe 100644 --- a/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs +++ b/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs @@ -1,4 +1,5 @@ using System; +using Newtonsoft.Json; namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events { @@ -10,7 +11,17 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events CreationDate = DateTime.UtcNow; } - public Guid Id { get; } - public DateTime CreationDate { get; } + [JsonConstructor] + public IntegrationEvent(Guid id, DateTime createDate) + { + Id = id; + CreationDate = createDate; + } + + [JsonProperty] + public Guid Id { get; private set; } + + [JsonProperty] + public DateTime CreationDate { get; private set; } } } diff --git a/src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs b/src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs index 88be8cf96..e789081f3 100644 --- a/src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs +++ b/src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs @@ -1,10 +1,8 @@ -using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; -using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; -using System; +using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; -using System.Text; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus { @@ -37,8 +35,13 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus where TH : IIntegrationEventHandler { var eventName = GetEventKey(); + DoAddSubscription(typeof(TH), eventName, isDynamic: false); - _eventTypes.Add(typeof(T)); + + if (!_eventTypes.Contains(typeof(T))) + { + _eventTypes.Add(typeof(T)); + } } private void DoAddSubscription(Type handlerType, string eventName, bool isDynamic) diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs index 49a417635..a3b6437ef 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs @@ -217,14 +217,16 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ if (subscription.IsDynamic) { var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler; + if (handler == null) continue; dynamic eventData = JObject.Parse(message); await handler.Handle(eventData); } else { + var handler = scope.ResolveOptional(subscription.HandlerType); + if (handler == null) continue; var eventType = _subsManager.GetEventTypeByName(eventName); var integrationEvent = JsonConvert.DeserializeObject(message, eventType); - var handler = scope.ResolveOptional(subscription.HandlerType); var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType); await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent }); } diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj index 06514ba41..6e0215c29 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs b/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs index 2cd86669b..d16eb4625 100644 --- a/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs +++ b/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs @@ -163,14 +163,16 @@ if (subscription.IsDynamic) { var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler; + if (handler == null) continue; dynamic eventData = JObject.Parse(message); await handler.Handle(eventData); } else { - var eventType = _subsManager.GetEventTypeByName(eventName); - var integrationEvent = JsonConvert.DeserializeObject(message, eventType); var handler = scope.ResolveOptional(subscription.HandlerType); + if (handler == null) continue; + var eventType = _subsManager.GetEventTypeByName(eventName); + var integrationEvent = JsonConvert.DeserializeObject(message, eventType); var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType); await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent }); } diff --git a/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj b/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj index 9eb4bd19a..288c4256a 100644 --- a/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj +++ b/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/EventStateEnum.cs b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/EventStateEnum.cs index 3efb78e74..079cf7d7e 100644 --- a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/EventStateEnum.cs +++ b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/EventStateEnum.cs @@ -7,7 +7,8 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF public enum EventStateEnum { NotPublished = 0, - Published = 1, - PublishedFailed = 2 + InProgress = 1, + Published = 2, + PublishedFailed = 3 } } diff --git a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj index 5ffc3c9eb..731953225 100644 --- a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj +++ b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj @@ -6,10 +6,10 @@ - - - - + + + + diff --git a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs index 3cab9e500..e5c3bc9ad 100644 --- a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs +++ b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs @@ -3,6 +3,9 @@ using System.Collections.Generic; using System.Text; using Newtonsoft.Json; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using System.Linq; +using System.ComponentModel.DataAnnotations.Schema; +using System.Reflection; namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF { @@ -11,7 +14,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF private IntegrationEventLogEntry() { } public IntegrationEventLogEntry(IntegrationEvent @event) { - EventId = @event.Id; + EventId = @event.Id; CreationTime = @event.CreationDate; EventTypeName = @event.GetType().FullName; Content = JsonConvert.SerializeObject(@event); @@ -20,9 +23,19 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF } public Guid EventId { get; private set; } public string EventTypeName { get; private set; } + [NotMapped] + public string EventTypeShortName => EventTypeName.Split('.')?.Last(); + [NotMapped] + public IntegrationEvent IntegrationEvent { get; private set; } public EventStateEnum State { get; set; } public int TimesSent { get; set; } public DateTime CreationTime { get; private set; } public string Content { get; private set; } + + public IntegrationEventLogEntry DeserializeJsonContent(Type type) + { + IntegrationEvent = JsonConvert.DeserializeObject(Content, type) as IntegrationEvent; + return this; + } } } diff --git a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IIntegrationEventLogService.cs b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IIntegrationEventLogService.cs index ed1f74616..6167d8ae8 100644 --- a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IIntegrationEventLogService.cs +++ b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IIntegrationEventLogService.cs @@ -9,7 +9,10 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Servi { public interface IIntegrationEventLogService { + Task> RetrieveEventLogsPendingToPublishAsync(); Task SaveEventAsync(IntegrationEvent @event, DbTransaction transaction); - Task MarkEventAsPublishedAsync(IntegrationEvent @event); + Task MarkEventAsPublishedAsync(Guid eventId); + Task MarkEventAsInProgressAsync(Guid eventId); + Task MarkEventAsFailedAsync(Guid eventId); } } diff --git a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IntegrationEventLogService.cs b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IntegrationEventLogService.cs index a12309482..2712c5e1c 100644 --- a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IntegrationEventLogService.cs +++ b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IntegrationEventLogService.cs @@ -1,9 +1,14 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using Newtonsoft.Json; using System; +using System.Collections; +using System.Collections.Generic; using System.Data.Common; using System.Linq; +using System.Reflection; using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services @@ -12,6 +17,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Servi { private readonly IntegrationEventLogContext _integrationEventLogContext; private readonly DbConnection _dbConnection; + private readonly List _eventTypes; public IntegrationEventLogService(DbConnection dbConnection) { @@ -21,6 +27,20 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Servi .UseSqlServer(_dbConnection) .ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning)) .Options); + + _eventTypes = Assembly.Load(Assembly.GetEntryAssembly().FullName) + .GetTypes() + .Where(t => t.Name.EndsWith(nameof(IntegrationEvent))) + .ToList(); + } + + public async Task> RetrieveEventLogsPendingToPublishAsync() + { + return await _integrationEventLogContext.IntegrationEventLogs + .Where(e => e.State == EventStateEnum.NotPublished) + .OrderBy(o => o.CreationTime) + .Select(e => e.DeserializeJsonContent(_eventTypes.Find(t=> t.Name == e.EventTypeShortName))) + .ToListAsync(); } public Task SaveEventAsync(IntegrationEvent @event, DbTransaction transaction) @@ -38,11 +58,28 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Servi return _integrationEventLogContext.SaveChangesAsync(); } - public Task MarkEventAsPublishedAsync(IntegrationEvent @event) + public Task MarkEventAsPublishedAsync(Guid eventId) + { + return UpdateEventStatus(eventId, EventStateEnum.Published); + } + + public Task MarkEventAsInProgressAsync(Guid eventId) { - var eventLogEntry = _integrationEventLogContext.IntegrationEventLogs.Single(ie => ie.EventId == @event.Id); - eventLogEntry.TimesSent++; - eventLogEntry.State = EventStateEnum.Published; + return UpdateEventStatus(eventId, EventStateEnum.InProgress); + } + + public Task MarkEventAsFailedAsync(Guid eventId) + { + return UpdateEventStatus(eventId, EventStateEnum.PublishedFailed); + } + + private Task UpdateEventStatus(Guid eventId, EventStateEnum status) + { + var eventLogEntry = _integrationEventLogContext.IntegrationEventLogs.Single(ie => ie.EventId == eventId); + eventLogEntry.State = status; + + if(status == EventStateEnum.InProgress) + eventLogEntry.TimesSent++; _integrationEventLogContext.IntegrationEventLogs.Update(eventLogEntry); diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckMiddleware.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckMiddleware.cs deleted file mode 100644 index f8e68c957..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckMiddleware.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; -using Microsoft.Extensions.HealthChecks; -using Newtonsoft.Json; - -namespace Microsoft.AspNetCore.HealthChecks -{ - public class HealthCheckMiddleware - { - private readonly RequestDelegate _next; - private readonly string _path; - private readonly int? _port; - private readonly IHealthCheckService _service; - private readonly TimeSpan _timeout; - - public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, int port, TimeSpan timeout) - { - _port = port; - _service = service; - _next = next; - _timeout = timeout; - } - - public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, string path, TimeSpan timeout) - { - _path = path; - _service = service; - _next = next; - _timeout = timeout; - } - - public async Task Invoke(HttpContext context) - { - if (IsHealthCheckRequest(context)) - { - var timeoutTokenSource = new CancellationTokenSource(_timeout); - var result = await _service.CheckHealthAsync(timeoutTokenSource.Token); - var status = result.CheckStatus; - - if (status != CheckStatus.Healthy) - context.Response.StatusCode = 503; - - context.Response.Headers.Add("content-type", "application/json"); - await context.Response.WriteAsync(JsonConvert.SerializeObject(new { status = status.ToString() })); - return; - } - else - { - await _next.Invoke(context); - } - } - - private bool IsHealthCheckRequest(HttpContext context) - { - if (_port.HasValue) - { - var connInfo = context.Features.Get(); - if (connInfo.LocalPort == _port) - return true; - } - - if (context.Request.Path == _path) - { - return true; - } - - return false; - } - } -} \ No newline at end of file diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckStartupFilter.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckStartupFilter.cs deleted file mode 100644 index cac4b1188..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckStartupFilter.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; - -namespace Microsoft.AspNetCore.HealthChecks -{ - public class HealthCheckStartupFilter : IStartupFilter - { - private string _path; - private int? _port; - private TimeSpan _timeout; - - public HealthCheckStartupFilter(int port, TimeSpan timeout) - { - _port = port; - _timeout = timeout; - } - - public HealthCheckStartupFilter(string path, TimeSpan timeout) - { - _path = path; - _timeout = timeout; - } - - public Action Configure(Action next) - { - return app => - { - if (_port.HasValue) - { - app.UseMiddleware(_port, _timeout); - } - else - { - app.UseMiddleware(_path, _timeout); - } - - next(app); - }; - } - } -} \ No newline at end of file diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostBuilderExtension.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostBuilderExtension.cs deleted file mode 100644 index 467293137..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostBuilderExtension.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using Microsoft.AspNetCore.HealthChecks; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.AspNetCore.Hosting -{ - public static class HealthCheckWebHostBuilderExtension - { - public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(10); - - public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, int port) - => UseHealthChecks(builder, port, DefaultTimeout); - - public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, int port, TimeSpan timeout) - { - Guard.ArgumentValid(port > 0 && port < 65536, nameof(port), "Port must be a value between 1 and 65535."); - Guard.ArgumentValid(timeout > TimeSpan.Zero, nameof(timeout), "Health check timeout must be a positive time span."); - - builder.ConfigureServices(services => - { - var existingUrl = builder.GetSetting(WebHostDefaults.ServerUrlsKey); - builder.UseSetting(WebHostDefaults.ServerUrlsKey, $"{existingUrl};http://localhost:{port}"); - - services.AddSingleton(new HealthCheckStartupFilter(port, timeout)); - }); - return builder; - } - - public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, string path) - => UseHealthChecks(builder, path, DefaultTimeout); - - public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, string path, TimeSpan timeout) - { - Guard.ArgumentNotNull(nameof(path), path); - // REVIEW: Is there a better URL path validator somewhere? - Guard.ArgumentValid(!path.Contains("?"), nameof(path), "Path cannot contain query string values."); - Guard.ArgumentValid(path.StartsWith("/"), nameof(path), "Path should start with '/'."); - Guard.ArgumentValid(timeout > TimeSpan.Zero, nameof(timeout), "Health check timeout must be a positive time span."); - - builder.ConfigureServices(services => services.AddSingleton(new HealthCheckStartupFilter(path, timeout))); - return builder; - } - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostExtensions.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostExtensions.cs deleted file mode 100644 index 67448ff17..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostExtensions.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using Microsoft.Extensions.HealthChecks; - -namespace Microsoft.AspNetCore.Hosting -{ - public static class HealthCheckWebHostExtensions - { - private const int DEFAULT_TIMEOUT_SECONDS = 300; - - public static void RunWhenHealthy(this IWebHost webHost) - { - webHost.RunWhenHealthy(TimeSpan.FromSeconds(DEFAULT_TIMEOUT_SECONDS)); - } - - public static void RunWhenHealthy(this IWebHost webHost, TimeSpan timeout) - { - var healthChecks = webHost.Services.GetService(typeof(IHealthCheckService)) as IHealthCheckService; - - var loops = 0; - do - { - var checkResult = healthChecks.CheckHealthAsync().Result; - if (checkResult.CheckStatus == CheckStatus.Healthy) - { - webHost.Run(); - break; - } - - System.Threading.Thread.Sleep(1000); - loops++; - - } while (loops < timeout.TotalSeconds); - } - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/Microsoft.AspNetCore.HealthChecks.csproj b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/Microsoft.AspNetCore.HealthChecks.csproj deleted file mode 100644 index ce659345f..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/Microsoft.AspNetCore.HealthChecks.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - netstandard2.0 - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.AzureStorage/AzureHealthCheckBuilderExtensions.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.AzureStorage/AzureHealthCheckBuilderExtensions.cs deleted file mode 100644 index 5a06a3ba2..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.AzureStorage/AzureHealthCheckBuilderExtensions.cs +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using Microsoft.WindowsAzure.Storage; -using Microsoft.WindowsAzure.Storage.Auth; - -namespace Microsoft.Extensions.HealthChecks -{ - // REVIEW: Do we want these to continue to use default parameters? - // REVIEW: What are the appropriate guards for these functions? - - public static class AzureHealthCheckBuilderExtensions - { - public static HealthCheckBuilder AddAzureBlobStorageCheck(this HealthCheckBuilder builder, string accountName, string accountKey, string containerName = null, TimeSpan? cacheDuration = null) - { - var credentials = new StorageCredentials(accountName, accountKey); - var storageAccount = new CloudStorageAccount(credentials, true); - return AddAzureBlobStorageCheck(builder, storageAccount, containerName, cacheDuration); - } - - public static HealthCheckBuilder AddAzureBlobStorageCheck(HealthCheckBuilder builder, CloudStorageAccount storageAccount, string containerName = null, TimeSpan? cacheDuration = null) - { - builder.AddCheck($"AzureBlobStorageCheck {storageAccount.BlobStorageUri} {containerName}", async () => - { - bool result; - try - { - var blobClient = storageAccount.CreateCloudBlobClient(); - - var properties = await blobClient.GetServicePropertiesAsync().ConfigureAwait(false); - - if (!String.IsNullOrWhiteSpace(containerName)) - { - var container = blobClient.GetContainerReference(containerName); - - result = await container.ExistsAsync(); - } - - result = true; - } - catch (Exception) - { - result = false; - } - - return result - ? HealthCheckResult.Healthy($"AzureBlobStorage {storageAccount.BlobStorageUri} is available") - : HealthCheckResult.Unhealthy($"AzureBlobStorage {storageAccount.BlobStorageUri} is unavailable"); - }, cacheDuration ?? builder.DefaultCacheDuration); - - return builder; - } - - public static HealthCheckBuilder AddAzureTableStorageCheck(this HealthCheckBuilder builder, string accountName, string accountKey, string tableName = null, TimeSpan? cacheDuration = null) - { - var credentials = new StorageCredentials(accountName, accountKey); - var storageAccount = new CloudStorageAccount(credentials, true); - return AddAzureTableStorageCheck(builder, storageAccount, tableName, cacheDuration); - } - - public static HealthCheckBuilder AddAzureTableStorageCheck(HealthCheckBuilder builder, CloudStorageAccount storageAccount, string tableName = null, TimeSpan? cacheDuration = null) - { - builder.AddCheck($"AzureTableStorageCheck {storageAccount.TableStorageUri} {tableName}", async () => - { - bool result; - try - { - var tableClient = storageAccount.CreateCloudTableClient(); - - var properties = await tableClient.GetServicePropertiesAsync().ConfigureAwait(false); - - if (!String.IsNullOrWhiteSpace(tableName)) - { - var table = tableClient.GetTableReference(tableName); - - result = await table.ExistsAsync(); - } - result = true; - } - catch (Exception) - { - result = false; - } - - return result - ? HealthCheckResult.Healthy($"AzureTableStorage {storageAccount.BlobStorageUri} is available") - : HealthCheckResult.Unhealthy($"AzureTableStorage {storageAccount.BlobStorageUri} is unavailable"); - - }, cacheDuration ?? builder.DefaultCacheDuration); - - return builder; - } - - public static HealthCheckBuilder AddAzureFileStorageCheck(this HealthCheckBuilder builder, string accountName, string accountKey, string shareName = null, TimeSpan? cacheDuration = null) - { - var credentials = new StorageCredentials(accountName, accountKey); - var storageAccount = new CloudStorageAccount(credentials, true); - return AddAzureFileStorageCheck(builder, storageAccount, shareName, cacheDuration); - } - - public static HealthCheckBuilder AddAzureFileStorageCheck(HealthCheckBuilder builder, CloudStorageAccount storageAccount, string shareName = null, TimeSpan? cacheDuration = null) - { - builder.AddCheck($"AzureFileStorageCheck {storageAccount.FileStorageUri} {shareName}", async () => - { - bool result; - try - { - var fileClient = storageAccount.CreateCloudFileClient(); - - var properties = await fileClient.GetServicePropertiesAsync().ConfigureAwait(false); - - if (!String.IsNullOrWhiteSpace(shareName)) - { - var share = fileClient.GetShareReference(shareName); - - result = await share.ExistsAsync(); - } - - result = true; - } - catch (Exception) - { - result = false; - } - - return result - ? HealthCheckResult.Healthy($"AzureFileStorage {storageAccount.BlobStorageUri} is available") - : HealthCheckResult.Unhealthy($"AzureFileStorage {storageAccount.BlobStorageUri} is unavailable"); - }, cacheDuration ?? builder.DefaultCacheDuration); - - return builder; - } - - public static HealthCheckBuilder AddAzureQueueStorageCheck(this HealthCheckBuilder builder, string accountName, string accountKey, string queueName = null, TimeSpan? cacheDuration = null) - { - var credentials = new StorageCredentials(accountName, accountKey); - var storageAccount = new CloudStorageAccount(credentials, true); - return AddAzureQueueStorageCheck(builder, storageAccount, queueName, cacheDuration); - } - - public static HealthCheckBuilder AddAzureQueueStorageCheck(HealthCheckBuilder builder, CloudStorageAccount storageAccount, string queueName = null, TimeSpan? cacheDuration = null) - { - builder.AddCheck($"AzureQueueStorageCheck {storageAccount.QueueStorageUri} {queueName}", async () => - { - bool result; - try - { - var queueClient = storageAccount.CreateCloudQueueClient(); - - var properties = await queueClient.GetServicePropertiesAsync().ConfigureAwait(false); - - if (!String.IsNullOrWhiteSpace(queueName)) - { - var queue = queueClient.GetQueueReference(queueName); - - result = await queue.ExistsAsync(); - } - result = true; - } - catch (Exception) - { - result = false; - } - - return result - ? HealthCheckResult.Healthy($"AzureFileStorage {storageAccount.BlobStorageUri} is available") - : HealthCheckResult.Unhealthy($"AzureFileStorage {storageAccount.BlobStorageUri} is unavailable"); - - }, cacheDuration ?? builder.DefaultCacheDuration); - - return builder; - } - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.AzureStorage/Microsoft.Extensions.HealthChecks.AzureStorage.csproj b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.AzureStorage/Microsoft.Extensions.HealthChecks.AzureStorage.csproj deleted file mode 100644 index 844098052..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.AzureStorage/Microsoft.Extensions.HealthChecks.AzureStorage.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - netstandard2.0 - false - false - false - - - - - - - - - - - - - - - - - - - diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.AzureStorage/Properties/AssemblyInfo.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.AzureStorage/Properties/AssemblyInfo.cs deleted file mode 100644 index ef88235f5..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.AzureStorage/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("HealthChecks.Azure")] -[assembly: AssemblyTrademark("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("0c4158b7-7153-4d2e-abfa-4ce07d44f75f")] diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/HealthCheckBuilderSqlServerExtensions.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/HealthCheckBuilderSqlServerExtensions.cs deleted file mode 100644 index 1837d9638..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/HealthCheckBuilderSqlServerExtensions.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Data; -using System.Data.SqlClient; - -namespace Microsoft.Extensions.HealthChecks -{ - // REVIEW: What are the appropriate guards for these functions? - - public static class HealthCheckBuilderSqlServerExtensions - { - public static HealthCheckBuilder AddSqlCheck(this HealthCheckBuilder builder, string name, string connectionString) - { - Guard.ArgumentNotNull(nameof(builder), builder); - - return AddSqlCheck(builder, name, connectionString, builder.DefaultCacheDuration); - } - - public static HealthCheckBuilder AddSqlCheck(this HealthCheckBuilder builder, string name, string connectionString, TimeSpan cacheDuration) - { - builder.AddCheck($"SqlCheck({name})", async () => - { - try - { - //TODO: There is probably a much better way to do this. - using (var connection = new SqlConnection(connectionString)) - { - connection.Open(); - using (var command = connection.CreateCommand()) - { - command.CommandType = CommandType.Text; - command.CommandText = "SELECT 1"; - var result = (int)await command.ExecuteScalarAsync().ConfigureAwait(false); - if (result == 1) - { - return HealthCheckResult.Healthy($"SqlCheck({name}): Healthy"); - } - - return HealthCheckResult.Unhealthy($"SqlCheck({name}): Unhealthy"); - } - } - } - catch (Exception ex) - { - return HealthCheckResult.Unhealthy($"SqlCheck({name}): Exception during check: {ex.GetType().FullName}"); - } - }, cacheDuration); - - return builder; - } - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/Microsoft.Extensions.HealthChecks.SqlServer.csproj b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/Microsoft.Extensions.HealthChecks.SqlServer.csproj deleted file mode 100644 index 6217f6069..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/Microsoft.Extensions.HealthChecks.SqlServer.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - netstandard2.0 - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheck.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheck.cs deleted file mode 100644 index 39ed087eb..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheck.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.Extensions.HealthChecks -{ - public abstract class CachedHealthCheck - { - private static readonly TypeInfo HealthCheckTypeInfo = typeof(IHealthCheck).GetTypeInfo(); - - private volatile int _writerCount; - - public CachedHealthCheck(string name, TimeSpan cacheDuration) - { - Guard.ArgumentNotNullOrEmpty(nameof(name), name); - Guard.ArgumentValid(cacheDuration.TotalMilliseconds >= 0, nameof(cacheDuration), "Cache duration must be zero (disabled) or greater than zero."); - - Name = name; - CacheDuration = cacheDuration; - } - - public IHealthCheckResult CachedResult { get; internal set; } - - public TimeSpan CacheDuration { get; } - - public DateTimeOffset CacheExpiration { get; internal set; } - - public string Name { get; } - - protected virtual DateTimeOffset UtcNow => DateTimeOffset.UtcNow; - - protected abstract IHealthCheck Resolve(IServiceProvider serviceProvider); - - public async ValueTask RunAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken = default(CancellationToken)) - { - while (CacheExpiration <= UtcNow) - { - // Can't use a standard lock here because of async, so we'll use this flag to determine when we should write a value, - // and the waiters who aren't allowed to write will just spin wait for the new value. - if (Interlocked.Exchange(ref _writerCount, 1) != 0) - { - await Task.Delay(5, cancellationToken).ConfigureAwait(false); - continue; - } - - try - { - var check = Resolve(serviceProvider); - CachedResult = await check.CheckAsync(cancellationToken); - } - catch (OperationCanceledException) - { - CachedResult = HealthCheckResult.Unhealthy("The health check operation timed out"); - } - catch (Exception ex) - { - CachedResult = HealthCheckResult.Unhealthy($"Exception during check: {ex.GetType().FullName}"); - } - - CacheExpiration = UtcNow + CacheDuration; - _writerCount = 0; - break; - } - - return CachedResult; - } - - public static CachedHealthCheck FromHealthCheck(string name, TimeSpan cacheDuration, IHealthCheck healthCheck) - { - Guard.ArgumentNotNull(nameof(healthCheck), healthCheck); - - return new TypeOrHealthCheck_HealthCheck(name, cacheDuration, healthCheck); - } - - public static CachedHealthCheck FromType(string name, TimeSpan cacheDuration, Type healthCheckType) - { - Guard.ArgumentNotNull(nameof(healthCheckType), healthCheckType); - Guard.ArgumentValid(HealthCheckTypeInfo.IsAssignableFrom(healthCheckType.GetTypeInfo()), nameof(healthCheckType), $"Health check must implement '{typeof(IHealthCheck).FullName}'."); - - return new TypeOrHealthCheck_Type(name, cacheDuration, healthCheckType); - } - - class TypeOrHealthCheck_HealthCheck : CachedHealthCheck - { - private readonly IHealthCheck _healthCheck; - - public TypeOrHealthCheck_HealthCheck(string name, TimeSpan cacheDuration, IHealthCheck healthCheck) : base(name, cacheDuration) - => _healthCheck = healthCheck; - - protected override IHealthCheck Resolve(IServiceProvider serviceProvider) => _healthCheck; - } - - class TypeOrHealthCheck_Type : CachedHealthCheck - { - private readonly Type _healthCheckType; - - public TypeOrHealthCheck_Type(string name, TimeSpan cacheDuration, Type healthCheckType) : base(name, cacheDuration) - => _healthCheckType = healthCheckType; - - protected override IHealthCheck Resolve(IServiceProvider serviceProvider) - => (IHealthCheck)serviceProvider.GetRequiredService(_healthCheckType); - } - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheckExtensions.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheckExtensions.cs deleted file mode 100644 index 2c3388709..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CachedHealthCheckExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Extensions.HealthChecks -{ - public static class CachedHealthCheckExtensions - { - public static ValueTask RunAsync(this CachedHealthCheck check, IServiceProvider serviceProvider) - { - Guard.ArgumentNotNull(nameof(check), check); - - return check.RunAsync(serviceProvider, CancellationToken.None); - } - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CheckStatus.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CheckStatus.cs deleted file mode 100644 index c5d1a093f..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CheckStatus.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.Extensions.HealthChecks -{ - public enum CheckStatus - { - Unknown, - Unhealthy, - Healthy, - Warning - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/AddCheck.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/AddCheck.cs deleted file mode 100644 index 5b7b49af0..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/AddCheck.cs +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Extensions.HealthChecks -{ - public static partial class HealthCheckBuilderExtensions - { - // Lambda versions of AddCheck for Func/Func/Func - - public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func check) - { - Guard.ArgumentNotNull(nameof(builder), builder); - - return builder.AddCheck(name, HealthCheck.FromCheck(check), builder.DefaultCacheDuration); - } - - public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func check) - { - Guard.ArgumentNotNull(nameof(builder), builder); - - return builder.AddCheck(name, HealthCheck.FromCheck(check), builder.DefaultCacheDuration); - } - - public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func check, TimeSpan cacheDuration) - { - Guard.ArgumentNotNull(nameof(builder), builder); - - return builder.AddCheck(name, HealthCheck.FromCheck(check), cacheDuration); - } - - public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func check, TimeSpan cacheDuration) - { - Guard.ArgumentNotNull(nameof(builder), builder); - - return builder.AddCheck(name, HealthCheck.FromCheck(check), cacheDuration); - } - - public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func> check) - { - Guard.ArgumentNotNull(nameof(builder), builder); - - return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), builder.DefaultCacheDuration); - } - - public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func> check) - { - Guard.ArgumentNotNull(nameof(builder), builder); - - return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), builder.DefaultCacheDuration); - } - - public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func> check, TimeSpan cacheDuration) - { - Guard.ArgumentNotNull(nameof(builder), builder); - - return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), cacheDuration); - } - - public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func> check, TimeSpan cacheDuration) - { - Guard.ArgumentNotNull(nameof(builder), builder); - - return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), cacheDuration); - } - - public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func> check) - { - Guard.ArgumentNotNull(nameof(builder), builder); - - return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), builder.DefaultCacheDuration); - } - - public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func> check) - { - Guard.ArgumentNotNull(nameof(builder), builder); - - return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), builder.DefaultCacheDuration); - } - - public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func> check, TimeSpan cacheDuration) - { - Guard.ArgumentNotNull(nameof(builder), builder); - - return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), cacheDuration); - } - - public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func> check, TimeSpan cacheDuration) - { - Guard.ArgumentNotNull(nameof(builder), builder); - - return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), cacheDuration); - } - - // IHealthCheck versions of AddCheck - - public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string checkName, IHealthCheck check) - { - Guard.ArgumentNotNull(nameof(builder), builder); - - return builder.AddCheck(checkName, check, builder.DefaultCacheDuration); - } - - // Type versions of AddCheck - - public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name) where TCheck : class, IHealthCheck - { - Guard.ArgumentNotNull(nameof(builder), builder); - - return builder.AddCheck(name, builder.DefaultCacheDuration); - } - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/NumericChecks.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/NumericChecks.cs deleted file mode 100644 index 4c958234e..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/NumericChecks.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; - -namespace Microsoft.Extensions.HealthChecks -{ - public static partial class HealthCheckBuilderExtensions - { - // Numeric checks - - public static HealthCheckBuilder AddMinValueCheck(this HealthCheckBuilder builder, string name, T minValue, Func currentValueFunc) where T : IComparable - { - Guard.ArgumentNotNull(nameof(builder), builder); - - return AddMinValueCheck(builder, name, minValue, currentValueFunc, builder.DefaultCacheDuration); - } - - public static HealthCheckBuilder AddMinValueCheck(this HealthCheckBuilder builder, string name, T minValue, Func currentValueFunc, TimeSpan cacheDuration) - where T : IComparable - { - Guard.ArgumentNotNull(nameof(builder), builder); - Guard.ArgumentNotNullOrEmpty(nameof(name), name); - Guard.ArgumentNotNull(nameof(currentValueFunc), currentValueFunc); - - builder.AddCheck(name, () => - { - var currentValue = currentValueFunc(); - var status = currentValue.CompareTo(minValue) >= 0 ? CheckStatus.Healthy : CheckStatus.Unhealthy; - return HealthCheckResult.FromStatus( - status, - $"min={minValue}, current={currentValue}", - new Dictionary { { "min", minValue }, { "current", currentValue } } - ); - }, cacheDuration); - - return builder; - } - - public static HealthCheckBuilder AddMaxValueCheck(this HealthCheckBuilder builder, string name, T maxValue, Func currentValueFunc) where T : IComparable - { - Guard.ArgumentNotNull(nameof(builder), builder); - - return AddMaxValueCheck(builder, name, maxValue, currentValueFunc, builder.DefaultCacheDuration); - } - - public static HealthCheckBuilder AddMaxValueCheck(this HealthCheckBuilder builder, string name, T maxValue, Func currentValueFunc, TimeSpan cacheDuration) - where T : IComparable - { - Guard.ArgumentNotNull(nameof(builder), builder); - Guard.ArgumentNotNullOrEmpty(nameof(name), name); - Guard.ArgumentNotNull(nameof(currentValueFunc), currentValueFunc); - - builder.AddCheck(name, () => - { - var currentValue = currentValueFunc(); - var status = currentValue.CompareTo(maxValue) <= 0 ? CheckStatus.Healthy : CheckStatus.Unhealthy; - return HealthCheckResult.FromStatus( - status, - $"max={maxValue}, current={currentValue}", - new Dictionary { { "max", maxValue }, { "current", currentValue } } - ); - }, cacheDuration); - - return builder; - } - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/SystemChecks.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/SystemChecks.cs deleted file mode 100644 index dbd9feff2..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/SystemChecks.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; - -namespace Microsoft.Extensions.HealthChecks -{ - public static partial class HealthCheckBuilderExtensions - { - // System checks - - public static HealthCheckBuilder AddPrivateMemorySizeCheck(this HealthCheckBuilder builder, long maxSize) - => AddMaxValueCheck(builder, $"PrivateMemorySize({maxSize})", maxSize, () => Process.GetCurrentProcess().PrivateMemorySize64); - - public static HealthCheckBuilder AddPrivateMemorySizeCheck(this HealthCheckBuilder builder, long maxSize, TimeSpan cacheDuration) - => AddMaxValueCheck(builder, $"PrivateMemorySize({maxSize})", maxSize, () => Process.GetCurrentProcess().PrivateMemorySize64, cacheDuration); - - public static HealthCheckBuilder AddVirtualMemorySizeCheck(this HealthCheckBuilder builder, long maxSize) - => AddMaxValueCheck(builder, $"VirtualMemorySize({maxSize})", maxSize, () => Process.GetCurrentProcess().VirtualMemorySize64); - - public static HealthCheckBuilder AddVirtualMemorySizeCheck(this HealthCheckBuilder builder, long maxSize, TimeSpan cacheDuration) - => AddMaxValueCheck(builder, $"VirtualMemorySize({maxSize})", maxSize, () => Process.GetCurrentProcess().VirtualMemorySize64, cacheDuration); - - public static HealthCheckBuilder AddWorkingSetCheck(this HealthCheckBuilder builder, long maxSize) - => AddMaxValueCheck(builder, $"WorkingSet({maxSize})", maxSize, () => Process.GetCurrentProcess().WorkingSet64); - - public static HealthCheckBuilder AddWorkingSetCheck(this HealthCheckBuilder builder, long maxSize, TimeSpan cacheDuration) - => AddMaxValueCheck(builder, $"WorkingSet({maxSize})", maxSize, () => Process.GetCurrentProcess().WorkingSet64, cacheDuration); - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/UrlChecks.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/UrlChecks.cs deleted file mode 100644 index 2a6cfe908..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/UrlChecks.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.Extensions.HealthChecks.Internal; - -namespace Microsoft.Extensions.HealthChecks -{ - public static partial class HealthCheckBuilderExtensions - { - // Default URL check - - public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url) - { - Guard.ArgumentNotNull(nameof(builder), builder); - - return AddUrlCheck(builder, url, builder.DefaultCacheDuration); - } - - public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, TimeSpan cacheDuration) - => AddUrlCheck(builder, url, response => UrlChecker.DefaultUrlCheck(response), cacheDuration); - - // Func returning IHealthCheckResult - - public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, Func checkFunc) - { - Guard.ArgumentNotNull(nameof(builder), builder); - - return AddUrlCheck(builder, url, checkFunc, builder.DefaultCacheDuration); - } - - public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, - Func checkFunc, - TimeSpan cacheDuration) - { - Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); - - return AddUrlCheck(builder, url, response => new ValueTask(checkFunc(response)), cacheDuration); - } - - // Func returning Task - - public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, Func> checkFunc) - { - Guard.ArgumentNotNull(nameof(builder), builder); - - return AddUrlCheck(builder, url, checkFunc, builder.DefaultCacheDuration); - } - - public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, - Func> checkFunc, - TimeSpan cacheDuration) - { - Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); - - return AddUrlCheck(builder, url, response => new ValueTask(checkFunc(response)), cacheDuration); - } - - // Func returning ValueTask - - public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, Func> checkFunc) - { - Guard.ArgumentNotNull(nameof(builder), builder); - - return AddUrlCheck(builder, url, checkFunc, builder.DefaultCacheDuration); - } - - public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, - Func> checkFunc, - TimeSpan cacheDuration) - { - Guard.ArgumentNotNull(nameof(builder), builder); - Guard.ArgumentNotNullOrEmpty(nameof(url), url); - Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); - - var urlCheck = new UrlChecker(checkFunc, url); - builder.AddCheck($"UrlCheck({url})", () => urlCheck.CheckAsync(), cacheDuration); - return builder; - } - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CompositeHealthCheckResult.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CompositeHealthCheckResult.cs deleted file mode 100644 index 6894ce85f..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CompositeHealthCheckResult.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Microsoft.Extensions.HealthChecks -{ - /// - /// Represents a composite health check result built from several results. - /// - public class CompositeHealthCheckResult : IHealthCheckResult - { - private static readonly IReadOnlyDictionary _emptyData = new Dictionary(); - private readonly CheckStatus _initialStatus; - private readonly CheckStatus _partiallyHealthyStatus; - private readonly Dictionary _results = new Dictionary(StringComparer.OrdinalIgnoreCase); - - public CompositeHealthCheckResult(CheckStatus partiallyHealthyStatus = CheckStatus.Warning, - CheckStatus initialStatus = CheckStatus.Unknown) - { - _partiallyHealthyStatus = partiallyHealthyStatus; - _initialStatus = initialStatus; - } - - public CheckStatus CheckStatus - { - get - { - var checkStatuses = new HashSet(_results.Select(x => x.Value.CheckStatus)); - if (checkStatuses.Count == 0) - { - return _initialStatus; - } - if (checkStatuses.Count == 1) - { - return checkStatuses.First(); - } - if (checkStatuses.Contains(CheckStatus.Healthy)) - { - return _partiallyHealthyStatus; - } - - return CheckStatus.Unhealthy; - } - } - - public string Description => string.Join(Environment.NewLine, _results.Select(r => $"{r.Key}: {r.Value.Description}")); - - public IReadOnlyDictionary Data - { - get - { - var result = new Dictionary(); - - foreach (var kvp in _results) - result.Add(kvp.Key, kvp.Value.Data); - - return result; - } - } - - public IReadOnlyDictionary Results => _results; - - public void Add(string name, CheckStatus status, string description) - => Add(name, status, description, null); - - public void Add(string name, CheckStatus status, string description, Dictionary data) - { - Guard.ArgumentNotNullOrEmpty(nameof(name), name); - Guard.ArgumentValid(status != CheckStatus.Unknown, nameof(status), "Cannot add 'Unknown' status to composite health check result."); - Guard.ArgumentNotNullOrEmpty(nameof(description), description); - - _results.Add(name, HealthCheckResult.FromStatus(status, description, data)); - } - - public void Add(string name, IHealthCheckResult checkResult) - { - Guard.ArgumentNotNullOrEmpty(nameof(name), name); - Guard.ArgumentNotNull(nameof(checkResult), checkResult); - - _results.Add(name, checkResult); - } - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheck.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheck.cs deleted file mode 100644 index 5e1caa2ff..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheck.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Extensions.HealthChecks -{ - public class HealthCheck : IHealthCheck - { - protected HealthCheck(Func> check) - { - Guard.ArgumentNotNull(nameof(check), check); - - Check = check; - } - - protected Func> Check { get; } - - public ValueTask CheckAsync(CancellationToken cancellationToken = default(CancellationToken)) - => Check(cancellationToken); - - public static HealthCheck FromCheck(Func check) - => new HealthCheck(token => new ValueTask(check())); - - public static HealthCheck FromCheck(Func check) - => new HealthCheck(token => new ValueTask(check(token))); - - public static HealthCheck FromTaskCheck(Func> check) - => new HealthCheck(token => new ValueTask(check())); - - public static HealthCheck FromTaskCheck(Func> check) - => new HealthCheck(token => new ValueTask(check(token))); - - public static HealthCheck FromValueTaskCheck(Func> check) - => new HealthCheck(token => check()); - - public static HealthCheck FromValueTaskCheck(Func> check) - => new HealthCheck(check); - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckBuilder.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckBuilder.cs deleted file mode 100644 index 006e4a6ef..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckBuilder.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; - -namespace Microsoft.Extensions.HealthChecks -{ - public class HealthCheckBuilder - { - private readonly Dictionary _checksByName; - private readonly HealthCheckGroup _currentGroup; - private readonly Dictionary _groups; - - public HealthCheckBuilder() - { - _checksByName = new Dictionary(StringComparer.OrdinalIgnoreCase); - _currentGroup = new HealthCheckGroup(string.Empty, CheckStatus.Unhealthy); - _groups = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - [string.Empty] = _currentGroup - }; - - DefaultCacheDuration = TimeSpan.FromMinutes(5); - } - - /// - /// This constructor should only be used when creating a grouped health check builder. - /// - public HealthCheckBuilder(HealthCheckBuilder rootBuilder, HealthCheckGroup currentGroup) - { - Guard.ArgumentNotNull(nameof(rootBuilder), rootBuilder); - Guard.ArgumentNotNull(nameof(currentGroup), currentGroup); - - _checksByName = rootBuilder._checksByName; - _currentGroup = currentGroup; - _groups = rootBuilder._groups; - - DefaultCacheDuration = rootBuilder.DefaultCacheDuration; - } - - /// - /// Gets the registered checks, indexed by check name. - /// - public IReadOnlyDictionary ChecksByName => _checksByName; - - /// - /// Gets the current default cache duration used when registering checks. - /// - public TimeSpan DefaultCacheDuration { get; private set; } - - /// - /// Gets the registered groups, indexed by group name. The root group's name is . - /// - public IReadOnlyDictionary Groups => _groups; - - /// - /// Registers a health check type that will later be resolved via dependency - /// injection. - /// - public HealthCheckBuilder AddCheck(string checkName, TimeSpan cacheDuration) where TCheck : class, IHealthCheck - { - Guard.ArgumentNotNullOrEmpty(nameof(checkName), checkName); - Guard.ArgumentValid(!_checksByName.ContainsKey(checkName), nameof(checkName), $"A check with name '{checkName}' has already been registered."); - - var namedCheck = CachedHealthCheck.FromType(checkName, cacheDuration, typeof(TCheck)); - - _checksByName.Add(checkName, namedCheck); - _currentGroup.ChecksInternal.Add(namedCheck); - - return this; - } - - /// - /// Registers a concrete health check to the builder. - /// - public HealthCheckBuilder AddCheck(string checkName, IHealthCheck check, TimeSpan cacheDuration) - { - Guard.ArgumentNotNullOrEmpty(nameof(checkName), checkName); - Guard.ArgumentNotNull(nameof(check), check); - Guard.ArgumentValid(!_checksByName.ContainsKey(checkName), nameof(checkName), $"A check with name '{checkName}' has already been registered."); - - var namedCheck = CachedHealthCheck.FromHealthCheck(checkName, cacheDuration, check); - - _checksByName.Add(checkName, namedCheck); - _currentGroup.ChecksInternal.Add(namedCheck); - - return this; - } - - /// - /// Creates a new health check group, to which you can add one or more health - /// checks. Uses when the group is - /// partially successful. - /// - public HealthCheckBuilder AddHealthCheckGroup(string groupName, Action groupChecks) - => AddHealthCheckGroup(groupName, groupChecks, CheckStatus.Unhealthy); - - /// - /// Creates a new health check group, to which you can add one or more health - /// checks. - /// - public HealthCheckBuilder AddHealthCheckGroup(string groupName, Action groupChecks, CheckStatus partialSuccessStatus) - { - Guard.ArgumentNotNullOrEmpty(nameof(groupName), groupName); - Guard.ArgumentNotNull(nameof(groupChecks), groupChecks); - Guard.ArgumentValid(partialSuccessStatus != CheckStatus.Unknown, nameof(partialSuccessStatus), "Check status 'Unknown' is not valid for partial success."); - Guard.ArgumentValid(!_groups.ContainsKey(groupName), nameof(groupName), $"A group with name '{groupName}' has already been registered."); - Guard.OperationValid(_currentGroup.GroupName == string.Empty, "Nested groups are not supported by HealthCheckBuilder."); - - var group = new HealthCheckGroup(groupName, partialSuccessStatus); - _groups.Add(groupName, group); - - var innerBuilder = new HealthCheckBuilder(this, group); - groupChecks(innerBuilder); - - return this; - } - - public HealthCheckBuilder WithDefaultCacheDuration(TimeSpan duration) - { - Guard.ArgumentValid(duration >= TimeSpan.Zero, nameof(duration), "Duration must be zero (disabled) or a positive duration."); - - DefaultCacheDuration = duration; - return this; - } - - public HealthCheckBuilder WithPartialSuccessStatus(CheckStatus partiallyHealthyStatus) - { - _currentGroup.PartiallyHealthyStatus = partiallyHealthyStatus; - - return this; - } - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckGroup.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckGroup.cs deleted file mode 100644 index 18c55132b..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckGroup.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; - -namespace Microsoft.Extensions.HealthChecks -{ - public class HealthCheckGroup - { - private CheckStatus _partialSuccessStatus; - - public HealthCheckGroup(string groupName, CheckStatus partialSuccessStatus) - { - Guard.ArgumentNotNull(nameof(groupName), groupName); - - GroupName = groupName; - PartiallyHealthyStatus = partialSuccessStatus; - } - - public IReadOnlyList Checks => ChecksInternal.AsReadOnly(); - - internal List ChecksInternal { get; } = new List(); - - public string GroupName { get; } - - public CheckStatus PartiallyHealthyStatus - { - get => _partialSuccessStatus; - internal set - { - Guard.ArgumentValid(value != CheckStatus.Unknown, nameof(value), "Check status 'Unknown' is not valid for partial success."); - - _partialSuccessStatus = value; - } - } - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResult.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResult.cs deleted file mode 100644 index d8ef80dc4..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResult.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; - -namespace Microsoft.Extensions.HealthChecks -{ - public class HealthCheckResult : IHealthCheckResult - { - private static readonly IReadOnlyDictionary _emptyData = new Dictionary(); - - public CheckStatus CheckStatus { get; } - public IReadOnlyDictionary Data { get; } - public string Description { get; } - - private HealthCheckResult(CheckStatus checkStatus, string description, IReadOnlyDictionary data) - { - CheckStatus = checkStatus; - Description = description; - Data = data ?? _emptyData; - } - - public static HealthCheckResult Unhealthy(string description) - => new HealthCheckResult(CheckStatus.Unhealthy, description, null); - - public static HealthCheckResult Unhealthy(string description, IReadOnlyDictionary data) - => new HealthCheckResult(CheckStatus.Unhealthy, description, data); - - public static HealthCheckResult Healthy(string description) - => new HealthCheckResult(CheckStatus.Healthy, description, null); - - public static HealthCheckResult Healthy(string description, IReadOnlyDictionary data) - => new HealthCheckResult(CheckStatus.Healthy, description, data); - - public static HealthCheckResult Warning(string description) - => new HealthCheckResult(CheckStatus.Warning, description, null); - - public static HealthCheckResult Warning(string description, IReadOnlyDictionary data) - => new HealthCheckResult(CheckStatus.Warning, description, data); - - public static HealthCheckResult Unknown(string description) - => new HealthCheckResult(CheckStatus.Unknown, description, null); - - public static HealthCheckResult Unknown(string description, IReadOnlyDictionary data) - => new HealthCheckResult(CheckStatus.Unknown, description, data); - - public static HealthCheckResult FromStatus(CheckStatus status, string description) - => new HealthCheckResult(status, description, null); - - public static HealthCheckResult FromStatus(CheckStatus status, string description, IReadOnlyDictionary data) - => new HealthCheckResult(status, description, data); - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResults.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResults.cs deleted file mode 100644 index d85dd9e39..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResults.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; - -namespace Microsoft.Extensions.HealthChecks -{ - public class HealthCheckResults - { - public IList CheckResults { get; } = new List(); - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckService.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckService.cs deleted file mode 100644 index 1d2934e0e..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckService.cs +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.Extensions.HealthChecks -{ - public class HealthCheckService : IHealthCheckService - { - private readonly HealthCheckBuilder _builder; - private readonly IReadOnlyList _groups; - private readonly HealthCheckGroup _root; - private readonly IServiceProvider _serviceProvider; - private readonly IServiceScopeFactory _serviceScopeFactory; - - public HealthCheckService(HealthCheckBuilder builder, IServiceProvider serviceProvider, IServiceScopeFactory serviceScopeFactory) - { - _builder = builder; - _groups = GetGroups().Where(group => group.GroupName != string.Empty).ToList(); - _root = GetGroup(string.Empty); - _serviceProvider = serviceProvider; - _serviceScopeFactory = serviceScopeFactory; - } - - public async Task CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken)) - { - using (var scope = GetServiceScope()) - { - var scopeServiceProvider = scope.ServiceProvider; - var groupTasks = _groups.Select(group => new { Group = group, Task = RunGroupAsync(scopeServiceProvider, group, cancellationToken) }).ToList(); - var result = await RunGroupAsync(scopeServiceProvider, _root, cancellationToken).ConfigureAwait(false); - - await Task.WhenAll(groupTasks.Select(x => x.Task)); - - foreach (var groupTask in groupTasks) - { - result.Add($"Group({groupTask.Group.GroupName})", groupTask.Task.Result); - } - - return result; - } - } - - public IReadOnlyList GetAllChecks() - => _builder.ChecksByName.Values.ToList().AsReadOnly(); - - public CachedHealthCheck GetCheck(string checkName) - => _builder.ChecksByName[checkName]; - - public HealthCheckGroup GetGroup(string groupName) - => _builder.Groups[groupName]; - - public IReadOnlyList GetGroups() - => _builder.Groups.Values.ToList().AsReadOnly(); - - private IServiceScope GetServiceScope() - => _serviceScopeFactory == null ? new UnscopedServiceProvider(_serviceProvider) : _serviceScopeFactory.CreateScope(); - - public async ValueTask RunCheckAsync(CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken)) - { - using (var scope = GetServiceScope()) - { - return await RunCheckAsync(scope.ServiceProvider, healthCheck, cancellationToken).ConfigureAwait(false); - } - } - - /// - /// Uses the provided service provider and executes the provided check. - /// - public ValueTask RunCheckAsync(IServiceProvider serviceProvider, CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken)) - { - Guard.ArgumentNotNull(nameof(serviceProvider), serviceProvider); - Guard.ArgumentNotNull(nameof(healthCheck), healthCheck); - - return healthCheck.RunAsync(serviceProvider, cancellationToken); - } - - /// - /// Creates a new resolution scope from the default service provider and executes the checks in the given group. - /// - public async Task RunGroupAsync(HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken)) - { - using (var scope = GetServiceScope()) - return await RunGroupAsync(scope.ServiceProvider, group, cancellationToken).ConfigureAwait(false); - } - - /// - /// Uses the provided service provider and executes the checks in the given group. - /// - public async Task RunGroupAsync(IServiceProvider serviceProvider, HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken)) - { - var result = new CompositeHealthCheckResult(group.PartiallyHealthyStatus); - var checkTasks = group.Checks.Select(check => new { Check = check, Task = check.RunAsync(serviceProvider, cancellationToken).AsTask() }).ToList(); - await Task.WhenAll(checkTasks.Select(checkTask => checkTask.Task)); - - foreach (var checkTask in checkTasks) - { - result.Add(checkTask.Check.Name, checkTask.Task.Result); - } - - return result; - } - - private class UnscopedServiceProvider : IServiceScope - { - public UnscopedServiceProvider(IServiceProvider serviceProvider) - => ServiceProvider = serviceProvider; - - public IServiceProvider ServiceProvider { get; } - - public void Dispose() { } - } - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceCollectionExtensions.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceCollectionExtensions.cs deleted file mode 100644 index 678731737..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceCollectionExtensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Linq; -using Microsoft.Extensions.HealthChecks; - -namespace Microsoft.Extensions.DependencyInjection -{ - public static class HealthCheckServiceCollectionExtensions - { - private static readonly Type HealthCheckServiceInterface = typeof(IHealthCheckService); - - public static IServiceCollection AddHealthChecks(this IServiceCollection services, Action checks) - { - Guard.OperationValid(!services.Any(descriptor => descriptor.ServiceType == HealthCheckServiceInterface), "AddHealthChecks may only be called once."); - - var builder = new HealthCheckBuilder(); - - services.AddSingleton(serviceProvider => - { - var serviceScopeFactory = serviceProvider.GetService(); - return new HealthCheckService(builder, serviceProvider, serviceScopeFactory); - }); - - checks(builder); - return services; - } - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheck.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheck.cs deleted file mode 100644 index e4aa45d28..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheck.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Extensions.HealthChecks -{ - public interface IHealthCheck - { - ValueTask CheckAsync(CancellationToken cancellationToken = default(CancellationToken)); - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckResult.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckResult.cs deleted file mode 100644 index 18a97f93c..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckResult.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; - -namespace Microsoft.Extensions.HealthChecks -{ - public interface IHealthCheckResult - { - CheckStatus CheckStatus { get; } - string Description { get; } - IReadOnlyDictionary Data { get; } - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckService.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckService.cs deleted file mode 100644 index 17a49cb00..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckService.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Extensions.HealthChecks -{ - public interface IHealthCheckService - { - /// - /// Runs all registered health checks. - /// - Task CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Gets all registered health checks as a flat list. - /// - IReadOnlyList GetAllChecks(); - - /// - /// Gets a health check by name. - /// - CachedHealthCheck GetCheck(string checkName); - - /// - /// Gets all health checks in a group. - /// - HealthCheckGroup GetGroup(string groupName); - - /// - /// Gets all the health check groups. - /// - IReadOnlyList GetGroups(); - - /// - /// Creates a new resolution scope from the default service provider and executes the provided check. - /// - ValueTask RunCheckAsync(CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Uses the provided service provider and executes the provided check. - /// - ValueTask RunCheckAsync(IServiceProvider serviceProvider, CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Creates a new resolution scope from the default service provider and executes the checks in the given group. - /// - Task RunGroupAsync(HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Uses the provided service provider and executes the checks in the given group. - /// - Task RunGroupAsync(IServiceProvider serviceProvider, HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken)); - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Internal/UrlChecker.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Internal/UrlChecker.cs deleted file mode 100644 index 56800d334..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Internal/UrlChecker.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading.Tasks; - -namespace Microsoft.Extensions.HealthChecks.Internal -{ - public class UrlChecker - { - private readonly Func> _checkFunc; - private readonly string _url; - - public UrlChecker(Func> checkFunc, string url) - { - Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); - Guard.ArgumentNotNullOrEmpty(nameof(url), url); - - _checkFunc = checkFunc; - _url = url; - } - - public CheckStatus PartiallyHealthyStatus { get; set; } = CheckStatus.Warning; - - public async Task CheckAsync() - { - using (var httpClient = CreateHttpClient()) - { - try - { - var response = await httpClient.GetAsync(_url).ConfigureAwait(false); - return await _checkFunc(response); - } - catch (Exception ex) - { - var data = new Dictionary { { "url", _url } }; - return HealthCheckResult.Unhealthy($"Exception during check: {ex.GetType().FullName}", data); - } - } - } - - private HttpClient CreateHttpClient() - { - var httpClient = GetHttpClient(); - httpClient.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue { NoCache = true }; - return httpClient; - } - - public static async ValueTask DefaultUrlCheck(HttpResponseMessage response) - { - var status = response.IsSuccessStatusCode ? CheckStatus.Healthy : CheckStatus.Unhealthy; - var data = new Dictionary - { - { "url", response.RequestMessage.RequestUri.ToString() }, - { "status", (int)response.StatusCode }, - { "reason", response.ReasonPhrase }, - { "body", await response.Content?.ReadAsStringAsync() } - }; - return HealthCheckResult.FromStatus(status, $"status code {response.StatusCode} ({(int)response.StatusCode})", data); - } - - protected virtual HttpClient GetHttpClient() - => new HttpClient(); - } -} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Microsoft.Extensions.HealthChecks.csproj b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Microsoft.Extensions.HealthChecks.csproj deleted file mode 100644 index 5e63cac75..000000000 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Microsoft.Extensions.HealthChecks.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - netstandard2.0 - - - - - - - - - - - - - - - - diff --git a/src/BuildingBlocks/HealthChecks/src/common/Guard.cs b/src/BuildingBlocks/HealthChecks/src/common/Guard.cs deleted file mode 100644 index 9f394be51..000000000 --- a/src/BuildingBlocks/HealthChecks/src/common/Guard.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; - -static class Guard -{ - public static void ArgumentNotNull(string argumentName, object value) - { - if (value == null) - { - throw new ArgumentNullException(argumentName); - } - } - - public static void ArgumentNotNullOrEmpty(string argumentName, string value) - { - if (value == null) - { - throw new ArgumentNullException(argumentName); - } - if (string.IsNullOrEmpty(value)) - { - throw new ArgumentException("Value cannot be an empty string.", argumentName); - } - } - - // Use IReadOnlyCollection instead of IEnumerable to discourage double enumeration - public static void ArgumentNotNullOrEmpty(string argumentName, IReadOnlyCollection items) - { - if (items == null) - { - throw new ArgumentNullException(argumentName); - } - if (items.Count == 0) - { - throw new ArgumentException("Collection must contain at least one item.", argumentName); - } - } - - public static void ArgumentValid(bool valid, string argumentName, string exceptionMessage) - { - if (!valid) - { - throw new ArgumentException(exceptionMessage, argumentName); - } - } - - public static void OperationValid(bool valid, string exceptionMessage) - { - if (!valid) - { - throw new InvalidOperationException(exceptionMessage); - } - } -} diff --git a/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj b/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj index ad891e6bd..2c5f8b773 100644 --- a/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj +++ b/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj @@ -1,12 +1,12 @@  - netcoreapp2.1 + netcoreapp2.2 - + diff --git a/src/Services/Basket/Basket.API/Basket.API.csproj b/src/Services/Basket/Basket.API/Basket.API.csproj index 3ae587781..4add803b6 100644 --- a/src/Services/Basket/Basket.API/Basket.API.csproj +++ b/src/Services/Basket/Basket.API/Basket.API.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp2.2 $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; ..\..\..\..\docker-compose.dcproj @@ -13,24 +13,31 @@ + + + + - - + + + + + - + + + - - diff --git a/src/Services/Basket/Basket.API/Controllers/BasketController.cs b/src/Services/Basket/Basket.API/Controllers/BasketController.cs index c7fdac57d..0e15e65dd 100644 --- a/src/Services/Basket/Basket.API/Controllers/BasketController.cs +++ b/src/Services/Basket/Basket.API/Controllers/BasketController.cs @@ -13,54 +13,44 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers { [Route("api/v1/[controller]")] [Authorize] - public class BasketController : Controller + [ApiController] + public class BasketController : ControllerBase { private readonly IBasketRepository _repository; - private readonly IIdentityService _identitySvc; + private readonly IIdentityService _identityService; private readonly IEventBus _eventBus; - public BasketController(IBasketRepository repository, - IIdentityService identityService, - IEventBus eventBus) + public BasketController(IBasketRepository repository, IIdentityService identityService, IEventBus eventBus) { _repository = repository; - _identitySvc = identityService; + _identityService = identityService; _eventBus = eventBus; } - // GET /id [HttpGet("{id}")] [ProducesResponseType(typeof(CustomerBasket), (int)HttpStatusCode.OK)] - public async Task Get(string id) + public async Task> GetBasketByIdAsync(string id) { var basket = await _repository.GetBasketAsync(id); - if (basket == null) - { - return Ok(new CustomerBasket(id) { }); - } - return Ok(basket); + return basket ?? new CustomerBasket(id); } - // POST /value [HttpPost] [ProducesResponseType(typeof(CustomerBasket), (int)HttpStatusCode.OK)] - public async Task Post([FromBody]CustomerBasket value) + public async Task> UpdateBasketAsync([FromBody]CustomerBasket value) { - var basket = await _repository.UpdateBasketAsync(value); - - return Ok(basket); + return await _repository.UpdateBasketAsync(value); } [Route("checkout")] [HttpPost] [ProducesResponseType((int)HttpStatusCode.Accepted)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task Checkout([FromBody]BasketCheckout basketCheckout, [FromHeader(Name = "x-requestid")] string requestId) + public async Task CheckoutAsync([FromBody]BasketCheckout basketCheckout, [FromHeader(Name = "x-requestid")] string requestId) { - var userId = _identitySvc.GetUserIdentity(); + var userId = _identityService.GetUserIdentity(); - basketCheckout.RequestId = (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty) ? guid : basketCheckout.RequestId; @@ -80,17 +70,17 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers // Once basket is checkout, sends an integration event to // ordering.api to convert basket to order and proceeds with // order creation process - _eventBus.Publish(eventMessage); + _eventBus.Publish(eventMessage); return Accepted(); } // DELETE api/values/5 [HttpDelete("{id}")] - public void Delete(string id) + [ProducesResponseType(typeof(void), (int)HttpStatusCode.OK)] + public async Task DeleteBasketByIdAsync(string id) { - _repository.DeleteBasketAsync(id); + await _repository.DeleteBasketAsync(id); } - } } diff --git a/src/Services/Basket/Basket.API/Dockerfile b/src/Services/Basket/Basket.API/Dockerfile index 7027cfc2d..a5ccd56f5 100644 --- a/src/Services/Basket/Basket.API/Dockerfile +++ b/src/Services/Basket/Basket.API/Dockerfile @@ -1,14 +1,20 @@ -FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +FROM microsoft/dotnet:2.2.0-aspnetcore-runtime AS base WORKDIR /app EXPOSE 80 -FROM microsoft/dotnet:2.1-sdk AS build +FROM microsoft/dotnet:2.2.100-sdk AS build WORKDIR /src COPY . . WORKDIR /src/src/Services/Basket/Basket.API RUN dotnet restore -nowarn:msb3202,nu1503 RUN dotnet build --no-restore -c Release -o /app +FROM build as functionaltest +WORKDIR /src/src/Services/Basket/Basket.FunctionalTests + +FROM build as unittest +WORKDIR /src/src/Services/Basket/Basket.UnitTests + FROM build AS publish RUN dotnet publish --no-restore -c Release -o /app diff --git a/src/Services/Basket/Basket.API/Model/RedisBasketRepository.cs b/src/Services/Basket/Basket.API/Model/RedisBasketRepository.cs index f748d2c25..5000e6c29 100644 --- a/src/Services/Basket/Basket.API/Model/RedisBasketRepository.cs +++ b/src/Services/Basket/Basket.API/Model/RedisBasketRepository.cs @@ -10,7 +10,6 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model public class RedisBasketRepository : IBasketRepository { private readonly ILogger _logger; - private readonly ConnectionMultiplexer _redis; private readonly IDatabase _database; @@ -30,12 +29,14 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model { var server = GetServer(); var data = server.Keys(); + return data?.Select(k => k.ToString()); } public async Task GetBasketAsync(string customerId) { var data = await _database.StringGetAsync(customerId); + if (data.IsNullOrEmpty) { return null; @@ -47,6 +48,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model public async Task UpdateBasketAsync(CustomerBasket basket) { var created = await _database.StringSetAsync(basket.BuyerId, JsonConvert.SerializeObject(basket)); + if (!created) { _logger.LogInformation("Problem occur persisting the item."); diff --git a/src/Services/Basket/Basket.API/Program.cs b/src/Services/Basket/Basket.API/Program.cs index 303a4625d..e4328645e 100644 --- a/src/Services/Basket/Basket.API/Program.cs +++ b/src/Services/Basket/Basket.API/Program.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Serilog; using System; using System.IO; @@ -22,7 +23,6 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API { options.ConfigPath = "/Failing"; }) - .UseHealthChecks("/hc") .UseContentRoot(Directory.GetCurrentDirectory()) .UseStartup() .ConfigureAppConfiguration((builderContext, config) => @@ -49,6 +49,13 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API builder.AddConsole(); builder.AddDebug(); }) + .UseSerilog((builderContext, config) => + { + config + .MinimumLevel.Information() + .Enrich.FromLogContext() + .WriteTo.Console(); + }) .UseApplicationInsights() .Build(); } diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs index 7d65ba8b4..eecc8bdd4 100644 --- a/src/Services/Basket/Basket.API/Startup.cs +++ b/src/Services/Basket/Basket.API/Startup.cs @@ -4,12 +4,16 @@ using Basket.API.Infrastructure.Filters; using Basket.API.Infrastructure.Middlewares; using Basket.API.IntegrationEvents.EventHandling; using Basket.API.IntegrationEvents.Events; +using HealthChecks.UI.Client; + using Microsoft.ApplicationInsights.Extensibility; using Microsoft.ApplicationInsights.ServiceFabric; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.ServiceBus; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; @@ -21,7 +25,7 @@ using Microsoft.eShopOnContainers.Services.Basket.API.Model; using Microsoft.eShopOnContainers.Services.Basket.API.Services; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.HealthChecks; +using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using RabbitMQ.Client; @@ -30,7 +34,6 @@ using Swashbuckle.AspNetCore.Swagger; using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; -using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Basket.API { @@ -51,20 +54,17 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API // Add framework services. services.AddMvc(options => - { - options.Filters.Add(typeof(HttpGlobalExceptionFilter)); - options.Filters.Add(typeof(ValidateModelStateFilter)); + { + options.Filters.Add(typeof(HttpGlobalExceptionFilter)); + options.Filters.Add(typeof(ValidateModelStateFilter)); - }).AddControllersAsServices(); + }) + .SetCompatibilityVersion(CompatibilityVersion.Version_2_2) + .AddControllersAsServices(); ConfigureAuthService(services); - services.AddHealthChecks(checks => - { - checks.AddValueTaskCheck("HTTP Endpoint", () => new ValueTask(HealthCheckResult.Healthy("Ok")), - TimeSpan.Zero //No cache for this HealthCheck, better just for demos - ); - }); + services.AddCustomHealthCheck(Configuration); services.Configure(Configuration); @@ -159,7 +159,8 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API services.AddCors(options => { options.AddPolicy("CorsPolicy", - builder => builder.AllowAnyOrigin() + builder => builder + .SetIsOriginAllowed((host) => true) .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials()); @@ -189,10 +190,16 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API app.UsePathBase(pathBase); } + app.UseHealthChecks("/hc", new HealthCheckOptions() + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - app.Map("/liveness", lapp => lapp.Run(async ctx => ctx.Response.StatusCode = 200)); -#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + app.UseHealthChecks("/liveness", new HealthCheckOptions + { + Predicate = r => r.Name.Contains("self") + }); app.UseStaticFiles(); app.UseCors("CorsPolicy"); @@ -309,6 +316,42 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API eventBus.Subscribe(); eventBus.Subscribe(); + } + } + + public static class CustomExtensionMethods + { + public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration) + { + var hcBuilder = services.AddHealthChecks(); + + hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); + + hcBuilder + .AddRedis( + configuration["ConnectionString"], + name: "redis-check", + tags: new string[] { "redis" }); + + if (configuration.GetValue("AzureServiceBusEnabled")) + { + hcBuilder + .AddAzureServiceBusTopic( + configuration["EventBusConnection"], + topicName: "eshop_event_bus", + name: "basket-servicebus-check", + tags: new string[] { "servicebus" }); + } + else + { + hcBuilder + .AddRabbitMQ( + $"amqp://{configuration["EventBusConnection"]}", + name: "basket-rabbitmqbus-check", + tags: new string[] { "rabbitmqbus" }); + } + + return services; } } } diff --git a/src/Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj b/src/Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj index 21c64158c..2a2635354 100644 --- a/src/Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj +++ b/src/Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp2.2 false @@ -18,7 +18,7 @@ - + diff --git a/src/Services/Basket/Basket.FunctionalTests/RedisBasketRepositoryTests.cs b/src/Services/Basket/Basket.FunctionalTests/RedisBasketRepositoryTests.cs index 123967558..84d41aa1a 100644 --- a/src/Services/Basket/Basket.FunctionalTests/RedisBasketRepositoryTests.cs +++ b/src/Services/Basket/Basket.FunctionalTests/RedisBasketRepositoryTests.cs @@ -1,5 +1,7 @@ -using Microsoft.eShopOnContainers.Services.Basket.API; +using Basket.FunctionalTests.Base; +using Microsoft.eShopOnContainers.Services.Basket.API; using Microsoft.eShopOnContainers.Services.Basket.API.Model; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Moq; @@ -13,54 +15,60 @@ using Xunit; namespace Basket.FunctionalTests { public class RedisBasketRepositoryTests + : BasketScenarioBase { - private Mock> _optionsMock; - - public RedisBasketRepositoryTests() - { - _optionsMock = new Mock>(); - } [Fact] public async Task UpdateBasket_return_and_add_basket() { - var redisBasketRepository = BuildBasketRepository(); - - var basket = await redisBasketRepository.UpdateBasketAsync(new CustomerBasket("customerId") + using (var server = CreateServer()) { - BuyerId = "buyerId", - Items = BuildBasketItems() - }); + var redis = server.Host.Services.GetRequiredService(); + + var redisBasketRepository = BuildBasketRepository(redis); - Assert.NotNull(basket); - Assert.Single(basket.Items); + var basket = await redisBasketRepository.UpdateBasketAsync(new CustomerBasket("customerId") + { + BuyerId = "buyerId", + Items = BuildBasketItems() + }); + + Assert.NotNull(basket); + Assert.Single(basket.Items); + } + + } [Fact] public async Task Delete_Basket_return_null() { - var redisBasketRepository = BuildBasketRepository(); - var basket = await redisBasketRepository.UpdateBasketAsync(new CustomerBasket("customerId") + using (var server = CreateServer()) { - BuyerId = "buyerId", - Items = BuildBasketItems() - }); + var redis = server.Host.Services.GetRequiredService(); + + var redisBasketRepository = BuildBasketRepository(redis); + + var basket = await redisBasketRepository.UpdateBasketAsync(new CustomerBasket("customerId") + { + BuyerId = "buyerId", + Items = BuildBasketItems() + }); - var deleteResult = await redisBasketRepository.DeleteBasketAsync("buyerId"); + var deleteResult = await redisBasketRepository.DeleteBasketAsync("buyerId"); - var result = await redisBasketRepository.GetBasketAsync(basket.BuyerId); + var result = await redisBasketRepository.GetBasketAsync(basket.BuyerId); - Assert.True(deleteResult); - Assert.Null(result); + Assert.True(deleteResult); + Assert.Null(result); + } } - RedisBasketRepository BuildBasketRepository() + RedisBasketRepository BuildBasketRepository(ConnectionMultiplexer connMux) { var loggerFactory = new LoggerFactory(); - var configuration = ConfigurationOptions.Parse("127.0.0.1", true); - configuration.ResolveDns = true; - return new RedisBasketRepository(loggerFactory, ConnectionMultiplexer.Connect(configuration)); + return new RedisBasketRepository(loggerFactory, connMux); } List BuildBasketItems() diff --git a/src/Services/Basket/Basket.UnitTests/Application/BasketWebApiTest.cs b/src/Services/Basket/Basket.UnitTests/Application/BasketWebApiTest.cs index 88dda058d..a2e382b92 100644 --- a/src/Services/Basket/Basket.UnitTests/Application/BasketWebApiTest.cs +++ b/src/Services/Basket/Basket.UnitTests/Application/BasketWebApiTest.cs @@ -44,10 +44,10 @@ namespace UnitTest.Basket.Application //Act var basketController = new BasketController( _basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object); - var actionResult = await basketController.Get(fakeCustomerId) as OkObjectResult; + var actionResult = await basketController.GetBasketByIdAsync(fakeCustomerId); //Assert - Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.OK); + Assert.Equal((actionResult.Result as OkObjectResult).StatusCode, (int)System.Net.HttpStatusCode.OK); Assert.Equal(((CustomerBasket)actionResult.Value).BuyerId, fakeCustomerId); } @@ -67,10 +67,10 @@ namespace UnitTest.Basket.Application var basketController = new BasketController( _basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object); - var actionResult = await basketController.Post(fakeCustomerBasket) as OkObjectResult; + var actionResult = await basketController.UpdateBasketAsync(fakeCustomerBasket); //Assert - Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.OK); + Assert.Equal((actionResult.Result as OkObjectResult).StatusCode, (int)System.Net.HttpStatusCode.OK); Assert.Equal(((CustomerBasket)actionResult.Value).BuyerId, fakeCustomerId); } @@ -86,7 +86,7 @@ namespace UnitTest.Basket.Application var basketController = new BasketController( _basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object); - var result = await basketController.Checkout(new BasketCheckout(), Guid.NewGuid().ToString()) as BadRequestResult; + var result = await basketController.CheckoutAsync(new BasketCheckout(), Guid.NewGuid().ToString()) as BadRequestResult; Assert.NotNull(result); } @@ -114,7 +114,7 @@ namespace UnitTest.Basket.Application }; //Act - var result = await basketController.Checkout(new BasketCheckout(), Guid.NewGuid().ToString()) as AcceptedResult; + var result = await basketController.CheckoutAsync(new BasketCheckout(), Guid.NewGuid().ToString()) as AcceptedResult; _serviceBusMock.Verify(mock => mock.Publish(It.IsAny()), Times.Once); diff --git a/src/Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj b/src/Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj index 4c2266921..071bbf52b 100644 --- a/src/Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj +++ b/src/Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp2.2 false diff --git a/src/Services/Catalog/Catalog.API/Catalog.API.csproj b/src/Services/Catalog/Catalog.API/Catalog.API.csproj index 047a0ef65..a2be6b522 100644 --- a/src/Services/Catalog/Catalog.API/Catalog.API.csproj +++ b/src/Services/Catalog/Catalog.API/Catalog.API.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp2.2 portable true Catalog.API @@ -34,14 +34,24 @@ + + + + + + + + + - + + @@ -50,10 +60,6 @@ - - - - diff --git a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs index 584f7a0af..d9fa4002e 100644 --- a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs +++ b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs @@ -28,7 +28,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers _catalogIntegrationEventService = catalogIntegrationEventService ?? throw new ArgumentNullException(nameof(catalogIntegrationEventService)); _settings = settings.Value; - ((DbContext)context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; } // GET api/v1/[controller]/items[?pageSize=3&pageIndex=10] @@ -36,11 +36,19 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers [Route("items")] [ProducesResponseType(typeof(PaginatedItemsViewModel), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)] - public async Task Items([FromQuery]int pageSize = 10, [FromQuery]int pageIndex = 0, [FromQuery] string ids = null) + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + public async Task ItemsAsync([FromQuery]int pageSize = 10, [FromQuery]int pageIndex = 0, string ids = null) { if (!string.IsNullOrEmpty(ids)) { - return GetItemsByIds(ids); + var items = await GetItemsByIdsAsync(ids); + + if (!items.Any()) + { + return BadRequest("ids value invalid. Must be comma-separated list of numbers"); + } + + return Ok(items); } var totalItems = await _catalogContext.CatalogItems @@ -54,37 +62,36 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers itemsOnPage = ChangeUriPlaceholder(itemsOnPage); - var model = new PaginatedItemsViewModel( - pageIndex, pageSize, totalItems, itemsOnPage); + var model = new PaginatedItemsViewModel(pageIndex, pageSize, totalItems, itemsOnPage); return Ok(model); } - private IActionResult GetItemsByIds(string ids) + private async Task> GetItemsByIdsAsync(string ids) { - var numIds = ids.Split(',') - .Select(id => (Ok: int.TryParse(id, out int x), Value: x)); + var numIds = ids.Split(',').Select(id => (Ok: int.TryParse(id, out int x), Value: x)); if (!numIds.All(nid => nid.Ok)) { - return BadRequest("ids value invalid. Must be comma-separated list of numbers"); + return new List(); } var idsToSelect = numIds .Select(id => id.Value); - var items = _catalogContext.CatalogItems.Where(ci => idsToSelect.Contains(ci.Id)).ToList(); + var items = await _catalogContext.CatalogItems.Where(ci => idsToSelect.Contains(ci.Id)).ToListAsync(); items = ChangeUriPlaceholder(items); - return Ok(items); + return items; } [HttpGet] [Route("items/{id:int}")] [ProducesResponseType((int)HttpStatusCode.NotFound)] + [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType(typeof(CatalogItem), (int)HttpStatusCode.OK)] - public async Task GetItemById(int id) + public async Task> ItemByIdAsync(int id) { if (id <= 0) { @@ -95,11 +102,12 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers var baseUri = _settings.PicBaseUrl; var azureStorageEnabled = _settings.AzureStorageEnabled; + item.FillProductUrl(baseUri, azureStorageEnabled: azureStorageEnabled); if (item != null) { - return Ok(item); + return item; } return NotFound(); @@ -107,11 +115,10 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers // GET api/v1/[controller]/items/withname/samplename[?pageSize=3&pageIndex=10] [HttpGet] - [Route("[action]/withname/{name:minlength(1)}")] + [Route("items/withname/{name:minlength(1)}")] [ProducesResponseType(typeof(PaginatedItemsViewModel), (int)HttpStatusCode.OK)] - public async Task Items(string name, [FromQuery]int pageSize = 10, [FromQuery]int pageIndex = 0) + public async Task>> ItemsWithNameAsync(string name, [FromQuery]int pageSize = 10, [FromQuery]int pageIndex = 0) { - var totalItems = await _catalogContext.CatalogItems .Where(c => c.Name.StartsWith(name)) .LongCountAsync(); @@ -124,17 +131,14 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers itemsOnPage = ChangeUriPlaceholder(itemsOnPage); - var model = new PaginatedItemsViewModel( - pageIndex, pageSize, totalItems, itemsOnPage); - - return Ok(model); + return new PaginatedItemsViewModel(pageIndex, pageSize, totalItems, itemsOnPage); } // GET api/v1/[controller]/items/type/1/brand[?pageSize=3&pageIndex=10] [HttpGet] - [Route("[action]/type/{catalogTypeId}/brand/{catalogBrandId:int?}")] + [Route("items/type/{catalogTypeId}/brand/{catalogBrandId:int?}")] [ProducesResponseType(typeof(PaginatedItemsViewModel), (int)HttpStatusCode.OK)] - public async Task Items(int catalogTypeId, int? catalogBrandId, [FromQuery]int pageSize = 10, [FromQuery]int pageIndex = 0) + public async Task>> ItemsByTypeIdAndBrandIdAsync(int catalogTypeId, int? catalogBrandId, [FromQuery]int pageSize = 10, [FromQuery]int pageIndex = 0) { var root = (IQueryable)_catalogContext.CatalogItems; @@ -155,16 +159,14 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers itemsOnPage = ChangeUriPlaceholder(itemsOnPage); - var model = new PaginatedItemsViewModel( - pageIndex, pageSize, totalItems, itemsOnPage); - - return Ok(model); + return new PaginatedItemsViewModel(pageIndex, pageSize, totalItems, itemsOnPage); } + // GET api/v1/[controller]/items/type/all/brand[?pageSize=3&pageIndex=10] [HttpGet] - [Route("[action]/type/all/brand/{catalogBrandId:int?}")] + [Route("items/type/all/brand/{catalogBrandId:int?}")] [ProducesResponseType(typeof(PaginatedItemsViewModel), (int)HttpStatusCode.OK)] - public async Task Items(int? catalogBrandId, [FromQuery]int pageSize = 10, [FromQuery]int pageIndex = 0) + public async Task>> ItemsByBrandIdAsync(int? catalogBrandId, [FromQuery]int pageSize = 10, [FromQuery]int pageIndex = 0) { var root = (IQueryable)_catalogContext.CatalogItems; @@ -183,34 +185,25 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers itemsOnPage = ChangeUriPlaceholder(itemsOnPage); - var model = new PaginatedItemsViewModel( - pageIndex, pageSize, totalItems, itemsOnPage); - - return Ok(model); + return new PaginatedItemsViewModel(pageIndex, pageSize, totalItems, itemsOnPage); } // GET api/v1/[controller]/CatalogTypes [HttpGet] - [Route("[action]")] - [ProducesResponseType(typeof(List), (int)HttpStatusCode.OK)] - public async Task CatalogTypes() + [Route("catalogtypes")] + [ProducesResponseType(typeof(List), (int)HttpStatusCode.OK)] + public async Task>> CatalogTypesAsync() { - var items = await _catalogContext.CatalogTypes - .ToListAsync(); - - return Ok(items); + return await _catalogContext.CatalogTypes.ToListAsync(); } // GET api/v1/[controller]/CatalogBrands [HttpGet] - [Route("[action]")] - [ProducesResponseType(typeof(List), (int)HttpStatusCode.OK)] - public async Task CatalogBrands() + [Route("catalogbrands")] + [ProducesResponseType(typeof(List), (int)HttpStatusCode.OK)] + public async Task>> CatalogBrandsAsync() { - var items = await _catalogContext.CatalogBrands - .ToListAsync(); - - return Ok(items); + return await _catalogContext.CatalogBrands.ToListAsync(); } //PUT api/v1/[controller]/items @@ -218,10 +211,9 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers [HttpPut] [ProducesResponseType((int)HttpStatusCode.NotFound)] [ProducesResponseType((int)HttpStatusCode.Created)] - public async Task UpdateProduct([FromBody]CatalogItem productToUpdate) + public async Task UpdateProductAsync([FromBody]CatalogItem productToUpdate) { - var catalogItem = await _catalogContext.CatalogItems - .SingleOrDefaultAsync(i => i.Id == productToUpdate.Id); + var catalogItem = await _catalogContext.CatalogItems.SingleOrDefaultAsync(i => i.Id == productToUpdate.Id); if (catalogItem == null) { @@ -231,7 +223,6 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers var oldPrice = catalogItem.Price; var raiseProductPriceChangedEvent = oldPrice != productToUpdate.Price; - // Update current product catalogItem = productToUpdate; _catalogContext.CatalogItems.Update(catalogItem); @@ -252,14 +243,14 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers await _catalogContext.SaveChangesAsync(); } - return CreatedAtAction(nameof(GetItemById), new { id = productToUpdate.Id }, null); + return CreatedAtAction(nameof(ItemByIdAsync), new { id = productToUpdate.Id }, null); } //POST api/v1/[controller]/items [Route("items")] [HttpPost] [ProducesResponseType((int)HttpStatusCode.Created)] - public async Task CreateProduct([FromBody]CatalogItem product) + public async Task CreateProductAsync([FromBody]CatalogItem product) { var item = new CatalogItem { @@ -270,18 +261,20 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers PictureFileName = product.PictureFileName, Price = product.Price }; + _catalogContext.CatalogItems.Add(item); await _catalogContext.SaveChangesAsync(); - return CreatedAtAction(nameof(GetItemById), new { id = item.Id }, null); + return CreatedAtAction(nameof(ItemByIdAsync), new { id = item.Id }, null); } //DELETE api/v1/[controller]/id [Route("{id}")] [HttpDelete] [ProducesResponseType((int)HttpStatusCode.NoContent)] - public async Task DeleteProduct(int id) + [ProducesResponseType((int)HttpStatusCode.NotFound)] + public async Task DeleteProductAsync(int id) { var product = _catalogContext.CatalogItems.SingleOrDefault(x => x.Id == id); diff --git a/src/Services/Catalog/Catalog.API/Controllers/PicController.cs b/src/Services/Catalog/Catalog.API/Controllers/PicController.cs index c7af86eed..7d798597e 100644 --- a/src/Services/Catalog/Catalog.API/Controllers/PicController.cs +++ b/src/Services/Catalog/Catalog.API/Controllers/PicController.cs @@ -9,8 +9,9 @@ using System.Threading.Tasks; // For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860 namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers -{ - public class PicController : Controller +{ + [ApiController] + public class PicController : ControllerBase { private readonly IHostingEnvironment _env; private readonly CatalogContext _catalogContext; @@ -27,7 +28,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers [ProducesResponseType((int)HttpStatusCode.NotFound)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] // GET: // - public async Task GetImage(int catalogItemId) + public async Task GetImageAsync(int catalogItemId) { if (catalogItemId <= 0) { diff --git a/src/Services/Catalog/Catalog.API/Dockerfile b/src/Services/Catalog/Catalog.API/Dockerfile index 4cbc9c8aa..a9fefb765 100644 --- a/src/Services/Catalog/Catalog.API/Dockerfile +++ b/src/Services/Catalog/Catalog.API/Dockerfile @@ -1,14 +1,20 @@ -FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +FROM microsoft/dotnet:2.2.0-aspnetcore-runtime AS base WORKDIR /app EXPOSE 80 -FROM microsoft/dotnet:2.1-sdk AS build +FROM microsoft/dotnet:2.2.100-sdk AS build WORKDIR /src COPY . . WORKDIR /src/src/Services/Catalog/Catalog.API RUN dotnet restore -nowarn:msb3202,nu1503 RUN dotnet build --no-restore -c Release -o /app +FROM build as unittest +WORKDIR /src/src/Services/Catalog/Catalog.UnitTests + +FROM build as functionaltest +WORKDIR /src/src/Services/Catalog/Catalog.FunctionalTests + FROM build AS publish RUN dotnet publish --no-restore -c Release -o /app diff --git a/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs b/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs index 1b82251e3..8c550bf27 100644 --- a/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs +++ b/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs @@ -29,9 +29,16 @@ namespace Catalog.API.IntegrationEvents public async Task PublishThroughEventBusAsync(IntegrationEvent evt) { - _eventBus.Publish(evt); - - await _eventLogService.MarkEventAsPublishedAsync(evt); + try + { + await _eventLogService.MarkEventAsInProgressAsync(evt.Id); + _eventBus.Publish(evt); + await _eventLogService.MarkEventAsPublishedAsync(evt.Id); + } + catch (Exception) + { + await _eventLogService.MarkEventAsFailedAsync(evt.Id); + } } public async Task SaveEventAndCatalogContextChangesAsync(IntegrationEvent evt) diff --git a/src/Services/Catalog/Catalog.API/Program.cs b/src/Services/Catalog/Catalog.API/Program.cs index 8f3910c84..3f4c19955 100644 --- a/src/Services/Catalog/Catalog.API/Program.cs +++ b/src/Services/Catalog/Catalog.API/Program.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Serilog; using System; using System.IO; namespace Microsoft.eShopOnContainers.Services.Catalog.API @@ -34,7 +35,6 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API WebHost.CreateDefaultBuilder(args) .UseStartup() .UseApplicationInsights() - .UseHealthChecks("/hc") .UseContentRoot(Directory.GetCurrentDirectory()) .UseWebRoot("Pics") .ConfigureAppConfiguration((builderContext, config) => @@ -60,7 +60,14 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); builder.AddConsole(); builder.AddDebug(); - }) + }) + .UseSerilog((builderContext, config) => + { + config + .MinimumLevel.Information() + .Enrich.FromLogContext() + .WriteTo.Console(); + }) .Build(); } } \ No newline at end of file diff --git a/src/Services/Catalog/Catalog.API/Startup.cs b/src/Services/Catalog/Catalog.API/Startup.cs index 408f870af..5c77d37c8 100644 --- a/src/Services/Catalog/Catalog.API/Startup.cs +++ b/src/Services/Catalog/Catalog.API/Startup.cs @@ -22,13 +22,15 @@ using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.EventHa using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.HealthChecks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using RabbitMQ.Client; using System; using System.Data.Common; using System.Reflection; +using HealthChecks.UI.Client; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.Diagnostics.HealthChecks; namespace Microsoft.eShopOnContainers.Services.Catalog.API { @@ -49,7 +51,8 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API .AddCustomOptions(Configuration) .AddIntegrationServices(Configuration) .AddEventBus(Configuration) - .AddSwagger(); + .AddSwagger() + .AddCustomHealthCheck(Configuration); var container = new ContainerBuilder(); container.Populate(services); @@ -72,9 +75,16 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API app.UsePathBase(pathBase); } -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - app.Map("/liveness", lapp => lapp.Run(async ctx => ctx.Response.StatusCode = 200)); -#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + app.UseHealthChecks("/hc", new HealthCheckOptions() + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); + + app.UseHealthChecks("/liveness", new HealthCheckOptions + { + Predicate = r => r.Name.Contains("self") + }); app.UseCors("CorsPolicy"); @@ -120,33 +130,19 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API } public static IServiceCollection AddCustomMVC(this IServiceCollection services, IConfiguration configuration) - { - services.AddHealthChecks(checks => - { - var minutes = 1; - if (int.TryParse(configuration["HealthCheck:Timeout"], out var minutesParsed)) - { - minutes = minutesParsed; - } - checks.AddSqlCheck("CatalogDb", configuration["ConnectionString"], TimeSpan.FromMinutes(minutes)); - - var accountName = configuration.GetValue("AzureStorageAccountName"); - var accountKey = configuration.GetValue("AzureStorageAccountKey"); - if (!string.IsNullOrEmpty(accountName) && !string.IsNullOrEmpty(accountKey)) - { - checks.AddAzureBlobStorageCheck(accountName, accountKey); - } - }); - + { services.AddMvc(options => - { - options.Filters.Add(typeof(HttpGlobalExceptionFilter)); - }).AddControllersAsServices(); + { + options.Filters.Add(typeof(HttpGlobalExceptionFilter)); + }) + .SetCompatibilityVersion(CompatibilityVersion.Version_2_2) + .AddControllersAsServices(); services.AddCors(options => { options.AddPolicy("CorsPolicy", - builder => builder.AllowAnyOrigin() + builder => builder + .SetIsOriginAllowed((host) => true) .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials()); @@ -155,6 +151,50 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API return services; } + public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration) + { + var accountName = configuration.GetValue("AzureStorageAccountName"); + var accountKey = configuration.GetValue("AzureStorageAccountKey"); + + var hcBuilder = services.AddHealthChecks(); + + hcBuilder + .AddCheck("self", () => HealthCheckResult.Healthy()) + .AddSqlServer( + configuration["ConnectionString"], + name: "CatalogDB-check", + tags: new string[] { "catalogdb" }); + + if (!string.IsNullOrEmpty(accountName) && !string.IsNullOrEmpty(accountKey)) + { + hcBuilder + .AddAzureBlobStorage( + $"DefaultEndpointsProtocol=https;AccountName={accountName};AccountKey={accountKey};EndpointSuffix=core.windows.net", + name: "catalog-storage-check", + tags: new string[] { "catalogstorage" }); + } + + if (configuration.GetValue("AzureServiceBusEnabled")) + { + hcBuilder + .AddAzureServiceBusTopic( + configuration["EventBusConnection"], + topicName: "eshop_event_bus", + name: "catalog-servicebus-check", + tags: new string[] { "servicebus" }); + } + else + { + hcBuilder + .AddRabbitMQ( + $"amqp://{configuration["EventBusConnection"]}", + name: "catalog-rabbitmqbus-check", + tags: new string[] { "rabbitmqbus" }); + } + + return services; + } + public static IServiceCollection AddCustomDbContext(this IServiceCollection services, IConfiguration configuration) { services.AddDbContext(options => @@ -232,7 +272,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API public static IServiceCollection AddIntegrationServices(this IServiceCollection services, IConfiguration configuration) { services.AddTransient>( - sp => (DbConnection c) => new IntegrationEventLogService(c)); + sp => (DbConnection c) => new IntegrationEventLogService(c)); services.AddTransient(); diff --git a/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj b/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj index a8e691e22..b38d20ff1 100644 --- a/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj +++ b/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp2.2 false @@ -34,7 +34,7 @@ - + diff --git a/src/Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj b/src/Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj index a336ba98d..9e6a4a4ba 100644 --- a/src/Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj +++ b/src/Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp2.2 false diff --git a/src/Services/Identity/Identity.API/Controllers/AccountController.cs b/src/Services/Identity/Identity.API/Controllers/AccountController.cs index 79e9c247e..7a1fea312 100644 --- a/src/Services/Identity/Identity.API/Controllers/AccountController.cs +++ b/src/Services/Identity/Identity.API/Controllers/AccountController.cs @@ -1,4 +1,9 @@ -using IdentityModel; +using System; +using System.Linq; +using System.Security.Claims; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using IdentityModel; using IdentityServer4; using IdentityServer4.Models; using IdentityServer4.Services; @@ -11,11 +16,6 @@ using Microsoft.eShopOnContainers.Services.Identity.API.Models; using Microsoft.eShopOnContainers.Services.Identity.API.Models.AccountViewModels; using Microsoft.eShopOnContainers.Services.Identity.API.Services; using Microsoft.Extensions.Logging; -using System; -using System.Linq; -using System.Security.Claims; -using System.Text.Encodings.Web; -using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers { @@ -79,9 +79,16 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers if (ModelState.IsValid) { var user = await _loginService.FindByUsername(model.Email); + if (await _loginService.ValidateCredentials(user, model.Password)) { - AuthenticationProperties props = null; + var props = new AuthenticationProperties + { + ExpiresUtc = DateTimeOffset.UtcNow.AddHours(2), + AllowRefresh = true, + RedirectUri = model.ReturnUrl + }; + if (model.RememberMe) { props = new AuthenticationProperties @@ -91,8 +98,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers }; }; - await _loginService.SignIn(user); - + await _loginService.SignInAsync(user, props); + // make sure the returnUrl is still valid, and if yes - redirect back to authorize endpoint if (_interaction.IsValidReturnUrl(model.ReturnUrl)) { @@ -113,7 +120,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers return View(vm); } - async Task BuildLoginViewModelAsync(string returnUrl, AuthorizationRequest context) + private async Task BuildLoginViewModelAsync(string returnUrl, AuthorizationRequest context) { var allowLocal = true; if (context?.ClientId != null) @@ -132,7 +139,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers }; } - async Task BuildLoginViewModelAsync(LoginViewModel model) + private async Task BuildLoginViewModelAsync(LoginViewModel model) { var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); var vm = await BuildLoginViewModelAsync(model.ReturnUrl, context); @@ -193,7 +200,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers try { - + // hack: try/catch to handle social providers that throw await HttpContext.SignOutAsync(idp, new AuthenticationProperties { @@ -209,6 +216,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers // delete authentication cookie await HttpContext.SignOutAsync(); + await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme); + // set this so UI rendering sees an anonymous user HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity()); diff --git a/src/Services/Identity/Identity.API/Dockerfile b/src/Services/Identity/Identity.API/Dockerfile index 3f3a2a773..f3d18f113 100644 --- a/src/Services/Identity/Identity.API/Dockerfile +++ b/src/Services/Identity/Identity.API/Dockerfile @@ -1,11 +1,8 @@ -FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +FROM microsoft/dotnet:2.2.0-aspnetcore-runtime AS base WORKDIR /app EXPOSE 80 -FROM microsoft/dotnet:2.1-sdk as dotnet-build -WORKDIR /src - -FROM dotnet-build as build +FROM microsoft/dotnet:2.2.100-sdk AS build WORKDIR /src COPY . . WORKDIR /src/src/Services/Identity/Identity.API diff --git a/src/Services/Identity/Identity.API/Identity.API.csproj b/src/Services/Identity/Identity.API/Identity.API.csproj index a7d93aa2a..077d6c624 100644 --- a/src/Services/Identity/Identity.API/Identity.API.csproj +++ b/src/Services/Identity/Identity.API/Identity.API.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp2.2 aspnet-eShopOnContainers.Identity-90487118-103c-4ff0-b9da-e5e26f7ab0c5 ..\..\..\..\docker-compose.dcproj @@ -13,17 +13,23 @@ + + + + - - - + + + - + + + @@ -41,9 +47,6 @@ - - - diff --git a/src/Services/Identity/Identity.API/Program.cs b/src/Services/Identity/Identity.API/Program.cs index 6b8353062..d06d49492 100644 --- a/src/Services/Identity/Identity.API/Program.cs +++ b/src/Services/Identity/Identity.API/Program.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Serilog; using System; using System.IO; @@ -40,7 +41,6 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseKestrel() - .UseHealthChecks("/hc") .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() @@ -69,6 +69,13 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API builder.AddDebug(); }) .UseApplicationInsights() + .UseSerilog((builderContext, config) => + { + config + .MinimumLevel.Information() + .Enrich.FromLogContext() + .WriteTo.Console(); + }) .Build(); } } diff --git a/src/Services/Identity/Identity.API/Services/EFLoginService.cs b/src/Services/Identity/Identity.API/Services/EFLoginService.cs index 63c4d4b7e..f3a9d5a03 100644 --- a/src/Services/Identity/Identity.API/Services/EFLoginService.cs +++ b/src/Services/Identity/Identity.API/Services/EFLoginService.cs @@ -1,15 +1,17 @@ -using Microsoft.AspNetCore.Identity; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Identity; using Microsoft.eShopOnContainers.Services.Identity.API.Models; -using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Identity.API.Services { public class EFLoginService : ILoginService { - UserManager _userManager; - SignInManager _signInManager; + private UserManager _userManager; + private SignInManager _signInManager; - public EFLoginService(UserManager userManager, SignInManager signInManager) { + public EFLoginService(UserManager userManager, SignInManager signInManager) + { _userManager = userManager; _signInManager = signInManager; } @@ -24,8 +26,14 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Services return await _userManager.CheckPasswordAsync(user, password); } - public Task SignIn(ApplicationUser user) { + public Task SignIn(ApplicationUser user) + { return _signInManager.SignInAsync(user, true); } + + public Task SignInAsync(ApplicationUser user, AuthenticationProperties properties, string authenticationMethod = null) + { + return _signInManager.SignInAsync(user, properties, authenticationMethod); + } } } diff --git a/src/Services/Identity/Identity.API/Services/ILoginService.cs b/src/Services/Identity/Identity.API/Services/ILoginService.cs index 7bff7f272..8a977205b 100644 --- a/src/Services/Identity/Identity.API/Services/ILoginService.cs +++ b/src/Services/Identity/Identity.API/Services/ILoginService.cs @@ -1,11 +1,16 @@ using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; namespace Microsoft.eShopOnContainers.Services.Identity.API.Services { public interface ILoginService { Task ValidateCredentials(T user, string password); + Task FindByUsername(string user); + Task SignIn(T user); + + Task SignInAsync(T user, AuthenticationProperties properties, string authenticationMethod = null); } } diff --git a/src/Services/Identity/Identity.API/Startup.cs b/src/Services/Identity/Identity.API/Startup.cs index 789047657..aa390779e 100644 --- a/src/Services/Identity/Identity.API/Startup.cs +++ b/src/Services/Identity/Identity.API/Startup.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.eShopOnContainers.Services.Identity.API.Certificates; using Microsoft.eShopOnContainers.Services.Identity.API.Data; @@ -14,11 +15,13 @@ using Microsoft.eShopOnContainers.Services.Identity.API.Models; using Microsoft.eShopOnContainers.Services.Identity.API.Services; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.HealthChecks; using Microsoft.Extensions.Logging; using StackExchange.Redis; using System; using System.Reflection; +using HealthChecks.UI.Client; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.Diagnostics.HealthChecks; namespace Microsoft.eShopOnContainers.Services.Identity.API { @@ -52,7 +55,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API services.Configure(Configuration); - services.AddMvc(); + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Version_2_2); if (Configuration.GetValue("IsClusterEnv") == bool.TrueString) { @@ -63,16 +67,12 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API .PersistKeysToRedis(ConnectionMultiplexer.Connect(Configuration["DPConnectionString"]), "DataProtection-Keys"); } - services.AddHealthChecks(checks => - { - var minutes = 1; - if (int.TryParse(Configuration["HealthCheck:Timeout"], out var minutesParsed)) - { - minutes = minutesParsed; - } - checks.AddSqlCheck("Identity_Db", Configuration["ConnectionString"], TimeSpan.FromMinutes(minutes)); - }); - + services.AddHealthChecks() + .AddCheck("self", () => HealthCheckResult.Healthy()) + .AddSqlServer(Configuration["ConnectionString"], + name: "IdentityDB-check", + tags: new string[] { "IdentityDB" }); + services.AddTransient, EFLoginService>(); services.AddTransient(); @@ -140,10 +140,16 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API app.UsePathBase(pathBase); } + app.UseHealthChecks("/hc", new HealthCheckOptions() + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - app.Map("/liveness", lapp => lapp.Run(async ctx => ctx.Response.StatusCode = 200)); -#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + app.UseHealthChecks("/liveness", new HealthCheckOptions + { + Predicate = r => r.Name.Contains("self") + }); app.UseStaticFiles(); @@ -159,6 +165,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API // Adds IdentityServer app.UseIdentityServer(); + app.UseHttpsRedirection(); app.UseMvc(routes => { routes.MapRoute( diff --git a/src/Services/Location/Locations.API/Controllers/LocationsController.cs b/src/Services/Location/Locations.API/Controllers/LocationsController.cs index 45f4d2ba7..a9cbae1c2 100644 --- a/src/Services/Location/Locations.API/Controllers/LocationsController.cs +++ b/src/Services/Location/Locations.API/Controllers/LocationsController.cs @@ -12,6 +12,7 @@ namespace Locations.API.Controllers { [Route("api/v1/[controller]")] [Authorize] + [ApiController] public class LocationsController : ControllerBase { private readonly ILocationsService _locationsService; @@ -27,30 +28,27 @@ namespace Locations.API.Controllers [Route("user/{userId:guid}")] [HttpGet] [ProducesResponseType(typeof(UserLocation), (int)HttpStatusCode.OK)] - public async Task GetUserLocation(Guid userId) + public async Task> GetUserLocationAsync(Guid userId) { - var userLocation = await _locationsService.GetUserLocation(userId.ToString()); - return Ok(userLocation); + return await _locationsService.GetUserLocationAsync(userId.ToString()); } //GET api/v1/[controller]/ [Route("")] [HttpGet] - //[ProducesResponseType(typeof(List), (int)HttpStatusCode.OK)] - public async Task GetAllLocations() + [ProducesResponseType(typeof(List), (int)HttpStatusCode.OK)] + public async Task>> GetAllLocationsAsync() { - var locations = await _locationsService.GetAllLocation(); - return Ok(locations); + return await _locationsService.GetAllLocationAsync(); } //GET api/v1/[controller]/1 [Route("{locationId}")] [HttpGet] - //[ProducesResponseType(typeof(List), (int)HttpStatusCode.OK)] - public async Task GetLocation(int locationId) + [ProducesResponseType(typeof(Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations), (int)HttpStatusCode.OK)] + public async Task> GetLocationAsync(int locationId) { - var location = await _locationsService.GetLocation(locationId); - return Ok(location); + return await _locationsService.GetLocationAsync(locationId); } //POST api/v1/[controller]/ @@ -58,14 +56,17 @@ namespace Locations.API.Controllers [HttpPost] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task CreateOrUpdateUserLocation([FromBody]LocationRequest newLocReq) + public async Task CreateOrUpdateUserLocationAsync([FromBody]LocationRequest newLocReq) { var userId = _identityService.GetUserIdentity(); - var result = await _locationsService.AddOrUpdateUserLocation(userId, newLocReq); - - return result ? - (IActionResult)Ok() : - (IActionResult)BadRequest(); + var result = await _locationsService.AddOrUpdateUserLocationAsync(userId, newLocReq); + + if (!result) + { + return BadRequest(); + } + + return Ok(); } } } diff --git a/src/Services/Location/Locations.API/Dockerfile b/src/Services/Location/Locations.API/Dockerfile index 12ac0e442..5a4dc35fc 100644 --- a/src/Services/Location/Locations.API/Dockerfile +++ b/src/Services/Location/Locations.API/Dockerfile @@ -1,14 +1,17 @@ -FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +FROM microsoft/dotnet:2.2.0-aspnetcore-runtime AS base WORKDIR /app EXPOSE 80 -FROM microsoft/dotnet:2.1-sdk AS build +FROM microsoft/dotnet:2.2.100-sdk AS build WORKDIR /src COPY . . WORKDIR /src/src/Services/Location/Locations.API RUN dotnet restore -nowarn:msb3202,nu1503 RUN dotnet build --no-restore -c Release -o /app +FROM build as functionaltest +WORKDIR /src/src/Services/Location/Locations.FunctionalTests + FROM build AS publish RUN dotnet publish --no-restore -c Release -o /app diff --git a/src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs b/src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs index 5d55bb25e..11459492a 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs @@ -7,12 +7,12 @@ public interface ILocationsService { - Task GetLocation(int locationId); + Task GetLocationAsync(int locationId); - Task GetUserLocation(string id); + Task GetUserLocationAsync(string id); - Task> GetAllLocation(); + Task> GetAllLocationAsync(); - Task AddOrUpdateUserLocation(string userId, LocationRequest locRequest); + Task AddOrUpdateUserLocationAsync(string userId, LocationRequest locRequest); } } diff --git a/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs b/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs index 771a075d2..e9c299d23 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs @@ -21,22 +21,22 @@ _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); } - public async Task GetLocation(int locationId) + public async Task GetLocationAsync(int locationId) { return await _locationsRepository.GetAsync(locationId); } - public async Task GetUserLocation(string userId) + public async Task GetUserLocationAsync(string userId) { return await _locationsRepository.GetUserLocationAsync(userId); } - public async Task> GetAllLocation() + public async Task> GetAllLocationAsync() { return await _locationsRepository.GetLocationListAsync(); } - public async Task AddOrUpdateUserLocation(string userId, LocationRequest currentPosition) + public async Task AddOrUpdateUserLocationAsync(string userId, LocationRequest currentPosition) { // Get the list of ordered regions the user currently is within var currentUserAreaLocationList = await _locationsRepository.GetCurrentUserRegionsListAsync(currentPosition); diff --git a/src/Services/Location/Locations.API/Locations.API.csproj b/src/Services/Location/Locations.API/Locations.API.csproj index 31b7c3930..38d72a235 100644 --- a/src/Services/Location/Locations.API/Locations.API.csproj +++ b/src/Services/Location/Locations.API/Locations.API.csproj @@ -1,31 +1,37 @@  - netcoreapp2.1 + netcoreapp2.2 ..\..\..\..\docker-compose.dcproj aspnet-Locations.API-20161122013619 + + + + - - - + + + + + + + - - diff --git a/src/Services/Location/Locations.API/Program.cs b/src/Services/Location/Locations.API/Program.cs index aedc1bed5..83c586088 100644 --- a/src/Services/Location/Locations.API/Program.cs +++ b/src/Services/Location/Locations.API/Program.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Serilog; using System; using System.IO; @@ -17,7 +18,6 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) - .UseHealthChecks("/hc") .UseContentRoot(Directory.GetCurrentDirectory()) .UseStartup() .ConfigureAppConfiguration((builderContext, config) => @@ -45,6 +45,13 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API builder.AddDebug(); }) .UseApplicationInsights() + .UseSerilog((builderContext, config) => + { + config + .MinimumLevel.Information() + .Enrich.FromLogContext() + .WriteTo.Console(); + }) .Build(); } } diff --git a/src/Services/Location/Locations.API/Startup.cs b/src/Services/Location/Locations.API/Startup.cs index be0263312..3e1936a55 100644 --- a/src/Services/Location/Locations.API/Startup.cs +++ b/src/Services/Location/Locations.API/Startup.cs @@ -1,11 +1,14 @@ using Autofac; using Autofac.Extensions.DependencyInjection; +using HealthChecks.UI.Client; using Microsoft.ApplicationInsights.Extensibility; using Microsoft.ApplicationInsights.ServiceFabric; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.ServiceBus; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; @@ -18,14 +21,13 @@ using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Reposito using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Services; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.HealthChecks; +using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging; using RabbitMQ.Client; using Swashbuckle.AspNetCore.Swagger; using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; -using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Locations.API { @@ -42,10 +44,14 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API { RegisterAppInsights(services); - services.AddMvc(options => - { - options.Filters.Add(typeof(HttpGlobalExceptionFilter)); - }).AddControllersAsServices(); + services + .AddCustomHealthCheck(Configuration) + .AddMvc(options => + { + options.Filters.Add(typeof(HttpGlobalExceptionFilter)); + }) + .SetCompatibilityVersion(CompatibilityVersion.Version_2_2) + .AddControllersAsServices(); ConfigureAuthService(services); @@ -92,12 +98,7 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount); }); - } - - services.AddHealthChecks(checks => - { - checks.AddValueTaskCheck("HTTP Endpoint", () => new ValueTask(HealthCheckResult.Healthy("Ok"))); - }); + } RegisterEventBus(services); @@ -132,7 +133,8 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API services.AddCors(options => { options.AddPolicy("CorsPolicy", - builder => builder.AllowAnyOrigin() + builder => builder + .SetIsOriginAllowed((host) => true) .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials()); @@ -162,9 +164,16 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API app.UsePathBase(pathBase); } -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - app.Map("/liveness", lapp => lapp.Run(async ctx => ctx.Response.StatusCode = 200)); -#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + app.UseHealthChecks("/liveness", new HealthCheckOptions + { + Predicate = r => r.Name.Contains("self") + }); + + app.UseHealthChecks("/hc", new HealthCheckOptions() + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); app.UseCors("CorsPolicy"); @@ -269,4 +278,40 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API services.AddSingleton(); } } + + public static class CustomExtensionMethods + { + public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration) + { + var hcBuilder = services.AddHealthChecks(); + + hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); + + hcBuilder + .AddMongoDb( + configuration["ConnectionString"], + name: "locations-mongodb-check", + tags: new string[] { "mongodb" }); + + if (configuration.GetValue("AzureServiceBusEnabled")) + { + hcBuilder + .AddAzureServiceBusTopic( + configuration["EventBusConnection"], + topicName: "eshop_event_bus", + name: "locations-servicebus-check", + tags: new string[] { "servicebus" }); + } + else + { + hcBuilder + .AddRabbitMQ( + $"amqp://{configuration["EventBusConnection"]}", + name: "locations-rabbitmqbus-check", + tags: new string[] { "rabbitmqbus" }); + } + + return services; + } + } } diff --git a/src/Services/Location/Locations.FunctionalTests/Locations.FunctionalTests.csproj b/src/Services/Location/Locations.FunctionalTests/Locations.FunctionalTests.csproj index 28a8bf0b4..ce098d42c 100644 --- a/src/Services/Location/Locations.FunctionalTests/Locations.FunctionalTests.csproj +++ b/src/Services/Location/Locations.FunctionalTests/Locations.FunctionalTests.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp2.2 false @@ -18,7 +18,7 @@ - + diff --git a/src/Services/Marketing/Marketing.API/Controllers/CampaignsController.cs b/src/Services/Marketing/Marketing.API/Controllers/CampaignsController.cs index 313d4d77b..67ac1bca5 100644 --- a/src/Services/Marketing/Marketing.API/Controllers/CampaignsController.cs +++ b/src/Services/Marketing/Marketing.API/Controllers/CampaignsController.cs @@ -20,7 +20,8 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers [Route("api/v1/[controller]")] [Authorize] - public class CampaignsController : Controller + [ApiController] + public class CampaignsController : ControllerBase { private readonly MarketingContext _context; private readonly MarketingSettings _settings; @@ -40,43 +41,37 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers [HttpGet] [ProducesResponseType(typeof(List), (int)HttpStatusCode.OK)] - public async Task GetAllCampaigns() + public async Task>> GetAllCampaignsAsync() { - var campaignList = await _context.Campaigns - .ToListAsync(); + var campaignList = await _context.Campaigns.ToListAsync(); if (campaignList is null) { return Ok(); } - var campaignDtoList = MapCampaignModelListToDtoList(campaignList); - - return Ok(campaignDtoList); + return MapCampaignModelListToDtoList(campaignList); } [HttpGet("{id:int}")] [ProducesResponseType(typeof(CampaignDTO), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NotFound)] - public async Task GetCampaignById(int id) + public async Task> GetCampaignByIdAsync(int id) { - var campaign = await _context.Campaigns - .SingleOrDefaultAsync(c => c.Id == id); + var campaign = await _context.Campaigns.SingleOrDefaultAsync(c => c.Id == id); if (campaign is null) { return NotFound(); } - var campaignDto = MapCampaignModelToDto(campaign); - - return Ok(campaignDto); + return MapCampaignModelToDto(campaign); } [HttpPost] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.Created)] - public async Task CreateCampaign([FromBody] CampaignDTO campaignDto) + public async Task CreateCampaignAsync([FromBody] CampaignDTO campaignDto) { if (campaignDto is null) { @@ -88,14 +83,14 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers await _context.Campaigns.AddAsync(campaign); await _context.SaveChangesAsync(); - return CreatedAtAction(nameof(GetCampaignById), new { id = campaign.Id }, null); + return CreatedAtAction(nameof(GetCampaignByIdAsync), new { id = campaign.Id }, null); } [HttpPut("{id:int}")] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.NotFound)] [ProducesResponseType((int)HttpStatusCode.Created)] - public async Task UpdateCampaign(int id, [FromBody] CampaignDTO campaignDto) + public async Task UpdateCampaignAsync(int id, [FromBody] CampaignDTO campaignDto) { if (id < 1 || campaignDto is null) { @@ -116,14 +111,14 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers await _context.SaveChangesAsync(); - return CreatedAtAction(nameof(GetCampaignById), new { id = campaignToUpdate.Id }, null); + return CreatedAtAction(nameof(GetCampaignByIdAsync), new { id = campaignToUpdate.Id }, null); } [HttpDelete("{id:int}")] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.NotFound)] [ProducesResponseType((int)HttpStatusCode.NoContent)] - public async Task Delete(int id) + public async Task DeleteCampaignByIdAsync(int id) { if (id < 1) { @@ -131,6 +126,7 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers } var campaignToDelete = await _context.Campaigns.FindAsync(id); + if (campaignToDelete is null) { return NotFound(); @@ -144,7 +140,7 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers [HttpGet("user")] [ProducesResponseType(typeof(PaginatedItemsViewModel), (int)HttpStatusCode.OK)] - public async Task GetCampaignsByUserId( int pageSize = 10, int pageIndex = 0) + public async Task>> GetCampaignsByUserIdAsync( int pageSize = 10, int pageIndex = 0) { var userId = _identityService.GetUserIdentity(); @@ -169,21 +165,17 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers var userCampaignDtoList = MapCampaignModelListToDtoList(userCampaignList); campaignDtoList.AddRange(userCampaignDtoList); } - } var totalItems = campaignDtoList.Count(); + campaignDtoList = campaignDtoList .Skip(pageSize * pageIndex) .Take(pageSize).ToList(); - var model = new PaginatedItemsViewModel( - pageIndex, pageSize, totalItems, campaignDtoList); - - return Ok(model); + return new PaginatedItemsViewModel(pageIndex, pageSize, totalItems, campaignDtoList); } - private List MapCampaignModelListToDtoList(List campaignList) { var campaignDtoList = new List(); diff --git a/src/Services/Marketing/Marketing.API/Controllers/LocationsController.cs b/src/Services/Marketing/Marketing.API/Controllers/LocationsController.cs index 0d47a63a4..7d5959cb5 100644 --- a/src/Services/Marketing/Marketing.API/Controllers/LocationsController.cs +++ b/src/Services/Marketing/Marketing.API/Controllers/LocationsController.cs @@ -11,7 +11,8 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers using System.Threading.Tasks; [Authorize] - public class LocationsController : Controller + [ApiController] + public class LocationsController : ControllerBase { private readonly MarketingContext _context; @@ -25,7 +26,7 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.NotFound)] [ProducesResponseType(typeof(UserLocationRuleDTO),(int)HttpStatusCode.OK)] - public IActionResult GetLocationByCampaignAndLocationRuleId(int campaignId, + public ActionResult GetLocationByCampaignAndLocationRuleId(int campaignId, int userLocationRuleId) { if (campaignId < 1 || userLocationRuleId < 1) @@ -42,9 +43,7 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers return NotFound(); } - var locationDto = MapUserLocationRuleModelToDto(location); - - return Ok(locationDto); + return MapUserLocationRuleModelToDto(location); } [HttpGet] @@ -52,7 +51,7 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType(typeof(List), (int)HttpStatusCode.OK)] - public IActionResult GetAllLocationsByCampaignId(int campaignId) + public ActionResult> GetAllLocationsByCampaignId(int campaignId) { if (campaignId < 1) { @@ -69,17 +68,14 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers return Ok(); } - var locationDtoList = MapUserLocationRuleModelListToDtoList(locationList); - - return Ok(locationDtoList); + return MapUserLocationRuleModelListToDtoList(locationList); } [HttpPost] [Route("api/v1/campaigns/{campaignId:int}/locations")] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.Created)] - public async Task CreateLocation(int campaignId, - [FromBody] UserLocationRuleDTO locationRuleDto) + public async Task CreateLocationAsync(int campaignId, [FromBody] UserLocationRuleDTO locationRuleDto) { if (campaignId < 1 || locationRuleDto is null) { @@ -100,7 +96,7 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers [Route("api/v1/campaigns/{campaignId:int}/locations/{userLocationRuleId:int}")] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.NotFound)] - public async Task DeleteLocationById(int campaignId, int userLocationRuleId) + public async Task DeleteLocationByIdAsync(int campaignId, int userLocationRuleId) { if (campaignId < 1 || userLocationRuleId < 1) { @@ -122,8 +118,6 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers return NoContent(); } - - private List MapUserLocationRuleModelListToDtoList(List userLocationRuleList) { var userLocationRuleDtoList = new List(); diff --git a/src/Services/Marketing/Marketing.API/Controllers/PicController.cs b/src/Services/Marketing/Marketing.API/Controllers/PicController.cs index 9c2b73c36..66ce1e750 100644 --- a/src/Services/Marketing/Marketing.API/Controllers/PicController.cs +++ b/src/Services/Marketing/Marketing.API/Controllers/PicController.cs @@ -4,8 +4,8 @@ using Microsoft.AspNetCore.Mvc; using System.IO; - - public class PicController : Controller + [ApiController] + public class PicController : ControllerBase { private readonly IHostingEnvironment _env; public PicController(IHostingEnvironment env) @@ -15,7 +15,7 @@ [HttpGet] [Route("api/v1/campaigns/{campaignId:int}/pic")] - public IActionResult GetImage(int campaignId) + public ActionResult GetImage(int campaignId) { var webRoot = _env.WebRootPath; var path = Path.Combine(webRoot, campaignId + ".png"); diff --git a/src/Services/Marketing/Marketing.API/Dockerfile b/src/Services/Marketing/Marketing.API/Dockerfile index 75d698bfe..42c0e3687 100644 --- a/src/Services/Marketing/Marketing.API/Dockerfile +++ b/src/Services/Marketing/Marketing.API/Dockerfile @@ -1,14 +1,17 @@ -FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +FROM microsoft/dotnet:2.2.0-aspnetcore-runtime AS base WORKDIR /app EXPOSE 80 -FROM microsoft/dotnet:2.1-sdk AS build +FROM microsoft/dotnet:2.2.100-sdk AS build WORKDIR /src COPY . . WORKDIR /src/src/Services/Marketing/Marketing.API RUN dotnet restore -nowarn:msb3202,nu1503 RUN dotnet build --no-restore -c Release -o /app +FROM build as functionaltest +WORKDIR /src/src/Services/Marketing/Marketing.FunctionalTests + FROM build AS publish RUN dotnet publish --no-restore -c Release -o /app diff --git a/src/Services/Marketing/Marketing.API/Marketing.API.csproj b/src/Services/Marketing/Marketing.API/Marketing.API.csproj index e96124d80..f3e15515b 100644 --- a/src/Services/Marketing/Marketing.API/Marketing.API.csproj +++ b/src/Services/Marketing/Marketing.API/Marketing.API.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp2.2 ..\..\..\..\docker-compose.dcproj Microsoft.eShopOnContainers.Services.Marketing.API $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; @@ -20,25 +20,32 @@ + + + + + + - + + + - - + + + + - - - diff --git a/src/Services/Marketing/Marketing.API/Program.cs b/src/Services/Marketing/Marketing.API/Program.cs index 06c5b72ca..a885a3af6 100644 --- a/src/Services/Marketing/Marketing.API/Program.cs +++ b/src/Services/Marketing/Marketing.API/Program.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; + using Serilog; using System; using System.IO; @@ -28,7 +29,6 @@ public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseApplicationInsights() - .UseHealthChecks("/hc") .UseContentRoot(Directory.GetCurrentDirectory()) .UseStartup() .UseWebRoot("Pics") @@ -57,6 +57,13 @@ builder.AddDebug(); }) .UseApplicationInsights() + .UseSerilog((builderContext, config) => + { + config + .MinimumLevel.Information() + .Enrich.FromLogContext() + .WriteTo.Console(); + }) .Build(); } } diff --git a/src/Services/Marketing/Marketing.API/Startup.cs b/src/Services/Marketing/Marketing.API/Startup.cs index 5683dc72d..826c8cb81 100644 --- a/src/Services/Marketing/Marketing.API/Startup.cs +++ b/src/Services/Marketing/Marketing.API/Startup.cs @@ -13,8 +13,8 @@ using EntityFrameworkCore; using Extensions.Configuration; using Extensions.DependencyInjection; - using Extensions.HealthChecks; using Extensions.Logging; + using HealthChecks.UI.Client; using Infrastructure; using Infrastructure.Filters; using Infrastructure.Repositories; @@ -24,15 +24,17 @@ using Microsoft.ApplicationInsights.Extensibility; using Microsoft.ApplicationInsights.ServiceFabric; using Microsoft.AspNetCore.Authentication.JwtBearer; + using Microsoft.AspNetCore.Diagnostics.HealthChecks; + using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Middlewares; + using Microsoft.Extensions.Diagnostics.HealthChecks; using RabbitMQ.Client; using Swashbuckle.AspNetCore.Swagger; using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Reflection; - using System.Threading.Tasks; public class Startup { @@ -50,27 +52,19 @@ RegisterAppInsights(services); // Add framework services. - services.AddMvc(options => - { - options.Filters.Add(typeof(HttpGlobalExceptionFilter)); - }).AddControllersAsServices(); //Injecting Controllers themselves thru DIFor further info see: http://docs.autofac.org/en/latest/integration/aspnetcore.html#controllers-as-services + services + .AddCustomHealthCheck(Configuration) + .AddMvc(options => + { + options.Filters.Add(typeof(HttpGlobalExceptionFilter)); + }) + .SetCompatibilityVersion(CompatibilityVersion.Version_2_2) + .AddControllersAsServices(); //Injecting Controllers themselves thru DIFor further info see: http://docs.autofac.org/en/latest/integration/aspnetcore.html#controllers-as-services services.Configure(Configuration); ConfigureAuthService(services); - services.AddHealthChecks(checks => - { - checks.AddValueTaskCheck("HTTP Endpoint", () => new ValueTask(HealthCheckResult.Healthy("Ok"))); - - var accountName = Configuration.GetValue("AzureStorageAccountName"); - var accountKey = Configuration.GetValue("AzureStorageAccountKey"); - if (!string.IsNullOrEmpty(accountName) && !string.IsNullOrEmpty(accountKey)) - { - checks.AddAzureBlobStorageCheck(accountName, accountKey); - } - }); - services.AddDbContext(options => { options.UseSqlServer(Configuration["ConnectionString"], @@ -160,7 +154,8 @@ services.AddCors(options => { options.AddPolicy("CorsPolicy", - builder => builder.AllowAnyOrigin() + builder => builder + .SetIsOriginAllowed((host) => true) .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials()); @@ -194,9 +189,16 @@ app.UsePathBase(pathBase); } -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - app.Map("/liveness", lapp => lapp.Run(async ctx => ctx.Response.StatusCode = 200)); -#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + app.UseHealthChecks("/hc", new HealthCheckOptions() + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); + + app.UseHealthChecks("/liveness", new HealthCheckOptions + { + Predicate = r => r.Name.Contains("self") + }); app.UseCors("CorsPolicy"); @@ -307,4 +309,55 @@ app.UseAuthentication(); } } + + public static class CustomExtensionMethods + { + public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration) + { + var hcBuilder = services.AddHealthChecks(); + + hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); + + hcBuilder + .AddSqlServer( + configuration["ConnectionString"], + name: "MarketingDB-check", + tags: new string[] { "marketingdb" }) + .AddMongoDb( + configuration["MongoConnectionString"], + name: "MarketingDB-mongodb-check", + tags: new string[] { "mongodb" }); + + var accountName = configuration.GetValue("AzureStorageAccountName"); + var accountKey = configuration.GetValue("AzureStorageAccountKey"); + if (!string.IsNullOrEmpty(accountName) && !string.IsNullOrEmpty(accountKey)) + { + hcBuilder + .AddAzureBlobStorage( + $"DefaultEndpointsProtocol=https;AccountName={accountName};AccountKey={accountKey};EndpointSuffix=core.windows.net", + name: "marketing-storage-check", + tags: new string[] { "marketingstorage" }); + } + + if (configuration.GetValue("AzureServiceBusEnabled")) + { + hcBuilder + .AddAzureServiceBusTopic( + configuration["EventBusConnection"], + topicName: "eshop_event_bus", + name: "marketing-servicebus-check", + tags: new string[] { "servicebus" }); + } + else + { + hcBuilder + .AddRabbitMQ( + $"amqp://{configuration["EventBusConnection"]}", + name: "marketing-rabbitmqbus-check", + tags: new string[] { "rabbitmqbus" }); + } + + return services; + } + } } diff --git a/src/Services/Marketing/Marketing.FunctionalTests/CampaignScenarios.cs b/src/Services/Marketing/Marketing.FunctionalTests/CampaignScenarios.cs index b2b49b92e..7085e27b5 100644 --- a/src/Services/Marketing/Marketing.FunctionalTests/CampaignScenarios.cs +++ b/src/Services/Marketing/Marketing.FunctionalTests/CampaignScenarios.cs @@ -27,7 +27,7 @@ namespace Marketing.FunctionalTests [Fact] public async Task Get_get_campaign_by_id_and_response_ok_status_code() { - var campaignId = 1; + var campaignId = 81; using (var server = CreateServer()) { var response = await server.CreateClient() diff --git a/src/Services/Marketing/Marketing.FunctionalTests/Marketing.FunctionalTests.csproj b/src/Services/Marketing/Marketing.FunctionalTests/Marketing.FunctionalTests.csproj index 9a5d833c8..2ffe9600b 100644 --- a/src/Services/Marketing/Marketing.FunctionalTests/Marketing.FunctionalTests.csproj +++ b/src/Services/Marketing/Marketing.FunctionalTests/Marketing.FunctionalTests.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp2.2 false @@ -18,7 +18,7 @@ - + diff --git a/src/Services/Marketing/Marketing.FunctionalTests/UserLocationRoleScenarios.cs b/src/Services/Marketing/Marketing.FunctionalTests/UserLocationRoleScenarios.cs index 196c1b459..fa734bbdf 100644 --- a/src/Services/Marketing/Marketing.FunctionalTests/UserLocationRoleScenarios.cs +++ b/src/Services/Marketing/Marketing.FunctionalTests/UserLocationRoleScenarios.cs @@ -28,7 +28,7 @@ namespace Marketing.FunctionalTests [Fact] public async Task Post_add_new_user_location_rule_and_response_ok_status_code() { - var campaignId = 1; + var campaignId = 81; using (var server = CreateServer()) { @@ -44,7 +44,7 @@ namespace Marketing.FunctionalTests [Fact] public async Task Delete_delete_user_location_role_and_response_not_content_status_code() { - var campaignId = 1; + var campaignId = 81; using (var server = CreateServer()) { diff --git a/src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs b/src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs new file mode 100644 index 000000000..6f9aed9e5 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs @@ -0,0 +1,62 @@ +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; +using Microsoft.Extensions.Logging; +using Ordering.API.Application.IntegrationEvents; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Behaviors +{ + public class TransactionBehaviour : IPipelineBehavior + { + private readonly ILogger> _logger; + private readonly OrderingContext _dbContext; + private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; + + public TransactionBehaviour(OrderingContext dbContext, + IOrderingIntegrationEventService orderingIntegrationEventService, + ILogger> logger) + { + _dbContext = dbContext ?? throw new ArgumentException(nameof(OrderingContext)); + _orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentException(nameof(orderingIntegrationEventService)); + _logger = logger ?? throw new ArgumentException(nameof(ILogger)); + } + + public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) + { + TResponse response = default(TResponse); + + try + { + var strategy = _dbContext.Database.CreateExecutionStrategy(); + await strategy.ExecuteAsync(async () => + { + _logger.LogInformation($"Begin transaction {typeof(TRequest).Name}"); + + await _dbContext.BeginTransactionAsync(); + + response = await next(); + + await _dbContext.CommitTransactionAsync(); + + _logger.LogInformation($"Committed transaction {typeof(TRequest).Name}"); + + await _orderingIntegrationEventService.PublishEventsThroughEventBusAsync(); + }); + + return response; + } + catch (Exception) + { + _logger.LogInformation($"Rollback transaction executed {typeof(TRequest).Name}"); + + _dbContext.RollbackTransaction(); + throw; + } + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs index e5f154c0c..9a3035d5c 100644 --- a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs @@ -1,6 +1,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands { using Domain.AggregatesModel.OrderAggregate; + using global::Ordering.API.Application.IntegrationEvents; + using global::Ordering.API.Application.IntegrationEvents.Events; using MediatR; using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; @@ -15,17 +17,26 @@ private readonly IOrderRepository _orderRepository; private readonly IIdentityService _identityService; private readonly IMediator _mediator; + private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; // Using DI to inject infrastructure persistence Repositories - public CreateOrderCommandHandler(IMediator mediator, IOrderRepository orderRepository, IIdentityService identityService) + public CreateOrderCommandHandler(IMediator mediator, + IOrderingIntegrationEventService orderingIntegrationEventService, + IOrderRepository orderRepository, + IIdentityService identityService) { _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService)); } public async Task Handle(CreateOrderCommand message, CancellationToken cancellationToken) { + // Add Integration event to clean the basket + var orderStartedIntegrationEvent = new OrderStartedIntegrationEvent(message.UserId); + await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStartedIntegrationEvent); + // Add/Update the Buyer AggregateRoot // DDD patterns comment: Add child entities and value-objects through the Order Aggregate-Root // methods and constructor so validations, invariants and business logic diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommand.cs b/src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommand.cs new file mode 100644 index 000000000..2007b95c6 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommand.cs @@ -0,0 +1,21 @@ +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Commands +{ + public class SetAwaitingValidationOrderStatusCommand : IRequest + { + + [DataMember] + public int OrderNumber { get; private set; } + + public SetAwaitingValidationOrderStatusCommand(int orderNumber) + { + OrderNumber = orderNumber; + } + } +} \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommandHandler.cs new file mode 100644 index 000000000..cee307ca2 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommandHandler.cs @@ -0,0 +1,52 @@ +using MediatR; +using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; +using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; +using System.Threading; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Commands +{ + // Regular CommandHandler + public class SetAwaitingValidationOrderStatusCommandHandler : IRequestHandler + { + private readonly IOrderRepository _orderRepository; + + public SetAwaitingValidationOrderStatusCommandHandler(IOrderRepository orderRepository) + { + _orderRepository = orderRepository; + } + + /// + /// Handler which processes the command when + /// graceperiod has finished + /// + /// + /// + public async Task Handle(SetAwaitingValidationOrderStatusCommand command, CancellationToken cancellationToken) + { + var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); + if(orderToUpdate == null) + { + return false; + } + + orderToUpdate.SetAwaitingValidationStatus(); + return await _orderRepository.UnitOfWork.SaveEntitiesAsync(); + } + } + + + // Use for Idempotency in Command process + public class SetAwaitingValidationIdentifiedOrderStatusCommandHandler : IdentifiedCommandHandler + { + public SetAwaitingValidationIdentifiedOrderStatusCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager) + { + } + + protected override bool CreateResultForDuplicateRequest() + { + return true; // Ignore duplicate requests for processing order. + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommand.cs b/src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommand.cs new file mode 100644 index 000000000..12bab9ac5 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommand.cs @@ -0,0 +1,21 @@ +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Commands +{ + public class SetPaidOrderStatusCommand : IRequest + { + + [DataMember] + public int OrderNumber { get; private set; } + + public SetPaidOrderStatusCommand(int orderNumber) + { + OrderNumber = orderNumber; + } + } +} \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommandHandler.cs new file mode 100644 index 000000000..211e568cb --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommandHandler.cs @@ -0,0 +1,55 @@ +using MediatR; +using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; +using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; +using System.Threading; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Commands +{ + // Regular CommandHandler + public class SetPaidOrderStatusCommandHandler : IRequestHandler + { + private readonly IOrderRepository _orderRepository; + + public SetPaidOrderStatusCommandHandler(IOrderRepository orderRepository) + { + _orderRepository = orderRepository; + } + + /// + /// Handler which processes the command when + /// Shipment service confirms the payment + /// + /// + /// + public async Task Handle(SetPaidOrderStatusCommand command, CancellationToken cancellationToken) + { + // Simulate a work time for validating the payment + await Task.Delay(10000); + + var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); + if(orderToUpdate == null) + { + return false; + } + + orderToUpdate.SetPaidStatus(); + return await _orderRepository.UnitOfWork.SaveEntitiesAsync(); + } + } + + + // Use for Idempotency in Command process + public class SetPaidIdentifiedOrderStatusCommandHandler : IdentifiedCommandHandler + { + public SetPaidIdentifiedOrderStatusCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager) + { + } + + protected override bool CreateResultForDuplicateRequest() + { + return true; // Ignore duplicate requests for processing order. + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommand.cs b/src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommand.cs new file mode 100644 index 000000000..74f002e21 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommand.cs @@ -0,0 +1,21 @@ +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Commands +{ + public class SetStockConfirmedOrderStatusCommand : IRequest + { + + [DataMember] + public int OrderNumber { get; private set; } + + public SetStockConfirmedOrderStatusCommand(int orderNumber) + { + OrderNumber = orderNumber; + } + } +} \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommandHandler.cs new file mode 100644 index 000000000..4e1bc6185 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommandHandler.cs @@ -0,0 +1,55 @@ +using MediatR; +using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; +using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; +using System.Threading; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Commands +{ + // Regular CommandHandler + public class SetStockConfirmedOrderStatusCommandHandler : IRequestHandler + { + private readonly IOrderRepository _orderRepository; + + public SetStockConfirmedOrderStatusCommandHandler(IOrderRepository orderRepository) + { + _orderRepository = orderRepository; + } + + /// + /// Handler which processes the command when + /// Stock service confirms the request + /// + /// + /// + public async Task Handle(SetStockConfirmedOrderStatusCommand command, CancellationToken cancellationToken) + { + // Simulate a work time for confirming the stock + await Task.Delay(10000); + + var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); + if(orderToUpdate == null) + { + return false; + } + + orderToUpdate.SetStockConfirmedStatus(); + return await _orderRepository.UnitOfWork.SaveEntitiesAsync(); + } + } + + + // Use for Idempotency in Command process + public class SetStockConfirmedOrderStatusIdenfifiedCommandHandler : IdentifiedCommandHandler + { + public SetStockConfirmedOrderStatusIdenfifiedCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager) + { + } + + protected override bool CreateResultForDuplicateRequest() + { + return true; // Ignore duplicate requests for processing order. + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommand.cs b/src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommand.cs new file mode 100644 index 000000000..c2293c0aa --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommand.cs @@ -0,0 +1,25 @@ +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Commands +{ + public class SetStockRejectedOrderStatusCommand : IRequest + { + + [DataMember] + public int OrderNumber { get; private set; } + + [DataMember] + public List OrderStockItems { get; private set; } + + public SetStockRejectedOrderStatusCommand(int orderNumber, List orderStockItems) + { + OrderNumber = orderNumber; + OrderStockItems = orderStockItems; + } + } +} \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommandHandler.cs new file mode 100644 index 000000000..2b7a72526 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommandHandler.cs @@ -0,0 +1,56 @@ +using MediatR; +using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; +using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; +using System.Threading; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Commands +{ + // Regular CommandHandler + public class SetStockRejectedOrderStatusCommandHandler : IRequestHandler + { + private readonly IOrderRepository _orderRepository; + + public SetStockRejectedOrderStatusCommandHandler(IOrderRepository orderRepository) + { + _orderRepository = orderRepository; + } + + /// + /// Handler which processes the command when + /// Stock service rejects the request + /// + /// + /// + public async Task Handle(SetStockRejectedOrderStatusCommand command, CancellationToken cancellationToken) + { + // Simulate a work time for rejecting the stock + await Task.Delay(10000); + + var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); + if(orderToUpdate == null) + { + return false; + } + + orderToUpdate.SetCancelledStatusWhenStockIsRejected(command.OrderStockItems); + + return await _orderRepository.UnitOfWork.SaveEntitiesAsync(); + } + } + + + // Use for Idempotency in Command process + public class SetStockRejectedOrderStatusIdenfifiedCommandHandler : IdentifiedCommandHandler + { + public SetStockRejectedOrderStatusIdenfifiedCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager) + { + } + + protected override bool CreateResultForDuplicateRequest() + { + return true; // Ignore duplicate requests for processing order. + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelled/OrderCancelledDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelled/OrderCancelledDomainEventHandler.cs index f8a7b06e5..32967f6a7 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelled/OrderCancelledDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelled/OrderCancelledDomainEventHandler.cs @@ -41,7 +41,7 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderCancelled var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString()); var orderStatusChangedToCancelledIntegrationEvent = new OrderStatusChangedToCancelledIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name); - await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToCancelledIntegrationEvent); + await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToCancelledIntegrationEvent); } } } diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderGracePeriodConfirmed/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderGracePeriodConfirmed/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs index 60efead1b..e1c54af4f 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderGracePeriodConfirmed/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderGracePeriodConfirmed/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs @@ -46,7 +46,7 @@ var orderStatusChangedToAwaitingValidationIntegrationEvent = new OrderStatusChangedToAwaitingValidationIntegrationEvent( order.Id, order.OrderStatus.Name, buyer.Name, orderStockList); - await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToAwaitingValidationIntegrationEvent); + await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToAwaitingValidationIntegrationEvent); } } } \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderPaid/OrderStatusChangedToPaidDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderPaid/OrderStatusChangedToPaidDomainEventHandler.cs index 59c1e4708..d3dca202f 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderPaid/OrderStatusChangedToPaidDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderPaid/OrderStatusChangedToPaidDomainEventHandler.cs @@ -51,7 +51,7 @@ buyer.Name, orderStockList); - await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToPaidIntegrationEvent); + await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToPaidIntegrationEvent); } } } \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShipped/OrderShippedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShipped/OrderShippedDomainEventHandler.cs index 0bf4cabcd..3be83a2ae 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShipped/OrderShippedDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShipped/OrderShippedDomainEventHandler.cs @@ -41,7 +41,7 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderShipped var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString()); var orderStatusChangedToShippedIntegrationEvent = new OrderStatusChangedToShippedIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name); - await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToShippedIntegrationEvent); + await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToShippedIntegrationEvent); } } } diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs index 0a8366893..99b2a21a0 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs @@ -59,7 +59,7 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderStartedEvent .SaveEntitiesAsync(); var orderStatusChangedTosubmittedIntegrationEvent = new OrderStatusChangedToSubmittedIntegrationEvent(orderStartedEvent.Order.Id, orderStartedEvent.Order.OrderStatus.Name, buyer.Name); - await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedTosubmittedIntegrationEvent); + await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedTosubmittedIntegrationEvent); _logger.CreateLogger(nameof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler)).LogTrace($"Buyer {buyerUpdated.Id} and related payment method were validated or updated for orderId: {orderStartedEvent.Order.Id}."); } diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmed/OrderStatusChangedToStockConfirmedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmed/OrderStatusChangedToStockConfirmedDomainEventHandler.cs index 910d764cf..e910964e8 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmed/OrderStatusChangedToStockConfirmedDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmed/OrderStatusChangedToStockConfirmedDomainEventHandler.cs @@ -41,7 +41,7 @@ var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString()); var orderStatusChangedToStockConfirmedIntegrationEvent = new OrderStatusChangedToStockConfirmedIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name); - await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToStockConfirmedIntegrationEvent); + await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToStockConfirmedIntegrationEvent); } } } \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs index c51619ff6..f8dcc6edb 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs @@ -1,5 +1,7 @@ -using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using MediatR; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; +using Ordering.API.Application.Commands; using Ordering.API.Application.IntegrationEvents.Events; using System.Threading.Tasks; @@ -7,11 +9,11 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling { public class GracePeriodConfirmedIntegrationEventHandler : IIntegrationEventHandler { - private readonly IOrderRepository _orderRepository; + private readonly IMediator _mediator; - public GracePeriodConfirmedIntegrationEventHandler(IOrderRepository orderRepository) + public GracePeriodConfirmedIntegrationEventHandler(IMediator mediator) { - _orderRepository = orderRepository; + _mediator = mediator; } /// @@ -24,9 +26,8 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling /// public async Task Handle(GracePeriodConfirmedIntegrationEvent @event) { - var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId); - orderToUpdate.SetAwaitingValidationStatus(); - await _orderRepository.UnitOfWork.SaveEntitiesAsync(); + var command = new SetAwaitingValidationOrderStatusCommand(@event.OrderId); + await _mediator.Send(command); } } } diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs index 259b7ec34..5f4fc28e1 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs @@ -1,27 +1,27 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling { + using MediatR; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; + using Ordering.API.Application.Commands; using Ordering.API.Application.IntegrationEvents.Events; + using System; using System.Threading.Tasks; public class OrderPaymentFailedIntegrationEventHandler : IIntegrationEventHandler { - private readonly IOrderRepository _orderRepository; + private readonly IMediator _mediator; - public OrderPaymentFailedIntegrationEventHandler(IOrderRepository orderRepository) + public OrderPaymentFailedIntegrationEventHandler(IMediator mediator) { - _orderRepository = orderRepository; + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); } public async Task Handle(OrderPaymentFailedIntegrationEvent @event) { - var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId); - - orderToUpdate.SetCancelledStatus(); - - await _orderRepository.UnitOfWork.SaveEntitiesAsync(); + var command = new CancelOrderCommand(@event.OrderId); + await _mediator.Send(command); } } } diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs index 1dbe59e10..6c201d77e 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs @@ -1,30 +1,27 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling { + using MediatR; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; + using Ordering.API.Application.Commands; using Ordering.API.Application.IntegrationEvents.Events; + using System; using System.Threading.Tasks; public class OrderPaymentSuccededIntegrationEventHandler : IIntegrationEventHandler { - private readonly IOrderRepository _orderRepository; + private readonly IMediator _mediator; - public OrderPaymentSuccededIntegrationEventHandler(IOrderRepository orderRepository) + public OrderPaymentSuccededIntegrationEventHandler(IMediator mediator) { - _orderRepository = orderRepository; + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); } public async Task Handle(OrderPaymentSuccededIntegrationEvent @event) { - // Simulate a work time for validating the payment - await Task.Delay(10000); - - var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId); - - orderToUpdate.SetPaidStatus(); - - await _orderRepository.UnitOfWork.SaveEntitiesAsync(); + var command = new SetPaidOrderStatusCommand(@event.OrderId); + await _mediator.Send(command); } } } \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs index c08554066..c5561508b 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs @@ -4,27 +4,24 @@ using System.Threading.Tasks; using Events; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; + using MediatR; + using System; + using Ordering.API.Application.Commands; public class OrderStockConfirmedIntegrationEventHandler : IIntegrationEventHandler { - private readonly IOrderRepository _orderRepository; + private readonly IMediator _mediator; - public OrderStockConfirmedIntegrationEventHandler(IOrderRepository orderRepository) + public OrderStockConfirmedIntegrationEventHandler(IMediator mediator) { - _orderRepository = orderRepository; + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); } public async Task Handle(OrderStockConfirmedIntegrationEvent @event) { - // Simulate a work time for confirming the stock - await Task.Delay(10000); - - var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId); - - orderToUpdate.SetStockConfirmedStatus(); - - await _orderRepository.UnitOfWork.SaveEntitiesAsync(); + var command = new SetStockConfirmedOrderStatusCommand(@event.OrderId); + await _mediator.Send(command); } } } \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs index c70eba187..af7d98f74 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs @@ -5,27 +5,27 @@ using Events; using System.Linq; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; + using MediatR; + using Ordering.API.Application.Commands; public class OrderStockRejectedIntegrationEventHandler : IIntegrationEventHandler { - private readonly IOrderRepository _orderRepository; + private readonly IMediator _mediator; - public OrderStockRejectedIntegrationEventHandler(IOrderRepository orderRepository) + public OrderStockRejectedIntegrationEventHandler(IMediator mediator) { - _orderRepository = orderRepository; + _mediator = mediator; } public async Task Handle(OrderStockRejectedIntegrationEvent @event) { - var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId); - var orderStockRejectedItems = @event.OrderStockItems .FindAll(c => !c.HasStock) - .Select(c => c.ProductId); - - orderToUpdate.SetCancelledStatusWhenStockIsRejected(orderStockRejectedItems); + .Select(c => c.ProductId) + .ToList(); - await _orderRepository.UnitOfWork.SaveEntitiesAsync(); + var command = new SetStockRejectedOrderStatusCommand(@event.OrderId, orderStockRejectedItems); + await _mediator.Send(command); } } } \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs index f46c5683c..33f327c6b 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs @@ -11,15 +11,13 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling public class UserCheckoutAcceptedIntegrationEventHandler : IIntegrationEventHandler { private readonly IMediator _mediator; - private readonly ILoggerFactory _logger; - private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; + private readonly ILoggerFactory _logger; public UserCheckoutAcceptedIntegrationEventHandler(IMediator mediator, - ILoggerFactory logger, IOrderingIntegrationEventService orderingIntegrationEventService) + ILoggerFactory logger) { _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } /// @@ -34,11 +32,7 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling public async Task Handle(UserCheckoutAcceptedIntegrationEvent eventMsg) { var result = false; - - // Send Integration event to clean basket once basket is converted to Order and before starting with the order creation process - var orderStartedIntegrationEvent = new OrderStartedIntegrationEvent(eventMsg.UserId); - await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStartedIntegrationEvent); - + if (eventMsg.RequestId != Guid.Empty) { var createOrderCommand = new CreateOrderCommand(eventMsg.Basket.Items, eventMsg.UserId, eventMsg.UserName, eventMsg.City, eventMsg.Street, diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/IOrderingIntegrationEventService.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/IOrderingIntegrationEventService.cs index 373bafaa5..05e8f0e4f 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/IOrderingIntegrationEventService.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/IOrderingIntegrationEventService.cs @@ -5,6 +5,7 @@ namespace Ordering.API.Application.IntegrationEvents { public interface IOrderingIntegrationEventService { - Task PublishThroughEventBusAsync(IntegrationEvent evt); + Task PublishEventsThroughEventBusAsync(); + Task AddAndSaveEventAsync(IntegrationEvent evt); } } diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs index b3c0201b5..9c1bd4e1b 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs @@ -2,12 +2,14 @@ using Microsoft.EntityFrameworkCore.Storage; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities; using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; using System; using System.Data.Common; using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; namespace Ordering.API.Application.IntegrationEvents @@ -17,34 +19,42 @@ namespace Ordering.API.Application.IntegrationEvents private readonly Func _integrationEventLogServiceFactory; private readonly IEventBus _eventBus; private readonly OrderingContext _orderingContext; + private readonly IntegrationEventLogContext _eventLogContext; private readonly IIntegrationEventLogService _eventLogService; - public OrderingIntegrationEventService(IEventBus eventBus, OrderingContext orderingContext, - Func integrationEventLogServiceFactory) + public OrderingIntegrationEventService(IEventBus eventBus, + OrderingContext orderingContext, + IntegrationEventLogContext eventLogContext, + Func integrationEventLogServiceFactory) { _orderingContext = orderingContext ?? throw new ArgumentNullException(nameof(orderingContext)); + _eventLogContext = eventLogContext ?? throw new ArgumentNullException(nameof(eventLogContext)); _integrationEventLogServiceFactory = integrationEventLogServiceFactory ?? throw new ArgumentNullException(nameof(integrationEventLogServiceFactory)); _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); _eventLogService = _integrationEventLogServiceFactory(_orderingContext.Database.GetDbConnection()); } - public async Task PublishThroughEventBusAsync(IntegrationEvent evt) + public async Task PublishEventsThroughEventBusAsync() { - await SaveEventAndOrderingContextChangesAsync(evt); - _eventBus.Publish(evt); - await _eventLogService.MarkEventAsPublishedAsync(evt); + var pendindLogEvents = await _eventLogService.RetrieveEventLogsPendingToPublishAsync(); + foreach (var logEvt in pendindLogEvents) + { + try + { + await _eventLogService.MarkEventAsInProgressAsync(logEvt.EventId); + _eventBus.Publish(logEvt.IntegrationEvent); + await _eventLogService.MarkEventAsPublishedAsync(logEvt.EventId); + } + catch (Exception) + { + await _eventLogService.MarkEventAsFailedAsync(logEvt.EventId); + } + } } - private async Task SaveEventAndOrderingContextChangesAsync(IntegrationEvent evt) + public async Task AddAndSaveEventAsync(IntegrationEvent evt) { - //Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction(): - //See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency - await ResilientTransaction.New(_orderingContext) - .ExecuteAsync(async () => { - // Achieving atomicity between original ordering database operation and the IntegrationEventLog thanks to a local transaction - await _orderingContext.SaveChangesAsync(); - await _eventLogService.SaveEventAsync(evt, _orderingContext.Database.CurrentTransaction.GetDbTransaction()); - }); + await _eventLogService.SaveEventAsync(evt, _orderingContext.GetCurrentTransaction.GetDbTransaction()); } } } diff --git a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs b/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs index 5542caf11..e2c711b69 100644 --- a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs +++ b/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs @@ -15,7 +15,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers [Route("api/v1/[controller]")] [Authorize] [ApiController] - public class OrdersController : Controller + public class OrdersController : ControllerBase { private readonly IMediator _mediator; private readonly IOrderQueries _orderQueries; @@ -23,7 +23,6 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers public OrdersController(IMediator mediator, IOrderQueries orderQueries, IIdentityService identityService) { - _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); _orderQueries = orderQueries ?? throw new ArgumentNullException(nameof(orderQueries)); _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); @@ -33,84 +32,89 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers [HttpPut] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task CancelOrder([FromBody]CancelOrderCommand command, [FromHeader(Name = "x-requestid")] string requestId) + public async Task CancelOrderAsync([FromBody]CancelOrderCommand command, [FromHeader(Name = "x-requestid")] string requestId) { bool commandResult = false; + if (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty) { var requestCancelOrder = new IdentifiedCommand(command, guid); commandResult = await _mediator.Send(requestCancelOrder); } - - return commandResult ? (IActionResult)Ok() : (IActionResult)BadRequest(); + if (!commandResult) + { + return BadRequest(); + } + + return Ok(); } [Route("ship")] [HttpPut] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task ShipOrder([FromBody]ShipOrderCommand command, [FromHeader(Name = "x-requestid")] string requestId) + public async Task ShipOrderAsync([FromBody]ShipOrderCommand command, [FromHeader(Name = "x-requestid")] string requestId) { bool commandResult = false; + if (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty) { var requestShipOrder = new IdentifiedCommand(command, guid); commandResult = await _mediator.Send(requestShipOrder); } - return commandResult ? (IActionResult)Ok() : (IActionResult)BadRequest(); + if (!commandResult) + { + return BadRequest(); + } + return Ok(); } [Route("{orderId:int}")] [HttpGet] [ProducesResponseType(typeof(Order),(int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NotFound)] - public async Task GetOrder(int orderId) + public async Task GetOrderAsync(int orderId) { try { - var order = await _orderQueries - .GetOrderAsync(orderId); + var order = await _orderQueries.GetOrderAsync(orderId); return Ok(order); } - catch (KeyNotFoundException) + catch { return NotFound(); } } - [Route("")] [HttpGet] [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)] - public async Task GetOrders() + public async Task>> GetOrdersAsync() { var userid = _identityService.GetUserIdentity(); var orders = await _orderQueries.GetOrdersFromUserAsync(Guid.Parse(userid)); + return Ok(orders); } [Route("cardtypes")] [HttpGet] [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)] - public async Task GetCardTypes() + public async Task>> GetCardTypesAsync() { - var cardTypes = await _orderQueries - .GetCardTypesAsync(); + var cardTypes = await _orderQueries.GetCardTypesAsync(); return Ok(cardTypes); - } + } [Route("draft")] [HttpPost] - public async Task GetOrderDraftFromBasketData([FromBody] CreateOrderDraftCommand createOrderDraftCommand) + public async Task> CreateOrderDraftFromBasketDataAsync([FromBody] CreateOrderDraftCommand createOrderDraftCommand) { - var draft = await _mediator.Send(createOrderDraftCommand); - return Ok(draft); + return await _mediator.Send(createOrderDraftCommand); } } -} - - +} \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Dockerfile b/src/Services/Ordering/Ordering.API/Dockerfile index 78c153641..e836d2728 100644 --- a/src/Services/Ordering/Ordering.API/Dockerfile +++ b/src/Services/Ordering/Ordering.API/Dockerfile @@ -1,14 +1,20 @@ -FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +FROM microsoft/dotnet:2.2.0-aspnetcore-runtime AS base WORKDIR /app EXPOSE 80 -FROM microsoft/dotnet:2.1-sdk AS build +FROM microsoft/dotnet:2.2.100-sdk AS build WORKDIR /src COPY . . WORKDIR /src/src/Services/Ordering/Ordering.API RUN dotnet restore -nowarn:msb3202,nu1503 RUN dotnet build --no-restore -c Release -o /app +FROM build as functionaltest +WORKDIR /src/src/Services/Ordering/Ordering.FunctionalTests + +FROM build as unittest +WORKDIR /src/src/Services/Ordering/Ordering.UnitTests + FROM build AS publish RUN dotnet publish --no-restore -c Release -o /app diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs index e720c7b76..99a413f9f 100644 --- a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs +++ b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs @@ -4,6 +4,7 @@ using Autofac; using FluentValidation; using MediatR; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; +using Ordering.API.Application.Behaviors; using Ordering.API.Application.DomainEventHandlers.OrderStartedEvent; using Ordering.API.Application.Validations; using Ordering.API.Infrastructure.Behaviors; @@ -40,6 +41,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof builder.RegisterGeneric(typeof(LoggingBehavior<,>)).As(typeof(IPipelineBehavior<,>)); builder.RegisterGeneric(typeof(ValidatorBehavior<,>)).As(typeof(IPipelineBehavior<,>)); + builder.RegisterGeneric(typeof(TransactionBehaviour<,>)).As(typeof(IPipelineBehavior<,>)); + } } } diff --git a/src/Services/Ordering/Ordering.API/Ordering.API.csproj b/src/Services/Ordering/Ordering.API/Ordering.API.csproj index 71bf02c6b..abd68e9a6 100644 --- a/src/Services/Ordering/Ordering.API/Ordering.API.csproj +++ b/src/Services/Ordering/Ordering.API/Ordering.API.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp2.2 aspnet-Ordering.API-20161122013547 $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; ..\..\..\..\docker-compose.dcproj @@ -20,16 +20,17 @@ - - - - + + + + + @@ -37,10 +38,14 @@ - - - + + + + + + + diff --git a/src/Services/Ordering/Ordering.API/Program.cs b/src/Services/Ordering/Ordering.API/Program.cs index 92ef6ba86..b97ced4d4 100644 --- a/src/Services/Ordering/Ordering.API/Program.cs +++ b/src/Services/Ordering/Ordering.API/Program.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Serilog; using System; using System.IO; @@ -34,7 +35,6 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup() - .UseHealthChecks("/hc") .UseContentRoot(Directory.GetCurrentDirectory()) .ConfigureAppConfiguration((builderContext, config) => { @@ -61,6 +61,13 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API builder.AddDebug(); }) .UseApplicationInsights() + .UseSerilog((builderContext, config) => + { + config + .MinimumLevel.Information() + .Enrich.FromLogContext() + .WriteTo.Console(); + }) .Build(); } } diff --git a/src/Services/Ordering/Ordering.API/Startup.cs b/src/Services/Ordering/Ordering.API/Startup.cs index 42567641a..b517dfaa9 100644 --- a/src/Services/Ordering/Ordering.API/Startup.cs +++ b/src/Services/Ordering/Ordering.API/Startup.cs @@ -26,7 +26,6 @@ using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.HealthChecks; using Microsoft.Extensions.Logging; using Ordering.Infrastructure; using RabbitMQ.Client; @@ -36,6 +35,9 @@ using System.Data.Common; using System.IdentityModel.Tokens.Jwt; using System.Reflection; + using HealthChecks.UI.Client; + using Microsoft.AspNetCore.Diagnostics.HealthChecks; + using Microsoft.Extensions.Diagnostics.HealthChecks; public class Startup { @@ -80,14 +82,21 @@ { loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'"); app.UsePathBase(pathBase); - } - -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - app.Map("/liveness", lapp => lapp.Run(async ctx => ctx.Response.StatusCode = 200)); -#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + } app.UseCors("CorsPolicy"); + app.UseHealthChecks("/liveness", new HealthCheckOptions + { + Predicate = r => r.Name.Contains("self") + }); + + app.UseHealthChecks("/hc", new HealthCheckOptions() + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); + ConfigureAuth(app); app.UseMvcWithDefaultRoute(); @@ -113,7 +122,7 @@ eventBus.Subscribe>(); eventBus.Subscribe>(); eventBus.Subscribe>(); - eventBus.Subscribe>(); + eventBus.Subscribe>(); } @@ -154,15 +163,18 @@ { // Add framework services. services.AddMvc(options => - { - options.Filters.Add(typeof(HttpGlobalExceptionFilter)); - }).AddControllersAsServices(); //Injecting Controllers themselves thru DI + { + options.Filters.Add(typeof(HttpGlobalExceptionFilter)); + }) + .SetCompatibilityVersion(CompatibilityVersion.Version_2_2) + .AddControllersAsServices(); //Injecting Controllers themselves thru DI //For further info see: http://docs.autofac.org/en/latest/integration/aspnetcore.html#controllers-as-services services.AddCors(options => { options.AddPolicy("CorsPolicy", - builder => builder.AllowAnyOrigin() + builder => builder + .SetIsOriginAllowed((host) => true) .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials()); @@ -173,15 +185,33 @@ public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) { - services.AddHealthChecks(checks => + var hcBuilder = services.AddHealthChecks(); + + hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); + + hcBuilder + .AddSqlServer( + configuration["ConnectionString"], + name: "OrderingDB-check", + tags: new string[] { "orderingdb" }); + + if (configuration.GetValue("AzureServiceBusEnabled")) { - var minutes = 1; - if (int.TryParse(configuration["HealthCheck:Timeout"], out var minutesParsed)) - { - minutes = minutesParsed; - } - checks.AddSqlCheck("OrderingDb", configuration["ConnectionString"], TimeSpan.FromMinutes(minutes)); - }); + hcBuilder + .AddAzureServiceBusTopic( + configuration["EventBusConnection"], + topicName: "eshop_event_bus", + name: "ordering-servicebus-check", + tags: new string[] { "servicebus" }); + } + else + { + hcBuilder + .AddRabbitMQ( + $"amqp://{configuration["EventBusConnection"]}", + name: "ordering-rabbitmqbus-check", + tags: new string[] { "rabbitmqbus" }); + } return services; } diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/Dockerfile b/src/Services/Ordering/Ordering.BackgroundTasks/Dockerfile index 0c01dcb96..7b66be834 100644 --- a/src/Services/Ordering/Ordering.BackgroundTasks/Dockerfile +++ b/src/Services/Ordering/Ordering.BackgroundTasks/Dockerfile @@ -1,8 +1,8 @@ -FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +FROM microsoft/dotnet:2.2.0-aspnetcore-runtime AS base WORKDIR /app EXPOSE 80 -FROM microsoft/dotnet:2.1-sdk AS build +FROM microsoft/dotnet:2.2.100-sdk AS build WORKDIR /src COPY . . WORKDIR /src/src/Services/Ordering/Ordering.BackgroundTasks diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj b/src/Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj index 5ca8b4b3b..b757cada9 100644 --- a/src/Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj +++ b/src/Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp2.2 $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; ..\..\..\..\docker-compose.dcproj @@ -14,18 +14,23 @@ + + + - + + + + + + - - - diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/Program.cs b/src/Services/Ordering/Ordering.BackgroundTasks/Program.cs index 23d0a5e42..3c78efa10 100644 --- a/src/Services/Ordering/Ordering.BackgroundTasks/Program.cs +++ b/src/Services/Ordering/Ordering.BackgroundTasks/Program.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Logging; +using Serilog; namespace Ordering.BackgroundTasks { @@ -13,13 +14,20 @@ namespace Ordering.BackgroundTasks public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) - .UseStartup() - .UseHealthChecks("/hc") + .UseStartup() .ConfigureLogging((hostingContext, builder) => { builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); builder.AddDebug(); builder.AddConsole(); - }).Build(); + }) + .UseSerilog((builderContext, config) => + { + config + .MinimumLevel.Information() + .Enrich.FromLogContext() + .WriteTo.Console(); + }) + .Build(); } } diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/Startup.cs b/src/Services/Ordering/Ordering.BackgroundTasks/Startup.cs index 2bc085dbf..5844a3964 100644 --- a/src/Services/Ordering/Ordering.BackgroundTasks/Startup.cs +++ b/src/Services/Ordering/Ordering.BackgroundTasks/Startup.cs @@ -9,13 +9,15 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.HealthChecks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Ordering.BackgroundTasks.Configuration; using Ordering.BackgroundTasks.Tasks; using RabbitMQ.Client; using System; +using HealthChecks.UI.Client; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.Diagnostics.HealthChecks; namespace Ordering.BackgroundTasks { @@ -32,16 +34,7 @@ namespace Ordering.BackgroundTasks public IServiceProvider ConfigureServices(IServiceCollection services) { //add health check for this service - services.AddHealthChecks(checks => - { - var minutes = 1; - - if (int.TryParse(Configuration["HealthCheck:Timeout"], out var minutesParsed)) - { - minutes = minutesParsed; - } - checks.AddSqlCheck("OrderingDb", Configuration["ConnectionString"], TimeSpan.FromMinutes(minutes)); - }); + services.AddCustomHealthCheck(Configuration); //configure settings @@ -112,10 +105,16 @@ namespace Ordering.BackgroundTasks // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IHostingEnvironment env) { + app.UseHealthChecks("/hc", new HealthCheckOptions() + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - app.Map("/liveness", lapp => lapp.Run(async ctx => ctx.Response.StatusCode = 200)); -#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + app.UseHealthChecks("/liveness", new HealthCheckOptions + { + Predicate = r => r.Name.Contains("self") + }); } @@ -158,4 +157,40 @@ namespace Ordering.BackgroundTasks services.AddSingleton(); } } + + public static class CustomExtensionMethods + { + public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration) + { + var hcBuilder = services.AddHealthChecks(); + + hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); + + hcBuilder + .AddSqlServer( + configuration["ConnectionString"], + name: "OrderingTaskDB-check", + tags: new string[] { "orderingtaskdb" }); + + if (configuration.GetValue("AzureServiceBusEnabled")) + { + hcBuilder + .AddAzureServiceBusTopic( + configuration["EventBusConnection"], + topicName: "eshop_event_bus", + name: "orderingtask-servicebus-check", + tags: new string[] { "servicebus" }); + } + else + { + hcBuilder + .AddRabbitMQ( + $"amqp://{configuration["EventBusConnection"]}", + name: "orderingtask-rabbitmqbus-check", + tags: new string[] { "rabbitmqbus" }); + } + + return services; + } + } } diff --git a/src/Services/Ordering/Ordering.FunctionalTests/Ordering.FunctionalTests.csproj b/src/Services/Ordering/Ordering.FunctionalTests/Ordering.FunctionalTests.csproj index 23fff0c44..1568886f0 100644 --- a/src/Services/Ordering/Ordering.FunctionalTests/Ordering.FunctionalTests.csproj +++ b/src/Services/Ordering/Ordering.FunctionalTests/Ordering.FunctionalTests.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp2.2 false @@ -18,7 +18,7 @@ - + diff --git a/src/Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj b/src/Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj index 72cd00442..d57c30031 100644 --- a/src/Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj +++ b/src/Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj @@ -9,9 +9,9 @@ - - - + + + diff --git a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs index 564acdfb4..ac9ec608f 100644 --- a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs +++ b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs @@ -1,12 +1,14 @@ using MediatR; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork; using Ordering.Infrastructure; using Ordering.Infrastructure.EntityConfigurations; using System; +using System.Data; using System.Threading; using System.Threading.Tasks; @@ -23,9 +25,12 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure public DbSet OrderStatus { get; set; } private readonly IMediator _mediator; + private IDbContextTransaction _currentTransaction; private OrderingContext(DbContextOptions options) : base (options) { } + public IDbContextTransaction GetCurrentTransaction => _currentTransaction; + public OrderingContext(DbContextOptions options, IMediator mediator) : base(options) { _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); @@ -60,7 +65,50 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure var result = await base.SaveChangesAsync(); return true; - } + } + + public async Task BeginTransactionAsync() + { + _currentTransaction = _currentTransaction ?? await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted); + } + + public async Task CommitTransactionAsync() + { + try + { + await SaveChangesAsync(); + _currentTransaction?.Commit(); + } + catch + { + RollbackTransaction(); + throw; + } + finally + { + if (_currentTransaction != null) + { + _currentTransaction.Dispose(); + _currentTransaction = null; + } + } + } + + public void RollbackTransaction() + { + try + { + _currentTransaction?.Rollback(); + } + finally + { + if (_currentTransaction != null) + { + _currentTransaction.Dispose(); + _currentTransaction = null; + } + } + } } public class OrderingContextDesignFactory : IDesignTimeDbContextFactory diff --git a/src/Services/Ordering/Ordering.SignalrHub/Dockerfile b/src/Services/Ordering/Ordering.SignalrHub/Dockerfile index 367b8db36..181e3ea0a 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/Dockerfile +++ b/src/Services/Ordering/Ordering.SignalrHub/Dockerfile @@ -1,8 +1,8 @@ -FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +FROM microsoft/dotnet:2.2.0-aspnetcore-runtime AS base WORKDIR /app EXPOSE 80 -FROM microsoft/dotnet:2.1-sdk AS build +FROM microsoft/dotnet:2.2.100-sdk AS build WORKDIR /src COPY . . WORKDIR /src/src/Services/Ordering/Ordering.SignalrHub diff --git a/src/Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj b/src/Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj index fad53bbcf..dfc6a45c3 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj +++ b/src/Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp2.2 ..\..\..\..\docker-compose.dcproj @@ -10,16 +10,24 @@ + + + - - - - + + + + + + + + + diff --git a/src/Services/Ordering/Ordering.SignalrHub/Program.cs b/src/Services/Ordering/Ordering.SignalrHub/Program.cs index 6598e5504..e8550dcc8 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/Program.cs +++ b/src/Services/Ordering/Ordering.SignalrHub/Program.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Serilog; namespace Ordering.SignalrHub { @@ -20,6 +21,13 @@ namespace Ordering.SignalrHub public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup() + .UseSerilog((builderContext, config) => + { + config + .MinimumLevel.Information() + .Enrich.FromLogContext() + .WriteTo.Console(); + }) .Build(); } } diff --git a/src/Services/Ordering/Ordering.SignalrHub/Startup.cs b/src/Services/Ordering/Ordering.SignalrHub/Startup.cs index 25a8b22d9..555606a67 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/Startup.cs +++ b/src/Services/Ordering/Ordering.SignalrHub/Startup.cs @@ -15,9 +15,11 @@ using Ordering.SignalrHub.IntegrationEvents; using Ordering.SignalrHub.IntegrationEvents.EventHandling; using Ordering.SignalrHub.IntegrationEvents.Events; using RabbitMQ.Client; -using StackExchange.Redis; using System; using System.IdentityModel.Tokens.Jwt; +using HealthChecks.UI.Client; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.Diagnostics.HealthChecks; namespace Ordering.SignalrHub { @@ -32,14 +34,17 @@ namespace Ordering.SignalrHub // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public IServiceProvider ConfigureServices(IServiceCollection services) { - services.AddCors(options => - { - options.AddPolicy("CorsPolicy", - builder => builder.AllowAnyOrigin() - .AllowAnyMethod() - .AllowAnyHeader() - .AllowCredentials()); - }); + services + .AddCustomHealthCheck(Configuration) + .AddCors(options => + { + options.AddPolicy("CorsPolicy", + builder => builder + .AllowAnyMethod() + .AllowAnyHeader() + .SetIsOriginAllowed((host) => true) + .AllowCredentials()); + }); if (Configuration.GetValue("IsClusterEnv") == bool.TrueString) { @@ -127,6 +132,17 @@ namespace Ordering.SignalrHub app.UsePathBase(pathBase); } + app.UseHealthChecks("/hc", new HealthCheckOptions() + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); + + app.UseHealthChecks("/liveness", new HealthCheckOptions + { + Predicate = r => r.Name.Contains("self") + }); + app.UseCors("CorsPolicy"); app.UseAuthentication(); @@ -211,4 +227,34 @@ namespace Ordering.SignalrHub services.AddSingleton(); } } + + public static class CustomExtensionMethods + { + public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration) + { + var hcBuilder = services.AddHealthChecks(); + + hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); + + if (configuration.GetValue("AzureServiceBusEnabled")) + { + hcBuilder + .AddAzureServiceBusTopic( + configuration["EventBusConnection"], + topicName: "eshop_event_bus", + name: "signalr-servicebus-check", + tags: new string[] { "servicebus" }); + } + else + { + hcBuilder + .AddRabbitMQ( + $"amqp://{configuration["EventBusConnection"]}", + name: "signalr-rabbitmqbus-check", + tags: new string[] { "rabbitmqbus" }); + } + + return services; + } + } } diff --git a/src/Services/Ordering/Ordering.UnitTests/Application/NewOrderCommandHandlerTest.cs b/src/Services/Ordering/Ordering.UnitTests/Application/NewOrderCommandHandlerTest.cs index 7fe02017b..80a0deb25 100644 --- a/src/Services/Ordering/Ordering.UnitTests/Application/NewOrderCommandHandlerTest.cs +++ b/src/Services/Ordering/Ordering.UnitTests/Application/NewOrderCommandHandlerTest.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; namespace UnitTest.Ordering.Application { + using global::Ordering.API.Application.IntegrationEvents; using global::Ordering.API.Application.Models; using MediatR; using System.Collections; @@ -22,12 +23,14 @@ namespace UnitTest.Ordering.Application private readonly Mock _orderRepositoryMock; private readonly Mock _identityServiceMock; private readonly Mock _mediator; + private readonly Mock _orderingIntegrationEventService; public NewOrderRequestHandlerTest() { _orderRepositoryMock = new Mock(); _identityServiceMock = new Mock(); + _orderingIntegrationEventService = new Mock(); _mediator = new Mock(); } @@ -48,7 +51,7 @@ namespace UnitTest.Ordering.Application _identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId); //Act - var handler = new CreateOrderCommandHandler(_mediator.Object, _orderRepositoryMock.Object, _identityServiceMock.Object); + var handler = new CreateOrderCommandHandler(_mediator.Object, _orderingIntegrationEventService.Object, _orderRepositoryMock.Object, _identityServiceMock.Object); var cltToken = new System.Threading.CancellationToken(); var result = await handler.Handle(fakeOrderCmd, cltToken); diff --git a/src/Services/Ordering/Ordering.UnitTests/Application/OrdersWebApiTest.cs b/src/Services/Ordering/Ordering.UnitTests/Application/OrdersWebApiTest.cs index aadd6c62a..c5b8a1764 100644 --- a/src/Services/Ordering/Ordering.UnitTests/Application/OrdersWebApiTest.cs +++ b/src/Services/Ordering/Ordering.UnitTests/Application/OrdersWebApiTest.cs @@ -36,7 +36,7 @@ namespace UnitTest.Ordering.Application //Act var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); - var actionResult = await orderController.CancelOrder(new CancelOrderCommand(1), Guid.NewGuid().ToString()) as OkResult; + var actionResult = await orderController.CancelOrderAsync(new CancelOrderCommand(1), Guid.NewGuid().ToString()) as OkResult; //Assert Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.OK); @@ -52,7 +52,7 @@ namespace UnitTest.Ordering.Application //Act var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); - var actionResult = await orderController.CancelOrder(new CancelOrderCommand(1), String.Empty) as BadRequestResult; + var actionResult = await orderController.CancelOrderAsync(new CancelOrderCommand(1), String.Empty) as BadRequestResult; //Assert Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.BadRequest); @@ -67,7 +67,7 @@ namespace UnitTest.Ordering.Application //Act var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); - var actionResult = await orderController.ShipOrder(new ShipOrderCommand(1), Guid.NewGuid().ToString()) as OkResult; + var actionResult = await orderController.ShipOrderAsync(new ShipOrderCommand(1), Guid.NewGuid().ToString()) as OkResult; //Assert Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.OK); @@ -83,7 +83,7 @@ namespace UnitTest.Ordering.Application //Act var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); - var actionResult = await orderController.ShipOrder(new ShipOrderCommand(1), String.Empty) as BadRequestResult; + var actionResult = await orderController.ShipOrderAsync(new ShipOrderCommand(1), String.Empty) as BadRequestResult; //Assert Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.BadRequest); @@ -103,10 +103,10 @@ namespace UnitTest.Ordering.Application //Act var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); - var actionResult = await orderController.GetOrders() as OkObjectResult; + var actionResult = await orderController.GetOrdersAsync(); //Assert - Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.OK); + Assert.Equal((actionResult.Result as OkObjectResult).StatusCode, (int)System.Net.HttpStatusCode.OK); } [Fact] @@ -120,7 +120,7 @@ namespace UnitTest.Ordering.Application //Act var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); - var actionResult = await orderController.GetOrder(fakeOrderId) as OkObjectResult; + var actionResult = await orderController.GetOrderAsync(fakeOrderId) as OkObjectResult; //Assert Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.OK); @@ -136,10 +136,10 @@ namespace UnitTest.Ordering.Application //Act var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); - var actionResult = await orderController.GetCardTypes() as OkObjectResult; + var actionResult = await orderController.GetCardTypesAsync(); //Assert - Assert.Equal(actionResult.StatusCode, (int)System.Net.HttpStatusCode.OK); + Assert.Equal((actionResult.Result as OkObjectResult).StatusCode, (int)System.Net.HttpStatusCode.OK); } } } diff --git a/src/Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj b/src/Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj index d039af0aa..04b155b65 100644 --- a/src/Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj +++ b/src/Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp2.2 false diff --git a/src/Services/Payment/Payment.API/Dockerfile b/src/Services/Payment/Payment.API/Dockerfile index d644417c6..cfa9a02de 100644 --- a/src/Services/Payment/Payment.API/Dockerfile +++ b/src/Services/Payment/Payment.API/Dockerfile @@ -1,8 +1,8 @@ -FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +FROM microsoft/dotnet:2.2.0-aspnetcore-runtime AS base WORKDIR /app EXPOSE 80 -FROM microsoft/dotnet:2.1-sdk AS build +FROM microsoft/dotnet:2.2.100-sdk AS build WORKDIR /src COPY . . WORKDIR /src/src/Services/Payment/Payment.API diff --git a/src/Services/Payment/Payment.API/Payment.API.csproj b/src/Services/Payment/Payment.API/Payment.API.csproj index 061e5f237..73aa4363e 100644 --- a/src/Services/Payment/Payment.API/Payment.API.csproj +++ b/src/Services/Payment/Payment.API/Payment.API.csproj @@ -1,28 +1,32 @@  - netcoreapp2.1 + netcoreapp2.2 ..\..\..\..\docker-compose.dcproj $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + + + + + + - + + - - - diff --git a/src/Services/Payment/Payment.API/Program.cs b/src/Services/Payment/Payment.API/Program.cs index aff7bf359..0b8c5e025 100644 --- a/src/Services/Payment/Payment.API/Program.cs +++ b/src/Services/Payment/Payment.API/Program.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Serilog; using System.IO; namespace Payment.API @@ -16,7 +17,6 @@ namespace Payment.API public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) - .UseHealthChecks("/hc") .UseContentRoot(Directory.GetCurrentDirectory()) .UseStartup() .ConfigureAppConfiguration((builderContext, config) => @@ -30,6 +30,13 @@ namespace Payment.API builder.AddDebug(); }) .UseApplicationInsights() + .UseSerilog((builderContext, config) => + { + config + .MinimumLevel.Information() + .Enrich.FromLogContext() + .WriteTo.Console(); + }) .Build(); } } diff --git a/src/Services/Payment/Payment.API/Startup.cs b/src/Services/Payment/Payment.API/Startup.cs index 02cd4c70e..5241fc1c7 100644 --- a/src/Services/Payment/Payment.API/Startup.cs +++ b/src/Services/Payment/Payment.API/Startup.cs @@ -1,5 +1,7 @@ using Autofac; using Autofac.Extensions.DependencyInjection; +using Microsoft.ApplicationInsights.Extensibility; +using Microsoft.ApplicationInsights.ServiceFabric; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Azure.ServiceBus; @@ -9,15 +11,14 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.HealthChecks; using Microsoft.Extensions.Logging; using Payment.API.IntegrationEvents.EventHandling; using Payment.API.IntegrationEvents.Events; using RabbitMQ.Client; using System; -using System.Threading.Tasks; -using Microsoft.ApplicationInsights.Extensibility; -using Microsoft.ApplicationInsights.ServiceFabric; +using HealthChecks.UI.Client; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.Diagnostics.HealthChecks; namespace Payment.API { @@ -32,7 +33,8 @@ namespace Payment.API // This method gets called by the runtime. Use this method to add services to the container. public IServiceProvider ConfigureServices(IServiceCollection services) - { + { + services.AddCustomHealthCheck(Configuration); services.Configure(Configuration); RegisterAppInsights(services); @@ -77,12 +79,7 @@ namespace Payment.API return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount); }); - } - - services.AddHealthChecks(checks => - { - checks.AddValueTaskCheck("HTTP Endpoint", () => new ValueTask(HealthCheckResult.Healthy("Ok"))); - }); + } RegisterEventBus(services); @@ -103,9 +100,17 @@ namespace Payment.API app.UsePathBase(pathBase); } -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - app.Map("/liveness", lapp => lapp.Run(async ctx => ctx.Response.StatusCode = 200)); -#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + + app.UseHealthChecks("/hc", new HealthCheckOptions() + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); + + app.UseHealthChecks("/liveness", new HealthCheckOptions + { + Predicate = r => r.Name.Contains("self") + }); ConfigureEventBus(app); } @@ -174,4 +179,34 @@ namespace Payment.API eventBus.Subscribe(); } } + + public static class CustomExtensionMethods + { + public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration) + { + var hcBuilder = services.AddHealthChecks(); + + hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); + + if (configuration.GetValue("AzureServiceBusEnabled")) + { + hcBuilder + .AddAzureServiceBusTopic( + configuration["EventBusConnection"], + topicName: "eshop_event_bus", + name: "payment-servicebus-check", + tags: new string[] { "servicebus" }); + } + else + { + hcBuilder + .AddRabbitMQ( + $"amqp://{configuration["EventBusConnection"]}", + name: "payment-rabbitmqbus-check", + tags: new string[] { "rabbitmqbus" }); + } + + return services; + } + } } \ No newline at end of file diff --git a/src/Web/WebMVC/Dockerfile b/src/Web/WebMVC/Dockerfile index 80a64017e..1a77e4e05 100644 --- a/src/Web/WebMVC/Dockerfile +++ b/src/Web/WebMVC/Dockerfile @@ -1,11 +1,8 @@ -FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +FROM microsoft/dotnet:2.2.0-aspnetcore-runtime AS base WORKDIR /app EXPOSE 80 -FROM microsoft/dotnet:2.1-sdk as dotnet-build -WORKDIR /src - -FROM dotnet-build as build +FROM microsoft/dotnet:2.2.100-sdk AS build WORKDIR /src COPY . . WORKDIR /src/src/Web/WebMVC diff --git a/src/Web/WebMVC/Program.cs b/src/Web/WebMVC/Program.cs index 07332a02a..7f88feeaf 100644 --- a/src/Web/WebMVC/Program.cs +++ b/src/Web/WebMVC/Program.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Serilog; using System.IO; namespace Microsoft.eShopOnContainers.WebMVC @@ -16,7 +17,6 @@ namespace Microsoft.eShopOnContainers.WebMVC public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseContentRoot(Directory.GetCurrentDirectory()) - .UseHealthChecks("/hc") .UseStartup() .ConfigureAppConfiguration((builderContext, config) => { @@ -29,6 +29,13 @@ namespace Microsoft.eShopOnContainers.WebMVC builder.AddDebug(); }) .UseApplicationInsights() + .UseSerilog((builderContext, config) => + { + config + .MinimumLevel.Information() + .Enrich.FromLogContext() + .WriteTo.Console(); + }) .Build(); } } diff --git a/src/Web/WebMVC/Startup.cs b/src/Web/WebMVC/Startup.cs index fe1f5de21..d69b21459 100644 --- a/src/Web/WebMVC/Startup.cs +++ b/src/Web/WebMVC/Startup.cs @@ -1,16 +1,19 @@ -using Microsoft.ApplicationInsights.Extensibility; +using HealthChecks.UI.Client; +using Microsoft.ApplicationInsights.Extensibility; using Microsoft.ApplicationInsights.ServiceFabric; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Microsoft.eShopOnContainers.WebMVC.Services; using Microsoft.eShopOnContainers.WebMVC.ViewModels; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.HealthChecks; +using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging; using Polly; using Polly.Extensions.Http; @@ -52,6 +55,12 @@ namespace Microsoft.eShopOnContainers.WebMVC loggerFactory.AddAzureWebAppDiagnostics(); loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); + app.UseHealthChecks("/hc", new HealthCheckOptions() + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); + if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); @@ -59,6 +68,7 @@ namespace Microsoft.eShopOnContainers.WebMVC else { app.UseExceptionHandler("/Error"); + app.UseHsts(); } var pathBase = Configuration["PATH_BASE"]; @@ -68,10 +78,10 @@ namespace Microsoft.eShopOnContainers.WebMVC app.UsePathBase(pathBase); } - -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - app.Map("/liveness", lapp => lapp.Run(async ctx => ctx.Response.StatusCode = 200)); -#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + app.UseHealthChecks("/liveness", new HealthCheckOptions + { + Predicate = r => r.Name.Contains("self") + }); app.UseSession(); app.UseStaticFiles(); @@ -87,6 +97,7 @@ namespace Microsoft.eShopOnContainers.WebMVC WebContextSeed.Seed(app, env, loggerFactory); + app.UseHttpsRedirection(); app.UseMvc(routes => { routes.MapRoute( @@ -126,21 +137,12 @@ namespace Microsoft.eShopOnContainers.WebMVC public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) { - services.AddHealthChecks(checks => - { - var minutes = 1; - if (int.TryParse(configuration["HealthCheck:Timeout"], out var minutesParsed)) - { - minutes = minutesParsed; - } - - checks.AddUrlCheck(configuration["CatalogUrlHC"], TimeSpan.FromMinutes(minutes)); - checks.AddUrlCheck(configuration["OrderingUrlHC"], TimeSpan.FromMinutes(minutes)); - checks.AddUrlCheck(configuration["BasketUrlHC"], TimeSpan.Zero); //No cache for this HealthCheck, better just for demos - checks.AddUrlCheck(configuration["IdentityUrlHC"], TimeSpan.FromMinutes(minutes)); - checks.AddUrlCheck(configuration["MarketingUrlHC"], TimeSpan.FromMinutes(minutes)); - }); - + services.AddHealthChecks() + .AddCheck("self", () => HealthCheckResult.Healthy()) + .AddUrlGroup(new Uri(configuration["PurchaseUrlHC"]), name: "purchaseapigw-check", tags: new string[] { "purchaseapigw" }) + .AddUrlGroup(new Uri(configuration["MarketingUrlHC"]), name: "marketingapigw-check", tags: new string[] { "marketingapigw" }) + .AddUrlGroup(new Uri(configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" }); + return services; } @@ -149,7 +151,8 @@ namespace Microsoft.eShopOnContainers.WebMVC services.AddOptions(); services.Configure(configuration); - services.AddMvc(); + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddSession(); diff --git a/src/Web/WebMVC/WebMVC.csproj b/src/Web/WebMVC/WebMVC.csproj index 552b3842a..5e5643c63 100644 --- a/src/Web/WebMVC/WebMVC.csproj +++ b/src/Web/WebMVC/WebMVC.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp2.2 aspnet-Microsoft.eShopOnContainers-946ae052-8305-4a99-965b-ec8636ddbae3 ..\..\..\docker-compose.dcproj 3.0 @@ -19,18 +19,25 @@ + + + - - - - + + + + + + + + @@ -41,12 +48,6 @@ - - - - - - diff --git a/src/Web/WebSPA/Dockerfile b/src/Web/WebSPA/Dockerfile index 4e806786c..d732684bf 100644 --- a/src/Web/WebSPA/Dockerfile +++ b/src/Web/WebSPA/Dockerfile @@ -1,9 +1,9 @@ ARG NODE_IMAGE=node:8.11 -FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +FROM microsoft/dotnet:2.2.0-aspnetcore-runtime AS base WORKDIR /app EXPOSE 80 -FROM microsoft/dotnet:2.1-sdk as dotnet-build +FROM microsoft/dotnet:2.2.100-sdk AS dotnet-build WORKDIR /src FROM ${NODE_IMAGE} as node-build diff --git a/src/Web/WebSPA/Program.cs b/src/Web/WebSPA/Program.cs index c1a7da269..f4a1ca1b4 100644 --- a/src/Web/WebSPA/Program.cs +++ b/src/Web/WebSPA/Program.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Hosting; using System.IO; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Configuration; +using Serilog; namespace eShopConContainers.WebSPA { @@ -16,7 +17,6 @@ namespace eShopConContainers.WebSPA public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup() - .UseHealthChecks("/hc") .UseContentRoot(Directory.GetCurrentDirectory()) .ConfigureAppConfiguration((builderContext, config) => { @@ -28,7 +28,14 @@ namespace eShopConContainers.WebSPA builder.AddConsole(); builder.AddDebug(); }) - .UseApplicationInsights() + .UseApplicationInsights() + .UseSerilog((builderContext, config) => + { + config + .MinimumLevel.Information() + .Enrich.FromLogContext() + .WriteTo.Console(); + }) .Build(); } } diff --git a/src/Web/WebSPA/Startup.cs b/src/Web/WebSPA/Startup.cs index f49eba772..5fe5d03b2 100644 --- a/src/Web/WebSPA/Startup.cs +++ b/src/Web/WebSPA/Startup.cs @@ -5,15 +5,18 @@ using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.HealthChecks; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Serialization; using StackExchange.Redis; using System; using System.IO; using WebSPA.Infrastructure; +using HealthChecks.UI.Client; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.Diagnostics.HealthChecks; namespace eShopConContainers.WebSPA { @@ -41,21 +44,11 @@ namespace eShopConContainers.WebSPA { RegisterAppInsights(services); - services.AddHealthChecks(checks => - { - var minutes = 1; - if (int.TryParse(Configuration["HealthCheck:Timeout"], out var minutesParsed)) - { - minutes = minutesParsed; - } - - checks.AddUrlCheck(Configuration["CatalogUrlHC"], TimeSpan.FromMinutes(minutes)); - checks.AddUrlCheck(Configuration["OrderingUrlHC"], TimeSpan.FromMinutes(minutes)); - checks.AddUrlCheck(Configuration["BasketUrlHC"], TimeSpan.Zero); //No cache for this HealthCheck, better just for demos - checks.AddUrlCheck(Configuration["IdentityUrlHC"], TimeSpan.FromMinutes(minutes)); - checks.AddUrlCheck(Configuration["MarketingUrlHC"], TimeSpan.FromMinutes(minutes)); - - }); + services.AddHealthChecks() + .AddCheck("self", () => HealthCheckResult.Healthy()) + .AddUrlGroup(new Uri(Configuration["PurchaseUrlHC"]), name: "purchaseapigw-check", tags: new string[] { "purchaseapigw" }) + .AddUrlGroup(new Uri(Configuration["MarketingUrlHC"]), name: "marketingapigw-check", tags: new string[] { "marketingapigw" }) + .AddUrlGroup(new Uri(Configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" }); services.Configure(Configuration); @@ -71,6 +64,7 @@ namespace eShopConContainers.WebSPA services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN"); services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Version_2_2) .AddJsonOptions(options => { options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); @@ -89,6 +83,22 @@ namespace eShopConContainers.WebSPA { app.UseDeveloperExceptionPage(); } + else + { + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + + app.UseHealthChecks("/liveness", new HealthCheckOptions + { + Predicate = r => r.Name.Contains("self") + }); + + app.UseHealthChecks("/hc", new HealthCheckOptions() + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); // Configure XSRF middleware, This pattern is for SPA style applications where XSRF token is added on Index page // load and passed back token on every subsequent async request @@ -112,11 +122,6 @@ namespace eShopConContainers.WebSPA app.UsePathBase(pathBase); } - -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - app.Map("/liveness", lapp => lapp.Run(async ctx => ctx.Response.StatusCode = 200)); -#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously - app.Use(async (context, next) => { await next(); diff --git a/src/Web/WebSPA/WebSPA.csproj b/src/Web/WebSPA/WebSPA.csproj index c833124ac..0d79706e5 100644 --- a/src/Web/WebSPA/WebSPA.csproj +++ b/src/Web/WebSPA/WebSPA.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp2.2 aspnetcorespa-c23d27a4-eb88-4b18-9b77-2a93f3b15119 ..\..\..\docker-compose.dcproj false @@ -85,14 +85,19 @@ + + - - - + + + + + + @@ -105,11 +110,6 @@ --> - - - - - @@ -175,7 +175,7 @@ - + diff --git a/src/Web/WebSPA/package-lock.json b/src/Web/WebSPA/package-lock.json index a254c3cd5..9c3e3a021 100644 --- a/src/Web/WebSPA/package-lock.json +++ b/src/Web/WebSPA/package-lock.json @@ -3047,12 +3047,6 @@ "is-obj": "^1.0.0" } }, - "duplexer": { - "version": "0.1.1", - "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", - "dev": true - }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -3456,22 +3450,6 @@ "es5-ext": "~0.10.14" } }, - "event-stream": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.6.tgz", - "integrity": "sha512-dGXNg4F/FgVzlApjzItL+7naHutA3fDqbV/zAZqDDlXTjiMnQmZKu+prImWKszeBM5UQeGvAl3u1wBiKeDh61g==", - "dev": true, - "requires": { - "duplexer": "^0.1.1", - "flatmap-stream": "^0.1.0", - "from": "^0.1.7", - "map-stream": "0.0.7", - "pause-stream": "^0.0.11", - "split": "^1.0.1", - "stream-combiner": "^0.2.2", - "through": "^2.3.8" - } - }, "eventemitter3": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", @@ -3932,12 +3910,6 @@ "write": "^0.2.1" } }, - "flatmap-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/flatmap-stream/-/flatmap-stream-0.1.0.tgz", - "integrity": "sha512-Nlic4ZRYxikqnK5rj3YoxDVKGGtUjcNDUtvQ7XsdGLZmMwdUYnXf10o1zcXtzEZTBgc6GxeRpQxV/Wu3WPIIHA==", - "dev": true - }, "flush-write-stream": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", @@ -4012,12 +3984,6 @@ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", "dev": true }, - "from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", - "dev": true - }, "from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -6379,12 +6345,6 @@ "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", "dev": true }, - "map-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", - "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", - "dev": true - }, "map-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", @@ -6936,16 +6896,16 @@ } }, "nodemon": { - "version": "1.18.4", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.18.4.tgz", - "integrity": "sha512-hyK6vl65IPnky/ee+D3IWvVGgJa/m3No2/Xc/3wanS6Ce1MWjCzH6NnhPJ/vZM+6JFym16jtHx51lmCMB9HDtg==", + "version": "1.18.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.18.7.tgz", + "integrity": "sha512-xuC1V0F5EcEyKQ1VhHYD13owznQbUw29JKvZ8bVH7TmuvVNHvvbp9pLgE4PjTMRJVe0pJ8fGRvwR2nMiosIsPQ==", "dev": true, "requires": { - "chokidar": "^2.0.2", + "chokidar": "^2.0.4", "debug": "^3.1.0", "ignore-by-default": "^1.0.1", "minimatch": "^3.0.4", - "pstree.remy": "^1.1.0", + "pstree.remy": "^1.1.2", "semver": "^5.5.0", "supports-color": "^5.2.0", "touch": "^3.1.0", @@ -7046,12 +7006,12 @@ } }, "npm-watch": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/npm-watch/-/npm-watch-0.4.0.tgz", - "integrity": "sha512-ekAUqXjQHSM3pAEB2kSHUzzU6i3Auucv8RwCz4sdNfLXVeysaXKSKbod3nIp+p3jb0u9mwDDdZ55uHPqMaWSdQ==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/npm-watch/-/npm-watch-0.5.0.tgz", + "integrity": "sha512-Rww9iNlmsVhcpUTDoKfUlbfmEtTZ3T3gUZBW/VjuL4ct/nwV0XlwBDOxR0ECJ/cLF8PF1wPVa/IrinI6K5ECQQ==", "dev": true, "requires": { - "nodemon": "^1.12.1", + "nodemon": "^1.18.7", "through2": "^2.0.0" } }, @@ -7481,15 +7441,6 @@ "pify": "^3.0.0" } }, - "pause-stream": { - "version": "0.0.11", - "resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", - "dev": true, - "requires": { - "through": "~2.3" - } - }, "pbkdf2": { "version": "3.0.16", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.16.tgz", @@ -7840,15 +7791,6 @@ "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" }, - "ps-tree": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz", - "integrity": "sha1-tCGyQUDWID8e08dplrRCewjowBQ=", - "dev": true, - "requires": { - "event-stream": "~3.3.0" - } - }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -7861,13 +7803,10 @@ "dev": true }, "pstree.remy": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.0.tgz", - "integrity": "sha512-q5I5vLRMVtdWa8n/3UEzZX7Lfghzrg9eG2IKk2ENLSofKRCXVqMvMUHxCKgXNaqH/8ebhBxrqftHWnyTFweJ5Q==", - "dev": true, - "requires": { - "ps-tree": "^1.1.0" - } + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.2.tgz", + "integrity": "sha512-vL6NLxNHzkNTjGJUpMm5PLC+94/0tTlC1vkP9bdU0pOHih+EujMjgMTwfZopZvHWRFbqJ5Y73OMoau50PewDDA==", + "dev": true }, "public-encrypt": { "version": "4.0.2", @@ -9222,15 +9161,6 @@ } } }, - "split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "dev": true, - "requires": { - "through": "2" - } - }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -9323,16 +9253,6 @@ "readable-stream": "^2.0.2" } }, - "stream-combiner": { - "version": "0.2.2", - "resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", - "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=", - "dev": true, - "requires": { - "duplexer": "~0.1.1", - "through": "~2.3.4" - } - }, "stream-each": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", @@ -10962,9 +10882,9 @@ } }, "widest-line": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.0.tgz", - "integrity": "sha1-AUKk6KJD+IgsAjOqDgKBqnYVInM=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", "dev": true, "requires": { "string-width": "^2.1.1" diff --git a/src/Web/WebSPA/package.json b/src/Web/WebSPA/package.json index c8e54d4e6..1c2b54af9 100644 --- a/src/Web/WebSPA/package.json +++ b/src/Web/WebSPA/package.json @@ -48,7 +48,7 @@ "normalize.css": "8.0.0", "popper.js": "^1.14.4", "preboot": "6.0.0-beta.5", - "rxjs": "^6.2.2", + "rxjs": "6.2.2", "webpack": "^4.17.1", "zone.js": "^0.8.26" }, @@ -70,6 +70,6 @@ "typedoc": "0.12.0", "typescript": "2.9.2", "url-loader": "1.1.1", - "npm-watch": "0.4.0" + "npm-watch": "0.5.0" } } diff --git a/src/Web/WebStatus/Controllers/HomeController.cs b/src/Web/WebStatus/Controllers/HomeController.cs index 7f139843a..15f912bc2 100644 --- a/src/Web/WebStatus/Controllers/HomeController.cs +++ b/src/Web/WebStatus/Controllers/HomeController.cs @@ -1,32 +1,22 @@ using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.HealthChecks; +using Microsoft.Extensions.Configuration; using System.Threading.Tasks; -using WebStatus.Viewmodels; namespace WebStatus.Controllers { public class HomeController : Controller { - private readonly IHealthCheckService _healthCheckSvc; - public HomeController(IHealthCheckService checkSvc) + private IConfiguration _configuration; + + public HomeController(IConfiguration configuration) { - _healthCheckSvc = checkSvc; + _configuration = configuration; } - public async Task Index() + public IActionResult Index() { - var result = await _healthCheckSvc.CheckHealthAsync(); - - var data = new HealthStatusViewModel(result.CheckStatus); - - foreach (var checkResult in result.Results) - { - data.AddResult(checkResult.Key, checkResult.Value); - } - - ViewBag.RefreshSeconds = 60; - - return View(data); + var basePath = _configuration["PATH_BASE"]; + return Redirect($"{basePath}/hc-ui"); } public IActionResult Error() diff --git a/src/Web/WebStatus/Dockerfile b/src/Web/WebStatus/Dockerfile index 10cfa25e5..df0883f68 100644 --- a/src/Web/WebStatus/Dockerfile +++ b/src/Web/WebStatus/Dockerfile @@ -1,8 +1,8 @@ -FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +FROM microsoft/dotnet:2.2.0-aspnetcore-runtime AS base WORKDIR /app EXPOSE 80 -FROM microsoft/dotnet:2.1-sdk AS build +FROM microsoft/dotnet:2.2.100-sdk AS build WORKDIR /src COPY . . WORKDIR /src/src/Web/WebStatus diff --git a/src/Web/WebStatus/Extensions/HealthCheckBuilderExtensions.cs b/src/Web/WebStatus/Extensions/HealthCheckBuilderExtensions.cs deleted file mode 100644 index a6076df52..000000000 --- a/src/Web/WebStatus/Extensions/HealthCheckBuilderExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Microsoft.Extensions.HealthChecks; -using System; - -namespace WebStatus.Extensions -{ - public static class HealthCheckBuilderExtensions - { - public static HealthCheckBuilder AddUrlCheckIfNotNull(this HealthCheckBuilder builder, string url, TimeSpan cacheDuration) - { - if (!string.IsNullOrEmpty(url)) - { - builder.AddUrlCheck(url, cacheDuration); - } - - return builder; - } - } -} diff --git a/src/Web/WebStatus/Program.cs b/src/Web/WebStatus/Program.cs index adab0e6b3..e9f5a4453 100644 --- a/src/Web/WebStatus/Program.cs +++ b/src/Web/WebStatus/Program.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Serilog; using System.IO; namespace WebStatus @@ -16,11 +17,7 @@ namespace WebStatus public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseContentRoot(Directory.GetCurrentDirectory()) - .UseStartup() - .ConfigureAppConfiguration((builderContext, config) => - { - config.AddEnvironmentVariables(); - }) + .UseStartup() .ConfigureLogging((hostingContext, builder) => { builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); @@ -28,6 +25,13 @@ namespace WebStatus builder.AddDebug(); }) .UseApplicationInsights() + .UseSerilog((builderContext, config) => + { + config + .MinimumLevel.Information() + .Enrich.FromLogContext() + .WriteTo.Console(); + }) .Build(); } } diff --git a/src/Web/WebStatus/Startup.cs b/src/Web/WebStatus/Startup.cs index 6a9ed32d1..b97ca1616 100644 --- a/src/Web/WebStatus/Startup.cs +++ b/src/Web/WebStatus/Startup.cs @@ -1,15 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using HealthChecks.UI.Client; +using Microsoft.ApplicationInsights.Extensibility; +using Microsoft.ApplicationInsights.ServiceFabric; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging; -using WebStatus.Extensions; -using Microsoft.ApplicationInsights.Extensibility; -using Microsoft.ApplicationInsights.ServiceFabric; namespace WebStatus { @@ -28,29 +27,13 @@ namespace WebStatus RegisterAppInsights(services); services.AddOptions(); + services.AddHealthChecks() + .AddCheck("self", () => HealthCheckResult.Healthy()); - // Add framework services. - services.AddHealthChecks(checks => - { - var minutes = 1; - if (int.TryParse(Configuration["HealthCheck:Timeout"], out var minutesParsed)) - { - minutes = minutesParsed; - } - - checks.AddUrlCheckIfNotNull(Configuration["OrderingUrl"], TimeSpan.FromMinutes(minutes)); - checks.AddUrlCheckIfNotNull(Configuration["OrderingBackgroundTasksUrl"], TimeSpan.FromMinutes(minutes)); - checks.AddUrlCheckIfNotNull(Configuration["BasketUrl"], TimeSpan.Zero); //No cache for this HealthCheck, better just for demos - checks.AddUrlCheckIfNotNull(Configuration["CatalogUrl"], TimeSpan.FromMinutes(minutes)); - checks.AddUrlCheckIfNotNull(Configuration["IdentityUrl"], TimeSpan.FromMinutes(minutes)); - checks.AddUrlCheckIfNotNull(Configuration["LocationsUrl"], TimeSpan.FromMinutes(minutes)); - checks.AddUrlCheckIfNotNull(Configuration["MarketingUrl"], TimeSpan.FromMinutes(minutes)); - checks.AddUrlCheckIfNotNull(Configuration["PaymentUrl"], TimeSpan.FromMinutes(minutes)); - checks.AddUrlCheckIfNotNull(Configuration["mvc"], TimeSpan.Zero); //No cache for this HealthCheck, better just for demos - checks.AddUrlCheckIfNotNull(Configuration["spa"], TimeSpan.Zero); //No cache for this HealthCheck, better just for demos - }); - - services.AddMvc(); + services.AddHealthChecksUI(); + + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -66,6 +49,8 @@ namespace WebStatus else { app.UseExceptionHandler("/Home/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); } var pathBase = Configuration["PATH_BASE"]; @@ -74,13 +59,20 @@ namespace WebStatus app.UsePathBase(pathBase); } + app.UseHealthChecks("/liveness", new HealthCheckOptions + { + Predicate = r => r.Name.Contains("self") + }); -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - app.Map("/liveness", lapp => lapp.Run(async ctx => ctx.Response.StatusCode = 200)); -#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously - + app.UseHealthChecksUI(config => { + config.ResourcesPath = string.IsNullOrEmpty(pathBase) ? "/ui/resources" : $"{pathBase}/ui/resources"; + config.UIPath = "/hc-ui"; + }); + app.UseStaticFiles(); + app.UseHttpsRedirection(); + app.UseMvc(routes => { routes.MapRoute( diff --git a/src/Web/WebStatus/Viewmodels/HealthStatusViewModel.cs b/src/Web/WebStatus/Viewmodels/HealthStatusViewModel.cs deleted file mode 100644 index 08126ce4f..000000000 --- a/src/Web/WebStatus/Viewmodels/HealthStatusViewModel.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.Extensions.HealthChecks; -using System.Collections.Generic; -using System.Linq; - -namespace WebStatus.Viewmodels -{ - public class HealthStatusViewModel - { - private readonly CheckStatus _overall; - - private readonly Dictionary _results; - - public CheckStatus OverallStatus => _overall; - - public IEnumerable Results => _results.Select(kvp => new NamedCheckResult(kvp.Key, kvp.Value)); - - private HealthStatusViewModel() => _results = new Dictionary(); - - public HealthStatusViewModel(CheckStatus overall) : this() => _overall = overall; - - public void AddResult(string name, IHealthCheckResult result) => _results.Add(name, result); - } -} diff --git a/src/Web/WebStatus/Viewmodels/NamedCheckResult.cs b/src/Web/WebStatus/Viewmodels/NamedCheckResult.cs deleted file mode 100644 index f6a9cd316..000000000 --- a/src/Web/WebStatus/Viewmodels/NamedCheckResult.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.Extensions.HealthChecks; - -namespace WebStatus.Viewmodels -{ - public class NamedCheckResult - { - public string Name { get; } - - public IHealthCheckResult Result { get; } - - public NamedCheckResult(string name, IHealthCheckResult result) - { - Name = name; - Result = result; - } - } -} diff --git a/src/Web/WebStatus/Views/Home/Index.cshtml b/src/Web/WebStatus/Views/Home/Index.cshtml deleted file mode 100644 index a3befe611..000000000 --- a/src/Web/WebStatus/Views/Home/Index.cshtml +++ /dev/null @@ -1,51 +0,0 @@ -@using Microsoft.AspNetCore.Html -@using Microsoft.Extensions.HealthChecks -@model WebStatus.Viewmodels.HealthStatusViewModel - -@{ - ViewData["Title"] = "System Status"; - -} - -@functions -{ - static readonly string[] LabelClass = new[] { "default", "danger", "success", "warning" }; - - public HtmlString StatusLabel(CheckStatus status) - { - return new HtmlString($@"{status}"); - } - -} - - - - -
-
-

Overall Status: @StatusLabel(Model.OverallStatus)

-
-
- -
- @foreach (var result in Model.Results) - { -
-
-

@result.Name

-

- @if (result.Result.Data.ContainsKey("url")) - { -

@result.Result.Data["url"]

- } -

- @result.Result.Description -

-

-
-
-

@StatusLabel(result.Result.CheckStatus)

-
-
- } -
\ No newline at end of file diff --git a/src/Web/WebStatus/Views/Shared/Error.cshtml b/src/Web/WebStatus/Views/Shared/Error.cshtml deleted file mode 100644 index e514139c4..000000000 --- a/src/Web/WebStatus/Views/Shared/Error.cshtml +++ /dev/null @@ -1,14 +0,0 @@ -@{ - ViewData["Title"] = "Error"; -} - -

Error.

-

An error occurred while processing your request.

- -

Development Mode

-

- Swapping to Development environment will display more detailed information about the error that occurred. -

-

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

diff --git a/src/Web/WebStatus/Views/Shared/_Layout.cshtml b/src/Web/WebStatus/Views/Shared/_Layout.cshtml deleted file mode 100644 index b74f006e4..000000000 --- a/src/Web/WebStatus/Views/Shared/_Layout.cshtml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - @ViewData["Title"] - WebStatus - - - - - - - - - - - @if (ViewBag.RefreshSeconds != null && ViewBag.RefreshSeconds > 0) - { - - } - - - - -
-
- @RenderBody() -
-
- - - - - - - - - - - - - diff --git a/src/Web/WebStatus/Views/_ViewImports.cshtml b/src/Web/WebStatus/Views/_ViewImports.cshtml deleted file mode 100644 index 6a5648da4..000000000 --- a/src/Web/WebStatus/Views/_ViewImports.cshtml +++ /dev/null @@ -1,2 +0,0 @@ -@using WebStatus -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/src/Web/WebStatus/Views/_ViewStart.cshtml b/src/Web/WebStatus/Views/_ViewStart.cshtml deleted file mode 100644 index a5f10045d..000000000 --- a/src/Web/WebStatus/Views/_ViewStart.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -@{ - Layout = "_Layout"; -} diff --git a/src/Web/WebStatus/WebStatus.csproj b/src/Web/WebStatus/WebStatus.csproj index 4755ec744..8bbb27b96 100644 --- a/src/Web/WebStatus/WebStatus.csproj +++ b/src/Web/WebStatus/WebStatus.csproj @@ -1,22 +1,30 @@  - netcoreapp2.1 + netcoreapp2.2 $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; ..\..\..\docker-compose.dcproj + + + + + + + + + + + + - - - - - - + + diff --git a/src/Web/WebStatus/appsettings.json b/src/Web/WebStatus/appsettings.json index 2ab5ad818..6f486424c 100644 --- a/src/Web/WebStatus/appsettings.json +++ b/src/Web/WebStatus/appsettings.json @@ -1,21 +1,84 @@ { - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" - } - }, - "OrderingUrl": "http://localhost:5102/hc", - "OrderingBackgroundTasksUrl": "http://localhost:5111/hc", - "BasketUrl": "http://localhost:5103/hc", - "CatalogUrl": "http://localhost:5101/hc", - "IdentityUrl": "http://localhost:5105/hc", - "MarketingUrl": "http://localhost:5110/hc", - "LocationsUrl": "http://localhost:5109/hc", - "PaymentUrl": "http://localhost:5108/hc", - "ApplicationInsights": { - "InstrumentationKey": "" + "HealthChecks-UI": { + "HealthChecks": [ + { + "Name": "Ordering HTTP Check", + "Uri": "http://localhost:5102/hc" + }, + { + "Name": "Ordering HTTP Background Check", + "Uri": "http://localhost:5111/hc" + }, + { + "Name": "Basket HTTP Check", + "Uri": "http://localhost:5103/hc" + }, + { + "Name": "Catalog HTTP Check", + "Uri": "http://localhost:5101/hc" + }, + { + "Name": "Identity HTTP Check", + "Uri": "http://localhost:5105/hc" + }, + { + "Name": "Marketing HTTP Check", + "Uri": "http://localhost:5110/hc" + }, + { + "Name": "Locations HTTP Check", + "Uri": "http://localhost:5109/hc" + }, + { + "Name": "Payments HTTP Check", + "Uri": "http://localhost:5108/hc" + }, + { + "Name": "WebMVC HTTP Check", + "Uri": "http://localhost:5100/hc" + }, + { + "Name": "WebSPA HTTP Check", + "Uri": "http://localhost:5104/hc" + }, + { + "Name": "SignalR HTTP Check", + "Uri": "http://localhost:5112/hc" + }, + { + "Name": "Mobile Shopping API GW HTTP Check", + "Uri": "http://localhost:5200/hc" + }, + { + "Name": "Mobile Marketing API GW HTTP Check", + "Uri": "http://localhost:5201/hc" + }, + { + "Name": "Web Shopping API GW HTTP Check", + "Uri": "http://localhost:5202/hc" + }, + { + "Name": "Web Marketing API GW HTTP Check", + "Uri": "http://localhost:5203/hc" + }, + { + "Name": "Mobile Shopping Aggregator HTTP Check", + "Uri": "http://localhost:5120/hc" + }, + { + "Name": "Web Shopping Aggregator HTTP Check", + "Uri": "http://localhost:5121/hc" + } + ], + "Webhooks": [ + { + "Name": "", + "Uri": "", + "Payload": "", + "RestoredPayload": "" + } + ], + "EvaluationTimeOnSeconds": 10, + "MinimumSecondsBetweenFailureNotifications": 60 } } diff --git a/test/ServicesTests/Application.FunctionalTests/Application.FunctionalTests.csproj b/test/ServicesTests/Application.FunctionalTests/Application.FunctionalTests.csproj index fd3b506c7..f8fc3efaf 100644 --- a/test/ServicesTests/Application.FunctionalTests/Application.FunctionalTests.csproj +++ b/test/ServicesTests/Application.FunctionalTests/Application.FunctionalTests.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp2.2 true false false @@ -68,7 +68,7 @@ - + diff --git a/tests-results/basket-test-results.xml b/tests-results/basket-test-results.xml new file mode 100644 index 000000000..933ed1b5b --- /dev/null +++ b/tests-results/basket-test-results.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [xUnit.net 00:00:06.1434181] Discovering: Basket.UnitTests[xUnit.net 00:00:06.4201662] Discovered: Basket.UnitTests[xUnit.net 00:00:06.4258049] Starting: Basket.UnitTests[xUnit.net 00:00:09.1721258] Finished: Basket.UnitTests + + + \ No newline at end of file diff --git a/tests-results/basket-unit-test-results.xml b/tests-results/basket-unit-test-results.xml new file mode 100644 index 000000000..c7a6cd004 --- /dev/null +++ b/tests-results/basket-unit-test-results.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [xUnit.net 00:00:06.7811016] Discovering: Basket.UnitTests[xUnit.net 00:00:07.1809374] Discovered: Basket.UnitTests[xUnit.net 00:00:07.1870723] Starting: Basket.UnitTests[xUnit.net 00:00:10.4183620] Finished: Basket.UnitTests + + + \ No newline at end of file diff --git a/tests-results/catalog-test-results.xml b/tests-results/catalog-test-results.xml new file mode 100644 index 000000000..73efaeb8f --- /dev/null +++ b/tests-results/catalog-test-results.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [xUnit.net 00:00:06.0723374] Discovering: Catalog.FunctionalTests[xUnit.net 00:00:06.4361995] Discovered: Catalog.FunctionalTests[xUnit.net 00:00:06.4447894] Starting: Catalog.FunctionalTests[xUnit.net 00:00:34.5834045] Finished: Catalog.FunctionalTests + + + \ No newline at end of file diff --git a/tests-results/catalog-unit-test-results.xml b/tests-results/catalog-unit-test-results.xml new file mode 100644 index 000000000..2bc65d5c9 --- /dev/null +++ b/tests-results/catalog-unit-test-results.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [xUnit.net 00:00:06.6354381] Discovering: Catalog.FunctionalTests[xUnit.net 00:00:07.0010904] Discovered: Catalog.FunctionalTests[xUnit.net 00:00:07.0749761] Starting: Catalog.FunctionalTests[xUnit.net 00:00:34.9063753] Finished: Catalog.FunctionalTests + + + \ No newline at end of file diff --git a/tests-results/locations-test-results.xml b/tests-results/locations-test-results.xml new file mode 100644 index 000000000..c8b5e6b82 --- /dev/null +++ b/tests-results/locations-test-results.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [xUnit.net 00:00:07.7665792] Discovering: Locations.FunctionalTests[xUnit.net 00:00:08.1972548] Discovered: Locations.FunctionalTests[xUnit.net 00:00:08.2137816] Starting: Locations.FunctionalTests[xUnit.net 00:00:27.6429580] Finished: Locations.FunctionalTests + + + \ No newline at end of file diff --git a/tests-results/marketing-test-results.xml b/tests-results/marketing-test-results.xml new file mode 100644 index 000000000..5545febcc --- /dev/null +++ b/tests-results/marketing-test-results.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [xUnit.net 00:00:01.0963036] Discovering: Marketing.FunctionalTests[xUnit.net 00:00:01.1452772] Discovered: Marketing.FunctionalTests[xUnit.net 00:00:01.1494340] Starting: Marketing.FunctionalTests[xUnit.net 00:00:06.9663675] Finished: Marketing.FunctionalTests + + + \ No newline at end of file diff --git a/tests-results/ordering-test-results.xml b/tests-results/ordering-test-results.xml new file mode 100644 index 000000000..231975d56 --- /dev/null +++ b/tests-results/ordering-test-results.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [xUnit.net 00:00:05.2039980] Discovering: Ordering.FunctionalTests[xUnit.net 00:00:05.4802527] Discovered: Ordering.FunctionalTests[xUnit.net 00:00:05.5249888] Starting: Ordering.FunctionalTests[xUnit.net 00:00:20.1396674] Finished: Ordering.FunctionalTests + + + \ No newline at end of file diff --git a/tests-results/ordering-unit-test-results.xml b/tests-results/ordering-unit-test-results.xml new file mode 100644 index 000000000..c9f85a60f --- /dev/null +++ b/tests-results/ordering-unit-test-results.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [xUnit.net 00:00:05.4257955] Discovering: Ordering.FunctionalTests[xUnit.net 00:00:05.9560169] Discovered: Ordering.FunctionalTests[xUnit.net 00:00:05.9845906] Starting: Ordering.FunctionalTests[xUnit.net 00:00:21.2769127] Finished: Ordering.FunctionalTests + + + \ No newline at end of file