github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controller/model/graph_client.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package model 21 22 import ( 23 "fmt" 24 "reflect" 25 26 "sigs.k8s.io/controller-runtime/pkg/client" 27 28 "github.com/1aal/kubeblocks/pkg/controller/graph" 29 ) 30 31 // GraphOption specifies behaviors of GraphWriter methods. 32 // currently enum is enough, maybe extend to interface in the future. 33 type GraphOption string 34 35 const ( 36 // ReplaceIfExistingOption tells the GraphWriter methods to replace Obj and OriObj with the given ones if already existing. 37 // used in Action methods: Create, Update, Patch, Status, Noop and Delete 38 ReplaceIfExistingOption = "ReplaceIfExisting" 39 40 // HaveDifferentTypeWithOption is used in FindAll method to find all objects have different type with the given one. 41 HaveDifferentTypeWithOption = "HaveDifferentTypeWith" 42 ) 43 44 type GraphWriter interface { 45 // Root setups the given obj as root vertex of the underlying DAG. 46 Root(dag *graph.DAG, objOld, objNew client.Object, action *Action) 47 48 // Create saves the object obj in the underlying DAG. 49 Create(dag *graph.DAG, obj client.Object, opts ...GraphOption) 50 51 // Delete deletes the given obj from the underlying DAG. 52 Delete(dag *graph.DAG, obj client.Object, opts ...GraphOption) 53 54 // Update updates the given obj in the underlying DAG. 55 Update(dag *graph.DAG, objOld, objNew client.Object, opts ...GraphOption) 56 57 // Patch patches the given objOld by the new version objNew in the underlying DAG. 58 Patch(dag *graph.DAG, objOld, objNew client.Object, opts ...GraphOption) 59 60 // Status updates the given obj's status in the underlying DAG. 61 Status(dag *graph.DAG, objOld, objNew client.Object, opts ...GraphOption) 62 63 // Noop means not to commit any change made to this obj in the execute phase. 64 Noop(dag *graph.DAG, obj client.Object, opts ...GraphOption) 65 66 // Do does 'action' to 'objOld' and 'objNew' and return the vertex created. 67 // this method creates a vertex directly even if the given object already exists in the underlying DAG. 68 // WARN: this is a rather low-level API, will be refactored out in near future, avoid to use it. 69 Do(dag *graph.DAG, objOld, objNew client.Object, action *Action, parent *ObjectVertex) *ObjectVertex 70 71 // IsAction tells whether the action of the vertex of this obj is same as 'action'. 72 IsAction(dag *graph.DAG, obj client.Object, action *Action) bool 73 74 // DependOn setups dependencies between 'object' and 'dependencies', 75 // which will guarantee the Write Order to the K8s cluster of these objects. 76 // if multiple vertices exist(which can occur when ForceCreatingVertexOption being used), the one with the largest depth will be used. 77 DependOn(dag *graph.DAG, object client.Object, dependencies ...client.Object) 78 79 // FindAll finds all objects that have same type with obj in the underlying DAG. 80 // obey the GraphOption if provided. 81 FindAll(dag *graph.DAG, obj interface{}, opts ...GraphOption) []client.Object 82 } 83 84 type GraphClient interface { 85 client.Reader 86 GraphWriter 87 } 88 89 // TODO(free6om): make DAG a member of realGraphClient 90 type realGraphClient struct { 91 client.Client 92 } 93 94 func (r *realGraphClient) Root(dag *graph.DAG, objOld, objNew client.Object, action *Action) { 95 var root *ObjectVertex 96 // find root vertex if already exists 97 if len(dag.Vertices()) > 0 { 98 if vertex := r.findMatchedVertex(dag, objNew); vertex != nil { 99 root, _ = vertex.(*ObjectVertex) 100 } 101 } 102 // create one if root vertex not found 103 if root == nil { 104 root = &ObjectVertex{} 105 dag.AddVertex(root) 106 } 107 root.Obj, root.OriObj, root.Action = objNew, objOld, action 108 // setup dependencies 109 for _, vertex := range dag.Vertices() { 110 if vertex != root { 111 dag.Connect(root, vertex) 112 } 113 } 114 } 115 116 func (r *realGraphClient) Create(dag *graph.DAG, obj client.Object, opts ...GraphOption) { 117 r.doWrite(dag, nil, obj, ActionCreatePtr(), opts...) 118 } 119 120 func (r *realGraphClient) Update(dag *graph.DAG, objOld, objNew client.Object, opts ...GraphOption) { 121 r.doWrite(dag, objOld, objNew, ActionUpdatePtr(), opts...) 122 } 123 124 func (r *realGraphClient) Patch(dag *graph.DAG, objOld, objNew client.Object, opts ...GraphOption) { 125 r.doWrite(dag, objOld, objNew, ActionPatchPtr(), opts...) 126 } 127 128 func (r *realGraphClient) Delete(dag *graph.DAG, obj client.Object, opts ...GraphOption) { 129 r.doWrite(dag, nil, obj, ActionDeletePtr(), opts...) 130 } 131 132 func (r *realGraphClient) Status(dag *graph.DAG, objOld, objNew client.Object, opts ...GraphOption) { 133 r.doWrite(dag, objOld, objNew, ActionStatusPtr(), opts...) 134 } 135 136 func (r *realGraphClient) Noop(dag *graph.DAG, obj client.Object, opts ...GraphOption) { 137 r.doWrite(dag, nil, obj, ActionNoopPtr(), opts...) 138 } 139 140 func (r *realGraphClient) Do(dag *graph.DAG, objOld, objNew client.Object, action *Action, parent *ObjectVertex) *ObjectVertex { 141 if dag.Root() == nil { 142 panic(fmt.Sprintf("root vertex not found. obj: %T, name: %s", objNew, objNew.GetName())) 143 } 144 vertex := &ObjectVertex{ 145 OriObj: objOld, 146 Obj: objNew, 147 Action: action, 148 } 149 switch { 150 case parent == nil: 151 dag.AddConnectRoot(vertex) 152 default: 153 dag.AddConnect(parent, vertex) 154 } 155 return vertex 156 } 157 158 func (r *realGraphClient) IsAction(dag *graph.DAG, obj client.Object, action *Action) bool { 159 vertex := r.findMatchedVertex(dag, obj) 160 if vertex == nil { 161 return false 162 } 163 v, _ := vertex.(*ObjectVertex) 164 if action == nil { 165 return v.Action == nil 166 } 167 if v.Action == nil { 168 return false 169 } 170 return *v.Action == *action 171 } 172 173 func (r *realGraphClient) DependOn(dag *graph.DAG, object client.Object, dependency ...client.Object) { 174 objectVertex := r.findMatchedVertex(dag, object) 175 if objectVertex == nil { 176 return 177 } 178 for _, d := range dependency { 179 if d == nil { 180 continue 181 } 182 v := r.findMatchedVertex(dag, d) 183 if v != nil { 184 dag.Connect(objectVertex, v) 185 } 186 } 187 } 188 189 func (r *realGraphClient) FindAll(dag *graph.DAG, obj interface{}, opts ...GraphOption) []client.Object { 190 hasSameType := func() bool { 191 for _, opt := range opts { 192 if opt == HaveDifferentTypeWithOption { 193 return false 194 } 195 } 196 return true 197 }() 198 assignableTo := func(src, dst reflect.Type) bool { 199 if dst == nil { 200 return src == nil 201 } 202 return src.AssignableTo(dst) 203 } 204 objType := reflect.TypeOf(obj) 205 objects := make([]client.Object, 0) 206 for _, vertex := range dag.Vertices() { 207 v, _ := vertex.(*ObjectVertex) 208 vertexType := reflect.TypeOf(v.Obj) 209 if assignableTo(vertexType, objType) == hasSameType { 210 objects = append(objects, v.Obj) 211 } 212 } 213 return objects 214 } 215 216 func (r *realGraphClient) doWrite(dag *graph.DAG, objOld, objNew client.Object, action *Action, opts ...GraphOption) { 217 replaceExisting := func() bool { 218 for _, opt := range opts { 219 if opt == ReplaceIfExistingOption { 220 return true 221 } 222 } 223 return false 224 }() 225 vertex := r.findMatchedVertex(dag, objNew) 226 switch { 227 case vertex != nil: 228 objVertex, _ := vertex.(*ObjectVertex) 229 objVertex.Action = action 230 if replaceExisting { 231 objVertex.Obj = objNew 232 objVertex.OriObj = objOld 233 } 234 default: 235 vertex = &ObjectVertex{ 236 Obj: objNew, 237 OriObj: objOld, 238 Action: action, 239 } 240 dag.AddConnectRoot(vertex) 241 } 242 } 243 244 func (r *realGraphClient) findMatchedVertex(dag *graph.DAG, object client.Object) graph.Vertex { 245 keyLookFor, err := GetGVKName(object) 246 if err != nil { 247 panic(fmt.Sprintf("parse gvk name failed, obj: %T, name: %s, err: %v", object, object.GetName(), err)) 248 } 249 var found graph.Vertex 250 findVertex := func(v graph.Vertex) error { 251 if found != nil { 252 return nil 253 } 254 ov, _ := v.(*ObjectVertex) 255 key, err := GetGVKName(ov.Obj) 256 if err != nil { 257 panic(fmt.Sprintf("parse gvk name failed, obj: %T, name: %s, err: %v", ov.Obj, ov.Obj.GetName(), err)) 258 } 259 if *keyLookFor == *key { 260 found = v 261 } 262 return nil 263 } 264 err = dag.WalkReverseTopoOrder(findVertex, nil) 265 if err != nil { 266 panic(fmt.Sprintf("walk DAG failed, err: %v", err)) 267 } 268 return found 269 } 270 271 var _ GraphClient = &realGraphClient{} 272 273 func NewGraphClient(cli client.Client) GraphClient { 274 return &realGraphClient{ 275 Client: cli, 276 } 277 }