Skip to content
Merged
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
9 changes: 7 additions & 2 deletions config/custom-environment-variables.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@
},
"tilesStorageProvider": "TILES_STORAGE_PROVIDER",
"gpkgStorageProvider": "GPKG_STORAGE_PROVIDER",
"storage": {
"internalPvc": {
"mountPath": "INTERNAL_PVC_MOUNT_PATH",
"gpkgSubPath": "GPKG_SUBPATH"
}
},
"ingestionSourcesDirPath": "INGESTION_SOURCES_DIR_PATH",
"servicesUrl": {
"mapproxyApi": "MAPPROXY_API_URL",
Expand Down Expand Up @@ -185,8 +191,7 @@
"cleanupExpirationDays": {
"__name": "EXPORT_CLEANUP_EXPIRATION_DAYS",
"__format": "number"
},
"gpkgsRootDir": "EXPORT_GPKGS_ROOT_DIR"
}
}
},
"tasks": {
Expand Down
9 changes: 7 additions & 2 deletions config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@
"disableHttpClientLogs": true,
"tilesStorageProvider": "FS",
"gpkgStorageProvider": "FS",
"storage": {
"internalPvc": {
"mountPath": "",
"gpkgSubPath": ""
}
},
"ingestionSourcesDirPath": "",
"linkTemplatesPath": "config/linkTemplates.template",
"servicesUrl": {
Expand Down Expand Up @@ -121,8 +127,7 @@
"pollingJobs": {
"export": {
"type": "Export",
"cleanupExpirationDays": 14,
"gpkgsRootDir": "gpkgs"
"cleanupExpirationDays": 14
}
},
"tasks": {
Expand Down
3 changes: 2 additions & 1 deletion helm/templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ data:
INGESTION_SEED_JOB_TYPE : {{ $jobDefinitions.jobs.seed.type | quote }}
EXPORT_JOB_TYPE: {{ $jobDefinitions.jobs.export.type | quote }}
EXPORT_CLEANUP_EXPIRATION_DAYS: {{ $jobDefinitions.jobs.export.cleanupExpirationDays | quote }}
EXPORT_GPKGS_ROOT_DIR: {{ $jobDefinitions.jobs.export.gpkgsRootDir | quote }}
INTERNAL_PVC_MOUNT_PATH: {{ $storage.fs.internalPvc.mountPath | quote }}
GPKG_SUBPATH: {{ $storage.fs.internalPvc.gpkgSubPath | quote }}
TILES_MERGING_TASK_TYPE: {{ $jobDefinitions.tasks.merge.type | quote }}
TILES_MERGING_TILE_BATCH_SIZE: {{ $jobDefinitions.tasks.merge.tileBatchSize | quote }}
TILES_MERGING_TASK_BATCH_SIZE: {{ $jobDefinitions.tasks.merge.taskBatchSize | quote }}
Expand Down
4 changes: 0 additions & 4 deletions helm/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
{{- $samePvc := and $storage.fs.ingestionSourcePvc.enabled $storage.fs.internalPvc.enabled (eq $storage.fs.internalPvc.name $storage.fs.ingestionSourcePvc.name) }}
{{- $internalVolumeName := ternary "ingestion-storage" "internal-storage" $samePvc }}

{{ $gpkgPath := (printf "%s/%s" $storage.fs.internalPvc.mountPath $storage.fs.internalPvc.gpkgSubPath) }}

{{- if .Values.enabled -}}
apiVersion: apps/v1
kind: Deployment
Expand Down Expand Up @@ -89,8 +87,6 @@ spec:
env:
- name: SERVER_PORT
value: {{ .Values.env.targetPort | quote }}
- name: GPKGS_LOCATION
value: {{ $gpkgPath }}
{{- if .Values.global.ca.secretName }}
- name: REQUESTS_CA_BUNDLE
value: {{ printf "%s/%s" .Values.global.ca.path .Values.global.ca.key | quote }}
Expand Down
1 change: 0 additions & 1 deletion helm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@ jobDefinitions:
export:
type: ""
cleanupExpirationDays: 14
gpkgsRootDir: "gpkgs"
tasks:
createTasks:
type: ""
Expand Down
23 changes: 10 additions & 13 deletions src/job/models/export/exportJobHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,13 @@ import { CallbackClient } from '../../../httpClients/callbackClient';
import { JobTrackerClient } from '../../../httpClients/jobTrackerClient';
import { PolygonPartsMangerClient } from '../../../httpClients/polygonPartsMangerClient';
import { convertObjectKeysToSnakeCase } from '../../../utils/db/dbUtils';
import { buildUrl } from '../../../utils/url';
import { ArtifactPathBuilder } from '../../../utils/storage/artifactPathBuilder';

@injectable()
export class ExportJobHandler extends JobHandler implements IJobHandler<ExportJob, ExportInitTask, ExportJob, ExportFinalizeTask> {
private readonly exportTaskType: string;
private readonly gpkgsRootDir: string;
private readonly isS3GpkgProvider: boolean;
private readonly cleanupExpirationDays: number;
private readonly downloadServerUrl: string;
public constructor(
@inject(SERVICES.LOGGER) logger: Logger,
@inject(SERVICES.CONFIG) config: IConfig,
Expand All @@ -68,16 +66,15 @@ export class ExportJobHandler extends JobHandler implements IJobHandler<ExportJo
private readonly fsService: FSService,
private readonly callbackClient: CallbackClient,
private readonly taskMetrics: TaskMetrics,
private readonly polygonPartsManagerClient: PolygonPartsMangerClient
private readonly polygonPartsManagerClient: PolygonPartsMangerClient,
private readonly pathBuilder: ArtifactPathBuilder
) {
super(logger, config, queueClient, jobTrackerClient);
this.exportTaskType = config.get<string>('jobManagement.export.tasks.tilesExporting.type');
this.gpkgsRootDir = config.get<string>('jobManagement.export.pollingJobs.export.gpkgsRootDir');
// eslint-disable-next-line @typescript-eslint/naming-convention
const gpkgProvider = config.get<StorageProvider>('gpkgStorageProvider');
this.isS3GpkgProvider = gpkgProvider === StorageProvider.S3;
this.cleanupExpirationDays = config.get<number>('jobManagement.export.pollingJobs.export.cleanupExpirationDays');
this.downloadServerUrl = config.get<string>('servicesUrl.downloadServerPublicDNS');
}
public async handleJobInit(job: ExportJob, task: ExportInitTask): Promise<void> {
await context.with(trace.setSpan(context.active(), this.tracer.startSpan(`${ExportJobHandler.name}.${this.handleJobInit.name}`)), async () => {
Expand Down Expand Up @@ -171,7 +168,7 @@ export class ExportJobHandler extends JobHandler implements IJobHandler<ExportJo
activeSpan?.setAttributes(monitorAttributes);

const gpkgRelativePath = job.parameters.additionalParams.packageRelativePath;
const gpkgFilePath = path.join('/', this.gpkgsRootDir, gpkgRelativePath);
const gpkgFilePath = this.pathBuilder.gpkgLocalPath(gpkgRelativePath);
const gpkgDirPath = path.dirname(gpkgFilePath);

return {
Expand Down Expand Up @@ -363,10 +360,10 @@ export class ExportJobHandler extends JobHandler implements IJobHandler<ExportJo
taskId: string,
taskParams: ExportFinalizeFullProcessingParams
): Promise<ExportFinalizeFullProcessingParams> {
const { gpkgFilePath } = paths;
const gpkgS3Key = path.posix.join(this.gpkgsRootDir, paths.gpkgRelativePath);
const jsonFilePath = gpkgFilePath.replace(/\.gpkg$/, '.json'); //TODO: In future, we will remove the json metadata file and support only gpkg
const jsonS3Key = gpkgS3Key.replace(/\.gpkg$/, '.json'); //TODO: In future, we will remove the json metadata file and support only gpkg
const { gpkgFilePath, gpkgRelativePath } = paths;
const gpkgS3Key = this.pathBuilder.gpkgS3Key(gpkgRelativePath);
const jsonFilePath = this.pathBuilder.jsonLocalPath(gpkgRelativePath); //TODO: In future, we will remove the json metadata file and support only gpkg
const jsonS3Key = this.pathBuilder.jsonS3Key(gpkgRelativePath); //TODO: In future, we will remove the json metadata file and support only gpkg

await this.s3Service.uploadFiles([
{ filePath: gpkgFilePath, s3Key: gpkgS3Key, contentType: GPKG_CONTENT_TYPE },
Expand Down Expand Up @@ -444,8 +441,8 @@ export class ExportJobHandler extends JobHandler implements IJobHandler<ExportJo
};

if (job.status === OperationStatus.COMPLETED) {
const gpkgDownloadUrl = buildUrl(this.downloadServerUrl, paths.gpkgFilePath);
const jsonDownloadUrl = gpkgDownloadUrl.replace(/\.gpkg$/, '.json'); //TODO: In future, we will remove the json metadata file and support only gpkg
const gpkgDownloadUrl = this.pathBuilder.gpkgDownloadUrl(paths.gpkgRelativePath);
const jsonDownloadUrl = this.pathBuilder.jsonDownloadUrl(paths.gpkgRelativePath); //TODO: In future, we will remove the json metadata file and support only gpkg

callbackResponse.artifacts = [
{
Expand Down
53 changes: 53 additions & 0 deletions src/utils/storage/artifactPathBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import path from 'path';
import { inject, injectable } from 'tsyringe';
import { SERVICES } from '../../common/constants';
import { IConfig } from '../../common/interfaces';
import { buildUrl } from '../url';

/* eslint-disable @typescript-eslint/naming-convention */
export const ARTIFACTS_EXTENSION = {
GPKG: '.gpkg',
JSON: '.json',
};
/* eslint-enable @typescript-eslint/naming-convention */

@injectable()
export class ArtifactPathBuilder {
private readonly internalMountPath: string;
private readonly gpkgSubPath: string;
private readonly downloadServerUrl: string;
private readonly gpkgPrefix: string;

public constructor(@inject(SERVICES.CONFIG) config: IConfig) {
this.internalMountPath = config.get<string>('storage.internalPvc.mountPath');
this.gpkgSubPath = config.get<string>('storage.internalPvc.gpkgSubPath');
this.downloadServerUrl = config.get<string>('servicesUrl.downloadServerPublicDNS');
this.gpkgPrefix = path.posix.basename(this.gpkgSubPath);
}

//GPKGs
public gpkgLocalPath(rel: string): string {
return path.join(this.internalMountPath, this.gpkgSubPath, rel);
}

public gpkgS3Key(rel: string): string {
return path.posix.join(this.gpkgPrefix, rel);
}

public gpkgDownloadUrl(rel: string): string {
return buildUrl(this.downloadServerUrl, this.gpkgPrefix, rel);
}

//JSONs(metadata)
public jsonLocalPath(rel: string): string {
return this.gpkgLocalPath(rel).replace(ARTIFACTS_EXTENSION.GPKG, ARTIFACTS_EXTENSION.JSON);
}

public jsonS3Key(rel: string): string {
return this.gpkgS3Key(rel).replace(ARTIFACTS_EXTENSION.GPKG, ARTIFACTS_EXTENSION.JSON);
}

public jsonDownloadUrl(rel: string): string {
return this.gpkgDownloadUrl(rel).replace(ARTIFACTS_EXTENSION.GPKG, ARTIFACTS_EXTENSION.JSON);
}
}
10 changes: 3 additions & 7 deletions tests/unit/job/exportJobHandler/exportJobHandler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,14 @@ describe('ExportJobHandler', () => {
});

describe('handleJobFinalize', () => {
const gpkgsRootDir = 'gpkgs';
const mountPath = '/outputs';
const gpkgSubPath = 'raster/artifacts/gpkgs';
const gpkgRelativePath = 'package.gpkg';
const gpkgFilePath = path.join('/', gpkgsRootDir, gpkgRelativePath);
const gpkgFilePath = path.join(mountPath, gpkgSubPath, gpkgRelativePath);
const jsonFilePath = gpkgFilePath.replace('.gpkg', '.json');
const gpkgDirPath = '/path/to/gpkgs';
let joinSpy: jest.SpyInstance;
let dirnameSpy: jest.SpyInstance;
beforeEach(() => {
joinSpy = jest.spyOn(path, 'join').mockReturnValue(gpkgFilePath);
dirnameSpy = jest.spyOn(path, 'dirname').mockReturnValue(gpkgDirPath);
});

Expand Down Expand Up @@ -165,7 +164,6 @@ describe('ExportJobHandler', () => {
await exportJobHandler.handleJobFinalize(job, task);

// Verify path methods were called correctly
expect(joinSpy).toHaveBeenCalledWith('/', gpkgsRootDir, gpkgRelativePath);
expect(dirnameSpy).toHaveBeenCalledWith(gpkgFilePath);

// Verify metadata processing
Expand Down Expand Up @@ -257,8 +255,6 @@ describe('ExportJobHandler', () => {

await exportJobHandler.handleJobFinalize(job, task);

expect(joinSpy).toHaveBeenCalledWith('/', gpkgsRootDir, gpkgRelativePath);

expect(s3ServiceMock.uploadFiles).toHaveBeenCalledWith([
{
filePath: gpkgFilePath,
Expand Down
6 changes: 5 additions & 1 deletion tests/unit/job/exportJobHandler/exportJobHandlerSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { FSService } from '../../../../src/utils/storage/fsService';
import { CallbackClient } from '../../../../src/httpClients/callbackClient';
import { JobTrackerClient } from '../../../../src/httpClients/jobTrackerClient';
import { PolygonPartsMangerClient } from '../../../../src/httpClients/polygonPartsMangerClient';
import { ArtifactPathBuilder } from '../../../../src/utils/storage/artifactPathBuilder';

export interface ExportJobHandlerTestContext {
configMock: IConfig;
Expand Down Expand Up @@ -57,6 +58,8 @@ export const setupExportJobHandlerTest = (): ExportJobHandlerTestContext => {
getAggregatedLayerMetadata: jest.fn(),
} as unknown as jest.Mocked<PolygonPartsMangerClient>;

const pathBuilder = new ArtifactPathBuilder(configMock);

const exportJobHandler = new ExportJobHandler(
jsLogger({ enabled: false }),
configMock,
Expand All @@ -69,7 +72,8 @@ export const setupExportJobHandlerTest = (): ExportJobHandlerTestContext => {
fsServiceMock,
callbackClientMock,
taskMetricsMock,
polygonPartsManagerClientMock
polygonPartsManagerClientMock,
pathBuilder
);

return {
Expand Down
7 changes: 6 additions & 1 deletion tests/unit/mocks/configMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ const registerDefaultConfig = (): void => {
ingestionSourcesDirPath: '/layerSources',
tilesStorageProvider: 'FS',
gpkgStorageProvider: 'FS',
storage: {
internalPvc: {
mountPath: '/outputs',
gpkgSubPath: 'raster/artifacts/gpkgs',
},
},
disableHttpClientLogs: true,
linkTemplatesPath: 'config/linkTemplates.template',
servicesUrl: {
Expand Down Expand Up @@ -170,7 +176,6 @@ const registerDefaultConfig = (): void => {
pollingJobs: {
export: {
type: 'Export',
gpkgsRootDir: 'gpkgs',
},
},
tasks: {
Expand Down
59 changes: 59 additions & 0 deletions tests/unit/utils/artifactPathBuilder.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import path from 'path';
import { faker } from '@faker-js/faker';
import { ArtifactPathBuilder, ARTIFACTS_EXTENSION } from '../../../src/utils/storage/artifactPathBuilder';
import { configMock, registerDefaultConfig } from '../mocks/configMock';

describe('ArtifactPathBuilder', () => {
let internalMountPath: string;
let gpkgSubPath: string;
let downloadServerPublicDNS: string;
let gpkgPrefix: string;

beforeEach(() => {
registerDefaultConfig();
internalMountPath = configMock.get('storage.internalPvc.mountPath');
gpkgSubPath = configMock.get('storage.internalPvc.gpkgSubPath');
downloadServerPublicDNS = configMock.get('servicesUrl.downloadServerPublicDNS');
gpkgPrefix = path.posix.basename(gpkgSubPath);
});

const rel = `${faker.string.uuid()}/package.gpkg`;

describe('gpkg paths', () => {
it('builds the local FS path under mountPath + gpkgSubPath', () => {
const builder = new ArtifactPathBuilder(configMock);
expect(builder.gpkgLocalPath(rel)).toBe(`${internalMountPath}/${gpkgSubPath}/${rel}`);
});

it('builds the S3 key relative to the artifacts anchor', () => {
const builder = new ArtifactPathBuilder(configMock);
expect(builder.gpkgS3Key(rel)).toBe(`${gpkgPrefix}/${rel}`);
});

it('builds the public download URL', () => {
const builder = new ArtifactPathBuilder(configMock);
expect(builder.gpkgDownloadUrl(rel)).toBe(`${downloadServerPublicDNS}/${gpkgPrefix}/${rel}`);
});
});

describe('json sidecar paths', () => {
it('swaps .gpkg → .json in the local path', () => {
const builder = new ArtifactPathBuilder(configMock);
expect(builder.jsonLocalPath(rel)).toBe(
`${internalMountPath}/${gpkgSubPath}/${rel.replace(ARTIFACTS_EXTENSION.GPKG, ARTIFACTS_EXTENSION.JSON)}`
);
});

it('swaps .gpkg → .json in the S3 key', () => {
const builder = new ArtifactPathBuilder(configMock);
expect(builder.jsonS3Key(rel)).toBe(`${gpkgPrefix}/${rel.replace(ARTIFACTS_EXTENSION.GPKG, ARTIFACTS_EXTENSION.JSON)}`);
});

it('swaps .gpkg → .json in the download URL', () => {
const builder = new ArtifactPathBuilder(configMock);
expect(builder.jsonDownloadUrl(rel)).toBe(
`${downloadServerPublicDNS}/${gpkgPrefix}/${rel.replace(ARTIFACTS_EXTENSION.GPKG, ARTIFACTS_EXTENSION.JSON)}`
);
});
});
});
Loading