github.com/clly/consul@v1.4.5/agent/consul/coordinate_endpoint.go (about) 1 package consul 2 3 import ( 4 "fmt" 5 "strings" 6 "sync" 7 "time" 8 9 "github.com/hashicorp/consul/acl" 10 "github.com/hashicorp/consul/agent/consul/state" 11 "github.com/hashicorp/consul/agent/structs" 12 "github.com/hashicorp/go-memdb" 13 ) 14 15 // Coordinate manages queries and updates for network coordinates. 16 type Coordinate struct { 17 // srv is a pointer back to the server. 18 srv *Server 19 20 // updates holds pending coordinate updates for the given nodes. This is 21 // keyed by node:segment so we can get a coordinate for each segment for 22 // servers, and we only track the latest update per node:segment. 23 updates map[string]*structs.CoordinateUpdateRequest 24 25 // updatesLock synchronizes access to the updates map. 26 updatesLock sync.Mutex 27 } 28 29 // NewCoordinate returns a new Coordinate endpoint. 30 func NewCoordinate(srv *Server) *Coordinate { 31 c := &Coordinate{ 32 srv: srv, 33 updates: make(map[string]*structs.CoordinateUpdateRequest), 34 } 35 36 go c.batchUpdate() 37 return c 38 } 39 40 // batchUpdate is a long-running routine that flushes pending coordinates to the 41 // Raft log in batches. 42 func (c *Coordinate) batchUpdate() { 43 for { 44 select { 45 case <-time.After(c.srv.config.CoordinateUpdatePeriod): 46 if err := c.batchApplyUpdates(); err != nil { 47 c.srv.logger.Printf("[WARN] consul.coordinate: Batch update failed: %v", err) 48 } 49 case <-c.srv.shutdownCh: 50 return 51 } 52 } 53 } 54 55 // batchApplyUpdates applies all pending updates to the Raft log in a series of 56 // batches. 57 func (c *Coordinate) batchApplyUpdates() error { 58 // Grab the pending updates and release the lock so we can still handle 59 // incoming messages. 60 c.updatesLock.Lock() 61 pending := c.updates 62 c.updates = make(map[string]*structs.CoordinateUpdateRequest) 63 c.updatesLock.Unlock() 64 65 // Enforce the rate limit. 66 limit := c.srv.config.CoordinateUpdateBatchSize * c.srv.config.CoordinateUpdateMaxBatches 67 size := len(pending) 68 if size > limit { 69 c.srv.logger.Printf("[WARN] consul.coordinate: Discarded %d coordinate updates", size-limit) 70 size = limit 71 } 72 73 // Transform the map into a slice that we can feed to the Raft log in 74 // batches. 75 i := 0 76 updates := make(structs.Coordinates, size) 77 for _, update := range pending { 78 if !(i < size) { 79 break 80 } 81 82 updates[i] = &structs.Coordinate{ 83 Node: update.Node, 84 Segment: update.Segment, 85 Coord: update.Coord, 86 } 87 i++ 88 } 89 90 // Apply the updates to the Raft log in batches. 91 for start := 0; start < size; start += c.srv.config.CoordinateUpdateBatchSize { 92 end := start + c.srv.config.CoordinateUpdateBatchSize 93 if end > size { 94 end = size 95 } 96 97 // We set the "safe to ignore" flag on this update type so old 98 // servers don't crash if they see one of these. 99 t := structs.CoordinateBatchUpdateType | structs.IgnoreUnknownTypeFlag 100 101 slice := updates[start:end] 102 resp, err := c.srv.raftApply(t, slice) 103 if err != nil { 104 return err 105 } 106 if respErr, ok := resp.(error); ok { 107 return respErr 108 } 109 } 110 return nil 111 } 112 113 // Update inserts or updates the LAN coordinate of a node. 114 func (c *Coordinate) Update(args *structs.CoordinateUpdateRequest, reply *struct{}) (err error) { 115 if done, err := c.srv.forward("Coordinate.Update", args, args, reply); done { 116 return err 117 } 118 119 // Older clients can send coordinates with invalid numeric values like 120 // NaN and Inf. We guard against these coming in, though newer clients 121 // should never send these. 122 if !args.Coord.IsValid() { 123 return fmt.Errorf("invalid coordinate") 124 } 125 126 // Since this is a coordinate coming from some place else we harden this 127 // and look for dimensionality problems proactively. 128 coord, err := c.srv.serfLAN.GetCoordinate() 129 if err != nil { 130 return err 131 } 132 if !coord.IsCompatibleWith(args.Coord) { 133 return fmt.Errorf("incompatible coordinate") 134 } 135 136 // Fetch the ACL token, if any, and enforce the node policy if enabled. 137 rule, err := c.srv.ResolveToken(args.Token) 138 if err != nil { 139 return err 140 } 141 if rule != nil && c.srv.config.ACLEnforceVersion8 { 142 if !rule.NodeWrite(args.Node, nil) { 143 return acl.ErrPermissionDenied 144 } 145 } 146 147 // Add the coordinate to the map of pending updates. 148 key := fmt.Sprintf("%s:%s", args.Node, args.Segment) 149 c.updatesLock.Lock() 150 c.updates[key] = args 151 c.updatesLock.Unlock() 152 return nil 153 } 154 155 // ListDatacenters returns the list of datacenters and their respective nodes 156 // and the raw coordinates of those nodes (if no coordinates are available for 157 // any of the nodes, the node list may be empty). 158 func (c *Coordinate) ListDatacenters(args *struct{}, reply *[]structs.DatacenterMap) error { 159 maps, err := c.srv.router.GetDatacenterMaps() 160 if err != nil { 161 return err 162 } 163 164 // Strip the datacenter suffixes from all the node names. 165 for i := range maps { 166 suffix := fmt.Sprintf(".%s", maps[i].Datacenter) 167 for j := range maps[i].Coordinates { 168 node := maps[i].Coordinates[j].Node 169 maps[i].Coordinates[j].Node = strings.TrimSuffix(node, suffix) 170 } 171 } 172 173 *reply = maps 174 return nil 175 } 176 177 // ListNodes returns the list of nodes with their raw network coordinates (if no 178 // coordinates are available for a node it won't appear in this list). 179 func (c *Coordinate) ListNodes(args *structs.DCSpecificRequest, reply *structs.IndexedCoordinates) error { 180 if done, err := c.srv.forward("Coordinate.ListNodes", args, args, reply); done { 181 return err 182 } 183 184 return c.srv.blockingQuery(&args.QueryOptions, 185 &reply.QueryMeta, 186 func(ws memdb.WatchSet, state *state.Store) error { 187 index, coords, err := state.Coordinates(ws) 188 if err != nil { 189 return err 190 } 191 192 reply.Index, reply.Coordinates = index, coords 193 if err := c.srv.filterACL(args.Token, reply); err != nil { 194 return err 195 } 196 197 return nil 198 }) 199 } 200 201 // Node returns the raw coordinates for a single node. 202 func (c *Coordinate) Node(args *structs.NodeSpecificRequest, reply *structs.IndexedCoordinates) error { 203 if done, err := c.srv.forward("Coordinate.Node", args, args, reply); done { 204 return err 205 } 206 207 // Fetch the ACL token, if any, and enforce the node policy if enabled. 208 rule, err := c.srv.ResolveToken(args.Token) 209 if err != nil { 210 return err 211 } 212 if rule != nil && c.srv.config.ACLEnforceVersion8 { 213 if !rule.NodeRead(args.Node) { 214 return acl.ErrPermissionDenied 215 } 216 } 217 218 return c.srv.blockingQuery(&args.QueryOptions, 219 &reply.QueryMeta, 220 func(ws memdb.WatchSet, state *state.Store) error { 221 index, nodeCoords, err := state.Coordinate(args.Node, ws) 222 if err != nil { 223 return err 224 } 225 226 var coords structs.Coordinates 227 for segment, coord := range nodeCoords { 228 coords = append(coords, &structs.Coordinate{ 229 Node: args.Node, 230 Segment: segment, 231 Coord: coord, 232 }) 233 } 234 reply.Index, reply.Coordinates = index, coords 235 return nil 236 }) 237 }