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  }