kubernetesApiClient.ts 8.4 KB
Newer Older
liuzhe-lz's avatar
liuzhe-lz committed
1
2
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
3
4
5

'use strict';

chicm-ms's avatar
chicm-ms committed
6
// eslint-disable-next-line @typescript-eslint/camelcase
7
8
import {Client1_10, config} from 'kubernetes-client';
import {getLogger, Logger} from '../../common/log';
9
10

/**
11
 * Generic Kubernetes client, target version >= 1.9
12
13
14
 */
class GeneralK8sClient {
    protected readonly client: any;
liuzhe-lz's avatar
liuzhe-lz committed
15
    protected readonly log: Logger = getLogger('GeneralK8sClient');
16
    protected namespace: string = 'default';
17
18

    constructor() {
19
        this.client = new Client1_10({config: config.fromKubeconfig(), version: '1.9'});
20
21
22
        this.client.loadSpec();
    }

23
24
25
    public set setNamespace(namespace: string) {
        this.namespace = namespace;
    }
26
27
28
    public get getNamespace(): string {
        return this.namespace;
    }
29

30
31
32
33
34
35
36
37
    private matchStorageClass(response: any): string {
        const adlSupportedProvisioners: RegExp[] = [
            new RegExp("microk8s.io/hostpath"),
            new RegExp(".*cephfs.csi.ceph.com"),
            new RegExp(".*azure.*"),
            new RegExp("\\b" + "efs" + "\\b")
        ]
        const templateLen = adlSupportedProvisioners.length,
38
            responseLen = response.items.length
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
        let i = 0,
            j = 0;
        for (; i < responseLen; i++) {
            const provisioner: string = response.items[i].provisioner
            for (; j < templateLen; j++) {
                if (provisioner.match(adlSupportedProvisioners[j])) {
                    return response.items[i].metadata.name;
                }
            }
        }
        return "Not Found!";
    }

    public async getStorageClass(): Promise<string> {
        let result: Promise<string>;
        const response: any = await this.client.apis["storage.k8s.io"].v1beta1.storageclasses.get()
        const storageClassType: string = this.matchStorageClass(response.body)
        if (response.statusCode && (response.statusCode >= 200 && response.statusCode <= 299)) {
            if (storageClassType != "Not Found!") {
                result = Promise.resolve(storageClassType);
            }
            else {
                result = Promise.reject("No StorageClasses are supported!")
            }
        } else {
            result = Promise.reject(`List storageclasses failed, statusCode is ${response.statusCode}`);
        }
        return result;
    }

    public async createDeployment(deploymentManifest: any): Promise<string> {
        let result: Promise<string>;
71
        const response: any = await this.client.apis.apps.v1.namespaces(this.namespace)
72
            .deployments.post({body: deploymentManifest})
73
74
75
76
77
78
79
80
81
82
83
        if (response.statusCode && (response.statusCode >= 200 && response.statusCode <= 299)) {
            result = Promise.resolve(response.body.metadata.uid);
        } else {
            result = Promise.reject(`Create deployment failed, statusCode is ${response.statusCode}`);
        }
        return result;
    }

    public async deleteDeployment(deploymentName: string): Promise<boolean> {
        let result: Promise<boolean>;
        // TODO: change this hard coded deployment name after demo
84
        const response: any = await this.client.apis.apps.v1.namespaces(this.namespace)
85
            .deployment(deploymentName).delete();
86
87
88
89
90
91
92
93
94
95
        if (response.statusCode && (response.statusCode >= 200 && response.statusCode <= 299)) {
            result = Promise.resolve(true);
        } else {
            result = Promise.reject(`Delete deployment failed, statusCode is ${response.statusCode}`);
        }
        return result;
    }

    public async createConfigMap(configMapManifest: any): Promise<boolean> {
        let result: Promise<boolean>;
96
        const response: any = await this.client.api.v1.namespaces(this.namespace)
97
            .configmaps.post({body: configMapManifest});
98
99
100
101
102
103
104
105
106
107
108
        if (response.statusCode && (response.statusCode >= 200 && response.statusCode <= 299)) {
            result = Promise.resolve(true);
        } else {
            result = Promise.reject(`Create configMap failed, statusCode is ${response.statusCode}`);
        }

        return result;
    }

