github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/core/policy/localcopy/oracle.go (about)

     1  /*
     2   * Copyright (C) 2020 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 localcopy
    19  
    20  import (
    21  	"fmt"
    22  	"net/http"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  
    27  	"github.com/mysteriumnetwork/node/logconfig/httptrace"
    28  	"github.com/mysteriumnetwork/node/market"
    29  	"github.com/mysteriumnetwork/node/requests"
    30  	"github.com/pkg/errors"
    31  	"github.com/rs/zerolog/log"
    32  )
    33  
    34  type policySubscription struct {
    35  	policy      market.AccessPolicy
    36  	eTag        string
    37  	subscribers []*Repository
    38  }
    39  
    40  // Oracle represents async policy fetcher from TrustOracle
    41  type Oracle struct {
    42  	client             *requests.HTTPClient
    43  	fetchURL           string
    44  	fetchInterval      time.Duration
    45  	fetchLock          sync.RWMutex
    46  	fetchSubscriptions []policySubscription
    47  	fetchingEnabled    bool
    48  
    49  	fetchShutdown     chan struct{}
    50  	fetchShutdownOnce sync.Once
    51  }
    52  
    53  // NewOracle create instance of policy fetcher
    54  func NewOracle(client *requests.HTTPClient, policyURL string, interval time.Duration, fetchingEnabled bool) *Oracle {
    55  	return &Oracle{
    56  		client:             client,
    57  		fetchURL:           policyURL,
    58  		fetchInterval:      interval,
    59  		fetchSubscriptions: make([]policySubscription, 0),
    60  		fetchingEnabled:    fetchingEnabled,
    61  		fetchShutdown:      make(chan struct{}),
    62  	}
    63  }
    64  
    65  // Start begins fetching policies to subscribers
    66  func (pr *Oracle) Start() {
    67  	if !pr.fetchingEnabled {
    68  		return
    69  	}
    70  	for {
    71  		select {
    72  		case <-pr.fetchShutdown:
    73  			return
    74  		case <-time.After(pr.fetchInterval):
    75  			pr.fetchLock.Lock()
    76  
    77  			subscriptionsActive := make([]policySubscription, len(pr.fetchSubscriptions))
    78  			copy(subscriptionsActive, pr.fetchSubscriptions)
    79  
    80  			for index := range subscriptionsActive {
    81  				if err := pr.fetchPolicyRules(&subscriptionsActive[index]); err != nil {
    82  					log.Warn().Err(err).Msg("synchronise fetch failed")
    83  				}
    84  			}
    85  			pr.fetchSubscriptions = subscriptionsActive
    86  
    87  			pr.fetchLock.Unlock()
    88  		}
    89  	}
    90  }
    91  
    92  // Stop ends fetching policies to subscribers
    93  func (pr *Oracle) Stop() {
    94  	pr.fetchShutdownOnce.Do(func() {
    95  		close(pr.fetchShutdown)
    96  	})
    97  }
    98  
    99  // Policy converts given value to valid policy rule
   100  func (pr *Oracle) Policy(policyID string) market.AccessPolicy {
   101  	policyURL := pr.fetchURL
   102  	if !strings.HasSuffix(policyURL, "/") {
   103  		policyURL += "/"
   104  	}
   105  	return market.AccessPolicy{
   106  		ID:     policyID,
   107  		Source: fmt.Sprintf("%v%v", policyURL, policyID),
   108  	}
   109  }
   110  
   111  // Policies converts given values to list of valid policies
   112  func (pr *Oracle) Policies(policyIDs []string) []market.AccessPolicy {
   113  	policies := make([]market.AccessPolicy, len(policyIDs))
   114  	for i, policyID := range policyIDs {
   115  		policies[i] = pr.Policy(policyID)
   116  	}
   117  	return policies
   118  }
   119  
   120  // SubscribePolicies adds given policies to repository and syncs changes of it's items from TrustOracle
   121  func (pr *Oracle) SubscribePolicies(policies []market.AccessPolicy, repository *Repository) error {
   122  	if !pr.fetchingEnabled {
   123  		return nil
   124  	}
   125  	pr.fetchLock.Lock()
   126  	defer pr.fetchLock.Unlock()
   127  
   128  	subscriptionsNew := make([]policySubscription, len(pr.fetchSubscriptions))
   129  	copy(subscriptionsNew, pr.fetchSubscriptions)
   130  
   131  	for _, policy := range policies {
   132  		index := len(subscriptionsNew)
   133  		subscriptionsNew = append(subscriptionsNew, policySubscription{
   134  			policy:      policy,
   135  			subscribers: []*Repository{repository},
   136  		})
   137  
   138  		if err := pr.fetchPolicyRules(&subscriptionsNew[index]); err != nil {
   139  			return errors.Wrap(err, "initial fetch failed")
   140  		}
   141  	}
   142  
   143  	pr.fetchSubscriptions = subscriptionsNew
   144  	return nil
   145  }
   146  
   147  // PeriodicFetchingEnabled returns if periodic fetching is enabled
   148  func (pr *Oracle) PeriodicFetchingEnabled() bool {
   149  	return pr.fetchingEnabled
   150  }
   151  
   152  func (pr *Oracle) fetchPolicyRules(subscription *policySubscription) error {
   153  	req, err := requests.NewGetRequest(subscription.policy.Source, "", nil)
   154  	if err != nil {
   155  		return errors.Wrap(err, "failed to create policy request")
   156  	}
   157  	req.Header.Add("If-None-Match", subscription.eTag)
   158  
   159  	res, err := pr.client.Do(req)
   160  	if err != nil {
   161  		return errors.Wrapf(err, "failed fetch policy rule %s", subscription.policy)
   162  	}
   163  	defer res.Body.Close()
   164  
   165  	httptrace.TraceRequestResponse(req, res)
   166  
   167  	if res.StatusCode == http.StatusNotModified {
   168  		return nil
   169  	}
   170  	if err := requests.ParseResponseError(res); err != nil {
   171  		return errors.Wrapf(err, "failed to fetch policy rule %s", subscription.policy)
   172  	}
   173  
   174  	var rules = market.AccessPolicyRuleSet{}
   175  	err = requests.ParseResponseJSON(res, &rules)
   176  	if err != nil {
   177  		return errors.Wrapf(err, "failed to parse policy rule %s", subscription.policy)
   178  	}
   179  	subscription.eTag = res.Header.Get("ETag")
   180  
   181  	for _, subscriber := range subscription.subscribers {
   182  		subscriber.SetPolicyRules(subscription.policy, rules)
   183  	}
   184  
   185  	return nil
   186  }