@cloud-carbon-footprint/gcp
Version:
The core logic to get cloud usage data and estimate energy and carbon emissions from Google Cloud Platform.
336 lines (282 loc) • 9.49 kB
text/typescript
/*
* © 2021 Thoughtworks, Inc.
*/
import { ProjectsClient } from '@google-cloud/resource-manager'
import {
AddressesClient,
DisksClient,
ImagesClient,
InstancesClient,
MachineTypesClient,
protos as googleCompute,
} from '@google-cloud/compute'
import { GoogleAuth } from 'google-auth-library'
import { RecommenderClient } from '@google-cloud/recommender'
import { GoogleAuthClient, wait, Logger } from '@cloud-carbon-footprint/common'
import {
mockAddressesResultItems,
mockedAddressGetDetails,
mockedDisksGetSSDDetails,
mockDisksResultItems,
mockedImageGetDetails,
mockedInstanceGetItems,
mockInstanceResultItems,
mockedMachineTypesGetItems,
} from './fixtures/googleapis.fixtures'
import { mockedProjects } from './fixtures/resourceManager.fixtures'
import { mockStopVMRecommendationsResults } from './fixtures/recommender.fixtures'
import Instance = googleCompute.google.cloud.compute.v1.IInstance
import MachineType = googleCompute.google.cloud.compute.v1.IMachineType
import ServiceWrapper from '../lib/ServiceWrapper'
import {
ActiveProject,
RecommenderRecommendations,
} from '../lib/RecommendationsTypes'
import { setupSpy, setupSpyWithRejectedValue } from './helpers'
jest.mock('@cloud-carbon-footprint/common', () => ({
...(jest.requireActual('@cloud-carbon-footprint/common') as Record<
string,
unknown
>),
wait: jest.fn(),
}))
jest.mock('@google-cloud/resource-manager', () => ({
ProjectsClient: jest.fn().mockImplementation(() => ({
searchProjects: jest.fn().mockResolvedValue(mockedProjects),
})),
}))
const mockRecommenderClientListRecommendations = jest.fn()
jest.mock('@google-cloud/recommender', () => ({
RecommenderClient: jest.fn().mockImplementation(() => ({
listRecommendations: mockRecommenderClientListRecommendations,
projectLocationRecommenderPath: jest.fn(),
})),
}))
describe('GCP Service Wrapper', () => {
let serviceWrapper: ServiceWrapper
beforeEach(async () => {
const auth = new GoogleAuth({
scopes: 'https://www.googleapis.com/auth/cloud-platform',
})
const getClientSpy = jest.spyOn(auth, 'getClient')
;(getClientSpy as jest.Mock).mockResolvedValue(jest.fn())
const googleAuthClient: GoogleAuthClient = await auth.getClient()
serviceWrapper = new ServiceWrapper(
new ProjectsClient(),
googleAuthClient,
new InstancesClient(),
new DisksClient(),
new AddressesClient(),
new ImagesClient(),
new MachineTypesClient(),
new RecommenderClient(),
)
setupSpy(
InstancesClient.prototype,
'aggregatedListAsync',
mockInstanceResultItems(),
)
setupSpy(
DisksClient.prototype,
'aggregatedListAsync',
mockDisksResultItems(),
)
setupSpy(DisksClient.prototype, 'get', mockedDisksGetSSDDetails)
setupSpy(
AddressesClient.prototype,
'aggregatedListAsync',
mockAddressesResultItems(),
)
setupSpy(MachineTypesClient.prototype, 'get', mockedMachineTypesGetItems)
setupSpy(InstancesClient.prototype, 'get', mockedInstanceGetItems)
setupSpy(ImagesClient.prototype, 'get', mockedImageGetDetails)
setupSpy(AddressesClient.prototype, 'get', mockedAddressGetDetails)
})
it('gets active projects', async () => {
const activeProjectsAndZones: ActiveProject[] =
await serviceWrapper.getActiveProjectsAndZones()
const expectedResult: ActiveProject[] = [
{
id: 'project',
name: 'project-name',
zones: ['us-west1-a', 'us-east1-a', 'global'],
},
]
expect(activeProjectsAndZones).toEqual(expectedResult)
})
it('gets recommendations by recommender ids', async () => {
mockRecommenderClientListRecommendations
.mockResolvedValueOnce(mockStopVMRecommendationsResults)
.mockResolvedValue([[]])
const recommenderIds = ['test-id-1', 'test-id-2']
const recommendations: RecommenderRecommendations[] =
await serviceWrapper.getRecommendationsForRecommenderIds(
'test-project-id',
'us-west1-a',
recommenderIds,
)
const expectedResult: RecommenderRecommendations[] = [
{
id: 'test-id-1',
zone: 'us-west1-a',
recommendations: mockStopVMRecommendationsResults[0],
},
{
id: 'test-id-2',
zone: 'us-west1-a',
recommendations: [],
},
]
expect(recommendations).toEqual(expectedResult)
})
it('gets instance details', async () => {
const instanceDetails: Instance = await serviceWrapper.getInstanceDetails(
'project',
'test-instance',
'us-west1-b',
)
const expectedResult: Instance = {
machineType:
'https://www.googleapis.com/compute/v1/projects/test-project/zones/us-west1-b/machineTypes/n2-standard-32',
disks: [],
id: '12456789012',
name: 'test-resource-name',
}
expect(instanceDetails).toEqual(expectedResult)
})
it('gets machine type details', async () => {
const machineTypeDetails: MachineType =
await serviceWrapper.getMachineTypeDetails(
'project',
'test-instance',
'us-west1-b',
)
const expectedResult = {
guestCpus: 32,
}
expect(machineTypeDetails).toEqual(expectedResult)
})
it('gets the storage type from a disk name', () => {
const ssdStorageType = serviceWrapper.getStorageTypeFromDiskName('ssd-test')
const hddStorageType = serviceWrapper.getStorageTypeFromDiskName('hdd-test')
expect(ssdStorageType).toBe('SSD')
expect(hddStorageType).toBe('HDD')
})
it('gets disks details', async () => {
const diskDetails = await serviceWrapper.getDiskDetails(
'project',
'test-disk',
'us-west1-b',
)
const expectedResult = {
sizeGb: '20',
type: 'https://www.googleapis.com/compute/v1/projects/techops-events/zones/us-central1-b/diskTypes/pd-standard-ssd',
id: '12456789012',
name: 'test-resource-name',
}
expect(diskDetails).toEqual(expectedResult)
})
it('gets image details', async () => {
const imageDetails = await serviceWrapper.getImageDetails(
'project',
'test-image',
)
const expectedResult = {
archiveSizeBytes: '580709696',
id: '12456789012',
name: 'test-resource-name',
}
expect(imageDetails).toEqual(expectedResult)
})
it('gets address details', async () => {
const addressDetails = await serviceWrapper.getAddressDetails(
'project',
'test-address',
'us-west1',
)
const expectedResult = {
id: '123456789012345',
name: 'test-address',
address: '38.141.210.105',
}
expect(addressDetails).toEqual(expectedResult)
})
describe('error handling', () => {
let serviceWrapper: ServiceWrapper
beforeEach(async () => {
const auth = new GoogleAuth({
scopes: 'https://www.googleapis.com/auth/cloud-platform',
})
const getClientSpy = jest.spyOn(auth, 'getClient')
;(getClientSpy as jest.Mock).mockResolvedValue(jest.fn())
const googleAuthClient: GoogleAuthClient = await auth.getClient()
serviceWrapper = new ServiceWrapper(
new ProjectsClient(),
googleAuthClient,
new InstancesClient(),
new DisksClient(),
new AddressesClient(),
new ImagesClient(),
new MachineTypesClient(),
new RecommenderClient(),
)
})
it('fails to get active zones for project', async () => {
setupSpyWithRejectedValue(
InstancesClient.prototype,
'aggregatedListAsync',
'error',
)
const activeProjectsAndZones: ActiveProject[] =
await serviceWrapper.getActiveProjectsAndZones()
const expectedResult: ActiveProject[] = []
expect(activeProjectsAndZones).toEqual(expectedResult)
})
it('exceeds the quota for recommender client api calls', async () => {
mockRecommenderClientListRecommendations
.mockRejectedValueOnce({
details: 'Quota exceeded',
})
.mockRejectedValueOnce({})
.mockResolvedValue([[]])
const warn = jest
.spyOn(Logger.prototype, 'warn')
.mockImplementation(() => {
return
})
const recommenderIds = ['test-id-1']
await serviceWrapper.getRecommendationsForRecommenderIds(
'test-project-id',
'us-west1-a',
recommenderIds,
)
expect(wait).toHaveBeenCalled()
expect(warn).toHaveBeenCalledWith(
'GCP Recommendations API quota exceeded. Retrying after 10 seconds.',
)
})
it('fails to get recommendations from recommender client api calls', async () => {
mockRecommenderClientListRecommendations
.mockRejectedValueOnce({
details: 'Permission denied',
message: 'Missing permissions for recommender',
})
.mockRejectedValueOnce({})
.mockResolvedValue([[]])
const warn = jest
.spyOn(Logger.prototype, 'warn')
.mockImplementation(() => {
return
})
const recommenderIds = ['test-id-1']
await serviceWrapper.getRecommendationsForRecommenderIds(
'test-project-id',
'us-west1-a',
recommenderIds,
)
expect(warn).toHaveBeenCalledWith(
'Failed to get recommendations for GCP recommender ID: test-id-1. Error: Missing permissions for recommender',
)
})
})
})