github.com/GuanceCloud/cliutils@v1.1.21/pipeline/ptinput/refertable/runner.go (about)

     1  // Unless explicitly stated otherwise all files in this repository are licensed
     2  // under the MIT License.
     3  // This product includes software developed at Guance Cloud (https://www.guance.com/).
     4  // Copyright 2021-present Guance, Inc.
     5  
     6  package refertable
     7  
     8  import (
     9  	"context"
    10  	"database/sql"
    11  	"fmt"
    12  	"io"
    13  	"net"
    14  	"net/http"
    15  	"net/url"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/GuanceCloud/cliutils/logger"
    20  	"github.com/hashicorp/go-retryablehttp"
    21  )
    22  
    23  // _plReferTables PlReferTables
    24  // _runner        = &Runner{
    25  // 	initFinished: make(chan struct{}),
    26  // }
    27  
    28  var l = logger.DefaultSLogger("refer-table")
    29  
    30  const (
    31  	SchemeHTTP  = "http"
    32  	SchemeHTTPS = "https"
    33  
    34  	PullDuration = time.Second
    35  )
    36  
    37  // func QueryReferTable(referTb PlReferTables, tableName string, colName []string, colValue []any,
    38  // 	selected []string,
    39  // ) (map[string]any, bool) {
    40  // 	defer func() {
    41  // 		if err := recover(); err != nil {
    42  // 			l.Error(fmt.Errorf("run pl: %s", err))
    43  // 		}
    44  // 	}()
    45  
    46  // 	if referTb == nil {
    47  // 		return nil, false
    48  // 	}
    49  
    50  // 	return referTb.query(tableName, colName, colValue, selected)
    51  // }
    52  
    53  // func InitFinished(interval time.Duration) bool {
    54  // 	return _runner.InitFinished(interval)
    55  // }
    56  
    57  func InitLog() {
    58  	l = logger.SLogger("refer-table")
    59  }
    60  
    61  type InConfig struct {
    62  	URL      string        `toml:"url"`
    63  	Interval time.Duration `toml:"interval"`
    64  }
    65  
    66  type RefTbCfg struct {
    67  	// table data pull config
    68  	URL      string
    69  	Interval time.Duration
    70  
    71  	// table store config
    72  	UseSQLite     bool
    73  	SQLiteMemMode bool
    74  	DBPath        string
    75  }
    76  
    77  type ReferTable struct {
    78  	inConfig     InConfig
    79  	cli          *retryablehttp.Client
    80  	initFinished chan struct{}
    81  	tables       PlReferTables
    82  }
    83  
    84  func NewReferTable(cfg RefTbCfg) (*ReferTable, error) {
    85  	ref := &ReferTable{
    86  		initFinished: make(chan struct{}),
    87  	}
    88  
    89  	if cfg.URL == "" {
    90  		return nil, fmt.Errorf("empty url")
    91  	}
    92  
    93  	if cfg.UseSQLite {
    94  		if cfg.SQLiteMemMode {
    95  			l.Infof("using in-memory SQLite for refer-table")
    96  			d, err := sql.Open("sqlite", ":memory:")
    97  			if err != nil {
    98  				return nil, fmt.Errorf("open in-memory SQLite failed: %w", err)
    99  			}
   100  			ref.tables = &PlReferTablesSqlite{db: d}
   101  		} else {
   102  			l.Infof("using on-disk SQLite for refer-table")
   103  			d, err := sql.Open("sqlite", cfg.DBPath)
   104  			if err != nil {
   105  				return nil, fmt.Errorf("open SQLite at %s failed: %w", cfg.DBPath, err)
   106  			}
   107  			ref.tables = &PlReferTablesSqlite{db: d}
   108  		}
   109  	} else {
   110  		l.Infof("using memory mode for refer-table")
   111  		ref.tables = &PlReferTablesInMemory{}
   112  	}
   113  
   114  	if cfg.Interval < PullDuration {
   115  		cfg.Interval = PullDuration
   116  	}
   117  
   118  	ref.inConfig.Interval = cfg.Interval
   119  	ref.inConfig.URL = cfg.URL
   120  
   121  	scheme, err := checkURL(ref.inConfig.URL)
   122  	if err != nil {
   123  		l.Error(err)
   124  		return nil, err
   125  	}
   126  
   127  	switch scheme {
   128  	case SchemeHTTP, SchemeHTTPS:
   129  		cli := http.Client{
   130  			Transport: &http.Transport{
   131  				DialContext: (&net.Dialer{
   132  					Timeout:   time.Second * 30,
   133  					KeepAlive: time.Second * 90,
   134  				}).DialContext,
   135  				MaxIdleConns:          100,
   136  				MaxConnsPerHost:       64,
   137  				IdleConnTimeout:       time.Second * 90,
   138  				TLSHandshakeTimeout:   time.Second * 10,
   139  				ExpectContinueTimeout: time.Second,
   140  			},
   141  		}
   142  		ref.cli = newRetryCli(&cli, time.Minute)
   143  	}
   144  
   145  	return ref, nil
   146  }
   147  
   148  func checkURL(tableURL string) (string, error) {
   149  	u, err := url.Parse(tableURL)
   150  	if err != nil {
   151  		return "", fmt.Errorf("parse url: %s, error: %w",
   152  			tableURL, err)
   153  	}
   154  	scheme := strings.ToLower(u.Scheme)
   155  	switch scheme {
   156  	case SchemeHTTP, SchemeHTTPS:
   157  	default:
   158  		return "", fmt.Errorf("url: %s, unsupported scheme %s",
   159  			tableURL, scheme)
   160  	}
   161  	return scheme, nil
   162  }
   163  
   164  // InitFinished used to check init status.
   165  func (refT *ReferTable) InitFinished(waitTime time.Duration) bool {
   166  	ticker := time.NewTicker(waitTime)
   167  
   168  	if refT.initFinished == nil {
   169  		return false
   170  	}
   171  
   172  	select {
   173  	case <-refT.initFinished:
   174  		return true
   175  	case <-ticker.C:
   176  		return false
   177  	}
   178  }
   179  
   180  func (refT *ReferTable) Tables() PlReferTables {
   181  	return refT.tables
   182  }
   183  
   184  func (refT *ReferTable) PullWorker(ctx context.Context) {
   185  	ticker := time.NewTicker(refT.inConfig.Interval)
   186  	for {
   187  		if err := refT.getAndUpdate(); err != nil {
   188  			l.Error(err)
   189  		}
   190  		select {
   191  		case <-ticker.C:
   192  		case <-ctx.Done():
   193  			return
   194  		}
   195  	}
   196  }
   197  
   198  func (refT *ReferTable) getAndUpdate() error {
   199  	if tables, err := httpGet(refT.cli, refT.inConfig.URL); err != nil {
   200  		return fmt.Errorf("get table data from URL: %w", err)
   201  	} else {
   202  		if refT.tables == nil {
   203  			return nil
   204  		}
   205  		if err := refT.tables.updateAll(tables); err != nil {
   206  			l.Errorf("failed to update tables: %w", err)
   207  		}
   208  	}
   209  
   210  	select {
   211  	case <-refT.initFinished:
   212  	default:
   213  		if refT.initFinished != nil {
   214  			close(refT.initFinished)
   215  		}
   216  	}
   217  	return nil
   218  }
   219  
   220  func httpGet(cli *retryablehttp.Client, url string) ([]referTable, error) {
   221  	resp, err := cli.Get(url)
   222  	defer func() { _ = resp.Body.Close() }()
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  
   227  	if resp.StatusCode != http.StatusOK {
   228  		return nil, fmt.Errorf("url: %s, status: %s", url, resp.Status)
   229  	}
   230  
   231  	data, err := io.ReadAll(resp.Body)
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  
   236  	tables, err := decodeJSONData(data)
   237  	if err != nil {
   238  		return nil, err
   239  	}
   240  
   241  	return tables, nil
   242  }