paiJobInfoCollector.ts 6.16 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';

6
// tslint:disable-next-line:no-implicit-dependencies
7
8
9
import * as request from 'request';
import { Deferred } from 'ts-deferred';
import { NNIError, NNIErrorNames } from '../../common/errors';
10
import { getLogger, Logger } from '../../common/log';
11
import { TrialJobStatus } from '../../common/trainingService';
12
13
import { PAIClusterConfig } from './paiConfig';
import { PAITrialJobDetail } from './paiData';
14
15
16
17
18
19
20
21
22
23
24
25
26

/**
 * Collector PAI jobs info from PAI cluster, and update pai job status locally
 */
export class PAIJobInfoCollector {
    private readonly trialJobsMap : Map<string, PAITrialJobDetail>;
    private readonly log: Logger = getLogger();
    private readonly statusesNeedToCheck : TrialJobStatus[];
    private readonly finalStatuses : TrialJobStatus[];

    constructor(jobMap: Map<string, PAITrialJobDetail>) {
        this.trialJobsMap = jobMap;
        this.statusesNeedToCheck = ['RUNNING', 'UNKNOWN', 'WAITING'];
QuanluZhang's avatar
QuanluZhang committed
27
        this.finalStatuses = ['SUCCEEDED', 'FAILED', 'USER_CANCELED', 'SYS_CANCELED', 'EARLY_STOPPED'];
28
29
    }

30
    public async retrieveTrialStatus(paiToken? : string, paiClusterConfig?: PAIClusterConfig) : Promise<void> {
31
        if (paiClusterConfig === undefined || paiToken === undefined) {
32
            return Promise.resolve();
33
34
35
        }

        const updatePaiTrialJobs : Promise<void>[] = [];
36
37
        for (const [trialJobId, paiTrialJob] of this.trialJobsMap) {
            if (paiTrialJob === undefined) {
38
39
                throw new NNIError(NNIErrorNames.NOT_FOUND, `trial job id ${trialJobId} not found`);
            }
40
            updatePaiTrialJobs.push(this.getSinglePAITrialJobInfo(paiTrialJob, paiToken, paiClusterConfig));
41
42
43
44
45
        }

        await Promise.all(updatePaiTrialJobs);
    }

46
47
    private getSinglePAITrialJobInfo(paiTrialJob : PAITrialJobDetail, paiToken : string, paiClusterConfig: PAIClusterConfig)
     : Promise<void> {
48
49
50
        const deferred : Deferred<void> = new Deferred<void>();
        if (!this.statusesNeedToCheck.includes(paiTrialJob.status)) {
            deferred.resolve();
51

52
53
54
55
56
57
            return deferred.promise;
        }

        // Rest call to get PAI job info and update status
        // Refer https://github.com/Microsoft/pai/blob/master/docs/rest-server/API.md for more detail about PAI Rest API
        const getJobInfoRequest: request.Options = {
58
            // tslint:disable-next-line:no-http-string
59
            uri: `http://${paiClusterConfig.host}/rest-server/api/v1/user/${paiClusterConfig.userName}/jobs/${paiTrialJob.paiJobName}`,
60
61
62
            method: 'GET',
            json: true,
            headers: {
63
64
                'Content-Type': 'application/json',
                Authorization: `Bearer ${paiToken}`
65
66
            }
        };
67
68

        // tslint:disable: no-unsafe-any no-any cyclomatic-complexity
69
        //TODO : pass in request timeout param?
70
        request(getJobInfoRequest, (error: Error, response: request.Response, body: any) => {
71
            if ((error !== undefined && error !== null) || response.statusCode >= 500) {
72
73
                this.log.error(`PAI Training service: get job info for trial ${paiTrialJob.id} from PAI Cluster failed!`);
                // Queried PAI job info failed, set job status to UNKNOWN
74
                if (paiTrialJob.status === 'WAITING' || paiTrialJob.status === 'RUNNING') {
75
76
77
                    paiTrialJob.status = 'UNKNOWN';
                }
            } else {
78
79
                if (response.body.jobStatus && response.body.jobStatus.state) {
                    switch (response.body.jobStatus.state) {
80
                        case 'WAITING':
81
82
83
84
                            paiTrialJob.status = 'WAITING';
                            break;
                        case 'RUNNING':
                            paiTrialJob.status = 'RUNNING';
85
                            if (paiTrialJob.startTime === undefined) {
86
87
                                paiTrialJob.startTime = response.body.jobStatus.appLaunchedTime;
                            }
88
                            if (paiTrialJob.url === undefined) {
89
                                paiTrialJob.url = response.body.jobStatus.appTrackingUrl;
90
91
92
93
94
95
                            }
                            break;
                        case 'SUCCEEDED':
                            paiTrialJob.status = 'SUCCEEDED';
                            break;
                        case 'STOPPED':
96
                            if (paiTrialJob.isEarlyStopped !== undefined) {
97
                                paiTrialJob.status = paiTrialJob.isEarlyStopped === true ?
98
99
                                        'EARLY_STOPPED' : 'USER_CANCELED';
                            } else {
100
101
102
                                /* if paiTrialJob's isEarlyStopped is undefined, that mean we didn't stop it via cancellation,
                                 * mark it as SYS_CANCELLED by PAI
                                 */
103
                                paiTrialJob.status = 'SYS_CANCELED';
QuanluZhang's avatar
QuanluZhang committed
104
                            }
105
106
                            break;
                        case 'FAILED':
107
                            paiTrialJob.status = 'FAILED';
108
109
110
111
112
                            break;
                        default:
                            paiTrialJob.status = 'UNKNOWN';
                    }
                    // For final job statues, update startTime, endTime and url
113
114
                    if (this.finalStatuses.includes(paiTrialJob.status)) {
                        if (paiTrialJob.startTime === undefined) {
115
116
                            paiTrialJob.startTime = response.body.jobStatus.appLaunchedTime;
                        }
117
                        if (paiTrialJob.endTime === undefined) {
118
119
120
                            paiTrialJob.endTime = response.body.jobStatus.completedTime;
                        }
                        // Set pai trial job's url to WebHDFS output path
121
                        if (paiTrialJob.hdfsLogPath !== undefined) {
122
                            paiTrialJob.url += `,${paiTrialJob.hdfsLogPath}`;
123
124
125
126
127
128
129
130
131
                        }
                    }
                }
            }
            deferred.resolve();
        });

        return deferred.promise;
    }
132
    // tslint:enable: no-unsafe-any no-any
QuanluZhang's avatar
QuanluZhang committed
133
}