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

add nodeselector and annotation build pod overrides and defaulters #11380

Merged
merged 1 commit into from
Oct 24, 2016

Conversation

bparees
Copy link
Contributor

@bparees bparees commented Oct 14, 2016

This PR allows cluster admins to set override nodeselectors and annotations(primarily intended to be used for tolerations) that will be applied to build pods.

Admins can also set default nodeselectors/annotations which will be applied to the build pod only if the pod does not contain those selector/annotation keys already.

Lastly it also allows users to set nodeselectors on their buildconfig, those nodeselectors will be applied to the build pod (but be overridden by the admin-configured override values)

fixes #9971

fixes bug 1277432

@bparees
Copy link
Contributor Author

bparees commented Oct 14, 2016

[test]

@bparees
Copy link
Contributor Author

bparees commented Oct 14, 2016

[testextended][extended:core(builds)]

@bparees bparees changed the title add nodeselector and annotation build pod overrides and defaulters [WIP] add nodeselector and annotation build pod overrides and defaulters Oct 14, 2016
@bparees bparees force-pushed the merging branch 4 times, most recently from dcba9cd to 6a9b95e Compare October 15, 2016 03:07
@bparees bparees changed the title [WIP] add nodeselector and annotation build pod overrides and defaulters add nodeselector and annotation build pod overrides and defaulters Oct 15, 2016
@bparees bparees force-pushed the merging branch 8 times, most recently from 7cad91e to be5eab3 Compare October 17, 2016 03:23
@bparees
Copy link
Contributor Author

bparees commented Oct 17, 2016

@openshift/api-review this is ready for review.

@deads2k it could also use your review of how i'm initializing the config+controller now with this change since it's not actually an admission controller anymore, but it's still reading/using the admission plugin config.

)

