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 }