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  }