Skip to content

Commit 049b37a

Browse files
authored
Register Azure benchmark provider (#2167)
Fixes OPS-3950 ## Additional Notes - Registers `Azure` provider - Extend `getBenchmarkFolderDisplayName` to Azure - Adds Azure related flows to `PROVIDER_LIFECYCLE_WORKFLOWS` - Modify integration tests to cover all registered providers
1 parent 67e0ee6 commit 049b37a

5 files changed

Lines changed: 285 additions & 180 deletions

File tree

packages/server/api/src/app/benchmark/catalog-manifests.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,8 @@ export const PROVIDER_LIFECYCLE_WORKFLOWS: Record<string, LifecycleWorkflow> = {
88
orchestratorWorkflowId: 'Run AWS Benchmark - orchestrator',
99
cleanupWorkflowId: 'Clean-up AWS Benchmark data',
1010
},
11+
azure: {
12+
orchestratorWorkflowId: 'Run Azure Benchmark - orchestrator',
13+
cleanupWorkflowId: 'Clean-up Azure Benchmark data',
14+
},
1115
};

packages/server/api/src/app/benchmark/create-benchmark.service.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ function getBenchmarkFolderDisplayName(provider: string): string {
7373
switch (provider) {
7474
case BenchmarkProviders.AWS:
7575
return 'AWS Benchmark';
76+
case BenchmarkProviders.AZURE:
77+
return 'Azure Benchmark';
7678
default:
7779
throwValidationError(`Unknown provider: ${provider}`);
7880
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { BenchmarkProviders } from '@openops/shared';
22
import { registerProvider } from './provider-adapter';
33
import { awsProviderAdapter } from './providers/aws';
4+
import { azureProviderAdapter } from './providers/azure';
45

56
function registerProviders(): void {
67
registerProvider(BenchmarkProviders.AWS, awsProviderAdapter);
8+
registerProvider(BenchmarkProviders.AZURE, azureProviderAdapter);
79
}
810

911
registerProviders();

packages/server/api/test/integration/ce/benchmark/benchmark-module.test.ts

Lines changed: 164 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ afterAll(async () => {
108108
process.env = originalEnv;
109109
});
110110

111+
const allBenchmarkProviders = Object.values(BenchmarkProviders);
112+
111113
describe('Benchmark wizard API', () => {
112114
const postWizard = async ({
113115
provider,
@@ -127,61 +129,68 @@ describe('Benchmark wizard API', () => {
127129

128130
describe('POST /v1/benchmarks/:provider/wizard', () => {
129131
beforeEach(() => {
132+
wizardServiceMock.resolveWizardNavigation.mockClear();
130133
wizardServiceMock.resolveWizardNavigation.mockResolvedValue(
131134
mockWizardStep,
132135
);
133136
process.env.OPS_FINOPS_BENCHMARK_ENABLED = 'true';
134137
});
135138

136-
it('calls resolveWizardNavigation with AWS provider, body, and projectId and returns mocked step', async () => {
137-
const { token, project } = await createAndInsertMocks();
138-
const body = {};
139-
140-
const response = await postWizard({
141-
provider: BenchmarkProviders.AWS,
142-
token,
143-
body,
144-
});
145-
146-
expect(response?.statusCode).toBe(StatusCodes.OK);
147-
expect(response?.json()).toEqual(mockWizardStep);
148-
expect(wizardServiceMock.resolveWizardNavigation).toHaveBeenCalledTimes(
149-
1,
150-
);
151-
expect(wizardServiceMock.resolveWizardNavigation).toHaveBeenCalledWith(
152-
BenchmarkProviders.AWS,
153-
{
154-
currentStep: undefined,
155-
benchmarkConfiguration: undefined,
156-
},
157-
project.id,
158-
);
159-
});
160-
161-
it('passes currentStep and benchmarkConfiguration to resolveWizardNavigation', async () => {
162-
const { token, project } = await createAndInsertMocks();
163-
const body = {
164-
currentStep: 'connection',
165-
benchmarkConfiguration: { connection: ['conn-1'] },
166-
};
167-
168-
const response = await postWizard({
169-
provider: BenchmarkProviders.AWS,
170-
token,
171-
body,
172-
});
139+
it.each(allBenchmarkProviders)(
140+
'calls resolveWizardNavigation with %s provider, body, and projectId and returns mocked step',
141+
async (provider) => {
142+
const { token, project } = await createAndInsertMocks();
143+
const body = {};
144+
145+
const response = await postWizard({
146+
provider,
147+
token,
148+
body,
149+
});
150+
151+
expect(response?.statusCode).toBe(StatusCodes.OK);
152+
expect(response?.json()).toEqual(mockWizardStep);
153+
expect(wizardServiceMock.resolveWizardNavigation).toHaveBeenCalledTimes(
154+
1,
155+
);
156+
expect(wizardServiceMock.resolveWizardNavigation).toHaveBeenCalledWith(
157+
provider,
158+
{
159+
currentStep: undefined,
160+
benchmarkConfiguration: undefined,
161+
},
162+
project.id,
163+
);
164+
},
165+
);
173166

174-
expect(response?.statusCode).toBe(StatusCodes.OK);
175-
expect(response?.json()).toEqual(mockWizardStep);
176-
expect(wizardServiceMock.resolveWizardNavigation).toHaveBeenCalledWith(
177-
BenchmarkProviders.AWS,
178-
{
167+
it.each(allBenchmarkProviders)(
168+
'passes currentStep and benchmarkConfiguration to resolveWizardNavigation (%s)',
169+
async (provider) => {
170+
const { token, project } = await createAndInsertMocks();
171+
const body = {
179172
currentStep: 'connection',
180173
benchmarkConfiguration: { connection: ['conn-1'] },
181-
},
182-
project.id,
183-
);
184-
});
174+
};
175+
176+
const response = await postWizard({
177+
provider,
178+
token,
179+
body,
180+
});
181+
182+
expect(response?.statusCode).toBe(StatusCodes.OK);
183+
expect(response?.json()).toEqual(mockWizardStep);
184+
expect(wizardServiceMock.resolveWizardNavigation).toHaveBeenCalledWith(
185+
provider,
186+
{
187+
currentStep: 'connection',
188+
benchmarkConfiguration: { connection: ['conn-1'] },
189+
},
190+
project.id,
191+
);
192+
},
193+
);
185194

186195
it('returns 400 when provider is not in BenchmarkProviders enum', async () => {
187196
const { token } = await createAndInsertMocks();
@@ -254,38 +263,49 @@ describe('Benchmark wizard API', () => {
254263
});
255264

256265
describe('Create Benchmark API (POST /v1/benchmarks/:provider)', () => {
257-
const validCreateBody = {
266+
const validAwsCreateBody = {
258267
benchmarkConfiguration: {
259268
connection: ['conn-1'],
260269
workflows: ['AWS Benchmark - Unattached EBS'],
261270
regions: ['us-east-1'],
262271
},
263272
};
264273

265-
const mockCreateResult = {
274+
const validAzureCreateBody = {
275+
benchmarkConfiguration: {
276+
connection: ['conn-1'],
277+
workflows: ['Azure Benchmark - Unattached Managed Disks'],
278+
regions: ['eastus'],
279+
subscriptions: ['sub-a', 'sub-b'],
280+
},
281+
};
282+
283+
const mockCreateBenchmarkWorkflows = [
284+
{
285+
flowId: 'flow-1',
286+
displayName: 'Orchestrator',
287+
isOrchestrator: true,
288+
isCleanup: false,
289+
},
290+
{
291+
flowId: 'flow-2',
292+
displayName: 'Cleanup',
293+
isOrchestrator: false,
294+
isCleanup: true,
295+
},
296+
{
297+
flowId: 'flow-3',
298+
displayName: 'Sub',
299+
isOrchestrator: false,
300+
isCleanup: false,
301+
},
302+
];
303+
304+
const mockAwsCreateResult = {
266305
benchmarkId: 'bench-1',
267306
folderId: 'folder-1',
268-
provider: 'aws',
269-
workflows: [
270-
{
271-
flowId: 'flow-1',
272-
displayName: 'Orchestrator',
273-
isOrchestrator: true,
274-
isCleanup: false,
275-
},
276-
{
277-
flowId: 'flow-2',
278-
displayName: 'Cleanup',
279-
isOrchestrator: false,
280-
isCleanup: true,
281-
},
282-
{
283-
flowId: 'flow-3',
284-
displayName: 'Sub',
285-
isOrchestrator: false,
286-
isCleanup: false,
287-
},
288-
],
307+
provider: BenchmarkProviders.AWS,
308+
workflows: mockCreateBenchmarkWorkflows,
289309
webhookPayload: {
290310
webhookBaseUrl: 'https://api.example.com',
291311
workflows: ['flow-3'],
@@ -295,6 +315,37 @@ describe('Create Benchmark API (POST /v1/benchmarks/:provider)', () => {
295315
},
296316
};
297317

318+
const mockAzureCreateResult = {
319+
benchmarkId: 'bench-1',
320+
folderId: 'folder-1',
321+
provider: BenchmarkProviders.AZURE,
322+
workflows: mockCreateBenchmarkWorkflows,
323+
webhookPayload: {
324+
webhookBaseUrl: 'https://api.example.com',
325+
workflows: ['flow-3'],
326+
cleanupWorkflows: ['flow-2'],
327+
subscriptions: ['sub-a', 'sub-b'],
328+
regions: ['eastus'],
329+
},
330+
};
331+
332+
const createBenchmarkSuccessCases: ReadonlyArray<{
333+
provider: BenchmarkProviders;
334+
body: typeof validAwsCreateBody | typeof validAzureCreateBody;
335+
mockResult: typeof mockAwsCreateResult | typeof mockAzureCreateResult;
336+
}> = [
337+
{
338+
provider: BenchmarkProviders.AWS,
339+
body: validAwsCreateBody,
340+
mockResult: mockAwsCreateResult,
341+
},
342+
{
343+
provider: BenchmarkProviders.AZURE,
344+
body: validAzureCreateBody,
345+
mockResult: mockAzureCreateResult,
346+
},
347+
];
348+
298349
const postCreate = async ({
299350
provider,
300351
token,
@@ -308,41 +359,44 @@ describe('Create Benchmark API (POST /v1/benchmarks/:provider)', () => {
308359
method: 'POST',
309360
url: `/v1/benchmarks/${provider}`,
310361
headers: token ? { authorization: `Bearer ${token}` } : undefined,
311-
body: body ?? validCreateBody,
362+
body: body ?? validAwsCreateBody,
312363
});
313364

314365
beforeEach(() => {
315366
createBenchmarkServiceMock.createBenchmark.mockReset();
316367
process.env.OPS_FINOPS_BENCHMARK_ENABLED = 'true';
317368
});
318369

319-
it('returns 201 and BenchmarkCreationResult when body is valid', async () => {
320-
createBenchmarkServiceMock.createBenchmark.mockResolvedValue(
321-
mockCreateResult,
322-
);
323-
const { token, project } = await createAndInsertMocks();
370+
it.each(createBenchmarkSuccessCases)(
371+
'returns 201 and BenchmarkCreationResult when body is valid ($provider)',
372+
async ({ provider, body, mockResult }) => {
373+
createBenchmarkServiceMock.createBenchmark.mockResolvedValue(mockResult);
374+
const { token, project } = await createAndInsertMocks();
324375

325-
const response = await postCreate({
326-
provider: BenchmarkProviders.AWS,
327-
token,
328-
body: validCreateBody,
329-
});
376+
const response = await postCreate({
377+
provider,
378+
token,
379+
body,
380+
});
330381

331-
expect(response?.statusCode).toBe(StatusCodes.CREATED);
332-
expect(response?.json()).toEqual(mockCreateResult);
333-
expect(createBenchmarkServiceMock.createBenchmark).toHaveBeenCalledTimes(1);
334-
expect(createBenchmarkServiceMock.createBenchmark).toHaveBeenCalledWith({
335-
provider: BenchmarkProviders.AWS,
336-
projectId: project.id,
337-
userId: expect.any(String),
338-
benchmarkConfiguration: validCreateBody.benchmarkConfiguration,
339-
});
340-
});
382+
expect(response?.statusCode).toBe(StatusCodes.CREATED);
383+
expect(response?.json()).toEqual(mockResult);
384+
expect(createBenchmarkServiceMock.createBenchmark).toHaveBeenCalledTimes(
385+
1,
386+
);
387+
expect(createBenchmarkServiceMock.createBenchmark).toHaveBeenCalledWith({
388+
provider,
389+
projectId: project.id,
390+
userId: expect.any(String),
391+
benchmarkConfiguration: body.benchmarkConfiguration,
392+
});
393+
},
394+
);
341395

342396
it('returns 401 when not authenticated', async () => {
343397
const response = await postCreate({
344398
provider: BenchmarkProviders.AWS,
345-
body: validCreateBody,
399+
body: validAwsCreateBody,
346400
});
347401

348402
expect(response?.statusCode).toBe(StatusCodes.UNAUTHORIZED);
@@ -356,7 +410,7 @@ describe('Create Benchmark API (POST /v1/benchmarks/:provider)', () => {
356410
const response = await postCreate({
357411
provider: BenchmarkProviders.AWS,
358412
token,
359-
body: validCreateBody,
413+
body: validAwsCreateBody,
360414
});
361415

362416
expect(response?.statusCode).toBe(StatusCodes.PAYMENT_REQUIRED);
@@ -371,7 +425,7 @@ describe('Create Benchmark API (POST /v1/benchmarks/:provider)', () => {
371425
const response = await postCreate({
372426
provider: 'invalidprovider',
373427
token,
374-
body: validCreateBody,
428+
body: validAwsCreateBody,
375429
});
376430

377431
expect(response?.statusCode).toBe(StatusCodes.BAD_REQUEST);
@@ -546,20 +600,23 @@ describe('List benchmarks API', () => {
546600
});
547601
});
548602

549-
it('passes provider filter to listBenchmarks', async () => {
550-
const { token, project } = await createAndInsertMocks();
551-
552-
const response = await getBenchmarkList({
553-
token,
554-
provider: BenchmarkProviders.AWS,
555-
});
556-
557-
expect(response?.statusCode).toBe(StatusCodes.OK);
558-
expect(benchmarkStatusServiceMock.listBenchmarks).toHaveBeenCalledWith({
559-
projectId: project.id,
560-
provider: BenchmarkProviders.AWS,
561-
});
562-
});
603+
it.each(allBenchmarkProviders)(
604+
'passes provider filter to listBenchmarks (%s)',
605+
async (provider) => {
606+
const { token, project } = await createAndInsertMocks();
607+
608+
const response = await getBenchmarkList({
609+
token,
610+
provider,
611+
});
612+
613+
expect(response?.statusCode).toBe(StatusCodes.OK);
614+
expect(benchmarkStatusServiceMock.listBenchmarks).toHaveBeenCalledWith({
615+
projectId: project.id,
616+
provider,
617+
});
618+
},
619+
);
563620

564621
it('returns 400 when provider is not a valid BenchmarkProviders value', async () => {
565622
const { token } = await createAndInsertMocks();

0 commit comments

Comments
 (0)