commit
						124f194f3a
					
				|  | @ -1,3 +1,10 @@ | ||||||
|  | # v1.4.1 | ||||||
|  | 
 | ||||||
|  | Released 2021-09-21 | ||||||
|  | 
 | ||||||
|  | - `k8s-csi-cleaner` script (see #81) | ||||||
|  | - bump deps | ||||||
|  | 
 | ||||||
| # v1.4.0 | # v1.4.0 | ||||||
| 
 | 
 | ||||||
| Released 2021-09-21 | Released 2021-09-21 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,177 @@ | ||||||
|  | #!/usr/bin/env -S node --nouse-idle-notification --expose-gc | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * The purpose of this script is to prune volumes in the storage system which | ||||||
|  |  * do not have correlating PVs in k8s. | ||||||
|  |  *  | ||||||
|  |  * kubectl -n democratic-csi exec -ti <controller pod> --container=csi-driver -- bash | ||||||
|  |  * ./bin/k8s-csi-cleaner | ||||||
|  |  * | ||||||
|  |  * env vars: | ||||||
|  |  * # prevents manual input on a per-volume basis to confirm delete action | ||||||
|  |  * # default is 0 | ||||||
|  |  * AUTO_DELETE=1 | ||||||
|  |  * | ||||||
|  |  * # outputs to the console which volumes would be cleaned vs not | ||||||
|  |  * # default is 0 | ||||||
|  |  * DRY_RUN=1 | ||||||
|  |  * | ||||||
|  |  * # endpoint for the csi grpc connection | ||||||
|  |  * # default is unix:///csi-data/csi.sock | ||||||
|  |  * CSI_ENDPOINT="localhost:50051" | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | const _ = require("lodash"); | ||||||
|  | const k8s = require("@kubernetes/client-node"); | ||||||
|  | const prompt = require("prompt"); | ||||||
|  | 
 | ||||||
|  | prompt.start(); | ||||||
|  | 
 | ||||||
|  | const PROTO_PATH = __dirname + "/../csi_proto/csi-v1.5.0.proto"; | ||||||
|  | //var grpc = require("grpc-uds"); | ||||||
|  | var grpc = require("@grpc/grpc-js"); | ||||||
|  | var protoLoader = require("@grpc/proto-loader"); | ||||||
|  | const { rsort } = require("semver"); | ||||||
|  | // Suggested options for similarity to existing grpc.load behavior | ||||||
|  | var packageDefinition = protoLoader.loadSync(PROTO_PATH, { | ||||||
|  |   keepCase: true, | ||||||
|  |   longs: String, | ||||||
|  |   enums: String, | ||||||
|  |   defaults: true, | ||||||
|  |   oneofs: true, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | var protoDescriptor = grpc.loadPackageDefinition(packageDefinition); | ||||||
|  | 
 | ||||||
|  | // The protoDescriptor object has the full package hierarchy | ||||||
|  | var csi = protoDescriptor.csi.v1; | ||||||
|  | //console.log(csi); | ||||||
|  | 
 | ||||||
|  | var connectionEndpoint = | ||||||
|  |   process.env.CSI_ENDPOINT || "unix:///csi-data/csi.sock"; | ||||||
|  | 
 | ||||||
|  | var clientIdentity = new csi.Identity( | ||||||
|  |   connectionEndpoint, | ||||||
|  |   grpc.credentials.createInsecure() | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | var clientController = new csi.Controller( | ||||||
|  |   connectionEndpoint, | ||||||
|  |   grpc.credentials.createInsecure() | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | var clientNode = new csi.Node( | ||||||
|  |   connectionEndpoint, | ||||||
|  |   grpc.credentials.createInsecure() | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | async function executeRPC(service, methodName, options = {}) { | ||||||
|  |   //console.log(service[methodName]); | ||||||
|  |   return new Promise((resolve, reject) => { | ||||||
|  |     const call = service[methodName](options, (error, data) => { | ||||||
|  |       //console.log("%s - error: %j, data: %j", methodName, error, data); | ||||||
|  | 
 | ||||||
|  |       if (error) { | ||||||
|  |         reject(error); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       resolve(data); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function runControllerListVolumes(starting_token = "") { | ||||||
|  |   const req = { | ||||||
|  |     //max_entries: 3, | ||||||
|  |     //starting_token: "77e73621-6fbc-4aec-9d6b-fc9ac2fd1a44:2" | ||||||
|  |     starting_token, | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   return executeRPC(clientController, "ListVolumes", req); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function runControllerDeleteVolume(volume_id) { | ||||||
|  |   const req = { | ||||||
|  |     volume_id: volume_id, | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   return executeRPC(clientController, "DeleteVolume", req); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function main() { | ||||||
|  |   // get k8s volumes | ||||||
|  |   let k8sVolumes = await new Promise((resolve, reject) => { | ||||||
|  |     const kc = new k8s.KubeConfig(); | ||||||
|  |     kc.loadFromDefault(); | ||||||
|  |     const k8sApi = kc.makeApiClient(k8s.CoreV1Api); | ||||||
|  |     // V1PersistentVolumeList | ||||||
|  |     k8sApi.listPersistentVolume().then((res) => { | ||||||
|  |       //console.log(res); | ||||||
|  |       //console.dir(res.body.items, { depth: null }); // `depth: null` ensures unlimited recursion | ||||||
|  |       resolve(res.body.items); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  |   console.log(`${k8sVolumes.length} k8s PVs discovered`); | ||||||
|  | 
 | ||||||
|  |   // get csi volumes | ||||||
|  |   let res; | ||||||
|  |   let csiVolumes = []; | ||||||
|  |   do { | ||||||
|  |     res = await runControllerListVolumes(); | ||||||
|  |     csiVolumes = csiVolumes.concat(res.entries); | ||||||
|  |   } while (res.next_token); | ||||||
|  |   console.log(`${csiVolumes.length} csi volumes discovered`); | ||||||
|  | 
 | ||||||
|  |   //console.log(k8sVolumes); | ||||||
|  |   //console.log(csiVolumes); | ||||||
|  | 
 | ||||||
|  |   for (let csiVolume of csiVolumes) { | ||||||
|  |     let volume_id = csiVolume.volume.volume_id; | ||||||
|  |     //console.log(`processing csi volume ${volume_id}`); | ||||||
|  |     let k8sVolume = k8sVolumes.find((i_k8sVolume) => { | ||||||
|  |       let volume_handle = _.get(i_k8sVolume, "spec.csi.volumeHandle", null); | ||||||
|  |       return volume_handle == volume_id; | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     if (!k8sVolume) { | ||||||
|  |       console.log(`volume ${volume_id} is NOT in k8s`); | ||||||
|  |       if (process.env.DRY_RUN == "1") { | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       let del = false; | ||||||
|  |       if (process.env.AUTO_DELETE == "1") { | ||||||
|  |         del = true; | ||||||
|  |       } else { | ||||||
|  |         res = await prompt.get([ | ||||||
|  |           { | ||||||
|  |             name: "delete", | ||||||
|  |             required: true, | ||||||
|  |             type: "boolean", | ||||||
|  |           }, | ||||||
|  |         ]); | ||||||
|  |         del = res.delete; | ||||||
|  |       } | ||||||
|  |       if (del) { | ||||||
|  |         res = await runControllerDeleteVolume(volume_id); | ||||||
|  |         console.log(`csi volume ${volume_id} deleted`); | ||||||
|  |       } else { | ||||||
|  |         console.log(`skipping delete of csi volume ${volume_id}`); | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       console.log(`volume ${volume_id} is in k8s`); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   console.log("Fin"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | if (require.main === module) { | ||||||
|  |   (async function () { | ||||||
|  |     try { | ||||||
|  |       await main(); | ||||||
|  |     } catch (e) { | ||||||
|  |       console.log(e); | ||||||
|  |     } | ||||||
|  |   })(); | ||||||
|  | } | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "democratic-csi", |   "name": "democratic-csi", | ||||||
|   "version": "1.4.0", |   "version": "1.4.1", | ||||||
|   "description": "kubernetes csi driver framework", |   "description": "kubernetes csi driver framework", | ||||||
|   "main": "bin/democratic-csi", |   "main": "bin/democratic-csi", | ||||||
|   "scripts": { |   "scripts": { | ||||||
|  | @ -20,6 +20,7 @@ | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@grpc/grpc-js": "^1.3.6", |     "@grpc/grpc-js": "^1.3.6", | ||||||
|     "@grpc/proto-loader": "^0.6.0", |     "@grpc/proto-loader": "^0.6.0", | ||||||
|  |     "@kubernetes/client-node": "^0.15.1", | ||||||
|     "async-mutex": "^0.3.1", |     "async-mutex": "^0.3.1", | ||||||
|     "bunyan": "^1.8.15", |     "bunyan": "^1.8.15", | ||||||
|     "grpc-uds": "^0.1.6", |     "grpc-uds": "^0.1.6", | ||||||
|  | @ -27,6 +28,7 @@ | ||||||
|     "js-yaml": "^4.0.0", |     "js-yaml": "^4.0.0", | ||||||
|     "lodash": "^4.17.21", |     "lodash": "^4.17.21", | ||||||
|     "lru-cache": "^6.0.0", |     "lru-cache": "^6.0.0", | ||||||
|  |     "prompt": "^1.2.0", | ||||||
|     "request": "^2.88.2", |     "request": "^2.88.2", | ||||||
|     "semver": "^7.3.4", |     "semver": "^7.3.4", | ||||||
|     "ssh2": "^1.1.0", |     "ssh2": "^1.1.0", | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue