Skip to content

Commit

Permalink
Add backend routes and frontend service for admin CRUD of ModelRegist…
Browse files Browse the repository at this point in the history
…ries

Signed-off-by: Mike Turley <[email protected]>
  • Loading branch information
mturley committed May 15, 2024
1 parent 8645fe6 commit b5b8257
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 2 deletions.
180 changes: 180 additions & 0 deletions backend/src/routes/api/modelRegistries/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import { FastifyReply, FastifyRequest } from 'fastify';
import { PatchUtils } from '@kubernetes/client-node';
import { secureAdminRoute } from '../../../utils/route-security';
import { KubeFastifyInstance, ModelRegistryKind, RecursivePartial } from '../../../types';
import { MODEL_REGISTRY_NAMESPACE } from '../../../utils/constants';

export default async (fastify: KubeFastifyInstance): Promise<void> => {
fastify.get(
'/',
secureAdminRoute(fastify)(
async (
request: FastifyRequest<{ Querystring: { labelSelector: string } }>,
reply: FastifyReply,
) => {
const { labelSelector } = request.query;
try {
const response = await fastify.kube.customObjectsApi.listNamespacedCustomObject(
'modelregistry.opendatahub.io',
'v1alpha1',
MODEL_REGISTRY_NAMESPACE,
'modelregistries',
undefined,
undefined,
undefined,
labelSelector,
);
return response.body;
} catch (e) {
fastify.log.error(
`ModelRegistries could not be listed, ${e.response?.body?.message || e.message}`,
);
reply.send(e);
}
},
),
);

fastify.post(
'/',
secureAdminRoute(fastify)(
async (
request: FastifyRequest<{
Querystring: { dryRun?: string };
Body: ModelRegistryKind;
}>,
reply: FastifyReply,
) => {
const { dryRun } = request.query;
const modelRegistry = request.body;
try {
const response = await fastify.kube.customObjectsApi.createNamespacedCustomObject(
'modelregistry.opendatahub.io',
'v1alpha1',
MODEL_REGISTRY_NAMESPACE,
'modelregistries',
request.body,
undefined,
dryRun,
);
return response.body;
} catch (e) {
fastify.log.error(
`ModelRegistry ${modelRegistry.metadata.name} could not be created, ${
e.response?.body?.message || e.message
}`,
);
reply.send(e);
}
},
),
);

fastify.get(
'/:modelRegistryName',
secureAdminRoute(fastify)(
async (
request: FastifyRequest<{ Params: { modelRegistryName: string } }>,
reply: FastifyReply,
) => {
const { modelRegistryName } = request.params;
try {
const response = await fastify.kube.customObjectsApi.getNamespacedCustomObject(
'modelregistry.opendatahub.io',
'v1alpha1',
MODEL_REGISTRY_NAMESPACE,
'modelregistries',
modelRegistryName,
);
return response.body;
} catch (e) {
fastify.log.error(
`ModelRegistry ${modelRegistryName} could not be read, ${
e.response?.body?.message || e.message
}`,
);
reply.send(e);
}
},
),
);

fastify.patch(
'/:modelRegistryName',
secureAdminRoute(fastify)(
async (
request: FastifyRequest<{
Querystring: { dryRun?: string };
Params: { modelRegistryName: string };
Body: RecursivePartial<ModelRegistryKind>;
}>,
reply: FastifyReply,
) => {
const options = {
headers: { 'Content-type': PatchUtils.PATCH_FORMAT_JSON_PATCH },
};
const { dryRun } = request.query;
const { modelRegistryName } = request.params;
try {
const response = await fastify.kube.customObjectsApi.patchNamespacedCustomObject(
'modelregistry.opendatahub.io',
'v1alpha1',
MODEL_REGISTRY_NAMESPACE,
'modelregistries',
modelRegistryName,
request.body,
dryRun,
undefined,
undefined,
options,
);
return response.body;
} catch (e) {
fastify.log.error(
`ModelRegistry ${modelRegistryName} could not be modified, ${
e.response?.body?.message || e.message
}`,
);
reply.send(e);
}
},
),
);

fastify.delete(
'/:modelRegistryName',
secureAdminRoute(fastify)(
async (
request: FastifyRequest<{
Querystring: { dryRun?: string };
Params: { modelRegistryName: string };
}>,
reply: FastifyReply,
) => {
const { dryRun } = request.query;
const { modelRegistryName } = request.params;
try {
const response = await fastify.kube.customObjectsApi.deleteNamespacedCustomObject(
'modelregistry.opendatahub.io',
'v1alpha1',
MODEL_REGISTRY_NAMESPACE,
'modelregistries',
modelRegistryName,
undefined,
undefined,
undefined,
dryRun,
);
return response.body;
} catch (e) {
fastify.log.error(
`ModelRegistry ${modelRegistryName} could not be deleted, ${
e.response?.body?.message || e.message
}`,
);
reply.send(e);
}
},
),
);
};
4 changes: 2 additions & 2 deletions backend/src/routes/api/service/modelregistry/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import httpProxy from '@fastify/http-proxy';
import { KubeFastifyInstance } from '../../../../types';
import { DEV_MODE } from '../../../../utils/constants';
import { DEV_MODE, MODEL_REGISTRY_NAMESPACE } from '../../../../utils/constants';
import { getParam, setParam } from '../../../../utils/proxy';

