-
Notifications
You must be signed in to change notification settings - Fork 9
/
cache.go
234 lines (203 loc) · 7.24 KB
/
cache.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
/*
Copyright (c) 2023 Nordix Foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package interfacename
import (
"context"
"os"
"sync"
"time"
"github.com/go-logr/logr"
"github.com/nordix/meridio/pkg/log"
)
const defaultReleaseTimeout = 600 * time.Second
type ReleaseTrigger func(ctx context.Context) <-chan struct{}
// InterfaceNameChache -
// InterfaceNameChache keeps track of interface names
// generated by NameGenerator and links them to IDs.
//
// Uses a delayed release functionality, so that the same
// interface name could be recovered within release timeout
// for a particular ID. Thus avoiding interface name change
// upon NSM heal with reselect.
//
// A particular interface name can be used by a single user.
type InterfaceNameChache struct {
nameGenerator NameGenerator
interfaceNames map[string]*interfaceName // key: id, value: interfaceName
interfaceNamesInUse map[string]string // key: string name of the interface, value: id
releaseTrigger ReleaseTrigger
logger logr.Logger
ctx context.Context
mu sync.Mutex
}
type interfaceName struct {
cancelRelease context.CancelFunc
name string
}
// Option is an option pattern for InterfaceNameChache
type Option func(*InterfaceNameChache)
// WithReleaseTrigger sets releaseTrigger for InterfaceNameChache
func WithReleaseTrigger(trigger ReleaseTrigger) Option {
return func(inc *InterfaceNameChache) {
inc.releaseTrigger = trigger
}
}
// InterfaceNameChache -
// Instantiates a new InterfaceNameChache.
func NewInterfaceNameChache(ctx context.Context, generator NameGenerator, options ...Option) *InterfaceNameChache {
cache := &InterfaceNameChache{
nameGenerator: generator,
interfaceNames: map[string]*interfaceName{},
interfaceNamesInUse: map[string]string{},
releaseTrigger: func(ctx context.Context) <-chan struct{} {
channel := make(chan struct{}, 1)
go func() {
select {
case <-ctx.Done():
case <-time.After(defaultReleaseTimeout):
channel <- struct{}{}
}
}()
return channel
},
logger: log.FromContextOrGlobal(ctx).WithValues("class", "InterfaceNameChache"),
ctx: ctx,
}
for _, opt := range options {
opt(cache)
}
return cache
}
// CheckAndReserve -
// Checks if preferred interface name is already taken. If it is associated with a different id,
// then it can not be reserved. If free, reserves the name and stores it in the cache. If there's
// already a cached name for the id, it will keep the old cached one.
//
// Returns preferredName or the name from cache, otherwise an empty string.
func (inc *InterfaceNameChache) CheckAndReserve(id string, preferredName string, namePrefix string, maxLength int) string {
inc.mu.Lock()
defer inc.mu.Unlock()
logger := inc.logger.WithValues("func", "CheckAndReserve", "preferred interface", preferredName, "ID", id)
cachedInterfaceName, exists := inc.interfaceNames[id]
if exists {
if cachedInterfaceName.cancelRelease != nil {
logger.V(1).Info("cancel pending release", "interface", cachedInterfaceName.name)
cachedInterfaceName.cancelRelease()
cachedInterfaceName.cancelRelease = nil
}
logger.V(1).Info("interface name from cache", "interface", cachedInterfaceName.name)
return cachedInterfaceName.name
}
if userId, ok := inc.interfaceNamesInUse[preferredName]; ok {
logger.Info("preferred interface name in use", "user ID", userId)
return ""
}
// try to reserve the preferred interface name
// Note: cache and name generator should be in sync, so no EEXIST error should be returned
if err := inc.nameGenerator.Reserve(preferredName, namePrefix, maxLength); err != nil && !os.IsExist(err) {
return ""
}
inc.interfaceNames[id] = &interfaceName{name: preferredName}
inc.interfaceNamesInUse[preferredName] = id
logger.Info("preferred interface name successfully reserved")
return preferredName
}
// Generate -
// Returns interface name from cache if it already exists.
// Or generates a new one and stores it in the cache.
func (inc *InterfaceNameChache) Generate(id string, namePrefix string, maxLength int) string {
inc.mu.Lock()
defer inc.mu.Unlock()
logger := inc.logger.WithValues("func", "Generate", "ID", id)
cachedInterfaceName, exists := inc.interfaceNames[id]
if exists {
if cachedInterfaceName.cancelRelease != nil {
logger.V(1).Info("cancel pending release", "interface", cachedInterfaceName.name)
cachedInterfaceName.cancelRelease()
cachedInterfaceName.cancelRelease = nil
}
logger.V(1).Info("interface name from cache", "interface", cachedInterfaceName.name)
return cachedInterfaceName.name
}
name := inc.nameGenerator.Generate(namePrefix, maxLength)
inc.interfaceNames[id] = &interfaceName{name: name}
inc.interfaceNamesInUse[name] = id
logger.Info("new interface name", "interface", name)
return name
}
// Release -
// Schedules delayed release of interface name associated
// with ID.
// Removal can be cancelled if re-added within the imeout.
func (inc *InterfaceNameChache) Release(id string) {
inc.mu.Lock()
defer inc.mu.Unlock()
logger := inc.logger.WithValues("func", "Release", "ID", id)
cachedInterfaceName, exists := inc.interfaceNames[id]
if !exists {
logger.Info("no interface name in cache")
return
}
if cachedInterfaceName.cancelRelease != nil {
logger.V(1).Info("release already scheduled")
return
}
cancelCtx, cancelRelease := context.WithCancel(inc.ctx)
cachedInterfaceName.cancelRelease = cancelRelease
name := cachedInterfaceName.name
releaseCh := inc.releaseTrigger(cancelCtx)
select {
case _, ok := <-releaseCh:
// if closed, do not release
if ok {
// release immediately
inc.release(cancelCtx, id, name)
}
default:
// start delayed release
go func() {
logger.V(1).Info("schedule release", "interface", name)
inc.pendingRelease(cancelCtx, releaseCh, id, name)
}()
}
}
func (inc *InterfaceNameChache) pendingRelease(ctx context.Context, releaseCh <-chan struct{}, id string, name string) {
logger := inc.logger.WithValues("func", "pendingRelease", "ID", id, "interface", name)
select {
case _, ok := <-releaseCh:
// if closed, do not release
if ok {
// ID not re-added before delayed deletion is triggered
inc.mu.Lock()
inc.release(ctx, id, name)
inc.mu.Unlock()
}
case <-ctx.Done():
// cancel to keep interface name associated with ID in cache
logger.V(1).Info("release cancelled")
return
}
}
// must be called with lock held
func (inc *InterfaceNameChache) release(ctx context.Context, id string, name string) {
logger := inc.logger.WithValues("func", "release", "ID", id, "interface", name)
_, exists := inc.interfaceNames[id]
if !exists {
logger.Info("no interface name in cache")
return
}
logger.Info("remove interface name")
delete(inc.interfaceNames, id)
delete(inc.interfaceNamesInUse, name)
inc.nameGenerator.Release(name)
}