tes.service.ts 14.7 KB
Newer Older
Chris Hines's avatar
Chris Hines committed
1
2
3
import { Injectable } from '@angular/core';
import { HttpClientModule, HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
Chris Hines's avatar
Chris Hines committed
4
import { fromEvent, throwError } from 'rxjs';
Chris Hines's avatar
Chris Hines committed
5
6
7
8
import { of } from 'rxjs/observable/of';
import { catchError, map, tap } from 'rxjs/operators';
import { Job } from './job';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
Chris Hines's avatar
Chris Hines committed
9
import { Subject } from 'rxjs/Subject';
Chris Hines's avatar
Chris Hines committed
10
import { Strudelapp, StrudelappInstance } from './strudelapp';
11
import { Computesite } from './computesite';
12
import { APIServer } from './apiserver';
Chris Hines's avatar
Chris Hines committed
13
import { Identity, AuthToken, KeyCert, SshAuthzServer } from './identity';
14
15
import { BatchInterface } from './batchinterface';
import { ComputesitesService } from './computesites.service';
16
import { StrudelappsService } from './strudelapps.service';
Chris Hines's avatar
Chris Hines committed
17
18
import { timer } from 'rxjs/observable/timer';
import { repeat } from 'rxjs/operators';
Chris Hines's avatar
Chris Hines committed
19
import { LocationStrategy, Location } from '@angular/common';
Chris Hines's avatar
Chris Hines committed
20
import { ActivatedRoute, Router } from '@angular/router';
Chris Hines's avatar
Chris Hines committed
21
22
import { ModaldialogComponent } from './modaldialog/modaldialog.component';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material';
Chris Hines's avatar
Chris Hines committed
23
24
import { AuthorisationService } from './authorisation.service';
import { environment } from '../environments/environment';
Chris Hines's avatar
Chris Hines committed
25

Chris Hines's avatar
Chris Hines committed
26
27
/** The TES service contains ways to start Tunnels, and Execute programs
Its also responsible for querying a compute site for running jobs */
Chris Hines's avatar
Chris Hines committed
28
29


30

Chris Hines's avatar
Chris Hines committed
31
32
33
@Injectable({
  providedIn: 'root',
})
Chris Hines's avatar
Chris Hines committed
34
export class TesService {
Chris Hines's avatar
Chris Hines committed
35
public Base=environment.tesurl;
36
private twsproxy = environment.twsurl;
Chris Hines's avatar
Chris Hines committed
37
// public Base='http://localhost:5000';
38
public statusMsg: BehaviorSubject<any>;
Chris Hines's avatar
Chris Hines committed
39
40
public jobs: any[];
public busy: BehaviorSubject<boolean> ;
Chris Hines's avatar
Chris Hines committed
41
42
43
// public joblist: BehaviorSubject<{ [id: string ]: Job[]}>;
public joblist: BehaviorSubject<Job[]>;

Chris Hines's avatar
Chris Hines committed
44
private timerSubscription: any;
Chris Hines's avatar
Chris Hines committed
45
46
private appwindow: any;
private appwindowWatcher: any;
47
48
public apiserver: BehaviorSubject<APIServer>;
public apiservers: BehaviorSubject<APIServer[]>;
49
// public batchinterface: {[id: string] : BatchInterface};
Chris Hines's avatar
Chris Hines committed
50

51
  constructor(private http: HttpClient,
Chris Hines's avatar
Chris Hines committed
52
              public dialog: MatDialog,
Chris Hines's avatar
Chris Hines committed
53
54
              private computesitesService: ComputesitesService,
              private authorisationService: AuthorisationService,
Chris Hines's avatar
Chris Hines committed
55
56
              private strudelappsService: StrudelappsService,
              private location: Location ) {
Chris Hines's avatar
Chris Hines committed
57

Chris Hines's avatar
Chris Hines committed
58
    this.busy = new BehaviorSubject<boolean>(false);
Chris Hines's avatar
Chris Hines committed
59
60
    // this.joblist = new BehaviorSubject<{[id: string]: Job[]}>({});
    this.joblist = new BehaviorSubject<Job[]>([]);
61
62
      this.apiserver = new BehaviorSubject<APIServer>(undefined);
    this.apiservers = new BehaviorSubject<APIServer[]>([]);
63
    this.apiserver.subscribe((r)=>this.onApiServerChange(r));
Chris Hines's avatar
Chris Hines committed
64

Chris Hines's avatar
Chris Hines committed
65
    this.timerSubscription = null;
Chris Hines's avatar
Chris Hines committed
66
    this.appwindowWatcher = null;
67
    this.getAPIServers();
68
    // this.batchinterface = {};
Chris Hines's avatar
Chris Hines committed
69
    // this.computesitesService.identities.subscribe(identities => this.startPolling(identities));
Chris Hines's avatar
Chris Hines committed
70
71
 }

Chris Hines's avatar
Chris Hines committed
72
73
74
public setStatusMsg(statusMsg: BehaviorSubject<any>) {
  this.statusMsg = statusMsg;
}
75

76
77
78
private onApiServerChange(r) {
    this.Base = this.apiserver.value.tes;
    this.twsproxy = this.apiserver.value.tws;
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
}

 setApiServer(server: APIServer) {
     console.log('calling setAPIServer');
     this.apiserver.next(server);
 }

 getAPIServers() {
   let headers = new HttpHeaders();
   let options = { headers: headers, withCredentials: false};
   this.http.get<SshAuthzServer[]>('./assets/config/apiservers.json',options)
                    .subscribe(resp => this.updateAPIServers(resp));
 }

 private updateAPIServers(resp) {
     this.apiservers.next(<APIServer[]>resp);
     this.apiserver.next(this.apiservers.value[0]);
 }


Chris Hines's avatar
Chris Hines committed
99
 private buildParams(app: Strudelapp, identity: Identity, batchinterface: BatchInterface, appinst?: any): string {
100
   let params = new URLSearchParams();
Chris Hines's avatar
Chris Hines committed
101
102
103
   let id = identity.copy_skip_catalog();
   id.site.appCatalog = null;

Chris Hines's avatar
Chris Hines committed
104
105
106
   if (appinst!== null)  {
     params.set('appinstance',JSON.stringify(appinst));
   }
107
108
   params.set('app',JSON.stringify(app));
   params.set('interface',JSON.stringify(batchinterface));
Chris Hines's avatar
Chris Hines committed
109
   params.set('identity',JSON.stringify(id));
110
   return params.toString();
Chris Hines's avatar
Chris Hines committed
111
112
 }

113
114
115
116
117
 updateJoblist(resp, identity: Identity) {
   let joblist = <Job[]>resp;
   let alljobs = this.joblist.value;
   let i = 0;
   for (let j of joblist) {
Chris Hines's avatar
Chris Hines committed
118
     j.app = this.strudelappsService.getApp(j.name,identity.site.appCatalog.value);
119
120
     j.identity = identity;
   }
Chris Hines's avatar
Chris Hines committed
121
122
123
   this.joblist.next(joblist)
   // alljobs[identity.repr()] = joblist;
   // this.joblist.next(alljobs);
124
125
 }

Chris Hines's avatar
Chris Hines committed
126
127
 private getBatchInterfaceError(error: any) {
   console.error(error);
128
129
 }

Chris Hines's avatar
Chris Hines committed
130
131
132
133
134
 private getJobsError(error: any) {
   this.joblist.next([]);
   if (error.status == 0) {
     this.statusMsg.next("A network error occured. Please try again latter");
     return
135
   }
136
   console.error(error);
137
   if (error.status == 404 || error.status == 400) {
Chris Hines's avatar
Chris Hines committed
138
     this.statusMsg.next("Login expired. Please log in again.");
139
     this.authorisationService.updateAgentContents();
Chris Hines's avatar
Chris Hines committed
140
     return
141
   }
142

Chris Hines's avatar
Chris Hines committed
143
144
 }

Chris Hines's avatar
Chris Hines committed
145
146
147
148
149
150
151
152
153
 getJobs(identity: Identity) {
  let headers = new HttpHeaders();
  let options = { headers: headers, withCredentials: true};
  // remove from the job list any jobs for identities that we don't know about
  let oldjobs = this.joblist.value;
  if ( identity === undefined) {
    this.joblist.next([]);
    return
  }
154
155
156
  let bi = new BatchInterface();
  bi.cancelcmd = identity.site.cancelcmd;
  bi.statcmd = identity.site.statcmd;
157
158
159
160
161
162
163
164
165
166
  let params = new URLSearchParams();
  params.set('statcmd',JSON.stringify(identity.site.statcmd));
  params.set('host',JSON.stringify(identity.site.host));
  params.set('username',JSON.stringify(identity.username));
  console.log('getjob with params',params.toString());
  
  this.http.get<Job[]>(this.Base+'/stat'+'?'+params.toString(),options)
                // .pipe(catchError(this.networkError))
                .subscribe(resp => this.updateJoblist(resp, identity),
                           error => this.getJobsError(error));
Chris Hines's avatar
Chris Hines committed
167
168
}

169
 public getconfig(app: Strudelapp, identity: Identity): Observable<any> {
Chris Hines's avatar
Chris Hines committed
170
171
172
   let headers = new HttpHeaders();
   let options = { headers: headers, withCredentials: true};
   return this.http.get<any>(identity.site.url+'getconfig/'+app.name,options)
Chris Hines's avatar
Chris Hines committed
173
                                        // .pipe(catchError(this.handleError))
Chris Hines's avatar
Chris Hines committed
174
175
}

Chris Hines's avatar
Chris Hines committed
176
177
 submissionError(error: any) {
   if (error.status != 0) {
178
179
180
     this.statusMsg.next('Job submission failed');
     //this.statusMsg.next(error.error.message);
     console.error(error.error.message);
Chris Hines's avatar
Chris Hines committed
181
182
183
184
185
186
     this.busy.next(false);
   }
 }

 buildBody(app: Strudelapp, appparams?: string) {
   return JSON.stringify({'app': app, 'appparams': appparams});
Chris Hines's avatar
Chris Hines committed
187
188
 }

189
 submit(app: Strudelapp, identity: Identity, batchinterface: BatchInterface, appparams?: any) {
Chris Hines's avatar
Chris Hines committed
190
191
   let headers = new HttpHeaders();
   let options = { headers: headers, withCredentials: true};
192
   this.statusMsg.next('Submitting job');
Chris Hines's avatar
Chris Hines committed
193
   this.busy.next(true);
Chris Hines's avatar
Chris Hines committed
194
   let paramstr = this.buildParams(app,identity,batchinterface);
Chris Hines's avatar
Chris Hines committed
195
196
197
   // let body = this.buildBody(app,appparams)
   let keys = this.authorisationService.getKeys();
   let body = {'app': app, 'appparams': appparams, 'keys': keys}
198
   console.log('tes is submitting the job with body',body);
Chris Hines's avatar
Chris Hines committed
199
   this.http.post<any>(this.Base+'/submit'+'?'+paramstr, body, options)
200
201
                                          .pipe(catchError(this.handleError))
                                          .subscribe(resp => {
Chris Hines's avatar
Chris Hines committed
202
203
204
205
                                                              this.busy.next(false);
                                                              this.statusMsg.next(null)
                                                            },
                                                    error => this.submissionError(error));
Chris Hines's avatar
Chris Hines committed
206
207
 }

Chris Hines's avatar
Chris Hines committed
208
 submitted(resp: any, identity: Identity ) {
209
   this.busy.next(false);
Chris Hines's avatar
Chris Hines committed
210
   // this.statusMsg.next(null);
Chris Hines's avatar
Chris Hines committed
211
   this.getJobs(identity);
212
213
 }

214
 cancel(job: Job) {
Chris Hines's avatar
Chris Hines committed
215
216
   let headers = new HttpHeaders();
   let options = { headers: headers, withCredentials: true};
Chris Hines's avatar
Chris Hines committed
217
   // this.statusMsg.next(null);
Chris Hines's avatar
Chris Hines committed
218
   let data = {};
219
220
221
222
   let bi = new BatchInterface();
   bi.statcmd = job.identity.site.statcmd;
   bi.cancelcmd = job.identity.site.cancelcmd;
   let paramstr = this.buildParams(job.app,job.identity,bi);
223
   this.http.delete<any>(this.Base+'/cancel/'+job.jobid+'?'+paramstr, options)
Chris Hines's avatar
Chris Hines committed
224
                      .pipe(catchError(this.handleError))
Chris Hines's avatar
Chris Hines committed
225
                      .subscribe(resp => this.submitted(resp,job.identity));
Chris Hines's avatar
Chris Hines committed
226
227
228
229
230
231
 }

 public watchAppwindow(appwindow, dialogRef) {
   if (appwindow.closed) {
     dialogRef.close();
   }
Chris Hines's avatar
Chris Hines committed
232
233
 }

Chris Hines's avatar
Chris Hines committed
234
235
236
237
238
 public getAppInstance(job: Job) {
   let username = job.identity.username;
   let loginhost = job.identity.site.host;
   let batchhost = job.batch_host;
   let params = new URLSearchParams;
239
240
   let headers = new HttpHeaders();
   let options = { headers: headers, withCredentials: true};
Chris Hines's avatar
Chris Hines committed
241
242
243
244
245
   params.set('cmd',JSON.stringify(job.app.paramscmd));
   let paramstr = params.toString();
   job.connectionState = 1;
   this.http.get<string>(this.Base+'/appinstance/'+username+'/'+loginhost+'/'+batchhost+'?'+paramstr, options)
                // .pipe(catchError(this.handleError))
246
                .subscribe(resp =>  { job.appinst = resp; this.createTunnel(job) } )
Chris Hines's avatar
Chris Hines committed
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
   // let paramstr = this.buildParams(job.app,job.identity,this.batchinterface[job.identity.repr()]);
   // let headers = new HttpHeaders();
   // let options = { headers: headers, withCredentials: true};
   // this.http.get<any>(this.Base+'/getappinst/'+paramstr,options)
   //                                      .pipe(catchError(this.handleError))
   //                                      .subscribe(resp =>  { job.appinst = <StrudelappInstance>resp; this.openAppWindow(job);});
 }

 public createTunnel(job: Job) {
   let username = job.identity.username;
   let loginhost = job.identity.site.host;
   let batchhost = job.batch_host;
   let params = new URLSearchParams;
   let headers = new HttpHeaders();
   job.connectionState = 2;
   let options = { headers: headers, withCredentials: true};
   params.set('cmd',JSON.stringify(job.app.paramscmd));
   let paramstr = params.toString();
   this.http.post<string>(this.Base+'/createtunnel/'+username+'/'+loginhost+'/'+batchhost+'?'+paramstr, job.appinst, options)
                // .pipe(catchError(this.handleError))
                .subscribe(() =>  { this.getAppUrl(job) } )
 }
Chris Hines's avatar
Chris Hines committed
269

Chris Hines's avatar
Chris Hines committed
270
271
272
273
274
275
276
277
278
279
 public getAppUrl(job: Job) {
   let params = new URLSearchParams;
   let headers = new HttpHeaders();
   let options = { headers: headers, withCredentials: true};
   params.set('app',JSON.stringify(job.app));
   params.set('appinst',JSON.stringify(job.appinst));
   let paramstr = params.toString();
   job.connectionState = 3;
   this.http.get<string>(this.Base+'/appurl?'+paramstr,options)
                .pipe(catchError(this.handleError))
280
                .subscribe(resp => {  job.connectionState = 0; this.openAppWindow(resp)});
Chris Hines's avatar
Chris Hines committed
281
282
283
284
285
286
287
288


 }

 public getInterface(job: Job) {
   this.getAppInstance(job);
 }

289
290
291
 public openAppWindow(url: any) {
     let windowloc = url.replace(/\{twsproxy\}/g,this.twsproxy);
     console.log('window loc is ',windowloc);
Chris Hines's avatar
Chris Hines committed
292
293
294
295
296
297
298
299
300
301
302
   // let windowloc = this.router.config
   this.appwindow = window.open(windowloc);
   if (!(this.appwindowWatcher === null)) {
     this.appwindowWatcher.unsubscribe()
   }

   let dialogRef = this.dialog.open(ModaldialogComponent, {
     width: '600px',
   });
   this.appwindowWatcher = timer(500).pipe(repeat()).subscribe(() => this.watchAppwindow(this.appwindow,dialogRef));
   // this.appwindow.location.assign(windowloc);
Chris Hines's avatar
Chris Hines committed
303
304
 }

Chris Hines's avatar
Chris Hines committed
305
306
307
308
309
310
311
 public connect(job: Job, appinst?: any) {
   // this.statusMsg.next(null);
   let headers = new HttpHeaders();
   let options = { headers: headers, withCredentials: true};
   // this.busy.next(true);
   this.getInterface(job); // getInterface will subsequently called getAppInstance, which will call createTunnel, which will openAppWindow
 }
Chris Hines's avatar
Chris Hines committed
312

Chris Hines's avatar
Chris Hines committed
313
314
315
316



public postAgentData(keyCert: KeyCert) {
317
318
319
320
  let headers = new HttpHeaders();
  let options = { headers: headers, withCredentials: true};

  this.statusMsg.next("Authorising ...")
Chris Hines's avatar
Chris Hines committed
321
  let data = {'key': keyCert.key, 'cert': keyCert.cert};
Chris Hines's avatar
Chris Hines committed
322
  return this.http.post<any>(this.Base+'/sshagent',data,options)
Chris Hines's avatar
Chris Hines committed
323
                      .pipe(catchError(this.handleError))
Chris Hines's avatar
Chris Hines committed
324

325
326
}

Chris Hines's avatar
Chris Hines committed
327
public postMkDir(id: Identity, path: string, name: string ) {
328
329
  let headers = new HttpHeaders();
  let options = { headers: headers, withCredentials: true};
Chris Hines's avatar
Chris Hines committed
330
331
332
333
  let params = new URLSearchParams;
  params.set('identity',JSON.stringify(id));
  params.set('path',JSON.stringify(path));

334
  return this.http.post<any>(this.Base+'/mkdir?'+params.toString(),name, options);
335
336
}

Chris Hines's avatar
Chris Hines committed
337
public getSftpData(id: Identity, path: string, cd: string ) {
338
339
  let headers = new HttpHeaders();
  let options = { headers: headers, withCredentials: true};
Chris Hines's avatar
Chris Hines committed
340
341
342
343
  let params = new URLSearchParams;
  params.set('identity',JSON.stringify(id));
  params.set('path',JSON.stringify(path));
  params.set('cd',JSON.stringify(cd));
344

Chris Hines's avatar
Chris Hines committed
345
  return this.http.get<any>(this.Base+'/ls?'+params.toString(),options)
346
                      // .pipe(catchError(this.handleError))
347

Chris Hines's avatar
Chris Hines committed
348
349
}

Chris Hines's avatar
Chris Hines committed
350
351


352
353
public getstatusMsgSubject(): BehaviorSubject<any> {
  return this.statusMsg;
Chris Hines's avatar
Chris Hines committed
354
355
}

Chris Hines's avatar
Chris Hines committed
356
357
358
359
360
private httperror(errorstr: string) {
  var re = /login expired/gi;
  let searchresult = errorstr.search(re);
  if (searchresult != -1) {
    this.statusMsg.next("Some authentication tokens have expired, you may need to log in again");
361
    console.error(errorstr);
Chris Hines's avatar
Chris Hines committed
362
    this.authorisationService.updateAgentContents();
Chris Hines's avatar
Chris Hines committed
363
  }
364
  console.error(errorstr);
Chris Hines's avatar
Chris Hines committed
365
}
Chris Hines's avatar
Chris Hines committed
366

Chris Hines's avatar
Chris Hines committed
367
368
369
370
371
372
373
374
375
// private networkError(error: HttpErrorResponse) {
//   if (error.error instanceof ErrorEvent) {
//     console.log('network error contacting TES backend');
//   } else {
//     return error;
//   }
// }


Chris Hines's avatar
Chris Hines committed
376
private handleError(error: HttpErrorResponse) {
377
378
  console.error('in handleError');
  console.error(error);
Chris Hines's avatar
Chris Hines committed
379
380
381
382
383
384
385
386
387
388
  if (error.error instanceof ErrorEvent) {
    // A client-side or network error occurred. Handle it accordingly.
    return throwError("Hmm, that didn't work. If you're using a local connection, please make sure Strudel-TES is running.");
  } else {
    console.log('NOT an instance of ErrorEvent');
    console.log(error);
    // Not sure if this code works correctly. It should update identities in case the error is
    // that the user isn't allowed to run the job

    var re = /identity/gi;
Chris Hines's avatar
Chris Hines committed
389
390
391
392
393
394
395
396
397
    if (error.error.message != undefined) {
      let searchresult = error.error.message.search(re);
      if (searchresult != -1) {
        // this.getIdentities();
        return throwError('login expired, refreshing');
      }
      return throwError(error.error.message)
    } else {
      return throwError('An error occured but I\'m not sure exactly what. Please try again latter or contact the sys admins');
Chris Hines's avatar
Chris Hines committed
398
    }
Chris Hines's avatar
Chris Hines committed
399
400
401
    // this.statusMsg.next("There was an error submitting that job. The backend gave me the message: " + error.error.message);

  }
Chris Hines's avatar
Chris Hines committed
402
}
Chris Hines's avatar
Chris Hines committed
403
404
405



Chris Hines's avatar
Chris Hines committed
406
}