-
Notifications
You must be signed in to change notification settings - Fork 1k
Move the CRDs to MetalLB #1237
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
Move the CRDs to MetalLB #1237
Conversation
29fd9ce to
a44d321
Compare
|
I don't think this using the latest changes from operator, can we have release for operator now and just base this PR based on it so we know exactly where we are |
I'll check, but can you make an example? Are you talking about the webhooks? Or what? |
0656b47 to
900e23c
Compare
|
commit 90d4218 (Add new CR definitions as APIs) looks good |
internal/config/config.go
Outdated
| } | ||
|
|
||
| err := validate(&raw) | ||
| func For(crds CRSConfig, validate Validate) (*Config, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering about the name of the function, why For and not Parse?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because no parsing is happening here, we are getting a Config for what we are passing.
Before we were parsing a yaml file, so the old name made sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just think the name is unclear for what is done here. But maybe that's just me.
It is parsing objects. For example: parsed, err := bfdProfileFromCR(bfd)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not parsing. And from the calling sites, it will look config.For(crds) which I think is clear.
900e23c to
7535d7f
Compare
internal/config/config.go
Outdated
| EchoMode bool `yaml:"echo-mode"` | ||
| PassiveMode bool `yaml:"passive-mode"` | ||
| MinimumTTL *uint32 `yaml:"minimum-ttl"` | ||
| type CRSConfig struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe "CRsConfig?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that name is pretty ugly, I knew it had to change while adding it :-)
K8sConfig or Resources or ClusterResources are valid alternatives, maybe the latter is the most convincing one (config.ClusterResources from outside the package)
7535d7f to
f7f3489
Compare
| func serviceNameForSlice(endpointSlice *discovery.EndpointSlice) (string, error) { | ||
| serviceName, ok := endpointSlice.Labels["kubernetes.io/service-name"] | ||
| if !ok || serviceName == "" { | ||
| return "", fmt.Errorf("endpointSlice missing %s label", "kubernetes.io/service-name") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know you just copied it but should we fix the fmt.Errorf here? The %s is not needed
| Slices | ||
| ) | ||
|
|
||
| const SlicesServiceIndexName = "ServiceName" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's not used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we use it here https://github.com/fedepaol/metallb/blob/0f608cd2638ae4e45bd50cc580a1d236dfeb50f1/internal/k8s/k8s.go#L179 and here https://github.com/fedepaol/metallb/blob/fcc7c64bdb47ab702cfb94af6c924a3312302ad1/internal/k8s/controllers/endpoints.go#L21
Those are coming in a sequent commit, I split the commits afterwards so that could be the reason.
| level.Error(r.Logger).Log("controller", "ConfigReconciler", "error", "failed to get addresspools", "error", err) | ||
| return ctrl.Result{RequeueAfter: RetryPeriod}, err | ||
| } | ||
| var bgpPeers metallbv1beta1.BGPPeerList |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing empty line for space?
f7f3489 to
3fdb522
Compare
| @@ -1,3 +1,411 @@ | |||
| apiVersion: apiextensions.k8s.io/v1 | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@fedepaol can you move CRDs definition and controller/speaker service account config into a separate file(s) ?
This way operator would just refer to these file(s) and deploy it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that makes sense, but goes in the direction of changing the manifests to a more kustomize-friendly structure.
I'd defer that after we complete this PR and before we do a final release.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure where to add this, we'd also need to cover this with CI - generate from api and check that the helm/raw manifests CRDs match.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My idea is, we switch to kustomize (where we can have a folder with CRDs only), and THEN we generate the -frr and vanilla manifest from there
|
commit 7b0623e (Replace the custom controller logic with controller-runtime) looks good |
controller/main.go
Outdated
| kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file (only needed when running outside of k8s)") | ||
| port = flag.Int("port", 7472, "HTTP listening port for Prometheus metrics") | ||
| namespace = flag.String("namespace", os.Getenv("METALLB_NAMESPACE"), "config / memberlist secret namespace") | ||
| // kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file (only needed when running outside of k8s)") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why it's commented?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's a result of my ramblings with controller-runtime, turns out the runtime looks for kubeconfig by itself and the parameter is no needed. I'll remove it.
|
Commit ab2941a (E2ETests: remove configmap mode) looks good |
charts/metallb/templates/crds.yaml
Outdated
| kind: "" | ||
| plural: "" | ||
| conditions: [] | ||
| storedVersions: [] No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing new line
api/v1alpha1/addresspool_types.go
Outdated
|
|
||
| // AddressPoolSpec defines the desired state of AddressPool. | ||
| type AddressPoolSpec struct { | ||
| // Protocol can be used to select how the announcement is done, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: done.
api/v1beta1/addresspool_types.go
Outdated
|
|
||
| // AddressPoolSpec defines the desired state of AddressPool. | ||
| type AddressPoolSpec struct { | ||
| // Protocol can be used to select how the announcement is done, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: done.
| // +optional | ||
| Password string `json:"password,omitempty" yaml:"password,omitempty"` | ||
|
|
||
| BFDProfile string `json:"bfdProfile,omitempty" yaml:"bfdprofile,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- shouldn't it be optional also?
- maybe add the following comment: Name of the BFDProfile to attach to this peer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This version of the CRDs is transient, and must be the same as the current one in the operator, so we'll be able to write conversion webhook and to preserve backward compatibility for those users already consuming the CRDs from the operator. For this particular annotation, I think it's useless since we are already able to write BGPPeers without bfdprofile(s), but that applies to the whole definition.
| port := uint16(179) | ||
| if p.Port != 0 { | ||
| port = p.Port | ||
| if p.Spec.Port != 0 { | ||
| port = p.Spec.Port |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we add port default value=179 to the CRD instead of this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See the comment above, I will do in the new version of the API
|
I reviewed all the commits. Left few small comments. Overall looks good. |
d65d744 to
61855dc
Compare
96adbbf to
bfdb1ec
Compare
internal/config/config.go
Outdated
| ret := &Pool{ | ||
| Protocol: p.Protocol, | ||
| AvoidBuggyIPs: p.AvoidBuggyIPs, | ||
| Protocol: Proto(p.Spec.Protocol), // TODO CRDs: validate proto |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the addresspool is coming from a k8s custom resource,
that has the Kubebuilder validation for enum: layer2; bgp.
why another validation is required here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that was a reminder for me because I don't like unchecked castings. I can remove the TODO, this version of CRDs / translation is transient and this part will go away in any case.
internal/config/config.go
Outdated
| res.EchoInterval, err = bfdIntFromConfig(p.EchoInterval, 10, 60000) | ||
| res.EchoInterval, err = bfdIntFromConfig(p.Spec.EchoInterval, 10, 60000) | ||
| if err != nil { | ||
| return nil, errors.Wrap(err, "invalid minimum ttl value") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: "invalid echo interval value"
| } | ||
|
|
||
| func parseBGPAdvertisements(ads []bgpAdvertisement, cidrsPerAddresses map[string][]*net.IPNet, communities map[string]uint32) ([]*BGPAdvertisement, error) { | ||
| func bgpAdvertisementsFromCR(ads []metallbv1beta1.BgpAdvertisement, cidrsPerAddresses map[string][]*net.IPNet, communities map[string]uint32) ([]*BGPAdvertisement, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just an opinion: it would be nice to have a lint rule that limits
code lines to 120 chars. (this one is 173 chars long and runs off the screen)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I don't know if / what go guidelines are, but makes sense I guess
internal/config/config.go
Outdated
| if rawAd.AggregationLength != nil { | ||
| ad.AggregationLength = *rawAd.AggregationLength | ||
| if crdAd.AggregationLength != nil { | ||
| ad.AggregationLength = int(*crdAd.AggregationLength) // TODO CRD cast |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does it mean CRD cast?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's another reminder that this could be done in a more elegant way, but I'll remove it
b6ae19d to
9a1bd08
Compare
Import the api definition as it is from the metallb operator. Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
In order to preserve metallb's non k8s internal structure, we keep the configuration as it is today, but we build it starting from the collection of the CRs (in a single struct) as opposed to doing that using the raw configfile. Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
This is required to avoid circular dependencies between the k8s package and the new added controllers package. Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
We use controller-runtime to create a controllers listening for the newly added CRs. At the same time we add controllers for services and nodes. Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
Here we do the wiring between the added controllers and the k8s independent logic scattered across the internal folder. We preserve the handlers, and we change the way we feed them. A new set of controllers listen for config, nodes, services, and call the previous callbacks. No need to wait for the controllers to be synced, because now this is handled by the manager. The corner case where we ask for reprocessing all the services is handled by the ServiceReload controller, which listens to a channel for events. It's one way to avoid chainging the existing logic, because it assumes one controller in charge of the reload and other controllers able to signal it. Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
Update the packages moved to avoid circular dependencies. Also, since the manager handles the check for the caches to be synced before starting, there is no need anymore to do it manually. Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
Fixing the dependencies moved to avoid circular dependencies. Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
Now that the configmap is removed, we remove the configmap mode from the tests and we leave only CRs. We still have the updater, as we'll change the format of the CRs and we'll want to guarantee backward compatibility. Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
We add the new CRDs and set the permissions, and we remove all the configmap related configurations. Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
We remove the permissions on the configmaps and add the CRDs and the related permissions. Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
We need to adjust the operator not to ship the CRs and only to deploy metallb and its CRDs. Until then, the operator lane must be disabled. Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
We can now reach frr from different controllers (i.e. when the config changes, or when a service changes) so we need to protect it to avoid races. Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
The core implementation assumes that the events are processed serially, because originally there was a single queue being fed by all the events. When switching to controller-runtime we have several controllers, each one backed up by a different goroutine. To preserve the seriality, we add a mutex to avoid concurrent processing of k8s events. Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
|
Should we delete the manifests/tutorial.yaml files (configmap) and supply CRs yaml examples instead? |
Yes, but I'd do that when everything settles down and we start working on the docs. |
|
/lgtm |
|
bring back configmap support. CRDs are terrible in general. |
This is the first step of what's described in #942, which is moving the CRDs as they are from the operator to metallb.
The PR is split in several commits for review convenience, but it was done afterwars so there might be some change leak in between them.
We replaced the low level informer / workqueue structure with controller-runtime, providing a higher level way to define controllers (and yaml generation for the CRDs).
We wired the existing callbacks to the new controllers, and added a channel based controller to replicate the corner case where a config change was causing all the services to be reprocessed.
Finally, the old "operator mode" is the only enabled one now, as the configmap is not there anymore.