-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
Signed-off-by: Dmitry Shmulevich <[email protected]>
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
/* | ||
* Copyright (c) 2024, NVIDIA CORPORATION. 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 engine | ||
|
||
import ( | ||
"context" | ||
"sync" | ||
"time" | ||
|
||
"github.com/go-logr/logr" | ||
"k8s.io/client-go/kubernetes" | ||
"k8s.io/client-go/util/workqueue" | ||
|
||
"github.com/NVIDIA/knavigator/pkg/config" | ||
) | ||
|
||
type executor interface { | ||
RunTask(context.Context, *config.Task) error | ||
} | ||
|
||
type Deferrer struct { | ||
log logr.Logger | ||
executor executor | ||
queue workqueue.DelayingInterface | ||
client kubernetes.Interface | ||
wg sync.WaitGroup | ||
} | ||
|
||
func NewDereffer(log logr.Logger, client kubernetes.Interface, executor executor) *Deferrer { | ||
return &Deferrer{ | ||
log: log, | ||
executor: executor, | ||
queue: workqueue.NewDelayingQueue(), | ||
client: client, | ||
} | ||
} | ||
|
||
func (d *Deferrer) ScheduleTermination(taskID string) { | ||
d.wg.Add(1) | ||
d.queue.Add(taskID) | ||
} | ||
|
||
func (d *Deferrer) Start(ctx context.Context) { | ||
go d.start(ctx) | ||
} | ||
|
||
func (d *Deferrer) start(ctx context.Context) { | ||
for { | ||
// Get an item from the queue | ||
obj, shutdown := d.queue.Get() | ||
if shutdown { | ||
break | ||
} | ||
|
||
switch v := obj.(type) { | ||
case string: | ||
d.log.Info("Wait for running pods", "taskID", v) | ||
err := d.executor.RunTask(ctx, &config.Task{ | ||
ID: "status", | ||
Type: TaskCheckPod, | ||
Params: map[string]interface{}{ | ||
"refTaskId": v, | ||
"status": "Running", | ||
"timeout": "24h", | ||
}, | ||
}) | ||
if err != nil { | ||
d.log.Error(err, "Failed to watch pods") | ||
d.wg.Done() | ||
} else { | ||
d.log.Info("AddTask", "type", TaskDeleteObj) | ||
d.queue.AddAfter(&config.Task{ | ||
ID: "delete", | ||
Type: TaskDeleteObj, | ||
Params: map[string]interface{}{"refTaskId": v}, | ||
}, 5*time.Second) | ||
} | ||
|
||
case *config.Task: | ||
d.log.Info("Deferrer initiates task", "type", v.Type, "ID", v.ID) | ||
|
||
err := d.executor.RunTask(ctx, v) | ||
if err != nil { | ||
d.log.Error(err, "failed to execute task", "type", v.Type, "ID", v.ID) | ||
} | ||
d.wg.Done() | ||
} | ||
|
||
// Mark the item as done | ||
d.queue.Done(obj) | ||
} | ||
} | ||
|
||
func (d *Deferrer) Wait(ctx context.Context, timeout time.Duration) error { | ||
d.log.Info("Waiting for deferrer to complete task") | ||
ctx, cancel := context.WithTimeout(ctx, timeout) | ||
defer cancel() | ||
|
||
done := make(chan struct{}) | ||
|
||
go func() { | ||
d.wg.Wait() | ||
done <- struct{}{} | ||
}() | ||
|
||
select { | ||
case <-done: | ||
d.queue.ShutDown() | ||
d.log.Info("Deferrer stopped") | ||
return nil | ||
case <-ctx.Done(): | ||
d.log.Info("Deferrer didn't stop in allocated time") | ||
return ctx.Err() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
/* | ||
* Copyright (c) 2024, NVIDIA CORPORATION. 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 engine | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/NVIDIA/knavigator/pkg/config" | ||
) | ||
|
||
type testExecutor struct { | ||
tasks []string | ||
} | ||
|
||
func (exec *testExecutor) RunTask(_ context.Context, cfg *config.Task) error { | ||
exec.tasks = append(exec.tasks, cfg.ID) | ||
return nil | ||
} | ||
|
||
func TestDeferrer(t *testing.T) { | ||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer cancel() | ||
|
||
exec := &testExecutor{tasks: []string{}} | ||
deferrer := NewDereffer(testLogger, exec) | ||
Check failure on line 43 in pkg/engine/deferrer_test.go GitHub Actions / test
|
||
deferrer.Start(ctx) | ||
|
||
deferrer.Inc(6) | ||
Check failure on line 46 in pkg/engine/deferrer_test.go GitHub Actions / test
|
||
deferrer.AddTask(&config.Task{ID: "t3"}, 3*time.Second) | ||
Check failure on line 47 in pkg/engine/deferrer_test.go GitHub Actions / test
|
||
deferrer.AddTask(&config.Task{ID: "t1"}, 1*time.Second) | ||
Check failure on line 48 in pkg/engine/deferrer_test.go GitHub Actions / test
|
||
deferrer.AddTask(&config.Task{ID: "t5"}, 5*time.Second) | ||
Check failure on line 49 in pkg/engine/deferrer_test.go GitHub Actions / test
|
||
deferrer.AddTask(&config.Task{ID: "t4"}, 4*time.Second) | ||
Check failure on line 50 in pkg/engine/deferrer_test.go GitHub Actions / test
|
||
deferrer.AddTask(&config.Task{ID: "t2"}, 2*time.Second) | ||
Check failure on line 51 in pkg/engine/deferrer_test.go GitHub Actions / test
|
||
deferrer.AddTask(&config.Task{ID: "t6"}, 6*time.Second) | ||
Check failure on line 52 in pkg/engine/deferrer_test.go GitHub Actions / test
|
||
|
||
err := deferrer.Wait(ctx, 8*time.Second) | ||
require.NoError(t, err) | ||
require.Equal(t, []string{"t1", "t2", "t3", "t4", "t5", "t6"}, exec.tasks) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/* | ||
* Copyright (c) 2024, NVIDIA CORPORATION. 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 engine | ||
|
||
import ( | ||
"context" | ||
"time" | ||
|
||
"github.com/go-logr/logr" | ||
|
||
"github.com/NVIDIA/knavigator/pkg/config" | ||
) | ||
|
||
type WaitTask struct { | ||
BaseTask | ||
|
||
deferrer *Deferrer | ||
} | ||
|
||
func newWaitTask(log logr.Logger, deferrer *Deferrer, cfg *config.Task) *WaitTask { | ||
return &WaitTask{ | ||
BaseTask: BaseTask{ | ||
log: log, | ||
taskType: TaskWait, | ||
taskID: cfg.ID, | ||
}, | ||
deferrer: deferrer, | ||
} | ||
} | ||
|
||
// Exec implements Runnable interface | ||
func (task *WaitTask) Exec(ctx context.Context) error { | ||
return task.deferrer.Wait(ctx, time.Minute) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package utils | ||
|
||
import ( | ||
"sync" | ||
|
||
"k8s.io/apimachinery/pkg/runtime/schema" | ||
"k8s.io/client-go/dynamic" | ||
"k8s.io/client-go/dynamic/dynamicinformer" | ||
"k8s.io/client-go/tools/cache" | ||
) | ||
|
||
type informerManager struct { | ||
mutex sync.Mutex | ||
factories map[string]dynamicinformer.DynamicSharedInformerFactory | ||
} | ||
|
||
var informerMgr *informerManager | ||
|
||
func init() { | ||
informerMgr = &informerManager{ | ||
factories: make(map[string]dynamicinformer.DynamicSharedInformerFactory), | ||
} | ||
} | ||
|
||
func GetInformer(client dynamic.Interface, namespace string, gvr schema.GroupVersionResource) cache.SharedInformer { | ||
informerMgr.mutex.Lock() | ||
defer informerMgr.mutex.Unlock() | ||
|
||
factory, ok := informerMgr.factories[namespace] | ||
if !ok { | ||
factory = dynamicinformer.NewFilteredDynamicSharedInformerFactory(client, 0, namespace, nil) | ||
informerMgr.factories[namespace] = factory | ||
} | ||
|
||
return factory.ForResource(gvr).Informer() | ||
} |