google.golang.org/grpc@v1.62.1/xds/internal/xdsclient/clientimpl_watchers.go (about) 1 /* 2 * 3 * Copyright 2020 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package xdsclient 19 20 import ( 21 "context" 22 "fmt" 23 "sync" 24 25 "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" 26 ) 27 28 // WatchResource uses xDS to discover the resource associated with the provided 29 // resource name. The resource type implementation determines how xDS requests 30 // are sent out and how responses are deserialized and validated. Upon receipt 31 // of a response from the management server, an appropriate callback on the 32 // watcher is invoked. 33 func (c *clientImpl) WatchResource(rType xdsresource.Type, resourceName string, watcher xdsresource.ResourceWatcher) (cancel func()) { 34 // Return early if the client is already closed. 35 // 36 // The client returned from the top-level API is a ref-counted client which 37 // contains a pointer to `clientImpl`. When all references are released, the 38 // ref-counted client sets its pointer to `nil`. And if any watch APIs are 39 // made on such a closed client, we will get here with a `nil` receiver. 40 if c == nil || c.done.HasFired() { 41 logger.Warningf("Watch registered for name %q of type %q, but client is closed", rType.TypeName(), resourceName) 42 return func() {} 43 } 44 45 if err := c.resourceTypes.maybeRegister(rType); err != nil { 46 logger.Warningf("Watch registered for name %q of type %q which is already registered", rType.TypeName(), resourceName) 47 c.serializer.Schedule(func(context.Context) { watcher.OnError(err) }) 48 return func() {} 49 } 50 51 // TODO: replace this with the code does the following when we have 52 // implemented generic watch API on the authority: 53 // - Parse the resource name and extract the authority. 54 // - Locate the corresponding authority object and acquire a reference to 55 // it. If the authority is not found, error out. 56 // - Call the watchResource() method on the authority. 57 // - Return a cancel function to cancel the watch on the authority and to 58 // release the reference. 59 60 // TODO: Make ParseName return an error if parsing fails, and 61 // schedule the OnError callback in that case. 62 n := xdsresource.ParseName(resourceName) 63 a, unref, err := c.findAuthority(n) 64 if err != nil { 65 logger.Warningf("Watch registered for name %q of type %q, authority %q is not found", rType.TypeName(), resourceName, n.Authority) 66 c.serializer.Schedule(func(context.Context) { watcher.OnError(err) }) 67 return func() {} 68 } 69 cancelF := a.watchResource(rType, n.String(), watcher) 70 return func() { 71 cancelF() 72 unref() 73 } 74 } 75 76 // A registry of xdsresource.Type implementations indexed by their corresponding 77 // type URLs. Registration of an xdsresource.Type happens the first time a watch 78 // for a resource of that type is invoked. 79 type resourceTypeRegistry struct { 80 mu sync.Mutex 81 types map[string]xdsresource.Type 82 } 83 84 func newResourceTypeRegistry() *resourceTypeRegistry { 85 return &resourceTypeRegistry{types: make(map[string]xdsresource.Type)} 86 } 87 88 func (r *resourceTypeRegistry) get(url string) xdsresource.Type { 89 r.mu.Lock() 90 defer r.mu.Unlock() 91 return r.types[url] 92 } 93 94 func (r *resourceTypeRegistry) maybeRegister(rType xdsresource.Type) error { 95 r.mu.Lock() 96 defer r.mu.Unlock() 97 98 url := rType.TypeURL() 99 typ, ok := r.types[url] 100 if ok && typ != rType { 101 return fmt.Errorf("attempt to re-register a resource type implementation for %v", rType.TypeName()) 102 } 103 r.types[url] = rType 104 return nil 105 } 106 107 func (c *clientImpl) triggerResourceNotFoundForTesting(rType xdsresource.Type, resourceName string) error { 108 // Return early if the client is already closed. 109 if c == nil || c.done.HasFired() { 110 return fmt.Errorf("attempt to trigger resource-not-found-error for resource %q of type %q, but client is closed", rType.TypeName(), resourceName) 111 } 112 113 n := xdsresource.ParseName(resourceName) 114 a, unref, err := c.findAuthority(n) 115 if err != nil { 116 return fmt.Errorf("attempt to trigger resource-not-found-error for resource %q of type %q, but authority %q is not found", rType.TypeName(), resourceName, n.Authority) 117 } 118 defer unref() 119 a.triggerResourceNotFoundForTesting(rType, n.String()) 120 return nil 121 }