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

Decoupling of Parser #822

Merged
merged 13 commits into from
Sep 3, 2020
Merged

Decoupling of Parser #822

merged 13 commits into from
Sep 3, 2020

Conversation

mflendrich
Copy link
Contributor

@mflendrich mflendrich commented Sep 2, 2020

This is a refactoring PR for package parser inspired by my work on introducing Ingress v1 support. Its size may look intimidating, but when reviewed commit by commit, it should be easier to wrap one's head around the changes happening.

Before this PR:

  • type Parser had 26 member functions but did not hold any state,
  • Service, Route, Upstream etc. were mutable types, used as input+output arguments by free functions and methods of Parser
  • 55 functions were defined in parser.go (2005 LoC)
  • parser_test.go was 6046 LoC 😱
  • all member functions of Parser could short-circuit the dependency graph of functions by making direct access to store.Storer.

After this PR:

  • all (well, hopefully all) functions that mutate an input argument are now a member function of that argument (not a functional change)
  • if a function references a store.Storer, now it does so through an argument. In several places, this PR decouples parsing logic from store.Storer by replacing the store.Storer dependency with an actual batch of data. This is a preparation step for ingress v1: parse ingress class field in Ingress v1 resource #790 where the parsing logic will need to work with multiple presentation types for the same information.
  • wrapper types (Service, Route, Upstream, etc.) as well as KongState, including their mutator functions, are moved to individual (smaller) files in package kongstate. The general idea (and long-term direction set forth by this PR) is to decouple Kubernetes presentation from Kong presentation by removing concrete Kubernetes types from package kongstate. After this PR, we're still far from there yet. I plan to remove the concrete Ingress type from package kongstate in Merge branch 'master' into 0.2.x #179.
    • type Plugin is almost decoupled - the kongPluginFromK8SPlugin family of functions perform a clean transformation from one world (k8s entities) to the other (go-kong entities).

Notes to reviewers:

  • I recommend reviewing commit by commit.
  • I recommend merging without squashing.
  • The only intended changes in logic are:
    • some tests that operated on kong.(Service|Route|Backend) now operate on kongstate.(Service|Route|Backend),
    • local variables named log shadow function arguments named log where they add fields to the logger. There's potential for repetition of the same field over and over by multiple functions on the call stack.
    • testing for non-panicking if a function gets nil changed (expected to have no side effects),
    • the new type ingressRules and its merging logic (intended to yield identical results),
    • If there is some other (not obviously correct) change in logic that I missed here, don't hesitate to raise it in code review, because I may have missed a test.

@mflendrich mflendrich requested review from rainest and hbagdi September 2, 2020 13:58
@mflendrich mflendrich changed the title Refa/parser cleanup Decoupling of Parser Sep 2, 2020
Copy link
Member

@hbagdi hbagdi left a comment

Choose a reason for hiding this comment

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

One more question: What is the rationale behind moving Consumer and KongState into their own dedicated packages?

In more detail:
- create package KongState
- move all the types (KongState as well as wrappers for kong.* types
such as Service, Route, Upstream, etc) to package kongstate (to
individual .go and _test.go files)
- make functions that mutate the state of a wrapper (or a wrapped kong
entity) a member function of the wrapper object (for Route, Service, Upstream).
This required changes in tests:
    - to operate on the wrapper (e.g. kongstate.Route)
    - refactored assertions that calling methods on nil shall not panic
instead of the wrapped type (kong.Route)
- create package 'util' for helpers with no side effects
@mflendrich
Copy link
Contributor Author

One more question: What is the rationale behind moving Consumer and KongState into their own dedicated packages?

As discussed offline: it was the first type I moved and I didn't have a clear picture yet as to the target packages shape, so Consumer remained separate. Moved everything from package consumer to package kongstate. In a separate commit so as not to go through a rebasing hell.

@mflendrich mflendrich requested a review from hbagdi September 3, 2020 12:17
@rainest
Copy link
Contributor

rainest commented Sep 3, 2020

This helps a lot with difficulty I've been having with the poor separation between the parser and store, especially with trying to write unit tests for them that aren't covering behavior that's sorta shared between them. Separating out the Kong state in particular should help enabling future work to generate config diffs for troubleshooting.

My one note is that I'd caution against the use of acronyms or shorthand that aren't common elsewhere (e.g. "k8s" versus "Kubernetes" or "tech" versus "technology"). Those are harder to grasp when scanning through or reaching code via some path other than a straight readthrough (e.g. from a grep search or traceback, where I might start from somewhere in the middle rather than the start of a function):

if _, ok := ir.SecretNameToSNIs[secretKey]; !ok {
ir.SecretNameToSNIs[secretKey] = []string{}
}
if err == nil {
service.ClientCertificate = &kong.Certificate{
ID: kong.String(string(secret.UID)),
}
} else {
log.WithFields(logrus.Fields{
"secret_name": secretName,
"secret_namespace": service.K8sService.Namespace,
}).Errorf("failed to fetch secret: %v", err)
}
}
ir.ServiceNameToServices[key] = service
("ir" versus "rules" or "ingressRules")

err := cons.SetCredential(log, credential.Type, credential.Config)
("cons" versus "consumer")

