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 }