summaryrefslogtreecommitdiff
path: root/matcher
diff options
context:
space:
mode:
authorLukasz Wojciechowski <l.wojciechow@partner.samsung.com>2017-10-06 21:34:06 +0200
committerLukasz Wojciechowski <l.wojciechow@partner.samsung.com>2018-04-27 17:34:20 +0200
commit8449acda09a55dfc750fb20bd11eaa064dd15efe (patch)
treef3efa24a138396c66adf5c365ec732122b625a67 /matcher
parent1fceedd42bc6f61b2b151503421b3bc04e989b49 (diff)
downloadboruta-8449acda09a55dfc750fb20bd11eaa064dd15efe.tar.gz
boruta-8449acda09a55dfc750fb20bd11eaa064dd15efe.tar.bz2
boruta-8449acda09a55dfc750fb20bd11eaa064dd15efe.zip
Add ValidMatcher with tests
ValidMatcher is a Matcher interface implementation for handling events related to validation of requests after ValidAfter time is passed. It matches pending, ready to be run requests with idle workers that are capable to fulfill request capabilities and belong to group for which request owner has rights. Tests base on using MockRequestsManager, MockWorkersManager and MockJobsManager for mocking up RequestsManager, WorkersManager and JobsManager interfaces. Change-Id: Ib654f1ef276eecb14dc4ad3114afcccd83a7bf5d Signed-off-by: Lukasz Wojciechowski <l.wojciechow@partner.samsung.com>
Diffstat (limited to 'matcher')
-rw-r--r--matcher/validmatcher.go123
-rw-r--r--matcher/validmatcher_test.go173
2 files changed, 296 insertions, 0 deletions
diff --git a/matcher/validmatcher.go b/matcher/validmatcher.go
new file mode 100644
index 0000000..c89abf1
--- /dev/null
+++ b/matcher/validmatcher.go
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2017-2018 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 matcher/validmatcher.go provides ValidMatcher structure which implements
+// Matcher interface. It should be used for handling events caused by validation
+// of requests after ValidAfter time is passed.
+
+package matcher
+
+import (
+ "time"
+
+ . "git.tizen.org/tools/boruta"
+)
+
+// ValidMatcher implements Matcher interface for handling requests validation.
+type ValidMatcher struct {
+ Matcher
+ // requests provides internal boruta access to requests.
+ requests RequestsManager
+ // workers provides internal boruta access to workers.
+ workers WorkersManager
+ // jobs provides internal boruta access to jobs.
+ jobs JobsManager
+}
+
+// NewValidMatcher creates a new ValidMatcher structure.
+func NewValidMatcher(r RequestsManager, w WorkersManager, j JobsManager) *ValidMatcher {
+ return &ValidMatcher{
+ requests: r,
+ workers: w,
+ jobs: j,
+ }
+}
+
+// Notify implements Matcher interface. This method reacts on events passed to
+// matcher. In this implementation requests' IDs are ignored as requests must be
+// matched in order they are placed in requests priority queue.
+func (m ValidMatcher) Notify([]ReqID) {
+ // Repeat verification until iterateRequests() returns false indicating that
+ // there is no more job to be done.
+ for m.iterateRequests() {
+ }
+}
+
+// iterateRequests visits all requests in order they are placed in requests
+// priority queue, verifies if they can be run and tries to match an idle worker.
+// Method returns true if iteration should be repeated or false if there is
+// nothing more to be done.
+func (m ValidMatcher) iterateRequests() bool {
+
+ err := m.requests.InitIteration()
+ if err != nil {
+ // TODO log critical logic error. InitIterations should return no error
+ // as no iterations should by run by any other goroutine.
+ panic("Critical logic error. No iterations over requests collection should be running.")
+ }
+ defer m.requests.TerminateIteration()
+
+ now := time.Now()
+ // Iterate on requests priority queue.
+ for rid, rok := m.requests.Next(); rok; rid, rok = m.requests.Next() {
+ // Verify if request is ready to be run.
+ if !m.requests.VerifyIfReady(rid, now) {
+ continue
+ }
+ // Request is ready to be run. Get full information about it.
+ req, err := m.requests.Get(rid)
+ if err != nil {
+ continue
+ }
+
+ // Try finding an idle worker matching requests requirements.
+ if m.matchWorkers(req) {
+ // A match was made. Restarting iterations to process other requests.
+ return true
+ }
+ }
+ // All requests have been analyzed. No repetition is required.
+ return false
+}
+
+// matchWorkers tries to find the best of the idle workers matching capabilities
+// and groups of the requests. Best worker is the one with least matching penalty.
+// If such worker is found a job is created and the request is processed.
+func (m ValidMatcher) matchWorkers(req ReqInfo) bool {
+
+ worker, err := m.workers.TakeBestMatchingWorker(req.Owner.Groups, req.Caps)
+ if err != nil {
+ // No matching worker was found.
+ return false
+ }
+ // Match found.
+ err = m.jobs.Create(req.ID, worker)
+ if err != nil {
+ // TODO log error.
+ goto fail
+ }
+ err = m.requests.Run(req.ID, worker)
+ if err != nil {
+ // TODO log error.
+ goto fail
+ }
+ return true
+
+fail:
+ // Creating job failed. Bringing worker back to IDLE state.
+ m.workers.PrepareWorker(worker, false)
+ return false
+}
diff --git a/matcher/validmatcher_test.go b/matcher/validmatcher_test.go
new file mode 100644
index 0000000..f642daf
--- /dev/null
+++ b/matcher/validmatcher_test.go
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2017-2018 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 matcher
+
+import (
+ "errors"
+ "time"
+
+ . "git.tizen.org/tools/boruta"
+ "git.tizen.org/tools/boruta/workers"
+
+ gomock "github.com/golang/mock/gomock"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("ValidMatcher", func() {
+ var ctrl *gomock.Controller
+ var r *MockRequestsManager
+ var w *MockWorkersManager
+ var j *MockJobsManager
+ var m Matcher
+ var pre time.Time
+
+ zeroReq := ReqID(0)
+ req := ReqID(101)
+ groups := Groups{"A", "B", "C"}
+ caps := Capabilities{"keyX": "valX", "keyY": "valY", "keyZ": "valZ"}
+ worker := WorkerUUID("Test worker")
+ reqInfo := ReqInfo{ID: req, Caps: caps, Owner: UserInfo{Groups: groups}}
+
+ checkTime := func(_ ReqID, t time.Time) {
+ Expect(t).To(BeTemporally(">=", pre))
+ Expect(t).To(BeTemporally("<=", time.Now()))
+ }
+
+ BeforeEach(func() {
+ ctrl = gomock.NewController(GinkgoT())
+ r = NewMockRequestsManager(ctrl)
+ w = NewMockWorkersManager(ctrl)
+ j = NewMockJobsManager(ctrl)
+ pre = time.Now()
+ })
+ AfterEach(func() {
+ ctrl.Finish()
+ })
+ Describe("NewValidMatcher", func() {
+ It("should not use requests, workers nor jobs", func() {
+ m := NewValidMatcher(r, w, j)
+ Expect(m).NotTo(BeNil())
+ })
+ })
+ Describe("Notify", func() {
+ BeforeEach(func() {
+ m = NewValidMatcher(r, w, j)
+ })
+ It("should not iterate over requests when InitIteration fails", func() {
+ anyError := errors.New("test error")
+ r.EXPECT().InitIteration().Return(anyError)
+
+ Expect(func() {
+ m.Notify(nil)
+ }).To(Panic())
+ })
+ It("should run only Lock, Unlock, First on empty requests", func() {
+ gomock.InOrder(
+ r.EXPECT().InitIteration(),
+ r.EXPECT().Next().Return(zeroReq, false),
+ r.EXPECT().TerminateIteration(),
+ )
+
+ m.Notify(nil)
+ })
+ It("should ignore not-ready requests", func() {
+ gomock.InOrder(
+ r.EXPECT().InitIteration(),
+ r.EXPECT().Next().Return(req, true),
+ r.EXPECT().VerifyIfReady(req, gomock.Any()).Do(checkTime).Return(false),
+ r.EXPECT().Next().Return(zeroReq, false),
+ r.EXPECT().TerminateIteration(),
+ )
+
+ m.Notify(nil)
+ })
+ It("should continue iterating over requests when Get fails", func() {
+ gomock.InOrder(
+ r.EXPECT().InitIteration(),
+ r.EXPECT().Next().Return(req, true),
+ r.EXPECT().VerifyIfReady(req, gomock.Any()).Do(checkTime).Return(true),
+ r.EXPECT().Get(req).Return(ReqInfo{}, NotFoundError("Request")),
+ r.EXPECT().Next().Return(zeroReq, false),
+ r.EXPECT().TerminateIteration(),
+ )
+
+ m.Notify(nil)
+ })
+ It("should match workers when Get succeeds", func() {
+ gomock.InOrder(
+ r.EXPECT().InitIteration(),
+ r.EXPECT().Next().Return(req, true),
+ r.EXPECT().VerifyIfReady(req, gomock.Any()).Do(checkTime).Return(true),
+ r.EXPECT().Get(req).Return(reqInfo, nil),
+ w.EXPECT().TakeBestMatchingWorker(groups, caps).Return(WorkerUUID(""), workers.ErrWorkerNotFound),
+ r.EXPECT().Next().Return(zeroReq, false),
+ r.EXPECT().TerminateIteration(),
+ )
+
+ m.Notify(nil)
+ })
+ It("should prepare worker without key generation when job creation fails", func() {
+ gomock.InOrder(
+ r.EXPECT().InitIteration(),
+ r.EXPECT().Next().Return(req, true),
+ r.EXPECT().VerifyIfReady(req, gomock.Any()).Do(checkTime).Return(true),
+ r.EXPECT().Get(req).Return(reqInfo, nil),
+ w.EXPECT().TakeBestMatchingWorker(groups, caps).Return(worker, nil),
+ j.EXPECT().Create(req, worker).Return(ErrJobAlreadyExists),
+ w.EXPECT().PrepareWorker(worker, false),
+ r.EXPECT().Next().Return(zeroReq, false),
+ r.EXPECT().TerminateIteration(),
+ )
+
+ m.Notify(nil)
+ })
+ It("should prepare worker without key generation when running request fails", func() {
+ gomock.InOrder(
+ r.EXPECT().InitIteration(),
+ r.EXPECT().Next().Return(req, true),
+ r.EXPECT().VerifyIfReady(req, gomock.Any()).Do(checkTime).Return(true),
+ r.EXPECT().Get(req).Return(reqInfo, nil),
+ w.EXPECT().TakeBestMatchingWorker(groups, caps).Return(worker, nil),
+ j.EXPECT().Create(req, worker),
+ r.EXPECT().Run(req, worker).Return(NotFoundError("Request")),
+ w.EXPECT().PrepareWorker(worker, false),
+ r.EXPECT().Next().Return(zeroReq, false),
+ r.EXPECT().TerminateIteration(),
+ )
+
+ m.Notify(nil)
+ })
+ It("should create job when match is found and run request", func() {
+ gomock.InOrder(
+ r.EXPECT().InitIteration(),
+ r.EXPECT().Next().Return(req, true),
+ r.EXPECT().VerifyIfReady(req, gomock.Any()).Do(checkTime).Return(true),
+ r.EXPECT().Get(req).Return(reqInfo, nil),
+ w.EXPECT().TakeBestMatchingWorker(groups, caps).Return(worker, nil),
+ j.EXPECT().Create(req, worker),
+ r.EXPECT().Run(req, worker),
+ r.EXPECT().TerminateIteration(),
+ r.EXPECT().InitIteration(),
+ r.EXPECT().Next().Return(zeroReq, false),
+ r.EXPECT().TerminateIteration(),
+ )
+
+ m.Notify(nil)
+ })
+ })
+})