github.com/erda-project/erda-infra@v1.0.9/base/servicehub/hub.go (about)

     1  // Copyright (c) 2021 Terminus, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package servicehub
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"fmt"
    21  	"io"
    22  	"os"
    23  	"reflect"
    24  	"runtime"
    25  	"strconv"
    26  	"strings"
    27  	"sync"
    28  	"syscall"
    29  	"time"
    30  
    31  	"github.com/recallsong/go-utils/errorx"
    32  	"github.com/recallsong/go-utils/os/signalx"
    33  	"github.com/sirupsen/logrus"
    34  	"github.com/spf13/pflag"
    35  
    36  	"github.com/erda-project/erda-infra/base/logs"
    37  	"github.com/erda-project/erda-infra/base/logs/logrusx"
    38  	graph "github.com/erda-project/erda-infra/base/servicehub/dependency-graph"
    39  	"github.com/erda-project/erda-infra/pkg/config"
    40  )
    41  
    42  // Hub .
    43  type Hub struct {
    44  	logger        logs.Logger
    45  	providersMap  map[string][]*providerContext
    46  	providers     []*providerContext
    47  	servicesMap   map[string][]*providerContext
    48  	servicesTypes map[reflect.Type][]*providerContext
    49  	lock          sync.RWMutex
    50  
    51  	started bool
    52  	ctx     context.Context
    53  	cancel  func()
    54  	wg      sync.WaitGroup
    55  
    56  	listeners []Listener
    57  
    58  	// problematic providers
    59  	problematicProviderNames []string
    60  	problemLock              sync.Mutex
    61  }
    62  
    63  // New .
    64  func New(options ...interface{}) *Hub {
    65  	hub := &Hub{}
    66  	hub.ctx, hub.cancel = context.WithCancel(context.Background())
    67  	for _, opt := range options {
    68  		processOptions(hub, opt)
    69  	}
    70  	if hub.logger == nil {
    71  		level := os.Getenv("LOG_LEVEL")
    72  		lvl, err := logrus.ParseLevel(level)
    73  		if err == nil {
    74  			hub.logger = logrusx.New(logrusx.WithLevel(lvl))
    75  		} else {
    76  			hub.logger = logrusx.New()
    77  		}
    78  	}
    79  	return hub
    80  }
    81  
    82  // Init .
    83  func (h *Hub) Init(config map[string]interface{}, flags *pflag.FlagSet, args []string) (err error) {
    84  	defer func() {
    85  		// exp := recover()
    86  		// if exp != nil {
    87  		// 	if e, ok := exp.(error); ok {
    88  		// 		err = e
    89  		// 	} else {
    90  		// 		err = fmt.Errorf("%v", exp)
    91  		// 	}
    92  		// }
    93  		if err != nil {
    94  			h.logger.Errorf("fail to init service hub: %s", err)
    95  		}
    96  	}()
    97  	for i, l := 0, len(h.listeners); i < l; i++ {
    98  		err = h.listeners[i].BeforeInitialization(h, config)
    99  		if err != nil {
   100  			return err
   101  		}
   102  	}
   103  	err = h.loadProviders(config)
   104  	if err != nil {
   105  		return err
   106  	}
   107  
   108  	depGraph, err := h.resolveDependency(h.providersMap)
   109  	if err != nil {
   110  		return fmt.Errorf("fail to resolve dependency: %s", err)
   111  	}
   112  
   113  	flags.BoolP("providers", "p", false, "print all providers supported")
   114  	flags.BoolP("graph", "g", false, "print providers dependency graph")
   115  	for _, ctx := range h.providers {
   116  		err = ctx.BindConfig(flags)
   117  		if err != nil {
   118  			return fmt.Errorf("fail to bind config for provider %s: %s", ctx.name, err)
   119  		}
   120  	}
   121  	err = flags.Parse(args)
   122  	if err != nil {
   123  		return fmt.Errorf("fail to bind flags: %s", err)
   124  	}
   125  	if ok, err := flags.GetBool("providers"); err == nil && ok {
   126  		usage := Usage()
   127  		fmt.Println(usage)
   128  		os.Exit(0)
   129  	}
   130  	if ok, err := flags.GetBool("graph"); err == nil && ok {
   131  		depGraph.Display()
   132  		os.Exit(0)
   133  	}
   134  	for _, ctx := range h.providers {
   135  		h.logger.Infof("provider %s is initializing", ctx.key)
   136  		err = ctx.Init()
   137  		if err != nil {
   138  			return err
   139  		}
   140  		dependencies := ctx.dependencies()
   141  		if len(dependencies) > 0 {
   142  			h.logger.Infof("provider %s (depends %s) initialized", ctx.key, dependencies)
   143  		} else {
   144  			h.logger.Infof("provider %s initialized", ctx.key)
   145  		}
   146  	}
   147  	for i := len(h.listeners) - 1; i >= 0; i-- {
   148  		err = h.listeners[i].AfterInitialization(h)
   149  		if err != nil {
   150  			return err
   151  		}
   152  	}
   153  	return nil
   154  }
   155  
   156  func (h *Hub) resolveDependency(providersMap map[string][]*providerContext) (graph.Graph, error) {
   157  	services := map[string][]*providerContext{}
   158  	types := map[reflect.Type][]*providerContext{}
   159  	for _, p := range providersMap {
   160  		d := p[0].define
   161  		var list []string
   162  		if ps, ok := d.(ProviderServices); ok {
   163  			list = ps.Services()
   164  		} else if ps, ok := d.(ProviderService); ok {
   165  			list = ps.Service()
   166  		}
   167  		for _, s := range list {
   168  			if exist, ok := services[s]; ok {
   169  				return nil, fmt.Errorf("service %s conflict between %s and %s", s, exist[0].name, p[0].name)
   170  			}
   171  			services[s] = p
   172  		}
   173  		if ts, ok := d.(ServiceTypes); ok {
   174  			for _, t := range ts.Types() {
   175  				if exist, ok := types[t]; ok {
   176  					return nil, fmt.Errorf("service type %s conflict between %s and %s", t, exist[0].name, p[0].name)
   177  				}
   178  				types[t] = p
   179  			}
   180  		}
   181  	}
   182  	h.servicesMap = services
   183  	h.servicesTypes = types
   184  	var depGraph graph.Graph
   185  	for name, p := range providersMap {
   186  		providers := map[string]*providerContext{}
   187  		dependsServices, dependsProviders := p[0].Dependencies()
   188  	loop:
   189  		for _, service := range dependsServices {
   190  			name := service
   191  			var label string
   192  			idx := strings.Index(service, "@")
   193  			if idx > 0 {
   194  				name, label = service[0:idx], service[idx+1:]
   195  			}
   196  			if deps, ok := services[name]; ok {
   197  				if len(label) > 0 {
   198  					for _, dep := range deps {
   199  						if dep.label == label {
   200  							providers[dep.name] = dep
   201  							continue loop
   202  						}
   203  					}
   204  				} else if len(deps) > 0 {
   205  					providers[deps[0].name] = deps[0]
   206  					continue loop
   207  				}
   208  			}
   209  			return nil, fmt.Errorf("provider %s depends on service %s, but it not found", p[0].fullName(), service)
   210  		}
   211  		node := graph.NewNode(name)
   212  		for dep := range providers {
   213  			node.Deps = append(node.Deps, dep)
   214  		}
   215  		for _, dep := range dependsProviders {
   216  			if _, ok := providers[dep]; !ok {
   217  				node.Deps = append(node.Deps, dep)
   218  			}
   219  		}
   220  		depGraph = append(depGraph, node)
   221  	}
   222  	resolved, err := graph.Resolve(depGraph)
   223  	if err != nil {
   224  		depGraph.Display()
   225  		return depGraph, err
   226  	}
   227  	var providers []*providerContext
   228  	for _, node := range resolved {
   229  		providers = append(providers, providersMap[node.Name]...)
   230  	}
   231  	h.providers = providers
   232  	return resolved, nil
   233  }
   234  
   235  // StartWithSignal .
   236  func (h *Hub) StartWithSignal() error {
   237  	sigs := []os.Signal{syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT}
   238  	h.logger.Infof("signals to quit: %v", sigs)
   239  	return h.Start(signalx.Notify(sigs...))
   240  }
   241  
   242  // Start .
   243  func (h *Hub) Start(closer ...<-chan os.Signal) (err error) {
   244  	h.lock.Lock()
   245  	ctx := h.ctx
   246  	ch := make(chan error, len(h.providers))
   247  	var num int
   248  	for _, item := range h.providers {
   249  		key := item.key
   250  		if key != item.name {
   251  			key = fmt.Sprintf("%s (%s)", item.key, item.name)
   252  		}
   253  		if runner, ok := item.provider.(ProviderRunner); ok {
   254  			num++
   255  			h.wg.Add(1)
   256  			go func(key string, provider ProviderRunner) {
   257  				h.logger.Infof("provider %s starting ...", key)
   258  				err := provider.Start()
   259  				if err != nil {
   260  					h.logger.Errorf("failed to start provider %s: %s", key, err)
   261  					h.addProblematicProvider(key)
   262  				} else {
   263  					h.logger.Infof("provider %s closed", key)
   264  				}
   265  				h.wg.Done()
   266  				ch <- err
   267  			}(key, runner)
   268  		}
   269  		if runner, ok := item.provider.(ProviderRunnerWithContext); ok {
   270  			num++
   271  			h.wg.Add(1)
   272  			go func(key string, provider ProviderRunnerWithContext) {
   273  				h.logger.Infof("provider %s running ...", key)
   274  				err := provider.Run(ctx)
   275  				if err != nil {
   276  					h.addProblematicProvider(key)
   277  					h.logger.Errorf("failed to run provider %s: %s", key, err)
   278  				} else {
   279  					h.logger.Infof("provider %s Run exit", key)
   280  				}
   281  				h.wg.Done()
   282  				ch <- err
   283  			}(key, runner)
   284  		}
   285  		for i, t := range item.tasks {
   286  			num++
   287  			h.wg.Add(1)
   288  			go func(key string, i int, t task) {
   289  				tname := t.name
   290  				if len(tname) <= 0 {
   291  					tname = strconv.Itoa(i + 1)
   292  				}
   293  				h.logger.Infof("provider %s task(%s) running ...", key, tname)
   294  				err := t.fn(ctx)
   295  				if err != nil {
   296  					h.addProblematicProvider(key)
   297  					h.logger.Errorf("failed to run provider %s task(%s): %s", key, tname, err)
   298  				} else {
   299  					h.logger.Infof("provider %s task(%s) exit", key, tname)
   300  				}
   301  				h.wg.Done()
   302  				ch <- err
   303  			}(key, i, t)
   304  		}
   305  	}
   306  	h.started = true
   307  	h.lock.Unlock()
   308  	runtime.Gosched()
   309  
   310  	for i, l := 0, len(h.listeners); i < l; i++ {
   311  		err = h.listeners[i].AfterStart(h)
   312  		if err != nil {
   313  			return err
   314  		}
   315  	}
   316  
   317  	closeCh, closed := make(chan struct{}), false
   318  	var elock sync.Mutex
   319  	for _, ch := range closer {
   320  		go func(ch <-chan os.Signal) {
   321  			select {
   322  			case signal := <-ch:
   323  				h.logger.Errorf("signal received: %s, hub begin quitting ...\n", signal.String())
   324  			case <-closeCh:
   325  			}
   326  			elock.Lock()
   327  			fmt.Println()
   328  			wait := make(chan error)
   329  			go func() {
   330  				wait <- h.Close()
   331  			}()
   332  			select {
   333  			case <-time.After(30 * time.Second):
   334  				h.logger.Errorf("exit service manager timeout !")
   335  				h.printProblematicProviders()
   336  				os.Exit(1)
   337  			case err := <-wait:
   338  				h.printProblematicProviders()
   339  				if err != nil {
   340  					h.logger.Errorf("fail to exit: %s", err)
   341  					os.Exit(1)
   342  				}
   343  			}
   344  		}(ch)
   345  	}
   346  	// wait to stop
   347  	errs := errorx.Errors{}
   348  	for i := 0; i < num; i++ {
   349  		select {
   350  		case err := <-ch:
   351  			if err != nil {
   352  				errs = append(errs, err)
   353  				if !closed {
   354  					close(closeCh)
   355  					closed = true
   356  				}
   357  			}
   358  		}
   359  	}
   360  	err = errs.MaybeUnwrap()
   361  	for i, l := 0, len(h.listeners); i < l; i++ {
   362  		err = h.listeners[i].BeforeExit(h, err)
   363  	}
   364  	h.printProblematicProviders()
   365  	return err
   366  }
   367  
   368  func (h *Hub) addProblematicProvider(name string) {
   369  	h.problemLock.Lock()
   370  	defer h.problemLock.Unlock()
   371  	h.problematicProviderNames = append(h.problematicProviderNames, name)
   372  }
   373  func (h *Hub) printProblematicProviders() {
   374  	if len(h.problematicProviderNames) == 0 {
   375  		return
   376  	}
   377  	h.logger.Errorf("problematic providers: %v", h.problematicProviderNames)
   378  }
   379  
   380  // Close .
   381  func (h *Hub) Close() error {
   382  	h.lock.Lock()
   383  	if !h.started {
   384  		h.lock.Unlock()
   385  		return nil
   386  	}
   387  	var errs errorx.Errors
   388  	for i := len(h.providers) - 1; i >= 0; i-- {
   389  		if runner, ok := h.providers[i].provider.(ProviderRunner); ok {
   390  			err := runner.Close()
   391  			if err != nil {
   392  				errs = append(errs, err)
   393  			}
   394  		}
   395  	}
   396  	h.cancel()
   397  	h.wg.Wait()
   398  	h.started = false
   399  	h.ctx, h.cancel = context.WithCancel(context.Background())
   400  	h.lock.Unlock()
   401  	return errs.MaybeUnwrap()
   402  }
   403  
   404  // ForeachServices .
   405  func (h *Hub) ForeachServices(fn func(service string) bool) {
   406  	for key := range h.servicesMap {
   407  		if !fn(key) {
   408  			return
   409  		}
   410  	}
   411  }
   412  
   413  // IsServiceExist .
   414  func (h *Hub) IsServiceExist(service string) bool {
   415  	return len(h.servicesMap[service]) > 0
   416  }
   417  
   418  // Service .
   419  func (h *Hub) Service(name string, options ...interface{}) interface{} {
   420  	return h.getService(newDependencyContext(
   421  		name,
   422  		"",
   423  		nil,
   424  		reflect.StructTag(""),
   425  	), options...)
   426  }
   427  
   428  func (h *Hub) getService(dc DependencyContext, options ...interface{}) (instance interface{}) {
   429  	var pc *providerContext
   430  	if len(dc.Service()) > 0 {
   431  		if providers, ok := h.servicesMap[dc.Service()]; ok {
   432  			if len(providers) > 0 {
   433  				if len(dc.Label()) > 0 {
   434  					for _, item := range providers {
   435  						if item.label == dc.Label() {
   436  							pc = item
   437  							break
   438  						}
   439  					}
   440  				} else {
   441  					for _, item := range providers {
   442  						if item.key == item.name {
   443  							pc = item
   444  							break
   445  						}
   446  					}
   447  					if pc == nil && len(providers) > 0 {
   448  						pc = providers[0]
   449  					}
   450  				}
   451  			}
   452  		}
   453  	} else if dc.Type() != nil {
   454  		providers := h.servicesTypes[dc.Type()]
   455  		for _, item := range providers {
   456  			if item.key == item.name {
   457  				pc = item
   458  				break
   459  			}
   460  		}
   461  		if pc == nil && len(providers) > 0 {
   462  			pc = providers[0]
   463  		}
   464  	}
   465  	if pc != nil {
   466  		provider := pc.provider
   467  		if prod, ok := provider.(DependencyProvider); ok {
   468  			return prod.Provide(dc, options...)
   469  		}
   470  		return provider
   471  	}
   472  	return nil
   473  }
   474  
   475  // Provider .
   476  func (h *Hub) Provider(name string) interface{} {
   477  	var label string
   478  	idx := strings.Index(name, "@")
   479  	if idx > 0 {
   480  		label = name[idx+1:]
   481  		name = name[0:idx]
   482  	}
   483  	ps := h.providersMap[name]
   484  	if len(label) > 0 {
   485  		for _, p := range ps {
   486  			if p.label == label {
   487  				return p.provider
   488  			}
   489  		}
   490  	} else if len(ps) > 0 {
   491  		return ps[0].provider
   492  	}
   493  	return nil
   494  }
   495  
   496  // RunOptions .
   497  type RunOptions struct {
   498  	Name       string
   499  	ConfigFile string
   500  	Content    interface{}
   501  	Format     string
   502  	Args       []string
   503  }
   504  
   505  // RunWithOptions .
   506  func (h *Hub) RunWithOptions(opts *RunOptions) {
   507  	name := opts.Name
   508  	if len(name) <= 0 {
   509  		name = getAppName(opts.Args...)
   510  	}
   511  	config.LoadEnvFile()
   512  
   513  	var err error
   514  	var start bool
   515  	defer func() {
   516  		if !start {
   517  			for i, l := 0, len(h.listeners); i < l; i++ {
   518  				err = h.listeners[i].BeforeExit(h, err)
   519  			}
   520  		}
   521  		if err != nil {
   522  			os.Exit(1)
   523  		}
   524  	}()
   525  
   526  	format := "yaml"
   527  	if len(opts.Format) > 0 {
   528  		format = opts.Format
   529  	}
   530  	cfgmap := make(map[string]interface{})
   531  	if opts.Content != nil {
   532  		var reader io.Reader
   533  		switch val := opts.Content.(type) {
   534  		case map[string]interface{}:
   535  			cfgmap = val
   536  		case string:
   537  			reader = strings.NewReader(val)
   538  		case []byte:
   539  			reader = bytes.NewReader(val)
   540  		default:
   541  			err = fmt.Errorf("invalid config content type")
   542  			h.logger.Error(err)
   543  			return
   544  		}
   545  		if reader != nil {
   546  			err = config.UnmarshalToMap(reader, format, cfgmap)
   547  			if err != nil {
   548  				h.logger.Errorf("fail to parse %s config: %s", format, err)
   549  				return
   550  			}
   551  		}
   552  	}
   553  
   554  	cfgfile := opts.ConfigFile
   555  	if len(cfgmap) <= 0 && len(opts.ConfigFile) <= 0 {
   556  		cfgfile = name + "." + format
   557  	}
   558  	cfgmap, err = h.loadConfigWithArgs(cfgfile, cfgmap, opts.Args...)
   559  	if err != nil {
   560  		return
   561  	}
   562  
   563  	flags := pflag.NewFlagSet(name, pflag.ExitOnError)
   564  	flags.StringP("config", "c", cfgfile, "config file to load providers")
   565  	err = h.Init(cfgmap, flags, opts.Args)
   566  	if err != nil {
   567  		return
   568  	}
   569  	defer h.Close()
   570  	start = true
   571  	err = h.StartWithSignal()
   572  	if err != nil {
   573  		return
   574  	}
   575  }
   576  
   577  // Run .
   578  func (h *Hub) Run(name, cfgfile string, args ...string) {
   579  	h.RunWithOptions(&RunOptions{
   580  		Name:       name,
   581  		ConfigFile: cfgfile,
   582  		Args:       args,
   583  	})
   584  }
   585  
   586  // Run .
   587  func Run(opts *RunOptions) *Hub {
   588  	hub := New()
   589  	hub.RunWithOptions(opts)
   590  	return hub
   591  }
   592  
   593  func getAppName(args ...string) string {
   594  	if len(args) <= 0 {
   595  		return ""
   596  	}
   597  	name := args[0]
   598  	idx := strings.LastIndex(os.Args[0], "/")
   599  	if idx >= 0 {
   600  		return name[idx+1:]
   601  	}
   602  	return ""
   603  }