github.com/projecteru2/core@v0.0.0-20240321043226-06bcc1c23f58/engine/factory/factory.go (about) 1 package factory 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 "sync" 8 "time" 9 10 "github.com/alphadose/haxmap" 11 "github.com/cockroachdb/errors" 12 "github.com/panjf2000/ants/v2" 13 14 "github.com/projecteru2/core/engine" 15 "github.com/projecteru2/core/engine/docker" 16 "github.com/projecteru2/core/engine/fake" 17 "github.com/projecteru2/core/engine/mocks/fakeengine" 18 "github.com/projecteru2/core/engine/systemd" 19 "github.com/projecteru2/core/engine/virt" 20 "github.com/projecteru2/core/log" 21 "github.com/projecteru2/core/store" 22 "github.com/projecteru2/core/types" 23 "github.com/projecteru2/core/utils" 24 ) 25 26 type factory func(ctx context.Context, config types.Config, nodename, endpoint, ca, cert, key string) (engine.API, error) 27 28 var ( 29 engines = map[string]factory{ 30 docker.TCPPrefixKey: docker.MakeClient, 31 docker.SockPrefixKey: docker.MakeClient, 32 virt.GRPCPrefixKey: virt.MakeClient, 33 systemd.TCPPrefix: systemd.MakeClient, 34 fakeengine.PrefixKey: fakeengine.MakeClient, 35 } 36 engineCache *EngineCache 37 ) 38 39 // EngineCache . 40 type EngineCache struct { 41 cache *utils.EngineCache 42 keysToCheck *haxmap.Map[string, engineParams] 43 pool *ants.PoolWithFunc 44 config types.Config 45 stor store.Store 46 } 47 48 // NewEngineCache . 49 func NewEngineCache(config types.Config, stor store.Store) *EngineCache { 50 pool, _ := utils.NewPool(config.MaxConcurrency) 51 return &EngineCache{ 52 cache: utils.NewEngineCache(12*time.Hour, 10*time.Minute), 53 keysToCheck: haxmap.New[string, engineParams](), 54 pool: pool, 55 config: config, 56 stor: stor, 57 } 58 } 59 60 // InitEngineCache init engine cache and start engine cache checker 61 func InitEngineCache(ctx context.Context, config types.Config, stor store.Store) { 62 engineCache = NewEngineCache(config, stor) 63 // init the cache, we don't care the return values 64 if stor != nil { 65 _, _ = engineCache.stor.GetNodesByPod(ctx, &types.NodeFilter{ 66 All: true, 67 }) 68 } 69 go engineCache.CheckAlive(ctx) 70 go engineCache.CheckNodeStatus(ctx) 71 } 72 73 // Get . 74 func (e *EngineCache) Get(key string) engine.API { 75 return e.cache.Get(key) 76 } 77 78 // Set . 79 func (e *EngineCache) Set(params engineParams, client engine.API) { 80 e.cache.Set(params.getCacheKey(), client) 81 e.keysToCheck.Set(params.getCacheKey(), params) 82 } 83 84 // Delete . 85 func (e *EngineCache) Delete(key string) { 86 e.cache.Delete(key) 87 } 88 89 // CheckAlive checks if the engine in cache is available 90 func (e *EngineCache) CheckAlive(ctx context.Context) { 91 logger := log.WithFunc("engine.factory.CheckAlive") 92 logger.Info(ctx, "check alive starts") 93 defer logger.Info(ctx, "check alive ends") 94 defer e.pool.Release() 95 for { 96 select { 97 case <-ctx.Done(): 98 return 99 default: 100 } 101 102 paramsChan := make(chan engineParams) 103 go func() { 104 e.keysToCheck.ForEach(func(_ string, v engineParams) bool { 105 paramsChan <- v 106 return true 107 }) 108 close(paramsChan) 109 }() 110 111 wg := &sync.WaitGroup{} 112 for params := range paramsChan { 113 wg.Add(1) 114 params := params 115 _ = e.pool.Invoke(func() { 116 defer wg.Done() 117 cacheKey := params.getCacheKey() 118 client := e.cache.Get(cacheKey) 119 if client == nil { 120 e.cache.Delete(params.getCacheKey()) 121 e.keysToCheck.Del(cacheKey) 122 return 123 } 124 if _, ok := client.(*fake.EngineWithErr); ok { 125 if newClient, err := newEngine(ctx, e.config, params.nodename, params.endpoint, params.ca, params.key, params.cert); err != nil { 126 logger.Errorf(ctx, err, "engine %+v is still unavailable", cacheKey) 127 e.cache.Set(cacheKey, &fake.EngineWithErr{DefaultErr: err}) 128 // check node status 129 e.checkOneNodeStatus(ctx, ¶ms) 130 } else { 131 e.cache.Set(cacheKey, newClient) 132 } 133 return 134 } 135 if err := validateEngine(ctx, client, e.config.ConnectionTimeout); err != nil { 136 logger.Errorf(ctx, err, "engine %+v is unavailable, will be replaced and removed", cacheKey) 137 e.cache.Set(cacheKey, &fake.EngineWithErr{DefaultErr: err}) 138 } 139 logger.Debugf(ctx, "engine %+v is available", cacheKey) 140 }) 141 } 142 wg.Wait() 143 time.Sleep(e.config.ConnectionTimeout) 144 } 145 } 146 147 func (e *EngineCache) CheckNodeStatus(ctx context.Context) { 148 logger := log.WithFunc("engine.factory.CheckNodeStatus") 149 logger.Info(ctx, "check NodeStatus starts") 150 defer logger.Info(ctx, "check NodeStatus ends") 151 if e.stor == nil { 152 logger.Warnf(ctx, "nodeStore is nil") 153 return 154 } 155 for { 156 select { 157 case <-ctx.Done(): 158 return 159 default: 160 } 161 ch := e.stor.NodeStatusStream(ctx) 162 163 for ns := range ch { 164 if ns.Alive { 165 // GetNode will call GetEngine, so GetNode updates the engine cache automatically 166 if _, err := e.stor.GetNode(ctx, ns.Nodename); err != nil { 167 logger.Warnf(ctx, "failed to get node %s: %s", ns.Nodename, err) 168 } 169 continue 170 } 171 // a node may have multiple engines, so we need check all key here 172 e.keysToCheck.ForEach(func(_ string, ep engineParams) bool { 173 if ep.nodename == ns.Nodename { 174 logger.Infof(ctx, "remove engine %+v from cache", ep.getCacheKey()) 175 RemoveEngineFromCache(ctx, ep.endpoint, ep.ca, ep.cert, ep.key) 176 } 177 return true 178 }) 179 } 180 } 181 } 182 183 // GetEngineFromCache . 184 func GetEngineFromCache(_ context.Context, endpoint, ca, cert, key string) engine.API { 185 return engineCache.Get(getEngineCacheKey(endpoint, ca, cert, key)) 186 } 187 188 // RemoveEngineFromCache . 189 func RemoveEngineFromCache(ctx context.Context, endpoint, ca, cert, key string) { 190 cacheKey := getEngineCacheKey(endpoint, ca, cert, key) 191 log.WithFunc("engine.factory.RemoveEngineFromCache").Infof(ctx, "remove engine %+v from cache", cacheKey) 192 engineCache.Delete(cacheKey) 193 } 194 195 // GetEngine get engine with cache 196 func GetEngine(ctx context.Context, config types.Config, nodename, endpoint, ca, cert, key string) (client engine.API, err error) { 197 logger := log.WithFunc("engine.factory.GetEngine") 198 if client = GetEngineFromCache(ctx, endpoint, ca, cert, key); client != nil { 199 return client, nil 200 } 201 202 defer func() { 203 params := engineParams{ 204 nodename: nodename, 205 endpoint: endpoint, 206 ca: ca, 207 cert: cert, 208 key: key, 209 } 210 cacheKey := params.getCacheKey() 211 if err == nil { 212 engineCache.Set(params, client) 213 logger.Infof(ctx, "store engine %+v in cache", cacheKey) 214 } else { 215 engineCache.Set(params, &fake.EngineWithErr{DefaultErr: err}) 216 logger.Infof(ctx, "store fake engine %+v in cache", cacheKey) 217 } 218 }() 219 220 return newEngine(ctx, config, nodename, endpoint, ca, cert, key) 221 } 222 223 type engineParams struct { 224 nodename string 225 endpoint string 226 ca string 227 cert string 228 key string 229 } 230 231 func (ep engineParams) getCacheKey() string { 232 return getEngineCacheKey(ep.endpoint, ep.ca, ep.cert, ep.key) 233 } 234 235 func validateEngine(ctx context.Context, engine engine.API, timeout time.Duration) (err error) { 236 utils.WithTimeout(ctx, timeout, func(ctx context.Context) { 237 err = engine.Ping(ctx) 238 }) 239 return err 240 } 241 242 func getEnginePrefix(endpoint string) (string, error) { 243 for prefix := range engines { 244 if strings.HasPrefix(endpoint, prefix) { 245 return prefix, nil 246 } 247 } 248 return "", errors.Wrapf(types.ErrInvaildNodeEndpoint, "endpoint invalid %+v", endpoint) 249 } 250 251 func getEngineCacheKey(endpoint, ca, cert, key string) string { 252 return fmt.Sprintf("%+v-%+v", endpoint, utils.SHA256(fmt.Sprintf(":%+v:%+v:%+v", ca, cert, key))[:8]) 253 } 254 255 // newEngine get engine 256 func newEngine(ctx context.Context, config types.Config, nodename, endpoint, ca, cert, key string) (client engine.API, err error) { 257 prefix, err := getEnginePrefix(endpoint) 258 if err != nil { 259 return nil, err 260 } 261 e, ok := engines[prefix] 262 if !ok { 263 return nil, types.ErrInvaildEngineEndpoint 264 } 265 utils.WithTimeout(ctx, config.ConnectionTimeout, func(ctx context.Context) { 266 client, err = e(ctx, config, nodename, endpoint, ca, cert, key) 267 }) 268 if err != nil { 269 return nil, err 270 } 271 if err = validateEngine(ctx, client, config.ConnectionTimeout); err != nil { 272 log.WithFunc("engine.factory.newEngine").Errorf(ctx, err, "engine of %+v is unavailable", endpoint) 273 return nil, err 274 } 275 return client, nil 276 } 277 278 func (e *EngineCache) checkOneNodeStatus(ctx context.Context, params *engineParams) { 279 if e.stor == nil { 280 return 281 } 282 logger := log.WithFunc("engine.factory.checkOneNodeStatus") 283 nodename := params.nodename 284 cacheKey := params.getCacheKey() 285 if ns, err := e.stor.GetNodeStatus(ctx, nodename); (err != nil && errors.Is(err, types.ErrInvaildCount)) || (!ns.Alive) { 286 logger.Warnf(ctx, "node %s is offline, the cache will be removed", nodename) 287 e.Delete(cacheKey) 288 } 289 }