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

golang based watcher for ovn-kubernetes #110

Merged
merged 5 commits into from
Apr 24, 2017
Merged

Conversation

rajatchopra
Copy link

Code for golang based controller that uses go-client libraries from upstream kubernetes to perform watch/get/post functions for ovn-kubernetes. This code has two main enhancements:

  • Replaces the watcher daemon for better reliability
  • Adds an IPAM for nodes as they are born, so that node setup/initialization is a one command job

To be done:

  • For services, only ClusterIP is supported in this code, NodePort/ExternalIP remain
  • Better documentation of usage
  • Unit tests

@rajatchopra
Copy link
Author

Attn: @shettyg @salv-orlando
/cc @dcbw

Big fat PR I know, but its non-intrusive. Please take a look as I look to add callbacks for Network Policy stuff.
Will integrate this with the vagrant setup if the code looks broadly okay to all.

@shettyg
Copy link
Collaborator

shettyg commented Apr 10, 2017

Rajat,
Would you mind adding a Signed-off-by to the commit. The author as shown in 'git log' and Signed-off-by should be the same.

Signed-off-by: Rajat Chopra <rchopra@redhat.com>
@rajatchopra
Copy link
Author

@shettyg just did. realised as soon as I pushed.

Copy link
Collaborator

@shettyg shettyg left a comment

Choose a reason for hiding this comment

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

I just cloned the repo. Before I go deep into the review, should we have a coding style defined? Since I do not know much about golang, do you have a recommendation here? Is golint acceptable? In Python we use flake8, since that takes care of all basic violations, it is easy to just look at important parts of the code.

exit 1
fi

install() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is very RHEL specific. Can we only call this when the platform supports yum?


### Build

Ensure go version >= 1.8
Copy link
Collaborator

Choose a reason for hiding this comment

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

The hack/build-go.sh seems satisfied with 1.6. I suppose that needs a change.

Copy link
Author

Choose a reason for hiding this comment

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

There was a reason, but now I cannot recall it.
Will fix if I cannot recollect soon.

Copy link
Collaborator

@shettyg shettyg left a comment

Choose a reason for hiding this comment

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

I went through a few golang tutorials. So first set of comments. I am still looking through the pod and endpoint watcher.

var subnet *net.IPNet

for count > 0 {
if count != 30 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This looks a little suspect. count = 30 initially. So count will never be decremented.

Copy link
Author

Choose a reason for hiding this comment

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

Good catch. Thank you. Fixed.

}

func calculateMasterSwitchNetwork(clusterNetwork string, hostSubnetLength uint32) (string, error) {
subAllocator, err := netutils.NewSubnetAllocator(clusterNetwork, hostSubnetLength, make([]string, 0))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since the functions being called do not have comments on what they do, can we add a comment here about what they do and what is the return value for both the called functions here?

subrange = append(subrange, hostsubnet)
}
}
masterSwitchNetwork, err := calculateMasterSwitchNetwork(clusterNetwork.String(), hostSubnetLength)
Copy link
Collaborator

Choose a reason for hiding this comment

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

It is not clear to me how this will work. Above, we look at all the nodes that have already been allocated a subnet. We call a subnet allocator for master. How do we know that they will not overlap?

Copy link
Author

Choose a reason for hiding this comment

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

that is what the line 'subrange = append(subrange, masterSwitchNetwork)' ensures.
The subnetAllocator will give out subnets, but will assume 'subrange' argument as one that is already taken.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess, I understand now. Since master.go will always be run first before any node will get subnet allocated, it takes the first subnet. Right?

Copy link
Author

Choose a reason for hiding this comment

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

Right. Subnet allocation happens only in this code anyway which is only called in the master mode.
It's not intended that we run two copies of this code in parallel, but we could keep locks in place to check for that. Right now that feels more like paranoia than perfection.

return err
}
subrange = append(subrange, masterSwitchNetwork)
cluster.masterSubnetAllocator, err = netutils.NewSubnetAllocator(clusterNetwork.String(), hostSubnetLength, subrange)
Copy link
Collaborator

Choose a reason for hiding this comment

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

A comment here about what NewSubnetAllocator does would be nice (as the function itself does not have any documentation).

Copy link
Author

Choose a reason for hiding this comment

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

Yes. Done the needful.

}
}

cluster.SetupMaster(masterNodeName, masterSwitchNetwork)
Copy link
Collaborator

Choose a reason for hiding this comment

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

If "master-init" is called twice, we will call the setup with 2 different subnets?