    public async createPersistentVolumeClaim(pvcManifest: any): Promise<boolean> {
        let result: Promise<boolean>;
109
        const response: any = await this.client.api.v1.namespaces(this.namespace)
110
            .persistentvolumeclaims.post({body: pvcManifest});
111
112
113
114
115
116
117
118
        if (response.statusCode && (response.statusCode >= 200 && response.statusCode <= 299)) {
            result = Promise.resolve(true);
        } else {
            result = Promise.reject(`Create pvc failed, statusCode is ${response.statusCode}`);
        }
        return result;
    }

119
    public async createSecret(secretManifest: any): Promise<boolean> {
120
        let result: Promise<boolean>;
121
        const response: any = await this.client.api.v1.namespaces(this.namespace)
122
            .secrets.post({body: secretManifest});
123
        if (response.statusCode && (response.statusCode >= 200 && response.statusCode <= 299)) {
124
125
126
127
            result = Promise.resolve(true);
        } else {
            result = Promise.reject(`Create secrets failed, statusCode is ${response.statusCode}`);
        }
128

129
130
131
132
        return result;
    }
}

133
134
135
/**
 * Kubernetes CRD client
 */
136
abstract class KubernetesCRDClient {
137
    protected readonly client: any;
liuzhe-lz's avatar
liuzhe-lz committed
138
    protected readonly log: Logger = getLogger('KubernetesCRDClient');
139
140
141
    protected crdSchema: any;

    constructor() {
142
        this.client = new Client1_10({config: config.fromKubeconfig()});
143
144
145
146
147
148
149
150
        this.client.loadSpec();
    }

    protected abstract get operator(): any;

    public abstract get containerName(): string;

    public get jobKind(): string {
151
        if (this.crdSchema
152
            && this.crdSchema.spec
153
154
155
156
157
158
159
160
161
            && this.crdSchema.spec.names
            && this.crdSchema.spec.names.kind) {
            return this.crdSchema.spec.names.kind;
        } else {
            throw new Error('KubeflowOperatorClient: getJobKind failed, kind is undefined in crd schema!');
        }
    }

    public get apiVersion(): string {
162
        if (this.crdSchema
163
            && this.crdSchema.spec
164
165
166
167
168
169
            && this.crdSchema.spec.version) {
            return this.crdSchema.spec.version;
        } else {
            throw new Error('KubeflowOperatorClient: get apiVersion failed, version is undefined in crd schema!');
        }
    }
170

171
    public async createKubernetesJob(jobManifest: any): Promise<boolean> {
172
        let result: Promise<boolean>;
chicm-ms's avatar
chicm-ms committed
173
        const response: any = await this.operator.post({body: jobManifest});
174
        if (response.statusCode && (response.statusCode >= 200 && response.statusCode <= 299)) {
175
176
            result = Promise.resolve(true);
        } else {
177
            result = Promise.reject(`KubernetesApiClient createKubernetesJob failed, statusCode is ${response.statusCode}`);
178
        }
179

180
181
182
183
        return result;
    }

    //TODO : replace any
184
    public async getKubernetesJob(kubeflowJobName: string): Promise<any> {
185
        let result: Promise<any>;
chicm-ms's avatar
chicm-ms committed
186
        const response: any = await this.operator(kubeflowJobName)
187
            .get();
188
        if (response.statusCode && (response.statusCode >= 200 && response.statusCode <= 299)) {
189
190
            result = Promise.resolve(response.body);
        } else {
191
            result = Promise.reject(`KubernetesApiClient getKubernetesJob failed, statusCode is ${response.statusCode}`);
192
        }
193

194
195
196
        return result;
    }

197
    public async deleteKubernetesJob(labels: Map<string, string>): Promise<boolean> {
198
199
        let result: Promise<boolean>;
        // construct match query from labels for deleting tfjob
200
        const matchQuery: string = Array.from(labels.keys())
201
202
            .map((labelKey: string) => `${labelKey}=${labels.get(labelKey)}`)
            .join(',');
203
        try {
chicm-ms's avatar
chicm-ms committed
204
            const deleteResult: any = await this.operator()
205
206
207
208
209
210
                .delete({
                    qs: {
                        labelSelector: matchQuery,
                        propagationPolicy: 'Background'
                    }
                });
211
            if (deleteResult.statusCode && deleteResult.statusCode >= 200 && deleteResult.statusCode <= 299) {
212
213
                result = Promise.resolve(true);
            } else {
214
                result = Promise.reject(
215
                    `KubernetesApiClient, delete labels ${matchQuery} get wrong statusCode ${deleteResult.statusCode}`);
216
            }
217
        } catch (err) {
218
219
220
221
222
223
224
            result = Promise.reject(err);
        }

        return result;
    }
}

225
export {KubernetesCRDClient, GeneralK8sClient};