There was a bug in handling "default-allow" ipsets - it could not
cope with out of order receive of Pod updates. E.g:
1) AddPod(Pod_A, PodIP_A),
2) AddPod(Pod_B, PodIP_A),
3) DeletePod(Pod_A),
would result with PodIP_A being removed from "default-allow" ipset,
as such ipset entries were not refcounted. Bypassing of refcounting
was due to the fact that adding a netpol which dst-selects a Pod which
IP addr is in "default-allow" ipset has to remove the IP addr from
the ipset regardless of refcount value of the IP addr entry in the
ipset. So, refcounting in such case didn't make sense and it was
bypassed.
This commit changes the way entries are refcounted in ipsets, and
it makes "default-allow" ipset the same citizen as other ipsets, so
from now "default-allow" ipset is refcounted.
175 lines
4.9 KiB
Go
175 lines
4.9 KiB
Go
package npc
|
|
|
|
import (
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
|
|
"github.com/weaveworks/weave/common"
|
|
"github.com/weaveworks/weave/npc/ipset"
|
|
)
|
|
|
|
type selectorSpec struct {
|
|
key string // string representation (for hash keying/equality comparison)
|
|
selector labels.Selector // k8s Selector object (for matching)
|
|
dst bool // destination selector
|
|
|
|
ipsetType ipset.Type // type of ipset to provision
|
|
ipsetName ipset.Name // generated ipset name
|
|
nsName string // Namespace name
|
|
}
|
|
|
|
func newSelectorSpec(json *metav1.LabelSelector, dst bool, nsName string, ipsetType ipset.Type) (*selectorSpec, error) {
|
|
selector, err := metav1.LabelSelectorAsSelector(json)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
key := selector.String()
|
|
return &selectorSpec{
|
|
key: key,
|
|
selector: selector,
|
|
dst: dst,
|
|
// We prefix the selector string with the namespace name when generating
|
|
// the shortname because you can specify the same selector in multiple
|
|
// namespaces - we need those to map to distinct ipsets
|
|
ipsetName: ipset.Name(IpsetNamePrefix + shortName(nsName+":"+key)),
|
|
ipsetType: ipsetType,
|
|
nsName: nsName}, nil
|
|
}
|
|
|
|
type selector struct {
|
|
ips ipset.Interface
|
|
spec *selectorSpec
|
|
}
|
|
|
|
func (s *selector) matches(labelMap map[string]string) bool {
|
|
return s.spec.selector.Matches(labels.Set(labelMap))
|
|
}
|
|
|
|
func (s *selector) addEntry(user types.UID, entry string, comment string) error {
|
|
return s.ips.AddEntry(user, s.spec.ipsetName, entry, comment)
|
|
}
|
|
|
|
func (s *selector) delEntry(user types.UID, entry string) error {
|
|
return s.ips.DelEntry(user, s.spec.ipsetName, entry)
|
|
}
|
|
|
|
type selectorFn func(selector *selector) error
|
|
|
|
type selectorSet struct {
|
|
ips ipset.Interface
|
|
onNewSelector selectorFn
|
|
|
|
// invoked after dst selector has been provisioned for the first time
|
|
onNewDstSelector selectorFn
|
|
// invoked after the last instance of dst selector has been deprovisioned
|
|
onDestroyDstSelector selectorFn
|
|
|
|
users map[string]map[types.UID]struct{} // list of users per selector
|
|
entries map[string]*selector
|
|
|
|
// We need to keep track of dst selector instances to be able to invoke
|
|
// onNewDstSelector and onDestroyDstSelector callbacks at the right time;
|
|
// selectorSpec.Key -> count
|
|
dstSelectorsCount map[string]int
|
|
}
|
|
|
|
func newSelectorSet(ips ipset.Interface, onNewSelector, onNewDstSelector selectorFn, onDestroyDstSelector selectorFn) *selectorSet {
|
|
return &selectorSet{
|
|
ips: ips,
|
|
onNewSelector: onNewSelector,
|
|
onNewDstSelector: onNewDstSelector,
|
|
onDestroyDstSelector: onDestroyDstSelector,
|
|
users: make(map[string]map[types.UID]struct{}),
|
|
entries: make(map[string]*selector),
|
|
dstSelectorsCount: make(map[string]int)}
|
|
}
|
|
|
|
func (ss *selectorSet) addToMatching(user types.UID, labelMap map[string]string, entry string, comment string) (bool, error) {
|
|
found := false
|
|
for _, s := range ss.entries {
|
|
if s.matches(labelMap) {
|
|
if ss.dstSelectorExist(s) {
|
|
found = true
|
|
}
|
|
if err := s.addEntry(user, entry, comment); err != nil {
|
|
return found, err
|
|
}
|
|
}
|
|
}
|
|
return found, nil
|
|
}
|
|
|
|
func (ss *selectorSet) delFromMatching(user types.UID, labelMap map[string]string, entry string) error {
|
|
for _, s := range ss.entries {
|
|
if s.matches(labelMap) {
|
|
if err := s.delEntry(user, entry); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ss *selectorSet) dstSelectorExist(s *selector) bool {
|
|
return ss.dstSelectorsCount[s.spec.key] > 0
|
|
}
|
|
|
|
func (ss *selectorSet) deprovision(user types.UID, current, desired map[string]*selectorSpec) error {
|
|
for key, spec := range current {
|
|
if _, found := desired[key]; !found {
|
|
delete(ss.users[key], user)
|
|
if len(ss.users[key]) == 0 {
|
|
common.Log.Infof("destroying ipset: %#v", spec)
|
|
if err := ss.ips.Destroy(spec.ipsetName); err != nil {
|
|
return err
|
|
}
|
|
|
|
delete(ss.entries, key)
|
|
delete(ss.users, key)
|
|
}
|
|
|
|
if spec.dst {
|
|
ss.dstSelectorsCount[key]--
|
|
if ss.dstSelectorsCount[key] == 0 {
|
|
if err := ss.onDestroyDstSelector(&selector{ss.ips, spec}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ss *selectorSet) provision(user types.UID, current, desired map[string]*selectorSpec) error {
|
|
for key, spec := range desired {
|
|
if _, found := current[key]; !found {
|
|
selector := &selector{ss.ips, spec}
|
|
|
|
if _, found := ss.users[key]; !found {
|
|
common.Log.Infof("creating ipset: %#v", spec)
|
|
if err := ss.ips.Create(spec.ipsetName, spec.ipsetType); err != nil {
|
|
return err
|
|
}
|
|
if err := ss.onNewSelector(selector); err != nil {
|
|
return err
|
|
}
|
|
ss.users[key] = make(map[types.UID]struct{})
|
|
ss.entries[key] = selector
|
|
}
|
|
ss.users[key][user] = struct{}{}
|
|
|
|
if spec.dst {
|
|
ss.dstSelectorsCount[key]++
|
|
if ss.dstSelectorsCount[key] == 1 {
|
|
if err := ss.onNewDstSelector(selector); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|