From f583c86e4d21cf49c6af14fea65147d740ebf69b Mon Sep 17 00:00:00 2001 From: Anthony Maton Date: Wed, 15 Feb 2017 15:19:04 +0100 Subject: [PATCH 01/10] Add a message for status --- go/src/pythia/structs.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/go/src/pythia/structs.go b/go/src/pythia/structs.go index 0964cb8..dc41d0e 100644 --- a/go/src/pythia/structs.go +++ b/go/src/pythia/structs.go @@ -90,6 +90,9 @@ const ( // (or another status if the job has ended meanwhile). // Frontend->Queue, Queue->Pool AbortMsg MsgType = "abort" + + // Request status of the Queue + StatusMsg MsgType = "status" ) // A Message is the basic entity that is sent between components. Messages are From 45eca18b0feecfb6a669b18fd4f5a4bc207e3c2d Mon Sep 17 00:00:00 2001 From: Anthony Maton Date: Wed, 15 Feb 2017 16:24:14 +0100 Subject: [PATCH 02/10] Add tags for json marshaler --- go/src/pythia/backend/queue.go | 60 +++++++++++++++++----------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/go/src/pythia/backend/queue.go b/go/src/pythia/backend/queue.go index 9214834..fee6f98 100644 --- a/go/src/pythia/backend/queue.go +++ b/go/src/pythia/backend/queue.go @@ -67,7 +67,7 @@ type queueJob struct { // The client having submitted this job. Origin *queueClient - // Element of the queue.waiting list pointing to this job, or nil if the job + // Element of the queue.Waiting list pointing to this job, or nil if the job // is currently running. WaitingElement *list.Element @@ -104,7 +104,7 @@ const ( // components connect to it. type Queue struct { // The maximum number of jobs that can wait to be executed. - Capacity int + Capacity int `json:"capacity"` // Channel to send messages to the main goroutine master chan<- queueMessage @@ -116,13 +116,13 @@ type Queue struct { wg sync.WaitGroup // Active connections - clients map[int]*queueClient + Clients map[int]*queueClient `json:"clients"` // Jobs to be processed/currently processing - jobs map[string]*queueJob + Jobs map[string]*queueJob `json:"jobs"` // List of jobs (*queueJob) waiting to be assigned. - waiting *list.List + Waiting *list.List `json:"waiting"` } // NewQueue returns a new queue with default parameters. @@ -192,21 +192,21 @@ func (queue *Queue) Shutdown() { // Main goroutine responsible for scheduling the jobs. func (queue *Queue) main(master <-chan queueMessage) { defer queue.wg.Done() - queue.clients = make(map[int]*queueClient) - queue.jobs = make(map[string]*queueJob) - queue.waiting = list.New() + queue.Clients = make(map[int]*queueClient) + queue.Jobs = make(map[string]*queueJob) + queue.Waiting = list.New() for qm := range master { switch qm.Msg.Message { case connectMsg: log.Print("Client ", qm.Client.Id, ": connected.") - queue.clients[qm.Client.Id] = qm.Client + queue.Clients[qm.Client.Id] = qm.Client case pythia.RegisterPoolMsg: log.Print("Client ", qm.Client.Id, ": pool capacity ", qm.Msg.Capacity) qm.Client.Capacity = qm.Msg.Capacity case pythia.LaunchMsg: id := qm.Msg.Id - if _, ok := queue.jobs[id]; ok { + if _, ok := queue.Jobs[id]; ok { log.Print("Job ", id, ": already launched, rejecting.") qm.Client.Response <- pythia.Message{ Message: pythia.DoneMsg, @@ -214,7 +214,7 @@ func (queue *Queue) main(master <-chan queueMessage) { Status: pythia.Fatal, Output: "Job already launched", } - } else if queue.waiting.Len() >= queue.Capacity { + } else if queue.Waiting.Len() >= queue.Capacity { log.Print("Job ", id, ": queue full, rejecting.") qm.Client.Response <- pythia.Message{ Message: pythia.DoneMsg, @@ -229,14 +229,14 @@ func (queue *Queue) main(master <-chan queueMessage) { Origin: qm.Client, } qm.Client.Submitted[id] = job - queue.jobs[id] = job - job.WaitingElement = queue.waiting.PushBack(job) + queue.Jobs[id] = job + job.WaitingElement = queue.Waiting.PushBack(job) log.Print("Job ", id, ": queued.") } case pythia.DoneMsg: id := qm.Msg.Id log.Print("Job ", id, ": done.") - job := queue.jobs[id] + job := queue.Jobs[id] if job == nil { log.Println("Ignoring message for unknown job", qm.Msg) break @@ -246,7 +246,7 @@ func (queue *Queue) main(master <-chan queueMessage) { log.Println("Ignoring message from wrong source", qm.Msg) break } - delete(queue.jobs, id) + delete(queue.Jobs, id) delete(pool.Running, id) if job.Origin != nil { // job.Origin is nil if the submitting client has disconnected @@ -257,22 +257,22 @@ func (queue *Queue) main(master <-chan queueMessage) { case closedMsg: log.Print("Client ", qm.Client.Id, ": disconnected.") close(qm.Client.Response) - delete(queue.clients, qm.Client.Id) + delete(queue.Clients, qm.Client.Id) for _, job := range qm.Client.Running { if job.Origin == nil { // Submitter disconnected, we can discard the job. - delete(queue.jobs, job.Id) + delete(queue.Jobs, job.Id) } else { // Otherwise, reschedule it. job.Pool = nil - job.WaitingElement = queue.waiting.PushFront(job) + job.WaitingElement = queue.Waiting.PushFront(job) } } for _, job := range qm.Client.Submitted { if job.WaitingElement != nil { // Job is in waiting queue, discard it. - queue.waiting.Remove(job.WaitingElement) - delete(queue.jobs, job.Id) + queue.Waiting.Remove(job.WaitingElement) + delete(queue.Jobs, job.Id) } else if job.Pool != nil { // Job is running, abort it. job.Origin = nil @@ -280,7 +280,7 @@ func (queue *Queue) main(master <-chan queueMessage) { Message: pythia.AbortMsg, Id: job.Id, } - // Keep job in queue.jobs to handle abort result + // Keep job in queue.Jobs to handle abort result } } case quitMsg: @@ -295,19 +295,19 @@ func (queue *Queue) main(master <-chan queueMessage) { } quit: - if len(queue.clients) == 0 { + if len(queue.Clients) == 0 { return } - for _, client := range queue.clients { + for _, client := range queue.Clients { close(client.Response) } - // Wait for all clients to quit. We flush messages from the master channel + // Wait for all Clients to quit. We flush messages from the master channel // to ensure no connection handler is in a deadlock. for qm := range master { switch qm.Msg.Message { case closedMsg: - delete(queue.clients, qm.Client.Id) - if len(queue.clients) == 0 { + delete(queue.Clients, qm.Client.Id) + if len(queue.Clients) == 0 { return } default: @@ -320,17 +320,17 @@ quit: // This function shall be called from the main goroutine, as it manipulates // the queue data structures. func (queue *Queue) schedule() { - if queue.waiting.Len() == 0 { + if queue.Waiting.Len() == 0 { return } - for _, client := range queue.clients { + for _, client := range queue.Clients { for len(client.Running) < client.Capacity { - job := queue.waiting.Remove(queue.waiting.Front()).(*queueJob) + job := queue.Waiting.Remove(queue.Waiting.Front()).(*queueJob) job.WaitingElement = nil job.Pool = client client.Running[job.Id] = job client.Response <- job.Msg - if queue.waiting.Len() == 0 { + if queue.Waiting.Len() == 0 { return } } From 0f67df8a126c50a2eb4233e33259d43622536c5f Mon Sep 17 00:00:00 2001 From: Anthony Maton Date: Wed, 15 Feb 2017 16:30:50 +0100 Subject: [PATCH 03/10] Add CreationDate to the queue for monitoring purposes --- go/src/pythia/backend/queue.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/go/src/pythia/backend/queue.go b/go/src/pythia/backend/queue.go index fee6f98..bdaadcd 100644 --- a/go/src/pythia/backend/queue.go +++ b/go/src/pythia/backend/queue.go @@ -23,6 +23,7 @@ import ( "pythia" "strings" "sync" + "time" ) func init() { @@ -123,6 +124,9 @@ type Queue struct { // List of jobs (*queueJob) waiting to be assigned. Waiting *list.List `json:"waiting"` + + // Get the Queue creation datetime + CreationDate Time `json:"creation_date"` } // NewQueue returns a new queue with default parameters. @@ -130,6 +134,7 @@ func NewQueue() *Queue { queue := new(Queue) queue.Capacity = 500 queue.quit = make(chan bool, 1) + queue.CreationDate = time.Now() return queue } From 963fb069236327a5e7ea4f4a2564d1da02ba71ec Mon Sep 17 00:00:00 2001 From: Anthony Maton Date: Thu, 16 Feb 2017 09:03:36 +0100 Subject: [PATCH 04/10] Debugging instructions --- go/src/pythia/backend/queue.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/go/src/pythia/backend/queue.go b/go/src/pythia/backend/queue.go index bdaadcd..cd3265e 100644 --- a/go/src/pythia/backend/queue.go +++ b/go/src/pythia/backend/queue.go @@ -24,6 +24,8 @@ import ( "strings" "sync" "time" + + "encoding/json" ) func init() { @@ -154,6 +156,11 @@ func (queue *Queue) Run() { closing := false master := make(chan queueMessage) queue.master = master + + /* DEBUG */ + json, err := json.Marshal(queue) + fmt.Println(string(json)) + /* END DEBUG */ go func() { <-queue.quit closing = true From f8a529396e79bc8c645158f824c0c0040add7734 Mon Sep 17 00:00:00 2001 From: Anthony Maton Date: Thu, 16 Feb 2017 16:54:21 +0100 Subject: [PATCH 05/10] Add possibility to get status via a TCP Message --- go/src/pythia/backend/queue.go | 84 +++++++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 7 deletions(-) diff --git a/go/src/pythia/backend/queue.go b/go/src/pythia/backend/queue.go index cd3265e..0a21602 100644 --- a/go/src/pythia/backend/queue.go +++ b/go/src/pythia/backend/queue.go @@ -17,6 +17,7 @@ package backend import ( "container/list" + "encoding/json" "flag" "fmt" "log" @@ -24,8 +25,6 @@ import ( "strings" "sync" "time" - - "encoding/json" ) func init() { @@ -99,6 +98,17 @@ const ( quitMsg pythia.MsgType = "-quit" ) +// A queueStatus is an internal structure required to marshal the state of the Queue +// in a semantically right JSON. +type QueueStatus struct { + Capacity int `json:"capacity"` + Available int `json:"available"` + Clients []*queueClient `json:"clients"` + Jobs []*queueJob `json:"jobs"` + Waiting *list.List `json:"waiting"` + CreationDate time.Time `json:"creation_date"` +} + // The Queue is the central component of Pythia. // It receives jobs (tasks with inputs) from front-ends and dispatches them // to the sandboxes. @@ -128,7 +138,7 @@ type Queue struct { Waiting *list.List `json:"waiting"` // Get the Queue creation datetime - CreationDate Time `json:"creation_date"` + CreationDate time.Time `json:"creation_date"` } // NewQueue returns a new queue with default parameters. @@ -157,10 +167,6 @@ func (queue *Queue) Run() { master := make(chan queueMessage) queue.master = master - /* DEBUG */ - json, err := json.Marshal(queue) - fmt.Println(string(json)) - /* END DEBUG */ go func() { <-queue.quit closing = true @@ -298,6 +304,19 @@ func (queue *Queue) main(master <-chan queueMessage) { case quitMsg: log.Println("Quitting.") goto quit + + case pythia.StatusMsg: + log.Println("Status Requested") + status := fillQueueStatus(queue) + id := qm.Msg.Id + serializedStatus, _ := json.Marshal(status) + qm.Client.Response <- pythia.Message{ + Message: pythia.DoneMsg, + Id: id, + Status: pythia.Success, + Output: string(serializedStatus), + } + log.Println("Status sent") default: log.Fatal("Invalid internal message", qm.Msg) } @@ -373,6 +392,8 @@ func (queue *Queue) handle(conn *pythia.Conn, client *queueClient, response chan queue.master <- queueMessage{msg, client} case pythia.DoneMsg: queue.master <- queueMessage{msg, client} + case pythia.StatusMsg: + queue.master <- queueMessage{msg, client} default: log.Println("Ignoring message", msg) } @@ -386,10 +407,59 @@ func (queue *Queue) handle(conn *pythia.Conn, client *queueClient, response chan case pythia.DoneMsg: msg.Id = msg.Id[strings.Index(msg.Id, ":")+1:] conn.Send(msg) + case pythia.StatusMsg: + conn.Send(msg) default: log.Fatal("Invalid internal message", msg) } } } +// Utilitary function + +// Convert a Map given in argument into a slice +// Note: the Map id is lost during the process +/*func convertMapToSlice(myMap interface{}) (err Error, mySlice []interface{}) { + reflectedMap := reflect.ValueOf(myMap) + mapContentType := reflect.TypeOf(myMap).Elem() + if reflectedMap.Kind() == reflect.Map { + slice := reflect.MakeSlice(reflect.SliceOf(mapContentType), 0, 0).Interface() + for index, element := range reflectedMap { + append(slice, element) + } + return nil, slice + } else { + return errors.New("The parameter must be of type Map") + } +}*/ +// TODO: Should be generified to use any kind of input type +func convertClientsToSlice(clients map[int]*queueClient) (clientsSlice []*queueClient) { + clientsSlice = make([]*queueClient, len(clients)) + for _, element := range clients { + clientsSlice = append(clientsSlice, element) + } + return clientsSlice +} + +// TODO: Should be generified to use any kind of input type +func convertJobsToSlice(jobs map[string]*queueJob) (jobsSlice []*queueJob) { + jobsSlice = make([]*queueJob, len(jobs)) + for _, element := range jobs { + jobsSlice = append(jobsSlice, element) + } + return jobsSlice +} + +// Return a QueueStatus struct filled with information coming from the Queue +func fillQueueStatus(queue *Queue) (status QueueStatus) { + status.Capacity = queue.Capacity + status.Available = queue.Capacity - len(queue.Jobs) - queue.Waiting.Len() + status.Clients = convertClientsToSlice(queue.Clients) + status.Jobs = convertJobsToSlice(queue.Jobs) + status.Waiting = queue.Waiting + status.CreationDate = queue.CreationDate + + return status +} + // vim:set ts=4 sw=4 noet: From 597ca040a34f1f04b7f7c166d58194995eba3c1c Mon Sep 17 00:00:00 2001 From: Anthony Maton Date: Fri, 17 Feb 2017 10:45:23 +0100 Subject: [PATCH 06/10] Implement TCP Message Status --- go/src/pythia/backend/queue.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/go/src/pythia/backend/queue.go b/go/src/pythia/backend/queue.go index 0a21602..4389de8 100644 --- a/go/src/pythia/backend/queue.go +++ b/go/src/pythia/backend/queue.go @@ -43,7 +43,7 @@ type queueClient struct { Id int // The response channel. - Response chan<- pythia.Message + Response chan<- pythia.Message `json:"-"` // The number of parallel jobs this pool can handle. Capacity int @@ -306,17 +306,20 @@ func (queue *Queue) main(master <-chan queueMessage) { goto quit case pythia.StatusMsg: - log.Println("Status Requested") status := fillQueueStatus(queue) id := qm.Msg.Id - serializedStatus, _ := json.Marshal(status) + serializedStatus, err := json.Marshal(status) + if err != nil { + log.Fatal("Queue is in an invalid state") + log.Fatal(err) + } qm.Client.Response <- pythia.Message{ Message: pythia.DoneMsg, Id: id, Status: pythia.Success, Output: string(serializedStatus), } - log.Println("Status sent") + log.Println("Client ", qm.Client.Id, " : Status sent") default: log.Fatal("Invalid internal message", qm.Msg) } From 5288416e6f6d9f95e120d1166662405a2b6bf3ca Mon Sep 17 00:00:00 2001 From: Anthony Maton Date: Fri, 17 Feb 2017 10:45:46 +0100 Subject: [PATCH 07/10] Add an HTTP GET Route on /status to get Queue status --- go/src/pythia/frontend/server.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/go/src/pythia/frontend/server.go b/go/src/pythia/frontend/server.go index fc0d923..046e171 100644 --- a/go/src/pythia/frontend/server.go +++ b/go/src/pythia/frontend/server.go @@ -83,6 +83,7 @@ func (server *Server) Run() { }() // Start the web server http.HandleFunc("/execute", handler) + http.HandleFunc("/status", statusHandler) log.Println("Server listening on", server.Port) if err := http.ListenAndServe(fmt.Sprint(":", server.Port), nil); err != nil { log.Fatal(err) @@ -140,4 +141,28 @@ func handler(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusInternalServerError) } +// Handle for /status route to get the status of the Queue +func statusHandler(rw http.ResponseWriter, req *http.Request) { + log.Println("Client connected: ", req.URL) + if req.Method != "GET" { + rw.WriteHeader(http.StatusMethodNotAllowed) + return + } + // Connection to the pool + conn := pythia.DialRetry(pythia.QueueAddr) + defer conn.Close() + conn.Send(pythia.Message{ + Message: pythia.StatusMsg, + }) + if msg, ok := <-conn.Receive(); ok { + switch msg.Status { + case "success": + rw.Header().Set("Content-Type", "application/json") + fmt.Fprintf(rw, msg.Output) + } + return + } + rw.WriteHeader(http.StatusInternalServerError) +} + // vim:set sw=4 ts=4 noet: From df713c161f1fde648702fd4089ec421877a88e87 Mon Sep 17 00:00:00 2001 From: Anthony Maton Date: Mon, 20 Feb 2017 15:36:15 +0100 Subject: [PATCH 08/10] Unit test regarding QueueStatus --- go/src/pythia/backend/queue_test.go | 45 +++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/go/src/pythia/backend/queue_test.go b/go/src/pythia/backend/queue_test.go index bb51466..eb010f2 100644 --- a/go/src/pythia/backend/queue_test.go +++ b/go/src/pythia/backend/queue_test.go @@ -16,7 +16,10 @@ package backend import ( + "encoding/json" "pythia" + "reflect" + "strconv" "testing" "testutils" "testutils/pytest" @@ -114,4 +117,46 @@ func TestQueueSimple(t *testing.T) { f.TearDown() } +func TestQueueStatus(t *testing.T) { + f := SetupQueueFixture(t, 500, 2) + frontend := f.Clients[0] + + frontend.Send(pythia.Message{ + Message: pythia.StatusMsg, + Id: "test", + }) + + // Removing Clients array from Message as this part will differ due to client list + // referencing the test client as a client but said client is no longer connected + // when the status is emitted + status := fillQueueStatus(f.Queue) + status.Clients = make([]*queueClient, 0) + + msg := <-frontend.Conn.Receive() + if msg.Message != pythia.DoneMsg || msg.Id != "test" || msg.Status != pythia.Success { + t.Fatal("Message content mismatching") + } + + var expected QueueStatus + expected.Clients = make([]*queueClient, 0) + json.Unmarshal([]byte(msg.Output), &expected) + + // The content of the Waiting list is not compared because it's not efficient + // and it's not really interesting because the list is supposed to be empty + if expected.Capacity != status.Capacity || + expected.Available != status.Available || + !reflect.DeepEqual(expected.Jobs, status.Jobs) || + !reflect.DeepEqual(expected.Waiting.Len(), status.Waiting.Len()) || + !expected.CreationDate.Equal(status.CreationDate) { + + t.Error("Capacity : " + strconv.FormatBool(expected.Capacity != status.Capacity)) + t.Error("Available : " + strconv.FormatBool(expected.Available != status.Available)) + t.Error("Jobs : " + strconv.FormatBool(!reflect.DeepEqual(expected.Jobs, status.Jobs))) + t.Error("Waiting : " + strconv.FormatBool(!reflect.DeepEqual(expected.Waiting, status.Waiting))) + t.Error("CreationDate : " + strconv.FormatBool(!expected.CreationDate.Equal(status.CreationDate))) + } + + f.TearDown() +} + // vim:set sw=4 ts=4 noet: From 88c7f52ea33d993116c3669c4b8288de349d31f7 Mon Sep 17 00:00:00 2001 From: Anthony Maton Date: Mon, 20 Feb 2017 15:36:43 +0100 Subject: [PATCH 09/10] Remove dead code --- go/src/pythia/backend/queue.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/go/src/pythia/backend/queue.go b/go/src/pythia/backend/queue.go index 4389de8..2c749bd 100644 --- a/go/src/pythia/backend/queue.go +++ b/go/src/pythia/backend/queue.go @@ -103,8 +103,8 @@ const ( type QueueStatus struct { Capacity int `json:"capacity"` Available int `json:"available"` - Clients []*queueClient `json:"clients"` - Jobs []*queueJob `json:"jobs"` + Clients []*queueClient `json:"clients, omitempty"` + Jobs []*queueJob `json:"jobs, omitempty"` Waiting *list.List `json:"waiting"` CreationDate time.Time `json:"creation_date"` } @@ -117,7 +117,7 @@ type QueueStatus struct { // components connect to it. type Queue struct { // The maximum number of jobs that can wait to be executed. - Capacity int `json:"capacity"` + Capacity int // Channel to send messages to the main goroutine master chan<- queueMessage @@ -129,16 +129,16 @@ type Queue struct { wg sync.WaitGroup // Active connections - Clients map[int]*queueClient `json:"clients"` + Clients map[int]*queueClient // Jobs to be processed/currently processing - Jobs map[string]*queueJob `json:"jobs"` + Jobs map[string]*queueJob // List of jobs (*queueJob) waiting to be assigned. - Waiting *list.List `json:"waiting"` + Waiting *list.List // Get the Queue creation datetime - CreationDate time.Time `json:"creation_date"` + CreationDate time.Time } // NewQueue returns a new queue with default parameters. @@ -437,7 +437,7 @@ func (queue *Queue) handle(conn *pythia.Conn, client *queueClient, response chan }*/ // TODO: Should be generified to use any kind of input type func convertClientsToSlice(clients map[int]*queueClient) (clientsSlice []*queueClient) { - clientsSlice = make([]*queueClient, len(clients)) + clientsSlice = make([]*queueClient, 0) for _, element := range clients { clientsSlice = append(clientsSlice, element) } @@ -446,7 +446,7 @@ func convertClientsToSlice(clients map[int]*queueClient) (clientsSlice []*queueC // TODO: Should be generified to use any kind of input type func convertJobsToSlice(jobs map[string]*queueJob) (jobsSlice []*queueJob) { - jobsSlice = make([]*queueJob, len(jobs)) + jobsSlice = make([]*queueJob, 0) for _, element := range jobs { jobsSlice = append(jobsSlice, element) } From bcf60ddf39b30a3b93a9b3061a851af343234600 Mon Sep 17 00:00:00 2001 From: Anthony Maton Date: Mon, 20 Feb 2017 15:44:01 +0100 Subject: [PATCH 10/10] Remove unecessary comment --- go/src/pythia/backend/queue.go | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/go/src/pythia/backend/queue.go b/go/src/pythia/backend/queue.go index 2c749bd..592e8c3 100644 --- a/go/src/pythia/backend/queue.go +++ b/go/src/pythia/backend/queue.go @@ -418,24 +418,6 @@ func (queue *Queue) handle(conn *pythia.Conn, client *queueClient, response chan } } -// Utilitary function - -// Convert a Map given in argument into a slice -// Note: the Map id is lost during the process -/*func convertMapToSlice(myMap interface{}) (err Error, mySlice []interface{}) { - reflectedMap := reflect.ValueOf(myMap) - mapContentType := reflect.TypeOf(myMap).Elem() - if reflectedMap.Kind() == reflect.Map { - slice := reflect.MakeSlice(reflect.SliceOf(mapContentType), 0, 0).Interface() - for index, element := range reflectedMap { - append(slice, element) - } - return nil, slice - } else { - return errors.New("The parameter must be of type Map") - } -}*/ -// TODO: Should be generified to use any kind of input type func convertClientsToSlice(clients map[int]*queueClient) (clientsSlice []*queueClient) { clientsSlice = make([]*queueClient, 0) for _, element := range clients { @@ -444,7 +426,6 @@ func convertClientsToSlice(clients map[int]*queueClient) (clientsSlice []*queueC return clientsSlice } -// TODO: Should be generified to use any kind of input type func convertJobsToSlice(jobs map[string]*queueJob) (jobsSlice []*queueJob) { jobsSlice = make([]*queueJob, 0) for _, element := range jobs {