if ks.Services[i].Routes[j].IsTCP {
kongIngress, err = getKongIngressFromTCPIngress(s,
&ks.Services[i].Routes[j].TCPIngress)
if err != nil {
log.WithFields(logrus.Fields{
"tcpingress_name": ks.Services[i].Routes[j].TCPIngress.Name,
"tcpingress_namespace": ks.Services[i].Routes[j].TCPIngress.Namespace,
}).Errorf("failed to fetch KongIngress resource for Ingress: %v", err)
}
} else {
("ks" versus "state" or "kongState")

If the full names are getting egregiously long (4+ words or whatever as an arbitrary threshold), or it's something we need to use often (of the three, I'd argue "ks" is the most reasonable, since it's both used throughout that code and is an acronym for the main concept in that file), then we need to look at shortening, but otherwise, I'd suggest defaulting to the longer form.

Non-blocking though: I was able to figure out the meaning in a couple seconds of looking back, but it was something where I needed to stop scanning and step back to look around for a bit.

@mflendrich
Copy link
Contributor Author

If the full names are getting egregiously long (4+ words or whatever as an arbitrary threshold), or it's something we need to use often (of the three, I'd argue "ks" is the most reasonable, since it's both used throughout that code and is an acronym for the main concept in that file), then we need to look at shortening, but otherwise, I'd suggest defaulting to the longer form.

It is a Go idiom to name receivers with a one- or two-letter abbreviation of the receiver type name. I cannot say I wasn't surprised by this as I was switching from C++ to Go the other day.

@rainest is that ok if we follow the convention for our projects, or would you like us to use longer receiver names?
I do prefer the shorter ones (it's easy to read when one gets used to it IMO) but I'll accept that if you have a strong preference for longer ones.

@rainest
Copy link
Contributor

rainest commented Sep 3, 2020

If it's idiomatic I'll note it down as "Go thing for receivers specifically, expect that in Go code in general"; TIL.

@hbagdi hbagdi merged commit 82b2332 into next Sep 3, 2020
@hbagdi hbagdi deleted the refa/parser-cleanup branch September 3, 2020 20:21
mflendrich added a commit that referenced this pull request Sep 11, 2020
Ingress V1: apiversion negotiation and flags, support in store,
parser-k8s decoupling, ingress parsing refactor

closes #788

This PR does the following (each high-level bullet point roughly
matching a group of commits):
- refactors `parseIngressRules` into separate functions (without side
effects) and places them in `translate.go`: `fromIngressV1beta1` and
`fromTCPIngressV1beta1`. Also moves `parseKnativeIngressRules` to follow
suit. All of these get merged in the end using `mergeIngressRules`
recently implemented in #822.
    - this comes with a tiny, potentially behavior changing, logic
    modification: after this PR, default backends get added to the
    overall set of `ingressRules` at a different stage of _"parsing"_
    than they were before.
    - this change is here as a preparation for introduction of
    `fromIngressV1` which will be a copy-paste of `fromIngressV1beta1`
    with changes as described in the ingress v1 KEP.
- removes concrete k8s types (`<your-favorite-apiversion>.Ingress`) and
`TCPIngress` from `package kongstate` by introducing an intermediate
`utils.K8sObjectInfo`:
    - I considered using k8s `ObjectMeta` instead of implementing
    `K8sObjectInfo` but that would violate separation between Kong and
    Kubernetes data formats that I wanted to achieve in `package
    kongstate`.
    - this change is here to accommodate Ingress V1.
- reimplements Ingress API version negotiation:
    - to look not only at supported API groups, but also at the specific
    resource kind available in that API group (because
    `networking.k8s.io/v1` existed in k8s before the `Ingress` kind was
    added thereto)
    - to support `networking.k8s.io/v1` alongside the already supported
    versions
    - to support enabling and disabling support for specific APIs in
    Kong. This is for several reasons:
	- this PR does not implement Ingress V1 fully, so we can merge
	this PR in with ingress v1 code paths disabled
	- if Ingress V1 support is enabled in KIC, and the apiserver is
	>=1.19, then KIC will use a completely new code path in parser
	**for all preexisting Ingress resources**, posing a significant
	regression risk for existing users. There are two possible
	strategies for Ingress v1 rollout:
	    - defensive: choose to leave ingress V1 APIs disabled by
	    default
	    - less defensive but with a workaround by design: make
	    ingress V1 APIs enabled by default, but have the disabling
	    flag readily at hand as a workaround for users whom we break
    - drive-by side effect: makes it possible to "soft-disable"
    extensions/v1beta1 ingress by setting its flag default to false
- implements Ingress v1 alongside v1beta1 in store.
    - Note that the distinction between v1 and both v1beta1 types is
    implemented differently than between `extensions` and `networking`
    v1beta1.
	- This is because `extensions` and `networking` v1beta1
	Ingresses are fully compatible, and they reuse the same parsing
	logic. Ingress V1, however, will be a separate implementation of
	the parsing logic. I wanted to ensure type safety (between
	v1beta1 and v1 ingresses), which would be impossible without
	differentiated storage.
- additionally: adds `*.orig` to `.gitignore`. `*.orig` files originate
from manually resolved Git conflicts and are easy to accidentally commit
into the repo.

Notes to the reviewer:
- Consider reviewing commit by commit.
- Commits are (intended to be) self-contained. Consider not squashing
when merging.

From #826
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.

3 participants