Skip to content

Conversation

@fedepaol
Copy link
Member

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.

@fedepaol fedepaol changed the title Move the CRDs in MetalLB Move the CRDs to MetalLB Feb 22, 2022
@fedepaol fedepaol force-pushed the crdsinmetallb branch 2 times, most recently from 29fd9ce to a44d321 Compare February 22, 2022 16:32
@msherif1234
Copy link
Contributor

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

@fedepaol
Copy link
Member Author

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?
The operator was released.

@fedepaol fedepaol force-pushed the crdsinmetallb branch 4 times, most recently from 0656b47 to 900e23c Compare February 23, 2022 12:54
@sabinaaledort
Copy link
Contributor

commit 90d4218 (Add new CR definitions as APIs) looks good

}

err := validate(&raw)
func For(crds CRSConfig, validate Validate) (*Config, error) {
Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Contributor

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)

Copy link
Member Author

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.

EchoMode bool `yaml:"echo-mode"`
PassiveMode bool `yaml:"passive-mode"`
MinimumTTL *uint32 `yaml:"minimum-ttl"`
type CRSConfig struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe "CRsConfig?

Copy link
Member Author

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)

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")
Copy link
Contributor

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"
Copy link
Contributor

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

Copy link
Member Author

Choose a reason for hiding this comment

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

level.Error(r.Logger).Log("controller", "ConfigReconciler", "error", "failed to get addresspools", "error", err)
return ctrl.Result{RequeueAfter: RetryPeriod}, err
}
var bgpPeers metallbv1beta1.BGPPeerList
Copy link
Contributor

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?

@@ -1,3 +1,411 @@
apiVersion: apiextensions.k8s.io/v1
Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member Author

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

@sabinaaledort
Copy link
Contributor

sabinaaledort commented Feb 27, 2022

commit 7b0623e (Replace the custom controller logic with controller-runtime) looks good

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)")
Copy link
Contributor

Choose a reason for hiding this comment

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

Why it's commented?

Copy link
Member Author

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.

@sabinaaledort
Copy link
Contributor

Commit ab2941a (E2ETests: remove configmap mode) looks good

kind: ""
plural: ""
conditions: []
storedVersions: [] No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

missing new line


// AddressPoolSpec defines the desired state of AddressPool.
type AddressPoolSpec struct {
// Protocol can be used to select how the announcement is done,
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: done.


// AddressPoolSpec defines the desired state of AddressPool.
type AddressPoolSpec struct {
// Protocol can be used to select how the announcement is done,
Copy link
Contributor

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"`
Copy link
Contributor

Choose a reason for hiding this comment

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

  1. shouldn't it be optional also?
  2. maybe add the following comment: Name of the BFDProfile to attach to this peer

Copy link
Member Author

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.

Comment on lines 287 to +289
port := uint16(179)
if p.Port != 0 {
port = p.Port
if p.Spec.Port != 0 {
port = p.Spec.Port
Copy link
Contributor

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?

Copy link
Member Author

@fedepaol fedepaol Feb 28, 2022

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

@sabinaaledort
Copy link
Contributor

I reviewed all the commits. Left few small comments. Overall looks good.

@fedepaol fedepaol force-pushed the crdsinmetallb branch 2 times, most recently from 96adbbf to bfdb1ec Compare March 2, 2022 11:45
ret := &Pool{
Protocol: p.Protocol,
AvoidBuggyIPs: p.AvoidBuggyIPs,
Protocol: Proto(p.Spec.Protocol), // TODO CRDs: validate proto
Copy link
Contributor

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?

Copy link
Member Author

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.

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")
Copy link
Contributor

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) {
Copy link
Contributor

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)

Copy link
Member Author

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

if rawAd.AggregationLength != nil {
ad.AggregationLength = *rawAd.AggregationLength
if crdAd.AggregationLength != nil {
ad.AggregationLength = int(*crdAd.AggregationLength) // TODO CRD cast
Copy link
Contributor

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?

Copy link
Member Author

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

@fedepaol fedepaol force-pushed the crdsinmetallb branch 2 times, most recently from b6ae19d to 9a1bd08 Compare March 2, 2022 16:26
fedepaol added 13 commits March 3, 2022 17:46
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>
@liornoy
Copy link
Contributor

liornoy commented Mar 6, 2022

Should we delete the manifests/tutorial.yaml files (configmap) and supply CRs yaml examples instead?

@fedepaol
Copy link
Member Author

fedepaol commented Mar 7, 2022

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.

@sabinaaledort
Copy link
Contributor

/lgtm

@mingfang
Copy link

mingfang commented Jul 6, 2022

bring back configmap support. CRDs are terrible in general.

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.

7 participants