Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Very much in Alpha.
- [ ] Add artifact model to image versions
- [ ] Link artifacts to objects
- [x] (DDI) poll deployments
- [ ] (DDI) send deployment base
- [x] (DDI) send deployment base
- [x] (DDI) send installed base
- [ ] (DDI) deployment feedback
- [ ] (DDI) send artifacts
Expand Down
27 changes: 23 additions & 4 deletions apps/server/src/ddi/ddi.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { DdiService } from './ddi.service';
import { NotImplementedException, NotFoundException } from '@nestjs/common';
import { WorkspaceDeviceParams, WorkspaceDeviceDeploymentParams, WorkspaceDeviceImageVersionParams, WorkspaceDeviceImageVersionFilenameParams } from './dtos/path-params.dto';
import { ConfigDto, LinkDto, LinksDto, PollingConfigDto, RootDto } from './dtos/root-res.dto';
import { FinishedEnum } from './dtos/deployment-feedback-req.dto';
import { ExecutionEnum } from './dtos/deployment-feedback-req.dto';
import { DeploymentBaseFeedbackDto } from './dtos/deployment-feedback-req.dto';

describe('DdiController', () => {
let controller: DdiController;
Expand Down Expand Up @@ -190,11 +193,27 @@ describe('DdiController', () => {
deploymentId: mockDeploymentId,
};

mockDdiService.postDeploymentFeedback.mockResolvedValue({ hello: 'world' });
const mockDeploymentBaseFeedback: DeploymentBaseFeedbackDto = {
status: {
execution: ExecutionEnum.CLOSED,
result: {
finished: FinishedEnum.SUCCESS,
progress: {
cnt: 10,
of: 100,
},
},
code: 0,
details: ['detail1', 'detail2'],
},
time: new Date().toISOString(),
};

const result = await controller.postDeploymentFeedback(params);
expect(result).toEqual({ hello: 'world' });
expect(service.postDeploymentFeedback).toHaveBeenCalledWith(mockWorkspaceId, mockDeviceId, mockDeploymentId);
mockDdiService.postDeploymentFeedback.mockResolvedValue(null);

const result = await controller.postDeploymentFeedback(params, mockDeploymentBaseFeedback);
expect(result).toBeNull();
expect(service.postDeploymentFeedback).toHaveBeenCalledWith(mockWorkspaceId, mockDeviceId, mockDeploymentId, mockDeploymentBaseFeedback);
});
});

Expand Down
8 changes: 5 additions & 3 deletions apps/server/src/ddi/ddi.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Controller, Get, NotImplementedException, Param, Post, Put } from '@nestjs/common';
import { Body, Controller, Get, HttpCode, NotImplementedException, Param, Post, Put } from '@nestjs/common';
import { WorkspaceDeviceDeploymentParams, WorkspaceDeviceImageVersionFilenameParams, WorkspaceDeviceImageVersionParams, WorkspaceDeviceParams } from './dtos/path-params.dto';
import { DdiService } from './ddi.service';
import { DeploymentBaseFeedbackDto } from './dtos/deployment-feedback-req.dto';

