diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 78dfac3a7..1b69a04c8 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:6.0 +FROM mcr.microsoft.com/dotnet/sdk:8.0 RUN apt-get update diff --git a/.github/ISSUE_TEMPLATE/agenda-template.md b/.github/ISSUE_TEMPLATE/agenda-template.md index 41a9f8646..01114ccae 100644 --- a/.github/ISSUE_TEMPLATE/agenda-template.md +++ b/.github/ISSUE_TEMPLATE/agenda-template.md @@ -8,7 +8,7 @@ assignees: '' --- ## Date -YYYY-MM-DD - 8am UTC +YYYY-MM-DD - 7am UTC ## Meeting notices @@ -18,7 +18,7 @@ Linux Foundation meetings involve participation by industry competitors, and it Examples of types of actions that are prohibited at Linux Foundation meetings and in connection with Linux Foundation activities are described in the Linux Foundation Antitrust Policy available at http://www.linuxfoundation.org/antitrust-policy. If you have questions about these matters, please contact your company counsel, or if you are a member of the Linux Foundation, feel free to contact Andrew Updegrove of the firm of Gesmer Updegrove LLP, which provides legal counsel to the Linux Foundation. ### Recordings -GSF project meetings may be recorded for use solely by the GSF team for administration purposes. In very limited instances, and with explicit approval, recordings may be made more widely available. +We're happy to start recording these meetings if we receive any requests to record them. ### Roll Call Please *add a comment* to this issue during the meeting to denote attendance. diff --git a/.github/ISSUE_TEMPLATE/case-study-template.md b/.github/ISSUE_TEMPLATE/case-study-template.md new file mode 100644 index 000000000..976c16d94 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/case-study-template.md @@ -0,0 +1,62 @@ +--- +name: Case-study template +about: Case-study submissions +title: Case-study submissions +labels: Case-study submissions +assignees: vaughanknight + +--- + +# Case Study Template + +*This is a template to use for case studies that are submitted that leverage the Carbon Aware SDK. These case studies will be published to the Carbon Aware SDK repository* + +> We will contact the person who created this issue to follow up prior to publishing any case studies for clarification and alignment + +*Please delete the text in italics and replace it with the appropriate information.* + +*For more information on any of the items, the final reference is the [SCI Specification](https://github.com/Green-Software-Foundation/software_carbon_intensity/blob/main/Software_Carbon_Intensity/Software_Carbon_Intensity_Specification.md)* + +*If you find errors, or have further questions, please feel free to raise an [issue](https://github.com/Green-Software-Foundation/carbon-aware-sdk/issues).* + +## Overview + +_Please provide information describing the use case in a few bullet points_ + +## Describe the solution that implements the Carbon Aware SDK + +_A textual description of the solution that implements the Carbon Aware SDK_ + +_A textual description of the action that is taken that reduces emissiosn due to leveraging the Carbon Aware SDK_ + +## Architecture for the solution (if applicable) + +_An architecture diagram of the solution described in this use case_ + +### Technical details of the components in the architecture + +_Textual description with technical details of each component provided in the architecture diagram_ + +## Carbon Aware SDK impact (both emissions impact and business impact) + +_Textual description of the emissions impact and how it was measured_ + +_Textual description of the business impact beyond the emissions impact (if applicable)_ + +## Any public press release information that relates to the + +_Titles and links to press releases that can be viewed publicly online_ + +## Any other notes of significance + +_For example_ + +_Was the SCI used?_ + +_Was this built in conjuction with another party that should be included in the case study?_ + +_Was there a contribution back to the Carbon Aware SDK project as part of this?_ + +_Are there any further plans to implement the Carbon Aware SDK across other solutions that can be discussed?_ + +_Are there any further plans to leverage other aspects of the Green Software Foundation to drive positive impact with software?_ diff --git a/.github/workflows/1-pr.yaml b/.github/workflows/1-pr.yaml index c6d8f9f44..b38b914d0 100644 --- a/.github/workflows/1-pr.yaml +++ b/.github/workflows/1-pr.yaml @@ -8,7 +8,7 @@ env: # web app DOCKERFILE_PATH: "CarbonAware.WebApi/src/Dockerfile" HEALTH_ENDPOINT: "0.0.0.0:8080/health" - DLL_FILE_PATH: "./bin/Release/net6.0/CarbonAware.WebApi.dll" + DLL_FILE_PATH: "./bin/Release/net8.0/CarbonAware.WebApi.dll" DOTNET_SRC_DIR: "./src" # console app packages DOTNET_SOLUTION: "src/GSF.CarbonAware/src/GSF.CarbonAware.csproj" @@ -40,9 +40,9 @@ jobs: - uses: actions/checkout@v2 - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v3 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL @@ -86,14 +86,14 @@ jobs: needs: sln-build-and-test runs-on: ubuntu-latest container: - image: mcr.microsoft.com/dotnet/sdk:6.0 + image: mcr.microsoft.com/dotnet/sdk:8.0 steps: - uses: actions/checkout@v3 - - name: Setup .NET Core SDK 6 - uses: actions/setup-dotnet@v2 + - name: Setup .NET Core SDK 8 + uses: actions/setup-dotnet@v3 with: - dotnet-version: '6.0.x' + dotnet-version: '8.0.x' include-prerelease: false - name: Install dependencies @@ -123,6 +123,8 @@ jobs: - name: Generate Open API run: dotnet tool run swagger tofile --output ./wwwroot/api/v1/swagger.yaml --yaml ${{ env.DLL_FILE_PATH }} v1 + env: + DOTNET_ROLL_FORWARD: LatestMajor working-directory: ./src/CarbonAware.WebApi/src - name: Upload swagger artifact @@ -144,7 +146,7 @@ jobs: - name: Docker Run Container run: | - docker run -d --name runnable-container -p 8080:80 ca-api + docker run -d --name runnable-container -p 8080:8080 ca-api docker container ls - name: Docker WGET Health Endpoint @@ -164,10 +166,10 @@ jobs: uses: actions/checkout@v3 with: ref: dev - - name: Setup .NET Core SDK 6 - uses: actions/setup-dotnet@v2 + - name: Setup .NET Core SDK 8 + uses: actions/setup-dotnet@v3 with: - dotnet-version: '6.0.x' + dotnet-version: '8.0.x' include-prerelease: false - name: Install dependencies run: dotnet restore @@ -179,6 +181,8 @@ jobs: working-directory: ${{ env.DOTNET_SRC_DIR }} - name: Generate Open API run: dotnet tool run swagger tofile --output ./wwwroot/api/v1/swagger.yaml --yaml ${{ env.DLL_FILE_PATH }} v1 + env: + DOTNET_ROLL_FORWARD: LatestMajor - name: Upload dev artifact uses: actions/upload-artifact@v1 with: @@ -199,10 +203,10 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - name: Setup .NET Core SDK 6 - uses: actions/setup-dotnet@v2 + - name: Setup .NET Core SDK 8 + uses: actions/setup-dotnet@v3 with: - dotnet-version: '6.0.x' + dotnet-version: '8.0.x' include-prerelease: false - name: Create packages @@ -244,4 +248,4 @@ jobs: command: config globs: | ./custom.markdownlint.jsonc - {"*[^.github]/**,*"}.md \ No newline at end of file + {"*[^.github]/**,*"}.md diff --git a/.github/workflows/2-pre-release.yaml b/.github/workflows/2-pre-release.yaml index ddd6afe72..816cc7e66 100644 --- a/.github/workflows/2-pre-release.yaml +++ b/.github/workflows/2-pre-release.yaml @@ -23,8 +23,6 @@ jobs: permissions: packages: write steps: - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Log in to the Container registry diff --git a/.github/workflows/4-release.yaml b/.github/workflows/4-release.yaml index b4eaa34d1..54fcc9deb 100644 --- a/.github/workflows/4-release.yaml +++ b/.github/workflows/4-release.yaml @@ -14,8 +14,6 @@ jobs: permissions: packages: write steps: - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Log in to the Container registry diff --git a/.github/workflows/dev_carbon-aware-api.yml b/.github/workflows/dev_carbon-aware-api.yml index 643d58b8d..f1c4dc4ae 100644 --- a/.github/workflows/dev_carbon-aware-api.yml +++ b/.github/workflows/dev_carbon-aware-api.yml @@ -20,7 +20,7 @@ jobs: - name: Set up .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: '6.0.x' + dotnet-version: '8.0.x' include-prerelease: true - name: Build with dotnet diff --git a/.vscode/launch.json b/.vscode/launch.json index 7df71697f..8921c133f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "buildCLI", - "program": "${workspaceFolder}/src/CarbonAware.CLI/src/bin/Debug/net6.0/caw.dll", + "program": "${workspaceFolder}/src/CarbonAware.CLI/src/bin/Debug/net8.0/caw.dll", "args": [ "emissions", "--location", "${input:caw_location}" @@ -27,7 +27,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "buildWebApi", - "program": "${workspaceFolder}/src/CarbonAware.WebApi/src/bin/Debug/net6.0/CarbonAware.WebApi.dll", + "program": "${workspaceFolder}/src/CarbonAware.WebApi/src/bin/Debug/net8.0/CarbonAware.WebApi.dll", "args": [], "cwd": "${workspaceFolder}/src/CarbonAware.WebApi/src/", "stopAtEntry": false, diff --git a/CHANGELOG.md b/CHANGELOG.md index a3863d0c3..d41501578 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,50 @@ All notable changes to the Carbon Aware SDK will be documented in this file. + +## [1.4.0] - 2024-05 + +### Added + +-[#401] [Feature Contribution]: Upgrade .NET version to .NET 8 ](https://github.com/Green-Software-Foundation/carbon-aware-sdk/issues/401) +-[#419] [Feature Contribution]: Migrate sample implementation of Azure Function to isolated worker model ](https://github.com/Green-Software-Foundation/carbon-aware-sdk/issues/419) +-[PR #500] Up Helm chart version to 1.2.0 ](https://github.com/Green-Software-Foundation/carbon-aware-sdk/pull/500) + +-[#397] [Feature Contribution]: Data caching in the SDK ](https://github.com/Green-Software-Foundation/carbon-aware-sdk/issues/397) + +### Fixed + +-[#505] [Bug]: Project Page wiki from GSF website still says it's in incubation ](https://github.com/Green-Software-Foundation/carbon-aware-sdk/issues/505) +-[#496] [URGENT] WebAPI container has not built due to segmentation fault ](https://github.com/Green-Software-Foundation/carbon-aware-sdk/issues/496) +-[#487] [Bug]: Getting started guide is lost ](https://github.com/Green-Software-Foundation/carbon-aware-sdk/issues/487) + + +### Changed + +-[#477] [Bug]: Ensure the readme file shows as the project overview content on the documentation site ](https://github.com/Green-Software-Foundation/carbon-aware-sdk/issues/477) +-[PR #485] Docs overview, disclaimer & pipeline updates for graduation ](https://github.com/Green-Software-Foundation/carbon-aware-sdk/pull/485) + +#### API + +- + +#### API Deployment + +- + +#### SDK + +- + + +#### Other + +- + + +For more details, checkout [https://github.com/Green-Software-Foundation/carbon-aware-sdk/issues/503](https://github.com/Green-Software-Foundation/carbon-aware-sdk/issues/503) + + ## [1.3.0] - 2024-02 ### Added diff --git a/README.md b/README.md index ec7765990..5f8372fd1 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ By knowing the carbon emissions of the energy that powers your applications, you * Running software updates at greener energy time windows * Using data to run hypothetical models to understand how you could start driving impact and reduce emissions, drive business cases for change, and create a greener future. + Within the [Green Software Foundations Theory of Change](https://greensoftware.foundation/articles/theory-of-change), we look at 3 pillars, that being **Knowledge**, **Tech Culture**, and **Tooling** as focus areas to drive this change. The Carbon Aware SDK at it's core sits firmly in the **Tooling** pillar, and also supports the other pillars, providing **Knowledge** through emissions data to inform change, and being core enabler for the **Tech Culture** for building carbon aware software. Companies including UBS and Vestas have already deployed the Carbon Aware SDK to build greener software, and you can too! @@ -102,7 +103,7 @@ centralised management, auditability and traceability, and more. The Carbon Aware SDK is a collaborative effort between companies around the world, with the intention of providing a platform that everyone can use. This means the API will be striving towards what solves the highest impact issues -with diverse perspectives from these organisation and contributors. +with diverse perspectives from these organisations and contributors. ### Standardization @@ -151,7 +152,7 @@ capability. ### Aggregated Sources -A feature we have in the roadmap is the ability aggregate data sources across +A feature we have in the roadmap is the ability to aggregate data sources across multiple providers. Different data providers have different levels of granularity depending on region, and it may be that data provider A is preferred in Japan, while data provider B is preferred in US regions. diff --git a/SECURITY.md b/SECURITY.md index 468aa5c50..bba41e31f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -75,11 +75,11 @@ We perform regular reviews inline with the information provided below. All rele ### Use basic good cryptographic practices - https://www.bestpractices.dev/en/criteria/0#0.crypto_published - ✅ uses HTTPS for WebAPI, N/A for CLI -- https://www.bestpractices.dev/en/criteria/0#0.crypto_floss - ✅ uses dotnet 6.0 implementations -- https://www.bestpractices.dev/en/criteria/0#0.crypto_keylength - ✅ uses dotnet 6.0 implementations -- https://www.bestpractices.dev/en/criteria/0#0.crypto_working - ✅ uses dotnet 6.0 implementations -- https://www.bestpractices.dev/en/criteria/0#0.crypto_password_storage - ✅ ⚠️ uses dotnet 6.0 implementations -- https://www.bestpractices.dev/en/criteria/0#0.crypto_random - ✅ uses dotnet 6.0 implementatons for HTTPS +- https://www.bestpractices.dev/en/criteria/0#0.crypto_floss - ✅ uses dotnet 8.0 implementations +- https://www.bestpractices.dev/en/criteria/0#0.crypto_keylength - ✅ uses dotnet 8.0 implementations +- https://www.bestpractices.dev/en/criteria/0#0.crypto_working - ✅ uses dotnet 8.0 implementations +- https://www.bestpractices.dev/en/criteria/0#0.crypto_password_storage - ✅ ⚠️ uses dotnet 8.0 implementations +- https://www.bestpractices.dev/en/criteria/0#0.crypto_random - ✅ uses dotnet 8.0 implementatons for HTTPS ### Secured delivery against man-in-the-middle (MITM) attacks - Delivery mechanisms that counters MITM - ✅ uses HTTPS diff --git a/casdk-docs/docs/overview/adopters.md b/casdk-docs/docs/overview/adopters.md index 8411426c5..c1ffbf8a5 100644 --- a/casdk-docs/docs/overview/adopters.md +++ b/casdk-docs/docs/overview/adopters.md @@ -4,7 +4,9 @@ sidebar_position: 7 # Carbon Aware SDK adopters -We're sharing adopters of the Carbon Aware SDK with public evidence, although we know many others are also using the Carbon Aware SDK to reduce the carbon footprint their software. + +We're sharing adopters of the Carbon Aware SDK with public evidence, although we know many others are also using the Carbon Aware SDK to reduce the carbon footprint of their software. + If you're using the Carbon Aware SDK and can share evidence, we'd love to add you to this list. Please reach out to carbon-aware-sdk@greensoftware.foundation or send a pull request. diff --git a/casdk-docs/docs/overview/contributing.md b/casdk-docs/docs/overview/contributing.md index 23b602d7a..afae220fa 100644 --- a/casdk-docs/docs/overview/contributing.md +++ b/casdk-docs/docs/overview/contributing.md @@ -22,13 +22,14 @@ We have opportunities for both code and non code contributors. We're currently l | Contribution Areas | Description | |----------|----------| -|**Sample Creation** | These help adopters of the SDK learn how they can quick get started and build their own carbon aware solutions.| +|**Sample Creation** | These help adopters of the SDK learn how they can quickly get started and build their own carbon aware solutions.| |**Documentation Updates** | The documentation always can be improved to make the Carbon Aware SDK more accessible to everyone. Guides, SDK and API document, and more! | -|**Video Content Creation (how to enable, demos etc)** | Quick videos help adopters undersatnd just how easy it is to get started in an easy to consume form. +|**Video Content Creation (how to enable, demos etc)** | Quick videos help adopters understand just how easy it is to get started in an easy to consume form. |**Slide Deck Creation
Available for presenter use, including real time video demo**| We get a lot of traction at conferences, and if we have a standard deck for anyone to present, it will enable those who might not be able to create a deck, but could easily present it, to also participate. ## How To Get Started -Introduce yourself on on our [discussions page](https://github.com/orgs/Green-Software-Foundation/discussions/65) and let us know where you think you can help. +Introduce yourself on our [discussions page](https://github.com/orgs/Green-Software-Foundation/discussions/65) and let us know where you think you can help. + Find the Project Key contacts in the [Confluence page](https://greensoftwarefoundation.atlassian.net/wiki/spaces/~612dd45e45cd76006a84071a/pages/17137665/Opensource+Carbon+Aware+SDK). If you are a GSF member organisation employee, you should: diff --git a/casdk-docs/docs/overview/enablement.md b/casdk-docs/docs/overview/enablement.md index 2028bfdbe..969eb9c99 100644 --- a/casdk-docs/docs/overview/enablement.md +++ b/casdk-docs/docs/overview/enablement.md @@ -15,7 +15,7 @@ sidebar_position: 3 2. [How to Use Carbon Aware SDK](#2-how-to-use-carbon-aware-sdk) - 2.1 [Pre-requisities](#21-pre-requisities) + 2.1 [Pre-requisites](#21-pre-requisites) * Data sources * System requirement @@ -44,16 +44,16 @@ different endpoints to provide the most flexibility to integrate to your environment: * CLI -You can run the application using the [CLI](../src/CarbonAware.CLI) and refer +You can run the application using the [CLI](/src/CarbonAware.CLI) and refer to more documentation [here](../tutorial-basics/carbon-aware-cli.md). * WebAPI -You can build a container containing the [WebAPI](../src/CarbonAware.WebApi) +You can build a container containing the [WebAPI](/src/CarbonAware.WebApi) and connect via REST requests and refer to more documentation [here](../tutorial-basics/carbon-aware-webapi.md). * SDK -You can reference the [Carbon Aware C# Library](../src/GSF.CarbonAware) in your +You can reference the [Carbon Aware C# Library](/src/GSF.CarbonAware) in your projects and make use of its functionalities and features. | ![Image 2](./images/readme/screenshot_cli.png) | ![Image 1](./images/readme/screenshot_web_api.png) | @@ -67,7 +67,7 @@ we show some examples of the [use case](./adopters.md). ## 2. How to use Carbon Aware SDK? -### 2.1 Pre-requisities +### 2.1 Pre-requisites #### Data sources @@ -87,7 +87,7 @@ providers into the carbon aware SDK. #### System requirement * Command Line Interface (CLI) - * .NET Core 6.0 + * .NET 8.0 * Alternatively: * Docker * VSCode and its [Remote Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) @@ -588,7 +588,7 @@ using environment variables, you'd do this: #### Local project settings For local-only settings you can use environment variables, -[the Secret Manager tool](https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-6.0&tabs=windows#secret-manager) +[the Secret Manager tool](https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?tabs=windows#secret-manager) , or an untracked Development appsettings file to override the default project settings. @@ -598,7 +598,7 @@ remove the first line of (invalid) comments. Then update any settings according to your preferences. > Wherever possible, the projects leverage the -> [default .NET configuration](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-6.0#default-application-configuration-sources) +> [default .NET configuration](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?#default-application-configuration-sources) > expectations. Thus, they can be configured using any file matching the format: > `appsettings..json`. Where `` is the value of the > `ASPNETCORE_ENVIRONMENT` environment variable. By convention projects tend to diff --git a/casdk-docs/docs/overview/overview.md b/casdk-docs/docs/overview/overview.md index ec7765990..cd628235f 100644 --- a/casdk-docs/docs/overview/overview.md +++ b/casdk-docs/docs/overview/overview.md @@ -17,13 +17,13 @@ By knowing the carbon emissions of the energy that powers your applications, you * Running software updates at greener energy time windows * Using data to run hypothetical models to understand how you could start driving impact and reduce emissions, drive business cases for change, and create a greener future. -Within the [Green Software Foundations Theory of Change](https://greensoftware.foundation/articles/theory-of-change), we look at 3 pillars, that being **Knowledge**, **Tech Culture**, and **Tooling** as focus areas to drive this change. The Carbon Aware SDK at it's core sits firmly in the **Tooling** pillar, and also supports the other pillars, providing **Knowledge** through emissions data to inform change, and being core enabler for the **Tech Culture** for building carbon aware software. +Within the [Green Software Foundations Theory of Change](https://greensoftware.foundation/articles/theory-of-change), we look at 3 pillars, that being **Knowledge**, **Tech Culture**, and **Tooling** as focus areas to drive this change. The Carbon Aware SDK at its core sits firmly in the **Tooling** pillar, and also supports the other pillars, providing **Knowledge** through emissions data to inform change, and being core enabler for the **Tech Culture** for building carbon aware software. Companies including UBS and Vestas have already deployed the Carbon Aware SDK to build greener software, and you can too! # Getting Started Overview -Head on over to the [Getting Started Overview Guide](./casdk-docs/docs/overview/overview.md) to get up and running. +Head on over to the [quickstart guide](../quickstart.md) to get up and running. Get started on creating sustainable software innovation for a greener future today! @@ -83,7 +83,7 @@ deployment in the greenest location. The Carbon Aware SDK is being used by large and small companies around the world. Some of the world’s biggest enterprises and software companies, through -to start-ups. Both UBS and Vestas have used the SDK, with further details over on the [adopters overview](./casdk-docs/docs/overview/adopters.md). +to start-ups. Both UBS and Vestas have used the SDK, with further details over on the [adopters overview](./adopters.md). Machine Learning (ML) workloads are a great example of long running compute intensive workloads, that often are also not time critical. By moving these workloads to a different time, the carbon emissions from the ML training can be reduced by up to 15%, and by moving the location of the training this can be @@ -102,7 +102,7 @@ centralised management, auditability and traceability, and more. The Carbon Aware SDK is a collaborative effort between companies around the world, with the intention of providing a platform that everyone can use. This means the API will be striving towards what solves the highest impact issues -with diverse perspectives from these organisation and contributors. +with diverse perspectives from these organisations and contributors. ### Standardization @@ -151,7 +151,7 @@ capability. ### Aggregated Sources -A feature we have in the roadmap is the ability aggregate data sources across +A feature we have in the roadmap is the ability to aggregate data sources across multiple providers. Different data providers have different levels of granularity depending on region, and it may be that data provider A is preferred in Japan, while data provider B is preferred in US regions. @@ -170,7 +170,7 @@ percentage information only at the moment. ## Contributing The Carbon Aware SDK is open for contribution! Want to contribute? Check out the -[contribution guide](./CONTRIBUTING.md). +[contribution guide](./contributing.md). ## Green Software Foundation Project Summary diff --git a/casdk-docs/docs/quickstart.md b/casdk-docs/docs/quickstart.md index ab86b4d97..a500520f5 100644 --- a/casdk-docs/docs/quickstart.md +++ b/casdk-docs/docs/quickstart.md @@ -18,7 +18,7 @@ generated libraries for your language of choice! Prerequisites: -- .NET Core 6.0 +- .NET Core 8.0 - Alternatively: - Docker - VSCode (it is recommended to work in a Dev Container) @@ -404,7 +404,7 @@ installation and example usage. 3. Install the Python client library using [`setuptools`](http://pypi.python.org/pypi/setuptools)): `python setup.py install --user` -4. The library is now succesfully installed! +4. The library is now successfully installed! There should be an example script in the `README` file, but this guide suggests trying the following example first: diff --git a/casdk-docs/docs/tutorial-basics/carbon-aware-webapi.md b/casdk-docs/docs/tutorial-basics/carbon-aware-webapi.md index f971f0714..028db6273 100644 --- a/casdk-docs/docs/tutorial-basics/carbon-aware-webapi.md +++ b/casdk-docs/docs/tutorial-basics/carbon-aware-webapi.md @@ -456,10 +456,10 @@ CarbonAware.LocationSources.LocationSource: Warning: New key swedencentral_1 gen ## Error Handling The WebAPI leveraged the -[.Net controller filter pipeline](https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-6.0) +[.Net controller filter pipeline](https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-8.0) to ensure that all requests respond with a consistent JSON schema. -![.Net controller filter pipeline image](https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters/_static/filter-pipeline-2.png?view=aspnetcore-6.0) +![.Net controller filter pipeline image](https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters/_static/filter-pipeline-2.png?view=aspnetcore-8.0) Controllers are responsible for managing the "Success" responses. If an error occurs in the WebAPI code and an unhandled exception is thrown, the @@ -470,7 +470,7 @@ caught and handled by the WebAPI code, the controller will continue to manage the response. The .Net framework will automatically respond to validation errors with a -[ValidationProblemDetails](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.validationproblemdetails?view=aspnetcore-6.0) +[ValidationProblemDetails](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.validationproblemdetails?view=aspnetcore-8.0) object. Using the Exception Filter class enables the WebAPI to consistently respond with the `ValidationProblemDetails` error schema in all error cases and take advantage of error handling automatically provided by the framework. @@ -489,7 +489,7 @@ specification cd CarbonAware.WebApi/src dotnet tool restore dotnet build --configuration Release --no-restore - dotnet tool run swagger tofile --output ./wwwroot/api/v1/swagger.yaml --yaml bin/Release/net6.0/CarbonAware.WebApi.dll v1 + dotnet tool run swagger tofile --output ./wwwroot/api/v1/swagger.yaml --yaml bin/Release/net8.0/CarbonAware.WebApi.dll v1 ``` 1. The `CarbonAware.WebApi/src/wwwroot/api/v1/swagger.yaml` file contains the supported OpenApi specification. diff --git a/casdk-docs/docs/tutorial-basics/containerization.md b/casdk-docs/docs/tutorial-basics/containerization.md index 355a9a9b0..8f8211e05 100644 --- a/casdk-docs/docs/tutorial-basics/containerization.md +++ b/casdk-docs/docs/tutorial-basics/containerization.md @@ -25,11 +25,11 @@ carbon_aware v1 6293e2528bf2 About an hour ago 230MB ## Run WebApi Image 1. Run the image using `docker run` with host port 8000 mapped to the WebApi - port 80 and configure environment variable settings for + port 8080 and configure environment variable settings for [WattTime](https://www.watttime.org) provider. ```sh - docker run --rm -p 8000:80 \ + docker run --rm -p 8000:8080 \ > -e DataSources__EmissionsDataSource="WattTime" \ > -e DataSources__ForecastDataSource="WattTime" \ > -e DataSources__Configurations__WattTime__Type="WattTime" \ @@ -40,7 +40,7 @@ carbon_aware v1 6293e2528bf2 About an hour ago 230MB or the [ElectricityMaps](https://www.electricitymaps.com) provider ```sh - docker run --rm -p 8000:80 \ + docker run --rm -p 8000:8080 \ > -e DataSources__EmissionsDataSource="ElectricityMaps" \ > -e DataSources__ForecastDataSource="ElectricityMaps" \ > -e DataSources__Configurations__ElectricityMaps__Type="ElectricityMaps" \ @@ -52,7 +52,7 @@ carbon_aware v1 6293e2528bf2 About an hour ago 230MB or the [ElectricityMapsFree](https://www.co2signal.com/) provider ```sh - docker run --rm -p 8000:80 \ + docker run --rm -p 8000:8080 \ > -e DataSources__EmissionsDataSource="ElectricityMapsFree" \ > -e DataSources__Configurations__ElectricityMapsFree__Type="ElectricityMapsFree" \ > -e DataSources__Configurations__ElectricityMapsFree__token="" \ diff --git a/casdk-docs/docs/tutorial-extras/configuration.md b/casdk-docs/docs/tutorial-extras/configuration.md index 7453b8e82..c65aa44f2 100644 --- a/casdk-docs/docs/tutorial-extras/configuration.md +++ b/casdk-docs/docs/tutorial-extras/configuration.md @@ -19,9 +19,11 @@ - [ElectricityMapsFree Configuration](#electricitymapsfree-configuration) - [API Token](#api-token) - [BaseUrl](#baseurl) + - [Cache](#cache) - [CarbonAwareVars](#carbonawarevars) - [Tracing and Monitoring Configuration](#tracing-and-monitoring-configuration) - [Verbosity](#verbosity) + - [Prometheus exporter](#prometheus-exporter-for-emissions-data) - [Web API Prefix](#web-api-prefix) - [LocationDataSourcesConfiguration](#locationdatasourcesconfiguration) - [Sample Configurations](#sample-configurations) @@ -194,7 +196,7 @@ custom `EmissionsData` sets. The file should be located under the `/src/data/data-sources/` directory that is part of the repository. At build time, all the JSON files under `/src/data/data-sources/` are copied over the destination directory -`/src/CarbonAware.WebApi/src/bin/[Debug|Publish]/net6.0/data-sources/json` +`/src/CarbonAware.WebApi/src/bin/[Debug|Publish]/net8.0/data-sources/json` that is part of the `CarbonAware.WebApi` assembly. Also the file can be placed where the assembly `CarbonAware.WebApi.dll` is located under `data-sources/json` directory. For instance, if the application is installed under `/app`, copy the @@ -327,6 +329,29 @@ The url to use when connecting to ElectricityMapsFree. Defaults to "https://api.co2signal.com/v1/" but can be overridden in the config if needed (such as to enable integration testing scenarios). +## Cache + +Frequent access to data sources could cause problems such as performance trouble +or exceed rate limit. To avoid them, you can configure data cache like this: + +```json +{ + "EmissionsDataCache": { + "Enabled": true, + "ExpirationMin": 30 + } +} +``` + +The behavior of current cache implementation: +* Only emissions data are cached + * Forecast data are not stored +* The result of the latest query to data sources is cached +* Use cache rather than data sources if even one datum in cache match with the query + * Even though more data in data sources would be matched, they are not retrieved +* Cached data are stored in memory + * They are cleard when the process of the SDK is down + ## CarbonAwareVars This section contains the global settings for the SDK. The configuration looks @@ -379,6 +404,32 @@ InstrumentationKey. For more details, please refer to AppInsights_InstrumentationKey="AppInsightsInstrumentationKey" ``` +### Prometheus exporter for emissions data + +> DISCLAIMER: The `/metrics` Prometheus exporter is currently unsupported, and is used for internal GSF needs, and may change in the future. It will retrieve _all_ emissions data and create heavy load on your data API's. It is turned off by default. + +In the WebApi project, this application can exporse latest carbon emissions data as a prometheus exporter. + +```bash +CarbonAwareVars__EnableCarbonExporter="true" +``` +The scraping endpoint is `/metrics` like this: + +```bash +http://localhost/metrics +``` + +By default, the exposed data are latest ones within last 24 hours. If you would like to change the period +in some reasones, you can configure the value like this: + +```json +{ + "CarbonExporter": { + "PeriodInHours": 48 + } +} +``` + ### Verbosity You can configure the verbosity of the application error messages by setting the @@ -427,7 +478,7 @@ By setting `LocationDataSourcesConfiguration` property with one or more location data sources, it is possible to load different `Location` data sets in order to have more than one location. For instance by setting two location regions, the property would be set as follow using -[environment](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-6.0#naming-of-environment-variables) +[environment](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-8.0#naming-of-environment-variables) variables: ```sh @@ -458,7 +509,7 @@ curl "http://${IP_HOST}:${PORT}/emissions/bylocations/best?location=${REGION}&ti At build time, all the JSON files under `/src/data/location-sources` are copied over the destination directory -`/src/CarbonAware.WebApi/src/bin/[Debug|Publish]/net6.0/location-sources/json` +`/src/CarbonAware.WebApi/src/bin/[Debug|Publish]/net8.0/location-sources/json` that is part of the `CarbonAware.WebApi` assembly. Also the file can be placed where the assembly `CarbonAware.WebApi.dll` is located under `location-sources/json` directory. For instance, if the application is installed diff --git a/casdk-docs/docs/tutorial-extras/packaging.md b/casdk-docs/docs/tutorial-extras/packaging.md index ac8dac6d7..7aef80801 100644 --- a/casdk-docs/docs/tutorial-extras/packaging.md +++ b/casdk-docs/docs/tutorial-extras/packaging.md @@ -63,7 +63,7 @@ project. When running in the dev container you will need: - [Remote Containers extension for VSCode](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) Alternatively you can run in your local environment using the -[.NET Core 6.0 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/6.0). +[.NET Core 8.0 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0). ## SDK Configuration diff --git a/global.json b/global.json index 1c64019b5..ad8ad01d1 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "6.0.418", + "version": "8.0.201", "rollForward": "latestFeature" } } \ No newline at end of file diff --git a/helm-chart/Chart.yaml b/helm-chart/Chart.yaml index fadfb8f70..5570e9904 100644 --- a/helm-chart/Chart.yaml +++ b/helm-chart/Chart.yaml @@ -15,10 +15,10 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.1.0 +version: 1.2.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "v1.1.0" +appVersion: "v1.4.0" diff --git a/helm-chart/templates/deployment.yaml b/helm-chart/templates/deployment.yaml index cec413296..5a21b6682 100644 --- a/helm-chart/templates/deployment.yaml +++ b/helm-chart/templates/deployment.yaml @@ -39,7 +39,7 @@ spec: {{- end }} ports: - name: http - containerPort: 80 + containerPort: 8080 protocol: TCP volumeMounts: - name: appsettings diff --git a/helm-chart/values.yaml b/helm-chart/values.yaml index 452ef6a21..1f5cdaf9e 100644 --- a/helm-chart/values.yaml +++ b/helm-chart/values.yaml @@ -37,7 +37,7 @@ securityContext: {} service: type: ClusterIP - port: 80 + port: 8080 ingress: enabled: false diff --git a/samples/azure/azure-function/Dockerfile b/samples/azure/azure-function/Dockerfile index 53316cbf0..cc5a6d5f1 100644 --- a/samples/azure/azure-function/Dockerfile +++ b/samples/azure/azure-function/Dockerfile @@ -1,10 +1,10 @@ # Find the Dockerfile at this URL # https://github.com/Azure/azure-functions-docker/blob/dev/host/4/bullseye/amd64/dotnet/dotnet-inproc/dotnet.Dockerfile -FROM mcr.microsoft.com/azure-functions/dotnet:4.0 AS base +FROM mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0 AS base WORKDIR /home/site/wwwroot -FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build COPY ["src/", "data/src/"] COPY ["scripts/", "data/scripts/"] COPY ["samples/", "data/samples/"] @@ -21,3 +21,4 @@ RUN dotnet publish "samples/azure/azure-function/function.csproj" -c Release -o FROM base AS final WORKDIR /home/site/wwwroot COPY --from=publish /app/publish . +ENV ASPNETCORE_CONTENTROOT=/home/site/wwwroot diff --git a/samples/azure/azure-function/GetCarbonIntensity.cs b/samples/azure/azure-function/GetCarbonIntensity.cs index c1e2a441e..7ca8db605 100644 --- a/samples/azure/azure-function/GetCarbonIntensity.cs +++ b/samples/azure/azure-function/GetCarbonIntensity.cs @@ -1,7 +1,7 @@ using GSF.CarbonAware.Handlers; using Microsoft.AspNetCore.Mvc; -using Microsoft.Azure.WebJobs; -using Microsoft.Azure.WebJobs.Extensions.Http; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using System; @@ -14,17 +14,18 @@ namespace function public class GetCarbonIntensity { private readonly IEmissionsHandler _handler; + private readonly ILogger _log; - public GetCarbonIntensity(IEmissionsHandler handler) + public GetCarbonIntensity(IEmissionsHandler handler, ILogger log) { this._handler = handler; + this._log = log; } - [FunctionName("GetAverageCarbonIntensity")] + [Function("GetAverageCarbonIntensity")] public async Task Run( - [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, - ILogger log) + [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req) { //Get the startDate, endDate, and location from the request query if the values are present in the query string startDate = req.Query["startdate"]; @@ -47,7 +48,7 @@ public async Task Run( try { var result = await _handler.GetAverageCarbonIntensityAsync(location, DateTimeOffset.Parse(startDate), DateTimeOffset.Parse(endDate)); - log.LogInformation($"For location {location} Starting at: {startDate} Ending at: {endDate} the Average Emissions Rating is: {result}."); + _log.LogInformation($"For location {location} Starting at: {startDate} Ending at: {endDate} the Average Emissions Rating is: {result}."); return new OkObjectResult(result); } diff --git a/samples/azure/azure-function/GetForecast.cs b/samples/azure/azure-function/GetForecast.cs index 564a0138e..eb3536620 100644 --- a/samples/azure/azure-function/GetForecast.cs +++ b/samples/azure/azure-function/GetForecast.cs @@ -1,7 +1,7 @@ using GSF.CarbonAware.Handlers; using Microsoft.AspNetCore.Mvc; -using Microsoft.Azure.WebJobs; -using Microsoft.Azure.WebJobs.Extensions.Http; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using System; @@ -15,16 +15,17 @@ namespace CarbonAwareFunctions public class GetForecast { private readonly IForecastHandler _handler; + private readonly ILogger _log; - public GetForecast(IForecastHandler handler) + public GetForecast(IForecastHandler handler, ILogger log) { this._handler = handler; + this._log = log; } - [FunctionName("GetCurrentForecast")] + [Function("GetCurrentForecast")] public async Task Run( - [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, - ILogger log) + [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req) { //Get the startDate, endDate, location, and duration from the request query if the values are present in the query string startDate = req.Query["startdate"]; diff --git a/samples/azure/azure-function/Program.cs b/samples/azure/azure-function/Program.cs new file mode 100644 index 000000000..0f64de4d5 --- /dev/null +++ b/samples/azure/azure-function/Program.cs @@ -0,0 +1,24 @@ +using GSF.CarbonAware.Configuration; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Configuration; +using System.IO; + +var host = new HostBuilder() + .ConfigureFunctionsWebApplication() + .ConfigureAppConfiguration((context, builder) => { + var env = context.HostingEnvironment; + builder.AddJsonFile(Path.Combine(env.ContentRootPath, "appsettings.json"), optional: true, reloadOnChange: false) + .AddJsonFile(Path.Combine(env.ContentRootPath, $"appsettings.{env.EnvironmentName}.json"), optional: true, reloadOnChange: false) + .AddEnvironmentVariables(); + }) + .ConfigureServices((context,services) => { + services.AddApplicationInsightsTelemetryWorkerService(); + services.ConfigureFunctionsApplicationInsights(); + services.AddEmissionsServices(context.Configuration); + services.AddForecastServices(context.Configuration); + }) + .Build(); + +host.Run(); \ No newline at end of file diff --git a/samples/azure/azure-function/README.md b/samples/azure/azure-function/README.md index 0021630de..641919de4 100644 --- a/samples/azure/azure-function/README.md +++ b/samples/azure/azure-function/README.md @@ -19,29 +19,20 @@ will use. The Carbon Aware SDK is included in the function .csproj file by [creating and adding the SDK as a package](../../docs/packaging.md#included-scripts). -The [Startup.cs](./Startup.cs) file uses dependency injection to access the +The [Program.cs](./Program.cs) file uses dependency injection to access the handlers in the library. The following code initializes the C# Library: ```C# - public override void Configure(IFunctionsHostBuilder builder) - { - var configuration = builder.GetContext().Configuration; - builder.Services - .AddEmissionsServices(configuration) - .AddForecastServices(configuration); - } + // omitted + .ConfigureServices((context,services) => { + services.AddApplicationInsightsTelemetryWorkerService(); + services.ConfigureFunctionsApplicationInsights(); + services.AddEmissionsServices(context.Configuration); + services.AddForecastServices(context.Configuration); + }) + // omitted ``` -> Note as the in-process -> [Azure Function uses dependency injection](https://learn.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection) -> though via -> [Microsoft.Azure.Functions.Extensions](https://www.nuget.org/packages/Microsoft.Azure.Functions.Extensions/) -> there is a version conflict of -> [Microsoft.Extensions.Configuration](https://www.nuget.org/packages/Microsoft.Extensions.Configuration). -> It is fixed adding a version specific project dependency (in .csproj) to the -> same version as the Carbon Aware SDK. Microsoft.Extensions.Configuration is -> backwards compatible. - ## Run Function Locally Both Azure Function apps can be diff --git a/samples/azure/azure-function/Startup.cs b/samples/azure/azure-function/Startup.cs deleted file mode 100644 index 1660065ba..000000000 --- a/samples/azure/azure-function/Startup.cs +++ /dev/null @@ -1,31 +0,0 @@ -using GSF.CarbonAware.Configuration; -using Microsoft.Azure.Functions.Extensions.DependencyInjection; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using System.IO; - -[assembly: FunctionsStartup(typeof(CarbonAwareFunctions.Startup))] - -namespace CarbonAwareFunctions -{ - public class Startup : FunctionsStartup - { - public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder) - { - FunctionsHostBuilderContext context = builder.GetContext(); - - builder.ConfigurationBuilder - .AddJsonFile(Path.Combine(context.ApplicationRootPath, "appsettings.json"), optional: true, reloadOnChange: false) - .AddJsonFile(Path.Combine(context.ApplicationRootPath, $"appsettings.{context.EnvironmentName}.json"), optional: true, reloadOnChange: false) - .AddEnvironmentVariables(); - } - - public override void Configure(IFunctionsHostBuilder builder) - { - var configuration = builder.GetContext().Configuration; - builder.Services - .AddEmissionsServices(configuration) - .AddForecastServices(configuration); - } - } -} \ No newline at end of file diff --git a/samples/azure/azure-function/function.csproj b/samples/azure/azure-function/function.csproj index e48592f82..4dd9facd2 100644 --- a/samples/azure/azure-function/function.csproj +++ b/samples/azure/azure-function/function.csproj @@ -1,9 +1,16 @@ - net6.0 + net8.0 v4 + Exe + + + + + + @@ -11,8 +18,6 @@ - - @@ -26,4 +31,7 @@ PreserveNewest + + + diff --git a/samples/lib-integration/ConsoleApp/ConsoleApp.csproj b/samples/lib-integration/ConsoleApp/ConsoleApp.csproj index c688178ea..ffd7e1267 100644 --- a/samples/lib-integration/ConsoleApp/ConsoleApp.csproj +++ b/samples/lib-integration/ConsoleApp/ConsoleApp.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 enable enable diff --git a/src/CarbonAware.CLI/src/CarbonAware.CLI.csproj b/src/CarbonAware.CLI/src/CarbonAware.CLI.csproj index fcc1f9bfb..f097ae0c9 100644 --- a/src/CarbonAware.CLI/src/CarbonAware.CLI.csproj +++ b/src/CarbonAware.CLI/src/CarbonAware.CLI.csproj @@ -4,7 +4,7 @@ caw win-x64;osx-x64;linux-x64 Exe - net6.0 + net8.0 enable enable 34d82203-20b1-4fcd-9bd4-3b247f13bad7 diff --git a/src/CarbonAware.CLI/src/Dockerfile b/src/CarbonAware.CLI/src/Dockerfile index 94c08a68e..8bd94af86 100644 --- a/src/CarbonAware.CLI/src/Dockerfile +++ b/src/CarbonAware.CLI/src/Dockerfile @@ -1,5 +1,5 @@ -# Set the base image as the .NET 6.0 SDK (this includes the runtime) -FROM mcr.microsoft.com/dotnet/sdk:6.0 as build-env +# Set the base image as the .NET 8.0 SDK (this includes the runtime) +FROM mcr.microsoft.com/dotnet/sdk:8.0 as build-env # Copy everything and publish the release (publish implicitly restores and builds) COPY ./src/ ./ @@ -27,7 +27,7 @@ LABEL com.github.actions.icon="sliders" LABEL com.github.actions.color="purple" # Relayer the .NET SDK, anew with the build output -FROM mcr.microsoft.com/dotnet/runtime:6.0 +FROM mcr.microsoft.com/dotnet/runtime:8.0 COPY --from=build-env /out . RUN apt-get update && apt-get install jq -y diff --git a/src/CarbonAware.CLI/test/integrationTests/CarbonAware.CLI.IntegrationTests.csproj b/src/CarbonAware.CLI/test/integrationTests/CarbonAware.CLI.IntegrationTests.csproj index 9b0dd0efe..a79c9cdf0 100644 --- a/src/CarbonAware.CLI/test/integrationTests/CarbonAware.CLI.IntegrationTests.csproj +++ b/src/CarbonAware.CLI/test/integrationTests/CarbonAware.CLI.IntegrationTests.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 enable false enable diff --git a/src/CarbonAware.CLI/test/unitTests/CarbonAware.CLI.UnitTests.csproj b/src/CarbonAware.CLI/test/unitTests/CarbonAware.CLI.UnitTests.csproj index a97048508..8c56dd8ba 100644 --- a/src/CarbonAware.CLI/test/unitTests/CarbonAware.CLI.UnitTests.csproj +++ b/src/CarbonAware.CLI/test/unitTests/CarbonAware.CLI.UnitTests.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 enable false enable diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMaps/mock/CarbonAware.DataSources.ElectricityMaps.Mocks.csproj b/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMaps/mock/CarbonAware.DataSources.ElectricityMaps.Mocks.csproj index ac0346757..10277e544 100644 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMaps/mock/CarbonAware.DataSources.ElectricityMaps.Mocks.csproj +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMaps/mock/CarbonAware.DataSources.ElectricityMaps.Mocks.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 enable enable false diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMaps/src/CarbonAware.DataSources.ElectricityMaps.csproj b/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMaps/src/CarbonAware.DataSources.ElectricityMaps.csproj index d9dab5e3e..1d3c1ad32 100644 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMaps/src/CarbonAware.DataSources.ElectricityMaps.csproj +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMaps/src/CarbonAware.DataSources.ElectricityMaps.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 enable enable true diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMaps/test/CarbonAware.DataSources.ElectricityMaps.Tests.csproj b/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMaps/test/CarbonAware.DataSources.ElectricityMaps.Tests.csproj index 34e343371..b726b4773 100644 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMaps/test/CarbonAware.DataSources.ElectricityMaps.Tests.csproj +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMaps/test/CarbonAware.DataSources.ElectricityMaps.Tests.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 enable enable false diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMapsFree/mock/CarbonAware.DataSources.ElectricityMapsFree.Mocks.csproj b/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMapsFree/mock/CarbonAware.DataSources.ElectricityMapsFree.Mocks.csproj index 60a945672..a8878c6ae 100644 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMapsFree/mock/CarbonAware.DataSources.ElectricityMapsFree.Mocks.csproj +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMapsFree/mock/CarbonAware.DataSources.ElectricityMapsFree.Mocks.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 enable enable diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMapsFree/src/CarbonAware.DataSources.ElectricityMapsFree.csproj b/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMapsFree/src/CarbonAware.DataSources.ElectricityMapsFree.csproj index 8db8bc27b..310a6ed47 100644 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMapsFree/src/CarbonAware.DataSources.ElectricityMapsFree.csproj +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMapsFree/src/CarbonAware.DataSources.ElectricityMapsFree.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 enable enable diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMapsFree/test/CarbonAware.DataSources.ElectricityMapsFree.Tests.csproj b/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMapsFree/test/CarbonAware.DataSources.ElectricityMapsFree.Tests.csproj index 742632c0e..0375049af 100644 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMapsFree/test/CarbonAware.DataSources.ElectricityMapsFree.Tests.csproj +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMapsFree/test/CarbonAware.DataSources.ElectricityMapsFree.Tests.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 enable false diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.Json/mock/CarbonAware.DataSources.Json.Mocks.csproj b/src/CarbonAware.DataSources/CarbonAware.DataSources.Json/mock/CarbonAware.DataSources.Json.Mocks.csproj index a6f5e7724..898d7f162 100644 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.Json/mock/CarbonAware.DataSources.Json.Mocks.csproj +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.Json/mock/CarbonAware.DataSources.Json.Mocks.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 enable enable false diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.Json/src/CarbonAware.DataSources.Json.csproj b/src/CarbonAware.DataSources/CarbonAware.DataSources.Json/src/CarbonAware.DataSources.Json.csproj index 7c9905d41..7bba6e61d 100644 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.Json/src/CarbonAware.DataSources.Json.csproj +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.Json/src/CarbonAware.DataSources.Json.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 enable enable true diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.Json/test/CarbonAware.DataSources.Json.Tests.csproj b/src/CarbonAware.DataSources/CarbonAware.DataSources.Json/test/CarbonAware.DataSources.Json.Tests.csproj index 1addeb0dd..065ebeee5 100644 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.Json/test/CarbonAware.DataSources.Json.Tests.csproj +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.Json/test/CarbonAware.DataSources.Json.Tests.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 enable false diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.Registration/CarbonAware.DataSources.Registration.csproj b/src/CarbonAware.DataSources/CarbonAware.DataSources.Registration/CarbonAware.DataSources.Registration.csproj index d66d5e8c7..40ecc3ffe 100644 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.Registration/CarbonAware.DataSources.Registration.csproj +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.Registration/CarbonAware.DataSources.Registration.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 enable enable true diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.Registration/Configuration/ServiceCollectionExtensions.cs b/src/CarbonAware.DataSources/CarbonAware.DataSources.Registration/Configuration/ServiceCollectionExtensions.cs index 872ec4bae..7ceb5b012 100644 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.Registration/Configuration/ServiceCollectionExtensions.cs +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.Registration/Configuration/ServiceCollectionExtensions.cs @@ -5,6 +5,7 @@ using CarbonAware.DataSources.Json.Configuration; using CarbonAware.DataSources.WattTime.Configuration; using CarbonAware.Exceptions; +using CarbonAware.Proxies.Cache; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -48,6 +49,8 @@ public static IServiceCollection AddDataSourceService(this IServiceCollection se } } + services.SetupCacheForEmissionsDataSource(configuration); + switch (forecastDataSource) { case DataSourceType.JSON: @@ -96,4 +99,29 @@ private static DataSourceType GetDataSourceTypeFromValue(string? srcVal) } return result; } + + private static IServiceCollection SetupCacheForEmissionsDataSource(this IServiceCollection services, IConfiguration configuration) + { + var emissionsDataCache = configuration.EmissionsDataCache(); + if(emissionsDataCache.Enabled){ + var emissionsDataSourceDescriptor = services.SingleOrDefault(s => s.ServiceType == typeof(IEmissionsDataSource)); + var type = emissionsDataSourceDescriptor!.ImplementationType; + if(type == null) return services; + services.Replace + ( + ServiceDescriptor.Describe + ( + typeof(IEmissionsDataSource), + serviceProvider => + LatestEmissionsCache.CreateProxy + ( + (IEmissionsDataSource)ActivatorUtilities.GetServiceOrCreateInstance(serviceProvider, type!), + emissionsDataCache + )!, + emissionsDataSourceDescriptor.Lifetime + ) + ); + } + return services; + } } diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/mock/CarbonAware.DataSources.WattTime.Mocks.csproj b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/mock/CarbonAware.DataSources.WattTime.Mocks.csproj index 1adda21a9..061c0cbab 100644 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/mock/CarbonAware.DataSources.WattTime.Mocks.csproj +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/mock/CarbonAware.DataSources.WattTime.Mocks.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 enable enable false diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/CarbonAware.DataSources.WattTime.csproj b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/CarbonAware.DataSources.WattTime.csproj index 20f6d7230..996b31ae4 100644 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/CarbonAware.DataSources.WattTime.csproj +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/CarbonAware.DataSources.WattTime.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 enable enable true diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/CarbonAware.DataSources.WattTime.Tests.csproj b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/CarbonAware.DataSources.WattTime.Tests.csproj index 295fb782c..77f142861 100644 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/CarbonAware.DataSources.WattTime.Tests.csproj +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/CarbonAware.DataSources.WattTime.Tests.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 enable false diff --git a/src/CarbonAware.LocationSources/src/CarbonAware.LocationSources.csproj b/src/CarbonAware.LocationSources/src/CarbonAware.LocationSources.csproj index d05da871e..50211d65d 100644 --- a/src/CarbonAware.LocationSources/src/CarbonAware.LocationSources.csproj +++ b/src/CarbonAware.LocationSources/src/CarbonAware.LocationSources.csproj @@ -5,7 +5,7 @@ - net6.0 + net8.0 enable enable true diff --git a/src/CarbonAware.LocationSources/test/CarbonAware.LocationSources.Test.csproj b/src/CarbonAware.LocationSources/test/CarbonAware.LocationSources.Test.csproj index 498ef5124..28dc668ef 100644 --- a/src/CarbonAware.LocationSources/test/CarbonAware.LocationSources.Test.csproj +++ b/src/CarbonAware.LocationSources/test/CarbonAware.LocationSources.Test.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 false enable diff --git a/src/CarbonAware.Tools/CarbonAware.Tools.AWSRegionTestDataGenerator/CarbonAware.Tools.AWSRegionTestDataGenerator.csproj b/src/CarbonAware.Tools/CarbonAware.Tools.AWSRegionTestDataGenerator/CarbonAware.Tools.AWSRegionTestDataGenerator.csproj index 203c50ed1..b5757b3b9 100644 --- a/src/CarbonAware.Tools/CarbonAware.Tools.AWSRegionTestDataGenerator/CarbonAware.Tools.AWSRegionTestDataGenerator.csproj +++ b/src/CarbonAware.Tools/CarbonAware.Tools.AWSRegionTestDataGenerator/CarbonAware.Tools.AWSRegionTestDataGenerator.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 enable enable false diff --git a/src/CarbonAware.Tools/CarbonAware.Tools.AzureRegionTestDataGenerator/CarbonAware.Tools.AzureRegionTestDataGenerator.csproj b/src/CarbonAware.Tools/CarbonAware.Tools.AzureRegionTestDataGenerator/CarbonAware.Tools.AzureRegionTestDataGenerator.csproj index dc24429ba..ddfd8938d 100644 --- a/src/CarbonAware.Tools/CarbonAware.Tools.AzureRegionTestDataGenerator/CarbonAware.Tools.AzureRegionTestDataGenerator.csproj +++ b/src/CarbonAware.Tools/CarbonAware.Tools.AzureRegionTestDataGenerator/CarbonAware.Tools.AzureRegionTestDataGenerator.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 enable enable false diff --git a/src/CarbonAware.WebApi/src/.config/dotnet-tools.json b/src/CarbonAware.WebApi/src/.config/dotnet-tools.json index 1a1607fe5..9a07f6919 100644 --- a/src/CarbonAware.WebApi/src/.config/dotnet-tools.json +++ b/src/CarbonAware.WebApi/src/.config/dotnet-tools.json @@ -3,10 +3,10 @@ "isRoot": true, "tools": { "swashbuckle.aspnetcore.cli": { - "version": "6.2.3", + "version": "6.5.0", "commands": [ "swagger" ] } } -} \ No newline at end of file +} diff --git a/src/CarbonAware.WebApi/src/CarbonAware.WebApi.csproj b/src/CarbonAware.WebApi/src/CarbonAware.WebApi.csproj index 356e23088..de8904092 100644 --- a/src/CarbonAware.WebApi/src/CarbonAware.WebApi.csproj +++ b/src/CarbonAware.WebApi/src/CarbonAware.WebApi.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 enable enable 8d822819-8a1f-45e4-95fb-d4a9c3a9439f @@ -15,13 +15,15 @@ + + - - - - - + + + + + @@ -45,4 +47,4 @@ - \ No newline at end of file + diff --git a/src/CarbonAware.WebApi/src/Configuration/CarbonExporterConfiguration.cs b/src/CarbonAware.WebApi/src/Configuration/CarbonExporterConfiguration.cs new file mode 100644 index 000000000..35f8a3bc1 --- /dev/null +++ b/src/CarbonAware.WebApi/src/Configuration/CarbonExporterConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Configuration; + +namespace CarbonAware.WebApi.Configuration; + + +internal class CarbonExporterConfiguration +{ + public const string Key = "CarbonExporter"; + + public int PeriodInHours { get; set; } = 24; + + public void AssertValid() + { + if(PeriodInHours <= 0) + { + throw new ArgumentException($"The value of CarbonExporter.PeriodInHours must be greater than 0."); + } + } +} \ No newline at end of file diff --git a/src/CarbonAware.WebApi/src/Configuration/ServiceCollectionExtensions.cs b/src/CarbonAware.WebApi/src/Configuration/ServiceCollectionExtensions.cs index 48820a8bb..0c1458b23 100644 --- a/src/CarbonAware.WebApi/src/Configuration/ServiceCollectionExtensions.cs +++ b/src/CarbonAware.WebApi/src/Configuration/ServiceCollectionExtensions.cs @@ -1,4 +1,6 @@ using Microsoft.Extensions.Options; +using CarbonAware.WebApi.Metrics; +using OpenTelemetry.Metrics; namespace CarbonAware.WebApi.Configuration; @@ -30,6 +32,34 @@ public static void AddMonitoringAndTelemetry(this IServiceCollection services, I // Can be extended in the future to support a different provider like Zipkin, Prometheus etc } + + } + + public static IServiceCollection AddCarbonExporter(this IServiceCollection services, IConfiguration configuration) + { + var envVars = configuration?.GetSection(CarbonAwareVariablesConfiguration.Key).Get(); + var enableCarbonExporter = envVars?.EnableCarbonExporter ?? false; + if(enableCarbonExporter){ + var carbonExporter = configuration?.GetSection(CarbonExporterConfiguration.Key); + services.Configure(c => + { + carbonExporter?.Bind(c); + }); + + services.AddOpenTelemetry() + .WithMetrics(meterProviderBuilder => + meterProviderBuilder + .ConfigureServices(services => + { + services.AddSingleton(); + services.AddSingleton(); + }) + .ConfigureResource(rb => rb.AddDetector(sp => sp.GetRequiredService())) + .AddMeter(CarbonMetrics.MeterName) + .AddPrometheusExporter() + ); + } + return services; } private static bool IsAppInsightsConfigured(IConfiguration? configuration, ILogger logger) diff --git a/src/CarbonAware.WebApi/src/Dockerfile b/src/CarbonAware.WebApi/src/Dockerfile index bc46714fe..068357d4a 100644 --- a/src/CarbonAware.WebApi/src/Dockerfile +++ b/src/CarbonAware.WebApi/src/Dockerfile @@ -1,22 +1,33 @@ -FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env +# For OpenAPI document +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS openapi-env WORKDIR /app +ENV DOTNET_ROLL_FORWARD LatestMajor +COPY . ./ +RUN dotnet build CarbonAware.WebApi/src/CarbonAware.WebApi.csproj -o build +WORKDIR /app/CarbonAware.WebApi/src +RUN dotnet tool restore && \ + dotnet tool run swagger tofile --output /app/build/swagger.yaml --yaml /app/build/CarbonAware.WebApi.dll v1 + +# Builder +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env +ARG TARGETARCH +WORKDIR /app +ENV DOTNET_ROLL_FORWARD LatestMajor # Copy everything from source COPY . ./ # Use implicit restore to build and publish -RUN dotnet publish CarbonAware.WebApi/src/CarbonAware.WebApi.csproj -c Release -o publish -# Generate OpenAPI spec -WORKDIR /app/CarbonAware.WebApi/src -RUN dotnet tool restore && \ - mkdir -p /app/publish/wwwroot/api/v1 && \ - dotnet tool run swagger tofile --output /app/publish/wwwroot/api/v1/swagger.yaml --yaml /app/publish/CarbonAware.WebApi.dll v1 +RUN dotnet publish CarbonAware.WebApi/src/CarbonAware.WebApi.csproj -a $TARGETARCH -o publish + # Build runtime image -FROM mcr.microsoft.com/dotnet/aspnet:6.0 +FROM mcr.microsoft.com/dotnet/aspnet:8.0 # Install curl for health check RUN apt-get update && \ apt-get install -y --no-install-recommends curl -# Copy artifacts from build-env +# Copy artifacts WORKDIR /app COPY --from=build-env /app/publish . +RUN mkdir -p /app/wwwroot/api/v1 +COPY --from=openapi-env /app/build/swagger.yaml /app/wwwroot/api/v1/ ENTRYPOINT ["dotnet", "CarbonAware.WebApi.dll"] diff --git a/src/CarbonAware.WebApi/src/Metrics/CarbonMetrics.cs b/src/CarbonAware.WebApi/src/Metrics/CarbonMetrics.cs new file mode 100644 index 000000000..7efc244a1 --- /dev/null +++ b/src/CarbonAware.WebApi/src/Metrics/CarbonMetrics.cs @@ -0,0 +1,93 @@ +using CarbonAware.WebApi.Configuration; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Diagnostics.Metrics; +using Microsoft.Extensions.Options; +using GSF.CarbonAware.Handlers; +using GSF.CarbonAware.Models; + +namespace CarbonAware.WebApi.Metrics; + +internal class CarbonMetrics : IDisposable +{ + private readonly ILogger _logger; + + private readonly IOptionsMonitor _configurationMonitor; + private CarbonExporterConfiguration _configuration => this._configurationMonitor.CurrentValue; + internal const string MeterName = "Carbon.Aware.Metric"; + internal const string ActivitySourceName = "Carbon.Aware.Metric"; + internal const string GaugeName = "carbon.aware.intensity"; + + public ActivitySource ActivitySource { get; } + + private readonly IEmissionsHandler _emissionsHandler; + private readonly ILocationHandler _locationHandler; + + private readonly Meter _meter; + + private readonly ConcurrentBag _locations = new ConcurrentBag(); + private readonly IDictionary> _gauges = new ConcurrentDictionary>(); + + public CarbonMetrics(IMeterFactory meterFactory, IOptionsMonitor monitor, ILogger logger, IEmissionsHandler emissionsHandler, ILocationHandler locationHandler) + { + _emissionsHandler = emissionsHandler ?? throw new ArgumentNullException(nameof(emissionsHandler)); + _locationHandler = locationHandler ?? throw new ArgumentNullException(nameof(locationHandler)); + _configurationMonitor = monitor; + _logger = logger; + _configuration.AssertValid(); + + string? version = typeof(CarbonMetrics).Assembly.GetName().Version?.ToString(); + ActivitySource = new ActivitySource(ActivitySourceName, version); + _meter = meterFactory.Create(MeterName, version); + InitLocations(); + } + + private void InitLocations() + { + // initialize locations and guages + _locations.Clear(); + _gauges.Clear(); + + // load locations + Task> locationsTask = _locationHandler.GetLocationsAsync(); + try + { + locationsTask.Result.Keys.ToList().ForEach(d => _locations.Add(d)); + // create guages for each locaton + foreach(var loc in _locations){ + _gauges[loc] = _meter.CreateObservableGauge(CarbonMetrics.GaugeName, () => GetIntensity(loc)); + } + } + catch(Exception ex) + { + _logger.LogWarning(ex.Message); + _locations.Clear(); + _gauges.Clear(); + } + } + + public void Dispose() + { + _meter.Dispose(); + ActivitySource.Dispose(); + } + + private Measurement GetIntensity(string location){ + try + { + var end = DateTimeOffset.UtcNow; + var start = end.AddHours(-1*_configuration.PeriodInHours); + var intensity = _emissionsHandler.GetEmissionsDataAsync(location, start, end) + .Result + .MaxBy(d => d.Time)! + .Rating; + var measurement = new Measurement(intensity, new TagList(){{"location", location}}); + return measurement; + } + catch(Exception ex) + { + _logger.LogWarning(ex.Message); + return new Measurement(0, new TagList(){{"location", location}}); + } + } +} \ No newline at end of file diff --git a/src/CarbonAware.WebApi/src/Metrics/MetricsResourceDetector.cs b/src/CarbonAware.WebApi/src/Metrics/MetricsResourceDetector.cs new file mode 100644 index 000000000..0f8398c09 --- /dev/null +++ b/src/CarbonAware.WebApi/src/Metrics/MetricsResourceDetector.cs @@ -0,0 +1,17 @@ +using CarbonAware.WebApi.Metrics; +using OpenTelemetry.Resources; + +internal class MetricsResourceDetector : IResourceDetector +{ + private readonly CarbonMetrics _carbonMetrics; + + public MetricsResourceDetector(CarbonMetrics carbonMetrics) + { + _carbonMetrics = carbonMetrics; + } + + public Resource Detect() + { + return ResourceBuilder.CreateEmpty().Build(); + } +} \ No newline at end of file diff --git a/src/CarbonAware.WebApi/src/Program.cs b/src/CarbonAware.WebApi/src/Program.cs index 9a70c15ab..b027ad12f 100644 --- a/src/CarbonAware.WebApi/src/Program.cs +++ b/src/CarbonAware.WebApi/src/Program.cs @@ -7,6 +7,7 @@ using Microsoft.OpenApi.Models; using OpenTelemetry.Resources; using OpenTelemetry.Trace; +using OpenTelemetry.Metrics; using Swashbuckle.AspNetCore.SwaggerGen; using System.Reflection; @@ -16,17 +17,16 @@ var builder = WebApplication.CreateBuilder(args); -builder.Services.AddOpenTelemetryTracing(tracerProviderBuilder => -{ - tracerProviderBuilder +builder.Services.AddOpenTelemetry() + .WithTracing(tracerProviderBuilder => + tracerProviderBuilder .AddConsoleExporter() .AddSource(serviceName) .SetResourceBuilder( ResourceBuilder.CreateDefault() .AddService(serviceName: serviceName, serviceVersion: serviceVersion)) .AddHttpClientInstrumentation() - .AddAspNetCoreInstrumentation(); -}); + .AddAspNetCoreInstrumentation()); // Add services to the container. builder.Services.AddControllers(options => @@ -70,6 +70,8 @@ builder.Services.AddMonitoringAndTelemetry(builder.Configuration); +builder.Services.AddCarbonExporter(builder.Configuration); + builder.Services.AddSwaggerGen(c => { c.MapType(() => new OpenApiSchema { Type = "string", Format = "time-span" }); }); @@ -112,9 +114,14 @@ app.MapHealthChecks("/health"); +var enableCarbonExporter = config?.EnableCarbonExporter ?? false; +if(enableCarbonExporter){ + app.UseOpenTelemetryPrometheusScrapingEndpoint(); +} + app.Run(); -// Please view https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-6.0#basic-tests-with-the-default-webapplicationfactory +// Please view https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-8.0#basic-tests-with-the-default-webapplicationfactory // This line is needed to allow for Integration Testing public partial class Program { } diff --git a/src/CarbonAware.WebApi/test/integrationTests/CarbonAware.WebApi.IntegrationTests.csproj b/src/CarbonAware.WebApi/test/integrationTests/CarbonAware.WebApi.IntegrationTests.csproj index df1ed5ada..fd9179089 100644 --- a/src/CarbonAware.WebApi/test/integrationTests/CarbonAware.WebApi.IntegrationTests.csproj +++ b/src/CarbonAware.WebApi/test/integrationTests/CarbonAware.WebApi.IntegrationTests.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 enable false enable diff --git a/src/CarbonAware.WebApi/test/integrationTests/CarbonAwareControllerTests.cs b/src/CarbonAware.WebApi/test/integrationTests/CarbonAwareControllerTests.cs index d4954bbd6..975d00d04 100644 --- a/src/CarbonAware.WebApi/test/integrationTests/CarbonAwareControllerTests.cs +++ b/src/CarbonAware.WebApi/test/integrationTests/CarbonAwareControllerTests.cs @@ -20,6 +20,7 @@ class CarbonAwareControllerTests : IntegrationTestingBase { private readonly string healthURI = "/health"; private readonly string fakeURI = "/fake-endpoint"; + private readonly string metricsURI = "/metrics"; private readonly string bestLocationsURI = "/emissions/bylocations/best"; private readonly string bylocationsURI = "/emissions/bylocations"; private readonly string bylocationURI = "/emissions/bylocation"; @@ -47,6 +48,16 @@ public async Task FakeEndPoint_ReturnsNotFound() Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); } + [Test] + public async Task MetricsEndPoint_ReturnsOK() + { + //Use client to get endpoint + var result = await _client.GetAsync(metricsURI); + Assert.That(result, Is.Not.Null); + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.OK)); + } + + //ISO8601: YYYY-MM-DD [TestCase("2022-1-1T04:05:06Z", "2022-1-2T04:05:06Z", "eastus", nameof(ByLocationURI_ReturnsOK) + "0")] [TestCase("2021-12-25T00:00:00+06:00", "2021-12-26T00:00:00+06:00", "westus", nameof(ByLocationURI_ReturnsOK) + "1")] diff --git a/src/CarbonAware.WebApi/test/integrationTests/IntegrationTestingBase.cs b/src/CarbonAware.WebApi/test/integrationTests/IntegrationTestingBase.cs index 5dd7310b5..8d6d1c46c 100644 --- a/src/CarbonAware.WebApi/test/integrationTests/IntegrationTestingBase.cs +++ b/src/CarbonAware.WebApi/test/integrationTests/IntegrationTestingBase.cs @@ -21,6 +21,7 @@ internal abstract class IntegrationTestingBase internal DataSourceType _dataSource; internal string? _emissionsDataSourceEnv; internal string? _forecastDataSourceEnv; + internal string? _enableCarbonExporterEnv; internal WebApplicationFactory _factory; protected HttpClient _client; internal IDataSourceMocker _dataSourceMocker; @@ -33,6 +34,7 @@ public IntegrationTestingBase(DataSourceType dataSource) _dataSource = dataSource; _emissionsDataSourceEnv = null; _forecastDataSourceEnv = null; + _enableCarbonExporterEnv = null; _factory = new WebApplicationFactory(); } @@ -77,6 +79,8 @@ public void Setup() { _emissionsDataSourceEnv = Environment.GetEnvironmentVariable("DataSources__EmissionsDataSource"); _forecastDataSourceEnv = Environment.GetEnvironmentVariable("DataSources__ForecastDataSource"); + _enableCarbonExporterEnv = Environment.GetEnvironmentVariable("CarbonAwareVars__EnableCarbonExporter"); + Environment.SetEnvironmentVariable("CarbonAwareVars__EnableCarbonExporter", "true"); //Switch between different data sources as needed //Each datasource should have an accompanying DataSourceMocker that will perform setup activities switch (_dataSource) @@ -153,5 +157,6 @@ public void TearDown() _dataSourceMocker?.Dispose(); Environment.SetEnvironmentVariable("DataSources__EmissionsDataSource", _emissionsDataSourceEnv); Environment.SetEnvironmentVariable("DataSources__ForecastDataSource", _forecastDataSourceEnv); + Environment.SetEnvironmentVariable("CarbonAwareVars__EnableCarbonMetrics", _enableCarbonExporterEnv); } } \ No newline at end of file diff --git a/src/CarbonAware.WebApi/test/unitTests/CarbonAware.WebApi.UnitTests.csproj b/src/CarbonAware.WebApi/test/unitTests/CarbonAware.WebApi.UnitTests.csproj index a1859042d..f630cd0f8 100644 --- a/src/CarbonAware.WebApi/test/unitTests/CarbonAware.WebApi.UnitTests.csproj +++ b/src/CarbonAware.WebApi/test/unitTests/CarbonAware.WebApi.UnitTests.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 enable false enable diff --git a/src/CarbonAware.WebApi/test/unitTests/Configuration/CarbonExporterConfigurationTests.cs b/src/CarbonAware.WebApi/test/unitTests/Configuration/CarbonExporterConfigurationTests.cs new file mode 100644 index 000000000..af29280f0 --- /dev/null +++ b/src/CarbonAware.WebApi/test/unitTests/Configuration/CarbonExporterConfigurationTests.cs @@ -0,0 +1,30 @@ +using CarbonAware.WebApi.Configuration; +using NUnit.Framework; + +namespace CarbonAware.WepApi.UnitTests; + +class CarbonExporterConfigurationTests +{ + [TestCase(12, TestName = "AssertValid: PeriodInHours greater than 0")] + [TestCase(1, TestName = "AssertValid: PeriodInHours equals to 1")] + public void AssertValid_PeriodInHoursGreaterThanZero_DoesNotThrowException(int periodInHours) + { + CarbonExporterConfiguration carbonExporterConfiguration = new CarbonExporterConfiguration() + { + PeriodInHours = periodInHours + }; + + Assert.DoesNotThrow(() => carbonExporterConfiguration.AssertValid()); + } + + [TestCase(0, TestName = "AssertValid: PeriodInHours equals to 0")] + public void AssertValid_PeriodInHoursGreaterThanZero_ThrowException(int periodInHours) + { + CarbonExporterConfiguration carbonExporterConfiguration = new CarbonExporterConfiguration() + { + PeriodInHours = periodInHours + }; + + Assert.Throws(() => carbonExporterConfiguration.AssertValid()); + } +} \ No newline at end of file diff --git a/src/CarbonAware.WebApi/test/unitTests/Configuration/ServiceCollectionExtensionsTests.cs b/src/CarbonAware.WebApi/test/unitTests/Configuration/ServiceCollectionExtensionsTests.cs index 2e8edd87c..f675160a2 100644 --- a/src/CarbonAware.WebApi/test/unitTests/Configuration/ServiceCollectionExtensionsTests.cs +++ b/src/CarbonAware.WebApi/test/unitTests/Configuration/ServiceCollectionExtensionsTests.cs @@ -82,6 +82,61 @@ public void AddMonitoringAndTelemetry_DoesNotAddServices_WithoutTelemetryProvide Assert.That(services.Count, Is.EqualTo(0)); } + [Test] + public void AddCarbonExporter_AddsServices_IsEnabledInConfiguration() + { + // Arrange + var services = new ServiceCollection(); + + var inMemorySettings = new Dictionary + { + { "CarbonAwareVars:EnableCarbonExporter", "true" } + }; + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(inMemorySettings) + .Build(); + + // Act & Assert + Assert.DoesNotThrow(() => services.AddCarbonExporter(configuration)); + Assert.That(services.Count, Is.GreaterThan(0)); + } + + [Test] + public void AddCarbonExporter_DoesNotAddServices_IsDisabledInConfiguration() + { + // Arrange + var services = new ServiceCollection(); + + var inMemorySettings = new Dictionary + { + { "CarbonAwareVars:EnableCarbonExporter", "false" } + }; + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(inMemorySettings) + .Build(); + + // Act & Assert + Assert.DoesNotThrow(() => services.AddCarbonExporter(configuration)); + Assert.That(services.Count, Is.EqualTo(0)); + } + + + [Test] + public void AddCarbonExporter_DoesNotAddServices_WithoutConfiguration() + { + // Arrange + var services = new ServiceCollection(); + + var inMemorySettings = new Dictionary{}; + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(inMemorySettings) + .Build(); + + // Act & Assert + Assert.DoesNotThrow(() => services.AddCarbonExporter(configuration)); + Assert.That(services.Count, Is.EqualTo(0)); + } + [Test] public void CreateConsoleLogger_ReturnsILogger() { diff --git a/src/CarbonAware/src/CarbonAware.csproj b/src/CarbonAware/src/CarbonAware.csproj index d691e0f80..d04ce3a78 100644 --- a/src/CarbonAware/src/CarbonAware.csproj +++ b/src/CarbonAware/src/CarbonAware.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 enable enable true diff --git a/src/CarbonAware/src/CarbonAwareVariablesConfiguration.cs b/src/CarbonAware/src/CarbonAwareVariablesConfiguration.cs index 13eea1dcb..ae37c78aa 100644 --- a/src/CarbonAware/src/CarbonAwareVariablesConfiguration.cs +++ b/src/CarbonAware/src/CarbonAwareVariablesConfiguration.cs @@ -36,6 +36,8 @@ internal class CarbonAwareVariablesConfiguration public string TelemetryProvider { get; set; } + public Boolean EnableCarbonExporter { get;set; } + public Boolean VerboseApi {get; set;} } diff --git a/src/CarbonAware/src/Configuration/EmissionsDataCacheConfiguration.cs b/src/CarbonAware/src/Configuration/EmissionsDataCacheConfiguration.cs new file mode 100644 index 000000000..9eec7a89b --- /dev/null +++ b/src/CarbonAware/src/Configuration/EmissionsDataCacheConfiguration.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.Configuration; + +namespace CarbonAware.Configuration; + +internal class EmissionsDataCacheConfiguration +{ + public const string Key = "EmissionsDataCache"; + + public bool Enabled { get; set; } = false; + + public int ExpirationMin { get; set; } = 0; + + public void AssertValid() + { + if(Enabled & ExpirationMin <= 0) + { + throw new ArgumentException($"Expiration period for data cache value must be greater than 0."); + } + } +} \ No newline at end of file diff --git a/src/CarbonAware/src/Configuration/EmissionsDataCacheConfigurationExtensions.cs b/src/CarbonAware/src/Configuration/EmissionsDataCacheConfigurationExtensions.cs new file mode 100644 index 000000000..76e1ed345 --- /dev/null +++ b/src/CarbonAware/src/Configuration/EmissionsDataCacheConfigurationExtensions.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.Configuration; + +namespace CarbonAware.Configuration; + +internal static class EmissionsDataCacheConfigurationExtensions +{ + public static EmissionsDataCacheConfiguration EmissionsDataCache(this IConfiguration configuration) + { + var dataCache = configuration.GetSection(EmissionsDataCacheConfiguration.Key).Get() ?? new EmissionsDataCacheConfiguration(); + dataCache.AssertValid(); + + return dataCache; + } +} \ No newline at end of file diff --git a/src/CarbonAware/src/Proxies/Cache/LatestEmissionsCache.cs b/src/CarbonAware/src/Proxies/Cache/LatestEmissionsCache.cs new file mode 100644 index 000000000..116f44db6 --- /dev/null +++ b/src/CarbonAware/src/Proxies/Cache/LatestEmissionsCache.cs @@ -0,0 +1,150 @@ +using CarbonAware.Configuration; +using CarbonAware.Interfaces; +using System.Collections; +using System.Collections.Concurrent; +using System.Reflection; + +namespace CarbonAware.Proxies.Cache; + +/// +/// A proxy class for IEmissionsDataSource to cache EmissionsData. +/// This class caches data which is queried latestly for each Location. +/// The cached value are used if it satisfies all of the conditions listed below. +/// +/// +/// it is not exceeded the expiration period +/// +/// +/// the name of the location is match with the query +/// +/// +/// the time is match with the query +/// +/// +/// +/// The target class of the proxy +class LatestEmissionsCache : DispatchProxy where T : class, IEmissionsDataSource +{ + + private IEmissionsDataSource? _target { get; set; } + + readonly private ConcurrentDictionary)> _cache = + new ConcurrentDictionary)>(); + + private EmissionsDataCacheConfiguration? _config { get; set; } + + public static T? CreateProxy(T target, EmissionsDataCacheConfiguration config) + { + var proxy = Create>() as LatestEmissionsCache; + proxy!._target = target; + proxy._config = config; + return proxy as T; + } + + protected override object? Invoke(MethodInfo? targetMethod, object?[]? args) + { + if(targetMethod!.Name.Equals("GetCarbonIntensityAsync") && args![0]!.GetType() == typeof(Location)) + { + var location = (Location?)args[0]; + var start = (DateTimeOffset)args[1]!; + var end = (DateTimeOffset)args[2]!; + return ProxyGetCarbonIntensityAsync(targetMethod, location!, start, end); + } else if(targetMethod.Name.Equals("GetCarbonIntensityAsync") && args![0]!.GetType().GetInterfaces().Contains(typeof(IEnumerable))) + { + var locations = (IEnumerable?)args[0]; + var start = (DateTimeOffset)args[1]!; + var end = (DateTimeOffset)args[2]!; + return ProxyGetCarbonIntensityAsync(targetMethod, locations!, start, end); + } + return targetMethod.Invoke(_target, args); + } + + private Task> ProxyGetCarbonIntensityAsync(MethodInfo? original, Location location, DateTimeOffset periodStartTime, DateTimeOffset periodEndTime) + { + var cachedData = GetCachedData(location, periodStartTime, periodEndTime); + if(cachedData.Count() != 0) + { + return Task.FromResult(cachedData); + } + + object[] args = {location, periodStartTime, periodEndTime}; + var resultFromOriginal = (Task>?)original!.Invoke(_target, args); + + if(string.IsNullOrEmpty(location.Name)) + { + return resultFromOriginal!; + } else + { + var expiration = DateTimeOffset.UtcNow.AddMinutes(_config!.ExpirationMin); + var result = resultFromOriginal!.ContinueWith + ( + c => + { + _cache.AddOrUpdate(location.Name, (expiration, c.Result), (_, _) => (expiration, c.Result)); + return c.Result; + } + ); + return result; + } + } + + private Task> ProxyGetCarbonIntensityAsync(MethodInfo? original, IEnumerable locations, DateTimeOffset periodStartTime, DateTimeOffset periodEndTime) + { + var cachedData = Enumerable.Empty(); + var useCacheFor = Enumerable.Empty(); + foreach (var location in locations) + { + var cachedDataForLocation = GetCachedData(location, periodStartTime, periodEndTime); + if(cachedDataForLocation.Count() != 0) + { + cachedData = cachedData.Union(cachedDataForLocation); + useCacheFor = useCacheFor.Append(location.Name); + } + } + + var locationsForQuery = locations.Where(l => string.IsNullOrEmpty(l.Name) || !useCacheFor.Contains(l.Name)); + if(locationsForQuery.Count() == 0){ + return Task.FromResult(cachedData); + } + + object[] args = {locationsForQuery, periodStartTime, periodEndTime}; + var resultFromOriginal = (Task>?)original!.Invoke(_target, args); + + var expiration = DateTimeOffset.UtcNow.AddMinutes(_config!.ExpirationMin); + var result = resultFromOriginal!.ContinueWith + ( + c => + { + foreach (var location in locationsForQuery) + { + if(!string.IsNullOrEmpty(location.Name)) + { + var data = c.Result.Where(d => d.Location.Equals(location.Name)); + _cache.AddOrUpdate(location.Name, (expiration, data), (_, _) => (expiration, data)); + } + } + return c.Result.Union(cachedData); + } + ); + return result; + } + + private IEnumerable GetCachedData(Location location, DateTimeOffset periodStartTime, DateTimeOffset periodEndTime) + { + if(!string.IsNullOrEmpty(location.Name) && _cache.ContainsKey(location.Name)) + { + var cachedValue = _cache.GetValueOrDefault(location.Name); + + // check expiration + if(cachedValue.Item1.CompareTo(DateTimeOffset.UtcNow) > 0) + { + IEnumerable emissions = cachedValue.Item2.Where(d => d.TimeBetween(periodStartTime, periodEndTime)); + if(emissions.Count() != 0) + { + return emissions; + } + } + } + return Enumerable.Empty(); + } +} \ No newline at end of file diff --git a/src/CarbonAware/test/CarbonAware.Tests.csproj b/src/CarbonAware/test/CarbonAware.Tests.csproj index c11002e82..39071e6e9 100644 --- a/src/CarbonAware/test/CarbonAware.Tests.csproj +++ b/src/CarbonAware/test/CarbonAware.Tests.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 enable false @@ -13,6 +13,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/CarbonAware/test/Configuration/EmissionsDataCacheConfigurationTests.cs b/src/CarbonAware/test/Configuration/EmissionsDataCacheConfigurationTests.cs new file mode 100644 index 000000000..5480204de --- /dev/null +++ b/src/CarbonAware/test/Configuration/EmissionsDataCacheConfigurationTests.cs @@ -0,0 +1,31 @@ +using CarbonAware.Configuration; + +namespace CarbonAware.Tests.Configuration; + +class EmissionsDataCacheConfigurationTests +{ + [TestCase(10, TestName = "AssertValid: ExpirationMin greater than 0")] + public void AssertValid_ExpirationMinGreaterThanOrEqualsToZero_DoesNotThrowException(int expirationMin) + { + EmissionsDataCacheConfiguration emissionsDataCacheConfig = new EmissionsDataCacheConfiguration() + { + Enabled = true, + ExpirationMin = expirationMin + }; + + Assert.DoesNotThrow(() => emissionsDataCacheConfig.AssertValid()); + } + + [TestCase(0, TestName = "AssertValid: ExpirationMin equals to 0")] + [TestCase(-10, TestName = "AssertValid: ExpirationMin less than 0")] + public void AssertValid_ExpirationMinLessThanZero_ThrowException(int expirationMin) + { + EmissionsDataCacheConfiguration emissionsDataCacheConfig = new EmissionsDataCacheConfiguration() + { + Enabled = true, + ExpirationMin = expirationMin + }; + + Assert.Throws(() => emissionsDataCacheConfig.AssertValid()); + } +} diff --git a/src/CarbonAware/test/Proxy/Cache/LatestEmissionsCacheTests.cs b/src/CarbonAware/test/Proxy/Cache/LatestEmissionsCacheTests.cs new file mode 100644 index 000000000..cd806e91e --- /dev/null +++ b/src/CarbonAware/test/Proxy/Cache/LatestEmissionsCacheTests.cs @@ -0,0 +1,231 @@ +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using CarbonAware.Configuration; +using CarbonAware.Interfaces; +using CarbonAware.Model; +using Moq; + +namespace CarbonAware.Proxies.Cache; + +class LatestEmissionsCacheTests +{ + + Mock? _mock; + + IEmissionsDataSource? _dataSource; + + IDictionary? _dateTimes; + + IDictionary? _emissions; + + IDictionary? _locations; + + [Test] + public void GetCarbonIntensityAsync_ForSingleLocation_UseCachedDataForSameQuery() + { + var config = new EmissionsDataCacheConfiguration(); + config.Enabled = true; + config.ExpirationMin = 10; + + var dataSource = LatestEmissionsCache.CreateProxy(_dataSource!, config); + var resultFromDataSource = dataSource!.GetCarbonIntensityAsync(_locations!["eastus"], _dateTimes!["firstDay"], _dateTimes["secondDay"]).Result; + var resultFromCache = dataSource!.GetCarbonIntensityAsync(_locations!["eastus"], _dateTimes!["firstDay"], _dateTimes["secondDay"]).Result; + + // The results are same + Assert.AreEqual(resultFromDataSource, resultFromCache); + // The access to the data source occurs just once + _mock!.Verify(ds => ds.GetCarbonIntensityAsync(_locations!["eastus"], _dateTimes!["firstDay"], _dateTimes["secondDay"]), Moq.Times.Once); + } + + [Test] + public void GetCarbonIntensityAsync_ForSingleLocation_UsePartOfCachedData() + { + var config = new EmissionsDataCacheConfiguration(); + config.Enabled = true; + config.ExpirationMin = 10; + + var dataSource = LatestEmissionsCache.CreateProxy(_dataSource!, config); + var resultFromDataSource = dataSource!.GetCarbonIntensityAsync(_locations!["eastus"], _dateTimes!["firstDay"], _dateTimes["thirdDay"]).Result; + var resultFromCache = dataSource!.GetCarbonIntensityAsync(_locations!["eastus"], _dateTimes!["firstDay"], _dateTimes["secondDay"]).Result; + + // The result for the second query is a part of the first one + Assert.AreEqual(resultFromDataSource.Count(), 2); + Assert.AreEqual(resultFromCache.Count(), 1); + // The second query does not access to tha data source + _mock!.Verify(ds => ds.GetCarbonIntensityAsync(_locations!["eastus"], _dateTimes!["firstDay"], _dateTimes["secondDay"]), Moq.Times.Never); + } + + [Test] + public void GetCarbonIntensityAsync_ForSingleLocation_AccessToDataSourceIfCachedDataIsNotMached() + { + var config = new EmissionsDataCacheConfiguration(); + config.Enabled = true; + config.ExpirationMin = 10; + + var dataSource = LatestEmissionsCache.CreateProxy(_dataSource!, config); + var resultOfFirst = dataSource!.GetCarbonIntensityAsync(_locations!["eastus"], _dateTimes!["firstDay"], _dateTimes["secondDay"]).Result; + var resultOfSecond = dataSource!.GetCarbonIntensityAsync(_locations!["eastus"], _dateTimes!["secondDay"], _dateTimes["thirdDay"]).Result; + + Assert.Contains(_emissions!["eastus-firstDay"], resultOfFirst.ToList()); + Assert.Contains(_emissions["eastus-secondDay"], resultOfSecond.ToList()); + // Both queries access to tha data source + _mock!.Verify(ds => ds.GetCarbonIntensityAsync(_locations!["eastus"], _dateTimes!["firstDay"], _dateTimes["secondDay"]), Moq.Times.Once); + _mock!.Verify(ds => ds.GetCarbonIntensityAsync(_locations!["eastus"], _dateTimes!["secondDay"], _dateTimes["thirdDay"]), Moq.Times.Once); + } + + [Test] + public void GetCarbonIntensityAsync_ForSingleLocation_AccessToDataSourceIfLocationNameIsEmpty() + { + var config = new EmissionsDataCacheConfiguration(); + config.Enabled = true; + config.ExpirationMin = 10; + + var dataSource = LatestEmissionsCache.CreateProxy(_dataSource!, config); + var resultOfFirst = dataSource!.GetCarbonIntensityAsync(_locations!["coordinate"], _dateTimes!["firstDay"], _dateTimes["secondDay"]).Result; + var resultOfSecond = dataSource!.GetCarbonIntensityAsync(_locations!["coordinate"], _dateTimes!["firstDay"], _dateTimes["secondDay"]).Result; + + Assert.AreEqual(resultOfFirst, resultOfSecond); + // Both queries access to tha data source + _mock!.Verify(ds => ds.GetCarbonIntensityAsync(_locations!["coordinate"], _dateTimes!["firstDay"], _dateTimes["secondDay"]), Moq.Times.Exactly(2)); + } + + [Test] + public void GetCarbonIntensityAsync_ForMultiLocation_UseCachedDataForSameQuery() + { + var config = new EmissionsDataCacheConfiguration(); + config.Enabled = true; + config.ExpirationMin = 10; + + var dataSource = LatestEmissionsCache.CreateProxy(_dataSource!, config); + var resultFromDataSource = dataSource!.GetCarbonIntensityAsync(new List{_locations!["eastus"], _locations["westus"]}, _dateTimes!["firstDay"], _dateTimes["secondDay"]).Result; + var resultFromCache = dataSource!.GetCarbonIntensityAsync(new List{_locations!["eastus"], _locations["westus"]}, _dateTimes!["firstDay"], _dateTimes["secondDay"]).Result; + + // The results are same + Assert.AreEqual(resultFromDataSource, resultFromCache); + // The access to the data source occurs just once + _mock!.Verify(ds => ds.GetCarbonIntensityAsync(new List{_locations!["eastus"], _locations["westus"]}, _dateTimes!["firstDay"], _dateTimes["secondDay"]), Moq.Times.Once); + } + + [Test] + public void GetCarbonIntensityAsync_ForMultiLocation_UsePartOfCachedData() + { + var config = new EmissionsDataCacheConfiguration(); + config.Enabled = true; + config.ExpirationMin = 10; + + var dataSource = LatestEmissionsCache.CreateProxy(_dataSource!, config); + var resultFromDataSource = dataSource!.GetCarbonIntensityAsync(new List{_locations!["eastus"], _locations["westus"]}, _dateTimes!["firstDay"], _dateTimes["secondDay"]).Result; + var resultFromCache = dataSource!.GetCarbonIntensityAsync(new List{_locations!["eastus"]}, _dateTimes!["firstDay"], _dateTimes["secondDay"]).Result; + + // The result for the second query is a part of the first one + Assert.AreEqual(resultFromDataSource.Count(), 2); + Assert.AreEqual(resultFromCache.Count(), 1); + // The access to the data source occurs just once + _mock!.Verify(ds => ds.GetCarbonIntensityAsync(new List{_locations!["eastus"]}, _dateTimes!["firstDay"], _dateTimes["secondDay"]), Moq.Times.Never); + } + + [Test] + public void GetCarbonIntensityAsync_ForMultiLocation_UsePartOfCachedData2() + { + var config = new EmissionsDataCacheConfiguration(); + config.Enabled = true; + config.ExpirationMin = 10; + + var dataSource = LatestEmissionsCache.CreateProxy(_dataSource!, config); + var resultForFirst = dataSource!.GetCarbonIntensityAsync(new List{_locations!["eastus"]}, _dateTimes!["firstDay"], _dateTimes["secondDay"]).Result; + var resultForSecond = dataSource!.GetCarbonIntensityAsync(new List{_locations!["eastus"], _locations["westus"]}, _dateTimes!["firstDay"], _dateTimes["secondDay"]).Result; + + Assert.AreEqual(resultForFirst.Count(), 1); + Assert.AreEqual(resultForSecond.Count(), 2); + // the first query + _mock!.Verify(ds => ds.GetCarbonIntensityAsync(new List{_locations!["eastus"]}, _dateTimes!["firstDay"], _dateTimes["secondDay"]), Moq.Times.Once); + // the second query does not contain "eastus" because the data for "eastus" have been already cached + _mock!.Verify(ds => ds.GetCarbonIntensityAsync(new List{_locations!["westus"]}, _dateTimes!["firstDay"], _dateTimes["secondDay"]), Moq.Times.Once); + } + + [SetUp] + public void Setup() + { + _mock!.Invocations.Clear(); + } + + [OneTimeSetUp] + public void SetupDataAndMock() + { + // Crate test data + _dateTimes = new Dictionary + { + {"firstDay", new DateTimeOffset(2021, 11, 16, 0, 0, 0, TimeSpan.Zero)}, + {"secondDay", new DateTimeOffset(2021, 11, 17, 0, 0, 0, TimeSpan.Zero)}, + {"thirdDay", new DateTimeOffset(2021, 11, 18, 0, 0, 0, TimeSpan.Zero)} + }; + + var location1 = new Location(); + location1.Name = "eastus"; + var location2 = new Location(); + location2.Name = "westus"; + var location3 = new Location(); + location3.Latitude = 0; + location3.Longitude = 0; + _locations = new Dictionary + { + {"eastus", location1}, + {"westus", location2}, + {"coordinate", location3} + }; + + var emissions1 = new EmissionsData(); + emissions1.Location = "eastus"; + emissions1.Time = new DateTimeOffset(2021, 11, 16, 0, 55, 0, TimeSpan.Zero); + var emissions2 = new EmissionsData(); + emissions2.Location = "eastus"; + emissions2.Time = new DateTimeOffset(2021, 11, 17, 0, 55, 0, TimeSpan.Zero); + var emissions3 = new EmissionsData(); + emissions3.Location = "westus"; + emissions3.Time = new DateTimeOffset(2021, 11, 16, 0, 55, 0, TimeSpan.Zero); + var emissions4 = new EmissionsData(); + emissions4.Location = ""; + emissions4.Time = new DateTimeOffset(2021, 11, 16, 0, 55, 0, TimeSpan.Zero); + _emissions = new Dictionary + { + {"eastus-firstDay", emissions1}, + {"eastus-secondDay", emissions2}, + {"westus-firstDay", emissions3}, + {"coordinate-firstDay", emissions4} + }; + + // setup a mock + _mock = new Mock(); + + // setup for GetCarbonIntensityAsync(Location, DateTimeOffset, DateTimeOffset) + _mock.Setup(ds => + ds.GetCarbonIntensityAsync(_locations["eastus"], _dateTimes["firstDay"], _dateTimes["secondDay"])) + .Returns(Task.FromResult((IEnumerable)new List{_emissions["eastus-firstDay"]})); + _mock.Setup(ds => + ds.GetCarbonIntensityAsync(_locations["eastus"], _dateTimes["secondDay"], _dateTimes["thirdDay"])) + .Returns(Task.FromResult((IEnumerable)new List{_emissions["eastus-secondDay"]})); + _mock.Setup(ds => + ds.GetCarbonIntensityAsync(_locations["eastus"], _dateTimes["firstDay"], _dateTimes["thirdDay"])) + .Returns(Task.FromResult((IEnumerable)new List{_emissions["eastus-firstDay"], _emissions["eastus-secondDay"]})); + _mock.Setup(ds => + ds.GetCarbonIntensityAsync(_locations["westus"], _dateTimes["firstDay"], _dateTimes["secondDay"])) + .Returns(Task.FromResult((IEnumerable)new List{_emissions["westus-firstDay"]})); + _mock.Setup(ds => + ds.GetCarbonIntensityAsync(_locations["coordinate"], _dateTimes["firstDay"], _dateTimes["secondDay"])) + .Returns(Task.FromResult((IEnumerable)new List{_emissions["coordinate-firstDay"]})); + + // setup for GetCarbonIntensityAsync(IEnumerable, DateTimeOffset, DateTimeOffset) + _mock.Setup(ds => + ds.GetCarbonIntensityAsync(new List{_locations["eastus"], _locations["westus"]}, _dateTimes["firstDay"], _dateTimes["secondDay"])) + .Returns(Task.FromResult((IEnumerable)new List{_emissions["eastus-firstDay"], _emissions["westus-firstDay"]})); + _mock.Setup(ds => + ds.GetCarbonIntensityAsync(new List{_locations["eastus"]}, _dateTimes["firstDay"], _dateTimes["secondDay"])) + .Returns(Task.FromResult((IEnumerable)new List{_emissions["eastus-firstDay"]})); + _mock.Setup(ds => + ds.GetCarbonIntensityAsync(new List{_locations["westus"]}, _dateTimes["firstDay"], _dateTimes["secondDay"])) + .Returns(Task.FromResult((IEnumerable)new List{_emissions["westus-firstDay"]})); + + _dataSource = _mock.Object; + } + +} diff --git a/src/GSF.CarbonAware/src/Configuration/ServiceCollectionExtensions.cs b/src/GSF.CarbonAware/src/Configuration/ServiceCollectionExtensions.cs index 459351ae6..440e657b4 100644 --- a/src/GSF.CarbonAware/src/Configuration/ServiceCollectionExtensions.cs +++ b/src/GSF.CarbonAware/src/Configuration/ServiceCollectionExtensions.cs @@ -11,15 +11,22 @@ namespace GSF.CarbonAware.Configuration; public static class ServiceCollectionExtensions { - /// - /// Add services needed in order to use an Emissions service. - /// - public static IServiceCollection AddEmissionsServices(this IServiceCollection services, IConfiguration configuration) + + private static IServiceCollection ConfigureLocationDataSourcesConfiguration(this IServiceCollection services, IConfiguration configuration) { services.Configure(c => { configuration.GetSection(LocationDataSourcesConfiguration.Key).Bind(c); }); + return services; + } + + /// + /// Add services needed in order to use an Emissions service. + /// + public static IServiceCollection AddEmissionsServices(this IServiceCollection services, IConfiguration configuration) + { + services.ConfigureLocationDataSourcesConfiguration(configuration); services.TryAddSingleton(); services.AddDataSourceService(configuration); services.TryAddSingleton(); @@ -32,10 +39,7 @@ public static IServiceCollection AddEmissionsServices(this IServiceCollection se /// public static IServiceCollection AddForecastServices(this IServiceCollection services, IConfiguration configuration) { - services.Configure(c => - { - configuration.GetSection(LocationDataSourcesConfiguration.Key).Bind(c); - }); + services.ConfigureLocationDataSourcesConfiguration(configuration); services.TryAddSingleton(); services.AddDataSourceService(configuration); services.TryAddSingleton(); diff --git a/src/GSF.CarbonAware/src/GSF.CarbonAware.csproj b/src/GSF.CarbonAware/src/GSF.CarbonAware.csproj index f113e0b98..6bb9fac4b 100644 --- a/src/GSF.CarbonAware/src/GSF.CarbonAware.csproj +++ b/src/GSF.CarbonAware/src/GSF.CarbonAware.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 enable enable true @@ -37,6 +37,7 @@ + @@ -48,13 +49,13 @@ - + - + - \ No newline at end of file + diff --git a/src/GSF.CarbonAware/src/GSF.CarbonAware.targets b/src/GSF.CarbonAware/src/GSF.CarbonAware.targets index 0e9a1ace2..752a668a6 100644 --- a/src/GSF.CarbonAware/src/GSF.CarbonAware.targets +++ b/src/GSF.CarbonAware/src/GSF.CarbonAware.targets @@ -16,7 +16,7 @@ - + diff --git a/src/GSF.CarbonAware/test/GSF.CarbonAware.Tests.csproj b/src/GSF.CarbonAware/test/GSF.CarbonAware.Tests.csproj index 64e74590c..696005074 100644 --- a/src/GSF.CarbonAware/test/GSF.CarbonAware.Tests.csproj +++ b/src/GSF.CarbonAware/test/GSF.CarbonAware.Tests.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 enable false diff --git a/src/clients/docker-generate-clients.sh b/src/clients/docker-generate-clients.sh index 142a64179..23478f2d4 100755 --- a/src/clients/docker-generate-clients.sh +++ b/src/clients/docker-generate-clients.sh @@ -45,7 +45,7 @@ docker run --rm \ -i http://$1/swagger/v1/swagger.json \ -g csharp-netcore \ -o /local/csharp \ - --additional-properties=targetFramework=net6.0 + --additional-properties=targetFramework=net8.0 # golang docker run --rm \ diff --git a/src/clients/generate-clients.sh b/src/clients/generate-clients.sh index 529d78e7a..9677589b8 100755 --- a/src/clients/generate-clients.sh +++ b/src/clients/generate-clients.sh @@ -12,5 +12,5 @@ cd generated openapi-generator-cli generate -i http://$1/swagger/v1/swagger.json -g java -o ./java openapi-generator-cli generate -i http://$1/swagger/v1/swagger.json -g python -o ./python openapi-generator-cli generate -i http://$1/swagger/v1/swagger.json -g javascript -o ./javascript -openapi-generator-cli generate -i http://$1/swagger/v1/swagger.json -g csharp-netcore -o ./csharp --additional-properties=targetFramework=net6.0 +openapi-generator-cli generate -i http://$1/swagger/v1/swagger.json -g csharp-netcore -o ./csharp --additional-properties=targetFramework=net8.0 openapi-generator-cli generate -i http://$1/swagger/v1/swagger.json -g go -o ./golang diff --git a/src/clients/tests/csharp/Dockerfile b/src/clients/tests/csharp/Dockerfile index 56b1cffcd..4bb8ffe61 100644 --- a/src/clients/tests/csharp/Dockerfile +++ b/src/clients/tests/csharp/Dockerfile @@ -1,5 +1,5 @@ # https://hub.docker.com/_/microsoft-dotnet -FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build # copy csharp client WORKDIR /source/clients/csharp/src/Org.OpenAPITools @@ -13,7 +13,7 @@ COPY tests/csharp . RUN dotnet publish -c release -o /app # final stage/image -FROM mcr.microsoft.com/dotnet/aspnet:6.0 +FROM mcr.microsoft.com/dotnet/aspnet:8.0 WORKDIR /app COPY tests/temp.env /.env COPY --from=build /app ./ diff --git a/src/clients/tests/csharp/csharp.csproj b/src/clients/tests/csharp/csharp.csproj index 0976a8879..f620d14f3 100644 --- a/src/clients/tests/csharp/csharp.csproj +++ b/src/clients/tests/csharp/csharp.csproj @@ -1,15 +1,15 @@ - net6.0 + net8.0 - - + +