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
19// Package loadstore contains the loadStoreWrapper shared by the balancers.
20package loadstore
21
22import (
23	"sync"
24
25	"google.golang.org/grpc/xds/internal/xdsclient/load"
26)
27
28// NewWrapper creates a Wrapper.
29func NewWrapper() *Wrapper {
30	return &Wrapper{}
31}
32
33// Wrapper wraps a load store with cluster and edsService.
34//
35// It's store and cluster/edsService can be updated separately. And it will
36// update its internal perCluster store so that new stats will be added to the
37// correct perCluster.
38//
39// Note that this struct is a temporary walkaround before we implement graceful
40// switch for EDS. Any update to the clusterName and serviceName is too early,
41// the perfect timing is when the picker is updated with the new connection.
42// This early update could cause picks for the old SubConn being reported to the
43// new services.
44//
45// When the graceful switch in EDS is done, there should be no need for this
46// struct. The policies that record/report load shouldn't need to handle update
47// of lrsServerName/cluster/edsService. Its parent should do a graceful switch
48// of the whole tree when one of that changes.
49type Wrapper struct {
50	mu         sync.RWMutex
51	cluster    string
52	edsService string
53	// store and perCluster are initialized as nil. They are only set by the
54	// balancer when LRS is enabled. Before that, all functions to record loads
55	// are no-op.
56	store      *load.Store
57	perCluster load.PerClusterReporter
58}
59
60// UpdateClusterAndService updates the cluster name and eds service for this
61// wrapper. If any one of them is changed from before, the perCluster store in
62// this wrapper will also be updated.
63func (lsw *Wrapper) UpdateClusterAndService(cluster, edsService string) {
64	lsw.mu.Lock()
65	defer lsw.mu.Unlock()
66	if cluster == lsw.cluster && edsService == lsw.edsService {
67		return
68	}
69	lsw.cluster = cluster
70	lsw.edsService = edsService
71	lsw.perCluster = lsw.store.PerCluster(lsw.cluster, lsw.edsService)
72}
73
74// UpdateLoadStore updates the load store for this wrapper. If it is changed
75// from before, the perCluster store in this wrapper will also be updated.
76func (lsw *Wrapper) UpdateLoadStore(store *load.Store) {
77	lsw.mu.Lock()
78	defer lsw.mu.Unlock()
79	if store == lsw.store {
80		return
81	}
82	lsw.store = store
83	lsw.perCluster = lsw.store.PerCluster(lsw.cluster, lsw.edsService)
84}
85
86// CallStarted records a call started in the store.
87func (lsw *Wrapper) CallStarted(locality string) {
88	lsw.mu.RLock()
89	defer lsw.mu.RUnlock()
90	if lsw.perCluster != nil {
91		lsw.perCluster.CallStarted(locality)
92	}
93}
94
95// CallFinished records a call finished in the store.
96func (lsw *Wrapper) CallFinished(locality string, err error) {
97	lsw.mu.RLock()
98	defer lsw.mu.RUnlock()
99	if lsw.perCluster != nil {
100		lsw.perCluster.CallFinished(locality, err)
101	}
102}
103
104// CallServerLoad records the server load in the store.
105func (lsw *Wrapper) CallServerLoad(locality, name string, val float64) {
106	lsw.mu.RLock()
107	defer lsw.mu.RUnlock()
108	if lsw.perCluster != nil {
109		lsw.perCluster.CallServerLoad(locality, name, val)
110	}
111}
112
113// CallDropped records a call dropped in the store.
114func (lsw *Wrapper) CallDropped(category string) {
115	lsw.mu.RLock()
116	defer lsw.mu.RUnlock()
117	if lsw.perCluster != nil {
118		lsw.perCluster.CallDropped(category)
119	}
120}
121