Copy link
Author

Choose a reason for hiding this comment

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

That is correct. This might break the cluster. So we need to lock it such that only one subnet is alive at one point. Added a TODO here.

Copy link
Author

Choose a reason for hiding this comment

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

I mean added a TODO at the top of this function


cluster.SetupMaster(masterNodeName, masterSwitchNetwork)

go utilwait.Forever(cluster.watchNodes, 0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we add a comment here about what the go routine would do. If I understand right, this waits forever watching nodes and calls addNode and deleteNode functions. So a master-init will actually run forever? If so, won't we have 2 watchers in the master node? Would it make sense to add the node watcher functionality when we add other watcher?

Copy link
Collaborator

@shettyg shettyg left a comment

Choose a reason for hiding this comment

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

I am done. I am happy with it. I really cannot give any meaningful golang feedback though.

if logical_switch != "" {
break
}
if count != 30 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

The check for count looks suscpect.

Copy link
Author

Choose a reason for hiding this comment

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

Fixed :)

p, err := oc.Kube.GetPod(pod.Namespace, pod.Name)
if err != nil {
glog.Errorf("Could not get pod %s/%s for obtaining the logical switch it belongs to", pod.Namespace, pod.Name)
return
Copy link
Collaborator

Choose a reason for hiding this comment

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

Did you want a continue instead of return? I guess, I don't understand why we have the loop here?

Copy link
Author

Choose a reason for hiding this comment

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

Fair point. Added continue here.
The loop is primarily there because we are racing with the scheduler. If this controller catches the birth of a pod sooner than the scheduler, we want to give enough tries so that scheduler can assign the NodeName field for us (which decides the logical switch). Will matter in large clusters.

break
}
glog.V(4).Infof("Error while obtaining addresses for %s - %v", portName, err)
time.Sleep(time.Second)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I do not see a counter decrement. Though because we use --wait=sb in the ovn-nbctl command, we are supposed to not need the retry logic here. But it does not hurt.

err = oc.Kube.SetAnnotationOnPod(pod, "ovn", annotation)
if err != nil {
glog.Errorf("Failed to set annotation on pod %s - %v", pod.Name, err)
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

We currently are not handling named ports here. The python version does handle it. See "k8s_l4_port_name_cache" in overlay.py. The support necessarily need not come with the first version.

kapi "k8s.io/client-go/pkg/api/v1"
)

func (ovn *OvnController) getLoadBalancer(protocol kapi.Protocol) string {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since the load-balancer is created before watcher gets any events and it remains constant, a cache here will save ovn-nbctl calls.

Copy link
Author

Choose a reason for hiding this comment

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

Added a TODO here. Will catch it in the next sweep.

// key is of the form "IP:port" (with quotes around)
key := fmt.Sprintf("\"%s:%d\"", serviceIP, port)

if len(ips) == 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

In the python watcher, we had a watcher for services too. So when a service got deleted, we would get a service delete event and that inturn would cause len(ips) to be zero. When I had tested it, I would not get endpoint events when the service itself got deleted. Has that changed now? Do you get endpoint event with zero ips when a service gets deleted (without pods getting deleted)?

Copy link
Author

Choose a reason for hiding this comment

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

In my tests, the endpoints get deleted when service is deleted. Will keep this in mind though if I see any peculiarities.

@shettyg
Copy link
Collaborator

shettyg commented Apr 20, 2017

On another note, I do not see any code for syncing. In the python version we would do it when the watcher started. Is the syncing taken care by the kubernetes library here?

@rajatchopra
Copy link
Author

On another note, I do not see any code for syncing. In the python version we would do it when the watcher started. Is the syncing taken care by the kubernetes library here?

If you mean the re-sync on http disconnect, then yes that is what the golang client library automatically gives us. Also upon a re-sync it gives back any events that we may have missed by listing and comparing with the cache.

@shettyg
Copy link
Collaborator

shettyg commented Apr 21, 2017

@rajatchopra

Any opinion on coding style? I am personally not a fan of unlimited line lengths.

Should we be using golint?

@rajatchopra
Copy link
Author

@shettyg
golint is definitely desirable. I am lousy at following any style we pick, so the police is needed, let me put up a PR to put that as part of checks within travis/otherwise.

@rajatchopra
Copy link
Author

@shettyg I have lint'ed this code(the last commit). Will add the travis part in the next PR as soon as this gets in.

@shettyg shettyg merged commit 0ed86ae into ovn-org:master Apr 24, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants