From 984c9353c19e9f8b58bc2fbd4c7256209e97accc Mon Sep 17 00:00:00 2001 From: Raviteja Sunkavalli Date: Mon, 26 May 2025 12:30:22 -0700 Subject: [PATCH 01/19] enable app-signals and autoinstrumentation on petsite --- PetAdoptions/cdk/pet_stack/lib/services.ts | 10 ++++++++-- .../pet_stack/resources/k8s_petsite/deployment.yaml | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/PetAdoptions/cdk/pet_stack/lib/services.ts b/PetAdoptions/cdk/pet_stack/lib/services.ts index 41d0cab3..2d105d3b 100644 --- a/PetAdoptions/cdk/pet_stack/lib/services.ts +++ b/PetAdoptions/cdk/pet_stack/lib/services.ts @@ -18,6 +18,7 @@ import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch'; import * as applicationinsights from 'aws-cdk-lib/aws-applicationinsights'; import * as resourcegroups from 'aws-cdk-lib/aws-resourcegroups'; +import * as applicationsignals from 'aws-cdk-lib/aws-applicationsignals'; import { Construct } from 'constructs' import { PayForAdoptionService } from './services/pay-for-adoption-service' @@ -44,6 +45,11 @@ export class Services extends Stack { visibilityTimeout: Duration.seconds(300) }); + // Enable Application Signals in the account + const cfnDiscovery = new applicationsignals.CfnDiscovery(this, + 'ApplicationSignalsServiceRole', { } + ); + // Create SNS and an email topic to send notifications to const topic_petadoption = new sns.Topic(this, 'topic_petadoption'); var topic_email = this.node.tryGetContext('snstopic_email'); @@ -503,7 +509,7 @@ export class Services extends Stack { // NOTE: Amazon CloudWatch Observability Addon for CloudWatch Agent and Fluentbit - const otelAddon = new eks.CfnAddon(this, 'otelObservabilityAddon', { + const cwAddon = new eks.CfnAddon(this, 'CloudWatchObservabilityAddon', { addonName: 'amazon-cloudwatch-observability', addonVersion: 'v3.3.0-eksbuild.1', clusterName: cluster.clusterName, @@ -710,4 +716,4 @@ export class Services extends Stack { new CfnOutput(this, key, { value: value }) }); } -} +} \ No newline at end of file diff --git a/PetAdoptions/cdk/pet_stack/resources/k8s_petsite/deployment.yaml b/PetAdoptions/cdk/pet_stack/resources/k8s_petsite/deployment.yaml index 1e02d0b9..25a44213 100644 --- a/PetAdoptions/cdk/pet_stack/resources/k8s_petsite/deployment.yaml +++ b/PetAdoptions/cdk/pet_stack/resources/k8s_petsite/deployment.yaml @@ -39,6 +39,8 @@ spec: metadata: labels: app: petsite + annotations: + instrumentation.opentelemetry.io/inject-dotnet: "true" spec: serviceAccountName: petsite-sa containers: From c2b54b4e7a285758cd2c01bf8dff40b1ebca40d8 Mon Sep 17 00:00:00 2001 From: Raviteja Sunkavalli Date: Mon, 26 May 2025 14:57:21 -0700 Subject: [PATCH 02/19] remove xray sdk and add custom span attributes to the traces from auto-instrumentation --- PetAdoptions/cdk/pet_stack/lib/services.ts | 8 +- .../resources/k8s_petsite/deployment.yaml | 3 - .../k8s_petsite/xray-daemon-config.yaml | 106 ----- .../petsite/Controllers/AdoptionController.cs | 175 ++++---- .../petsite/Controllers/HomeController.cs | 372 ++++++++---------- .../petsite/Controllers/PaymentController.cs | 338 ++++++++-------- .../petsite/Controllers/PetFoodController.cs | 84 ++-- .../Controllers/PetHistoryController.cs | 59 ++- .../Controllers/PetListAdoptionsController.cs | 40 +- PetAdoptions/petsite/petsite/Dockerfile | 38 +- PetAdoptions/petsite/petsite/PetSite.csproj | 7 +- PetAdoptions/petsite/petsite/Program.cs | 96 +++-- PetAdoptions/petsite/petsite/Startup.cs | 130 +++--- 13 files changed, 689 insertions(+), 767 deletions(-) delete mode 100644 PetAdoptions/cdk/pet_stack/resources/k8s_petsite/xray-daemon-config.yaml diff --git a/PetAdoptions/cdk/pet_stack/lib/services.ts b/PetAdoptions/cdk/pet_stack/lib/services.ts index 2d105d3b..705cdba1 100644 --- a/PetAdoptions/cdk/pet_stack/lib/services.ts +++ b/PetAdoptions/cdk/pet_stack/lib/services.ts @@ -395,6 +395,8 @@ export class Services extends Stack { }); cwserviceaccount.assumeRolePolicy?.addStatements(cw_trustRelationship); + // Comment out X-Ray service account for petsite + /* const xray_federatedPrincipal = new iam.FederatedPrincipal( cluster.openIdConnectProvider.openIdConnectProviderArn, { @@ -420,6 +422,7 @@ export class Services extends Stack { ], }); xrayserviceaccount.assumeRolePolicy?.addStatements(xray_trustRelationship); + */ const loadbalancer_federatedPrincipal = new iam.FederatedPrincipal( cluster.openIdConnectProvider.openIdConnectProviderArn, @@ -457,6 +460,8 @@ export class Services extends Stack { ]); } + // Comment out X-Ray deployment for petsite + /* var xRayYaml = yaml.loadAll(readFileSync("./resources/k8s_petsite/xray-daemon-config.yaml", "utf8")) as Record[]; xRayYaml[0].metadata.annotations["eks.amazonaws.com/role-arn"] = new CfnJson(this, "xray_Role", { value: `${xrayserviceaccount.roleArn}` }); @@ -465,6 +470,7 @@ export class Services extends Stack { cluster: cluster, manifest: xRayYaml }); + */ var loadBalancerServiceAccountYaml = yaml.loadAll(readFileSync("./resources/load_balancer/service_account.yaml", "utf8")) as Record[]; loadBalancerServiceAccountYaml[0].metadata.annotations["eks.amazonaws.com/role-arn"] = new CfnJson(this, "loadBalancer_Role", { value: `${loadBalancerserviceaccount.roleArn}` }); @@ -659,7 +665,7 @@ export class Services extends Stack { this.createOuputs(new Map(Object.entries({ 'CWServiceAccountArn': cwserviceaccount.roleArn, 'NetworkFlowMonitorServiceAccountArn': networkFlowMonitorRole.attrArn, - 'XRayServiceAccountArn': xrayserviceaccount.roleArn, + //'XRayServiceAccountArn': xrayserviceaccount.roleArn, 'OIDCProviderUrl': cluster.clusterOpenIdConnectIssuerUrl, 'OIDCProviderArn': cluster.openIdConnectProvider.openIdConnectProviderArn, 'PetSiteUrl': `http://${alb.loadBalancerDnsName}`, diff --git a/PetAdoptions/cdk/pet_stack/resources/k8s_petsite/deployment.yaml b/PetAdoptions/cdk/pet_stack/resources/k8s_petsite/deployment.yaml index 25a44213..1d73a392 100644 --- a/PetAdoptions/cdk/pet_stack/resources/k8s_petsite/deployment.yaml +++ b/PetAdoptions/cdk/pet_stack/resources/k8s_petsite/deployment.yaml @@ -50,9 +50,6 @@ spec: ports: - containerPort: 80 protocol: TCP - env: - - name: AWS_XRAY_DAEMON_ADDRESS - value: xray-service.default:2000 --- apiVersion: elbv2.k8s.aws/v1beta1 kind: TargetGroupBinding diff --git a/PetAdoptions/cdk/pet_stack/resources/k8s_petsite/xray-daemon-config.yaml b/PetAdoptions/cdk/pet_stack/resources/k8s_petsite/xray-daemon-config.yaml deleted file mode 100644 index ef420312..00000000 --- a/PetAdoptions/cdk/pet_stack/resources/k8s_petsite/xray-daemon-config.yaml +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). -# You may not use this file except in compliance with the License. -# A copy of the License is located at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# or in the "license" file accompanying this file. This file is distributed -# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -# express or implied. See the License for the specific language governing -# permissions and limitations under the License. -apiVersion: v1 -kind: ServiceAccount -metadata: - annotations: - eks.amazonaws.com/role-arn: XRAY_SA_ROLE - labels: - app: xray-daemon - name: xray-daemon - namespace: default ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: xray-daemon - labels: - app: xray-daemon -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: cluster-admin -subjects: -- kind: ServiceAccount - name: xray-daemon - namespace: default ---- -apiVersion: apps/v1 -kind: DaemonSet -metadata: - name: xray-daemon -spec: - updateStrategy: - type: RollingUpdate - selector: - matchLabels: - app: xray-daemon - template: - metadata: - labels: - app: xray-daemon - spec: - serviceAccountName: xray-daemon - volumes: - - name: config-volume - configMap: - name: xray-config - hostNetwork: true - containers: - - name: xray-daemon - image: public.ecr.aws/xray/aws-xray-daemon:3.3.3 - imagePullPolicy: Always - command: - - "/xray" - - "-c" - - "/aws/xray/config.yaml" - resources: - limits: - memory: 24Mi - ports: - - name: xray-ingest - containerPort: 2000 - hostPort: 2000 - protocol: UDP - volumeMounts: - - name: config-volume - mountPath: "/aws/xray" - readOnly: true ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: xray-config - namespace: default -data: - config.yaml: |- - TotalBufferSizeMB: 24 - Socket: - UDPAddress: "0.0.0.0:2000" - TCPAddress: "0.0.0.0:2000" - Version: 2 - Logging: - LogLevel: "info" ---- -apiVersion: v1 -kind: Service -metadata: - name: xray-service -spec: - selector: - app: xray-daemon - clusterIP: None - ports: - - name: incoming - port: 2000 - protocol: UDP diff --git a/PetAdoptions/petsite/petsite/Controllers/AdoptionController.cs b/PetAdoptions/petsite/petsite/Controllers/AdoptionController.cs index 72f371df..6acdf7b9 100644 --- a/PetAdoptions/petsite/petsite/Controllers/AdoptionController.cs +++ b/PetAdoptions/petsite/petsite/Controllers/AdoptionController.cs @@ -1,85 +1,90 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Text.Json; -using System.Threading.Tasks; -using Amazon.XRay.Recorder.Core; -using Amazon.XRay.Recorder.Handlers.AwsSdk; -using Amazon.XRay.Recorder.Handlers.System.Net; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using PetSite.ViewModels; - -namespace PetSite.Controllers -{ - public class AdoptionController : Controller - { - private static readonly HttpClient HttpClient = new HttpClient(new HttpClientXRayTracingHandler(new HttpClientHandler())); - private static Variety _variety = new Variety(); - private static IConfiguration _configuration; - - private static string _searchApiurl; - - public AdoptionController(IConfiguration configuration) - { - _configuration = configuration; - - //_searchApiurl = _configuration["searchapiurl"]; - _searchApiurl = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"searchapiurl"); - - AWSSDKHandler.RegisterXRayForAllServices(); - } - // GET: Adoption - [HttpGet] - public IActionResult Index([FromQuery] Pet pet) - { - return View(pet); - } - private async Task GetPetDetails(SearchParams searchParams) - { - string searchString = string.Empty; - - if (!String.IsNullOrEmpty(searchParams.pettype) && searchParams.pettype != "all") searchString = $"pettype={searchParams.pettype}"; - if (!String.IsNullOrEmpty(searchParams.petcolor) && searchParams.petcolor != "all") searchString = $"&{searchString}&petcolor={searchParams.petcolor}"; - if (!String.IsNullOrEmpty(searchParams.petid) && searchParams.petid != "all") searchString = $"&{searchString}&petid={searchParams.petid}"; - - return await HttpClient.GetStringAsync($"{_searchApiurl}{searchString}"); - } - - [HttpPost] - public async Task TakeMeHome([FromForm] SearchParams searchParams) - { - - Console.WriteLine( - $"[{AWSXRayRecorder.Instance.TraceContext.GetEntity().RootSegment.TraceId}][{AWSXRayRecorder.Instance.GetEntity().TraceId}] - Inside TakeMehome. Pet in context - PetId:{searchParams.petid}, PetType:{searchParams.pettype}, PetColor:{searchParams.petcolor}"); - - - AWSXRayRecorder.Instance.AddMetadata("PetType", searchParams.pettype); - AWSXRayRecorder.Instance.AddMetadata("PetId", searchParams.petid); - AWSXRayRecorder.Instance.AddMetadata("PetColor", searchParams.petcolor); - - //String traceId = TraceId.NewId(); // This function is present in : Amazon.XRay.Recorder.Core.Internal.Entities - AWSXRayRecorder.Instance - .BeginSubsegment("Calling Search API"); // custom traceId used while creating segment - string result; - - try - { - result = await GetPetDetails(searchParams); - } - catch (Exception e) - { - AWSXRayRecorder.Instance.AddException(e); - throw e; - } - finally - { - AWSXRayRecorder.Instance.EndSubsegment(); - } - - return View("Index", JsonSerializer.Deserialize>(result).FirstOrDefault()); - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; +using System.Diagnostics; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using PetSite.ViewModels; + +namespace PetSite.Controllers +{ + public class AdoptionController : Controller + { + private static readonly HttpClient HttpClient = new HttpClient(); + private static Variety _variety = new Variety(); + private static IConfiguration _configuration; + + private static string _searchApiurl; + + public AdoptionController(IConfiguration configuration) + { + _configuration = configuration; + + //_searchApiurl = _configuration["searchapiurl"]; + _searchApiurl = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"searchapiurl"); + } + + // GET: Adoption + [HttpGet] + public IActionResult Index([FromQuery] Pet pet) + { + return View(pet); + } + + private async Task GetPetDetails(SearchParams searchParams) + { + string searchString = string.Empty; + + if (!String.IsNullOrEmpty(searchParams.pettype) && searchParams.pettype != "all") searchString = $"pettype={searchParams.pettype}"; + if (!String.IsNullOrEmpty(searchParams.petcolor) && searchParams.petcolor != "all") searchString = $"&{searchString}&petcolor={searchParams.petcolor}"; + if (!String.IsNullOrEmpty(searchParams.petid) && searchParams.petid != "all") searchString = $"&{searchString}&petid={searchParams.petid}"; + + return await HttpClient.GetStringAsync($"{_searchApiurl}{searchString}"); + } + + [HttpPost] + public async Task TakeMeHome([FromForm] SearchParams searchParams) + { + // Add custom span attributes using Activity API (compatible with Application Signals auto-instrumentation) + var currentActivity = Activity.Current; + if (currentActivity != null) + { + currentActivity.SetTag("pet.id", searchParams.petid); + currentActivity.SetTag("pet.type", searchParams.pettype); + currentActivity.SetTag("pet.color", searchParams.petcolor); + + Console.WriteLine($"Processing adoption request - PetId:{searchParams.petid}, PetType:{searchParams.pettype}, PetColor:{searchParams.petcolor}"); + } + + string result; + + try + { + // Create a new activity for the API call + using (var activity = new Activity("Calling Search API").Start()) + { + if (activity != null) + { + activity.SetTag("pet.id", searchParams.petid); + activity.SetTag("pet.type", searchParams.pettype); + activity.SetTag("pet.color", searchParams.petcolor); + } + + result = await GetPetDetails(searchParams); + } + } + catch (Exception e) + { + // Log the exception + Console.WriteLine($"Error calling search API: {e.Message}"); + throw; + } + + return View("Index", JsonSerializer.Deserialize>(result).FirstOrDefault()); + } + } +} diff --git a/PetAdoptions/petsite/petsite/Controllers/HomeController.cs b/PetAdoptions/petsite/petsite/Controllers/HomeController.cs index 77b7dee1..dccb9ea4 100644 --- a/PetAdoptions/petsite/petsite/Controllers/HomeController.cs +++ b/PetAdoptions/petsite/petsite/Controllers/HomeController.cs @@ -1,199 +1,173 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using PetSite.Models; -using Amazon.XRay.Recorder.Handlers.AwsSdk; -using System.Net.Http; -using Amazon.XRay.Recorder.Handlers.System.Net; -using Amazon.XRay.Recorder.Core; -using System.Text.Json; -using Amazon; -using PetSite.ViewModels; -using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.Extensions.Configuration; -using Prometheus; - -namespace PetSite.Controllers -{ - public class HomeController : Controller - { - private readonly ILogger _logger; - private static HttpClient _httpClient; - private static Variety _variety = new Variety(); - - private IConfiguration _configuration; - - //Prometheus metric to count the number of searches performed - private static readonly Counter PetSearchCount = - Metrics.CreateCounter("petsite_petsearches_total", "Count the number of searches performed"); - - //Prometheus metric to count the number of puppy searches performed - private static readonly Counter PuppySearchCount = - Metrics.CreateCounter("petsite_pet_puppy_searches_total", "Count the number of puppy searches performed"); - - //Prometheus metric to count the number of kitten searches performed - private static readonly Counter KittenSearchCount = - Metrics.CreateCounter("petsite_pet_kitten_searches_total", "Count the number of kitten searches performed"); - - //Prometheus metric to count the number of bunny searches performed - private static readonly Counter BunnySearchCount = - Metrics.CreateCounter("petsite_pet_bunny_searches_total", "Count the number of bunny searches performed"); - - private static readonly Gauge PetsWaitingForAdoption = Metrics - .CreateGauge("petsite_pets_waiting_for_adoption", "Number of pets waiting for adoption."); - - public HomeController(ILogger logger, IConfiguration configuration) - { - AWSXRayRecorder.RegisterLogger(LoggingOptions.Console); - _configuration = configuration; - AWSSDKHandler.RegisterXRayForAllServices(); - - _httpClient = new HttpClient(new HttpClientXRayTracingHandler(new HttpClientHandler())); - _logger = logger; - - _variety.PetTypes = new List() - { - new SelectListItem() {Value = "all", Text = "All"}, - new SelectListItem() {Value = "puppy", Text = "Puppy"}, - new SelectListItem() {Value = "kitten", Text = "Kitten"}, - new SelectListItem() {Value = "bunny", Text = "Bunny"} - }; - - _variety.PetColors = new List() - { - new SelectListItem() {Value = "all", Text = "All"}, - new SelectListItem() {Value = "brown", Text = "Brown"}, - new SelectListItem() {Value = "black", Text = "Black"}, - new SelectListItem() {Value = "white", Text = "White"} - }; - } - - private async Task GetPetDetails(string pettype, string petcolor, string petid) - { - string searchUri = string.Empty; - - if (!String.IsNullOrEmpty(pettype) && pettype != "all") searchUri = $"pettype={pettype}"; - if (!String.IsNullOrEmpty(petcolor) && petcolor != "all") searchUri = $"&{searchUri}&petcolor={petcolor}"; - if (!String.IsNullOrEmpty(petid) && petid != "all") searchUri = $"&{searchUri}&petid={petid}"; - - switch (pettype) - { - case "puppy": - PuppySearchCount.Inc(); - PetSearchCount.Inc(); - break; - case "kitten": - KittenSearchCount.Inc(); - PetSearchCount.Inc(); - break; - case "bunny": - BunnySearchCount.Inc(); - PetSearchCount.Inc(); - break; - } - //string searchapiurl = _configuration["searchapiurl"]; - string searchapiurl = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"searchapiurl"); - return await _httpClient.GetStringAsync($"{searchapiurl}{searchUri}"); - } - - [HttpGet("housekeeping")] - public async Task HouseKeeping() - { - Console.WriteLine( - $"[{AWSXRayRecorder.Instance.TraceContext.GetEntity().RootSegment.TraceId}][{AWSXRayRecorder.Instance.GetEntity().TraceId}] - In Housekeeping, trying to reset the app."); - - /*var result = await GetPetDetails(null, null, null); - var Pets = JsonSerializer.Deserialize>(result); - - var searchParams = new SearchParams(); - - //string updateadoptionstatusurl = _configuration["updateadoptionstatusurl"]; - string updateadoptionstatusurl = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"updateadoptionstatusurl"); - - - foreach (var pet in Pets.Where(item => item.availability == "no")) - { - searchParams.pettype = pet.pettype; - searchParams.petid = pet.petid; - searchParams.petavailability = "yes"; - - StringContent putData = new StringContent(JsonSerializer.Serialize(searchParams)); - await _httpClient.PutAsync(updateadoptionstatusurl, putData); - }*/ - - //string cleanupadoptionsurl = _configuration["cleanupadoptionsurl"]; - string cleanupadoptionsurl = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"cleanupadoptionsurl"); - - await _httpClient.PostAsync(cleanupadoptionsurl, null); - - return View(); - } - - [HttpGet] - public async Task Index(string selectedPetType, string selectedPetColor, string petid) - { - Console.WriteLine( - $"AWS_XRAY_DAEMON_ADDRESS:- {Environment.GetEnvironmentVariable("AWS_XRAY_DAEMON_ADDRESS")}"); - - - AWSXRayRecorder.Instance.BeginSubsegment("Calling Search API"); - - AWSXRayRecorder.Instance.AddMetadata("PetType", selectedPetType); - AWSXRayRecorder.Instance.AddMetadata("PetId", petid); - AWSXRayRecorder.Instance.AddMetadata("PetColor", selectedPetColor); - - - Console.WriteLine( - $"[{AWSXRayRecorder.Instance.TraceContext.GetEntity().RootSegment.TraceId}]- Search string - PetType:{selectedPetType} PetColor:{selectedPetColor} PetId:{petid}"); - - // | SegmentId: [{AWSXRayRecorder.Instance.TraceContext.GetEntity().RootSegment.Id} - string result; - - try - { - result = await GetPetDetails(selectedPetType, selectedPetColor, petid); - } - catch (Exception e) - { - AWSXRayRecorder.Instance.AddException(e); - throw e; - } - finally - { - AWSXRayRecorder.Instance.EndSubsegment(); - } - - var Pets = JsonSerializer.Deserialize>(result); - - var PetDetails = new PetDetails() - { - Pets = Pets, - Varieties = new Variety - { - PetTypes = _variety.PetTypes, - PetColors = _variety.PetColors, - SelectedPetColor = selectedPetColor, - SelectedPetType = selectedPetType - } - }; - AWSXRayRecorder.Instance.AddMetadata("results", System.Text.Json.JsonSerializer.Serialize(PetDetails)); - Console.WriteLine( - $" TraceId: [{AWSXRayRecorder.Instance.GetEntity().TraceId}] - {JsonSerializer.Serialize(PetDetails)}"); - - // Sets the metric value to the number of pets available for adoption at the moment - PetsWaitingForAdoption.Set(Pets.Where(pet => pet.availability == "yes").Count()); - - return View(PetDetails); - } - - [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - public IActionResult Error() - { - return View(new ErrorViewModel {RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier}); - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using PetSite.Models; +using System.Net.Http; +using System.Text.Json; +using PetSite.ViewModels; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.Extensions.Configuration; +using Prometheus; + +namespace PetSite.Controllers +{ + public class HomeController : Controller + { + private readonly ILogger _logger; + private static HttpClient _httpClient; + private static Variety _variety = new Variety(); + + private IConfiguration _configuration; + + //Prometheus metric to count the number of searches performed + private static readonly Counter PetSearchCount = + Metrics.CreateCounter("petsite_petsearches_total", "Count the number of searches performed"); + + //Prometheus metric to count the number of puppy searches performed + private static readonly Counter PuppySearchCount = + Metrics.CreateCounter("petsite_pet_puppy_searches_total", "Count the number of puppy searches performed"); + + //Prometheus metric to count the number of kitten searches performed + private static readonly Counter KittenSearchCount = + Metrics.CreateCounter("petsite_pet_kitten_searches_total", "Count the number of kitten searches performed"); + + //Prometheus metric to count the number of bunny searches performed + private static readonly Counter BunnySearchCount = + Metrics.CreateCounter("petsite_pet_bunny_searches_total", "Count the number of bunny searches performed"); + + private static readonly Gauge PetsWaitingForAdoption = Metrics + .CreateGauge("petsite_pets_waiting_for_adoption", "Number of pets waiting for adoption."); + + public HomeController(ILogger logger, IConfiguration configuration) + { + _configuration = configuration; + _httpClient = new HttpClient(); + _logger = logger; + + _variety.PetTypes = new List() + { + new SelectListItem() {Value = "all", Text = "All"}, + new SelectListItem() {Value = "puppy", Text = "Puppy"}, + new SelectListItem() {Value = "kitten", Text = "Kitten"}, + new SelectListItem() {Value = "bunny", Text = "Bunny"} + }; + + _variety.PetColors = new List() + { + new SelectListItem() {Value = "all", Text = "All"}, + new SelectListItem() {Value = "brown", Text = "Brown"}, + new SelectListItem() {Value = "black", Text = "Black"}, + new SelectListItem() {Value = "white", Text = "White"} + }; + } + + private async Task GetPetDetails(string pettype, string petcolor, string petid) + { + string searchUri = string.Empty; + + if (!String.IsNullOrEmpty(pettype) && pettype != "all") searchUri = $"pettype={pettype}"; + if (!String.IsNullOrEmpty(petcolor) && petcolor != "all") searchUri = $"&{searchUri}&petcolor={petcolor}"; + if (!String.IsNullOrEmpty(petid) && petid != "all") searchUri = $"&{searchUri}&petid={petid}"; + + switch (pettype) + { + case "puppy": + PuppySearchCount.Inc(); + PetSearchCount.Inc(); + break; + case "kitten": + KittenSearchCount.Inc(); + PetSearchCount.Inc(); + break; + case "bunny": + BunnySearchCount.Inc(); + PetSearchCount.Inc(); + break; + } + + string searchapiurl = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"searchapiurl"); + return await _httpClient.GetStringAsync($"{searchapiurl}{searchUri}"); + } + + [HttpGet("housekeeping")] + public async Task HouseKeeping() + { + Console.WriteLine("In Housekeeping, trying to reset the app."); + + string cleanupadoptionsurl = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"cleanupadoptionsurl"); + + await _httpClient.PostAsync(cleanupadoptionsurl, null); + + return View(); + } + + [HttpGet] + public async Task Index(string selectedPetType, string selectedPetColor, string petid) + { + // Add custom span attributes using Activity API + var currentActivity = Activity.Current; + if (currentActivity != null) + { + currentActivity.SetTag("pet.type", selectedPetType); + currentActivity.SetTag("pet.color", selectedPetColor); + currentActivity.SetTag("pet.id", petid); + + Console.WriteLine($"Search string - PetType:{selectedPetType} PetColor:{selectedPetColor} PetId:{petid}"); + } + + string result; + + try + { + // Create a new activity for the API call + using (var activity = new Activity("Calling Search API").Start()) + { + if (activity != null) + { + activity.SetTag("pet.type", selectedPetType); + activity.SetTag("pet.color", selectedPetColor); + activity.SetTag("pet.id", petid); + } + + result = await GetPetDetails(selectedPetType, selectedPetColor, petid); + } + } + catch (Exception e) + { + Console.WriteLine($"Error calling search API: {e.Message}"); + throw; + } + + var Pets = JsonSerializer.Deserialize>(result); + + var PetDetails = new PetDetails() + { + Pets = Pets, + Varieties = new Variety + { + PetTypes = _variety.PetTypes, + PetColors = _variety.PetColors, + SelectedPetColor = selectedPetColor, + SelectedPetType = selectedPetType + } + }; + + Console.WriteLine($"{JsonSerializer.Serialize(PetDetails)}"); + + // Sets the metric value to the number of pets available for adoption at the moment + PetsWaitingForAdoption.Set(Pets.Where(pet => pet.availability == "yes").Count()); + + return View(PetDetails); + } + + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public IActionResult Error() + { + return View(new ErrorViewModel {RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier}); + } + } +} diff --git a/PetAdoptions/petsite/petsite/Controllers/PaymentController.cs b/PetAdoptions/petsite/petsite/Controllers/PaymentController.cs index 56f99871..b10799be 100644 --- a/PetAdoptions/petsite/petsite/Controllers/PaymentController.cs +++ b/PetAdoptions/petsite/petsite/Controllers/PaymentController.cs @@ -1,168 +1,170 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using Amazon.XRay.Recorder.Core; -using Amazon.XRay.Recorder.Handlers.AwsSdk; -using Amazon.XRay.Recorder.Handlers.System.Net; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Amazon.SQS; -using Amazon.SQS.Model; -using System.Text.Json.Serialization; -using System.Text.Json; -using Amazon; -using Amazon.Runtime; -using Amazon.SimpleNotificationService; -using Amazon.SimpleNotificationService.Model; -using Amazon.StepFunctions; -using Amazon.StepFunctions.Model; -using Microsoft.Extensions.Configuration; -using PetSite.Models; -using Prometheus; -using Newtonsoft; - -namespace PetSite.Controllers -{ - public class PaymentController : Controller - { - private static string _txStatus = String.Empty; - - private static HttpClient _httpClient = - new HttpClient(new HttpClientXRayTracingHandler(new HttpClientHandler())); - - private static AmazonSQSClient _sqsClient; - private static IConfiguration _configuration; - - //Prometheus metric to count the number of Pets adopted - private static readonly Counter PetAdoptionCount = - Metrics.CreateCounter("petsite_petadoptions_total", "Count the number of Pets adopted"); - - public PaymentController(IConfiguration configuration) - { - AWSSDKHandler.RegisterXRayForAllServices(); - _configuration = configuration; - - _sqsClient = new AmazonSQSClient(Amazon.Util.EC2InstanceMetadata.Region); - } - - // GET: Payment - [HttpGet] - private ActionResult Index() - { - return View(); - } - - // POST: Payment/MakePayment - [HttpPost] - // [ValidateAntiForgeryToken] - public async Task MakePayment(string petId, string pettype) - { - AWSXRayRecorder.Instance.AddMetadata("PetType", pettype); - AWSXRayRecorder.Instance.AddMetadata("PetId", petId); - - ViewData["txStatus"] = "success"; - - try - { - AWSXRayRecorder.Instance.BeginSubsegment("Call Payment API"); - - Console.WriteLine( - $"[{AWSXRayRecorder.Instance.TraceContext.GetEntity().RootSegment.TraceId}][{AWSXRayRecorder.Instance.GetEntity().TraceId}] - Inside MakePayment Action method - PetId:{petId} - PetType:{pettype}"); - - AWSXRayRecorder.Instance.AddAnnotation("PetId", petId); - AWSXRayRecorder.Instance.AddAnnotation("PetType", pettype); - - var result = await PostTransaction(petId, pettype); - AWSXRayRecorder.Instance.EndSubsegment(); - - AWSXRayRecorder.Instance.BeginSubsegment("Post Message to SQS"); - var messageResponse = PostMessageToSqs(petId, pettype).Result; - AWSXRayRecorder.Instance.EndSubsegment(); - - AWSXRayRecorder.Instance.BeginSubsegment("Send Notification"); - var snsResponse = SendNotification(petId).Result; - AWSXRayRecorder.Instance.EndSubsegment(); - - if ("bunny" == pettype) // Only call StepFunction for "bunny" pettype to reduce number of invocations - { - // Console.WriteLine($"STEPLOG- PETTYPE- {pettype}"); - // AWSXRayRecorder.Instance.BeginSubsegment("Start Step Function"); - var stepFunctionResult = StartStepFunctionExecution(petId, pettype).Result; - //Console.WriteLine($"STEPLOG - RESPONSE - {stepFunctionResult.HttpStatusCode}"); - // AWSXRayRecorder.Instance.EndSubsegment(); - } - - //Increase purchase metric count - PetAdoptionCount.Inc(); - return View("Index"); - } - catch (Exception ex) - { - ViewData["txStatus"] = "failure"; - ViewData["error"] = ex.Message; - AWSXRayRecorder.Instance.AddException(ex); - return View("Index"); - } - } - - private async Task PostTransaction(string petId, string pettype) - { - return await _httpClient.PostAsync($"{SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"paymentapiurl")}?petId={petId}&petType={pettype}", - null); - } - - private async Task PostMessageToSqs(string petId, string petType) - { - AWSSDKHandler.RegisterXRay(); - - return await _sqsClient.SendMessageAsync(new SendMessageRequest() - { - MessageBody = JsonSerializer.Serialize($"{petId}-{petType}"), - QueueUrl = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"queueurl") - }); - } - - private async Task StartStepFunctionExecution(string petId, string petType) - { - /* - - // Code to invoke StepFunction through API Gateway - var stepFunctionInputModel = new StepFunctionInputModel() - { - input = JsonSerializer.Serialize(new SearchParams() {petid = petId, pettype = petType}), - name = $"{petType}-{petId}-{Guid.NewGuid()}", - stateMachineArn = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"petadoptionsstepfnarn") - }; - - var content = new StringContent( - JsonSerializer.Serialize(stepFunctionInputModel), - Encoding.UTF8, - "application/json"); - - return await _httpClient.PostAsync(SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"petadoptionsstepfnurl"), content); - - */ - // Console.WriteLine($"STEPLOG -ARN - {SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"petadoptionsstepfnarn")}"); - //Console.WriteLine($"STEPLOG - SERIALIZE - {JsonSerializer.Serialize(new SearchParams() {petid = petId, pettype = petType})}"); - AWSSDKHandler.RegisterXRay(); - return await new AmazonStepFunctionsClient().StartExecutionAsync(new StartExecutionRequest() - { - Input = JsonSerializer.Serialize(new SearchParams() {petid = petId, pettype = petType}), - Name = $"{petType}-{petId}-{Guid.NewGuid()}", - StateMachineArn = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"petadoptionsstepfnarn") - }); - } - - private async Task SendNotification(string petId) - { - AWSSDKHandler.RegisterXRay(); - - var snsClient = new AmazonSimpleNotificationServiceClient(); - return await snsClient.PublishAsync(topicArn: _configuration["snsarn"], - message: $"PetId {petId} was adopted on {DateTime.Now}"); - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using System.Diagnostics; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Amazon.SQS; +using Amazon.SQS.Model; +using System.Text.Json.Serialization; +using System.Text.Json; +using Amazon; +using Amazon.Runtime; +using Amazon.SimpleNotificationService; +using Amazon.SimpleNotificationService.Model; +using Amazon.StepFunctions; +using Amazon.StepFunctions.Model; +using Microsoft.Extensions.Configuration; +using PetSite.Models; +using Prometheus; +using Newtonsoft; + +namespace PetSite.Controllers +{ + public class PaymentController : Controller + { + private static string _txStatus = String.Empty; + + private static HttpClient _httpClient = new HttpClient(); + private static AmazonSQSClient _sqsClient; + private static IConfiguration _configuration; + + //Prometheus metric to count the number of Pets adopted + private static readonly Counter PetAdoptionCount = + Metrics.CreateCounter("petsite_petadoptions_total", "Count the number of Pets adopted"); + + public PaymentController(IConfiguration configuration) + { + _configuration = configuration; + _sqsClient = new AmazonSQSClient(Amazon.Util.EC2InstanceMetadata.Region); + } + + // GET: Payment + [HttpGet] + private ActionResult Index() + { + return View(); + } + + // POST: Payment/MakePayment + [HttpPost] + // [ValidateAntiForgeryToken] + public async Task MakePayment(string petId, string pettype) + { + // Add custom span attributes using Activity API + var currentActivity = Activity.Current; + if (currentActivity != null) + { + currentActivity.SetTag("pet.id", petId); + currentActivity.SetTag("pet.type", pettype); + + Console.WriteLine($"Inside MakePayment Action method - PetId:{petId} - PetType:{pettype}"); + } + + ViewData["txStatus"] = "success"; + + try + { + // Create a new activity for the Payment API call + using (var activity = new Activity("Call Payment API").Start()) + { + if (activity != null) + { + activity.SetTag("pet.id", petId); + activity.SetTag("pet.type", pettype); + } + + var result = await PostTransaction(petId, pettype); + } + + // Create a new activity for SQS message + using (var activity = new Activity("Post Message to SQS").Start()) + { + if (activity != null) + { + activity.SetTag("pet.id", petId); + activity.SetTag("pet.type", pettype); + } + + var messageResponse = await PostMessageToSqs(petId, pettype); + } + + // Create a new activity for SNS notification + using (var activity = new Activity("Send Notification").Start()) + { + if (activity != null) + { + activity.SetTag("pet.id", petId); + activity.SetTag("pet.type", pettype); + } + + var snsResponse = await SendNotification(petId); + } + + if ("bunny" == pettype) // Only call StepFunction for "bunny" pettype to reduce number of invocations + { + // Create a new activity for Step Function execution + using (var activity = new Activity("Start Step Function").Start()) + { + if (activity != null) + { + activity.SetTag("pet.id", petId); + activity.SetTag("pet.type", pettype); + } + + var stepFunctionResult = await StartStepFunctionExecution(petId, pettype); + } + } + + //Increase purchase metric count + PetAdoptionCount.Inc(); + return View("Index"); + } + catch (Exception ex) + { + ViewData["txStatus"] = "failure"; + ViewData["error"] = ex.Message; + + // Log the exception + Console.WriteLine($"Error in MakePayment: {ex.Message}"); + + return View("Index"); + } + } + + private async Task PostTransaction(string petId, string pettype) + { + return await _httpClient.PostAsync($"{SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"paymentapiurl")}?petId={petId}&petType={pettype}", + null); + } + + private async Task PostMessageToSqs(string petId, string petType) + { + return await _sqsClient.SendMessageAsync(new SendMessageRequest() + { + MessageBody = JsonSerializer.Serialize($"{petId}-{petType}"), + QueueUrl = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"queueurl") + }); + } + + private async Task StartStepFunctionExecution(string petId, string petType) + { + return await new AmazonStepFunctionsClient().StartExecutionAsync(new StartExecutionRequest() + { + Input = JsonSerializer.Serialize(new SearchParams() {petid = petId, pettype = petType}), + Name = $"{petType}-{petId}-{Guid.NewGuid()}", + StateMachineArn = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"petadoptionsstepfnarn") + }); + } + + private async Task SendNotification(string petId) + { + var snsClient = new AmazonSimpleNotificationServiceClient(); + return await snsClient.PublishAsync(topicArn: _configuration["snsarn"], + message: $"PetId {petId} was adopted on {DateTime.Now}"); + } + } +} diff --git a/PetAdoptions/petsite/petsite/Controllers/PetFoodController.cs b/PetAdoptions/petsite/petsite/Controllers/PetFoodController.cs index 53c0a35f..024bbcb1 100644 --- a/PetAdoptions/petsite/petsite/Controllers/PetFoodController.cs +++ b/PetAdoptions/petsite/petsite/Controllers/PetFoodController.cs @@ -3,38 +3,48 @@ using System; using System.Net.Http; using System.Threading.Tasks; -using Amazon.XRay.Recorder.Core; -using Amazon.XRay.Recorder.Handlers.System.Net; -using Amazon.XRay.Recorder.Handlers.AwsSdk; - +using System.Diagnostics; namespace PetSite.Controllers { public class PetFoodController : Controller { - private static HttpClient httpClient; private IConfiguration _configuration; public PetFoodController(IConfiguration configuration) - { - AWSSDKHandler.RegisterXRayForAllServices(); + _configuration = configuration; + httpClient = new HttpClient(); } [HttpGet("/petfood")] public async Task Index() { - // X-Ray FTW - AWSXRayRecorder.Instance.BeginSubsegment("Calling PetFood"); - Console.WriteLine($"[{AWSXRayRecorder.Instance.GetEntity().TraceId}][{AWSXRayRecorder.Instance.TraceContext.GetEntity().RootSegment.TraceId}] - Calling PetFood"); + // Add custom span attributes using Activity API + var currentActivity = Activity.Current; + if (currentActivity != null) + { + currentActivity.SetTag("operation", "GetPetFood"); + Console.WriteLine("Calling PetFood"); + } - // Get our data from petfood - var httpClient = new HttpClient(new HttpClientXRayTracingHandler(new HttpClientHandler())); - string result = await httpClient.GetStringAsync("http://petfood"); + string result; - // Close the segment - AWSXRayRecorder.Instance.EndSubsegment(); + try + { + // Create a new activity for the API call + using (var activity = new Activity("Calling PetFood").Start()) + { + // Get our data from petfood + result = await httpClient.GetStringAsync("http://petfood"); + } + } + catch (Exception e) + { + Console.WriteLine($"Error calling PetFood: {e.Message}"); + throw; + } // Return the result! return result; @@ -43,20 +53,40 @@ public async Task Index() [HttpGet("/petfood-metric/{entityId}/{value}")] public async Task PetFoodMetric(string entityId, float value) { - // X-Ray FTW - AWSXRayRecorder.Instance.BeginSubsegment("Calling PetFood metric"); - Console.WriteLine("Calling: " + "http://petfood-metric/metric/" + entityId + "/" + value.ToString()); - Console.WriteLine($"[{AWSXRayRecorder.Instance.GetEntity().TraceId}][{AWSXRayRecorder.Instance.TraceContext.GetEntity().RootSegment.TraceId}] - Calling PetFood metric"); + // Add custom span attributes using Activity API + var currentActivity = Activity.Current; + if (currentActivity != null) + { + currentActivity.SetTag("operation", "PetFoodMetric"); + currentActivity.SetTag("entityId", entityId); + currentActivity.SetTag("value", value.ToString()); + + Console.WriteLine("Calling: " + "http://petfood-metric/metric/" + entityId + "/" + value.ToString()); + } - var httpClient = new HttpClient(new HttpClientXRayTracingHandler(new HttpClientHandler())); - string result = await httpClient.GetStringAsync("http://petfood-metric/metric/" + entityId + "/" + value.ToString()); - - AWSXRayRecorder.Instance.AddAnnotation("entityId", entityId); - AWSXRayRecorder.Instance.AddAnnotation("value", value.ToString()); - AWSXRayRecorder.Instance.EndSubsegment(); + string result; + + try + { + // Create a new activity for the API call + using (var activity = new Activity("Calling PetFood metric").Start()) + { + if (activity != null) + { + activity.SetTag("entityId", entityId); + activity.SetTag("value", value.ToString()); + } + + result = await httpClient.GetStringAsync("http://petfood-metric/metric/" + entityId + "/" + value.ToString()); + } + } + catch (Exception e) + { + Console.WriteLine($"Error calling PetFood metric: {e.Message}"); + throw; + } return result; } - } -} \ No newline at end of file +} diff --git a/PetAdoptions/petsite/petsite/Controllers/PetHistoryController.cs b/PetAdoptions/petsite/petsite/Controllers/PetHistoryController.cs index 11501dc4..1bf34ea4 100644 --- a/PetAdoptions/petsite/petsite/Controllers/PetHistoryController.cs +++ b/PetAdoptions/petsite/petsite/Controllers/PetHistoryController.cs @@ -3,9 +3,7 @@ using System; using System.Net.Http; using System.Threading.Tasks; -using Amazon.XRay.Recorder.Core; -using Amazon.XRay.Recorder.Handlers.System.Net; -using Amazon.XRay.Recorder.Handlers.AwsSdk; +using System.Diagnostics; using Microsoft.Extensions.Logging; namespace PetSite.Controllers; @@ -19,13 +17,11 @@ public class PetHistoryController : Controller public PetHistoryController(IConfiguration configuration) { - AWSSDKHandler.RegisterXRayForAllServices(); _configuration = configuration; - _httpClient = new HttpClient(new HttpClientXRayTracingHandler(new HttpClientHandler())); + _httpClient = new HttpClient(); _pethistoryurl = _configuration["pethistoryurl"]; //string _pethistoryurl = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"pethistoryurl"); - } /// @@ -35,9 +31,27 @@ public PetHistoryController(IConfiguration configuration) [HttpGet] public async Task Index() { - AWSXRayRecorder.Instance.BeginSubsegment("Calling GetPetAdoptionsHistory"); - ViewData["pethistory"] = await _httpClient.GetStringAsync($"{_pethistoryurl}/api/home/transactions"); - AWSXRayRecorder.Instance.EndSubsegment(); + // Add custom span attributes using Activity API + var currentActivity = Activity.Current; + if (currentActivity != null) + { + currentActivity.SetTag("operation", "GetPetAdoptionsHistory"); + } + + try + { + // Create a new activity for the API call + using (var activity = new Activity("Calling GetPetAdoptionsHistory").Start()) + { + ViewData["pethistory"] = await _httpClient.GetStringAsync($"{_pethistoryurl}/api/home/transactions"); + } + } + catch (Exception e) + { + Console.WriteLine($"Error calling GetPetAdoptionsHistory: {e.Message}"); + throw; + } + return View(); } @@ -48,10 +62,27 @@ public async Task Index() [HttpDelete] public async Task DeletePetAdoptionsHistory() { - AWSXRayRecorder.Instance.BeginSubsegment("Calling DeletePetAdoptionsHistory"); - ViewData["pethistory"] = await _httpClient.DeleteAsync($"{_pethistoryurl}/api/home/transactions"); - AWSXRayRecorder.Instance.EndSubsegment(); + // Add custom span attributes using Activity API + var currentActivity = Activity.Current; + if (currentActivity != null) + { + currentActivity.SetTag("operation", "DeletePetAdoptionsHistory"); + } + + try + { + // Create a new activity for the API call + using (var activity = new Activity("Calling DeletePetAdoptionsHistory").Start()) + { + ViewData["pethistory"] = await _httpClient.DeleteAsync($"{_pethistoryurl}/api/home/transactions"); + } + } + catch (Exception e) + { + Console.WriteLine($"Error calling DeletePetAdoptionsHistory: {e.Message}"); + throw; + } + return View("Index"); } - -} \ No newline at end of file +} diff --git a/PetAdoptions/petsite/petsite/Controllers/PetListAdoptionsController.cs b/PetAdoptions/petsite/petsite/Controllers/PetListAdoptionsController.cs index c7de96ed..28510494 100644 --- a/PetAdoptions/petsite/petsite/Controllers/PetListAdoptionsController.cs +++ b/PetAdoptions/petsite/petsite/Controllers/PetListAdoptionsController.cs @@ -5,10 +5,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using PetSite.Models; -using Amazon.XRay.Recorder.Handlers.AwsSdk; using System.Net.Http; -using Amazon.XRay.Recorder.Handlers.System.Net; -using Amazon.XRay.Recorder.Core; using System.Text.Json; using PetSite.ViewModels; using Microsoft.AspNetCore.Mvc.Rendering; @@ -24,42 +21,39 @@ public class PetListAdoptionsController : Controller public PetListAdoptionsController(IConfiguration configuration) { _configuration = configuration; - AWSSDKHandler.RegisterXRayForAllServices(); - - _httpClient = new HttpClient(new HttpClientXRayTracingHandler(new HttpClientHandler())); + _httpClient = new HttpClient(); } // GET public async Task Index() { - AWSXRayRecorder.Instance.BeginSubsegment("Calling PetListAdoptions"); - - Console.WriteLine($"[{AWSXRayRecorder.Instance.GetEntity().TraceId}][{AWSXRayRecorder.Instance.TraceContext.GetEntity().RootSegment.TraceId}] - Calling PetListAdoptions API"); + // Add custom span attributes using Activity API + var currentActivity = Activity.Current; + if (currentActivity != null) + { + Console.WriteLine("Calling PetListAdoptions API"); + } string result; - List Pets = new List(); try { - //string petlistadoptionsurl = _configuration["petlistadoptionsurl"]; - string petlistadoptionsurl = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"petlistadoptionsurl"); - - - result = await _httpClient.GetStringAsync($"{petlistadoptionsurl}"); - Pets = JsonSerializer.Deserialize>(result); + // Create a new activity for the API call + using (var activity = new Activity("Calling PetListAdoptions").Start()) + { + string petlistadoptionsurl = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"petlistadoptionsurl"); + result = await _httpClient.GetStringAsync($"{petlistadoptionsurl}"); + Pets = JsonSerializer.Deserialize>(result); + } } catch (Exception e) { - AWSXRayRecorder.Instance.AddException(e); - throw e; - } - finally - { - AWSXRayRecorder.Instance.EndSubsegment(); + Console.WriteLine($"Error calling PetListAdoptions API: {e.Message}"); + throw; } return View(Pets); } } -} \ No newline at end of file +} diff --git a/PetAdoptions/petsite/petsite/Dockerfile b/PetAdoptions/petsite/petsite/Dockerfile index 04a9114b..38d8051a 100644 --- a/PetAdoptions/petsite/petsite/Dockerfile +++ b/PetAdoptions/petsite/petsite/Dockerfile @@ -1,19 +1,19 @@ -FROM mcr.microsoft.com/dotnet/aspnet:7.0-bullseye-slim-amd64 AS base -WORKDIR /app -EXPOSE 80 -EXPOSE 443 - -FROM mcr.microsoft.com/dotnet/sdk:7.0-bullseye-slim-amd64 AS build -WORKDIR /src -COPY . . -RUN dotnet restore "PetSite.csproj" -RUN dotnet build "PetSite.csproj" -c Release -o /app/build - -FROM build AS publish -RUN dotnet publish "PetSite.csproj" -c Release -o /app/publish - -FROM base AS final -WORKDIR /app -#ENV AWS_XRAY_DAEMON_ADDRESS=xray-service.default -COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "PetSite.dll"] +FROM mcr.microsoft.com/dotnet/aspnet:7.0-bullseye-slim-amd64 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +FROM mcr.microsoft.com/dotnet/sdk:7.0-bullseye-slim-amd64 AS build +WORKDIR /src +COPY . . +RUN dotnet restore "PetSite.csproj" +RUN dotnet build "PetSite.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "PetSite.csproj" -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +# Removed X-Ray daemon address environment variable +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "PetSite.dll"] diff --git a/PetAdoptions/petsite/petsite/PetSite.csproj b/PetAdoptions/petsite/petsite/PetSite.csproj index 463932d2..13aae307 100644 --- a/PetAdoptions/petsite/petsite/PetSite.csproj +++ b/PetAdoptions/petsite/petsite/PetSite.csproj @@ -22,12 +22,7 @@ - - - - - all @@ -54,4 +49,4 @@ <_ContentIncludedByDefault Remove="wwwroot\fonts\Montserrat-Regular.woff" /> <_ContentIncludedByDefault Remove="wwwroot\fonts\Montserrat-Regular.woff2" /> - \ No newline at end of file + diff --git a/PetAdoptions/petsite/petsite/Program.cs b/PetAdoptions/petsite/petsite/Program.cs index e90e88e3..5cbfd893 100644 --- a/PetAdoptions/petsite/petsite/Program.cs +++ b/PetAdoptions/petsite/petsite/Program.cs @@ -1,51 +1,45 @@ -using System; -using Amazon.Extensions.NETCore.Setup; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Prometheus.DotNetRuntime; - -namespace PetSite -{ - public class Program - { - public static void Main(string[] args) - { - // Sets default settings to collect dotnet runtime specific metrics - DotNetRuntimeStatsBuilder.Default().StartCollecting(); - - //You can also set the specifics on what metrics you want to collect as below - // DotNetRuntimeStatsBuilder.Customize() - // .WithThreadPoolSchedulingStats() - // .WithContentionStats() - // .WithGcStats() - // .WithJitStats() - // .WithThreadPoolStats() - // .WithErrorHandler(ex => Console.WriteLine("ERROR: " + ex.ToString())) - // //.WithDebuggingMetrics(true); - // .StartCollecting(); - - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureAppConfiguration((hostingContext, config) => - { - var env = hostingContext.HostingEnvironment; - Console.WriteLine($"ENVIRONMENT NAME IS: {env.EnvironmentName}"); - if (env.EnvironmentName.ToLower() == "development") - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", - optional: true, reloadOnChange: true); - else - config.AddSystemsManagerWithReload(configureSource => - { - configureSource.Path = "/petstore"; - configureSource.Optional = true; - configureSource.ReloadAfter = TimeSpan.FromMinutes(5); - }); - }) - .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); - } -} \ No newline at end of file +using System; +using Amazon.Extensions.NETCore.Setup; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Prometheus.DotNetRuntime; +using System.Diagnostics; + +namespace PetSite +{ + public class Program + { + public static void Main(string[] args) + { + // Sets default settings to collect dotnet runtime specific metrics + DotNetRuntimeStatsBuilder.Default().StartCollecting(); + + // Configure Activity source for custom spans + Activity.DefaultIdFormat = ActivityIdFormat.W3C; + Activity.ForceDefaultIdFormat = true; + + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureAppConfiguration((hostingContext, config) => + { + var env = hostingContext.HostingEnvironment; + Console.WriteLine($"ENVIRONMENT NAME IS: {env.EnvironmentName}"); + if (env.EnvironmentName.ToLower() == "development") + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", + optional: true, reloadOnChange: true); + else + config.AddSystemsManagerWithReload(configureSource => + { + configureSource.Path = "/petstore"; + configureSource.Optional = true; + configureSource.ReloadAfter = TimeSpan.FromMinutes(5); + }); + }) + .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); + } +} diff --git a/PetAdoptions/petsite/petsite/Startup.cs b/PetAdoptions/petsite/petsite/Startup.cs index 9c439d93..53f979b6 100644 --- a/PetAdoptions/petsite/petsite/Startup.cs +++ b/PetAdoptions/petsite/petsite/Startup.cs @@ -1,65 +1,65 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.HttpsPolicy; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Prometheus; - -namespace PetSite -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - new ConfigurationBuilder() - .AddEnvironmentVariables() - .Build(); - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddControllersWithViews(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - app.UseXRay("PetSite", Configuration); - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Home/Error"); - app.UseHsts(); - } - - app.UseHttpsRedirection(); - app.UseStaticFiles(); - - app.UseRouting(); - app.UseHttpMetrics(); - - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllerRoute( - name: "default", - pattern: "{controller=Home}/{action=Index}/{id?}"); - endpoints.MapMetrics(); - }); - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Prometheus; + +namespace PetSite +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + new ConfigurationBuilder() + .AddEnvironmentVariables() + .Build(); + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllersWithViews(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + // Removed X-Ray middleware + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseExceptionHandler("/Home/Error"); + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseStaticFiles(); + + app.UseRouting(); + app.UseHttpMetrics(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}"); + endpoints.MapMetrics(); + }); + } + } +} From a949fc7f62bc6bdb669fc7d5d105f8ccbed84b91 Mon Sep 17 00:00:00 2001 From: Imaya Kumar Jagannathan Date: Fri, 8 Aug 2025 11:23:58 -0400 Subject: [PATCH 03/19] .NET Version, new page design, favicon etc --- .gitignore | 1 + .../petsite/Controllers/AdoptionController.cs | 58 +- .../petsite/Controllers/BaseController.cs | 43 + .../petsite/Controllers/HomeController.cs | 80 +- .../petsite/Controllers/PaymentController.cs | 126 +- .../Controllers/PetHistoryController.cs | 15 +- .../Controllers/PetListAdoptionsController.cs | 19 +- PetAdoptions/petsite/petsite/Dockerfile | 4 +- PetAdoptions/petsite/petsite/PetSite.csproj | 21 +- .../petsite/Services/PetSearchService.cs | 92 ++ PetAdoptions/petsite/petsite/Startup.cs | 12 +- ...sManagerConfigurationProviderWithReload.cs | 39 +- .../petsite/Views/Adoption/Index.cshtml | 109 +- .../petsite/Views/Home/HouseKeeping.cshtml | 3 +- .../petsite/petsite/Views/Home/Index.cshtml | 35 +- .../petsite/Views/Payment/Index.cshtml | 73 +- .../petsite/Views/Shared/_AdoptionItem.cshtml | 33 +- .../petsite/Views/Shared/_Layout.cshtml | 34 +- .../petsite/Views/Shared/_PetItem.cshtml | 7 +- .../petsite/appsettings.Development.json | 10 +- PetAdoptions/petsite/petsite/appsettings.json | 4 +- .../petsite/petsite/wwwroot/favicon.ico | Bin 32038 -> 15406 bytes .../wwwroot/images/main_banner_text.ai | 1454 +++++++++++++++++ .../wwwroot/images/main_banner_text.png | Bin 9302 -> 26093 bytes .../lib/bootstrap/dist/css/bootstrap.min.css | 11 +- .../bootstrap/dist/js/bootstrap.bundle.min.js | 8 +- 26 files changed, 1937 insertions(+), 354 deletions(-) create mode 100644 PetAdoptions/petsite/petsite/Controllers/BaseController.cs create mode 100644 PetAdoptions/petsite/petsite/Services/PetSearchService.cs create mode 100644 PetAdoptions/petsite/petsite/wwwroot/images/main_banner_text.ai diff --git a/.gitignore b/.gitignore index 0f9e7cc9..cb2e6bed 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ PetAdoptions/payforadoption-go/petadoptions # editor settings .vscode/settings.json +PetAdoptions/petsite/petsite/appsettings.Development.json diff --git a/PetAdoptions/petsite/petsite/Controllers/AdoptionController.cs b/PetAdoptions/petsite/petsite/Controllers/AdoptionController.cs index 6acdf7b9..60d82ba5 100644 --- a/PetAdoptions/petsite/petsite/Controllers/AdoptionController.cs +++ b/PetAdoptions/petsite/petsite/Controllers/AdoptionController.cs @@ -8,47 +8,47 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + using PetSite.ViewModels; + namespace PetSite.Controllers { - public class AdoptionController : Controller + public class AdoptionController : BaseController { - private static readonly HttpClient HttpClient = new HttpClient(); + private readonly PetSite.Services.IPetSearchService _petSearchService; private static Variety _variety = new Variety(); - private static IConfiguration _configuration; + private readonly ILogger _logger; - private static string _searchApiurl; - - public AdoptionController(IConfiguration configuration) + public AdoptionController(ILogger logger, PetSite.Services.IPetSearchService petSearchService) { - _configuration = configuration; - - //_searchApiurl = _configuration["searchapiurl"]; - _searchApiurl = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"searchapiurl"); + _petSearchService = petSearchService; + _logger = logger; } // GET: Adoption [HttpGet] public IActionResult Index([FromQuery] Pet pet) { + if (EnsureUserId()) return new EmptyResult(); // Redirect happened, stop processing + + // Check if pet data exists in TempData (from TakeMeHome redirect) + if (TempData["SelectedPet"] != null) + { + var petJson = TempData["SelectedPet"].ToString(); + pet = JsonSerializer.Deserialize(petJson); + } + return View(pet); } - private async Task GetPetDetails(SearchParams searchParams) - { - string searchString = string.Empty; - if (!String.IsNullOrEmpty(searchParams.pettype) && searchParams.pettype != "all") searchString = $"pettype={searchParams.pettype}"; - if (!String.IsNullOrEmpty(searchParams.petcolor) && searchParams.petcolor != "all") searchString = $"&{searchString}&petcolor={searchParams.petcolor}"; - if (!String.IsNullOrEmpty(searchParams.petid) && searchParams.petid != "all") searchString = $"&{searchString}&petid={searchParams.petid}"; - - return await HttpClient.GetStringAsync($"{_searchApiurl}{searchString}"); - } [HttpPost] public async Task TakeMeHome([FromForm] SearchParams searchParams) { + EnsureUserId(); // Add custom span attributes using Activity API (compatible with Application Signals auto-instrumentation) var currentActivity = Activity.Current; if (currentActivity != null) @@ -57,15 +57,16 @@ public async Task TakeMeHome([FromForm] SearchParams searchParams currentActivity.SetTag("pet.type", searchParams.pettype); currentActivity.SetTag("pet.color", searchParams.petcolor); - Console.WriteLine($"Processing adoption request - PetId:{searchParams.petid}, PetType:{searchParams.pettype}, PetColor:{searchParams.petcolor}"); + _logger.LogInformation($"Processing adoption request - PetId:{searchParams.petid}, PetType:{searchParams.pettype}, PetColor:{searchParams.petcolor}"); + } - string result; + List pets; try { // Create a new activity for the API call - using (var activity = new Activity("Calling Search API").Start()) + using (var activity = new Activity("Calling PetSearch API").Start()) { if (activity != null) { @@ -74,17 +75,22 @@ public async Task TakeMeHome([FromForm] SearchParams searchParams activity.SetTag("pet.color", searchParams.petcolor); } - result = await GetPetDetails(searchParams); + pets = await _petSearchService.GetPetDetails(searchParams.pettype, searchParams.petcolor, searchParams.petid); } } catch (Exception e) { // Log the exception - Console.WriteLine($"Error calling search API: {e.Message}"); - throw; + _logger.LogError(e, "Error calling PetSearch API"); + pets = new List(); } - return View("Index", JsonSerializer.Deserialize>(result).FirstOrDefault()); + var selectedPet = pets.FirstOrDefault(); + if (selectedPet != null) + { + TempData["SelectedPet"] = JsonSerializer.Serialize(selectedPet); + } + return RedirectToAction("Index", new { userid = ViewBag.UserId }); } } } diff --git a/PetAdoptions/petsite/petsite/Controllers/BaseController.cs b/PetAdoptions/petsite/petsite/Controllers/BaseController.cs new file mode 100644 index 00000000..8204f37d --- /dev/null +++ b/PetAdoptions/petsite/petsite/Controllers/BaseController.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; + +namespace PetSite.Controllers +{ + public class BaseController : Controller + { + private static readonly List UserIds = new List + { + "user001", "user002", "user003", "user004", "user005", + "user006", "user007", "user008", "user009", "user010", + "user011", "user012", "user013", "user014", "user015", + "user016", "user017", "user018", "user019", "user020", + "user021", "user022", "user023", "user024", "user025" + }; + private static readonly Random Random = new Random(); + + protected bool EnsureUserId() + { + string userid = Request.Query["userid"]; + + if (string.IsNullOrEmpty(userid)) + { + userid = HttpContext.Session.GetString("userid"); + if (string.IsNullOrEmpty(userid)) + { + userid = UserIds[Random.Next(UserIds.Count)]; + HttpContext.Session.SetString("userid", userid); + } + + var queryString = Request.QueryString.HasValue ? Request.QueryString.Value + "&userid=" + userid : "?userid=" + userid; + Response.Redirect(Request.Path + queryString); + return true; // Indicates redirect happened + } + + HttpContext.Session.SetString("userid", userid); + ViewBag.UserId = userid; + return false; // No redirect needed + } + } +} \ No newline at end of file diff --git a/PetAdoptions/petsite/petsite/Controllers/HomeController.cs b/PetAdoptions/petsite/petsite/Controllers/HomeController.cs index dccb9ea4..fea3f555 100644 --- a/PetAdoptions/petsite/petsite/Controllers/HomeController.cs +++ b/PetAdoptions/petsite/petsite/Controllers/HomeController.cs @@ -11,17 +11,18 @@ using PetSite.ViewModels; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.Extensions.Configuration; +using Microsoft.AspNetCore.Http; using Prometheus; namespace PetSite.Controllers { - public class HomeController : Controller + public class HomeController : BaseController { private readonly ILogger _logger; - private static HttpClient _httpClient; + private readonly PetSite.Services.IPetSearchService _petSearchService; + private readonly IHttpClientFactory _httpClientFactory; private static Variety _variety = new Variety(); - - private IConfiguration _configuration; + private readonly IConfiguration _configuration; //Prometheus metric to count the number of searches performed private static readonly Counter PetSearchCount = @@ -42,10 +43,13 @@ public class HomeController : Controller private static readonly Gauge PetsWaitingForAdoption = Metrics .CreateGauge("petsite_pets_waiting_for_adoption", "Number of pets waiting for adoption."); - public HomeController(ILogger logger, IConfiguration configuration) + + + public HomeController(ILogger logger, IConfiguration configuration, PetSite.Services.IPetSearchService petSearchService, IHttpClientFactory httpClientFactory) { _configuration = configuration; - _httpClient = new HttpClient(); + _petSearchService = petSearchService; + _httpClientFactory = httpClientFactory; _logger = logger; _variety.PetTypes = new List() @@ -65,42 +69,18 @@ public HomeController(ILogger logger, IConfiguration configurati }; } - private async Task GetPetDetails(string pettype, string petcolor, string petid) - { - string searchUri = string.Empty; - if (!String.IsNullOrEmpty(pettype) && pettype != "all") searchUri = $"pettype={pettype}"; - if (!String.IsNullOrEmpty(petcolor) && petcolor != "all") searchUri = $"&{searchUri}&petcolor={petcolor}"; - if (!String.IsNullOrEmpty(petid) && petid != "all") searchUri = $"&{searchUri}&petid={petid}"; - - switch (pettype) - { - case "puppy": - PuppySearchCount.Inc(); - PetSearchCount.Inc(); - break; - case "kitten": - KittenSearchCount.Inc(); - PetSearchCount.Inc(); - break; - case "bunny": - BunnySearchCount.Inc(); - PetSearchCount.Inc(); - break; - } - - string searchapiurl = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"searchapiurl"); - return await _httpClient.GetStringAsync($"{searchapiurl}{searchUri}"); - } [HttpGet("housekeeping")] public async Task HouseKeeping() { - Console.WriteLine("In Housekeeping, trying to reset the app."); + EnsureUserId(); + _logger.LogInformation("In Housekeeping, trying to reset the app."); - string cleanupadoptionsurl = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"cleanupadoptionsurl"); + string cleanupadoptionsurl = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"CLEANUP_ADOPTIONS_URL"); - await _httpClient.PostAsync(cleanupadoptionsurl, null); + using var httpClient = _httpClientFactory.CreateClient(); + await httpClient.PostAsync(cleanupadoptionsurl, null); return View(); } @@ -108,6 +88,7 @@ public async Task HouseKeeping() [HttpGet] public async Task Index(string selectedPetType, string selectedPetColor, string petid) { + EnsureUserId(); // Add custom span attributes using Activity API var currentActivity = Activity.Current; if (currentActivity != null) @@ -116,15 +97,15 @@ public async Task Index(string selectedPetType, string selectedPe currentActivity.SetTag("pet.color", selectedPetColor); currentActivity.SetTag("pet.id", petid); - Console.WriteLine($"Search string - PetType:{selectedPetType} PetColor:{selectedPetColor} PetId:{petid}"); + _logger.LogInformation($"Search string - PetType:{selectedPetType} PetColor:{selectedPetColor} PetId:{petid}"); } - string result; + List Pets; try { // Create a new activity for the API call - using (var activity = new Activity("Calling Search API").Start()) + using (var activity = new Activity("Calling PetSearch API").Start()) { if (activity != null) { @@ -133,17 +114,28 @@ public async Task Index(string selectedPetType, string selectedPe activity.SetTag("pet.id", petid); } - result = await GetPetDetails(selectedPetType, selectedPetColor, petid); + Pets = await _petSearchService.GetPetDetails(selectedPetType, selectedPetColor, petid); } } + catch (HttpRequestException e) + { + _logger.LogError(e, "HTTP error received after calling PetSearch API"); + ViewBag.ErrorMessage = $"Unable to search pets at this time. Please try again later. \nError message received - {e.Message}"; + Pets = new List(); + } + catch (TaskCanceledException e) + { + _logger.LogError(e, "Timeout calling PetSearch API"); + ViewBag.ErrorMessage = "Search request timed out. Please try again."; + Pets = new List(); + } catch (Exception e) { - Console.WriteLine($"Error calling search API: {e.Message}"); - throw; + _logger.LogError(e, "Unexpected error calling PetSearch API"); + ViewBag.ErrorMessage = "An unexpected error occurred. Please try again."; + Pets = new List(); } - var Pets = JsonSerializer.Deserialize>(result); - var PetDetails = new PetDetails() { Pets = Pets, @@ -156,7 +148,7 @@ public async Task Index(string selectedPetType, string selectedPe } }; - Console.WriteLine($"{JsonSerializer.Serialize(PetDetails)}"); + _logger.LogInformation("Search completed with {PetCount} pets found", Pets.Count); // Sets the metric value to the number of pets available for adoption at the moment PetsWaitingForAdoption.Set(Pets.Where(pet => pet.availability == "yes").Count()); diff --git a/PetAdoptions/petsite/petsite/Controllers/PaymentController.cs b/PetAdoptions/petsite/petsite/Controllers/PaymentController.cs index b10799be..7041b31f 100644 --- a/PetAdoptions/petsite/petsite/Controllers/PaymentController.cs +++ b/PetAdoptions/petsite/petsite/Controllers/PaymentController.cs @@ -1,51 +1,48 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Net.Http; -using System.Text; using System.Threading.Tasks; using System.Diagnostics; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using Amazon.SQS; -using Amazon.SQS.Model; -using System.Text.Json.Serialization; -using System.Text.Json; -using Amazon; -using Amazon.Runtime; -using Amazon.SimpleNotificationService; -using Amazon.SimpleNotificationService.Model; -using Amazon.StepFunctions; -using Amazon.StepFunctions.Model; using Microsoft.Extensions.Configuration; -using PetSite.Models; using Prometheus; -using Newtonsoft; namespace PetSite.Controllers { - public class PaymentController : Controller + public class PaymentController : BaseController { private static string _txStatus = String.Empty; + + private readonly ILogger _logger; - private static HttpClient _httpClient = new HttpClient(); - private static AmazonSQSClient _sqsClient; - private static IConfiguration _configuration; + private readonly IHttpClientFactory _httpClientFactory; + private readonly IConfiguration _configuration; //Prometheus metric to count the number of Pets adopted private static readonly Counter PetAdoptionCount = Metrics.CreateCounter("petsite_petadoptions_total", "Count the number of Pets adopted"); - public PaymentController(IConfiguration configuration) + public PaymentController(ILogger logger, IConfiguration configuration, IHttpClientFactory httpClientFactory) { _configuration = configuration; - _sqsClient = new AmazonSQSClient(Amazon.Util.EC2InstanceMetadata.Region); + _httpClientFactory = httpClientFactory; + _logger = logger; } // GET: Payment [HttpGet] - private ActionResult Index() + public ActionResult Index() { + if (EnsureUserId()) return new EmptyResult(); + + // Transfer TempData to ViewData for the view + if (TempData["txStatus"] != null) + { + ViewData["txStatus"] = TempData["txStatus"]; + ViewData["error"] = TempData["error"]; + } + return View(); } @@ -54,6 +51,7 @@ private ActionResult Index() // [ValidateAntiForgeryToken] public async Task MakePayment(string petId, string pettype) { + EnsureUserId(); // Add custom span attributes using Activity API var currentActivity = Activity.Current; if (currentActivity != null) @@ -61,11 +59,9 @@ public async Task MakePayment(string petId, string pettype) currentActivity.SetTag("pet.id", petId); currentActivity.SetTag("pet.type", pettype); - Console.WriteLine($"Inside MakePayment Action method - PetId:{petId} - PetType:{pettype}"); + _logger.LogInformation($"Inside MakePayment Action method - PetId:{petId} - PetType:{pettype}"); } - ViewData["txStatus"] = "success"; - try { // Create a new activity for the Payment API call @@ -80,91 +76,29 @@ public async Task MakePayment(string petId, string pettype) var result = await PostTransaction(petId, pettype); } - // Create a new activity for SQS message - using (var activity = new Activity("Post Message to SQS").Start()) - { - if (activity != null) - { - activity.SetTag("pet.id", petId); - activity.SetTag("pet.type", pettype); - } - - var messageResponse = await PostMessageToSqs(petId, pettype); - } - - // Create a new activity for SNS notification - using (var activity = new Activity("Send Notification").Start()) - { - if (activity != null) - { - activity.SetTag("pet.id", petId); - activity.SetTag("pet.type", pettype); - } - - var snsResponse = await SendNotification(petId); - } - - if ("bunny" == pettype) // Only call StepFunction for "bunny" pettype to reduce number of invocations - { - // Create a new activity for Step Function execution - using (var activity = new Activity("Start Step Function").Start()) - { - if (activity != null) - { - activity.SetTag("pet.id", petId); - activity.SetTag("pet.type", pettype); - } - - var stepFunctionResult = await StartStepFunctionExecution(petId, pettype); - } - } - //Increase purchase metric count PetAdoptionCount.Inc(); - return View("Index"); + TempData["txStatus"] = "success"; + return RedirectToAction("Index", new { userid = ViewBag.UserId }); } catch (Exception ex) { - ViewData["txStatus"] = "failure"; - ViewData["error"] = ex.Message; + TempData["txStatus"] = "failure"; + TempData["error"] = ex.Message; // Log the exception - Console.WriteLine($"Error in MakePayment: {ex.Message}"); + _logger.LogError(ex, $"Error in MakePayment: {ex.Message}"); - return View("Index"); + return RedirectToAction("Index", new { userid = ViewBag.UserId }); } } private async Task PostTransaction(string petId, string pettype) { - return await _httpClient.PostAsync($"{SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"paymentapiurl")}?petId={petId}&petType={pettype}", + using var httpClient = _httpClientFactory.CreateClient(); + return await httpClient.PostAsync($"{SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"PAYMENT_API_URL")}?petId={petId}&petType={pettype}", null); } - - private async Task PostMessageToSqs(string petId, string petType) - { - return await _sqsClient.SendMessageAsync(new SendMessageRequest() - { - MessageBody = JsonSerializer.Serialize($"{petId}-{petType}"), - QueueUrl = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"queueurl") - }); - } - - private async Task StartStepFunctionExecution(string petId, string petType) - { - return await new AmazonStepFunctionsClient().StartExecutionAsync(new StartExecutionRequest() - { - Input = JsonSerializer.Serialize(new SearchParams() {petid = petId, pettype = petType}), - Name = $"{petType}-{petId}-{Guid.NewGuid()}", - StateMachineArn = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"petadoptionsstepfnarn") - }); - } - - private async Task SendNotification(string petId) - { - var snsClient = new AmazonSimpleNotificationServiceClient(); - return await snsClient.PublishAsync(topicArn: _configuration["snsarn"], - message: $"PetId {petId} was adopted on {DateTime.Now}"); - } + } } diff --git a/PetAdoptions/petsite/petsite/Controllers/PetHistoryController.cs b/PetAdoptions/petsite/petsite/Controllers/PetHistoryController.cs index 1bf34ea4..f0c26385 100644 --- a/PetAdoptions/petsite/petsite/Controllers/PetHistoryController.cs +++ b/PetAdoptions/petsite/petsite/Controllers/PetHistoryController.cs @@ -10,15 +10,14 @@ namespace PetSite.Controllers; public class PetHistoryController : Controller { - private IConfiguration _configuration; - private readonly ILogger _logger; - private static HttpClient _httpClient; + private readonly IConfiguration _configuration; + private readonly IHttpClientFactory _httpClientFactory; private static string _pethistoryurl; - public PetHistoryController(IConfiguration configuration) + public PetHistoryController(IConfiguration configuration, IHttpClientFactory httpClientFactory) { _configuration = configuration; - _httpClient = new HttpClient(); + _httpClientFactory = httpClientFactory; _pethistoryurl = _configuration["pethistoryurl"]; //string _pethistoryurl = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"pethistoryurl"); @@ -43,7 +42,8 @@ public async Task Index() // Create a new activity for the API call using (var activity = new Activity("Calling GetPetAdoptionsHistory").Start()) { - ViewData["pethistory"] = await _httpClient.GetStringAsync($"{_pethistoryurl}/api/home/transactions"); + using var httpClient = _httpClientFactory.CreateClient(); + ViewData["pethistory"] = await httpClient.GetStringAsync($"{_pethistoryurl}/api/home/transactions"); } } catch (Exception e) @@ -74,7 +74,8 @@ public async Task DeletePetAdoptionsHistory() // Create a new activity for the API call using (var activity = new Activity("Calling DeletePetAdoptionsHistory").Start()) { - ViewData["pethistory"] = await _httpClient.DeleteAsync($"{_pethistoryurl}/api/home/transactions"); + using var httpClient = _httpClientFactory.CreateClient(); + ViewData["pethistory"] = await httpClient.DeleteAsync($"{_pethistoryurl}/api/home/transactions"); } } catch (Exception e) diff --git a/PetAdoptions/petsite/petsite/Controllers/PetListAdoptionsController.cs b/PetAdoptions/petsite/petsite/Controllers/PetListAdoptionsController.cs index 28510494..a8b13b0f 100644 --- a/PetAdoptions/petsite/petsite/Controllers/PetListAdoptionsController.cs +++ b/PetAdoptions/petsite/petsite/Controllers/PetListAdoptionsController.cs @@ -15,13 +15,15 @@ namespace PetSite.Controllers { public class PetListAdoptionsController : Controller { - private static HttpClient _httpClient; - private IConfiguration _configuration; + private readonly IHttpClientFactory _httpClientFactory; + private readonly IConfiguration _configuration; + private readonly ILogger _logger; - public PetListAdoptionsController(IConfiguration configuration) + public PetListAdoptionsController(ILogger logger, IConfiguration configuration, IHttpClientFactory httpClientFactory) { _configuration = configuration; - _httpClient = new HttpClient(); + _httpClientFactory = httpClientFactory; + _logger= logger; } // GET @@ -31,7 +33,7 @@ public async Task Index() var currentActivity = Activity.Current; if (currentActivity != null) { - Console.WriteLine("Calling PetListAdoptions API"); + _logger.LogInformation("Calling PetListAdoptions API"); } string result; @@ -42,14 +44,15 @@ public async Task Index() // Create a new activity for the API call using (var activity = new Activity("Calling PetListAdoptions").Start()) { - string petlistadoptionsurl = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"petlistadoptionsurl"); - result = await _httpClient.GetStringAsync($"{petlistadoptionsurl}"); + string petlistadoptionsurl = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"PET_LIST_ADOPTION_URL"); + using var httpClient = _httpClientFactory.CreateClient(); + result = await httpClient.GetStringAsync($"{petlistadoptionsurl}"); Pets = JsonSerializer.Deserialize>(result); } } catch (Exception e) { - Console.WriteLine($"Error calling PetListAdoptions API: {e.Message}"); + _logger.LogError(e, $"Error calling PetListAdoptions API: {e.Message}"); throw; } diff --git a/PetAdoptions/petsite/petsite/Dockerfile b/PetAdoptions/petsite/petsite/Dockerfile index 38d8051a..24d95639 100644 --- a/PetAdoptions/petsite/petsite/Dockerfile +++ b/PetAdoptions/petsite/petsite/Dockerfile @@ -1,9 +1,9 @@ -FROM mcr.microsoft.com/dotnet/aspnet:7.0-bullseye-slim-amd64 AS base +FROM mcr.microsoft.com/dotnet/aspnet:8.0-bullseye-slim-amd64 AS base WORKDIR /app EXPOSE 80 EXPOSE 443 -FROM mcr.microsoft.com/dotnet/sdk:7.0-bullseye-slim-amd64 AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0-bullseye-slim-amd64 AS build WORKDIR /src COPY . . RUN dotnet restore "PetSite.csproj" diff --git a/PetAdoptions/petsite/petsite/PetSite.csproj b/PetAdoptions/petsite/petsite/PetSite.csproj index 13aae307..c488005d 100644 --- a/PetAdoptions/petsite/petsite/PetSite.csproj +++ b/PetAdoptions/petsite/petsite/PetSite.csproj @@ -1,6 +1,6 @@ - net7.0 + net8.0 a80ee246-1735-4630-bd6a-0fd3d01d8e35 Linux @@ -16,20 +16,21 @@ - + - - - - - + + + + + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/PetAdoptions/petsite/petsite/Services/PetSearchService.cs b/PetAdoptions/petsite/petsite/Services/PetSearchService.cs new file mode 100644 index 00000000..ea9b18c0 --- /dev/null +++ b/PetAdoptions/petsite/petsite/Services/PetSearchService.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using PetSite.Models; +using PetSite.ViewModels; +using Prometheus; + +namespace PetSite.Services +{ + public interface IPetSearchService + { + Task> GetPetDetails(string pettype, string petcolor, string petid); + } + + public class PetSearchService : IPetSearchService + { + private readonly IHttpClientFactory _httpClientFactory; + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + + //Prometheus metrics + private static readonly Counter PetSearchCount = + Metrics.CreateCounter("petsite_petsearches_total", "Count the number of searches performed"); + private static readonly Counter PuppySearchCount = + Metrics.CreateCounter("petsite_pet_puppy_searches_total", "Count the number of puppy searches performed"); + private static readonly Counter KittenSearchCount = + Metrics.CreateCounter("petsite_pet_kitten_searches_total", "Count the number of kitten searches performed"); + private static readonly Counter BunnySearchCount = + Metrics.CreateCounter("petsite_pet_bunny_searches_total", "Count the number of bunny searches performed"); + + public PetSearchService(IHttpClientFactory httpClientFactory, IConfiguration configuration, ILogger logger) + { + _httpClientFactory = httpClientFactory; + _configuration = configuration; + _logger = logger; + } + + public async Task> GetPetDetails(string pettype, string petcolor, string petid) + { + string searchUri = string.Empty; + + if (!String.IsNullOrEmpty(pettype) && pettype != "all") searchUri = $"pettype={pettype}"; + if (!String.IsNullOrEmpty(petcolor) && petcolor != "all") searchUri = $"&{searchUri}&petcolor={petcolor}"; + if (!String.IsNullOrEmpty(petid) && petid != "all") searchUri = $"&{searchUri}&petid={petid}"; + + switch (pettype) + { + case "puppy": + PuppySearchCount.Inc(); + PetSearchCount.Inc(); + break; + case "kitten": + KittenSearchCount.Inc(); + PetSearchCount.Inc(); + break; + case "bunny": + BunnySearchCount.Inc(); + PetSearchCount.Inc(); + break; + } + + string searchapiurl = SystemsManagerConfigurationProviderWithReloadExtensions.GetConfiguration(_configuration,"SEARCH_API_URL"); + using var httpClient = _httpClientFactory.CreateClient(); + httpClient.Timeout = TimeSpan.FromSeconds(30); + + try + { + var response = await httpClient.GetAsync($"{searchapiurl}{searchUri}"); + if (!response.IsSuccessStatusCode) + { + var responseContent = await response.Content.ReadAsStringAsync(); + throw new HttpRequestException($"HTTP {(int)response.StatusCode} {response.StatusCode}: {response.ReasonPhrase}. Response: {responseContent}"); + } + + var jsonContent = await response.Content.ReadAsStringAsync(); + if (string.IsNullOrEmpty(jsonContent)) + return new List(); + + return JsonSerializer.Deserialize>(jsonContent) ?? new List(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Exception occurred while fetching pet details."); + throw ex; + } + } + } +} \ No newline at end of file diff --git a/PetAdoptions/petsite/petsite/Startup.cs b/PetAdoptions/petsite/petsite/Startup.cs index 53f979b6..24ed5b4c 100644 --- a/PetAdoptions/petsite/petsite/Startup.cs +++ b/PetAdoptions/petsite/petsite/Startup.cs @@ -8,6 +8,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Amazon.Extensions.NETCore.Setup; +using Amazon; using Prometheus; namespace PetSite @@ -28,6 +30,13 @@ public Startup(IConfiguration configuration) public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); + services.AddHttpClient(); + services.AddSession(); + services.AddScoped(); + + // Configure AWS Services + services.AddAWSService(); + services.AddDefaultAWSOptions(Configuration.GetAWSOptions()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -49,8 +58,9 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseStaticFiles(); app.UseRouting(); + app.UseSession(); app.UseHttpMetrics(); - + app.UseAuthorization(); app.UseEndpoints(endpoints => diff --git a/PetAdoptions/petsite/petsite/SystemsManagerConfigurationProviderWithReload.cs b/PetAdoptions/petsite/petsite/SystemsManagerConfigurationProviderWithReload.cs index cd27aa00..d228e89c 100644 --- a/PetAdoptions/petsite/petsite/SystemsManagerConfigurationProviderWithReload.cs +++ b/PetAdoptions/petsite/petsite/SystemsManagerConfigurationProviderWithReload.cs @@ -79,35 +79,20 @@ private void ReloadIfNeeded(bool forceReload = false) } } - /*Amazon.Extensions.Configuration.SystemsManager doesn't support AssumeRoleWithWebIdentity see issue here. As a temporary solution, environment variables where provided to override configurations read from Parameter store as those were empty. Long term solution needs to update class SystemsManagerConfigurationProviderWithReloadExtensions - using a different base class or wait for the issue to be solved. - The workaround is to provide a way to inject the ParameterValues as environment variables*/ - - private static Dictionary ConfigurationMapping = new Dictionary { - { "searchapiurl", "SEARCH_API_URL"}, - { "updateadoptionstatusurl", "UPDATE_ADOPTION_STATUS_URL"}, - { "cleanupadoptionsurl", "CLEANUP_ADOPTIONS_URL"}, - { "paymentapiurl", "PAYMENT_API_URL"}, - { "queueurl", "QUEUE_URL"}, - { "snsarn", "SNS_ARN"}, - { "petlistadoptionsurl", "PET_LIST_ADOPTION_URL"}, - { "petadoptionsstepfnarn", "PET_ADOPTION_STEPFN_URL"} - }; - - public static string GetConfiguration(IConfiguration _configuration, string value) + // Optimized configuration retrieval - directly use IConfiguration instead of environment variables + public static string GetConfiguration(IConfiguration configuration, string key) { - string retVal = _configuration[value]; + // Try direct configuration lookup first (supports appsettings.json values) + var value = configuration[key.ToUpperInvariant()]; + if (!string.IsNullOrEmpty(value)) + return value; - string envVar = ConfigurationMapping[value]; - if (!string.IsNullOrEmpty(envVar)) - { - if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable(envVar))) - { - retVal = Environment.GetEnvironmentVariable(envVar); - } - } - return retVal; - + // Fallback to environment variable with standard naming convention + var envValue = Environment.GetEnvironmentVariable(key.ToUpperInvariant()); + if (!string.IsNullOrEmpty(envValue)) + return envValue; + + throw new InvalidOperationException($"Configuration key '{key}' not found in configuration or environment variables."); } } } \ No newline at end of file diff --git a/PetAdoptions/petsite/petsite/Views/Adoption/Index.cshtml b/PetAdoptions/petsite/petsite/Views/Adoption/Index.cshtml index d51f4af5..f902b26f 100644 --- a/PetAdoptions/petsite/petsite/Views/Adoption/Index.cshtml +++ b/PetAdoptions/petsite/petsite/Views/Adoption/Index.cshtml @@ -44,7 +44,7 @@ var jqxhr = $.getJSON( "/petfood", function() {
- +
@Model.pettype-@Model.petcolor
@@ -54,7 +54,7 @@ var jqxhr = $.getJSON( "/petfood", function() {
@for (int i = 0; i < Int32.Parse(Model.cuteness_rate); i++) { - + }
@@ -66,77 +66,56 @@ var jqxhr = $.getJSON( "/petfood", function() {
-
-
-
- Information entered on this page is neither stored nor processed. -
- -
-
-
-
-
-
-

- Payment Details -

-
-
-
-
- -
- - + +
+
+
+
+
+ +

Payment Details

+
+ This is a demo - no real payment will be processed +
+ +
+ + +
+ +
+ + +
+ +
+
+
+ +
-
-
-
- -
- -
-
- -
-
+
+
+ +
-
-
- - -
+
+
+
+ + + + +
- +
- -
-
- - - -
+ + diff --git a/PetAdoptions/petsite/petsite/Views/Home/HouseKeeping.cshtml b/PetAdoptions/petsite/petsite/Views/Home/HouseKeeping.cshtml index 02e50ac0..353d59d0 100644 --- a/PetAdoptions/petsite/petsite/Views/Home/HouseKeeping.cshtml +++ b/PetAdoptions/petsite/petsite/Views/Home/HouseKeeping.cshtml @@ -8,7 +8,8 @@
-
+
+