Skip to content

Commit ecd8583

Browse files
telpirionbcoe
andauthored
feat: adds cleaner utility for orphaned resources (#34)
* feat: adds cleaner utility function Co-authored-by: Benjamin E. Coe <[email protected]>
1 parent c6916e6 commit ecd8583

File tree

2 files changed

+218
-1
lines changed

2 files changed

+218
-1
lines changed

ai-platform/snippets/test/clean.js

+212
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/**
16+
* This module contains utility functions for removing unneeded, stale, or
17+
* orphaned resources from test project.
18+
*
19+
* Removes:
20+
* - Datasets
21+
* - Training pipelines
22+
* - Models
23+
* - Endpoints
24+
* - Batch prediction jobs
25+
*/
26+
const MAXIMUM_AGE = 3600000 * 24 * 2; // 2 days in milliseconds
27+
const TEMP_RESOURCE_PREFIX = 'temp';
28+
const LOCATION = 'us-central1';
29+
30+
// All AI Platform resources need to specify a hostname.
31+
const clientOptions = {
32+
apiEndpoint: 'us-central1-aiplatform.googleapis.com',
33+
};
34+
35+
/**
36+
* Determines whether a resource should be deleted based upon its
37+
* age and name.
38+
* @param {string} displayName the display name of the resource
39+
* @param {Timestamp} createTime when the resource is created
40+
* @returns {bool}
41+
*/
42+
function checkDeletionStatus(displayName, createTime) {
43+
const NOW = new Date();
44+
// Check whether this resources is a temporary resource
45+
if (displayName.indexOf(TEMP_RESOURCE_PREFIX) === -1) {
46+
return false;
47+
}
48+
49+
// Check how old the resource is
50+
const ageOfResource = new Date(createTime.seconds * 1000);
51+
if (NOW - ageOfResource < MAXIMUM_AGE) {
52+
return false;
53+
}
54+
55+
return true;
56+
}
57+
58+
/**
59+
* Removes all temporary datasets older than the maximum age.
60+
* @param {string} projectId the project to remove datasets from
61+
* @returns {Promise}
62+
*/
63+
async function cleanDatasets(projectId) {
64+
const {DatasetServiceClient} = require('@google-cloud/aiplatform');
65+
const datasetServiceClient = new DatasetServiceClient(clientOptions);
66+
67+
const [datasets] = await datasetServiceClient.listDatasets({
68+
parent: `projects/${projectId}/locations/${LOCATION}`,
69+
});
70+
71+
for (const dataset of datasets) {
72+
const {displayName, createTime, name} = dataset;
73+
74+
if (checkDeletionStatus(displayName, createTime)) {
75+
await datasetServiceClient.deleteDataset({
76+
name,
77+
});
78+
}
79+
}
80+
}
81+
82+
/**
83+
* Removes all temporary training pipelines older than the maximum age.
84+
* @param {string} projectId the project to remove pipelines from
85+
* @returns {Promise}
86+
*/
87+
async function cleanTrainingPipelines(projectId) {
88+
const {PipelineServiceClient} = require('@google-cloud/aiplatform');
89+
const pipelineServiceClient = new PipelineServiceClient(clientOptions);
90+
91+
const [pipelines] = await pipelineServiceClient.listTrainingPipelines({
92+
parent: `projects/${projectId}/locations/${LOCATION}`,
93+
});
94+
95+
for (const pipeline of pipelines) {
96+
const {displayName, createTime, name} = pipeline;
97+
98+
if (checkDeletionStatus(displayName, createTime)) {
99+
await pipelineServiceClient.deleteTrainingPipeline({
100+
name,
101+
});
102+
}
103+
}
104+
}
105+
106+
/**
107+
* Removes all temporary models older than the maximum age.
108+
* @param {string} projectId the project to remove models from
109+
* @returns {Promise}
110+
*/
111+
async function cleanModels(projectId) {
112+
const {
113+
ModelServiceClient,
114+
EndpointServiceClient,
115+
} = require('@google-cloud/aiplatform');
116+
const modelServiceClient = new ModelServiceClient(clientOptions);
117+
118+
const [models] = await modelServiceClient.listModels({
119+
parent: `projects/${projectId}/locations/${LOCATION}`,
120+
});
121+
122+
for (const model of models) {
123+
const {displayName, createTime, deployedModels, name} = model;
124+
125+
if (checkDeletionStatus(displayName, createTime)) {
126+
// Need to check if model is deployed to an endpoint
127+
// Undeploy the model everywhere it is deployed
128+
for (const deployedModel of deployedModels) {
129+
const {endpoint, deployedModelId} = deployedModel;
130+
131+
const endpointServiceClient = new EndpointServiceClient(clientOptions);
132+
await endpointServiceClient.undeployModel({
133+
endpoint,
134+
deployedModelId,
135+
});
136+
}
137+
138+
await modelServiceClient.deleteModel({
139+
name,
140+
});
141+
}
142+
}
143+
}
144+
145+
/**
146+
* Removes all temporary endpoints older than the maximum age.
147+
* @param {string} projectId the project to remove endpoints from
148+
* @returns {Promise}
149+
*/
150+
async function cleanEndpoints(projectId) {
151+
const {EndpointServiceClient} = require('@google-cloud/aiplatform');
152+
const endpointServiceClient = new EndpointServiceClient(clientOptions);
153+
154+
const [endpoints] = await endpointServiceClient.listEndpoints({
155+
parent: `projects/${projectId}/locations/${LOCATION}`,
156+
});
157+
158+
for (const endpoint of endpoints) {
159+
const {displayName, createTime, name} = endpoint;
160+
161+
if (checkDeletionStatus(displayName, createTime)) {
162+
await endpointServiceClient.deleteEndpoint({
163+
name,
164+
});
165+
}
166+
}
167+
}
168+
169+
/**
170+
* Removes all temporary batch prediction jobs
171+
* @param {string} projectId the project to remove prediction jobs from
172+
* @returns {Promise}
173+
*/
174+
async function cleanBatchPredictionJobs(projectId) {
175+
const {JobServiceClient} = require('@google-cloud/aiplatform');
176+
const jobServiceClient = new JobServiceClient(clientOptions);
177+
178+
const [batchPredictionJobs] = await jobServiceClient.listBatchPredictionJobs({
179+
parent: `projects/${projectId}/locations/${LOCATION}`,
180+
});
181+
182+
for (const job of batchPredictionJobs) {
183+
const {displayName, createTime, name} = job;
184+
if (checkDeletionStatus(displayName, createTime)) {
185+
await jobServiceClient.deleteBatchPredictionJob({
186+
name,
187+
});
188+
}
189+
}
190+
}
191+
192+
/**
193+
* Removes all of the temporary resources older than the maximum age.
194+
* @param {string} projectId the project to remove resources from
195+
* @returns {Promise}
196+
*/
197+
async function cleanAll(projectId) {
198+
await cleanDatasets(projectId);
199+
await cleanTrainingPipelines(projectId);
200+
await cleanModels(projectId);
201+
await cleanEndpoints(projectId);
202+
await cleanBatchPredictionJobs(projectId);
203+
}
204+
205+
module.exports = {
206+
cleanAll: cleanAll,
207+
cleanDatasets: cleanDatasets,
208+
cleanTrainingPipelines: cleanTrainingPipelines,
209+
cleanModels: cleanModels,
210+
cleanEndpoints: cleanEndpoints,
211+
cleanBatchPredictionJobs: cleanBatchPredictionJobs,
212+
};

ai-platform/snippets/test/create-training-pipeline-image-classification.test.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
'use strict';
1818

1919
const {assert} = require('chai');
20-
const {after, describe, it} = require('mocha');
20+
const {after, before, describe, it} = require('mocha');
21+
const clean = require('./clean');
2122

2223
const uuid = require('uuid').v4;
2324
const cp = require('child_process');
@@ -41,6 +42,10 @@ const location = process.env.LOCATION;
4142
let trainingPipelineId;
4243

4344
describe('AI platform create training pipeline image classification', () => {
45+
before('should delete any old and/or orphaned resources', async () => {
46+
await clean.cleanTrainingPipelines(project);
47+
});
48+
4449
it('should create a new image classification training pipeline', async () => {
4550
const stdout = execSync(
4651
`node ./create-training-pipeline-image-classification.js ${datasetId} ${modelDisplayName} ${trainingPipelineDisplayName} ${project} ${location}`

0 commit comments

Comments
 (0)