diff --git a/pkg/dag/dag.go b/pkg/graph/dag/dag.go similarity index 99% rename from pkg/dag/dag.go rename to pkg/graph/dag/dag.go index 14dec438c..68773f0e7 100644 --- a/pkg/dag/dag.go +++ b/pkg/graph/dag/dag.go @@ -308,7 +308,7 @@ func (d *dag[T]) depthFirstSearch(fromVertexID, toVertexID string) bool { return ok } -// depthFirstSearch finds successors of vertex. +// search finds successors of vertex. func (d *dag[T]) search(vertexID string, successors map[string]struct{}) { vertex, ok := d.vertices.Get(vertexID) if !ok { diff --git a/pkg/dag/dag_test.go b/pkg/graph/dag/dag_test.go similarity index 100% rename from pkg/dag/dag_test.go rename to pkg/graph/dag/dag_test.go diff --git a/pkg/dag/mocks/dag_mock.go b/pkg/graph/dag/mocks/dag_mock.go similarity index 99% rename from pkg/dag/mocks/dag_mock.go rename to pkg/graph/dag/mocks/dag_mock.go index 2465794d4..2860609da 100644 --- a/pkg/dag/mocks/dag_mock.go +++ b/pkg/graph/dag/mocks/dag_mock.go @@ -7,7 +7,7 @@ package mocks import ( reflect "reflect" - dag "d7y.io/dragonfly/v2/pkg/dag" + dag "d7y.io/dragonfly/v2/pkg/graph/dag" gomock "github.com/golang/mock/gomock" ) diff --git a/pkg/dag/vertex.go b/pkg/graph/dag/vertex.go similarity index 100% rename from pkg/dag/vertex.go rename to pkg/graph/dag/vertex.go diff --git a/pkg/dag/vertex_test.go b/pkg/graph/dag/vertex_test.go similarity index 100% rename from pkg/dag/vertex_test.go rename to pkg/graph/dag/vertex_test.go diff --git a/pkg/graph/dg/dg.go b/pkg/graph/dg/dg.go new file mode 100644 index 000000000..7595a1553 --- /dev/null +++ b/pkg/graph/dg/dg.go @@ -0,0 +1,308 @@ +/* + * Copyright 2023 The Dragonfly Authors + * + * 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. + */ + +//go:generate mockgen -destination mocks/dg_mock.go -source dg.go -package mocks + +package dg + +import ( + "errors" + "math/rand" + "sync" + "time" + + cmap "github.com/orcaman/concurrent-map/v2" +) + +var ( + // ErrVertexNotFound represents vertex not found. + ErrVertexNotFound = errors.New("vertex not found") + + // ErrVertexAlreadyExists represents vertex already exists. + ErrVertexAlreadyExists = errors.New("vertex already exists") + + // ErrParnetAlreadyExists represents parent of vertex already exists. + ErrParnetAlreadyExists = errors.New("parent of vertex already exists") + + // ErrChildAlreadyExists represents child of vertex already exists. + ErrChildAlreadyExists = errors.New("child of vertex already exists") + + // ErrCycleBetweenVertices represents cycle between vertices. + ErrCycleBetweenVertices = errors.New("cycle between vertices") +) + +// DG is the interface used for directed graph. +type DG[T comparable] interface { + // AddVertex adds vertex to graph. + AddVertex(id string, value T) error + + // DeleteVertex deletes vertex graph. + DeleteVertex(id string) + + // GetVertex gets vertex from graph. + GetVertex(id string) (*Vertex[T], error) + + // GetVertices returns map of vertices. + GetVertices() map[string]*Vertex[T] + + // GetRandomVertices returns random map of vertices. + GetRandomVertices(n uint) []*Vertex[T] + + // GetVertexKeys returns keys of vertices. + GetVertexKeys() []string + + // GetSourceVertices returns source vertices. + GetSourceVertices() []*Vertex[T] + + // GetSinkVertices returns sink vertices. + GetSinkVertices() []*Vertex[T] + + // VertexCount returns count of vertices. + VertexCount() int + + // AddEdge adds edge between two vertices. + AddEdge(fromVertexID, toVertexID string) error + + // DeleteEdge deletes edge between two vertices. + DeleteEdge(fromVertexID, toVertexID string) error + + // CanAddEdge finds whether there are circles through depth-first search. + CanAddEdge(fromVertexID, toVertexID string) bool +} + +// dg provides directed graph function. +type dg[T comparable] struct { + mu sync.RWMutex + vertices cmap.ConcurrentMap[string, *Vertex[T]] +} + +// New returns a new DG interface. +func NewDG[T comparable]() DG[T] { + return &dg[T]{ + vertices: cmap.New[*Vertex[T]](), + } +} + +// AddVertex adds vertex to graph. +func (d *dg[T]) AddVertex(id string, value T) error { + d.mu.Lock() + defer d.mu.Unlock() + + if _, ok := d.vertices.Get(id); ok { + return ErrVertexAlreadyExists + } + + d.vertices.Set(id, NewVertex(id, value)) + return nil +} + +// DeleteVertex deletes vertex graph. +func (d *dg[T]) DeleteVertex(id string) { + d.mu.Lock() + defer d.mu.Unlock() + + vertex, ok := d.vertices.Get(id) + if !ok { + return + } + + for _, parent := range vertex.Parents.Values() { + parent.Children.Delete(vertex) + } + + for _, child := range vertex.Children.Values() { + child.Parents.Delete(vertex) + continue + } + + d.vertices.Remove(id) +} + +// GetVertex gets vertex from graph. +func (d *dg[T]) GetVertex(id string) (*Vertex[T], error) { + vertex, ok := d.vertices.Get(id) + if !ok { + return nil, ErrVertexNotFound + } + + return vertex, nil +} + +// GetVertices returns map of vertices. +func (d *dg[T]) GetVertices() map[string]*Vertex[T] { + return d.vertices.Items() +} + +// GetRandomVertices returns random map of vertices. +func (d *dg[T]) GetRandomVertices(n uint) []*Vertex[T] { + d.mu.RLock() + defer d.mu.RUnlock() + + keys := d.GetVertexKeys() + if int(n) >= len(keys) { + n = uint(len(keys)) + } + + rand.Seed(time.Now().Unix()) + permutation := rand.Perm(len(keys))[:n] + randomVertices := make([]*Vertex[T], 0, n) + for _, v := range permutation { + key := keys[v] + if vertex, err := d.GetVertex(key); err == nil { + randomVertices = append(randomVertices, vertex) + } + } + + return randomVertices +} + +// GetVertexKeys returns keys of vertices. +func (d *dg[T]) GetVertexKeys() []string { + return d.vertices.Keys() +} + +// VertexCount returns count of vertices. +func (d *dg[T]) VertexCount() int { + return d.vertices.Count() +} + +// CanAddEdge finds whether there are circles through depth-first search. +func (d *dg[T]) CanAddEdge(fromVertexID, toVertexID string) bool { + d.mu.RLock() + defer d.mu.RUnlock() + + if fromVertexID == toVertexID { + return false + } + + fromVertex, ok := d.vertices.Get(fromVertexID) + if !ok { + return false + } + + if _, ok := d.vertices.Get(toVertexID); !ok { + return false + } + + for _, child := range fromVertex.Children.Values() { + if child.ID == toVertexID { + return false + } + } + + return true +} + +// AddEdge adds edge between two vertices. +func (d *dg[T]) AddEdge(fromVertexID, toVertexID string) error { + d.mu.Lock() + defer d.mu.Unlock() + + if fromVertexID == toVertexID { + return ErrCycleBetweenVertices + } + + fromVertex, ok := d.vertices.Get(fromVertexID) + if !ok { + return ErrVertexNotFound + } + + toVertex, ok := d.vertices.Get(toVertexID) + if !ok { + return ErrVertexNotFound + } + + for _, child := range fromVertex.Children.Values() { + if child.ID == toVertexID { + return ErrCycleBetweenVertices + } + } + + if ok := fromVertex.Children.Add(toVertex); !ok { + return ErrChildAlreadyExists + } + + if ok := toVertex.Parents.Add(fromVertex); !ok { + return ErrParnetAlreadyExists + } + + return nil +} + +// DeleteEdge deletes edge between two vertices. +func (d *dg[T]) DeleteEdge(fromVertexID, toVertexID string) error { + d.mu.Lock() + defer d.mu.Unlock() + + fromVertex, ok := d.vertices.Get(fromVertexID) + if !ok { + return ErrVertexNotFound + } + + toVertex, ok := d.vertices.Get(toVertexID) + if !ok { + return ErrVertexNotFound + } + + fromVertex.Children.Delete(toVertex) + toVertex.Parents.Delete(fromVertex) + return nil +} + +// GetSourceVertices returns source vertices. +func (d *dg[T]) GetSourceVertices() []*Vertex[T] { + d.mu.RLock() + defer d.mu.RUnlock() + + var sourceVertices []*Vertex[T] + for _, vertex := range d.vertices.Items() { + if vertex.InDegree() == 0 { + sourceVertices = append(sourceVertices, vertex) + } + } + + return sourceVertices +} + +// GetSinkVertices returns sink vertices. +func (d *dg[T]) GetSinkVertices() []*Vertex[T] { + d.mu.RLock() + defer d.mu.RUnlock() + + var sinkVertices []*Vertex[T] + for _, vertex := range d.vertices.Items() { + if vertex.OutDegree() == 0 { + sinkVertices = append(sinkVertices, vertex) + } + } + + return sinkVertices +} + +// search finds successors of vertex. +func (d *dg[T]) search(vertexID string, successors map[string]struct{}) { + vertex, ok := d.vertices.Get(vertexID) + if !ok { + return + } + + for _, child := range vertex.Children.Values() { + if _, ok := successors[child.ID]; !ok { + successors[child.ID] = struct{}{} + d.search(child.ID, successors) + } + } +} diff --git a/pkg/graph/dg/dg_test.go b/pkg/graph/dg/dg_test.go new file mode 100644 index 000000000..6b3c9e178 --- /dev/null +++ b/pkg/graph/dg/dg_test.go @@ -0,0 +1,924 @@ +/* + * Copyright 2023 The Dragonfly Authors + * + * 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 dg + +import ( + "errors" + "fmt" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewDG(t *testing.T) { + d := NewDG[string]() + assert := assert.New(t) + assert.Equal(reflect.TypeOf(d).Elem().Name(), "dg[string]") +} + +func TestDGAddVertex(t *testing.T) { + tests := []struct { + name string + id string + value any + expect func(t *testing.T, d DG[string], err error) + }{ + { + name: "add vertex", + id: mockVertexID, + value: mockVertexValue, + expect: func(t *testing.T, d DG[string], err error) { + assert := assert.New(t) + assert.NoError(err) + }, + }, + { + name: "vertex already exists", + id: mockVertexID, + value: mockVertexValue, + expect: func(t *testing.T, d DG[string], err error) { + assert := assert.New(t) + assert.NoError(err) + + assert.EqualError(d.AddVertex(mockVertexID, mockVertexValue), ErrVertexAlreadyExists.Error()) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + d := NewDG[string]() + tc.expect(t, d, d.AddVertex(tc.id, tc.name)) + }) + } +} + +func TestDGDeleteVertex(t *testing.T) { + tests := []struct { + name string + expect func(t *testing.T, d DG[string]) + }{ + { + name: "delete vertex", + expect: func(t *testing.T, d DG[string]) { + assert := assert.New(t) + if err := d.AddVertex(mockVertexID, mockVertexValue); err != nil { + assert.NoError(err) + } + d.DeleteVertex(mockVertexID) + + _, err := d.GetVertex(mockVertexID) + assert.EqualError(err, ErrVertexNotFound.Error()) + }, + }, + { + name: "delete vertex with edges", + expect: func(t *testing.T, d DG[string]) { + assert := assert.New(t) + + var ( + mockToVertexID = "baz" + ) + if err := d.AddVertex(mockToVertexID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexID, mockToVertexID); err != nil { + assert.NoError(err) + } + + d.DeleteVertex(mockVertexID) + + _, err := d.GetVertex(mockVertexID) + assert.EqualError(err, ErrVertexNotFound.Error()) + + vertex, err := d.GetVertex(mockToVertexID) + assert.NoError(err) + assert.Equal(vertex.Parents.Len(), uint(0)) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + d := NewDG[string]() + tc.expect(t, d) + }) + } +} + +func TestDGGetVertex(t *testing.T) { + tests := []struct { + name string + expect func(t *testing.T, d DG[string]) + }{ + { + name: "get vertex", + expect: func(t *testing.T, d DG[string]) { + assert := assert.New(t) + if err := d.AddVertex(mockVertexID, mockVertexValue); err != nil { + assert.NoError(err) + } + + vertex, err := d.GetVertex(mockVertexID) + assert.NoError(err) + assert.Equal(vertex.ID, mockVertexID) + assert.Equal(vertex.Value, mockVertexValue) + assert.Equal(vertex.Parents.Len(), uint(0)) + assert.Equal(vertex.Children.Len(), uint(0)) + }, + }, + { + name: "vertex not found", + expect: func(t *testing.T, d DG[string]) { + assert := assert.New(t) + _, err := d.GetVertex(mockVertexID) + assert.EqualError(err, ErrVertexNotFound.Error()) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + d := NewDG[string]() + tc.expect(t, d) + }) + } +} + +func TestDGVertexVertexCount(t *testing.T) { + tests := []struct { + name string + expect func(t *testing.T, d DG[string]) + }{ + { + name: "get length of vertex", + expect: func(t *testing.T, d DG[string]) { + assert := assert.New(t) + if err := d.AddVertex(mockVertexID, mockVertexValue); err != nil { + assert.NoError(err) + } + + d.VertexCount() + assert.Equal(d.VertexCount(), 1) + + d.DeleteVertex(mockVertexID) + assert.Equal(d.VertexCount(), 0) + }, + }, + { + name: "empty dg", + expect: func(t *testing.T, d DG[string]) { + assert := assert.New(t) + assert.Equal(d.VertexCount(), 0) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + d := NewDG[string]() + tc.expect(t, d) + }) + } +} + +func TestDGGetVertices(t *testing.T) { + tests := []struct { + name string + expect func(t *testing.T, d DG[string]) + }{ + { + name: "get vertices", + expect: func(t *testing.T, d DG[string]) { + assert := assert.New(t) + if err := d.AddVertex(mockVertexID, mockVertexValue); err != nil { + assert.NoError(err) + } + + vertices := d.GetVertices() + assert.Equal(len(vertices), 1) + assert.Equal(vertices[mockVertexID].ID, mockVertexID) + assert.Equal(vertices[mockVertexID].Value, mockVertexValue) + + d.DeleteVertex(mockVertexID) + vertices = d.GetVertices() + assert.Equal(len(vertices), 0) + }, + }, + { + name: "dg is empty", + expect: func(t *testing.T, d DG[string]) { + assert := assert.New(t) + vertices := d.GetVertices() + assert.Equal(len(vertices), 0) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + d := NewDG[string]() + tc.expect(t, d) + }) + } +} + +func TestDGGetRandomVertices(t *testing.T) { + tests := []struct { + name string + expect func(t *testing.T, d DG[string]) + }{ + { + name: "get random vertices", + expect: func(t *testing.T, d DG[string]) { + assert := assert.New(t) + var ( + mockVertexEID = "bae" + mockVertexFID = "baf" + ) + + if err := d.AddVertex(mockVertexEID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexFID, mockVertexValue); err != nil { + assert.NoError(err) + } + + vertices := d.GetRandomVertices(0) + assert.Equal(len(vertices), 0) + + vertices = d.GetRandomVertices(1) + assert.Equal(len(vertices), 1) + + vertices = d.GetRandomVertices(2) + assert.Equal(len(vertices), 2) + + vertices = d.GetRandomVertices(3) + assert.Equal(len(vertices), 2) + }, + }, + { + name: "dg is empty", + expect: func(t *testing.T, d DG[string]) { + assert := assert.New(t) + vertices := d.GetRandomVertices(0) + assert.Equal(len(vertices), 0) + + vertices = d.GetRandomVertices(1) + assert.Equal(len(vertices), 0) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + d := NewDG[string]() + tc.expect(t, d) + }) + } +} + +func TestDGGetVertexKeys(t *testing.T) { + tests := []struct { + name string + expect func(t *testing.T, d DG[string]) + }{ + { + name: "get keys of vertices", + expect: func(t *testing.T, d DG[string]) { + assert := assert.New(t) + if err := d.AddVertex(mockVertexID, mockVertexValue); err != nil { + assert.NoError(err) + } + + keys := d.GetVertexKeys() + assert.Equal(len(keys), 1) + assert.Equal(keys[0], mockVertexID) + + d.DeleteVertex(mockVertexID) + keys = d.GetVertexKeys() + assert.Equal(len(keys), 0) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + d := NewDG[string]() + tc.expect(t, d) + }) + } +} + +func TestDGAddEdge(t *testing.T) { + tests := []struct { + name string + expect func(t *testing.T, d DG[string]) + }{ + { + name: "add edge", + expect: func(t *testing.T, d DG[string]) { + assert := assert.New(t) + var ( + mockVertexEID = "bae" + mockVertexFID = "baf" + mockVertexGID = "bag" + mockVertexHID = "bah" + mockVertexIID = "bai" + ) + + if err := d.AddVertex(mockVertexEID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexFID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexGID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexHID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexIID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexEID, mockVertexFID); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexFID, mockVertexGID); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexFID, mockVertexHID); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexGID, mockVertexIID); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexIID, mockVertexHID); err != nil { + assert.NoError(err) + } + }, + }, + { + name: "vertex not found", + expect: func(t *testing.T, d DG[string]) { + assert := assert.New(t) + var ( + mockVertexEID = "bae" + mockVertexFID = "baf" + ) + + if err := d.AddVertex(mockVertexEID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexEID, mockVertexFID); err != nil { + assert.EqualError(err, ErrVertexNotFound.Error()) + } + + if err := d.AddEdge(mockVertexFID, mockVertexEID); err != nil { + assert.EqualError(err, ErrVertexNotFound.Error()) + } + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + d := NewDG[string]() + tc.expect(t, d) + }) + } +} + +func TestDGCanAddEdge(t *testing.T) { + tests := []struct { + name string + expect func(t *testing.T, d DG[string]) + }{ + { + name: "can add edge", + expect: func(t *testing.T, d DG[string]) { + assert := assert.New(t) + var ( + mockVertexEID = "bae" + mockVertexFID = "baf" + mockVertexGID = "bag" + mockVertexHID = "bah" + mockVertexIID = "bai" + ) + + if err := d.AddVertex(mockVertexEID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexFID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexGID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexHID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexIID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexEID, mockVertexFID); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexFID, mockVertexGID); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexFID, mockVertexHID); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexGID, mockVertexIID); err != nil { + assert.NoError(err) + } + + ok := d.CanAddEdge(mockVertexIID, mockVertexHID) + assert.True(ok) + }, + }, + { + name: "cycle between vertices", + expect: func(t *testing.T, d DG[string]) { + assert := assert.New(t) + var ( + mockVertexEID = "bae" + mockVertexFID = "baf" + mockVertexGID = "bag" + mockVertexHID = "bah" + mockVertexIID = "bai" + ) + + if err := d.AddVertex(mockVertexEID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexFID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexGID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexHID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexIID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexEID, mockVertexEID); err != nil { + assert.EqualError(err, ErrCycleBetweenVertices.Error()) + } + + if err := d.AddEdge(mockVertexEID, mockVertexFID); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexFID, mockVertexEID); err != nil { + assert.EqualError(err, ErrCycleBetweenVertices.Error()) + } + + if err := d.AddEdge(mockVertexFID, mockVertexGID); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexGID, mockVertexEID); err != nil { + assert.EqualError(err, ErrCycleBetweenVertices.Error()) + } + + if err := d.AddEdge(mockVertexGID, mockVertexHID); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexHID, mockVertexIID); err != nil { + assert.NoError(err) + } + + ok := d.CanAddEdge(mockVertexIID, mockVertexEID) + assert.True(ok) + }, + }, + { + name: "vertex not found", + expect: func(t *testing.T, d DG[string]) { + assert := assert.New(t) + var ( + mockVertexEID = "bae" + mockVertexFID = "baf" + ) + + if err := d.AddVertex(mockVertexEID, mockVertexValue); err != nil { + assert.NoError(err) + } + + ok := d.CanAddEdge(mockVertexEID, mockVertexFID) + assert.False(ok) + ok = d.CanAddEdge(mockVertexFID, mockVertexEID) + assert.False(ok) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + d := NewDG[string]() + tc.expect(t, d) + }) + } +} + +func TestDGDeleteEdge(t *testing.T) { + tests := []struct { + name string + expect func(t *testing.T, d DG[string]) + }{ + { + name: "delete edge", + expect: func(t *testing.T, d DG[string]) { + assert := assert.New(t) + var ( + mockVertexEID = "bae" + mockVertexFID = "baf" + ) + + if err := d.AddVertex(mockVertexEID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexFID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexEID, mockVertexFID); err != nil { + assert.NoError(err) + } + + if err := d.DeleteEdge(mockVertexFID, mockVertexEID); err != nil { + assert.NoError(err) + } + + vf, err := d.GetVertex(mockVertexFID) + assert.NoError(err) + assert.Equal(vf.Parents.Len(), uint(1)) + ve, err := d.GetVertex(mockVertexEID) + assert.NoError(err) + assert.Equal(ve.Children.Len(), uint(1)) + + if err := d.DeleteEdge(mockVertexEID, mockVertexFID); err != nil { + assert.NoError(err) + } + + vf, err = d.GetVertex(mockVertexFID) + assert.NoError(err) + assert.Equal(vf.Parents.Len(), uint(0)) + ve, err = d.GetVertex(mockVertexEID) + assert.NoError(err) + assert.Equal(ve.Children.Len(), uint(0)) + }, + }, + { + name: "vertex not found", + expect: func(t *testing.T, d DG[string]) { + assert := assert.New(t) + var ( + mockVertexEID = "bae" + mockVertexFID = "baf" + ) + + if err := d.AddVertex(mockVertexEID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexEID, mockVertexFID); err != nil { + assert.EqualError(err, ErrVertexNotFound.Error()) + } + + if err := d.AddEdge(mockVertexFID, mockVertexEID); err != nil { + assert.EqualError(err, ErrVertexNotFound.Error()) + } + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + d := NewDG[string]() + tc.expect(t, d) + }) + } +} + +func TestDGSourceVertices(t *testing.T) { + tests := []struct { + name string + expect func(t *testing.T, d DG[string]) + }{ + { + name: "get source vertices", + expect: func(t *testing.T, d DG[string]) { + assert := assert.New(t) + var ( + mockVertexEID = "bae" + mockVertexFID = "baf" + ) + if err := d.AddVertex(mockVertexEID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexFID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexEID, mockVertexFID); err != nil { + assert.NoError(err) + } + + sourceVertices := d.GetSourceVertices() + assert.Equal(len(sourceVertices), 1) + assert.Equal(sourceVertices[0].Value, mockVertexValue) + }, + }, + { + name: "source vertices not found", + expect: func(t *testing.T, d DG[string]) { + assert := assert.New(t) + sourceVertices := d.GetSourceVertices() + assert.Equal(len(sourceVertices), 0) + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + d := NewDG[string]() + tc.expect(t, d) + }) + } +} + +func TestDGSinkVertices(t *testing.T) { + tests := []struct { + name string + expect func(t *testing.T, d DG[string]) + }{ + { + name: "get sink vertices", + expect: func(t *testing.T, d DG[string]) { + assert := assert.New(t) + var ( + mockVertexEID = "bae" + mockVertexFID = "baf" + ) + if err := d.AddVertex(mockVertexEID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexFID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexEID, mockVertexFID); err != nil { + assert.NoError(err) + } + + sinkVertices := d.GetSinkVertices() + assert.Equal(len(sinkVertices), 1) + assert.Equal(sinkVertices[0].Value, mockVertexValue) + }, + }, + { + name: "sink vertices not found", + expect: func(t *testing.T, d DG[string]) { + assert := assert.New(t) + sinkVertices := d.GetSinkVertices() + assert.Equal(len(sinkVertices), 0) + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + d := NewDG[string]() + tc.expect(t, d) + }) + } +} + +func BenchmarkDGAddVertex(b *testing.B) { + var ids []string + d := NewDG[string]() + for n := 0; n < b.N; n++ { + ids = append(ids, fmt.Sprint(n)) + } + + b.ResetTimer() + for _, id := range ids { + if err := d.AddVertex(id, string(id)); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkDGDeleteVertex(b *testing.B) { + var ids []string + d := NewDG[string]() + for n := 0; n < b.N; n++ { + id := fmt.Sprint(n) + if err := d.AddVertex(id, string(id)); err != nil { + b.Fatal(err) + } + + ids = append(ids, id) + } + + b.ResetTimer() + for _, id := range ids { + d.DeleteVertex(id) + } +} + +func BenchmarkDGGetRandomKeys(b *testing.B) { + d := NewDG[string]() + for n := 0; n < b.N; n++ { + id := fmt.Sprint(n) + if err := d.AddVertex(id, string(id)); err != nil { + b.Fatal(err) + } + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + vertices := d.GetRandomVertices(uint(n)) + if len(vertices) != n { + b.Fatal(errors.New("get random vertices failed")) + } + } +} + +func BenchmarkDGDeleteVertexWithMultiEdges(b *testing.B) { + var ids []string + d := NewDG[string]() + for n := 0; n < b.N; n++ { + id := fmt.Sprint(n) + if err := d.AddVertex(id, string(id)); err != nil { + b.Fatal(err) + } + + ids = append(ids, id) + } + + edgeCount := 5 + for index, id := range ids { + if index+edgeCount > len(ids)-1 { + break + } + + for n := 1; n < edgeCount; n++ { + if err := d.AddEdge(id, ids[index+n]); err != nil { + b.Fatal(err) + } + } + } + + b.ResetTimer() + for _, id := range ids { + d.DeleteVertex(id) + } +} + +func BenchmarkDGAddEdge(b *testing.B) { + var ids []string + d := NewDG[string]() + for n := 0; n < b.N; n++ { + id := fmt.Sprint(n) + if err := d.AddVertex(id, string(id)); err != nil { + b.Fatal(err) + } + + ids = append(ids, id) + } + + b.ResetTimer() + for index, id := range ids { + if index < 1 { + continue + } + + if err := d.AddEdge(id, ids[index-1]); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkDGAddEdgeWithMultiEdges(b *testing.B) { + var ids []string + d := NewDG[string]() + for n := 0; n < b.N; n++ { + id := fmt.Sprint(n) + if err := d.AddVertex(id, string(id)); err != nil { + b.Fatal(err) + } + + ids = append(ids, id) + } + + edgeCount := 5 + for index, id := range ids { + if index+edgeCount > len(ids)-1 { + break + } + + for n := 1; n < edgeCount; n++ { + if err := d.AddEdge(id, ids[index+n]); err != nil { + b.Fatal(err) + } + } + } + + b.ResetTimer() + for index, id := range ids { + if index+edgeCount+1 > len(ids)-1 { + break + } + + if err := d.AddEdge(id, ids[index+edgeCount+1]); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkDGDeleteEdge(b *testing.B) { + var ids []string + d := NewDG[string]() + for n := 0; n < b.N; n++ { + id := fmt.Sprint(n) + if err := d.AddVertex(id, string(id)); err != nil { + b.Fatal(err) + } + + ids = append(ids, id) + } + + for index, id := range ids { + if index < 1 { + continue + } + + if err := d.AddEdge(id, ids[index-1]); err != nil { + b.Fatal(err) + } + } + + b.ResetTimer() + for index, id := range ids { + if index < 1 { + continue + } + + if err := d.DeleteEdge(id, ids[index-1]); err != nil { + b.Fatal(err) + } + } +} diff --git a/pkg/graph/dg/mocks/dg_mock.go b/pkg/graph/dg/mocks/dg_mock.go new file mode 100644 index 000000000..28aa6c556 --- /dev/null +++ b/pkg/graph/dg/mocks/dg_mock.go @@ -0,0 +1,202 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: dg.go + +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + + dg "d7y.io/dragonfly/v2/pkg/graph/dg" + gomock "github.com/golang/mock/gomock" +) + +// MockDG is a mock of DG interface. +type MockDG[T comparable] struct { + ctrl *gomock.Controller + recorder *MockDGMockRecorder[T] +} + +// MockDGMockRecorder is the mock recorder for MockDG. +type MockDGMockRecorder[T comparable] struct { + mock *MockDG[T] +} + +// NewMockDG creates a new mock instance. +func NewMockDG[T comparable](ctrl *gomock.Controller) *MockDG[T] { + mock := &MockDG[T]{ctrl: ctrl} + mock.recorder = &MockDGMockRecorder[T]{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDG[T]) EXPECT() *MockDGMockRecorder[T] { + return m.recorder +} + +// AddEdge mocks base method. +func (m *MockDG[T]) AddEdge(fromVertexID, toVertexID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddEdge", fromVertexID, toVertexID) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddEdge indicates an expected call of AddEdge. +func (mr *MockDGMockRecorder[T]) AddEdge(fromVertexID, toVertexID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddEdge", reflect.TypeOf((*MockDG[T])(nil).AddEdge), fromVertexID, toVertexID) +} + +// AddVertex mocks base method. +func (m *MockDG[T]) AddVertex(id string, value T) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddVertex", id, value) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddVertex indicates an expected call of AddVertex. +func (mr *MockDGMockRecorder[T]) AddVertex(id, value interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddVertex", reflect.TypeOf((*MockDG[T])(nil).AddVertex), id, value) +} + +// CanAddEdge mocks base method. +func (m *MockDG[T]) CanAddEdge(fromVertexID, toVertexID string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CanAddEdge", fromVertexID, toVertexID) + ret0, _ := ret[0].(bool) + return ret0 +} + +// CanAddEdge indicates an expected call of CanAddEdge. +func (mr *MockDGMockRecorder[T]) CanAddEdge(fromVertexID, toVertexID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CanAddEdge", reflect.TypeOf((*MockDG[T])(nil).CanAddEdge), fromVertexID, toVertexID) +} + +// DeleteEdge mocks base method. +func (m *MockDG[T]) DeleteEdge(fromVertexID, toVertexID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteEdge", fromVertexID, toVertexID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteEdge indicates an expected call of DeleteEdge. +func (mr *MockDGMockRecorder[T]) DeleteEdge(fromVertexID, toVertexID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteEdge", reflect.TypeOf((*MockDG[T])(nil).DeleteEdge), fromVertexID, toVertexID) +} + +// DeleteVertex mocks base method. +func (m *MockDG[T]) DeleteVertex(id string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "DeleteVertex", id) +} + +// DeleteVertex indicates an expected call of DeleteVertex. +func (mr *MockDGMockRecorder[T]) DeleteVertex(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteVertex", reflect.TypeOf((*MockDG[T])(nil).DeleteVertex), id) +} + +// GetRandomVertices mocks base method. +func (m *MockDG[T]) GetRandomVertices(n uint) []*dg.Vertex[T] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRandomVertices", n) + ret0, _ := ret[0].([]*dg.Vertex[T]) + return ret0 +} + +// GetRandomVertices indicates an expected call of GetRandomVertices. +func (mr *MockDGMockRecorder[T]) GetRandomVertices(n interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRandomVertices", reflect.TypeOf((*MockDG[T])(nil).GetRandomVertices), n) +} + +// GetSinkVertices mocks base method. +func (m *MockDG[T]) GetSinkVertices() []*dg.Vertex[T] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSinkVertices") + ret0, _ := ret[0].([]*dg.Vertex[T]) + return ret0 +} + +// GetSinkVertices indicates an expected call of GetSinkVertices. +func (mr *MockDGMockRecorder[T]) GetSinkVertices() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSinkVertices", reflect.TypeOf((*MockDG[T])(nil).GetSinkVertices)) +} + +// GetSourceVertices mocks base method. +func (m *MockDG[T]) GetSourceVertices() []*dg.Vertex[T] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSourceVertices") + ret0, _ := ret[0].([]*dg.Vertex[T]) + return ret0 +} + +// GetSourceVertices indicates an expected call of GetSourceVertices. +func (mr *MockDGMockRecorder[T]) GetSourceVertices() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSourceVertices", reflect.TypeOf((*MockDG[T])(nil).GetSourceVertices)) +} + +// GetVertex mocks base method. +func (m *MockDG[T]) GetVertex(id string) (*dg.Vertex[T], error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetVertex", id) + ret0, _ := ret[0].(*dg.Vertex[T]) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetVertex indicates an expected call of GetVertex. +func (mr *MockDGMockRecorder[T]) GetVertex(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVertex", reflect.TypeOf((*MockDG[T])(nil).GetVertex), id) +} + +// GetVertexKeys mocks base method. +func (m *MockDG[T]) GetVertexKeys() []string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetVertexKeys") + ret0, _ := ret[0].([]string) + return ret0 +} + +// GetVertexKeys indicates an expected call of GetVertexKeys. +func (mr *MockDGMockRecorder[T]) GetVertexKeys() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVertexKeys", reflect.TypeOf((*MockDG[T])(nil).GetVertexKeys)) +} + +// GetVertices mocks base method. +func (m *MockDG[T]) GetVertices() map[string]*dg.Vertex[T] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetVertices") + ret0, _ := ret[0].(map[string]*dg.Vertex[T]) + return ret0 +} + +// GetVertices indicates an expected call of GetVertices. +func (mr *MockDGMockRecorder[T]) GetVertices() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVertices", reflect.TypeOf((*MockDG[T])(nil).GetVertices)) +} + +// VertexCount mocks base method. +func (m *MockDG[T]) VertexCount() int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VertexCount") + ret0, _ := ret[0].(int) + return ret0 +} + +// VertexCount indicates an expected call of VertexCount. +func (mr *MockDGMockRecorder[T]) VertexCount() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VertexCount", reflect.TypeOf((*MockDG[T])(nil).VertexCount)) +} diff --git a/pkg/graph/dg/vertex.go b/pkg/graph/dg/vertex.go new file mode 100644 index 000000000..7ff7f2fd2 --- /dev/null +++ b/pkg/graph/dg/vertex.go @@ -0,0 +1,70 @@ +/* + * Copyright 2023 The Dragonfly Authors + * + * 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 dg + +import "d7y.io/dragonfly/v2/pkg/container/set" + +// Vertex is a vertex of the directed graph. +type Vertex[T comparable] struct { + ID string + Value T + Parents set.SafeSet[*Vertex[T]] + Children set.SafeSet[*Vertex[T]] +} + +// New returns a new Vertex instance. +func NewVertex[T comparable](id string, value T) *Vertex[T] { + return &Vertex[T]{ + ID: id, + Value: value, + Parents: set.NewSafeSet[*Vertex[T]](), + Children: set.NewSafeSet[*Vertex[T]](), + } +} + +// Degree returns the degree of vertex. +func (v *Vertex[T]) Degree() int { + return int(v.Parents.Len() + v.Children.Len()) +} + +// InDegree returns the indegree of vertex. +func (v *Vertex[T]) InDegree() int { + return int(v.Parents.Len()) +} + +// OutDegree returns the outdegree of vertex. +func (v *Vertex[T]) OutDegree() int { + return int(v.Children.Len()) +} + +// DeleteInEdges deletes inedges of vertex. +func (v *Vertex[T]) DeleteInEdges() { + for _, parent := range v.Parents.Values() { + parent.Children.Delete(v) + } + + v.Parents = set.NewSafeSet[*Vertex[T]]() +} + +// DeleteOutEdges deletes outedges of vertex. +func (v *Vertex[T]) DeleteOutEdges() { + for _, child := range v.Children.Values() { + child.Parents.Delete(v) + } + + v.Children = set.NewSafeSet[*Vertex[T]]() +} diff --git a/pkg/graph/dg/vertex_test.go b/pkg/graph/dg/vertex_test.go new file mode 100644 index 000000000..4d85916a9 --- /dev/null +++ b/pkg/graph/dg/vertex_test.go @@ -0,0 +1,127 @@ +/* + * Copyright 2023 The Dragonfly Authors + * + * 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 dg + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +const ( + mockVertexID = "foo" + mockVertexValue = "bar" +) + +func TestNewVertex(t *testing.T) { + v := NewVertex(mockVertexID, mockVertexValue) + assert := assert.New(t) + assert.Equal(v.ID, mockVertexID) + assert.Equal(v.Value, mockVertexValue) + assert.Equal(v.Parents.Len(), uint(0)) + assert.Equal(v.Children.Len(), uint(0)) +} + +func TestVertexDegree(t *testing.T) { + v := NewVertex(mockVertexID, mockVertexValue) + assert := assert.New(t) + assert.Equal(v.ID, mockVertexID) + assert.Equal(v.Value, mockVertexValue) + assert.Equal(v.Degree(), 0) + + v.Parents.Add(v) + assert.Equal(v.Degree(), 1) + + v.Children.Add(v) + assert.Equal(v.Degree(), 2) + + v.Parents.Delete(v) + assert.Equal(v.Degree(), 1) + + v.Children.Delete(v) + assert.Equal(v.Degree(), 0) +} + +func TestVertexInDegree(t *testing.T) { + v := NewVertex(mockVertexID, mockVertexValue) + assert := assert.New(t) + assert.Equal(v.ID, mockVertexID) + assert.Equal(v.Value, mockVertexValue) + assert.Equal(v.InDegree(), 0) + + v.Parents.Add(v) + assert.Equal(v.InDegree(), 1) + + v.Children.Add(v) + assert.Equal(v.InDegree(), 1) + + v.Parents.Delete(v) + assert.Equal(v.InDegree(), 0) + + v.Children.Delete(v) + assert.Equal(v.InDegree(), 0) +} + +func TestVertexOutDegree(t *testing.T) { + v := NewVertex(mockVertexID, mockVertexValue) + assert := assert.New(t) + assert.Equal(v.ID, mockVertexID) + assert.Equal(v.Value, mockVertexValue) + assert.Equal(v.OutDegree(), 0) + + v.Parents.Add(v) + assert.Equal(v.OutDegree(), 0) + + v.Children.Add(v) + assert.Equal(v.OutDegree(), 1) + + v.Parents.Delete(v) + assert.Equal(v.OutDegree(), 1) + + v.Children.Delete(v) + assert.Equal(v.OutDegree(), 0) +} + +func TestVertexDeleteInEdges(t *testing.T) { + va := NewVertex(mockVertexID, mockVertexValue) + vb := NewVertex(mockVertexID, mockVertexValue) + assert := assert.New(t) + + va.Parents.Add(vb) + vb.Children.Add(va) + assert.Equal(va.Parents.Len(), uint(1)) + assert.Equal(vb.Children.Len(), uint(1)) + + va.DeleteInEdges() + assert.Equal(va.Parents.Len(), uint(0)) + assert.Equal(vb.Children.Len(), uint(0)) +} + +func TestVertexDeleteOutEdges(t *testing.T) { + va := NewVertex(mockVertexID, mockVertexValue) + vb := NewVertex(mockVertexID, mockVertexValue) + assert := assert.New(t) + + va.Parents.Add(vb) + vb.Children.Add(va) + assert.Equal(va.Parents.Len(), uint(1)) + assert.Equal(vb.Children.Len(), uint(1)) + + vb.DeleteOutEdges() + assert.Equal(va.Parents.Len(), uint(0)) + assert.Equal(vb.Children.Len(), uint(0)) +} diff --git a/scheduler/resource/task.go b/scheduler/resource/task.go index 2e33a5db8..aaee44496 100644 --- a/scheduler/resource/task.go +++ b/scheduler/resource/task.go @@ -31,7 +31,7 @@ import ( logger "d7y.io/dragonfly/v2/internal/dflog" "d7y.io/dragonfly/v2/pkg/container/set" - "d7y.io/dragonfly/v2/pkg/dag" + "d7y.io/dragonfly/v2/pkg/graph/dag" "d7y.io/dragonfly/v2/pkg/types" )