Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend ConfigMap to store fwrule names #278

Merged
merged 2 commits into from
Mar 6, 2017
Merged

Conversation

csbell
Copy link
Contributor

@csbell csbell commented Feb 15, 2017

GLBC users can store a providerUid which is used in lieu of Uid when constructing l7 firewall rule names. This can be leveraged to resolve issues seen in Kubernetes #37306.

Backwards compatibility is retained by using Uid for l7 firewall rules unless providerUid is set.

@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Feb 15, 2017
@k8s-reviewable
Copy link

This change is Reviewable

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.03%) to 45.94% when pulling 85a3451 on csbell:fw-name into a8b8967 on kubernetes:master.

Copy link
Contributor

@bprashanth bprashanth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made a first pass, I think the structure of the code will change if you have the Get/Put methods take k/v pairs instead of entire maps so I'll wait for you to upload another pr or reply to existing comments before more review.

} else if err != nil {
glog.Errorf("Failed to reconcile cluster uid %v, currently set to %v", err, existing)
// First look for changes in firewallConfigMap.
vaultmap, found, err := uid_vault.Get()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// First look for changes in firewallConfigMap.
vaultmap, found, err := uid_vault.Get()
if !found || err != nil {
glog.Errorf("Can't read uidConfigMap %v", uidConfigMapName)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why eat this error insted of logging it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -50,46 +46,59 @@ type ConfigMapVault struct {
// If this method returns an error, it's guaranteed to be apiserver flake.
// If the error is a not found error it sets the boolean to false and
// returns and error of nil instead.
func (c *ConfigMapVault) Get() (string, bool, error) {
func (c *ConfigMapVault) Get() (map[string]string, bool, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with returning maps is the following:

package main

import (
	"fmt"
	"sync"
	"time"
)

type mapStore struct {
	mapValue map[string]string
	l        sync.Mutex
}

func (m *mapStore) getMap() map[string]string {
	m.l.Lock()
	defer m.l.Unlock()
	return m.mapValue
}

func (m *mapStore) set(k, v string) {
	m.l.Lock()
	defer m.l.Unlock()
	m.mapValue[k] = v
}

func main() {
	m := mapStore{mapValue: map[string]string{"foo": "bar"}}
	retrievedMap := m.getMap()

	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		for i := 0; i < 1000; i++ {
			fmt.Printf("%v", retrievedMap)
		}
		wg.Done()
	}()

	for i := 0; i < 1000; i++ {
		m.set("foo", fmt.Sprintf("%d", i))
		time.Sleep(10)
	}
	wg.Wait()
	fmt.Printf("%v\n", retrievedMap)
}

running with go run -race main.go reveals a race. Even if the race is not going to impact the actual program, go will complain about it in CI and cause tests to fail, and every programmer reading this code will probably need to convince themselves it's not racy.

Suggest passing the key down into this func, retrieving the string value under a lock and returning the string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the example -- I think the summary is just that we'll be issuing more configmap reads but won't be exposing the map which I think per go is fundamentally not threadsafe.

// Put stores the given UID in the cluster config map.
func (c *ConfigMapVault) Put(uid string) error {
// Put stores the given UID and EUID in the cluster config map.
func (c *ConfigMapVault) Put(keyvals map[string]string) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing here, suggest Put(k, v string) if possible, to reduce the risk of a map clobber

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

map was not being clobbered but we are moving to key->val puts.

"testing"

"k8s.io/kubernetes/pkg/api"
)

func TestConfigMapUID(t *testing.T) {
vault := NewFakeConfigMapVault(api.NamespaceSystem, "ingress-uid")
uid := ""
k, exists, err := vault.Get()
//uid := ""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this commendte out?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

func (n *Namer) GetFirewallName() string {
n.nameLock.Lock()
defer n.nameLock.Unlock()
return n.firewallName
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest defaulting this to clusterName if firewallName is unset, so we don't inadvertently break non-fed deployments by changing the name of the firewall and leaking it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

updated_needed := false

// Dump everything from keyvals into data.
for key, val := range keyvals {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah batch storing k,v pairs makes it harder to reason about consistency. Ideally the call sites that care about the fw name should only get/update the one key. That way, if there's a conflict, we know we own that keyspace.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack

}

// Put stores the given UID in the cluster config map.
func (c *ConfigMapVault) Put(uid string) error {
// Put stores the given UID and EUID in the cluster config map.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment conflicts with the code a little, i.e we take a random map of any values not just UID and EUID right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, fixed.

// Dump everything from keyvals into data.
for key, val := range keyvals {
if val_stored, ok := data[key]; !ok {
glog.Infof("Configmap %v will be updated with %v = %v", cfgMapKey, key, val)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This log line is misleading, it makes me think only the given k/v pair is updated but we actually plug the entire keyval map into the apiObj right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. N/A now with key/val puts

glog.Infof("Using saved cluster uid %q", existingUID)
return existingUID, nil
} else if err != nil {
if val, found := vaultmap[cm_key]; found {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/vaultmap/vaultMap

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@csbell
Copy link
Contributor Author

csbell commented Feb 16, 2017

Thanks for the first round of comments -- I've re-pushed the CL. The major differences are key/val-based puts and adding some unit testing.

However, I still plan on holding off until I can adequately integrate this with federation and test out the functionality.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.04%) to 46.014% when pulling c25aebca924449bdcac34326f309a8166c125e2a on csbell:fw-name into 9bba947 on kubernetes:master.

@csbell
Copy link
Contributor Author

csbell commented Feb 23, 2017

Kubernetes driver for this is here: kubernetes/kubernetes#41942

PTAL @nicksardo

@coveralls
Copy link

Coverage Status

Coverage increased (+0.004%) to 46.167% when pulling f498b1c on csbell:fw-name into 592814b on kubernetes:master.

@@ -103,6 +104,14 @@ func NewNamer(clusterName string) *Namer {
return namer
}

// NewNamer creates a new namer with a Firewall Name
func NewNamerWithFirewall(clusterName string, firewallName string) *Namer {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NewNamer is only used in tests; perhaps we should add the firewall name there instead of creating NewNamerWithFirewall. Also, the first string type can be omitted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

// This is effectively a client override via a command line flag.
// else, check configmap under 'configmap_name' as a key and if found, use the associated value
// else, return an empty 'name' and pass along an error iff the configmap lookup is erroneous.
func getFlagOrLookupVault(cfgVault *storage.ConfigMapVault, cm_key string, name string) (string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of this func name since you're supplying the override and doing nothing flag specific.

Furthermore, I see there's a clusterName flag but not a firewallName flag. Do we want one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name reflects the time where there was a flag fore firewallName. Since talking to beeps, there's no need to introduce a new flag since there will be a new configration mechanism coming up over the next few months that will make flags obsolete.

glog.Infof("Using saved cluster uid %q", existingUID)
return existingUID, nil
glog.Infof("Using %v = %q saved in ConfigMap", cm_key, val)
return val, nil
} else if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an master-existing nitpick of mine. Could we reorder the err != nil before the if found?

Thank you for fixing it in the newNamer! https://github.com/kubernetes/ingress/blob/master/controllers/gce/main.go#L269

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, done.

Copy link
Contributor Author

@csbell csbell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PTAL.

// This is effectively a client override via a command line flag.
// else, check configmap under 'configmap_name' as a key and if found, use the associated value
// else, return an empty 'name' and pass along an error iff the configmap lookup is erroneous.
func getFlagOrLookupVault(cfgVault *storage.ConfigMapVault, cm_key string, name string) (string, error) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name reflects the time where there was a flag fore firewallName. Since talking to beeps, there's no need to introduce a new flag since there will be a new configration mechanism coming up over the next few months that will make flags obsolete.

glog.Infof("Using saved cluster uid %q", existingUID)
return existingUID, nil
glog.Infof("Using %v = %q saved in ConfigMap", cm_key, val)
return val, nil
} else if err != nil {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, done.

@@ -103,6 +104,14 @@ func NewNamer(clusterName string) *Namer {
return namer
}

// NewNamer creates a new namer with a Firewall Name
func NewNamerWithFirewall(clusterName string, firewallName string) *Namer {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@nicksardo
Copy link
Contributor

/lgtm

@k8s-ci-robot
Copy link
Contributor

@nicksardo: you can't LGTM a PR unless you are an assignee.

In response to this comment:

/lgtm

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here.

@nicksardo nicksardo self-assigned this Mar 2, 2017
@csbell
Copy link
Contributor Author

csbell commented Mar 2, 2017

Need another lgtm

@nicksardo
Copy link
Contributor

/lgtm

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Mar 2, 2017
@csbell
Copy link
Contributor Author

csbell commented Mar 2, 2017

With /approve ?

@nicksardo
Copy link
Contributor

/approve

@coveralls
Copy link

Coverage Status

Coverage increased (+0.04%) to 45.393% when pulling 68097e9 on csbell:fw-name into f5f9f5e on kubernetes:master.

k8s-github-robot pushed a commit to kubernetes/kubernetes that referenced this pull request Mar 4, 2017
Automatic merge from submit-queue

Add ProviderUid support to Federated Ingress

This PR (along with GLBC support [here](kubernetes/ingress-nginx#278)) is a proposed fix for #39989. The Ingress controller uses a configMap reconciliation process to ensure that all underlying ingresses agree on a unique UID. This works for all of GLBC's resources except firewalls which need their own cluster-unique UID. This PR introduces a ProviderUid which is maintained and synchronized cross-cluster much like the UID. We chose to derive the ProviderUid from the cluster name (via md5 hash).

Testing here is augmented to guarantee that configMaps are adequately propagated prior to Ingress creation.

```release-note
Federated Ingress over GCE no longer requires separate firewall rules to be created for each cluster to circumvent flapping firewall health checks.
```

cc @madhusudancs @quinton-hoole
@nicksardo nicksardo merged commit a6e3822 into kubernetes:master Mar 6, 2017
perotinus pushed a commit to kubernetes-retired/cluster-registry that referenced this pull request Sep 2, 2017
Automatic merge from submit-queue

Add ProviderUid support to Federated Ingress

This PR (along with GLBC support [here](kubernetes/ingress-nginx#278)) is a proposed fix for #39989. The Ingress controller uses a configMap reconciliation process to ensure that all underlying ingresses agree on a unique UID. This works for all of GLBC's resources except firewalls which need their own cluster-unique UID. This PR introduces a ProviderUid which is maintained and synchronized cross-cluster much like the UID. We chose to derive the ProviderUid from the cluster name (via md5 hash).

Testing here is augmented to guarantee that configMaps are adequately propagated prior to Ingress creation.

```release-note
Federated Ingress over GCE no longer requires separate firewall rules to be created for each cluster to circumvent flapping firewall health checks.
```

cc @madhusudancs @quinton-hoole
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants