UNPKG

@cloud-carbon-footprint/aws

Version:

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

513 lines (478 loc) 12.6 kB
/* * © 2021 Thoughtworks, Inc. */ import AWSMock from 'aws-sdk-mock' import AWS, { CloudWatch, CostExplorer, CloudWatchLogs, S3 } from 'aws-sdk' import ElastiCache from '../lib/ElastiCache' import { ServiceWrapper } from '../lib/ServiceWrapper' import mockAWSCloudWatchGetMetricDataCall from '../lib/mockAWSCloudWatchGetMetricDataCall' beforeAll(() => { AWSMock.setSDKInstance(AWS) }) describe('ElastiCache', () => { const startDate = '2020-07-10' const dayTwo = '2020-07-11' const endDate = '2020-07-12' const region = 'us-west-1' const metrics = [ { Id: 'cpuUtilizationWithEmptyValues', Expression: "SEARCH('{AWS/ElastiCache} MetricName=\"CPUUtilization\"', 'Average', 3600)", ReturnData: false, }, { Id: 'cpuUtilization', Expression: 'REMOVE_EMPTY(cpuUtilizationWithEmptyValues)', }, ] const getServiceWrapper = () => new ServiceWrapper( new CloudWatch(), new CloudWatchLogs(), new CostExplorer(), new S3(), ) afterEach(() => { AWSMock.restore() }) it('should return the usage of two hours of different days', async () => { const response: any = { MetricDataResults: [ { Id: 'cpuUtilization', Label: 'AWS/ElastiCache CPUUtilization', Timestamps: [new Date(startDate), new Date(dayTwo)], Values: [1.0456, 2.03242], StatusCode: 'Complete', Messages: [], }, ], } mockAWSCloudWatchGetMetricDataCall( new Date(startDate), new Date(endDate), response, metrics, ) AWSMock.mock( 'CostExplorer', 'getCostAndUsage', ( params: CostExplorer.GetCostAndUsageRequest, callback: (a: Error, response: any) => any, ) => { expect(params).toEqual(costExplorerRequest(startDate, endDate, region)) callback(null, { ResultsByTime: [ { TimePeriod: { Start: startDate, End: dayTwo, }, Groups: [ { Keys: ['NodeUsage:cache.t3.medium'], Metrics: { UsageQuantity: { Amount: '2', }, }, }, ], }, { TimePeriod: { Start: dayTwo, End: endDate, }, Groups: [ { Keys: ['NodeUsage:cache.t3.medium'], Metrics: { UsageQuantity: { Amount: '2', }, }, }, ], }, ], }) }, ) const elasticacheService = new ElastiCache(getServiceWrapper()) const usageByHour = await elasticacheService.getUsage( new Date(startDate), new Date(endDate), region, ) expect(usageByHour).toEqual([ { cpuUtilizationAverage: 1.0456, vCpuHours: 4, timestamp: new Date(startDate), usesAverageCPUConstant: false, }, { cpuUtilizationAverage: 2.03242, vCpuHours: 4, timestamp: new Date(dayTwo), usesAverageCPUConstant: false, }, ]) }) it('should return empty list when no usage', async () => { const response: any = { MetricDataResults: [], } mockAWSCloudWatchGetMetricDataCall( new Date(startDate), new Date(endDate), response, metrics, ) AWSMock.mock( 'CostExplorer', 'getCostAndUsage', ( params: CostExplorer.GetCostAndUsageRequest, callback: (a: Error, response: any) => any, ) => { expect(params).toEqual(costExplorerRequest(startDate, endDate, region)) callback(null, { ResultsByTime: [ { TimePeriod: { Start: startDate, End: endDate, }, Total: { UsageQuantity: { Amount: 0, }, }, Groups: [], }, ], }) }, ) const elasticacheService = new ElastiCache(getServiceWrapper()) const usageByHour = await elasticacheService.getUsage( new Date(startDate), new Date(endDate), region, ) expect(usageByHour).toEqual([]) }) it('uses the cpu utilization constant for missing cpu utilization data', async () => { const response: any = { MetricDataResults: [ { Id: 'cpuUtilization', Label: 'cpuUtilization', Timestamps: [], Values: [], StatusCode: 'Complete', }, ], } mockAWSCloudWatchGetMetricDataCall( new Date(startDate), new Date(endDate), response, metrics, ) AWSMock.mock( 'CostExplorer', 'getCostAndUsage', ( params: CostExplorer.GetCostAndUsageRequest, callback: (a: Error, response: any) => any, ) => { expect(params).toEqual(costExplorerRequest(startDate, endDate, region)) callback(null, { ResultsByTime: [ { TimePeriod: { Start: startDate, End: endDate, }, Groups: [ { Keys: ['USE2-NodeUsage:cache.t3.medium'], Metrics: { UsageQuantity: { Amount: '1', }, }, }, ], }, ], }) }, ) const elasticacheService = new ElastiCache(getServiceWrapper()) const usageByHour = await elasticacheService.getUsage( new Date(startDate), new Date(endDate), region, ) expect(usageByHour).toEqual([ { cpuUtilizationAverage: 50, vCpuHours: 2, timestamp: new Date(startDate), usesAverageCPUConstant: true, }, ]) }) it('should return the usage when two different cache instances types were used', async () => { const response: any = { MetricDataResults: [ { Id: 'cpuUtilization', Label: 'cpuUtilization', Timestamps: [new Date(startDate)], Values: [50], StatusCode: 'Complete', }, ], } mockAWSCloudWatchGetMetricDataCall( new Date(startDate), new Date(endDate), response, metrics, ) AWSMock.mock( 'CostExplorer', 'getCostAndUsage', ( params: CostExplorer.GetCostAndUsageRequest, callback: (a: Error, response: any) => any, ) => { expect(params).toEqual(costExplorerRequest(startDate, endDate, region)) callback(null, { ResultsByTime: [ { TimePeriod: { Start: startDate, End: endDate, }, Groups: [ { Keys: ['USE2-NodeUsage:cache.t3.medium'], Metrics: { UsageQuantity: { Amount: '3', }, }, }, { Keys: ['USE2-NodeUsage:cache.t2.micro'], Metrics: { UsageQuantity: { Amount: '2', }, }, }, ], }, ], }) }, ) const elasticacheService = new ElastiCache(getServiceWrapper()) const usageByHour = await elasticacheService.getUsage( new Date(startDate), new Date(endDate), region, ) expect(usageByHour).toEqual([ { cpuUtilizationAverage: 50, vCpuHours: 8, timestamp: new Date(startDate), usesAverageCPUConstant: false, }, ]) }) it('should return the usage when two different cache instances types in different hours were used', async () => { const response: any = { MetricDataResults: [ { Id: 'cpuUtilization', Label: 'cpuUtilization', Timestamps: [ new Date(startDate + 'T22:00:00.000Z'), new Date(startDate + 'T22:06:00.000Z'), ], Values: [50, 70], StatusCode: 'Complete', }, ], } mockAWSCloudWatchGetMetricDataCall( new Date(startDate), new Date(endDate), response, metrics, ) AWSMock.mock( 'CostExplorer', 'getCostAndUsage', ( params: CostExplorer.GetCostAndUsageRequest, callback: (a: Error, response: any) => any, ) => { expect(params).toEqual(costExplorerRequest(startDate, endDate, region)) callback(null, { ResultsByTime: [ { TimePeriod: { Start: startDate, End: endDate, }, Groups: [ { Keys: ['USE2-NodeUsage:cache.t3.medium'], Metrics: { UsageQuantity: { Amount: '2', }, }, }, { Keys: ['USE2-NodeUsage:cache.t2.micro'], Metrics: { UsageQuantity: { Amount: '2', }, }, }, ], }, ], }) }, ) const elasticacheService = new ElastiCache(getServiceWrapper()) const usageByHour = await elasticacheService.getUsage( new Date(startDate), new Date(endDate), region, ) expect(usageByHour).toEqual([ { cpuUtilizationAverage: 60, vCpuHours: 6, timestamp: new Date(startDate), usesAverageCPUConstant: false, }, ]) }) it('should throw PartialData when AWS returns PartialData', async () => { const response: any = { MetricDataResults: [ { Id: 'cpuUtilization', Label: 'cpuUtilization', Timestamps: [ new Date(startDate + 'T22:00:00.000Z'), new Date(startDate + 'T22:06:00.000Z'), ], Values: [50, 70], StatusCode: 'PartialData', }, ], } mockAWSCloudWatchGetMetricDataCall( new Date(startDate), new Date(endDate), response, metrics, ) AWSMock.mock( 'CostExplorer', 'getCostAndUsage', ( params: CostExplorer.GetCostAndUsageRequest, callback: (a: Error, response: any) => any, ) => { expect(params).toEqual(costExplorerRequest(startDate, endDate, region)) callback(null, { ResultsByTime: [ { TimePeriod: { Start: startDate, End: endDate, }, Groups: [ { Keys: ['USE2-NodeUsage:cache.t3.medium'], Metrics: { UsageQuantity: { Amount: '2', }, }, }, { Keys: ['USE2-NodeUsage:cache.t2.micro'], Metrics: { UsageQuantity: { Amount: '2', }, }, }, ], }, ], }) }, ) const elasticacheService = new ElastiCache(getServiceWrapper()) const getUsageByHour = async () => await elasticacheService.getUsage( new Date(startDate), new Date(endDate), region, ) await expect(getUsageByHour).rejects.toThrow( 'Partial Data Returned from AWS', ) }) }) function costExplorerRequest( startDate: string, endDate: string, region: string, ) { return { TimePeriod: { Start: startDate, End: endDate, }, Filter: { And: [ { Dimensions: { Key: 'USAGE_TYPE_GROUP', Values: ['ElastiCache: Running Hours'], }, }, { Dimensions: { Key: 'REGION', Values: [region] } }, ], }, Granularity: 'DAILY', GroupBy: [ { Key: 'USAGE_TYPE', Type: 'DIMENSION', }, ], Metrics: ['UsageQuantity'], } }