func init() {
admission.RegisterPlugin("BuildDefaults", func(c clientset.Interface, config io.Reader) (admission.Interface, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

to continue allowing old configs to run, do we need to register a no-op plugin with this name that logs a warning that the admission plugin is deprecated and returns nil? same question for BuildOverrides?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think so? we're still honoring the admission plugin config, we're just using it in a different codepath instead of as an admission controller.

Copy link
Contributor

Choose a reason for hiding this comment

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

@deads2k, did we want to remove direct control of the admission chain in 1.4? if so, then no, I don't think we need to maintain the names as dummy plugins

Copy link
Contributor

Choose a reason for hiding this comment

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

@deads2k, did we want to remove direct control of the admission chain in 1.4? if so, then no, I don't think we need to maintain the names as dummy plugins

I don't strictly object to doing it 1.4, but I hadn't set a timeframe. I'm not entirely sure what the validator will do on admission config for a mismatched name.

Also, it's pretty weird to drive config for a controller from the admission config. If we do end up with split processes, this is going to be a problem. This would have instantly broken.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's the only way I can see to make this change and maintain backwards compatibility unless you've got another idea, and @smarterclayton insisted the admission controllers should go away.

i'm going to need you guys to elaborate on this admission chain issue, i don't know what you're referring to.

}
if !buildadmission.IsBuildPod(attributes) {
// ApplyDefaults applies configured build defaults to a build pod
func (b BuildDefaults) ApplyDefaults(pod *kapi.Pod) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

can we take the build as an arg instead of parsing from the annotation? looks like all callers have the build handy (same question applies to ApplyOverrides, I guess)

Copy link
Contributor

Choose a reason for hiding this comment

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

are the possible mutations to the build in applyBuildDefaults() things we want persisted? if not, we could pass in buildCopy

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 now we don't mutuate the actual build object in the admission controller, only the build as it is defined in the pod, so no, the mutations are not things we want persisted.

we can't just take the build as an arg because we also need the version info that is part of the serialized form of the build in the annotation. we have to keep the serialized version consistent before/after mutating the value.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(also taking the build as an arg would mean warning callers that the object they pass in is being mutated but should not be persisted after that mutation, to retain the existing behavior, or we'd have to make a copy within this method)

Copy link
Contributor

Choose a reason for hiding this comment

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

mm, ok.

@@ -17,4 +19,10 @@ type BuildOverridesConfig struct {
// If user provided a label in their Build/BuildConfig with the same name as one in this
// list, the user's label will be overwritten.
ImageLabels []buildapi.ImageLabel

// nodeSelector is a selector which must be true for the build pod to fit on a node
NodeSelector map[string]string
Copy link
Contributor

Choose a reason for hiding this comment

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

it looks like these defaults are applied key-by-key? so if I have a build with "nodeSelector": {"foo":"bar"} and defaults of "nodeSelector":{"baz":"qux"}, I'll get a pod with "nodeSelector":{"foo":"bar","baz":"qux"}. Is that what we want for the defaulting?

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 and yes. the intent is that an admin might set a default nodeSelector of "targetNode:defaultBuildNode" but the user might want to say "targetNode:highMemoryBuildNode" instead.

Copy link
Contributor

Choose a reason for hiding this comment

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

That works as long as a build never needs a selector that omits one of the default keys. I'm not sure what level of granularity is needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

my view of the default nodeselector was "must be at least this specific".

there are probably competing use cases. but my assumption is most admins are going to set their system up with node labels like "buildNode=foo", and a defaulter that assigns the buildNode nodeselector key, which ensures that all builds will land on a node with at least the "buildNode" key, of some value.

if build.Spec.Strategy.SourceStrategy != nil {
glog.V(5).Infof("Setting source strategy ForcePull to true in build %s/%s", build.Namespace, build.Name)
build.Spec.Strategy.SourceStrategy.ForcePull = true
for k, v := range b.config.NodeSelector {
Copy link
Contributor

Choose a reason for hiding this comment

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

do we want to do the same thing as the project nodeSelector and error if the build already specifies a conflicting key/value, or just stomp 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.

well obviously i want to stomp it :)

Copy link
Contributor

Choose a reason for hiding this comment

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

if they create a build with a nodeSelector, is it more confusing to fail build pod creation with errors about disallowed elements in the nodeSelector, or to create build pods that apparently ignore the nodeSelector they set? I'm really not sure...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i'm not sure either. i kind of prefer the path where the build succeeds for them, but i agree they aren't necessarily getting what they expected nor will they necessarily understand why (assuming they even notice that it didn't run on the node they expected)

return nil
for k, v := range b.config.Annotations {
glog.V(5).Infof("Adding override annotation %s=%s to build pod %s/%s", k, v, pod.Namespace, pod.Name)
pod.Annotations[k] = v
Copy link
Contributor

Choose a reason for hiding this comment

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

same question about erroring or stomping on conflicting key/value

Copy link
Contributor Author

Choose a reason for hiding this comment

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

same answer :)

@smarterclayton
Copy link
Contributor

smarterclayton commented Oct 18, 2016 via email

@bparees bparees force-pushed the merging branch 2 times, most recently from d4163ac to b121646 Compare October 18, 2016 13:47
@bparees
Copy link
Contributor Author

bparees commented Oct 18, 2016

flake #11240

@bparees
Copy link
Contributor Author

bparees commented Oct 18, 2016

@smarterclayton @liggitt @deads2k

The open questions on this PR appear to be:

  • should override stomp on the user's value, or result in an error/failure (and is this conflict on a key by key level, or is it an error for the user to set any key, if the override is also going to set some key(s))
  • should default keys merge with the user's keys, or get ignored if the user set any keys

And these questions may have different answers for nodselectors vs annotations (it's hard to imagine we'd want to fail all user builds that have an annotation set, when a non-conflicting override annotation is defined. Ditto for ignoring default annotations just because the user set some random annotation on their build).

We need this questions resolved asap so this can land for 3.4 feature cut. Especially if the answer is that the current choices are not acceptable and the behavior needs to be changed.

@bparees
Copy link
Contributor Author

bparees commented Oct 18, 2016

flake #11240
[test]

@pweil-
Copy link
Contributor

pweil- commented Oct 20, 2016

We need this questions resolved asap so this can land for 3.4 feature cut

@openshift/api-review bump for questions in #11380 (comment). I'd prefer the route that does not fail the build, especially if we can provide a message that their selection was overridden by cluster settings.

@smarterclayton
Copy link
Contributor

Suggestions:

  • annotations
    • overrides = stomp key by key
    • defaults = default key by key
  • selector
    • defaults = if selector is completely empty, default, otherwise do nothing
    • overrides = for each label specified by the admin, update the value (this will be broken when label selectors are more complex)

@liggitt
Copy link
Contributor

liggitt commented Oct 20, 2016

so I think the only change from the current state is making the nodeSelector defaulting all-or-nothing

follow up issue to try to surface stompage to the user would be nice but non-blocking

@smarterclayton
Copy link
Contributor

Yeah definitely non-blocking.

@bparees
Copy link
Contributor Author

bparees commented Oct 20, 2016

yeah, working on that change right now.

@bparees
Copy link
Contributor Author

bparees commented Oct 21, 2016

flake #11477
flake #11094
[test]

@bparees
Copy link
Contributor Author

bparees commented Oct 21, 2016

default selector is now all or nothing. @csrwng ptal.

@bparees
Copy link
Contributor Author

bparees commented Oct 21, 2016

(and per @liggitt's suggestion, map{} results in no defaults being applied. nil results in defaults being applied. so users can avoid the defaults if they want)

@bparees
Copy link
Contributor Author

bparees commented Oct 21, 2016

flake #11452

@bparees
Copy link
Contributor Author

bparees commented Oct 21, 2016

@jupierce ptal

Copy link
Contributor

@jupierce jupierce left a comment

Choose a reason for hiding this comment

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

LGTM.

// build if the specified variables do not exist on the build
Env []kapi.EnvVar `json:"env,omitempty"`

// SourceStrategyDefaults are default values that apply to builds using the
// sourceStrategyDefaults are default values that apply to builds using the
// source strategy.
SourceStrategyDefaults *SourceStrategyDefaultsConfig `json:"sourceStrategyDefaults,omitempty"`

// ImageLabels is a list of docker labels that are applied to the resulting image.
Copy link
Contributor

Choose a reason for hiding this comment

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

You lowercased every attribute name in doc except this 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.

result of rebase/merge. but yeah i'll lowercase it.

defer configData.Close()

configBytes, err := ioutil.ReadAll(configData)

err = configlatest.ReadYAMLInto(configBytes, config)
Copy link
Contributor

Choose a reason for hiding this comment

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

ReadYAMLFileInto instead?

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. i just moved this code from somewhere else but it makes sense to update it to use that instead.

}
return pod, nil
}

// SetBuild encodes a build object and sets it in a pod's BUILD environment variable
Copy link
Contributor

Choose a reason for hiding this comment

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

SetBuildInPod instead of SetBuild

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yup

}
return hasBuildAnnotation(pod) && hasBuildEnvVar(pod)
}

