--- /dev/null
+/*
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+// File controller/boruterimpl.go implements Boruter interface
+// for communication with Boruta. Communication is used for acquiring,
+// monitoring and releasing Dryads for Weles' Jobs.
+
+package controller
+
+import (
+ "fmt"
+ "sync"
+ "time"
+
+ "git.tizen.org/tools/boruta"
+ . "git.tizen.org/tools/weles"
+ "git.tizen.org/tools/weles/controller/notifier"
+)
+
+// TODO ProlongAccess to Dryad in Boruta, before time expires.
+
+// jobBorutaInfo contains information about status of acquiring Dryad from
+// Boruta for running a single Job.
+type jobBorutaInfo struct {
+ // rid is the Boruta's request ID for the Job.
+ rid boruta.ReqID
+ // status is the current state of the request.
+ status boruta.ReqState
+ // timeout defines until when Dryad is acquired.
+ timeout time.Time
+}
+
+// BoruterImpl is a Handler that is responsible for managing communication
+// with Boruta, acquiring Dryads, prolonging access and releasing them.
+type BoruterImpl struct {
+ // Notifier provides channel for communication with Controller.
+ notifier.Notifier
+ // jobs references module implementing Jobs management.
+ jobs JobsController
+ // boruta is Boruta's client.
+ boruta boruta.Requests
+
+ // info contains information about status of acquiring Dryad from Boruta.
+ info map[JobID]*jobBorutaInfo
+ // rid2job maps Boruta's RequestID to Weles' JobID.
+ rid2job map[boruta.ReqID]JobID
+ //mutex protects access to info and rid2job maps.
+ mutex *sync.Mutex
+ // borutaCheckPeriod defines how often Boruta is asked for requests' status.
+ borutaCheckPeriod time.Duration
+ // finish is channel for stopping internal goroutine.
+ finish chan int
+ // looper waits for internal goroutine running loop to finish.
+ looper sync.WaitGroup
+}
+
+// NewBoruter creates a new BoruterImpl structure setting up references
+// to used Weles and Boruta modules.
+func NewBoruter(j JobsController, b boruta.Requests, period time.Duration) Boruter {
+ ret := &BoruterImpl{
+ Notifier: notifier.NewNotifier(),
+ jobs: j,
+ boruta: b,
+ info: make(map[JobID]*jobBorutaInfo),
+ rid2job: make(map[boruta.ReqID]JobID),
+ mutex: new(sync.Mutex),
+ borutaCheckPeriod: period,
+ finish: make(chan int),
+ }
+ ret.looper.Add(1)
+ go ret.loop()
+ return ret
+}
+
+// Finish internal goroutine.
+func (h *BoruterImpl) Finish() {
+ h.finish <- 1
+ h.looper.Wait()
+}
+
+// add registers new Boruta's request ID for the Job to be monitored.
+func (h *BoruterImpl) add(j JobID, r boruta.ReqID) {
+ h.mutex.Lock()
+ defer h.mutex.Unlock()
+
+ h.info[j] = &jobBorutaInfo{
+ rid: r,
+ }
+ h.rid2job[r] = j
+}
+
+// remove Boruta's request ID for the Job from monitored requests.
+func (h *BoruterImpl) remove(j JobID, r boruta.ReqID) {
+ h.mutex.Lock()
+ defer h.mutex.Unlock()
+
+ delete(h.rid2job, r)
+ delete(h.info, j)
+}
+
+// getAndRemove Boruta's request ID for the Job and a Job from monitored set.
+// It returns request ID related to the removed Job
+func (h *BoruterImpl) getAndRemove(j JobID) (r boruta.ReqID, err error) {
+ h.mutex.Lock()
+ defer h.mutex.Unlock()
+
+ rinfo, ok := h.info[j]
+ if !ok {
+ return r, ErrJobNotFound
+ }
+ r = rinfo.rid
+ delete(h.rid2job, r)
+ delete(h.info, j)
+ return r, nil
+}
+
+// setProlongTime stores time until Dryad is acquired from Boruta.
+func (h *BoruterImpl) setProlongTime(j JobID, rinfo boruta.ReqInfo) {
+ h.mutex.Lock()
+ defer h.mutex.Unlock()
+ h.info[j].timeout = rinfo.Job.Timeout
+}
+
+// updateStatus analyzes single Boruta's request info and verifies if it is
+// related to any of Weles' Jobs. If so, method returns new status of request
+// and ID of related Job. Otherwise zero-value status is returned.
+func (h *BoruterImpl) updateStatus(rinfo boruta.ReqInfo) (newState boruta.ReqState, j JobID) {
+ h.mutex.Lock()
+ defer h.mutex.Unlock()
+
+ var ok bool
+ j, ok = h.rid2job[rinfo.ID]
+ if !ok {
+ return
+ }
+ info := h.info[j]
+ if info.status == rinfo.State {
+ return
+ }
+ info.status = rinfo.State
+ newState = rinfo.State
+
+ return
+}
+
+// acquire gets Dryad from Boruta and sets information about it in
+// JobsController. It saves time until Dryad is acquired from Boruta
+// and notifies Controller about getting Dryad for the Job.
+func (h *BoruterImpl) acquire(j JobID, rinfo boruta.ReqInfo) {
+ ai, err := h.boruta.AcquireWorker(rinfo.ID)
+ if err != nil {
+ h.remove(j, rinfo.ID)
+ h.SendFail(j, fmt.Sprintf("Cannot acquire worker from Boruta : %s", err.Error()))
+ return
+ }
+ err = h.jobs.SetDryad(j, Dryad{Addr: ai.Addr, Key: ai.Key})
+ if err != nil {
+ h.remove(j, rinfo.ID)
+ h.SendFail(j, fmt.Sprintf("Internal Weles error while setting Dryad : %s", err.Error()))
+ return
+ }
+ h.setProlongTime(j, rinfo)
+ h.SendOK(j)
+}
+
+// loop monitors Boruta's requests.
+func (h *BoruterImpl) loop() {
+ defer h.looper.Done()
+ for {
+ select {
+ case <-h.finish:
+ return
+ case <-time.After(h.borutaCheckPeriod):
+ }
+
+ // TODO use filter with slice of ReqIDs when implemented in Boruta.
+ requests, err := h.boruta.ListRequests(nil)
+ if err != nil {
+ // TODO log error
+ continue
+ }
+
+ for _, rinfo := range requests {
+ status, j := h.updateStatus(rinfo)
+
+ switch status {
+ case boruta.INPROGRESS:
+ h.acquire(j, rinfo)
+ case boruta.CANCEL:
+ h.remove(j, rinfo.ID)
+ case boruta.DONE:
+ h.remove(j, rinfo.ID)
+ case boruta.TIMEOUT:
+ h.remove(j, rinfo.ID)
+ h.SendFail(j, "Timeout in Boruta.")
+ case boruta.INVALID:
+ h.remove(j, rinfo.ID)
+ h.SendFail(j, "No suitable device in Boruta to run test.")
+ case boruta.FAILED:
+ h.remove(j, rinfo.ID)
+ h.SendFail(j, "Boruta failed during request execution.")
+ }
+ }
+ }
+}
+
+// setCaps prepares Capabilities for registering new request in Boruta.
+func (h *BoruterImpl) setCaps(config Config) boruta.Capabilities {
+ if config.DeviceType == "" {
+ return boruta.Capabilities{}
+ }
+
+ return boruta.Capabilities{
+ "DeviceType": config.DeviceType,
+ }
+}
+
+// setPriority prepares Priority for registering new request in Boruta.
+func (h *BoruterImpl) setPriority(config Config) boruta.Priority {
+ switch config.Priority {
+ case LOW:
+ return 11
+ case MEDIUM:
+ return 7
+ case HIGH:
+ return 3
+ default:
+ return 7
+ }
+}
+
+// setOwner prepares Owner for registering new request in Boruta.
+func (h *BoruterImpl) setOwner() boruta.UserInfo {
+ return boruta.UserInfo{}
+}
+
+// setValidAfter prepares ValidAfter time for registering new request in Boruta.
+func (h *BoruterImpl) setValidAfter(config Config) time.Time {
+ return time.Now()
+}
+
+// setDeadline prepares Deadline time for registering new request in Boruta.
+func (h *BoruterImpl) setDeadline(config Config) time.Time {
+ const defaultDelay = 24 * time.Hour
+ if config.Timeouts.JobTimeout == ValidityPeriod(0) {
+ return time.Now().Add(defaultDelay)
+ }
+
+ return time.Now().Add(time.Duration(config.Timeouts.JobTimeout))
+}
+
+// Request registers new request in Boruta and adds it to monitored requests.
+func (h *BoruterImpl) Request(j JobID) {
+ err := h.jobs.SetStatusAndInfo(j, JOB_WAITING, "")
+ if err != nil {
+ h.SendFail(j, fmt.Sprintf("Internal Weles error while changing Job status : %s", err.Error()))
+ return
+ }
+
+ config, err := h.jobs.GetConfig(j)
+ if err != nil {
+ h.SendFail(j, fmt.Sprintf("Internal Weles error while getting Job config : %s", err.Error()))
+ return
+ }
+
+ caps := h.setCaps(config)
+ priority := h.setPriority(config)
+ owner := h.setOwner()
+ validAfter := h.setValidAfter(config)
+ deadline := h.setDeadline(config)
+
+ r, err := h.boruta.NewRequest(caps, priority, owner, validAfter, deadline)
+ if err != nil {
+ h.SendFail(j, fmt.Sprintf("Cannot get Dryad from boruta : %s", err.Error()))
+ return
+ }
+
+ h.add(j, r)
+}
+
+// Release returns Dryad to Boruta's pool and closes Boruta's request.
+func (h *BoruterImpl) Release(j JobID) {
+ r, err := h.getAndRemove(j)
+ if err != nil {
+ return
+ }
+ h.boruta.CloseRequest(r)
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package controller
+
+import (
+ "errors"
+ "net"
+ "sync"
+ "time"
+
+ "git.tizen.org/tools/boruta"
+ . "git.tizen.org/tools/weles"
+ cmock "git.tizen.org/tools/weles/controller/mock"
+ . "git.tizen.org/tools/weles/controller/notifier"
+ gomock "github.com/golang/mock/gomock"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("BoruterImpl", func() {
+ var r <-chan Notification
+ var jc *cmock.MockJobsController
+ var req *cmock.MockRequests
+ var h Boruter
+ var ctrl *gomock.Controller
+ var config Config
+ var caps boruta.Capabilities
+ var priority boruta.Priority
+ j := JobID(0xCAFE)
+ rid := boruta.ReqID(0xD0DA)
+ period := 50 * time.Millisecond
+ jobTimeout := time.Hour
+ owner := boruta.UserInfo{}
+ err := errors.New("test error")
+
+ expectRegistered := func(offset int) {
+ h.(*BoruterImpl).mutex.Lock()
+ defer h.(*BoruterImpl).mutex.Unlock()
+
+ ExpectWithOffset(offset, len(h.(*BoruterImpl).info)).To(Equal(1))
+ info, ok := h.(*BoruterImpl).info[j]
+ ExpectWithOffset(offset, ok).To(BeTrue())
+ ExpectWithOffset(offset, info.rid).To(Equal(rid))
+
+ ExpectWithOffset(offset, len(h.(*BoruterImpl).rid2job)).To(Equal(1))
+ job, ok := h.(*BoruterImpl).rid2job[rid]
+ ExpectWithOffset(offset, ok).To(BeTrue())
+ ExpectWithOffset(offset, job).To(Equal(j))
+ }
+ eventuallyEmpty := func(offset int) {
+ EventuallyWithOffset(offset, func() int {
+ h.(*BoruterImpl).mutex.Lock()
+ defer h.(*BoruterImpl).mutex.Unlock()
+ return len(h.(*BoruterImpl).info)
+ }).Should(BeZero())
+ EventuallyWithOffset(offset, func() int {
+ h.(*BoruterImpl).mutex.Lock()
+ defer h.(*BoruterImpl).mutex.Unlock()
+ return len(h.(*BoruterImpl).rid2job)
+ }).Should(BeZero())
+ }
+ eventuallyNoti := func(offset int, ok bool, msg string) {
+ notification := Notification{}
+ expectedNotification := Notification{
+ JobID: j,
+ OK: ok,
+ Msg: msg,
+ }
+ EventuallyWithOffset(offset, r).Should(Receive(¬ification))
+ ExpectWithOffset(offset, notification).To(Equal(expectedNotification))
+ }
+
+ BeforeEach(func() {
+ ctrl = gomock.NewController(GinkgoT())
+
+ jc = cmock.NewMockJobsController(ctrl)
+ req = cmock.NewMockRequests(ctrl)
+
+ h = NewBoruter(jc, req, period)
+ r = h.Listen()
+
+ config = Config{
+ DeviceType: "TestDeviceType",
+ Priority: "medium",
+ Timeouts: Timeouts{
+ JobTimeout: ValidityPeriod(jobTimeout),
+ },
+ }
+ caps = boruta.Capabilities{"DeviceType": "TestDeviceType"}
+ priority = boruta.Priority(7)
+ })
+ AfterEach(func() {
+ h.(*BoruterImpl).Finish()
+ ctrl.Finish()
+ })
+ Describe("NewBoruter", func() {
+ It("should create a new object", func() {
+ Expect(h).NotTo(BeNil())
+ Expect(h.(*BoruterImpl).jobs).To(Equal(jc))
+ Expect(h.(*BoruterImpl).boruta).To(Equal(req))
+ Expect(h.(*BoruterImpl).info).NotTo(BeNil())
+ Expect(h.(*BoruterImpl).rid2job).NotTo(BeNil())
+ Expect(h.(*BoruterImpl).mutex).NotTo(BeNil())
+ Expect(h.(*BoruterImpl).borutaCheckPeriod).To(Equal(period))
+ })
+ })
+ Describe("loop", func() {
+ It("should ignore ListRequests errors", func() {
+ counter := 5
+ mutex := &sync.Mutex{}
+ jc.EXPECT().SetStatusAndInfo(j, JOB_WAITING, "")
+ jc.EXPECT().GetConfig(j).Return(config, nil)
+ req.EXPECT().NewRequest(caps, priority, owner, gomock.Any(), gomock.Any()).Return(rid, nil)
+ req.EXPECT().ListRequests(nil).AnyTimes().Return([]boruta.ReqInfo{}, err).Do(func(boruta.ListFilter) {
+ mutex.Lock()
+ defer mutex.Unlock()
+ counter--
+ })
+
+ h.Request(j)
+ Eventually(func() int {
+ mutex.Lock()
+ defer mutex.Unlock()
+ return counter
+ }).Should(BeNumerically("<", 0))
+
+ expectRegistered(1)
+ })
+ })
+ Describe("Request", func() {
+ It("should register job successfully", func() {
+ var va, dl time.Time
+ jc.EXPECT().SetStatusAndInfo(j, JOB_WAITING, "")
+ jc.EXPECT().GetConfig(j).Return(config, nil)
+ req.EXPECT().NewRequest(caps, priority, owner, gomock.Any(), gomock.Any()).Return(rid, nil).Do(
+ func(c boruta.Capabilities, p boruta.Priority, ui boruta.UserInfo, validAfter time.Time, deadline time.Time) {
+ va = validAfter
+ dl = deadline
+ })
+ req.EXPECT().ListRequests(nil).AnyTimes()
+
+ before := time.Now()
+ h.Request(j)
+ after := time.Now()
+
+ Expect(va).To(BeTemporally(">=", before))
+ Expect(va).To(BeTemporally("<=", after))
+ Expect(dl).To(BeTemporally(">=", before.Add(jobTimeout)))
+ Expect(dl).To(BeTemporally("<=", after.Add(jobTimeout)))
+
+ expectRegistered(1)
+ })
+ It("should register job successfully even when JobTimout is not defined in Config", func() {
+ var va, dl time.Time
+ config.Timeouts.JobTimeout = ValidityPeriod(0)
+ defaultDelay := 24 * time.Hour
+
+ jc.EXPECT().SetStatusAndInfo(j, JOB_WAITING, "")
+ jc.EXPECT().GetConfig(j).Return(config, nil)
+ req.EXPECT().NewRequest(caps, priority, owner, gomock.Any(), gomock.Any()).Return(rid, nil).Do(
+ func(c boruta.Capabilities, p boruta.Priority, ui boruta.UserInfo, validAfter time.Time, deadline time.Time) {
+ va = validAfter
+ dl = deadline
+ })
+ req.EXPECT().ListRequests(nil).AnyTimes()
+
+ before := time.Now()
+ h.Request(j)
+ after := time.Now()
+
+ Expect(va).To(BeTemporally(">=", before))
+ Expect(va).To(BeTemporally("<=", after))
+ Expect(dl).To(BeTemporally(">=", before.Add(defaultDelay)))
+ Expect(dl).To(BeTemporally("<=", after.Add(defaultDelay)))
+
+ expectRegistered(1)
+ })
+ It("should fail if NewRequest fails", func() {
+ jc.EXPECT().SetStatusAndInfo(j, JOB_WAITING, "")
+ jc.EXPECT().GetConfig(j).Return(config, nil)
+ req.EXPECT().NewRequest(caps, priority, owner, gomock.Any(), gomock.Any()).Return(boruta.ReqID(0), err)
+ req.EXPECT().ListRequests(nil).AnyTimes()
+
+ h.Request(j)
+
+ eventuallyNoti(1, false, "Cannot get Dryad from boruta : test error")
+ eventuallyEmpty(1)
+ })
+ It("should fail if GetConfig fails", func() {
+ jc.EXPECT().SetStatusAndInfo(j, JOB_WAITING, "")
+ jc.EXPECT().GetConfig(j).Return(Config{}, err)
+ req.EXPECT().ListRequests(nil).AnyTimes()
+
+ h.Request(j)
+
+ eventuallyNoti(1, false, "Internal Weles error while getting Job config : test error")
+ eventuallyEmpty(1)
+ })
+ It("should fail if SetStatusAndInfo fails", func() {
+ jc.EXPECT().SetStatusAndInfo(j, JOB_WAITING, "").Return(err)
+ req.EXPECT().ListRequests(nil).AnyTimes()
+
+ h.Request(j)
+
+ eventuallyNoti(1, false, "Internal Weles error while changing Job status : test error")
+ eventuallyEmpty(1)
+ })
+ It("should call NewRequest with empty caps if no device type provided", func() {
+ config.DeviceType = ""
+ jc.EXPECT().SetStatusAndInfo(j, JOB_WAITING, "")
+ jc.EXPECT().GetConfig(j).Return(config, nil)
+ req.EXPECT().NewRequest(boruta.Capabilities{}, priority, owner, gomock.Any(), gomock.Any()).Return(boruta.ReqID(0), err)
+ req.EXPECT().ListRequests(nil).AnyTimes()
+
+ h.Request(j)
+
+ eventuallyNoti(1, false, "Cannot get Dryad from boruta : test error")
+ eventuallyEmpty(1)
+ })
+ It("should call NewRequest with proper priority", func() {
+ m := map[Priority]boruta.Priority{
+ LOW: boruta.Priority(11),
+ MEDIUM: boruta.Priority(7),
+ HIGH: boruta.Priority(3),
+ Priority("unknown"): boruta.Priority(7),
+ }
+ for k, v := range m {
+ config.Priority = k
+ jc.EXPECT().SetStatusAndInfo(j, JOB_WAITING, "")
+ jc.EXPECT().GetConfig(j).Return(config, nil)
+ req.EXPECT().NewRequest(caps, v, owner, gomock.Any(), gomock.Any()).Return(boruta.ReqID(0), err)
+ req.EXPECT().ListRequests(nil).AnyTimes()
+
+ h.Request(j)
+
+ eventuallyNoti(1, false, "Cannot get Dryad from boruta : test error")
+ eventuallyEmpty(1)
+ }
+ })
+ })
+ Describe("With registered request", func() {
+ var listRequestRet chan []boruta.ReqInfo
+ states := []boruta.ReqState{
+ boruta.WAIT,
+ boruta.INPROGRESS,
+ boruta.CANCEL,
+ boruta.TIMEOUT,
+ boruta.INVALID,
+ boruta.DONE,
+ boruta.FAILED,
+ }
+ ai := boruta.AccessInfo{Addr: &net.IPNet{IP: net.IPv4(1, 2, 3, 4), Mask: net.IPv4Mask(5, 6, 7, 8)}}
+ BeforeEach(func() {
+ var va, dl time.Time
+ jc.EXPECT().SetStatusAndInfo(j, JOB_WAITING, "")
+ jc.EXPECT().GetConfig(j).Return(config, nil)
+ req.EXPECT().NewRequest(caps, priority, owner, gomock.Any(), gomock.Any()).Return(rid, nil).Do(
+ func(c boruta.Capabilities, p boruta.Priority, ui boruta.UserInfo, validAfter time.Time, deadline time.Time) {
+ va = validAfter
+ dl = deadline
+ })
+ listRequestRet = make(chan []boruta.ReqInfo)
+ req.EXPECT().ListRequests(nil).AnyTimes().DoAndReturn(func(boruta.ListFilter) ([]boruta.ReqInfo, error) {
+ return <-listRequestRet, nil
+ })
+
+ before := time.Now()
+ h.Request(j)
+ after := time.Now()
+
+ Expect(va).To(BeTemporally(">=", before))
+ Expect(va).To(BeTemporally("<=", after))
+ Expect(dl).To(BeTemporally(">=", before.Add(jobTimeout)))
+ Expect(dl).To(BeTemporally("<=", after.Add(jobTimeout)))
+
+ expectRegistered(1)
+ })
+ It("should ignore ID of not registered request", func() {
+ for _, s := range states {
+ rinfo := boruta.ReqInfo{ID: boruta.ReqID(0x0BCA), State: s}
+ listRequestRet <- []boruta.ReqInfo{rinfo}
+
+ expectRegistered(1)
+ }
+ })
+ for _, s := range states { // Every state is a separate It, because objects must be reinitialized
+ It("should ignore if request's state is unchanged : "+string(s), func() {
+ h.(*BoruterImpl).mutex.Lock()
+
+ Expect(len(h.(*BoruterImpl).info)).To(Equal(1))
+ info, ok := h.(*BoruterImpl).info[j]
+ Expect(ok).To(BeTrue())
+ info.status = s
+
+ h.(*BoruterImpl).mutex.Unlock()
+
+ rinfo := boruta.ReqInfo{ID: rid, State: s}
+ listRequestRet <- []boruta.ReqInfo{rinfo}
+
+ expectRegistered(1)
+ })
+ }
+ It("should acquire Dryad if state changes to INPROGRESS", func() {
+ req.EXPECT().AcquireWorker(rid).Return(ai, nil)
+ jc.EXPECT().SetDryad(j, Dryad{Addr: ai.Addr, Key: ai.Key})
+
+ rinfo := boruta.ReqInfo{ID: rid, State: boruta.INPROGRESS, Job: &boruta.JobInfo{Timeout: time.Now().AddDate(0, 0, 1)}}
+ listRequestRet <- []boruta.ReqInfo{rinfo}
+
+ eventuallyNoti(1, true, "")
+ })
+ It("should fail during acquire if SetDryad fails", func() {
+ req.EXPECT().AcquireWorker(rid).Return(ai, nil)
+ jc.EXPECT().SetDryad(j, Dryad{Addr: ai.Addr, Key: ai.Key}).Return(err)
+
+ rinfo := boruta.ReqInfo{ID: rid, State: boruta.INPROGRESS, Job: &boruta.JobInfo{Timeout: time.Now().AddDate(0, 0, 1)}}
+ listRequestRet <- []boruta.ReqInfo{rinfo}
+
+ eventuallyNoti(1, false, "Internal Weles error while setting Dryad : test error")
+ eventuallyEmpty(1)
+ })
+ It("should fail during acquire if AcquireWorker fails", func() {
+ req.EXPECT().AcquireWorker(rid).Return(boruta.AccessInfo{}, err)
+
+ rinfo := boruta.ReqInfo{ID: rid, State: boruta.INPROGRESS, Job: &boruta.JobInfo{Timeout: time.Now().AddDate(0, 0, 1)}}
+ listRequestRet <- []boruta.ReqInfo{rinfo}
+
+ eventuallyNoti(1, false, "Cannot acquire worker from Boruta : test error")
+ eventuallyEmpty(1)
+ })
+ It("should remove request if state changes to CANCEL", func() {
+ rinfo := boruta.ReqInfo{ID: rid, State: boruta.CANCEL}
+ listRequestRet <- []boruta.ReqInfo{rinfo}
+
+ eventuallyEmpty(1)
+ })
+ It("should remove request if state changes to DONE", func() {
+ rinfo := boruta.ReqInfo{ID: rid, State: boruta.DONE}
+ listRequestRet <- []boruta.ReqInfo{rinfo}
+
+ eventuallyEmpty(1)
+ })
+ It("should fail and remove request if state changes to TIMEOUT", func() {
+ rinfo := boruta.ReqInfo{ID: rid, State: boruta.TIMEOUT}
+ listRequestRet <- []boruta.ReqInfo{rinfo}
+
+ eventuallyNoti(1, false, "Timeout in Boruta.")
+ eventuallyEmpty(1)
+ })
+ It("should fail and remove request if state changes to INVALID", func() {
+ rinfo := boruta.ReqInfo{ID: rid, State: boruta.INVALID}
+ listRequestRet <- []boruta.ReqInfo{rinfo}
+
+ eventuallyNoti(1, false, "No suitable device in Boruta to run test.")
+ eventuallyEmpty(1)
+ })
+ It("should fail and remove request if state changes to FAILED", func() {
+ rinfo := boruta.ReqInfo{ID: rid, State: boruta.FAILED}
+ listRequestRet <- []boruta.ReqInfo{rinfo}
+
+ eventuallyNoti(1, false, "Boruta failed during request execution.")
+ eventuallyEmpty(1)
+ })
+ Describe("Release", func() {
+ It("should remove existing request and close it in Boruta", func() {
+ req.EXPECT().CloseRequest(rid)
+
+ h.Release(j)
+
+ eventuallyEmpty(1)
+ })
+ It("should ignore not existing request", func() {
+ h.Release(JobID(0x0BCA))
+ expectRegistered(1)
+ })
+ })
+ })
+})