Skip to content

Create a generic trigger controller for image streams #13242

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

Merged
merged 10 commits into from
May 18, 2017

Conversation

smarterclayton
Copy link
Contributor

@smarterclayton smarterclayton commented Mar 6, 2017

Create the structure for a generic image change trigger controller. The controller is passed a set of informers, which also specify how the data is loaded off the object for triggers and how the updated image is processed. The controller indexes triggers to provide O(1) notifications, and in theory, based on a shared informer, should be as efficient as can be.

Demonstrates how an annotation based trigger could be loaded and how builds can be triggered.

TODOs:

  • tests to validate caching is correct
  • deployment config change trigger example
  • jsonpath reaction for annotations
  • handle expectations (triggered resource update)
  • do a memory and perf test
  • validate this can be converted easily to shared informers

@bparees @mfojtik

@smarterclayton
Copy link
Contributor Author

smarterclayton commented Mar 6, 2017

[merge]

Instantiator BuildConfigInstantiator
}

func (r *BuildConfigReaction) ImageChanged(obj interface{}, is *imageapi.ImageStream, operations []TriggerOperation) error {
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 think this is equivalent to the bc image_change_controller.

// from is the object this should trigger from. The kind and name fields must be set.
From ObjectReference `json:"from"`
// fieldPath is a JSONPath string to the field to edit on the object. Required.
FieldPath string `json:"fieldPath"`
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 call this to ?

Copy link
Contributor

Choose a reason for hiding this comment

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

fieldPath is clearer

// namespaced, or if left empty on a namespaced object, means the current namespace.
Namespace string `json:"namespace,omitempty"`
// apiVersion is the group and version the type exists in. Optional.
APIVersion string `json:"apiVersion,omitempty"`
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 need Group here as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

APIVersion is group and version.

return nil, false, nil
}
r.Namespace = m.GetNamespace()
r.Key = r.Namespace + "/" + m.GetName()
Copy link
Contributor

Choose a reason for hiding this comment

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

i believe there is a helper for this somewhere in indexer.

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 can return an error, which isn't the case here.

if len(namespace) == 0 {
namespace = entry.Namespace
}
keys = append(keys, namespace+"/"+name)
Copy link
Contributor

Choose a reason for hiding this comment

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

and the same helper should be used here.

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

