Skip to content

Commit 85bcff9

Browse files
committed
Use GC rather than refcounting for VNID policy rules
1 parent acb8636 commit 85bcff9

File tree

7 files changed

+240
-106
lines changed

7 files changed

+240
-106
lines changed

pkg/sdn/plugin/multitenant.go

Lines changed: 22 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ type multiTenantPlugin struct {
1515
node *OsdnNode
1616
vnids *nodeVNIDMap
1717

18-
vnidRefsLock sync.Mutex
19-
vnidRefs map[uint32]int
18+
vnidInUseLock sync.Mutex
19+
vnidInUse map[uint32]bool
2020
}
2121

2222
func NewMultiTenantPlugin() osdnPolicy {
2323
return &multiTenantPlugin{
24-
vnidRefs: make(map[uint32]int),
24+
vnidInUse: make(map[uint32]bool),
2525
}
2626
}
2727

@@ -66,14 +66,10 @@ func (mp *multiTenantPlugin) updatePodNetwork(namespace string, oldNetID, netID
6666
}
6767

6868
if oldNetID != netID {
69-
movedVNIDRefs := 0
70-
7169
// Update OF rules for the existing/old pods in the namespace
7270
for _, pod := range pods {
7371
err = mp.node.UpdatePod(pod)
74-
if err == nil {
75-
movedVNIDRefs++
76-
} else {
72+
if err != nil {
7773
glog.Errorf("Could not update pod %q in namespace %q: %v", pod.Name, namespace, err)
7874
}
7975
}
@@ -86,12 +82,9 @@ func (mp *multiTenantPlugin) updatePodNetwork(namespace string, oldNetID, netID
8682

8783
mp.node.DeleteServiceRules(&svc)
8884
mp.node.AddServiceRules(&svc, netID)
89-
movedVNIDRefs++
9085
}
9186

92-
if movedVNIDRefs > 0 {
93-
mp.moveVNIDRefs(movedVNIDRefs, oldNetID, netID)
94-
}
87+
mp.EnsureVNIDRules(netID)
9588

9689
// Update namespace references in egress firewall rules
9790
mp.node.UpdateEgressNetworkPolicyVNID(namespace, oldNetID, netID)
@@ -126,18 +119,19 @@ func (mp *multiTenantPlugin) GetMulticastEnabled(vnid uint32) bool {
126119
return mp.vnids.GetMulticastEnabled(vnid)
127120
}
128121

129-
func (mp *multiTenantPlugin) RefVNID(vnid uint32) {
122+
func (mp *multiTenantPlugin) EnsureVNIDRules(vnid uint32) {
130123
if vnid == 0 {
131124
return
132125
}
133126

134-
mp.vnidRefsLock.Lock()
135-
defer mp.vnidRefsLock.Unlock()
136-
mp.vnidRefs[vnid] += 1
137-
if mp.vnidRefs[vnid] > 1 {
127+
mp.vnidInUseLock.Lock()
128+
defer mp.vnidInUseLock.Unlock()
129+
if mp.vnidInUse[vnid] {
138130
return
139131
}
140-
glog.V(5).Infof("RefVNID %d adding rule", vnid)
132+
mp.vnidInUse[vnid] = true
133+
134+
glog.V(5).Infof("EnsureVNIDRules %d - adding rules", vnid)
141135

142136
otx := mp.node.oc.NewTransaction()
143137
otx.AddFlow("table=80, priority=100, reg0=%d, reg1=%d, actions=output:NXM_NX_REG2[]", vnid, vnid)
@@ -146,52 +140,19 @@ func (mp *multiTenantPlugin) RefVNID(vnid uint32) {
146140
}
147141
}
148142

149-
func (mp *multiTenantPlugin) UnrefVNID(vnid uint32) {
150-
if vnid == 0 {
151-
return
152-
}
153-
154-
mp.vnidRefsLock.Lock()
155-
defer mp.vnidRefsLock.Unlock()
156-
if mp.vnidRefs[vnid] == 0 {
157-
glog.Warningf("refcounting error on vnid %d", vnid)
158-
return
159-
}
160-
mp.vnidRefs[vnid] -= 1
161-
if mp.vnidRefs[vnid] > 0 {
162-
return
163-
}
164-
glog.V(5).Infof("UnrefVNID %d removing rule", vnid)
165-
166-
otx := mp.node.oc.NewTransaction()
167-
otx.DeleteFlows("table=80, reg0=%d, reg1=%d", vnid, vnid)
168-
if err := otx.EndTransaction(); err != nil {
169-
glog.Errorf("Error deleting OVS flow for VNID: %v", err)
170-
}
171-
}
172-
173-
func (mp *multiTenantPlugin) moveVNIDRefs(num int, oldVNID, newVNID uint32) {
174-
glog.V(5).Infof("moveVNIDRefs %d -> %d", oldVNID, newVNID)
143+
func (mp *multiTenantPlugin) SyncVNIDRules() {
144+
mp.vnidInUseLock.Lock()
145+
defer mp.vnidInUseLock.Unlock()
175146

176-
mp.vnidRefsLock.Lock()
177-
defer mp.vnidRefsLock.Unlock()
147+
unused := mp.node.oc.FindUnusedVNIDs()
148+
glog.Infof("SyncVNIDRules: %d unused VNIDs", len(unused))
178149

179150
otx := mp.node.oc.NewTransaction()
180-
if mp.vnidRefs[oldVNID] <= num {
181-
otx.DeleteFlows("table=80, reg0=%d, reg1=%d", oldVNID, oldVNID)
151+
for _, vnid := range unused {
152+
mp.vnidInUse[uint32(vnid)] = false
153+
otx.DeleteFlows("table=80, reg1=%d", vnid)
182154
}
183-
if mp.vnidRefs[newVNID] == 0 {
184-
otx.AddFlow("table=80, priority=100, reg0=%d, reg1=%d, actions=output:NXM_NX_REG2[]", newVNID, newVNID)
185-
}
186-
err := otx.EndTransaction()
187-
if err != nil {
188-
glog.Errorf("Error modifying OVS flows for VNID: %v", err)
189-
}
190-
191-
mp.vnidRefs[oldVNID] -= num
192-
if mp.vnidRefs[oldVNID] < 0 {
193-
glog.Warningf("refcounting error on vnid %d", oldVNID)
194-
mp.vnidRefs[oldVNID] = 0
155+
if err := otx.EndTransaction(); err != nil {
156+
glog.Errorf("Error deleting syncing OVS VNID rules: %v", err)
195157
}
196-
mp.vnidRefs[newVNID] += num
197158
}

pkg/sdn/plugin/networkpolicy.go

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ type npNamespace struct {
4747
name string
4848
vnid uint32
4949
isolated bool
50-
refs int
5150
inUse bool
5251

5352
policies map[ktypes.UID]*npPolicy
@@ -121,7 +120,7 @@ func (np *networkPolicyPlugin) initNamespaces() error {
121120
name: ns.Name,
122121
vnid: vnid,
123122
isolated: namespaceIsIsolated(&ns),
124-
refs: 0,
123+
inUse: false,
125124
policies: make(map[ktypes.UID]*npPolicy),
126125
}
127126
}
@@ -164,7 +163,7 @@ func (np *networkPolicyPlugin) AddNetNamespace(netns *osapi.NetNamespace) {
164163
name: netns.NetName,
165164
vnid: netns.NetID,
166165
isolated: isolated,
167-
refs: 0,
166+
inUse: false,
168167
policies: make(map[ktypes.UID]*npPolicy),
169168
}
170169
}
@@ -197,15 +196,10 @@ func (np *networkPolicyPlugin) GetMulticastEnabled(vnid uint32) bool {
197196
}
198197

199198
func (np *networkPolicyPlugin) syncNamespace(npns *npNamespace) {
200-
inUse := npns.refs > 0
201-
if !inUse && !npns.inUse {
202-
return
203-
}
204-
205199
glog.V(5).Infof("syncNamespace %d", npns.vnid)
206200
otx := np.node.oc.NewTransaction()
207201
otx.DeleteFlows("table=80, reg1=%d", npns.vnid)
208-
if inUse {
202+
if npns.inUse {
209203
if npns.isolated {
210204
for _, npp := range npns.policies {
211205
for _, flow := range npp.flows {
@@ -219,37 +213,36 @@ func (np *networkPolicyPlugin) syncNamespace(npns *npNamespace) {
219213
if err := otx.EndTransaction(); err != nil {
220214
glog.Errorf("Error syncing OVS flows for VNID: %v", err)
221215
}
222-
npns.inUse = inUse
223216
}
224217

225-
func (np *networkPolicyPlugin) RefVNID(vnid uint32) {
218+
func (np *networkPolicyPlugin) EnsureVNIDRules(vnid uint32) {
226219
np.lock.Lock()
227220
defer np.lock.Unlock()
228221

229222
npns, exists := np.namespaces[vnid]
230-
if !exists {
223+
if !exists || npns.inUse {
231224
return
232225
}
233226

234-
npns.refs += 1
227+
npns.inUse = true
235228
np.syncNamespace(npns)
236229
}
237230

238-
func (np *networkPolicyPlugin) UnrefVNID(vnid uint32) {
231+
232+
func (np *networkPolicyPlugin) SyncVNIDRules() {
239233
np.lock.Lock()
240234
defer np.lock.Unlock()
241235

242-
npns, exists := np.namespaces[vnid]
243-
if !exists {
244-
return
245-
}
246-
if npns.refs == 0 {
247-
glog.Warningf("refcounting error on vnid %d", vnid)
248-
return
249-
}
236+
unused := np.node.oc.FindUnusedVNIDs()
237+
glog.Infof("SyncVNIDRules: %d unused VNIDs", len(unused))
250238

251-
npns.refs -= 1
252-
np.syncNamespace(npns)
239+
for _, vnid := range unused {
240+
npns, exists := np.namespaces[uint32(vnid)]
241+
if exists {
242+
npns.inUse = false
243+
np.syncNamespace(npns)
244+
}
245+
}
253246
}
254247

255248
func (np *networkPolicyPlugin) selectNamespaces(lsel *metav1.LabelSelector) []uint32 {
@@ -408,11 +401,15 @@ func (np *networkPolicyPlugin) watchNetworkPolicies() {
408401
switch delta.Type {
409402
case cache.Sync, cache.Added, cache.Updated:
410403
if changed := np.updateNetworkPolicy(npns, policy); changed {
411-
np.syncNamespace(npns)
404+
if npns.inUse {
405+
np.syncNamespace(npns)
406+
}
412407
}
413408
case cache.Deleted:
414409
delete(npns.policies, policy.UID)
415-
np.syncNamespace(npns)
410+
if npns.inUse {
411+
np.syncNamespace(npns)
412+
}
416413
}
417414

418415
return nil
@@ -522,7 +519,9 @@ func (np *networkPolicyPlugin) handleAddOrUpdateNamespace(obj, _ interface{}, ev
522519
np.kNamespaces[ns.Name] = *ns
523520
if npns, exists := np.namespaces[vnid]; exists {
524521
npns.isolated = namespaceIsIsolated(ns)
525-
np.syncNamespace(npns)
522+
if npns.inUse {
523+
np.syncNamespace(npns)
524+
}
526525
}
527526
// else the NetNamespace doesn't exist yet, but we will initialize
528527
// npns.isolated from the kapi.Namespace when it's created
@@ -555,7 +554,7 @@ func (np *networkPolicyPlugin) refreshNetworkPolicies(watchResourceName Resource
555554
}
556555
}
557556
}
558-
if changed {
557+
if changed && npns.inUse {
559558
np.syncNamespace(npns)
560559
}
561560
}

pkg/sdn/plugin/node.go

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ type osdnPolicy interface {
4747
GetNamespaces(vnid uint32) []string
4848
GetMulticastEnabled(vnid uint32) bool
4949

50-
RefVNID(vnid uint32)
51-
UnrefVNID(vnid uint32)
50+
EnsureVNIDRules(vnid uint32)
51+
SyncVNIDRules()
5252
}
5353

5454
type OsdnNode struct {
@@ -272,11 +272,13 @@ func (node *OsdnNode) Start() error {
272272
continue
273273
}
274274
if vnid, err := node.policy.GetVNID(p.Namespace); err == nil {
275-
node.policy.RefVNID(vnid)
275+
node.policy.EnsureVNIDRules(vnid)
276276
}
277277
}
278278
}
279279

280+
go kwait.Forever(node.policy.SyncVNIDRules, time.Hour)
281+
280282
log.V(5).Infof("openshift-sdn network plugin ready")
281283

282284
// Write our CNI config file out to disk to signal to kubelet that
@@ -369,18 +371,11 @@ func (node *OsdnNode) handleAddOrUpdateService(obj, oldObj interface{}, eventTyp
369371
}
370372

371373
node.AddServiceRules(serv, netid)
372-
if !exists {
373-
node.policy.RefVNID(netid)
374-
}
374+
node.policy.EnsureVNIDRules(netid)
375375
}
376376

377377
func (node *OsdnNode) handleDeleteService(obj interface{}) {
378378
serv := obj.(*kapi.Service)
379379
log.V(5).Infof("Watch %s event for Service %q", watch.Deleted, serv.Name)
380380
node.DeleteServiceRules(serv)
381-
382-
netid, err := node.policy.GetVNID(serv.Namespace)
383-
if err == nil {
384-
node.policy.UnrefVNID(netid)
385-
}
386381
}

pkg/sdn/plugin/ovscontroller.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
osapi "github.com/openshift/origin/pkg/sdn/api"
1414
"github.com/openshift/origin/pkg/util/ovs"
1515

16+
"k8s.io/apimachinery/pkg/util/sets"
1617
kapi "k8s.io/kubernetes/pkg/api"
1718
)
1819

@@ -540,3 +541,66 @@ func (oc *ovsController) UpdateVXLANMulticastFlows(remoteIPs []string) error {
540541

541542
return otx.EndTransaction()
542543
}
544+
545+
// FindUnusedVNIDs returns a list of VNIDs for which there are table 80 "check" rules,
546+
// but no table 60/70 "load" rules (meaning that there are no longer any pods or services
547+
// on this node with that VNID). There is no locking with respect to other ovsController
548+
// actions, but as long the "add a pod" and "add a service" codepaths add the
549+
// pod/service-specific rules before they call policy.EnsureVNIDRules(), then there is no
550+
// race condition.
551+
func (oc *ovsController) FindUnusedVNIDs() []int {
552+
flows, err := oc.ovs.DumpFlows()
553+
if err != nil {
554+
glog.Errorf("FindUnusedVNIDs: could not DumpFlows: %v", err)
555+
return nil
556+
}
557+
558+
// inUseVNIDs is the set of VNIDs in use by pods or services on this node.
559+
// policyVNIDs is the set of VNIDs that we have rules for delivering to.
560+
// VNID 0 is always assumed to be in both sets.
561+
inUseVNIDs := sets.NewInt(0)
562+
policyVNIDs := sets.NewInt(0)
563+
for _, flow := range flows {
564+
parsed, err := ovs.ParseFlow(ovs.ParseForDump, flow)
565+
if err != nil {
566+
glog.Warningf("FindUnusedVNIDs: could not parse flow %q: %v", flow, err)
567+
continue
568+
}
569+
570+
// A VNID is in use if there is a table 60 (services) or 70 (pods) flow that
571+
// loads that VNID into reg1 for later comparison.
572+
if parsed.Table == 60 || parsed.Table == 70 {
573+
// Can't use FindAction here since there may be multiple "load"s
574+
for _, action := range parsed.Actions {
575+
if action.Name != "load" || strings.Index(action.Value, "REG1") == -1 {
576+
continue
577+
}
578+
vnidEnd := strings.Index(action.Value, "->")
579+
if vnidEnd == -1 {
580+
continue
581+
}
582+
vnid, err := strconv.ParseInt(action.Value[:vnidEnd], 0, 32)
583+
if err != nil {
584+
glog.Warningf("FindUnusedVNIDs: could not parse VNID in 'load:%s': %v", action.Value, err)
585+
continue
586+
}
587+
inUseVNIDs.Insert(int(vnid))
588+
break
589+
}
590+
}
591+
592+
// A VNID is checked by policy if there is a table 80 rule comparing reg1 to it.
593+
if parsed.Table == 80 {
594+
if field, exists := parsed.FindField("reg1"); exists {
595+
vnid, err := strconv.ParseInt(field.Value, 0, 32)
596+
if err != nil {
597+
glog.Warningf("FindUnusedVNIDs: could not parse VNID in 'reg1=%s': %v", field.Value, err)
598+
continue
599+
}
600+
policyVNIDs.Insert(int(vnid))
601+
}
602+
}
603+
}
604+
605+
return policyVNIDs.Difference(inUseVNIDs).UnsortedList()
606+
}

0 commit comments

Comments
 (0)