Skip to content

Commit 4d74b77

Browse files
sanchezlsoltysh
authored andcommitted
UPSTREAM: <carry>: deprecateApiRequestHandler
UPSTREAM: <carry>: simplify apirequest counter code UPSTREAM: <carry>: add more unit tests UPSTREAM: <carry>: fix SetRequestCountsForNode UPSTREAM: <carry>: switch to apirequestcount for all resources UPSTREAM: <carry>: temporarily bypass validation for apirequest count removedInRelease UPSTREAM: <carry>: apirequestcount to show dominators instead of fewest UPSTREAM: <carry>: keep apirequestcounts for non-persisted users between updates UPSTREAM: <carry>: properly honor the max number of users in spec UPSTREAM: <carry>: apirequest count with empty .status.removedInRelease UPSTREAM: <carry>: add apirequestcount useragent UPSTREAM: <carry>: limit cardinality of useragent for removedrequest handling UPSTREAM: <carry>: correct apirequestcount lock UPSTREAM: <carry>: apirequestcount: smear out CR updates over interval squash with UPSTREAM: <carry>: deprecateApiRequestHandler openshift-rebase(v1.24):source=f1b2addabc1 UPSTREAM: <carry>: update list of deprecated apis UPSTREAM: <carry>: update list of deprecated apis openshift-rebase(v1.24):source=5ef33bccf60 UPSTREAM: <carry>: fix request count log rotation - discover existing resource request logs on restart - prevent un-needed updates - fixup small typos - reduce chatter in apiserver logs openshift-rebase(v1.24):source=bbd16356c37 UPSTREAM: <carry>: update list of deprecated apis Update the list of deprecated APIs marked for removal base on the latest [Deprecated API Migration Guide](https://kubernetes.io/docs/reference/using-api/deprecation-guide). openshift-rebase(v1.24):source=0c3aeba7b49 UPSTREAM: <carry>: apirequestcount filter should skip non-resources openshift-rebase(v1.24):source=5b8b08c1629 UPSTREAM: <carry>: ignore invalid apirequestcounts openshift-rebase(v1.24):source=e23f142b9ed UPSTREAM: <carry>: update list of deprecated apis
1 parent b762ced commit 4d74b77

File tree

10 files changed

+2562
-16
lines changed

10 files changed

+2562
-16
lines changed
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
package deprecatedapirequest
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"math/rand"
7+
"strings"
8+
"sync"
9+
"time"
10+
11+
apiv1 "github.com/openshift/api/apiserver/v1"
12+
apiv1client "github.com/openshift/client-go/apiserver/clientset/versioned/typed/apiserver/v1"
13+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
"k8s.io/apimachinery/pkg/runtime/schema"
15+
"k8s.io/apimachinery/pkg/util/runtime"
16+
"k8s.io/apimachinery/pkg/util/wait"
17+
"k8s.io/klog/v2"
18+
"k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation/apirequestcount"
19+
"k8s.io/kubernetes/openshift-kube-apiserver/filters/deprecatedapirequest/v1helpers"
20+
)
21+
22+
// NewController returns a controller
23+
func NewController(client apiv1client.APIRequestCountInterface, nodeName string) *controller {
24+
ret := &controller{
25+
client: client,
26+
nodeName: nodeName,
27+
updatePeriod: 5 * time.Minute,
28+
}
29+
ret.resetRequestCount()
30+
return ret
31+
}
32+
33+
// APIRequestLogger support logging deprecated API requests.
34+
type APIRequestLogger interface {
35+
IsDeprecated(resource, version, group string) bool
36+
LogRequest(resource schema.GroupVersionResource, timestamp time.Time, user, userAgent, verb string)
37+
Start(stop <-chan struct{})
38+
}
39+
40+
type controller struct {
41+
client apiv1client.APIRequestCountInterface
42+
nodeName string
43+
updatePeriod time.Duration
44+
loadOnce sync.Once
45+
46+
requestCountLock sync.RWMutex
47+
requestCounts *apiRequestCounts
48+
}
49+
50+
// IsDeprecated return true if the resource is deprecated.
51+
func (c *controller) IsDeprecated(resource, version, group string) bool {
52+
_, ok := deprecatedApiRemovedRelease[schema.GroupVersionResource{
53+
Group: group,
54+
Version: version,
55+
Resource: resource,
56+
}]
57+
return ok
58+
}
59+
60+
// LogRequest queues an api request for logging
61+
func (c *controller) LogRequest(resource schema.GroupVersionResource, timestamp time.Time, user, userAgent, verb string) {
62+
c.requestCountLock.RLock()
63+
defer c.requestCountLock.RUnlock()
64+
// we snip user agents to reduce cardinality and unique keys. For well-behaved agents, we see useragents about like
65+
// kube-controller-manager/v1.21.0 (linux/amd64) kubernetes/743bd58/kube-controller-manager
66+
// so we will snip at the first space.
67+
snippedUserAgent := userAgent
68+
if i := strings.Index(userAgent, " "); i > 0 {
69+
snippedUserAgent = userAgent[:i]
70+
}
71+
userKey := userKey{
72+
user: user,
73+
userAgent: snippedUserAgent,
74+
}
75+
c.requestCounts.IncrementRequestCount(resource, timestamp.Hour(), userKey, verb, 1)
76+
}
77+
78+
// resetCount returns the current count and creates a new requestCount instance var
79+
func (c *controller) resetRequestCount() *apiRequestCounts {
80+
c.requestCountLock.Lock()
81+
defer c.requestCountLock.Unlock()
82+
existing := c.requestCounts
83+
c.requestCounts = newAPIRequestCounts(c.nodeName)
84+
return existing
85+
}
86+
87+
// Start the controller
88+
func (c *controller) Start(stop <-chan struct{}) {
89+
klog.Infof("Starting DeprecatedAPIRequest controller.")
90+
91+
// create a context.Context needed for some API calls
92+
ctx, cancel := context.WithCancel(context.Background())
93+
go func() {
94+
<-stop
95+
klog.Infof("Shutting down DeprecatedAPIRequest controller.")
96+
cancel()
97+
}()
98+
99+
// write out logs every c.updatePeriod
100+
go wait.NonSlidingUntilWithContext(ctx, c.sync, c.updatePeriod)
101+
}
102+
func (c *controller) sync(ctx context.Context) {
103+
currentHour := time.Now().Hour()
104+
c.persistRequestCountForAllResources(ctx, currentHour)
105+
}
106+
107+
func (c *controller) persistRequestCountForAllResources(ctx context.Context, currentHour int) {
108+
klog.V(4).Infof("updating top APIRequest counts")
109+
defer klog.V(4).Infof("finished updating top APIRequest counts")
110+
111+
// get the current count to persist, start a new in-memory count
112+
countsToPersist := c.resetRequestCount()
113+
114+
// remove stale data
115+
expiredHour := (currentHour + 1) % 24
116+
countsToPersist.ExpireOldestCounts(expiredHour)
117+
118+
// when this function returns, add any remaining counts back to the total to be retried for update
119+
defer c.requestCounts.Add(countsToPersist)
120+
121+
// Add resources that have an existing APIRequestCount so that the current and hourly logs
122+
// continue to rotate even if the resource has not had a request since the last restart.
123+
c.loadOnce.Do(func() {
124+
// As resources are never fully removed from countsToPersist, we only need to do this once.
125+
// After the request counts have been persisted, the resources will be added "back" to the
126+
// in memory counts (c.requestCounts, see defer statement above).
127+
arcs, err := c.client.List(ctx, metav1.ListOptions{})
128+
if err != nil {
129+
runtime.HandleError(err) // oh well, we tried
130+
return
131+
}
132+
for _, arc := range arcs.Items {
133+
gvr, err := apirequestcount.NameToResource(arc.Name)
134+
if err != nil {
135+
runtime.HandleError(fmt.Errorf("invalid APIRequestCount %s (added manually) should be deleted: %v", arc.Name, err))
136+
continue
137+
}
138+
countsToPersist.Resource(gvr)
139+
}
140+
})
141+
142+
var wg sync.WaitGroup
143+
for gvr := range countsToPersist.resourceToRequestCount {
144+
resourceCount := countsToPersist.Resource(gvr)
145+
wg.Add(1)
146+
go func() {
147+
time.Sleep(time.Duration(rand.Int63n(int64(c.updatePeriod / 5 * 4)))) // smear out over the interval to avoid resource spikes
148+
c.persistRequestCountForResource(ctx, &wg, currentHour, expiredHour, resourceCount)
149+
}()
150+
}
151+
wg.Wait()
152+
}
153+
154+
func (c *controller) persistRequestCountForResource(ctx context.Context, wg *sync.WaitGroup, currentHour, expiredHour int, localResourceCount *resourceRequestCounts) {
155+
defer wg.Done()
156+
157+
klog.V(4).Infof("updating top %v APIRequest counts", localResourceCount.resource)
158+
defer klog.V(4).Infof("finished updating top %v APIRequest counts", localResourceCount.resource)
159+
160+
status, _, err := v1helpers.ApplyStatus(
161+
ctx,
162+
c.client,
163+
resourceToAPIName(localResourceCount.resource),
164+
nodeStatusDefaulter(c.nodeName, currentHour, expiredHour, localResourceCount.resource),
165+
SetRequestCountsForNode(c.nodeName, currentHour, expiredHour, localResourceCount),
166+
)
167+
if err != nil {
168+
runtime.HandleError(err)
169+
return
170+
}
171+
172+
// on successful update, remove the counts we don't need. This is every hour except the current hour
173+
// and every user recorded for the current hour on this node
174+
removePersistedRequestCounts(c.nodeName, currentHour, status, localResourceCount)
175+
}
176+
177+
// removePersistedRequestCounts removes the counts we don't need to keep in memory.
178+
// This is every hour except the current hour (those will no longer change) and every user recorded for the current hour on this node.
179+
// Then it tracks the amount that needs to be kept out of the sum. This is logically the amount we're adding back in.
180+
// Because we already counted all the users in the persisted sum, we need to exclude the amount we'll be placing back
181+
// in memory.
182+
func removePersistedRequestCounts(nodeName string, currentHour int, persistedStatus *apiv1.APIRequestCountStatus, localResourceCount *resourceRequestCounts) {
183+
for hourIndex := range localResourceCount.hourToRequestCount {
184+
if currentHour != hourIndex {
185+
localResourceCount.RemoveHour(hourIndex)
186+
}
187+
}
188+
for _, persistedNodeCount := range persistedStatus.CurrentHour.ByNode {
189+
if persistedNodeCount.NodeName != nodeName {
190+
continue
191+
}
192+
for _, persistedUserCount := range persistedNodeCount.ByUser {
193+
userKey := userKey{
194+
user: persistedUserCount.UserName,
195+
userAgent: persistedUserCount.UserAgent,
196+
}
197+
localResourceCount.Hour(currentHour).RemoveUser(userKey)
198+
}
199+
}
200+
201+
countToSuppress := int64(0)
202+
for _, userCounts := range localResourceCount.Hour(currentHour).usersToRequestCounts {
203+
for _, verbCount := range userCounts.verbsToRequestCounts {
204+
countToSuppress += verbCount.count
205+
}
206+
}
207+
208+
localResourceCount.Hour(currentHour).countToSuppress = countToSuppress
209+
}
210+
211+
func resourceToAPIName(resource schema.GroupVersionResource) string {
212+
apiName := resource.Resource + "." + resource.Version
213+
if len(resource.Group) > 0 {
214+
apiName += "." + resource.Group
215+
}
216+
return apiName
217+
}

0 commit comments

Comments
 (0)