export default async (fastify: KubeFastifyInstance): Promise<void> => {
Expand All @@ -20,7 +20,7 @@ export default async (fastify: KubeFastifyInstance): Promise<void> => {
// kubectl port-forward -n <namespace> svc/<service-name> <local.port>:<service.port>
`http://${process.env.MODEL_REGISTRY_SERVICE_HOST}:${process.env.MODEL_REGISTRY_SERVICE_PORT}`
: // Construct service URL
`http://${name}.odh-model-registries.svc.cluster.local:8080`;
`http://${name}.${MODEL_REGISTRY_NAMESPACE}.svc.cluster.local:8080`;

// assign the `upstream` param so we can dynamically set the upstream URL for http-proxy
setParam(request, 'upstream', upstream);
Expand Down
30 changes: 30 additions & 0 deletions backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,36 @@ export type TrustyAIKind = K8sResourceCommon & {
};

export type ModelRegistryKind = K8sResourceCommon & {
metadata: {
name: string;
namespace: string;
};
spec: {
grpc: {
port: number;
};
rest: {
port: number;
serviceRoute: string;
};
mysql?: {
database: string;
host: string;
port?: number;
};
postgres: {
database: string;
host?: string;
passwordSecret?: {
key: string;
name: string;
};
port: number;
skipDBCreation?: boolean;
sslMode?: string;
username?: string;
};
};
status?: {
conditions?: K8sCondition[];
};
Expand Down
2 changes: 2 additions & 0 deletions backend/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,5 @@ export const THANOS_RBAC_PORT = '9092';
export const THANOS_INSTANCE_NAME = 'thanos-querier';
export const THANOS_NAMESPACE = 'openshift-monitoring';
export const LABEL_SELECTOR_DASHBOARD_RESOURCE = `${KnownLabels.DASHBOARD_RESOURCE}=true`;

export const MODEL_REGISTRY_NAMESPACE = 'odh-model-registries';
60 changes: 60 additions & 0 deletions frontend/src/services/modelRegistryService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import axios from 'axios';
import { ModelRegistryKind } from '~/k8sTypes';
import { RecursivePartial } from '~/typeHelpers';

export const listModelRegistriesBackend = (

Check warning on line 5 in frontend/src/services/modelRegistryService.ts

View check run for this annotation

Codecov / codecov/patch

frontend/src/services/modelRegistryService.ts#L5

Added line #L5 was not covered by tests
labelSelector?: string,
): Promise<ModelRegistryKind[]> => {
const url = '/api/modelRegistries';
return axios

Check warning on line 9 in frontend/src/services/modelRegistryService.ts

View check run for this annotation

Codecov / codecov/patch

frontend/src/services/modelRegistryService.ts#L7-L9

Added lines #L7 - L9 were not covered by tests
.get(url, { params: { labelSelector } })
.then((response) => response.data.items)
.catch((e) => {
throw new Error(e.response.data.message);

Check warning on line 13 in frontend/src/services/modelRegistryService.ts

View check run for this annotation

Codecov / codecov/patch

frontend/src/services/modelRegistryService.ts#L11-L13

Added lines #L11 - L13 were not covered by tests
});
};

export const createModelRegistryBackend = (data: ModelRegistryKind): Promise<ModelRegistryKind> => {
const url = '/api/modelRegistries';
return axios

Check warning on line 19 in frontend/src/services/modelRegistryService.ts

View check run for this annotation

Codecov / codecov/patch

frontend/src/services/modelRegistryService.ts#L17-L19

Added lines #L17 - L19 were not covered by tests
.post(url, data)
.then((response) => response.data)
.catch((e) => {
throw new Error(e.response.data.message);

Check warning on line 23 in frontend/src/services/modelRegistryService.ts

View check run for this annotation

Codecov / codecov/patch

frontend/src/services/modelRegistryService.ts#L21-L23

Added lines #L21 - L23 were not covered by tests
});
};

export const getModelRegistryBackend = (modelRegistryName: string): Promise<ModelRegistryKind> => {
const url = `/api/modelRegistries/${modelRegistryName}`;
return axios

Check warning on line 29 in frontend/src/services/modelRegistryService.ts

View check run for this annotation

Codecov / codecov/patch

frontend/src/services/modelRegistryService.ts#L27-L29

Added lines #L27 - L29 were not covered by tests
.get(url)
.then((response) => response.data)
.catch((e) => {
throw new Error(e.response.data.message);

Check warning on line 33 in frontend/src/services/modelRegistryService.ts

View check run for this annotation

Codecov / codecov/patch

frontend/src/services/modelRegistryService.ts#L31-L33

Added lines #L31 - L33 were not covered by tests
});
};

export const updateModelRegistryBackend = (

Check warning on line 37 in frontend/src/services/modelRegistryService.ts

View check run for this annotation

Codecov / codecov/patch

frontend/src/services/modelRegistryService.ts#L37

Added line #L37 was not covered by tests
modelRegistryName: string,
patch: RecursivePartial<ModelRegistryKind>,
): Promise<ModelRegistryKind> => {
const url = `/api/modelRegistries/${modelRegistryName}`;
return axios

Check warning on line 42 in frontend/src/services/modelRegistryService.ts

View check run for this annotation

Codecov / codecov/patch

frontend/src/services/modelRegistryService.ts#L40-L42

Added lines #L40 - L42 were not covered by tests
.patch(url, patch)
.then((response) => response.data)
.catch((e) => {
throw new Error(e.response.data.message);

Check warning on line 46 in frontend/src/services/modelRegistryService.ts

View check run for this annotation

Codecov / codecov/patch

frontend/src/services/modelRegistryService.ts#L44-L46

Added lines #L44 - L46 were not covered by tests
});
};

export const deleteModelRegistryBackend = (

Check warning on line 50 in frontend/src/services/modelRegistryService.ts

View check run for this annotation

Codecov / codecov/patch

frontend/src/services/modelRegistryService.ts#L50

Added line #L50 was not covered by tests
modelRegistryName: string,
): Promise<ModelRegistryKind> => {
const url = `/api/modelRegistries/${modelRegistryName}`;
return axios

Check warning on line 54 in frontend/src/services/modelRegistryService.ts

View check run for this annotation

Codecov / codecov/patch

frontend/src/services/modelRegistryService.ts#L52-L54

Added lines #L52 - L54 were not covered by tests
.delete(url)
.then((response) => response.data)
.catch((e) => {
throw new Error(e.response.data.message);

Check warning on line 58 in frontend/src/services/modelRegistryService.ts

View check run for this annotation

Codecov / codecov/patch

frontend/src/services/modelRegistryService.ts#L56-L58

Added lines #L56 - L58 were not covered by tests
});
};
12 changes: 12 additions & 0 deletions manifests/base/cluster-role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,15 @@ rules:
- get
resources:
- dscinitializations
- apiGroups:
- modelregistry.opendatahub.io
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
resources:
- modelregistries

0 comments on commit b5b8257

Please sign in to comment.