return triggerCache{
ThreadSafeStore: cache.NewThreadSafeStore(
cache.Indexers{
"images": func(obj interface{}) ([]string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

@Kargakis you were advocating last time I was making my private indexers that they should all live in shared package :-)

Copy link
Contributor

Choose a reason for hiding this comment

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

Last time you didn't have a generic controller below you :)

entry := obj.(*TriggerCacheEntry)
var keys []string
for _, t := range entry.Triggers {
if t.From.Kind != "ImageStreamTag" || len(t.From.APIVersion) != 0 || t.Paused {
Copy link
Contributor

Choose a reason for hiding this comment

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

@smarterclayton this will work just for images or do we plan to make it generic enough to work with other resource types, like secrets or config maps?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A suggest has already been made for a PodPreset trigger. I think we can refactor this at that time - trigger.ObjectFieldTrigger is fairly generic, but not totally generic. Maybe pkg/trigger and then some helper code for this and others to be reused.

@mfojtik
Copy link
Contributor

mfojtik commented Mar 6, 2017

@smarterclayton this looks really good, I will have closer look later today :)

@mfojtik
Copy link
Contributor

mfojtik commented Mar 6, 2017

@soltysh FYI since you were interested in this area

@0xmichalis
Copy link
Contributor

As a follow-up to this, we should probably collapse DC config change triggers into the DC controller.

@smarterclayton
Copy link
Contributor Author

Ok, this should be close to a working prototype:

  1. Set of incoming shared informers for the types to react to (deployments, builds, etc) plus one for image streams
  2. Image stream informer drives a work queue sharded by image stream, that processes each image stream for all objects that trigger off of it (any image that registers a trigger that watches the stream by name) into a TriggerOperation, which is then put onto the operation queue, keyed by resource (i.e. deploymentconfigs/NAMESPACE/NAME).
  3. Each of the other informers has a TriggerIndexer that takes the items coming off each informer and calculates:
  4. whether triggers are registered or whether they have changed (to update the index of streams -> triggered objects)
  5. the latest state of the triggers as represented by []TriggerOperation
  6. Each triggered informer keeps the cache up to date (fast lookup of image stream to triggered object) and also queues trigger operations onto the operation queue
  7. The operation queue merges TriggerOperations based on RV - a newer read from the cache overrides the older item - to form the set of changes that need to be dispatched
  8. The resourceWorker in the controller grabs objects off the operation queue (sharded by object key) and tries to apply the reaction, such as launching a build.

Because image stream triggers are level driven, we can keep merging the queued operations forever.

If a triggered object and image stream are created at the same time, there is a window where:

  1. triggered object beats the image stream and reads nothing from the cache
  2. image stream update makes it to the worker, but the triggered object hasn't registered its triggers yet
  3. triggered object would not get fired

However, we can queue incomplete operations for the triggered object (tag didn't exist, go ahead and queue the operation), and as part of the resourceWorker do a lazy lookup on those tags. If the tags don't exist, requeue for later (backoff). That should ensure that we don't miss an update cycle. That is not yet implemented here.

@smarterclayton
Copy link
Contributor Author

smarterclayton commented Mar 22, 2017

Fixed the race condition - I check the for image updates on a triggered object after the item is added to the index - that means that either the image is in the cache already (processed by imageStreamWorker) or guaranteed to get the item from the index in the imageStreamWorker if it hasn't completed.

@smarterclayton smarterclayton force-pushed the generic_trigger branch 2 times, most recently from c6dd9b0 to 263d9da Compare March 22, 2017 04:23
}

// ObjectReference identifies an object by its name and kind.
type ObjectReference struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not use the existing ObjectReference type?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So that it's not dependent on kapi. Also doesn't need the same fields. Still effectively versioned, but not tied to Kube.

"github.com/openshift/origin/pkg/image/api/v1/trigger"
)

func calculateBuildConfigTriggers(bc *buildapi.BuildConfig, retrieveOperations bool) ([]trigger.ObjectFieldTrigger, []TriggerOperation) {
Copy link
Contributor

Choose a reason for hiding this comment

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

godoc

}

// ImageStreamNamespaceLister helps list and get ImageStreams.
type ImageStreamNamespaceLister interface {
Copy link
Contributor

Choose a reason for hiding this comment

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

The listers and informers in this file need to move to a different place.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is waiting for Andy's rebase anyway, so generated informers makes this irrelevant.

@0xmichalis
Copy link
Contributor

Try to keep the deployment example in a separate commit to make clearer what's needed for adding support for a resource.

Instantiate(namespace string, request *buildapi.BuildRequest) (*buildapi.Build, error)
}

type BuildConfigReaction struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

Reactor?

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.

@smarterclayton smarterclayton force-pushed the generic_trigger branch 3 times, most recently from 07cab0b to fc12799 Compare March 25, 2017 04:27
@0xmichalis 0xmichalis self-assigned this Mar 28, 2017
@@ -0,0 +1,24 @@
package trigger
Copy link
Contributor

Choose a reason for hiding this comment

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

Should move out of the image api

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These triggers are image specific. The struct is under the API because they are serialized into annotations for the generic path.

@openshift-bot
Copy link
Contributor

Evaluated for origin merge up to fb5994d

@smarterclayton
Copy link
Contributor Author

smarterclayton commented May 15, 2017 via email

@smarterclayton
Copy link
Contributor Author

[test]

@smarterclayton
Copy link
Contributor Author

smarterclayton commented May 16, 2017 via email

@0xmichalis
Copy link
Contributor

#14156 [test]

@openshift-bot openshift-bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label May 17, 2017
@openshift-bot openshift-bot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label May 17, 2017
Build a cache from changes to a set of watched objects that allow us to
efficiently detect which objects can be *triggered* by image changes.
Add a controller that watches image streams and mutates those objects
whenever the tag reference in the object no longer points to the same
docker image reference.

Fit the existing deployment config and build config triggers into this
framework, and add support for a new annotation based trigger. The
annotation trigger will use an array of references serialized under key
`image.alpha.openshift.io/triggers` that points to an image stream tag
and identifies a field path to mutate. Rather than implementing a
generic field path accessor, we define fast paths for known object
types. Future work should be able to leverage the unstructured type to
add triggers for third party resources or other unknown objects. Note
that triggers do not grant access to pull images.
Only on a limited set of types for now. Future work should detect which
types can have triggers.
We were not identifying container names correctly.
Remove the old build image trigger controller
@openshift-bot
Copy link
Contributor

Evaluated for origin testextended up to 0aec48e

@openshift-bot
Copy link
Contributor

Evaluated for origin test up to 0aec48e

@openshift-bot
Copy link
Contributor

continuous-integration/openshift-jenkins/test SUCCESS (https://ci.openshift.redhat.com/jenkins/job/test_pull_request_origin/1545/) (Base Commit: b370e3a)

@openshift-bot
Copy link
Contributor

continuous-integration/openshift-jenkins/testextended FAILURE (https://ci.openshift.redhat.com/jenkins/job/test_pull_request_origin_extended/421/) (Base Commit: b370e3a) (Extended Tests: core(build))

@smarterclayton
Copy link
Contributor Author

In order to get more soak on this I'm going to direct merge to bypass the queue.

@smarterclayton
Copy link
Contributor Author

We need the soak time in master more than the other things in the queue do.

@gabemontero
Copy link
Contributor

1 similar comment
@gabemontero
Copy link
Contributor

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