github.com/alibaba/ilogtail/pkg@v0.0.0-20250526110833-c53b480d046c/helper/platformmeta/aliyun_ecs.go (about) 1 // Copyright 2023 iLogtail Authors 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 platformmeta 16 17 import ( 18 "context" 19 "errors" 20 "io" 21 "net/http" 22 "strconv" 23 "strings" 24 "sync" 25 "time" 26 27 "github.com/alibaba/ilogtail/pkg/logger" 28 "github.com/alibaba/ilogtail/pkg/util" 29 ) 30 31 // global var 32 var ( 33 error404 = errors.New("404") 34 ) 35 36 func AlibabaCloudEcsPlatformRequest(api string, method string, f func(header *http.Header)) (string, error) { 37 r, _ := http.NewRequest(http.MethodPut, "http://100.100.100.200/latest"+api, nil) 38 r.Method = method 39 f(&r.Header) 40 c := new(http.Client) 41 c.Timeout = time.Second 42 resp, err := c.Do(r) 43 if err != nil { 44 return "", err 45 } 46 defer func() { 47 _ = resp.Body.Close() 48 }() 49 logger.Debug(context.Background(), "api", r.URL.Path) 50 if resp.StatusCode == 404 { 51 return "", error404 52 } 53 bytes, err := io.ReadAll(resp.Body) 54 if err != nil { 55 return "", err 56 } 57 return string(bytes), nil 58 } 59 60 func AlibabaCloudEcsPlatformReadMetaVal(api string, token string) (val string, err error) { 61 for i := 0; i < 2; i++ { 62 val, err = AlibabaCloudEcsPlatformRequest(api, http.MethodGet, func(header *http.Header) { 63 (*header)["X-aliyun-ecs-metadata-token"] = []string{token} 64 }) 65 if err == nil || err == error404 { 66 return 67 } 68 } 69 return 70 } 71 72 type tag struct { 73 k string 74 v string 75 } 76 type Data struct { 77 // unchanged meta 78 id string 79 region string 80 zone string 81 instanceType string 82 imageID string 83 84 // dynamic changed meta 85 name string 86 tags map[string]string 87 maxNetEngress int64 88 maxNetIngress int64 89 vpcID string 90 vswitchID string 91 } 92 93 type ECSManager struct { 94 mutex sync.RWMutex 95 data Data 96 ecsToken string 97 ecsLastFetchTokenTime time.Time 98 ecsMinimumFetchInterval time.Duration 99 ecsTokenExpireTime int 100 fetchRes bool 101 once sync.Once 102 unchangedAlreadyRead bool 103 resChan chan bool 104 } 105 106 func (m *ECSManager) fetchToken() (err error) { 107 var val string 108 for i := 0; i < 2; i++ { 109 val, err = AlibabaCloudEcsPlatformRequest("/api/token", http.MethodPut, func(header *http.Header) { 110 (*header)["X-aliyun-ecs-metadata-token-ttl-seconds"] = []string{strconv.Itoa(m.ecsTokenExpireTime)} 111 }) 112 if err == nil { 113 break 114 } 115 } 116 if err != nil { 117 return err 118 } 119 m.ecsToken = val 120 return nil 121 } 122 123 func (m *ECSManager) startFetch() { 124 m.once.Do(func() { 125 m.fetchAPI() 126 go func() { 127 for range time.NewTicker(m.ecsMinimumFetchInterval).C { 128 m.mutex.Lock() 129 m.fetchAPI() 130 m.mutex.Unlock() 131 } 132 }() 133 }) 134 } 135 136 func (m *ECSManager) fetchAPI() { 137 defer func() { 138 logger.Debug(context.Background(), "fetch ecs meta api res", m.fetchRes) 139 }() 140 now := time.Now() 141 if now.Sub(m.ecsLastFetchTokenTime).Seconds() > float64(m.ecsTokenExpireTime)*3/4 { 142 if err := m.fetchToken(); err != nil { 143 logger.Error(context.Background(), "ECS_ALARM", "read token error", err) 144 return 145 } 146 m.ecsLastFetchTokenTime = now 147 } 148 149 for k := range m.data.tags { 150 delete(m.data.tags, k) 151 } 152 153 asyncCount := 0 154 asyncReadMetaFunc := func(api string, key string, configFunc func(key, val string)) { 155 asyncCount++ 156 go func() { 157 val, err := AlibabaCloudEcsPlatformReadMetaVal(api, m.ecsToken) 158 if err != nil && err != error404 { 159 logger.Error(context.Background(), "ECS_ALARM", "read meta error", err) 160 m.resChan <- false 161 return 162 } 163 configFunc(key, val) 164 m.resChan <- true 165 }() 166 167 } 168 success := true 169 170 if !m.unchangedAlreadyRead { 171 asyncReadMetaFunc("/meta-data/instance-id", "", func(key, val string) { m.data.id = val }) 172 asyncReadMetaFunc("/meta-data/region-id", "", func(key, val string) { m.data.region = val }) 173 asyncReadMetaFunc("/meta-data/zone-id", "", func(key, val string) { m.data.zone = val }) 174 asyncReadMetaFunc("/meta-data/image-id", "", func(key, val string) { m.data.imageID = val }) 175 asyncReadMetaFunc("/meta-data/instance/instance-type", "", func(key, val string) { m.data.instanceType = val }) 176 for i := 0; i < asyncCount; i++ { 177 ok := <-m.resChan 178 success = success && ok 179 } 180 asyncCount = 0 181 } 182 if !success { 183 return 184 } 185 m.unchangedAlreadyRead = true 186 var tags string 187 asyncReadMetaFunc("/meta-data/instance/max-netbw-egress", "", func(key, val string) { m.data.maxNetEngress, _ = strconv.ParseInt(val, 10, 64) }) 188 asyncReadMetaFunc("/meta-data/instance/max-netbw-ingress", "", func(key, val string) { m.data.maxNetIngress, _ = strconv.ParseInt(val, 10, 64) }) 189 asyncReadMetaFunc("/meta-data/instance/instance-name", "", func(key, val string) { m.data.name = val }) 190 asyncReadMetaFunc("/meta-data/vswitch-id", "", func(key, val string) { m.data.vswitchID = val }) 191 asyncReadMetaFunc("/meta-data/vpc-id", "", func(key, val string) { m.data.vpcID = val }) 192 asyncReadMetaFunc("/meta-data/tags/instance/", "", func(key, val string) { tags = val }) 193 for i := 0; i < asyncCount; i++ { 194 ok := <-m.resChan 195 success = success && ok 196 } 197 asyncCount = 0 198 if success && tags != "" { 199 keys := strings.Split(tags, "\n") 200 num := 0 201 for i, key := range keys { 202 key = strings.TrimSpace(key) 203 if key == "" { 204 continue 205 } 206 keys[num] = keys[i] 207 num++ 208 } 209 keys = keys[:num] 210 res := make(chan *tag, len(keys)) 211 for _, key := range keys { 212 asyncReadMetaFunc("/meta-data/tags/instance/"+key, key, func(key, val string) { 213 res <- &tag{ 214 k: key, 215 v: val, 216 } 217 }) 218 } 219 for i := 0; i < len(keys); i++ { 220 <-m.resChan 221 } 222 count := len(res) 223 for i := 0; i < count; i++ { 224 t := <-res 225 m.data.tags[t.k] = t.v 226 } 227 } 228 m.fetchRes = true 229 } 230 231 func (m *ECSManager) StartCollect() { 232 m.startFetch() 233 } 234 235 func (m *ECSManager) GetInstanceID() string { 236 if !m.fetchRes { 237 return "" 238 } 239 return m.data.id 240 } 241 242 func (m *ECSManager) GetInstanceImageID() string { 243 if !m.fetchRes { 244 return "" 245 } 246 return m.data.imageID 247 } 248 249 func (m *ECSManager) GetInstanceRegion() string { 250 if !m.fetchRes { 251 return "" 252 } 253 return m.data.region 254 } 255 256 func (m *ECSManager) GetInstanceZone() string { 257 if !m.fetchRes { 258 return "" 259 } 260 return m.data.zone 261 } 262 263 func (m *ECSManager) GetInstanceType() string { 264 if !m.fetchRes { 265 return "" 266 } 267 return m.data.instanceType 268 } 269 270 func (m *ECSManager) GetInstanceName() string { 271 m.mutex.RLock() 272 defer m.mutex.RUnlock() 273 if !m.fetchRes { 274 return "" 275 } 276 return m.data.name 277 } 278 279 func (m *ECSManager) GetInstanceMaxNetEgress() int64 { 280 m.mutex.RLock() 281 defer m.mutex.RUnlock() 282 if !m.fetchRes { 283 return -1 284 } 285 return m.data.maxNetEngress 286 } 287 288 func (m *ECSManager) GetInstanceMaxNetIngress() int64 { 289 m.mutex.RLock() 290 defer m.mutex.RUnlock() 291 if !m.fetchRes { 292 return -1 293 } 294 return m.data.maxNetIngress 295 } 296 297 func (m *ECSManager) GetInstanceVpcID() string { 298 m.mutex.RLock() 299 defer m.mutex.RUnlock() 300 if !m.fetchRes { 301 return "" 302 } 303 return m.data.vpcID 304 } 305 306 func (m *ECSManager) GetInstanceVswitchID() string { 307 m.mutex.RLock() 308 defer m.mutex.RUnlock() 309 if !m.fetchRes { 310 return "" 311 } 312 return m.data.vswitchID 313 } 314 315 func (m *ECSManager) GetInstanceTags() map[string]string { 316 m.mutex.RLock() 317 defer m.mutex.RUnlock() 318 if !m.fetchRes { 319 return map[string]string{} 320 } 321 res := make(map[string]string) 322 for k, v := range m.data.tags { 323 res[k] = v 324 } 325 return res 326 } 327 328 func (m *ECSManager) Ping() bool { 329 _, err := AlibabaCloudEcsPlatformRequest("/meta-data/instance-id", http.MethodGet, func(header *http.Header) { 330 }) 331 if err != nil && strings.Contains(err.Error(), "Timeout") { 332 return false 333 } 334 return true 335 } 336 337 func initAliyun() { 338 e := &ECSManager{ 339 data: Data{ 340 tags: map[string]string{}, 341 }, 342 resChan: make(chan bool, 30), 343 } 344 var val int 345 _ = util.InitFromEnvInt("ALIYUN_ECS_MINIMUM_REFLUSH_INTERVAL", &val, 30) 346 e.ecsMinimumFetchInterval = time.Second * time.Duration(val) 347 _ = util.InitFromEnvInt("ALIYUN_ECS_TOKEN_EXPIRE_TIME", &e.ecsTokenExpireTime, 300) 348 register[Aliyun] = e 349 }