// GetBuild returns a build object encoded in a pod's BUILD environment variable along with
Copy link
Contributor

Choose a reason for hiding this comment

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

GetBuildFromPod instead of GetBuild

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yup thanks

if err != nil {
return nil, unversioned.GroupVersion{}, err
}
func GetBuildFromPod(pod *kapi.Pod) (*buildapi.Build, unversioned.GroupVersion, error) {
build, version, err := getBuildFromPod(pod)
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we not collapse the public/private methods into 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.

yeah, will do.


err = setBuildInPod(build, pod, groupVersion)
func SetBuildInPod(pod *kapi.Pod, build *buildapi.Build, groupVersion unversioned.GroupVersion) error {
err := setBuildInPod(build, pod, groupVersion)
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we not collapse the public/private methods into 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.

yup

return err
}

func SetPodLogLevelFromBuild(pod *kapi.Pod, build *buildapi.Build) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

Method name in documentation needs update.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yup

@bparees
Copy link
Contributor Author

bparees commented Oct 22, 2016

having some trouble getting nodeSelector to respect empty map values instead of nil values. otherwise i think this is done (addressed justin's comments)

will take another crack at getting nodeSelector to stop turning map{} into nil, tomorrow.

@bparees bparees force-pushed the merging branch 2 times, most recently from 111e2e3 to 1fad6cf Compare October 23, 2016 07:49
@bparees
Copy link
Contributor Author

bparees commented Oct 23, 2016

ok got the nil vs {} behavior working thanks to @smarterclayton's tip about protobuf logic.

@smarterclayton not sure if you want to do a general review of this (or double check the api changes for the optional map at least). still hoping to merge this before monday morning.

@openshift-bot
Copy link
Contributor

Evaluated for origin testextended up to c1e03fe

@openshift-bot
Copy link
Contributor

Evaluated for origin test up to c1e03fe

@openshift-bot
Copy link
Contributor

continuous-integration/openshift-jenkins/test SUCCESS (https://ci.openshift.redhat.com/jenkins/job/test_pr_origin/10498/) (Base Commit: 2047a73)

@openshift-bot
Copy link
Contributor

continuous-integration/openshift-jenkins/testextended SUCCESS (https://ci.openshift.redhat.com/jenkins/job/test_pr_origin_extended/663/) (Base Commit: 2047a73) (Extended Tests: core(builds))

@bparees
Copy link
Contributor Author

bparees commented Oct 24, 2016

[merge]

@openshift-bot
Copy link
Contributor

openshift-bot commented Oct 24, 2016

continuous-integration/openshift-jenkins/merge SUCCESS (https://ci.openshift.redhat.com/jenkins/job/test_pr_origin/10498/) (Image: devenv-rhel7_5236)

@openshift-bot
Copy link
Contributor

Evaluated for origin merge up to c1e03fe

@openshift-bot openshift-bot merged commit 1566e8d into openshift:master Oct 24, 2016
@bparees bparees deleted the merging branch October 24, 2016 18:12
@smarterclayton
Copy link
Contributor

You're tagged.

On Thu, Oct 20, 2016 at 6:13 PM, OpenShift Bot notifications@github.com
wrote:

continuous-integration/openshift-jenkins/test Running (
https://ci.openshift.redhat.com/jenkins/job/test_pr_origin/10351/) (Base
Commit: e1d9196
e1d9196
)


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#11380 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABG_p0W10R8YNEy87ooIwThLcw1rBd_oks5q1-eegaJpZM4KXhfQ
.

@bparees
Copy link
Contributor Author

bparees commented Oct 25, 2016

@smarterclayton also an already merged PR....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Proposal: Build isolation
7 participants