github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/core/quality/mysterium_morqa.go (about)

     1  /*
     2   * Copyright (C) 2018 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License as published by
     6   * the Free Software Foundation, either version 3 of the License, or
     7   * (at your option) any later version.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package quality
    19  
    20  import (
    21  	"bytes"
    22  	"encoding/json"
    23  	"fmt"
    24  	"io"
    25  	"net/http"
    26  	"sync"
    27  	"time"
    28  
    29  	gocache "github.com/patrickmn/go-cache"
    30  	"github.com/rs/zerolog/log"
    31  	"google.golang.org/protobuf/proto"
    32  
    33  	"github.com/mysteriumnetwork/metrics"
    34  	"github.com/mysteriumnetwork/node/core/node"
    35  	"github.com/mysteriumnetwork/node/identity"
    36  	"github.com/mysteriumnetwork/node/requests"
    37  )
    38  
    39  const (
    40  	mysteriumMorqaAgentName = "goclient-v0.1"
    41  
    42  	maxBatchMetricsToKeep = 100
    43  	maxBatchMetricsToWait = 30 * time.Second
    44  
    45  	maxBatchSentFails = 3
    46  )
    47  
    48  type metric struct {
    49  	owner string
    50  	event *metrics.Event
    51  }
    52  
    53  // MysteriumMORQA HTTP client for Mysterium Quality Oracle - MORQA.
    54  type MysteriumMORQA struct {
    55  	baseURL string
    56  	client  *requests.HTTPClient
    57  	signer  identity.SignerFactory
    58  
    59  	batch    map[string]*batchWithTimeout
    60  	eventsMu sync.RWMutex
    61  	metrics  chan metric
    62  
    63  	once sync.Once
    64  	stop chan struct{}
    65  
    66  	cache *gocache.Cache
    67  }
    68  
    69  type batchWithTimeout struct {
    70  	batch    *metrics.Batch
    71  	failed   int
    72  	lastFail time.Time
    73  }
    74  
    75  // NewMorqaClient creates Mysterium Morqa client with a real communication.
    76  func NewMorqaClient(httpClient *requests.HTTPClient, baseURL string, signer identity.SignerFactory) *MysteriumMORQA {
    77  	morqa := &MysteriumMORQA{
    78  		baseURL: baseURL,
    79  		client:  httpClient,
    80  		signer:  signer,
    81  
    82  		batch:   make(map[string]*batchWithTimeout),
    83  		metrics: make(chan metric, 1000*maxBatchMetricsToKeep),
    84  		stop:    make(chan struct{}),
    85  
    86  		cache: gocache.New(1*time.Minute, 10*time.Minute),
    87  	}
    88  
    89  	return morqa
    90  }
    91  
    92  // Start starts sending batch metrics to the Morqa server.
    93  func (m *MysteriumMORQA) Start() {
    94  	trigger := time.After(maxBatchMetricsToWait)
    95  
    96  	for {
    97  		select {
    98  		case metric := <-m.metrics:
    99  			m.addMetric(metric)
   100  
   101  			m.eventsMu.RLock()
   102  			size := len(m.batch[metric.owner].batch.Events)
   103  			failed := m.batch[metric.owner].failed
   104  			lastFailed := m.batch[metric.owner].lastFail
   105  			m.eventsMu.RUnlock()
   106  
   107  			if size < maxBatchMetricsToKeep {
   108  				continue
   109  			}
   110  
   111  			if failed > maxBatchSentFails {
   112  				m.eventsMu.Lock()
   113  				delete(m.batch, metric.owner)
   114  				m.eventsMu.Unlock()
   115  			}
   116  
   117  			if time.Now().Before(lastFailed.Add(maxBatchMetricsToWait)) {
   118  				continue
   119  			}
   120  
   121  		case <-trigger:
   122  		case <-m.stop:
   123  			return
   124  		}
   125  
   126  		m.sendAll()
   127  
   128  		trigger = time.After(maxBatchMetricsToWait)
   129  	}
   130  }
   131  
   132  // signBatch creates signature for the metrics batch.
   133  func (m *MysteriumMORQA) signBatch(owner string, batch *metrics.Batch) (string, error) {
   134  	bin, err := proto.Marshal(batch)
   135  	if err != nil {
   136  		return "", fmt.Errorf("failed to marshal metrics event: %w", err)
   137  	}
   138  
   139  	signature, err := m.signer(identity.FromAddress(owner)).Sign(bin)
   140  	if err != nil {
   141  		return "", fmt.Errorf("failed to sign metrics event: %w", err)
   142  	}
   143  
   144  	return signature.Base64(), nil
   145  }
   146  
   147  // Stop sends the final metrics to the MORQA and stops the sending process.
   148  func (m *MysteriumMORQA) Stop() {
   149  	m.once.Do(func() {
   150  		close(m.stop)
   151  	})
   152  
   153  	m.sendAll()
   154  }
   155  
   156  func (m *MysteriumMORQA) addMetric(metric metric) {
   157  	m.eventsMu.Lock()
   158  	defer m.eventsMu.Unlock()
   159  
   160  	batch, ok := m.batch[metric.owner]
   161  	if !ok || batch == nil {
   162  		batch = &batchWithTimeout{
   163  			batch: &metrics.Batch{},
   164  		}
   165  		m.batch[metric.owner] = batch
   166  	}
   167  
   168  	switch metric.event.Metric.(type) {
   169  	case *metrics.Event_SessionStatisticsPayload: // Allow sending only the last session statistics payload in a single batch.
   170  		for i, e := range m.batch[metric.owner].batch.Events {
   171  			if _, ok := e.Metric.(*metrics.Event_SessionStatisticsPayload); ok {
   172  				m.batch[metric.owner].batch.Events[i] = metric.event
   173  				return
   174  			}
   175  		}
   176  	case *metrics.Event_PingEvent: // Allow sending only the last ping event in a single batch.
   177  		for i, e := range m.batch[metric.owner].batch.Events {
   178  			if _, ok := e.Metric.(*metrics.Event_PingEvent); ok {
   179  				m.batch[metric.owner].batch.Events[i] = metric.event
   180  				return
   181  			}
   182  		}
   183  	}
   184  
   185  	batch.batch.Events = append(batch.batch.Events, metric.event)
   186  	m.batch[metric.owner] = batch
   187  }
   188  
   189  func (m *MysteriumMORQA) sendAll() {
   190  	m.eventsMu.Lock()
   191  	defer m.eventsMu.Unlock()
   192  
   193  	for owner := range m.batch {
   194  		err := m.sendMetrics(owner)
   195  		if err != nil {
   196  			log.Error().Err(err).Msgf("Failed to sent batch metrics request, %v", len(m.batch[owner].batch.GetEvents()))
   197  			m.batch[owner].failed++
   198  			m.batch[owner].lastFail = time.Now()
   199  		}
   200  	}
   201  }
   202  
   203  func (m *MysteriumMORQA) sendMetrics(owner string) error {
   204  	if len(m.batch[owner].batch.Events) == 0 {
   205  		return nil
   206  	}
   207  
   208  	batch := m.batch[owner].batch
   209  
   210  	signature, err := m.signBatch(owner, batch)
   211  	if err != nil {
   212  		log.Error().Err(err).Msg("Failed to sign metrics event")
   213  	}
   214  
   215  	sb := &metrics.SignedBatch{
   216  		Signature: signature,
   217  		Batch:     batch,
   218  	}
   219  
   220  	request, err := m.newRequestBinary(http.MethodPost, "batch", sb)
   221  	if err != nil {
   222  		return err
   223  	}
   224  
   225  	request.Close = true
   226  
   227  	response, err := m.client.Do(request)
   228  	if err != nil {
   229  		return err
   230  	}
   231  	defer response.Body.Close()
   232  
   233  	if err := parseResponseError(response); err != nil {
   234  		return err
   235  	}
   236  
   237  	delete(m.batch, owner)
   238  
   239  	return nil
   240  }
   241  
   242  // MonitoringStatus retrieve monitoring statuses.
   243  func (m *MysteriumMORQA) MonitoringStatus(providerIds []string) MonitoringStatusResponse {
   244  	payload := struct {
   245  		Providers []string `json:"providers"`
   246  	}{Providers: providerIds}
   247  
   248  	request, err := m.newRequestJSON(http.MethodPost, "providers/monitoring-status", &payload)
   249  	if err != nil {
   250  		log.Warn().Err(err).Msg("Failed creating request POST: /providers/monitoring-status")
   251  		return map[string]MonitoringStatus{}
   252  	}
   253  
   254  	var r MonitoringStatusResponse
   255  	if err = m.doRequestAndCacheResponse(request, time.Minute, &r); err != nil {
   256  		log.Warn().Err(err).Msg("Failed parsing response from POST: /providers/monitoring-status")
   257  		return nil
   258  	}
   259  
   260  	return r
   261  }
   262  
   263  // ProposalsQuality returns a list of proposals with a quality parameters.
   264  func (m *MysteriumMORQA) ProposalsQuality() []ProposalQuality {
   265  	request, err := m.newRequestJSON(http.MethodGet, "providers/quality", nil)
   266  	if err != nil {
   267  		log.Warn().Err(err).Msg("Failed to create proposals quality request")
   268  
   269  		return nil
   270  	}
   271  
   272  	var qualityResponse []ProposalQuality
   273  	if err = m.doRequestAndCacheResponse(request, 10*time.Minute, &qualityResponse); err != nil {
   274  		log.Warn().Err(err).Msg("Failed to parse proposals quality")
   275  
   276  		return nil
   277  	}
   278  
   279  	return qualityResponse
   280  }
   281  
   282  // ProviderSessions fetch provider sessions from prometheus
   283  func (m *MysteriumMORQA) ProviderSessions(providerID string) []ProviderSession {
   284  	request, err := m.newRequestJSON(http.MethodGet, fmt.Sprintf("providers/sessions?provider_id=%s", providerID), nil)
   285  	if err != nil {
   286  		log.Warn().Err(err).Msg("Failed to create proposals quality request")
   287  
   288  		return nil
   289  	}
   290  
   291  	var responseBody struct {
   292  		Connects []ProviderSession `json:"connects"`
   293  	}
   294  	if err = m.doRequestAndCacheResponse(request, 10*time.Minute, &responseBody); err != nil {
   295  		log.Warn().Err(err).Msg("Failed to parse proposals quality")
   296  
   297  		return nil
   298  	}
   299  	return responseBody.Connects
   300  }
   301  
   302  // ProviderStatuses fetch provider connectivity statuses from quality oracle.
   303  func (m *MysteriumMORQA) ProviderStatuses(providerID string) (node.MonitoringAgentStatuses, error) {
   304  	id := identity.FromAddress(providerID)
   305  
   306  	request, err := requests.NewSignedGetRequest(m.baseURL, "provider/statuses", m.signer(id))
   307  	if err != nil {
   308  		return nil, err
   309  	}
   310  
   311  	var statuses node.MonitoringAgentStatuses
   312  
   313  	if err = m.doRequestAndCacheResponse(request, 10*time.Minute, &statuses); err != nil {
   314  		log.Err(err).Msg("Failed to parse provider monitoring agent statuses")
   315  		return nil, err
   316  	}
   317  
   318  	return statuses, nil
   319  }
   320  
   321  // ProviderSessionsList fetch provider sessions list from quality oracle.
   322  func (m *MysteriumMORQA) ProviderSessionsList(id identity.Identity, rangeTime string) ([]node.SessionItem, error) {
   323  	request, err := requests.NewSignedGetRequest(m.baseURL, fmt.Sprintf("provider/sessions?range=%s", rangeTime), m.signer(id))
   324  	if err != nil {
   325  		return nil, err
   326  	}
   327  
   328  	var sessions []node.SessionItem
   329  
   330  	if err = m.doRequestAndCacheResponse(request, 10*time.Minute, &sessions); err != nil {
   331  		log.Err(err).Msg("Failed to parse provider monitoring sessions list")
   332  		return nil, err
   333  	}
   334  
   335  	return sessions, nil
   336  }
   337  
   338  // ProviderTransferredData fetch total traffic served by the provider during a period of time from quality oracle.
   339  func (m *MysteriumMORQA) ProviderTransferredData(id identity.Identity, rangeTime string) (node.TransferredData, error) {
   340  	var data node.TransferredData
   341  	request, err := requests.NewSignedGetRequest(m.baseURL, fmt.Sprintf("provider/transferred-data?range=%s", rangeTime), m.signer(id))
   342  	if err != nil {
   343  		return data, err
   344  	}
   345  
   346  	if err = m.doRequestAndCacheResponse(request, 10*time.Minute, &data); err != nil {
   347  		log.Err(err).Msg("Failed to parse provider transferred data")
   348  		return data, err
   349  	}
   350  
   351  	return data, nil
   352  }
   353  
   354  // ProviderSessionsCount fetch provider sessions number from quality oracle.
   355  func (m *MysteriumMORQA) ProviderSessionsCount(id identity.Identity, rangeTime string) (node.SessionsCount, error) {
   356  	var count node.SessionsCount
   357  	request, err := requests.NewSignedGetRequest(m.baseURL, fmt.Sprintf("provider/sessions-count?range=%s", rangeTime), m.signer(id))
   358  	if err != nil {
   359  		return count, err
   360  	}
   361  
   362  	if err = m.doRequestAndCacheResponse(request, 10*time.Minute, &count); err != nil {
   363  		log.Err(err).Msg("Failed to parse provider monitoring sessions count")
   364  		return count, err
   365  	}
   366  
   367  	return count, nil
   368  }
   369  
   370  // ProviderConsumersCount fetch consumers number served by provider from quality oracle.
   371  func (m *MysteriumMORQA) ProviderConsumersCount(id identity.Identity, rangeTime string) (node.ConsumersCount, error) {
   372  	var count node.ConsumersCount
   373  	request, err := requests.NewSignedGetRequest(m.baseURL, fmt.Sprintf("provider/consumers-count?range=%s", rangeTime), m.signer(id))
   374  	if err != nil {
   375  		return count, err
   376  	}
   377  
   378  	if err = m.doRequestAndCacheResponse(request, 10*time.Minute, &count); err != nil {
   379  		log.Err(err).Msg("Failed to parse provider monitoring consumers count")
   380  		return count, err
   381  	}
   382  
   383  	return count, nil
   384  }
   385  
   386  // ProviderEarningsSeries fetch earnings data series metrics from quality oracle.
   387  func (m *MysteriumMORQA) ProviderEarningsSeries(id identity.Identity, rangeTime string) (node.EarningsSeries, error) {
   388  	var data node.EarningsSeries
   389  	request, err := requests.NewSignedGetRequest(m.baseURL, fmt.Sprintf("provider/series-earnings?range=%s", rangeTime), m.signer(id))
   390  	if err != nil {
   391  		return data, err
   392  	}
   393  
   394  	if err = m.doRequestAndCacheResponse(request, 10*time.Minute, &data); err != nil {
   395  		log.Err(err).Msg("Failed to parse provider series earnings")
   396  		return data, err
   397  	}
   398  
   399  	return data, nil
   400  }
   401  
   402  // ProviderSessionsSeries fetch earnings data series metrics from quality oracle.
   403  func (m *MysteriumMORQA) ProviderSessionsSeries(id identity.Identity, rangeTime string) (node.SessionsSeries, error) {
   404  	var data node.SessionsSeries
   405  	request, err := requests.NewSignedGetRequest(m.baseURL, fmt.Sprintf("provider/series-sessions?range=%s", rangeTime), m.signer(id))
   406  	if err != nil {
   407  		return data, err
   408  	}
   409  
   410  	if err = m.doRequestAndCacheResponse(request, 10*time.Minute, &data); err != nil {
   411  		log.Err(err).Msg("Failed to parse provider series sessions")
   412  		return data, err
   413  	}
   414  
   415  	return data, nil
   416  }
   417  
   418  // ProviderTransferredDataSeries fetch transferred bytes data series metrics from quality oracle.
   419  func (m *MysteriumMORQA) ProviderTransferredDataSeries(id identity.Identity, rangeTime string) (node.TransferredDataSeries, error) {
   420  	var data node.TransferredDataSeries
   421  	request, err := requests.NewSignedGetRequest(m.baseURL, fmt.Sprintf("provider/series-data?range=%s", rangeTime), m.signer(id))
   422  	if err != nil {
   423  		return data, err
   424  	}
   425  
   426  	if err = m.doRequestAndCacheResponse(request, 10*time.Minute, &data); err != nil {
   427  		log.Err(err).Msg("Failed to parse provider series data")
   428  		return data, err
   429  	}
   430  
   431  	return data, nil
   432  }
   433  
   434  // ProviderServiceEarnings fetch earnings per service type.
   435  func (m *MysteriumMORQA) ProviderServiceEarnings(id identity.Identity) (node.EarningsPerService, error) {
   436  	var data node.EarningsPerService
   437  	request, err := requests.NewSignedGetRequest(m.baseURL, "provider/service-earnings", m.signer(id))
   438  	if err != nil {
   439  		return data, err
   440  	}
   441  
   442  	if err = m.doRequestAndCacheResponse(request, 10*time.Minute, &data); err != nil {
   443  		log.Err(err).Msg("Failed to parse service earnings")
   444  		return data, err
   445  	}
   446  
   447  	return data, nil
   448  }
   449  
   450  // ProviderActivityStats fetch provider activity stats from quality oracle.
   451  func (m *MysteriumMORQA) ProviderActivityStats(id identity.Identity) (node.ActivityStats, error) {
   452  	var stats node.ActivityStats
   453  	request, err := requests.NewSignedGetRequest(m.baseURL, fmt.Sprintf("provider/activity-stats"), m.signer(id))
   454  	if err != nil {
   455  		return stats, err
   456  	}
   457  
   458  	if err = m.doRequestAndCacheResponse(request, 10*time.Minute, &stats); err != nil {
   459  		log.Err(err).Msg("Failed to parse provider activity stats")
   460  		return stats, err
   461  	}
   462  
   463  	return stats, nil
   464  }
   465  
   466  // ProviderQuality fetch provider quality from quality oracle.
   467  func (m *MysteriumMORQA) ProviderQuality(id identity.Identity) (node.QualityInfo, error) {
   468  	var res node.QualityInfo
   469  	request, err := requests.NewSignedGetRequest(m.baseURL, "provider/quality", m.signer(id))
   470  	if err != nil {
   471  		return res, err
   472  	}
   473  
   474  	if err = m.doRequestAndCacheResponse(request, 10*time.Minute, &res); err != nil {
   475  		log.Err(err).Msg("Failed to parse provider quality")
   476  		return res, err
   477  	}
   478  
   479  	return res, nil
   480  }
   481  
   482  // SendMetric submits new metric.
   483  func (m *MysteriumMORQA) SendMetric(id string, event *metrics.Event) error {
   484  	select {
   485  	case m.metrics <- metric{owner: id, event: event}:
   486  	case <-time.After(10 * time.Second):
   487  		log.Warn().Msgf("Timeout waiting for metric store, skipping it %v:%v", id, event)
   488  	}
   489  
   490  	return nil
   491  }
   492  
   493  func (m *MysteriumMORQA) newRequest(method, path string, body []byte) (*http.Request, error) {
   494  	url := m.baseURL
   495  	if len(path) > 0 {
   496  		url = fmt.Sprintf("%v/%v", url, path)
   497  	}
   498  
   499  	request, err := http.NewRequest(method, url, bytes.NewBuffer(body))
   500  	request.Header.Set("User-Agent", mysteriumMorqaAgentName)
   501  	request.Header.Set("Accept", "application/json")
   502  
   503  	return request, err
   504  }
   505  
   506  func (m *MysteriumMORQA) newRequestJSON(method, path string, payload interface{}) (*http.Request, error) {
   507  	payloadBody, err := json.Marshal(payload)
   508  	if err != nil {
   509  		return nil, err
   510  	}
   511  
   512  	req, err := m.newRequest(method, path, payloadBody)
   513  	req.Header.Set("Accept", "application/json")
   514  
   515  	return req, err
   516  }
   517  
   518  func (m *MysteriumMORQA) newRequestBinary(method, path string, payload proto.Message) (*http.Request, error) {
   519  	payloadBody, err := proto.Marshal(payload)
   520  	if err != nil {
   521  		return nil, err
   522  	}
   523  
   524  	request, err := m.newRequest(method, path, payloadBody)
   525  	request.Header.Set("Content-Type", "application/octet-stream")
   526  
   527  	return request, err
   528  }
   529  
   530  func (m *MysteriumMORQA) doRequestAndCacheResponse(request *http.Request, ttl time.Duration, dto interface{}) error {
   531  	if err, ok := m.cache.Get("err" + request.URL.RequestURI()); ok {
   532  		return err.(error)
   533  	}
   534  
   535  	if resp, ok := m.cache.Get(request.URL.RequestURI()); ok {
   536  		serializedDTO, err := json.Marshal(resp)
   537  		if err != nil {
   538  			return err
   539  		}
   540  
   541  		return json.Unmarshal(serializedDTO, dto)
   542  	}
   543  
   544  	response, err := m.client.Do(request)
   545  	if err != nil {
   546  		m.cache.Set("err"+request.URL.RequestURI(), err, ttl)
   547  		log.Warn().Err(err).Msg("Failed to request proposals quality")
   548  
   549  		return nil
   550  	}
   551  	defer response.Body.Close()
   552  
   553  	if err := parseResponseJSON(response, dto); err != nil {
   554  		m.cache.Set("err"+request.URL.RequestURI(), err, ttl)
   555  		return err
   556  	}
   557  
   558  	m.cache.Set(request.URL.RequestURI(), dto, ttl)
   559  
   560  	return nil
   561  }
   562  
   563  func parseResponseJSON(response *http.Response, dto interface{}) error {
   564  	responseJSON, err := io.ReadAll(response.Body)
   565  	if err != nil {
   566  		return err
   567  	}
   568  
   569  	return json.Unmarshal(responseJSON, dto)
   570  }
   571  
   572  type errorDTO struct {
   573  	Message string `json:"message"`
   574  }
   575  
   576  // Sometimes we can get json message with single "message" field which represents error - try to get that.
   577  func parseResponseError(response *http.Response) error {
   578  	if response.StatusCode >= 200 && response.StatusCode < 300 {
   579  		return nil
   580  	}
   581  
   582  	var parsedBody errorDTO
   583  
   584  	var message string
   585  
   586  	err := parseResponseJSON(response, &parsedBody)
   587  	if err != nil {
   588  		message = err.Error()
   589  	} else {
   590  		message = parsedBody.Message
   591  	}
   592  
   593  	return fmt.Errorf("server response invalid: %s (%s). Possible error: %s", response.Status, response.Request.URL, message)
   594  }