github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/registry/grpc/source.go (about) 1 package grpc 2 3 import ( 4 "context" 5 "net" 6 "net/url" 7 "os" 8 "sync" 9 "time" 10 11 "github.com/operator-framework/operator-registry/pkg/client" 12 "github.com/sirupsen/logrus" 13 "golang.org/x/net/http/httpproxy" 14 "golang.org/x/net/proxy" 15 "google.golang.org/grpc" 16 "google.golang.org/grpc/connectivity" 17 "google.golang.org/grpc/credentials/insecure" 18 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 19 20 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" 21 ) 22 23 type SourceMeta struct { 24 Address string 25 LastConnect metav1.Time 26 ConnectionState connectivity.State 27 } 28 29 type SourceState struct { 30 Key registry.CatalogKey 31 State connectivity.State 32 } 33 34 type SourceConn struct { 35 SourceMeta 36 Conn *grpc.ClientConn 37 cancel context.CancelFunc 38 } 39 40 type SourceStore struct { 41 sync.Once 42 sources map[registry.CatalogKey]SourceConn 43 sourcesLock sync.RWMutex 44 syncFn func(SourceState) 45 logger *logrus.Logger 46 notify chan SourceState 47 timeout time.Duration 48 readyTimeout time.Duration 49 } 50 51 func NewSourceStore(logger *logrus.Logger, timeout, readyTimeout time.Duration, sync func(SourceState)) *SourceStore { 52 return &SourceStore{ 53 sources: make(map[registry.CatalogKey]SourceConn), 54 notify: make(chan SourceState), 55 syncFn: sync, 56 logger: logger, 57 timeout: timeout, 58 readyTimeout: readyTimeout, 59 } 60 } 61 62 func (s *SourceStore) Start(ctx context.Context) { 63 s.logger.Debug("starting source manager") 64 go func() { 65 s.Do(func() { 66 for { 67 select { 68 case <-ctx.Done(): 69 s.logger.Debug("closing source manager") 70 return 71 case e := <-s.notify: 72 s.logger.Debugf("Got source event: %#v", e) 73 s.syncFn(e) 74 } 75 } 76 }) 77 }() 78 } 79 80 func (s *SourceStore) GetMeta(key registry.CatalogKey) *SourceMeta { 81 s.sourcesLock.RLock() 82 source, ok := s.sources[key] 83 s.sourcesLock.RUnlock() 84 if !ok { 85 return nil 86 } 87 88 return &source.SourceMeta 89 } 90 91 func (s *SourceStore) Exists(key registry.CatalogKey) bool { 92 s.sourcesLock.RLock() 93 _, ok := s.sources[key] 94 s.sourcesLock.RUnlock() 95 return ok 96 } 97 98 func (s *SourceStore) Get(key registry.CatalogKey) *SourceConn { 99 s.sourcesLock.RLock() 100 source, ok := s.sources[key] 101 s.sourcesLock.RUnlock() 102 if !ok { 103 return nil 104 } 105 return &source 106 } 107 108 func grpcProxyURL(addr string) (*url.URL, error) { 109 // Handle ip addresses 110 host, _, err := net.SplitHostPort(addr) 111 if err != nil { 112 return nil, err 113 } 114 115 url, err := url.Parse(host) 116 if err != nil { 117 return nil, err 118 } 119 120 // Hardcode fields required for proxy resolution 121 url.Host = addr 122 url.Scheme = "http" 123 124 // Override HTTPS_PROXY and HTTP_PROXY with GRPC_PROXY 125 proxyConfig := &httpproxy.Config{ 126 HTTPProxy: getGRPCProxyEnv(), 127 HTTPSProxy: getGRPCProxyEnv(), 128 NoProxy: getEnvAny("NO_PROXY", "no_proxy"), 129 CGI: os.Getenv("REQUEST_METHOD") != "", 130 } 131 132 // Check if a proxy should be used based on environment variables 133 return proxyConfig.ProxyFunc()(url) 134 } 135 136 func getGRPCProxyEnv() string { 137 return getEnvAny("GRPC_PROXY", "grpc_proxy") 138 } 139 140 func getEnvAny(names ...string) string { 141 for _, n := range names { 142 if val := os.Getenv(n); val != "" { 143 return val 144 } 145 } 146 return "" 147 } 148 149 func grpcConnection(address string) (*grpc.ClientConn, error) { 150 dialOptions := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())} 151 proxyURL, err := grpcProxyURL(address) 152 if err != nil { 153 return nil, err 154 } 155 156 if proxyURL != nil { 157 dialOptions = append(dialOptions, grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) { 158 dialer, err := proxy.FromURL(proxyURL, &net.Dialer{}) 159 if err != nil { 160 return nil, err 161 } 162 return dialer.Dial("tcp", addr) 163 })) 164 } 165 166 return grpc.Dial(address, dialOptions...) 167 } 168 169 func (s *SourceStore) Add(key registry.CatalogKey, address string) (*SourceConn, error) { 170 _ = s.Remove(key) 171 172 conn, err := grpcConnection(address) 173 if err != nil { 174 return nil, err 175 } 176 177 ctx, cancel := context.WithCancel(context.Background()) 178 source := SourceConn{ 179 SourceMeta: SourceMeta{ 180 Address: address, 181 LastConnect: metav1.Now(), 182 ConnectionState: connectivity.Idle, 183 }, 184 Conn: conn, 185 cancel: cancel, 186 } 187 188 s.sourcesLock.Lock() 189 s.sources[key] = source 190 s.sourcesLock.Unlock() 191 192 go s.watch(ctx, key, source) 193 194 return &source, nil 195 } 196 197 func (s *SourceStore) stateTimeout(state connectivity.State) time.Duration { 198 if state == connectivity.Ready { 199 return s.readyTimeout 200 } 201 return s.timeout 202 } 203 204 func (s *SourceStore) watch(ctx context.Context, key registry.CatalogKey, source SourceConn) { 205 state := source.ConnectionState 206 for { 207 select { 208 case <-ctx.Done(): 209 return 210 default: 211 func() { 212 timer, cancel := context.WithTimeout(ctx, s.stateTimeout(state)) 213 defer cancel() 214 if source.Conn.WaitForStateChange(timer, state) { 215 newState := source.Conn.GetState() 216 state = newState 217 218 // update connection state 219 src := s.Get(key) 220 if src == nil { 221 // source was removed, cleanup this goroutine 222 return 223 } 224 225 src.LastConnect = metav1.Now() 226 src.ConnectionState = newState 227 s.sourcesLock.Lock() 228 s.sources[key] = *src 229 s.sourcesLock.Unlock() 230 231 // Always try to reconnect. If the connection is already connected, this is a no-op. 232 // 233 // This function is non-blocking. Therefore, when it returns we'll still return IDLE 234 // as the state (we'll see further state changes in subsequent iterations of the loop). 235 source.Conn.Connect() 236 237 // notify subscriber 238 s.notify <- SourceState{Key: key, State: newState} 239 } 240 }() 241 } 242 } 243 } 244 245 func (s *SourceStore) Remove(key registry.CatalogKey) error { 246 s.sourcesLock.RLock() 247 source, ok := s.sources[key] 248 s.sourcesLock.RUnlock() 249 250 // no source to close 251 if !ok { 252 return nil 253 } 254 255 s.sourcesLock.Lock() 256 delete(s.sources, key) 257 s.sourcesLock.Unlock() 258 259 // clean up watcher 260 source.cancel() 261 262 return source.Conn.Close() 263 } 264 265 func (s *SourceStore) AsClients(namespaces ...string) map[registry.CatalogKey]registry.ClientInterface { 266 refs := map[registry.CatalogKey]registry.ClientInterface{} 267 s.sourcesLock.RLock() 268 defer s.sourcesLock.RUnlock() 269 for key, source := range s.sources { 270 if source.LastConnect.IsZero() { 271 continue 272 } 273 for _, namespace := range namespaces { 274 if key.Namespace == namespace { 275 refs[key] = registry.NewClientFromConn(source.Conn) 276 } 277 } 278 } 279 280 // TODO : remove unhealthy 281 return refs 282 } 283 284 func (s *SourceStore) ClientsForNamespaces(namespaces ...string) map[registry.CatalogKey]client.Interface { 285 refs := map[registry.CatalogKey]client.Interface{} 286 s.sourcesLock.RLock() 287 defer s.sourcesLock.RUnlock() 288 for key, source := range s.sources { 289 if source.LastConnect.IsZero() { 290 continue 291 } 292 for _, namespace := range namespaces { 293 if key.Namespace == namespace { 294 refs[key] = client.NewClientFromConn(source.Conn) 295 } 296 } 297 } 298 299 // TODO : remove unhealthy 300 return refs 301 }