@Controller('ddi/:workspaceId/controller/v1/:deviceId')
export class DdiController {
Expand Down Expand Up @@ -29,9 +30,10 @@ export class DdiController {
return this.ddiService.getDeploymentBase(params.workspaceId, params.deviceId, params.deploymentId);
}

@HttpCode(200)
@Post('/deploymentBase/:deploymentId/feedback')
postDeploymentFeedback(@Param() params: WorkspaceDeviceDeploymentParams) {
return this.ddiService.postDeploymentFeedback(params.workspaceId, params.deviceId, params.deploymentId);
postDeploymentFeedback(@Param() params: WorkspaceDeviceDeploymentParams, @Body() deploymentBaseFeedback: DeploymentBaseFeedbackDto) {
return this.ddiService.postDeploymentFeedback(params.workspaceId, params.deviceId, params.deploymentId, deploymentBaseFeedback);
}

@Get('/softwaremodules/:imageVersionId/artifacts')
Expand Down
5 changes: 1 addition & 4 deletions apps/server/src/ddi/ddi.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,10 +328,7 @@ describe('DdiService', () => {
});

describe('postDeploymentFeedback', () => {
it('should return hello world object', async () => {
const result = await service.postDeploymentFeedback(mockWorkspaceId, mockDeviceId, mockDeploymentId);
expect(result).toEqual({ hello: 'world' });
});

});

describe('getArtifacts', () => {
Expand Down
86 changes: 83 additions & 3 deletions apps/server/src/ddi/ddi.service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { GoneException, Injectable, Logger, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Deployment, DeploymentState } from '../deployment/entities/deployment.entity';
import { In, Not, Repository } from 'typeorm';
import { ConfigDto, LinkDto, LinksDto, PollingConfigDto, RootDto } from './dtos/root-res.dto';
import { Device } from '../device/entities/device.entity';
import { ConfigService } from '@nestjs/config';
import { ExecutionEnum, FinishedEnum } from './dtos/deployment-feedback-req.dto';
import { DeploymentBaseFeedbackDto } from './dtos/deployment-feedback-req.dto';

@Injectable()
export class DdiService {
Expand Down Expand Up @@ -92,10 +94,22 @@ export class DdiService {
workspaceId: string,
deviceId: string,
deploymentId: string,
deploymentBaseFeedback: DeploymentBaseFeedbackDto,
) {
return {
hello: 'world',
const device = await this.getDeviceOrThrow(deviceId);
const deployment = await this.getDeploymentOrThrow(deploymentId);

if(deployment.isInTerminalState()) {
throw new GoneException('Deployment is in a terminal state');
}

const { state, messages } = this.handleDeploymentFeedback(deploymentBaseFeedback);

await this.deploymentRepository.update(deployment.uuid, {
state,
});

return;
}

async getArtifacts(
Expand Down Expand Up @@ -144,6 +158,18 @@ export class DdiService {
return device;
}

async getDeploymentOrThrow(deploymentId: string) {
const deployment = await this.deploymentRepository.findOne({
where: {
uuid: deploymentId,
},
});
if (!deployment) {
throw new NotFoundException('Deployment not found');
}
return deployment;
}

async findInFlightDeployment(deviceId: string): Promise<Deployment | null> {
return this.deploymentRepository.findOne({
where: {
Expand Down Expand Up @@ -234,5 +260,59 @@ export class DdiService {
const baseUrl = this.buildBaseUrl(deviceId);
return `${baseUrl}/deploymentBase/${deploymentId}`;
}


handleDeploymentFeedback(deploymentBaseFeedback: DeploymentBaseFeedbackDto): { state: DeploymentState; messages: string[]} {
let state: DeploymentState;
const messages: string[] = [];

const feedbackDetailMessages = deploymentBaseFeedback.status.details;
if (feedbackDetailMessages && feedbackDetailMessages.length > 0) {
messages.concat(feedbackDetailMessages);
}

switch (deploymentBaseFeedback.status.execution) {
case ExecutionEnum.CANCELED:
state = DeploymentState.CANCELED;
messages.push("Server update: Device confirmed cancelation.");
break;

case ExecutionEnum.REJECTED:
state = DeploymentState.WARNING;
messages.push("Server update: Device rejected update.");
break;

case ExecutionEnum.CLOSED:
const result = deploymentBaseFeedback.status.result.finished;
if (result === FinishedEnum.FAILURE) {
state = DeploymentState.ERROR;
messages.push("Server update: Device reported result with error.");
} else {
state = DeploymentState.FINISHED;
messages.push("Server update: Device reported result with success.");
}
break;

case ExecutionEnum.DOWNLOAD:
state = DeploymentState.DOWNLOAD;
messages.push("Server update: Device confirmed download start.");
break;

case ExecutionEnum.DOWNLOADED:
state = DeploymentState.DOWNLOADED;
messages.push("Server update: Device confirmed download finished.");
break;

default:
state = DeploymentState.RUNNING;
messages.push(`Server update: Device reported intermediate feedback ${deploymentBaseFeedback.status.execution}`);
break;
}

return {
state,
messages
};
}
}

2 changes: 1 addition & 1 deletion apps/server/src/ddi/dtos/deployment-feedback-req.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Type } from 'class-transformer';
export enum FinishedEnum {
SUCCESS = 'success',
FAILURE = 'failure',
NONE = 'none',
// NONE = 'none',
}

export enum ExecutionEnum {
Expand Down
26 changes: 26 additions & 0 deletions apps/server/src/deployment/entities/deployment.entity.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,32 @@ describe('Deployment', () => {
deployment.updatedAt = new Date();
});

describe('isInTerminalState', () => {
it.each([
DeploymentState.FINISHED,
DeploymentState.ERROR,
DeploymentState.DOWNLOADED,
])('should return true when state is %s', (state) => {
deployment.state = state;
expect(deployment.isInTerminalState()).toBe(true);
});

it.each([
DeploymentState.RUNNING,
DeploymentState.SCHEDULED,
DeploymentState.CANCELING,
DeploymentState.CANCELED,
DeploymentState.WARNING,
DeploymentState.RETRIEVED,
DeploymentState.DOWNLOAD,
DeploymentState.CANCEL_REJECTED,
DeploymentState.WAIT_FOR_CONFIRMATION,
])('should return false when state is %s', (state) => {
deployment.state = state;
expect(deployment.isInTerminalState()).toBe(false);
});
});

describe('getDownloadType', () => {
it('should return ATTEMPT', () => {
expect(deployment.getDownloadType()).toBe(DownloadUpdateEnum.ATTEMPT);
Expand Down
8 changes: 8 additions & 0 deletions apps/server/src/deployment/entities/deployment.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ export class Deployment {

@ManyToOne(() => ImageVersion, (imageVersion) => imageVersion.deployments)
imageVersion: ImageVersion;

isInTerminalState(): boolean {
return [
DeploymentState.FINISHED,
DeploymentState.ERROR,
DeploymentState.DOWNLOADED,
].includes(this.state);
}

getDownloadType(): DownloadUpdateEnum {
return DownloadUpdateEnum.ATTEMPT;
Expand Down
1 change: 1 addition & 0 deletions apps/server/test/factories/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const createMockDeployment = (overrides?: Partial<Deployment>): Deploymen
updatedAt: new Date('2024-01-01'),
device: createMockDevice(),
imageVersion: createMockImageVersion(),
isInTerminalState: jest.fn(),
getDownloadType: jest.fn(),
getUpdateType: jest.fn(),
getMaintenanceWindow: jest.fn(),
Expand Down