// GET: /job/{id} func (cr *JobController) Read(id string, cx *goweb.Context) { LogRequest(cx.Request) // Load job by id job, err := core.LoadJob(id) if err != nil { if err.Error() == e.MongoDocNotFound { cx.RespondWithNotFound() return } else { // In theory the db connection could be lost between // checking user and load but seems unlikely. logger.Error("Err@job_Read:LoadJob: " + id + ":" + err.Error()) cx.RespondWithErrorMessage("job not found:"+id, http.StatusBadRequest) return } } // Gather query params query := &Query{Li: cx.Request.URL.Query()} if query.Has("export") { target := query.Value("export") if target == "" { cx.RespondWithErrorMessage("lacking stage id from which the recompute starts", http.StatusBadRequest) } else if target == "taverna" { wfrun, err := taverna.ExportWorkflowRun(job) if err != nil { cx.RespondWithErrorMessage("failed to export job to taverna workflowrun:"+id, http.StatusBadRequest) } cx.RespondWithData(wfrun) return } } // Base case respond with job in json cx.RespondWithData(job) return }
// GET: /work/{id} // get a workunit by id, read-only func (cr *WorkController) Read(id string, cx *goweb.Context) { LogRequest(cx.Request) // Gather query params query := &Query{Li: cx.Request.URL.Query()} if (query.Has("datatoken") || query.Has("privateenv")) && query.Has("client") { cg, err := request.AuthenticateClientGroup(cx.Request) if err != nil { if err.Error() == e.NoAuth || err.Error() == e.UnAuth || err.Error() == e.InvalidAuth { if conf.CLIENT_AUTH_REQ == true { cx.RespondWithError(http.StatusUnauthorized) return } } else { logger.Error("Err@AuthenticateClientGroup: " + err.Error()) cx.RespondWithError(http.StatusInternalServerError) return } } // check that clientgroup auth token matches group of client clientid := query.Value("client") client, ok := core.QMgr.GetClient(clientid) if !ok { cx.RespondWithErrorMessage(e.ClientNotFound, http.StatusBadRequest) return } if cg != nil && client.Group != cg.Name { cx.RespondWithErrorMessage("Clientgroup name in token does not match that in the client configuration.", http.StatusBadRequest) return } if query.Has("datatoken") { //a client is requesting data token for this job token, err := core.QMgr.FetchDataToken(id, clientid) if err != nil { cx.RespondWithErrorMessage("error in getting token for job "+id, http.StatusBadRequest) return } //cx.RespondWithData(token) RespondTokenInHeader(cx, token) return } if query.Has("privateenv") { //a client is requesting data token for this job envs, err := core.QMgr.FetchPrivateEnv(id, clientid) if err != nil { cx.RespondWithErrorMessage("error in getting token for job "+id, http.StatusBadRequest) return } //cx.RespondWithData(token) RespondPrivateEnvInHeader(cx, envs) return } } // Try to authenticate user. u, err := request.Authenticate(cx.Request) if err != nil && err.Error() != e.NoAuth { cx.RespondWithErrorMessage(err.Error(), http.StatusUnauthorized) return } // If no auth was provided, and anonymous read is allowed, use the public user if u == nil { if conf.ANON_READ == true { u = &user.User{Uuid: "public"} } else { cx.RespondWithErrorMessage(e.NoAuth, http.StatusUnauthorized) return } } jobid, err := core.GetJobIdByWorkId(id) if err != nil { cx.RespondWithErrorMessage(err.Error(), http.StatusBadRequest) return } job, err := core.LoadJob(jobid) if err != nil { cx.RespondWithErrorMessage(err.Error(), http.StatusBadRequest) return } // User must have read permissions on job or be job owner or be an admin rights := job.Acl.Check(u.Uuid) if job.Acl.Owner != u.Uuid && rights["read"] == false && u.Admin == false { cx.RespondWithErrorMessage(e.UnAuth, http.StatusUnauthorized) return } if query.Has("report") { //retrieve report: stdout or stderr or worknotes reportmsg, err := core.QMgr.GetReportMsg(id, query.Value("report")) if err != nil { cx.RespondWithErrorMessage(err.Error(), http.StatusBadRequest) return } cx.RespondWithData(reportmsg) return } if err != nil { if err.Error() != e.QueueEmpty { logger.Error("Err@work_Read:core.QMgr.GetWorkById(): " + err.Error()) } cx.RespondWithErrorMessage(err.Error(), http.StatusBadRequest) return } // Base case respond with workunit in json workunit, err := core.QMgr.GetWorkById(id) if err != nil { cx.RespondWithErrorMessage(err.Error(), http.StatusBadRequest) return } cx.RespondWithData(workunit) return }
} // If no auth was provided, and anonymous read is allowed, use the public user if u == nil { if conf.ANON_READ == true { u = &user.User{Uuid: "public"} } else { cx.RespondWithErrorMessage(e.NoAuth, http.StatusUnauthorized) return } } jid := cx.PathParams["jid"] // Load job by id job, err := core.LoadJob(jid) if err != nil { if err == mgo.ErrNotFound { cx.RespondWithNotFound() } else { // In theory the db connection could be lost between // checking user and load but seems unlikely. cx.RespondWithErrorMessage("job not found: "+jid, http.StatusBadRequest) } return } // Only the owner, an admin, or someone with read access can view acl's. // // NOTE: If the job is publicly owned, then anyone can view all acl's. The owner can only // be "public" when anonymous job creation (ANON_WRITE) is enabled in AWE config.
// PUT: /job/{id} -> used for job manipulation func (cr *JobController) Update(id string, cx *goweb.Context) { // Log Request and check for Auth LogRequest(cx.Request) // Try to authenticate user. u, err := request.Authenticate(cx.Request) if err != nil && err.Error() != e.NoAuth { cx.RespondWithErrorMessage(err.Error(), http.StatusUnauthorized) return } // If no auth was provided, and anonymous write is allowed, use the public user if u == nil { if conf.ANON_WRITE == true { u = &user.User{Uuid: "public"} } else { cx.RespondWithErrorMessage(e.NoAuth, http.StatusUnauthorized) return } } // Load job by id job, err := core.LoadJob(id) if err != nil { if err == mgo.ErrNotFound { cx.RespondWithNotFound() } else { // In theory the db connection could be lost between // checking user and load but seems unlikely. // logger.Error("Err@job_Read:LoadJob: " + id + ":" + err.Error()) cx.RespondWithErrorMessage("job not found:"+id, http.StatusBadRequest) } return } // User must have write permissions on job or be job owner or be an admin rights := job.Acl.Check(u.Uuid) if job.Acl.Owner != u.Uuid && rights["write"] == false && u.Admin == false { cx.RespondWithErrorMessage(e.UnAuth, http.StatusUnauthorized) return } // Gather query params query := &Query{Li: cx.Request.URL.Query()} if query.Has("resume") { // to resume a suspended job if err := core.QMgr.ResumeSuspendedJob(id); err != nil { cx.RespondWithErrorMessage("fail to resume job: "+id+" "+err.Error(), http.StatusBadRequest) return } cx.RespondWithData("job resumed: " + id) return } if query.Has("suspend") { // to suspend an in-progress job if err := core.QMgr.SuspendJob(id, "manually suspended", ""); err != nil { cx.RespondWithErrorMessage("fail to suspend job: "+id+" "+err.Error(), http.StatusBadRequest) return } cx.RespondWithData("job suspended: " + id) return } if query.Has("resubmit") || query.Has("reregister") { // to re-submit a job from mongodb if err := core.QMgr.ResubmitJob(id); err != nil { cx.RespondWithErrorMessage("fail to resubmit job: "+id+" "+err.Error(), http.StatusBadRequest) return } cx.RespondWithData("job resubmitted: " + id) return } if query.Has("recompute") { // to recompute a job from task i, the successive/downstream tasks of i will all be computed stage := query.Value("recompute") if stage == "" { cx.RespondWithErrorMessage("lacking stage id from which the recompute starts", http.StatusBadRequest) return } if err := core.QMgr.RecomputeJob(id, stage); err != nil { cx.RespondWithErrorMessage("fail to recompute job: "+id+" "+err.Error(), http.StatusBadRequest) return } cx.RespondWithData("job recompute started: " + id) return } if query.Has("clientgroup") { // change the clientgroup attribute of the job newgroup := query.Value("clientgroup") if newgroup == "" { cx.RespondWithErrorMessage("lacking clientgroup name", http.StatusBadRequest) return } if err := core.QMgr.UpdateGroup(id, newgroup); err != nil { cx.RespondWithErrorMessage("failed to update group for job: "+id+" "+err.Error(), http.StatusBadRequest) return } cx.RespondWithData("job group updated: " + id + " to " + newgroup) return } if query.Has("priority") { // change the priority attribute of the job priority_str := query.Value("priority") if priority_str == "" { cx.RespondWithErrorMessage("lacking priority value", http.StatusBadRequest) return } priority, err := strconv.Atoi(priority_str) if err != nil { cx.RespondWithErrorMessage("priority value must be an integer"+err.Error(), http.StatusBadRequest) return } if err := core.QMgr.UpdatePriority(id, priority); err != nil { cx.RespondWithErrorMessage("failed to set the priority for job: "+id+" "+err.Error(), http.StatusBadRequest) return } cx.RespondWithData("job priority updated: " + id + " to " + priority_str) return } if query.Has("settoken") { // set data token token, err := request.RetrieveToken(cx.Request) if err != nil { cx.RespondWithErrorMessage("fail to retrieve token for job, pls set token in header: "+id+" "+err.Error(), http.StatusBadRequest) return } job, err := core.LoadJob(id) if err != nil { if err == mgo.ErrNotFound { cx.RespondWithNotFound() } else { logger.Error("Err@job_Read:LoadJob: " + id + ":" + err.Error()) cx.RespondWithErrorMessage("job not found:"+id, http.StatusBadRequest) } return } job.SetDataToken(token) cx.RespondWithData("data token set for job: " + id) return } cx.RespondWithData("requested job operation not supported") return }
// GET: /job/{id} func (cr *JobController) Read(id string, cx *goweb.Context) { LogRequest(cx.Request) // Try to authenticate user. u, err := request.Authenticate(cx.Request) if err != nil && err.Error() != e.NoAuth { cx.RespondWithErrorMessage(err.Error(), http.StatusUnauthorized) return } // If no auth was provided, and anonymous read is allowed, use the public user if u == nil { if conf.ANON_READ == true { u = &user.User{Uuid: "public"} } else { cx.RespondWithErrorMessage(e.NoAuth, http.StatusUnauthorized) return } } // Load job by id job, err := core.LoadJob(id) if err != nil { cx.RespondWithErrorMessage("job not found:"+id, http.StatusBadRequest) return } // User must have read permissions on job or be job owner or be an admin rights := job.Acl.Check(u.Uuid) prights := job.Acl.Check("public") if job.Acl.Owner != u.Uuid && rights["read"] == false && u.Admin == false && prights["read"] == false { cx.RespondWithErrorMessage(e.UnAuth, http.StatusUnauthorized) return } // Gather query params query := &Query{Li: cx.Request.URL.Query()} if query.Has("perf") { //Load job perf by id perf, err := core.LoadJobPerf(id) if err != nil { if err == mgo.ErrNotFound { cx.RespondWithNotFound() } else { // In theory the db connection could be lost between // checking user and load but seems unlikely. logger.Error("Err@LoadJobPerf: " + id + ":" + err.Error()) cx.RespondWithErrorMessage("job perf stats not found:"+id, http.StatusBadRequest) } return } cx.RespondWithData(perf) return //done with returning perf, no need to load job further. } if query.Has("position") { if job.State != "queued" && job.State != "in-progress" { cx.RespondWithErrorMessage("job is not queued or in-progress, job state:"+job.State, http.StatusBadRequest) return } // Retrieve the job's approximate position in the queue (this is a rough estimate since jobs are not actually in a queue) q := bson.M{} qState := bson.M{} // query job state qPriority := bson.M{} // query job priority qCgroup := bson.M{} // query job clietgroup qState["$or"] = []bson.M{bson.M{"state": core.JOB_STAT_INIT}, bson.M{"state": core.JOB_STAT_QUEUED}, bson.M{"state": core.JOB_STAT_INPROGRESS}} qPriority["$or"] = []bson.M{bson.M{"info.priority": bson.M{"$gt": job.Info.Priority}}, bson.M{"$and": []bson.M{bson.M{"info.priority": job.Info.Priority}, bson.M{"info.submittime": bson.M{"$lt": job.Info.SubmitTime}}}}} var cgroups []bson.M for _, value := range strings.Split(job.Info.ClientGroups, ",") { cgroups = append(cgroups, bson.M{"info.clientgroups": bson.M{"$regex": value}}) } qCgroup["$or"] = cgroups q["$and"] = []bson.M{qState, qPriority, qCgroup} if count, err := core.GetJobCount(q); err != nil { cx.RespondWithErrorMessage("error retrieving job position in queue", http.StatusInternalServerError) } else { m := make(map[string]int) m["position"] = count + 1 cx.RespondWithData(m) } return } if core.QMgr.IsJobRegistered(id) { job.Registered = true } else { job.Registered = false } if query.Has("export") { target := query.Value("export") if target == "" { cx.RespondWithErrorMessage("lacking stage id from which the recompute starts", http.StatusBadRequest) return } else if target == "taverna" { wfrun, err := taverna.ExportWorkflowRun(job) if err != nil { cx.RespondWithErrorMessage("failed to export job to taverna workflowrun:"+id, http.StatusBadRequest) return } cx.RespondWithData(wfrun) return } } // Base case respond with job in json cx.RespondWithData(job) return }
// GET: /job/{id} func (cr *JobController) Read(id string, cx *goweb.Context) { LogRequest(cx.Request) // Try to authenticate user. u, err := request.Authenticate(cx.Request) if err != nil && err.Error() != e.NoAuth { cx.RespondWithErrorMessage(err.Error(), http.StatusUnauthorized) return } // If no auth was provided, and anonymous read is allowed, use the public user if u == nil { if conf.ANON_READ == true { u = &user.User{Uuid: "public"} } else { cx.RespondWithErrorMessage(e.NoAuth, http.StatusUnauthorized) return } } // Load job by id job, err := core.LoadJob(id) if err != nil { if err == mgo.ErrNotFound { cx.RespondWithNotFound() } else { // In theory the db connection could be lost between // checking user and load but seems unlikely. // logger.Error("Err@job_Read:LoadJob: " + id + ":" + err.Error()) cx.RespondWithErrorMessage("job not found:"+id, http.StatusBadRequest) } return } // User must have read permissions on job or be job owner or be an admin rights := job.Acl.Check(u.Uuid) if job.Acl.Owner != u.Uuid && rights["read"] == false && u.Admin == false { cx.RespondWithErrorMessage(e.UnAuth, http.StatusUnauthorized) return } // Gather query params query := &Query{Li: cx.Request.URL.Query()} if query.Has("perf") { //Load job perf by id perf, err := core.LoadJobPerf(id) if err != nil { if err == mgo.ErrNotFound { cx.RespondWithNotFound() } else { // In theory the db connection could be lost between // checking user and load but seems unlikely. logger.Error("Err@LoadJobPerf: " + id + ":" + err.Error()) cx.RespondWithErrorMessage("job perf stats not found:"+id, http.StatusBadRequest) } return } cx.RespondWithData(perf) return //done with returning perf, no need to load job further. } if core.QMgr.IsJobRegistered(id) { job.Registered = true } else { job.Registered = false } if query.Has("export") { target := query.Value("export") if target == "" { cx.RespondWithErrorMessage("lacking stage id from which the recompute starts", http.StatusBadRequest) return } else if target == "taverna" { wfrun, err := taverna.ExportWorkflowRun(job) if err != nil { cx.RespondWithErrorMessage("failed to export job to taverna workflowrun:"+id, http.StatusBadRequest) return } cx.RespondWithData(wfrun) return } } // Base case respond with job in json cx.RespondWithData(job) return }
// GET: /queue // get status from queue manager func (cr *QueueController) ReadMany(cx *goweb.Context) { LogRequest(cx.Request) // Gather query params query := &Query{Li: cx.Request.URL.Query()} // unathenticated queue status, numbers only if query.Empty() { statusText := core.QMgr.GetTextStatus() cx.RespondWithData(statusText) return } if query.Has("json") { statusJson := core.QMgr.GetJsonStatus() cx.RespondWithData(statusJson) return } // Try to authenticate user. u, err := request.Authenticate(cx.Request) if err != nil && err.Error() != e.NoAuth { cx.RespondWithErrorMessage(err.Error(), http.StatusUnauthorized) return } // must be admin user if u == nil || u.Admin == false { cx.RespondWithErrorMessage(e.NoAuth, http.StatusUnauthorized) return } // check if valid queue type requested for _, q := range queueTypes { if query.Has(q) { queueData := core.QMgr.GetQueue(q) cx.RespondWithData(queueData) return } } // build set of running jobs based on clientgroup if query.Has("clientgroup") { if query.Value("clientgroup") == "" { cx.RespondWithErrorMessage("missing required clientgroup name", http.StatusBadRequest) return } cg, err := core.LoadClientGroupByName(query.Value("clientgroup")) if err != nil { cx.RespondWithErrorMessage("unable to retrieve clientgroup '"+query.Value("clientgroup")+"': "+err.Error(), http.StatusBadRequest) return } if cg == nil { cx.RespondWithErrorMessage("clientgroup '"+query.Value("clientgroup")+"' does not exist", http.StatusBadRequest) return } // User must have read permissions on clientgroup or be clientgroup owner or be an admin or the clientgroup is publicly readable. // The other possibility is that public read of clientgroups is enabled and the clientgroup is publicly readable. rights := cg.Acl.Check(u.Uuid) public_rights := cg.Acl.Check("public") if (u.Uuid != "public" && (cg.Acl.Owner == u.Uuid || rights["read"] == true || u.Admin == true || public_rights["read"] == true)) || (u.Uuid == "public" && conf.ANON_CG_READ == true && public_rights["read"] == true) { // get running jobs for clients for clientgroup jobs := []*core.Job{} for _, client := range core.QMgr.GetAllClients() { if client.Group == cg.Name { client.Current_work_lock.RLock() for wid, _ := range client.Current_work { jid, _ := core.GetJobIdByWorkId(wid) if job, err := core.LoadJob(jid); err == nil { jobs = append(jobs, job) } } client.Current_work_lock.RUnlock() } } cx.RespondWithData(jobs) return } cx.RespondWithErrorMessage(e.UnAuth, http.StatusUnauthorized) return } cx.RespondWithErrorMessage("requested queue operation not supported", http.StatusBadRequest) return }
// PUT: /job/{id} -> used for job manipulation func (cr *JobController) Update(id string, cx *goweb.Context) { // Log Request and check for Auth LogRequest(cx.Request) // Gather query params query := &Query{Li: cx.Request.URL.Query()} if query.Has("resume") { // to resume a suspended job if err := core.QMgr.ResumeSuspendedJob(id); err != nil { cx.RespondWithErrorMessage("fail to resume job: "+id+" "+err.Error(), http.StatusBadRequest) } cx.RespondWithData("job resumed: " + id) return } if query.Has("suspend") { // to suspend an in-progress job if err := core.QMgr.SuspendJob(id, "manually suspended"); err != nil { cx.RespondWithErrorMessage("fail to suspend job: "+id+" "+err.Error(), http.StatusBadRequest) } cx.RespondWithData("job suspended: " + id) return } if query.Has("resubmit") { // to re-submit a job from mongodb if err := core.QMgr.ResubmitJob(id); err != nil { cx.RespondWithErrorMessage("fail to resubmit job: "+id+" "+err.Error(), http.StatusBadRequest) } cx.RespondWithData("job resubmitted: " + id) return } if query.Has("recompute") { // to recompute a job from task i, the successive/downstream tasks of i will all be computed stage := query.Value("recompute") if stage == "" { cx.RespondWithErrorMessage("lacking stage id from which the recompute starts", http.StatusBadRequest) } if err := core.QMgr.RecomputeJob(id, stage); err != nil { cx.RespondWithErrorMessage("fail to recompute job: "+id+" "+err.Error(), http.StatusBadRequest) } cx.RespondWithData("job recompute started: " + id) return } if query.Has("clientgroup") { // change the clientgroup attribute of the job newgroup := query.Value("clientgroup") if newgroup == "" { cx.RespondWithErrorMessage("lacking groupname", http.StatusBadRequest) } if err := core.QMgr.UpdateGroup(id, newgroup); err != nil { cx.RespondWithErrorMessage("fail to update group for job: "+id+" "+err.Error(), http.StatusBadRequest) } cx.RespondWithData("job group updated: " + id + " to " + newgroup) return } if query.Has("settoken") { // set data token token, err := request.RetrieveToken(cx.Request) if err != nil { cx.RespondWithErrorMessage("fail to retrieve token for job: "+id+" "+err.Error(), http.StatusBadRequest) return } job, err := core.LoadJob(id) if err != nil { if err.Error() == e.MongoDocNotFound { cx.RespondWithNotFound() } else { logger.Error("Err@job_Read:LoadJob: " + id + ":" + err.Error()) cx.RespondWithErrorMessage("job not found:"+id, http.StatusBadRequest) } return } job.SetDataToken(token) cx.RespondWithData("data token set for job: " + id) return } cx.RespondWithData("requested job operation not supported") return }