UNPKG

@cloud-carbon-footprint/aws

Version:

The core logic to get cloud usage data and estimate energy and carbon emissions from Amazon Web Services.

187 lines (156 loc) 5.97 kB
/* * © 2021 Thoughtworks, Inc. */ import { mockClient } from 'aws-sdk-client-mock' import path from 'path' import moment from 'moment' import * as fs from 'fs' import { ComputeEstimator, MemoryEstimator, StorageEstimator, } from '@cloud-carbon-footprint/core' import { AWS_RECOMMENDATIONS_SERVICES, configLoader, RecommendationResult, } from '@cloud-carbon-footprint/common' import { AWS_CLOUD_CONSTANTS } from '../domain' import { ServiceWrapper, ComputeOptimizerRecommendations } from '../lib' import { mockEBSComputeOptimizerBucketList, mockEC2ComputeOptimizerBucketList, mockLambdaComputeOptimizerBucketList, } from './fixtures/computeOptimizer.fixtures' import { GetObjectCommand, ListObjectsV2Command, S3Client, } from '@aws-sdk/client-s3' import { CostExplorerClient } from '@aws-sdk/client-cost-explorer' import { CloudWatchLogsClient } from '@aws-sdk/client-cloudwatch-logs' import { CloudWatchClient } from '@aws-sdk/client-cloudwatch' const s3Mock = mockClient(S3Client) describe('AWS Compute Optimizer Recommendations Service', () => { const getServiceWrapper = () => new ServiceWrapper( new CloudWatchClient(), new CloudWatchLogsClient(), new CostExplorerClient(), new S3Client(), ) const getRecommendationsService = () => new ComputeOptimizerRecommendations( new ComputeEstimator(), new MemoryEstimator(AWS_CLOUD_CONSTANTS.MEMORY_COEFFICIENT), new StorageEstimator(AWS_CLOUD_CONSTANTS.SSDCOEFFICIENT), new StorageEstimator(AWS_CLOUD_CONSTANTS.HDDCOEFFICIENT), getServiceWrapper(), ) const listBucketObjectsSpy = jest.fn() const mockBucketName = 'test-bucket' const defaultConfig = configLoader().AWS.RECOMMENDATIONS_SERVICE function mockListComputeOptimizerBucket(response: any) { listBucketObjectsSpy.mockResolvedValue(response) s3Mock.on(ListObjectsV2Command).callsFake(listBucketObjectsSpy) } function mockGetComputeOptimizerBucket(mockCSVFilePath: string) { const mockFilePath = path.join(process.cwd(), mockCSVFilePath) const mockFile = fs.readFileSync(mockFilePath) s3Mock.on(GetObjectCommand).resolves({ $metadata: {}, Body: Buffer.from(mockFile) as any, }) } beforeAll(() => { configLoader().AWS.RECOMMENDATIONS_SERVICE = AWS_RECOMMENDATIONS_SERVICES.ComputeOptimizer }) afterEach(() => { s3Mock.reset() jest.restoreAllMocks() configLoader().AWS.RECOMMENDATIONS_SERVICE = defaultConfig }) it('gets recommendations for only "Over Provisioned" EC2 instances ignoring AutoScalingGroup', async () => { moment.now = function () { return +new Date('2022-01-21T00:00:00.000Z') } const mockCSVFilePath = '/src/__tests__/fixtures/computeOptimizerEC2.csv' mockListComputeOptimizerBucket(mockEC2ComputeOptimizerBucketList) mockGetComputeOptimizerBucket(mockCSVFilePath) const awsRecommendationsServices = getRecommendationsService() const result = await awsRecommendationsServices.getRecommendations(mockBucketName) const expectedResult: RecommendationResult[] = [ { cloudProvider: 'AWS', accountId: '1234567890', accountName: '1234567890', region: 'eu-central-1', recommendationType: 'EC2-OVER_PROVISIONED', kilowattHourSavings: 11.060730563099998, resourceId: 'i-0c80d1b0f3a0c5c69', instanceName: 'PA-VM-100 | Networks', co2eSavings: 0.004070348847220799, recommendationDetail: 'Save cost by changing instance type from m4.xlarge to t3.xlarge.', costSavings: 33.79, }, ] expect(result).toEqual(expectedResult) }) it('gets recommendations for only "Not Optimized" EBS volumes (with and without cost savings)', async () => { moment.now = function () { return +new Date('2022-01-21T00:00:00.000Z') } const mockCSVFilePath = '/src/__tests__/fixtures/computeOptimizerEBS.csv' mockListComputeOptimizerBucket(mockEBSComputeOptimizerBucketList) mockGetComputeOptimizerBucket(mockCSVFilePath) const awsRecommendationsServices = getRecommendationsService() const result = await awsRecommendationsServices.getRecommendations(mockBucketName) const expectedResult: RecommendationResult[] = [ { cloudProvider: 'AWS', accountId: '1234567890', accountName: '1234567890', region: 'us-west-2', recommendationType: 'EBS-NotOptimized', kilowattHourSavings: 0.0019612799999999993, resourceId: 'vol-00e39f1234a7eadfb', co2eSavings: 5.857366758275517e-7, recommendationDetail: 'Save cost by changing volume type from gp2(80GB) to gp3(50GB).', costSavings: 6.2, }, ] expect(result).toEqual(expectedResult) }) it('gets recommendations for only "Not Optimized" Lambda functions', async () => { moment.now = function () { return +new Date('2022-01-21T00:00:00.000Z') } const mockCSVFilePath = '/src/__tests__/fixtures/computeOptimizerLambda.csv' mockListComputeOptimizerBucket(mockLambdaComputeOptimizerBucketList) mockGetComputeOptimizerBucket(mockCSVFilePath) const awsRecommendationsServices = getRecommendationsService() const result = await awsRecommendationsServices.getRecommendations(mockBucketName) const expectedResult: RecommendationResult[] = [ { cloudProvider: 'AWS', accountId: '1234567890', accountName: '1234567890', region: 'us-east-2', recommendationType: 'Lambda-NotOptimized', kilowattHourSavings: 0.1781104877331825, resourceId: 'api-user-prod-add_user:$LATEST', co2eSavings: 0.00006699239827610782, recommendationDetail: 'Save cost by changing configuration memory size from 1024MB to 848MB.', costSavings: 2.988e-4, }, ] expect(result).toEqual(expectedResult) }) })