// PUT: /client func (cr *ClientController) UpdateMany(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_WRITE == true { u = &user.User{Uuid: "public"} } else { cx.RespondWithErrorMessage(e.NoAuth, http.StatusUnauthorized) return } } // Gather query params query := &Query{Li: cx.Request.URL.Query()} if query.Has("resumeall") { //resume the suspended client num := core.QMgr.ResumeSuspendedClientsByUser(u) cx.RespondWithData(fmt.Sprintf("%d suspended clients resumed", num)) return } if query.Has("suspendall") { //resume the suspended client num := core.QMgr.SuspendAllClientsByUser(u) cx.RespondWithData(fmt.Sprintf("%d clients suspended", num)) return } cx.RespondWithError(http.StatusNotImplemented) return }
// DELETE: /job?suspend, /job?zombie func (cr *JobController) DeleteMany(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 delete is allowed, use the public user if u == nil { if conf.ANON_DELETE == true { u = &user.User{Uuid: "public"} } else { cx.RespondWithErrorMessage(e.NoAuth, http.StatusUnauthorized) return } } // Gather query params query := &Query{Li: cx.Request.URL.Query()} if query.Has("suspend") { num := core.QMgr.DeleteSuspendedJobsByUser(u) cx.RespondWithData(fmt.Sprintf("deleted %d suspended jobs", num)) } else if query.Has("zombie") { num := core.QMgr.DeleteZombieJobsByUser(u) cx.RespondWithData(fmt.Sprintf("deleted %d zombie jobs", num)) } else { cx.RespondWithError(http.StatusNotImplemented) } return }
func RespondPrivateEnvInHeader(cx *goweb.Context, Envs map[string]string) (err error) { env_stream, err := json.Marshal(Envs) if err != nil { return err } cx.ResponseWriter.Header().Set("Privateenv", string(env_stream[:])) cx.Respond(nil, http.StatusOK, nil, cx) return }
func AuthError(err error, cx *goweb.Context) { if err.Error() == e.InvalidAuth { cx.RespondWithErrorMessage("Invalid authorization header or content", http.StatusBadRequest) return } logger.Error("Error at Auth: " + err.Error()) cx.RespondWithError(http.StatusInternalServerError) return }
// GET: /queue // get status from queue manager func (cr *QueueController) ReadMany(cx *goweb.Context) { LogRequest(cx.Request) // Gather query params // query := &Query{list: cx.Request.URL.Query()} msg := core.QMgr.ShowStatus() cx.RespondWithData(msg) return }
// GET: /awf/{name} // get a workflow by name, read-only func (cr *AwfController) Read(id string, cx *goweb.Context) { LogRequest(cx.Request) // Load workunit by id workflow, err := core.AwfMgr.GetWorkflow(id) if err != nil { cx.RespondWithErrorMessage(err.Error(), http.StatusBadRequest) return } // Base case respond with workunit in json cx.RespondWithData(workflow) return }
// DELETE: /client/{id} func (cr *ClientController) Delete(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_DELETE == true { u = &user.User{Uuid: "public"} } else { cx.RespondWithErrorMessage(e.NoAuth, http.StatusUnauthorized) return } } if err := core.QMgr.DeleteClientByUser(id, u); err != nil { cx.RespondWithErrorMessage(err.Error(), http.StatusBadRequest) } else { cx.RespondWithData("client deleted") } return }
// POST: /cgroup/{name} func (cr *ClientGroupController) CreateWithId(name 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 ANON_CG_WRITE is true, use the public user. // Otherwise if no auth was provided or user is not an admin, and ANON_CG_WRITE is false, throw an error. // Otherwise, proceed with creation of the clientgroup with the user. if u == nil && conf.ANON_CG_WRITE == true { u = &user.User{Uuid: "public"} } else if u == nil || !u.Admin { if conf.ANON_CG_WRITE == false { cx.RespondWithErrorMessage(e.UnAuth, http.StatusUnauthorized) return } } cg, err := core.CreateClientGroup(name, u) if err != nil { cx.RespondWithErrorMessage(err.Error(), http.StatusBadRequest) return } cx.RespondWithData(cg) return }
func AdminAuthenticated(cx *goweb.Context) bool { user, err := request.Authenticate(cx.Request) if err != nil { if err.Error() == e.NoAuth || err.Error() == e.UnAuth { cx.RespondWithError(http.StatusUnauthorized) } else { request.AuthError(err, cx) } return false } if _, ok := conf.Admin_Users[user.Username]; !ok { msg := fmt.Sprintf("user %s has no admin right", user.Username) cx.RespondWithErrorMessage(msg, http.StatusBadRequest) return false } return true }
// POST: /client - register a new client func (cr *ClientController) Create(cx *goweb.Context) { // Log Request and check for Auth LogRequest(cx.Request) 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 } } // Parse uploaded form _, files, err := ParseMultipartForm(cx.Request) if err != nil { if err.Error() != "request Content-Type isn't multipart/form-data" { logger.Error("Error parsing form: " + err.Error()) cx.RespondWithError(http.StatusBadRequest) return } } client, err := core.QMgr.RegisterNewClient(files, cg) if err != nil { msg := "Error in registering new client:" + err.Error() logger.Error(msg) cx.RespondWithErrorMessage(msg, http.StatusBadRequest) return } //log event about client registration (CR) logger.Event(event.CLIENT_REGISTRATION, "clientid="+client.Id+";name="+client.Name+";host="+client.Host+";group="+client.Group+";instance_id="+client.InstanceId+";instance_type="+client.InstanceType+";domain="+client.Domain) cx.RespondWithData(client) return }
func ResourceDescription(cx *goweb.Context) { LogRequest(cx.Request) r := resource{ R: []string{}, U: apiUrl(cx) + "/", D: siteUrl(cx) + "/", Title: conf.TITLE, C: conf.ADMIN_EMAIL, I: "AWE", T: core.Service, V: conf.VERSION, GitCommitHash: conf.GIT_COMMIT_HASH, } if core.Service == "server" { r.R = []string{"job", "work", "client", "queue", "awf"} } else if core.Service == "proxy" { r.R = []string{"client", "work"} } cx.WriteResponse(r, 200) return }
// GET: /client func (cr *ClientController) ReadMany(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 } } clients := core.QMgr.GetAllClientsByUser(u) query := &Query{Li: cx.Request.URL.Query()} filtered := []*core.Client{} if query.Has("busy") { for _, client := range clients { if len(client.Current_work) > 0 { filtered = append(filtered, client) } } } else { filtered = clients } cx.RespondWithData(filtered) return }
// OPTIONS: /work func (cr *WorkController) Options(cx *goweb.Context) { LogRequest(cx.Request) cx.RespondWithOK() return }
// GET: /work // checkout a workunit with earliest submission time // to-do: to support more options for workunit checkout func (cr *WorkController) ReadMany(cx *goweb.Context) { LogRequest(cx.Request) // Gather query params query := &Query{Li: cx.Request.URL.Query()} if !query.Has("client") { //view workunits // 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 } } // get pagination options limit := conf.DEFAULT_PAGE_SIZE offset := 0 order := "info.submittime" direction := "desc" if query.Has("limit") { limit, err = strconv.Atoi(query.Value("limit")) if err != nil { cx.RespondWithErrorMessage(err.Error(), http.StatusBadRequest) return } } if query.Has("offset") { offset, err = strconv.Atoi(query.Value("offset")) if err != nil { cx.RespondWithErrorMessage(err.Error(), http.StatusBadRequest) return } } if query.Has("order") { order = query.Value("order") } if query.Has("direction") { direction = query.Value("direction") } var workunits []*core.Workunit if query.Has("state") { workunits = core.QMgr.ShowWorkunitsByUser(query.Value("state"), u) } else { workunits = core.QMgr.ShowWorkunitsByUser("", u) } // if using query syntax then do pagination and sorting if query.Has("query") { filtered_work := []core.Workunit{} sorted_work := core.WorkunitsSortby{order, direction, workunits} sort.Sort(sorted_work) skip := 0 count := 0 for _, w := range sorted_work.Workunits { if skip < offset { skip += 1 continue } filtered_work = append(filtered_work, *w) count += 1 if count == limit { break } } cx.RespondWithPaginatedData(filtered_work, limit, offset, len(sorted_work.Workunits)) return } else { cx.RespondWithData(workunits) return } } 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 core.Service == "proxy" { //drive proxy workStealer to checkout work from server core.ProxyWorkChan <- true } //checkout a workunit in FCFS order workunits, err := core.QMgr.CheckoutWorkunits("FCFS", clientid, 1) if err != nil { if err.Error() != e.QueueEmpty && err.Error() != e.NoEligibleWorkunitFound && err.Error() != e.ClientNotFound && err.Error() != e.ClientSuspended { logger.Error("Err@work_ReadMany:core.QMgr.GetWorkByFCFS(): " + err.Error() + ";client=" + clientid) } cx.RespondWithErrorMessage(err.Error(), http.StatusBadRequest) return } //log event about workunit checkout (WO) workids := []string{} for _, work := range workunits { workids = append(workids, work.Id) } logger.Event(event.WORK_CHECKOUT, "workids="+strings.Join(workids, ","), "clientid="+clientid) // Base case respond with node in json cx.RespondWithData(workunits[0]) 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) // 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 }
// DELETE: /job/{id} func (cr *JobController) Delete(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) } // If no auth was provided, and anonymous delete is allowed, use the public user if u == nil { if conf.ANON_DELETE == true { u = &user.User{Uuid: "public"} } else { cx.RespondWithErrorMessage(e.NoAuth, http.StatusUnauthorized) return } } if err = core.QMgr.DeleteJobByUser(id, u); err != nil { if err == mgo.ErrNotFound { cx.RespondWithNotFound() return } else if err.Error() == e.UnAuth { cx.RespondWithErrorMessage(e.UnAuth, http.StatusUnauthorized) return } else { cx.RespondWithErrorMessage("fail to delete job: "+id, http.StatusBadRequest) return } } cx.RespondWithData("job deleted: " + id) return }
// DELETE: /queue func (cr *QueueController) DeleteMany(cx *goweb.Context) { LogRequest(cx.Request) cx.RespondWithError(http.StatusNotImplemented) return }
// POST: /job func (cr *JobController) Create(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 } } // Parse uploaded form params, files, err := ParseMultipartForm(cx.Request) if err != nil { if err.Error() == "request Content-Type isn't multipart/form-data" { cx.RespondWithErrorMessage("No job file is submitted", http.StatusBadRequest) } else { // Some error other than request encoding. Theoretically // could be a lost db connection between user lookup and parsing. // Blame the user, Its probaby their fault anyway. logger.Error("Error parsing form: " + err.Error()) cx.RespondWithError(http.StatusBadRequest) } return } _, has_upload := files["upload"] _, has_awf := files["awf"] if !has_upload && !has_awf { cx.RespondWithErrorMessage("No job script or awf is submitted", http.StatusBadRequest) return } //send job submission request and get back an assigned job number (jid) var jid string jid, err = core.QMgr.JobRegister() if err != nil { logger.Error("Err@job_Create:GetNextJobNum: " + err.Error()) cx.RespondWithErrorMessage(err.Error(), http.StatusBadRequest) return } var job *core.Job job, err = core.CreateJobUpload(u, params, files, jid) if err != nil { logger.Error("Err@job_Create:CreateJobUpload: " + err.Error()) cx.RespondWithErrorMessage(err.Error(), http.StatusBadRequest) return } if token, err := request.RetrieveToken(cx.Request); err == nil { job.SetDataToken(token) } core.QMgr.EnqueueTasksByJobId(job.Id, job.TaskList()) //log event about job submission (JB) logger.Event(event.JOB_SUBMISSION, "jobid="+job.Id+";jid="+job.Jid+";name="+job.Info.Name+";project="+job.Info.Project+";user="+job.Info.User) cx.RespondWithData(job) return }
// PUT: /client/{id} -> status update func (cr *ClientController) Update(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_WRITE == true { u = &user.User{Uuid: "public"} } else { cx.RespondWithErrorMessage(e.NoAuth, http.StatusUnauthorized) return } } // Gather query params query := &Query{Li: cx.Request.URL.Query()} if query.Has("subclients") { //update the number of subclients for a proxy if count, err := strconv.Atoi(query.Value("subclients")); err != nil { cx.RespondWithError(http.StatusNotImplemented) } else { core.QMgr.UpdateSubClientsByUser(id, count, u) cx.RespondWithData("ok") } return } if query.Has("suspend") { //resume the suspended client if err := core.QMgr.SuspendClientByUser(id, u); err != nil { cx.RespondWithErrorMessage(err.Error(), http.StatusBadRequest) } else { cx.RespondWithData("client suspended") } return } if query.Has("resume") { //resume the suspended client if err := core.QMgr.ResumeClientByUser(id, u); err != nil { cx.RespondWithErrorMessage(err.Error(), http.StatusBadRequest) } else { cx.RespondWithData("client resumed") } return } cx.RespondWithError(http.StatusNotImplemented) 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 }
// PUT: /queue/{id} -> status update func (cr *QueueController) Update(id string, cx *goweb.Context) { LogRequest(cx.Request) cx.RespondWithError(http.StatusNotImplemented) return }
// GET: /client/{id} func (cr *ClientController) Read(id string, cx *goweb.Context) { // Gather query params query := &Query{Li: cx.Request.URL.Query()} if query.Has("heartbeat") { //handle heartbeat 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 } } hbmsg, err := core.QMgr.ClientHeartBeat(id, cg) if err != nil { cx.RespondWithErrorMessage(err.Error(), http.StatusBadRequest) } else { cx.RespondWithData(hbmsg) } return } LogRequest(cx.Request) //skip heartbeat in access log // 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 } } client, err := core.QMgr.GetClientByUser(id, u) if err != nil { if err.Error() == e.ClientNotFound { cx.RespondWithErrorMessage(e.ClientNotFound, http.StatusBadRequest) } else { logger.Error("Error in GET client:" + err.Error()) cx.RespondWithError(http.StatusBadRequest) } return } cx.RespondWithData(client) return }
func RespondTokenInHeader(cx *goweb.Context, token string) { cx.ResponseWriter.Header().Set("Datatoken", token) cx.Respond(nil, http.StatusOK, nil, cx) return }
// GET: /awf // get all loaded workflows func (cr *AwfController) ReadMany(cx *goweb.Context) { // Gather query params workflows := core.AwfMgr.GetAllWorkflows() cx.RespondWithData(workflows) return }
// GET: /cgroup/{id} func (cr *ClientGroupController) 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 ANON_CG_READ is true, use the public user. // Otherwise if no auth was provided, throw an error. // Otherwise, proceed with retrieval of the clientgroup using the user. if u == nil { if conf.ANON_CG_READ == true { u = &user.User{Uuid: "public"} } else { cx.RespondWithErrorMessage(e.UnAuth, http.StatusUnauthorized) return } } // Load clientgroup by id cg, err := core.LoadClientGroup(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. cx.RespondWithErrorMessage("clientgroup id not found:"+id, 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) { cx.RespondWithData(cg) return } cx.RespondWithErrorMessage(e.UnAuth, http.StatusUnauthorized) 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 }
// GET: /job // To do: // - Iterate job queries func (cr *JobController) ReadMany(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 } // Gather query params query := &Query{Li: cx.Request.URL.Query()} // Setup query and jobs objects q := bson.M{} jobs := core.Jobs{} if u != nil { // Add authorization checking to query if the user is not an admin if u.Admin == false { q["$or"] = []bson.M{bson.M{"acl.read": "public"}, bson.M{"acl.read": u.Uuid}, bson.M{"acl.owner": u.Uuid}, bson.M{"acl": bson.M{"$exists": "false"}}} } } else { // User is anonymous if conf.ANON_READ { // select on only jobs that are publicly readable q["acl.read"] = "public" } else { cx.RespondWithErrorMessage(e.NoAuth, http.StatusUnauthorized) return } } limit := conf.DEFAULT_PAGE_SIZE offset := 0 order := "info.submittime" direction := "desc" if query.Has("limit") { limit, err = strconv.Atoi(query.Value("limit")) if err != nil { cx.RespondWithErrorMessage(err.Error(), http.StatusBadRequest) return } } if query.Has("offset") { offset, err = strconv.Atoi(query.Value("offset")) if err != nil { cx.RespondWithErrorMessage(err.Error(), http.StatusBadRequest) return } } if query.Has("order") { order = query.Value("order") } if query.Has("direction") { direction = query.Value("direction") } // Gather params to make db query. Do not include the // following list. skip := map[string]int{"limit": 1, "offset": 1, "query": 1, "recent": 1, "order": 1, "direction": 1, "active": 1, "suspend": 1, "registered": 1, "verbosity": 1, "userattr": 1, } if query.Has("query") { const shortForm = "2006-01-02" date_query := bson.M{} for key, val := range query.All() { _, s := skip[key] if !s { // special case for date range, either full date-time or just date if (key == "date_start") || (key == "date_end") { opr := "$gte" if key == "date_end" { opr = "$lt" } if t_long, err := time.Parse(time.RFC3339, val[0]); err != nil { if t_short, err := time.Parse(shortForm, val[0]); err != nil { cx.RespondWithErrorMessage("Invalid datetime format: "+val[0], http.StatusBadRequest) return } else { date_query[opr] = t_short } } else { date_query[opr] = t_long } } else { // handle either multiple values for key, or single comma-spereated value if len(val) == 1 { queryvalues := strings.Split(val[0], ",") q[key] = bson.M{"$in": queryvalues} } else if len(val) > 1 { q[key] = bson.M{"$in": val} } } } } // add submittime and completedtime range query if len(date_query) > 0 { q["$or"] = []bson.M{bson.M{"info.submittime": date_query}, bson.M{"info.completedtime": date_query}} } } else if query.Has("active") { q["state"] = bson.M{"$in": core.JOB_STATS_ACTIVE} } else if query.Has("suspend") { q["state"] = core.JOB_STAT_SUSPEND } else if query.Has("registered") { q["state"] = bson.M{"$in": core.JOB_STATS_REGISTERED} } //getting real active (in-progress) job (some jobs are in "submitted" states but not in the queue, //because they may have failed and not recovered from the mongodb). if query.Has("active") { err := jobs.GetAll(q, order, direction) if err != nil { logger.Error("err " + err.Error()) cx.RespondWithErrorMessage(err.Error(), http.StatusBadRequest) return } filtered_jobs := []core.Job{} act_jobs := core.QMgr.GetActiveJobs() length := jobs.Length() skip := 0 count := 0 for i := 0; i < length; i++ { job := jobs.GetJobAt(i) if _, ok := act_jobs[job.Id]; ok { if skip < offset { skip += 1 continue } job.Registered = true filtered_jobs = append(filtered_jobs, job) count += 1 if count == limit { break } } } cx.RespondWithPaginatedData(filtered_jobs, limit, offset, len(act_jobs)) return } //geting suspended job in the current queue (excluding jobs in db but not in qmgr) if query.Has("suspend") { err := jobs.GetAll(q, order, direction) if err != nil { logger.Error("err " + err.Error()) cx.RespondWithError(http.StatusBadRequest) return } filtered_jobs := []core.Job{} suspend_jobs := core.QMgr.GetSuspendJobs() length := jobs.Length() skip := 0 count := 0 for i := 0; i < length; i++ { job := jobs.GetJobAt(i) if _, ok := suspend_jobs[job.Id]; ok { if skip < offset { skip += 1 continue } job.Registered = true filtered_jobs = append(filtered_jobs, job) count += 1 if count == limit { break } } } cx.RespondWithPaginatedData(filtered_jobs, limit, offset, len(suspend_jobs)) return } if query.Has("registered") { err := jobs.GetAll(q, order, direction) if err != nil { logger.Error("err " + err.Error()) cx.RespondWithError(http.StatusBadRequest) return } paged_jobs := []core.Job{} registered_jobs := []core.Job{} length := jobs.Length() total := 0 for i := 0; i < length; i++ { job := jobs.GetJobAt(i) if core.QMgr.IsJobRegistered(job.Id) { job.Registered = true registered_jobs = append(registered_jobs, job) total += 1 } } count := 0 for i := offset; i < len(registered_jobs); i++ { paged_jobs = append(paged_jobs, registered_jobs[i]) count += 1 if count == limit { break } } cx.RespondWithPaginatedData(paged_jobs, limit, offset, total) return } if query.Has("verbosity") && (query.Value("verbosity") == "minimal") { // TODO - have mongo query only return fields needed to populate JobMin struct total, err := jobs.GetPaginated(q, limit, offset, order, direction) if err != nil { logger.Error("err " + err.Error()) cx.RespondWithError(http.StatusBadRequest) return } minimal_jobs := []core.JobMin{} length := jobs.Length() for i := 0; i < length; i++ { job := jobs.GetJobAt(i) // create and populate minimal job mjob := core.JobMin{} mjob.Id = job.Id mjob.Name = job.Info.Name mjob.SubmitTime = job.Info.SubmitTime mjob.CompletedTime = job.Info.CompletedTime // get size of input var size_sum int64 = 0 for _, v := range job.Tasks[0].Inputs { size_sum = size_sum + v.Size } mjob.Size = size_sum // add userattr fields if query.Has("userattr") { mjob.UserAttr = map[string]string{} for _, attr := range query.List("userattr") { if val, ok := job.Info.UserAttr[attr]; ok { mjob.UserAttr[attr] = val } } } if (job.State == "completed") || (job.State == "deleted") { // if completed or deleted move on, empty task array mjob.State = append(mjob.State, job.State) } else if job.State == "suspend" { // get failed task if info available, otherwise empty task array mjob.State = append(mjob.State, "suspend") parts := strings.Split(job.LastFailed, "_") if (len(parts) == 2) || (len(parts) == 3) { if tid, err := strconv.Atoi(parts[1]); err == nil { mjob.Task = append(mjob.Task, tid) } } } else { // get multiple tasks in state queued or in-progress for j := 0; j < len(job.Tasks); j++ { task := job.Tasks[j] if (task.State == "in-progress") || (task.State == "queued") { mjob.State = append(mjob.State, task.State) mjob.Task = append(mjob.Task, j) } } // otherwise get oldest pending or init task if len(mjob.State) == 0 { for j := 0; j < len(job.Tasks); j++ { task := job.Tasks[j] if (task.State == "pending") || (task.State == "init") { mjob.State = append(mjob.State, task.State) mjob.Task = append(mjob.Task, j) break } } } } minimal_jobs = append(minimal_jobs, mjob) } cx.RespondWithPaginatedData(minimal_jobs, limit, offset, total) return } total, err := jobs.GetPaginated(q, limit, offset, order, direction) if err != nil { logger.Error("err " + err.Error()) cx.RespondWithError(http.StatusBadRequest) return } filtered_jobs := []core.Job{} length := jobs.Length() for i := 0; i < length; i++ { job := jobs.GetJobAt(i) if core.QMgr.IsJobRegistered(job.Id) { job.Registered = true } else { job.Registered = false } filtered_jobs = append(filtered_jobs, job) } cx.RespondWithPaginatedData(filtered_jobs, limit, offset, total) return }
// PUT: /work/{id} -> status update func (cr *WorkController) Update(id string, cx *goweb.Context) { LogRequest(cx.Request) // Gather query params query := &Query{Li: cx.Request.URL.Query()} if !query.Has("client") { cx.RespondWithErrorMessage("This request type requires the client=clientid parameter.", http.StatusBadRequest) return } // Check auth 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("status") && query.Has("client") { //notify execution result: "done" or "fail" notice := core.Notice{WorkId: id, Status: query.Value("status"), ClientId: query.Value("client"), Notes: ""} if query.Has("computetime") { if comptime, err := strconv.Atoi(query.Value("computetime")); err == nil { notice.ComputeTime = comptime } } if query.Has("report") { // if "report" is specified in query, parse performance statistics or errlog if _, files, err := ParseMultipartForm(cx.Request); err == nil { if _, ok := files["perf"]; ok { core.QMgr.FinalizeWorkPerf(id, files["perf"].Path) } if _, ok := files["notes"]; ok { if notes, err := ioutil.ReadFile(files["notes"].Path); err == nil { notice.Notes = string(notes) } } if _, ok := files["stdout"]; ok { core.QMgr.SaveStdLog(id, "stdout", files["stdout"].Path) } if _, ok := files["stderr"]; ok { core.QMgr.SaveStdLog(id, "stderr", files["stderr"].Path) } if _, ok := files["worknotes"]; ok { core.QMgr.SaveStdLog(id, "worknotes", files["worknotes"].Path) } } } core.QMgr.NotifyWorkStatus(notice) } cx.RespondWithData("ok") return }
// GET: /cgroup func (cr *ClientGroupController) ReadMany(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 ANON_CG_READ is true, use the public user. // Otherwise if no auth was provided, throw an error. // Otherwise, proceed with retrieval of the clientgroups using the user. if u == nil { if conf.ANON_CG_READ == true { u = &user.User{Uuid: "public"} } else { cx.RespondWithErrorMessage(e.UnAuth, http.StatusUnauthorized) return } } // Gather query params query := &Query{Li: cx.Request.URL.Query()} // Setup query and clientgroups objects q := bson.M{} cgs := core.ClientGroups{} // Add authorization checking to query if the user is not an admin if u.Admin == false { q["$or"] = []bson.M{bson.M{"acl.read": "public"}, bson.M{"acl.read": u.Uuid}, bson.M{"acl.owner": u.Uuid}} } limit := conf.DEFAULT_PAGE_SIZE offset := 0 order := "last_modified" direction := "desc" if query.Has("limit") { limit, err = strconv.Atoi(query.Value("limit")) if err != nil { cx.RespondWithErrorMessage(err.Error(), http.StatusBadRequest) return } } if query.Has("offset") { offset, err = strconv.Atoi(query.Value("offset")) if err != nil { cx.RespondWithErrorMessage(err.Error(), http.StatusBadRequest) return } } if query.Has("order") { order = query.Value("order") } if query.Has("direction") { direction = query.Value("direction") } // Gather params to make db query. Do not include the following list. skip := map[string]int{ "limit": 1, "offset": 1, "order": 1, "direction": 1, } for key, val := range query.All() { _, s := skip[key] if !s { queryvalues := strings.Split(val[0], ",") q[key] = bson.M{"$in": queryvalues} } } total, err := cgs.GetPaginated(q, limit, offset, order, direction) if err != nil { cx.RespondWithError(http.StatusBadRequest) return } cx.RespondWithPaginatedData(cgs, limit, offset, total) return }