Skip to content

Conversation

@bbguimaraes
Copy link
Contributor

Basic implementation of the multi-stage test type. Subset of
#85, based on
#98. Design document:
https://docs.google.com/document/d/1md-1BMf4_7mtKgGVoeZ3jOh4zSIBSjwl6vTTAYESwIM.

Fully-functional but lacking management of artifacts, the test secret, cluster
profile, and release image(s).

@openshift-ci-robot openshift-ci-robot added the size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. label Aug 26, 2019
@openshift-ci-robot openshift-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Aug 26, 2019
Copy link
Member

@petr-muller petr-muller left a comment

Choose a reason for hiding this comment

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

Pretty well readable ever for me not really knowing what to expect here, nice job!

Some comments and questions inline.

RpmBuildCommands: "make rpms",
Images: []ProjectDirectoryImageBuildStepConfiguration{{To: "img"}},
}
for _, i := range []string{
Copy link
Member

Choose a reason for hiding this comment

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

Personally I would prefer this to be t.Run() based so it's executed in parallel and does not stop on first failure.

if testStep.MultiStageTestConfiguration != nil {
var err error
if step, err = steps.MultiStageTestStep(*testStep, config, podClient, jobSpec); err != nil {
return nil, nil, fmt.Errorf("unable to create end to end test step: %v", err)
Copy link
Member

Choose a reason for hiding this comment

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

Are we actually calling this end to end step in the user-facing documentation? The design doc calls it multi-stage tests, I think we should call it that way even 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.

No longer relevant because of #100 (comment).

func (s *multiStageTestStep) Done() (bool, error) { return false, nil }
func (s *multiStageTestStep) Name() string { return s.name }
func (s *multiStageTestStep) Description() string {
return fmt.Sprintf("Run test %s", s.name)
Copy link
Member

Choose a reason for hiding this comment

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

"Run multi-stage test..."?

}
func (s *multiStageTestStep) SubTests() []*junit.TestCase { return s.subTests }

func (s multiStageTestStep) runSteps(ctx context.Context, steps []api.TestStep, shortCircuit bool) error {
Copy link
Member

Choose a reason for hiding this comment

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

Hmm... is the non-pointer receiver here intentional?

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. Is there a problem?

Copy link
Contributor

Choose a reason for hiding this comment

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

Why?

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, that's what I asked.

Using a value receiver was based on the (reasonable but not optimal) assumption that it is what methods that don't mutate the object should use. After reading some golang nonsense, I suppose the size of the object is the contention here? Changed.

Copy link
Member

Choose a reason for hiding this comment

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

If we don't mutate the object, we don't need to pass the pointer.

Copy link
Contributor

Choose a reason for hiding this comment

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

Right but we also don't need to copy the values around every time either
/shrug

if errs != nil {
return nil, utilerrors.NewAggregate(errs)
}
return ret, nil
Copy link
Member

Choose a reason for hiding this comment

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

This could be simplified to just return ret, utilerrors.NewAggregate(errs) as NewAggregate is documented to return nil for an empty slice.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Didn't know that.

for _, step := range steps {
image := step.Image
if s.config.IsPipelineImage(step.Image) {
image = api.PipelineImageStream + ":" + step.Image
Copy link
Member

Choose a reason for hiding this comment

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

would prefer Sprintf over concatenation

errs = append(errs, err)
continue
}
name := s.name + "-" + step.Name
Copy link
Member

Choose a reason for hiding this comment

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

here too

return s.runPods(ctx, pods, shortCircuit)
}

func (s multiStageTestStep) genPods(steps []api.TestStep) ([]coreapi.Pod, error) {
Copy link
Member

Choose a reason for hiding this comment

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

No need to cut the name here. let it be generatePods

break
}
}
if err := waitForPodCompletion(s.podClient.Pods(s.jobSpec.Namespace), pod.Name, nil, false); err != nil {
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to pass a notifier 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.

Yes.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this outstanding?

}
}
if errs == nil {
return nil
Copy link
Member

Choose a reason for hiding this comment

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

no need for that

podClient PodClient,
jobSpec *api.JobSpec,
) (api.Step, error) {
return newMultiStageTestStep(testConfig, config, podClient, jobSpec), nil
Copy link
Member

Choose a reason for hiding this comment

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

why we need an extra unexported function and pass again the same args there, to return a new struct?

Also, this doesn't return any 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.

Error removed, the extra function is used by the tests.

want: true,
}} {
t.Run(tc.name, func(t *testing.T) {
if ret := tc.conf.BuildsImage("image"); ret != tc.want {
Copy link
Contributor

Choose a reason for hiding this comment

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

"image" here is a critical input to understanding a test case -- let's add it as a field to the test struct

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure what you mean, do you want image: "image" repeated in all cases?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes

Copy link
Contributor

Choose a reason for hiding this comment

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

I read

{
		name: "not in `images`",
		conf: ReleaseBuildConfiguration{
			Images: []ProjectDirectoryImageBuildStepConfiguration{
				{To: "egami"},
			},
		},
	}

As a test case and it's not obvious to me what is or is not in the list, and even what we expect the output to be

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 it's easier to read the test code here (a one-line conditional), but… changed.

Copy link
Contributor

@stevekuznetsov stevekuznetsov left a comment

Choose a reason for hiding this comment

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

Looking great. Two things I'd like to see:

  • a test that starts from CI Operator configuration YAML and tests the output YAML for the Pods end-to-end, with all of the bells and whistles -- artifacts, resources, etc
  • where are we calling resolver.Resolve? before we do step = steps.MultiStageTestStep(*testStep, config, podClient, jobSpec) right?

want: true,
}} {
t.Run(tc.name, func(t *testing.T) {
if ret := tc.conf.BuildsImage("image"); ret != tc.want {
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes

want: true,
}} {
t.Run(tc.name, func(t *testing.T) {
if ret := tc.conf.BuildsImage("image"); ret != tc.want {
Copy link
Contributor

Choose a reason for hiding this comment

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

I read

{
		name: "not in `images`",
		conf: ReleaseBuildConfiguration{
			Images: []ProjectDirectoryImageBuildStepConfiguration{
				{To: "egami"},
			},
		},
	}

As a test case and it's not obvious to me what is or is not in the list, and even what we expect the output to be

podClient PodClient
jobSpec *api.JobSpec
pre, test, post []api.TestStep
subTests []*junit.TestCase
Copy link
Contributor

Choose a reason for hiding this comment

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

We should elide this until we do the follow-up to add it

}
}

func TestGenPods(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: TestGeneratePods

expected := []coreapi.Pod{{
ObjectMeta: meta.ObjectMeta{
Name: "test-step0",
Labels: 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.

Shouldn't we be expecting org/repo/branch 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.

This was based on master before #92 merged.

Copy link
Contributor

Choose a reason for hiding this comment

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

So shouldn't this test be failing right now?

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 should have been more explicit in my comment, I corrected that when I rebased.

var pods []*coreapi.Pod
fakecs.PrependReactor("create", "pods", func(action clientgo_testing.Action) (bool, runtime.Object, error) {
pod := action.(clientgo_testing.CreateAction).GetObject().(*coreapi.Pod)
for _, f := range tc.failures {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: we're not running out of bytes in GitHub's git servers so let's use full variable names

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You'll hear from my orthopedist.

names = append(names, p.ObjectMeta.Name)
}
if !reflect.DeepEqual(names, tc.expected) {
t.Error(diff.ObjectReflectDiff(names, tc.expected))
Copy link
Contributor

Choose a reason for hiding this comment

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

In general for people who have never touched the tests in this file, it would be nice to be much more verbose:

t.Errorf("%s: did not execute correct pods: %v", testCase.name,  diff.ObjectReflectDiff(names, tc.expected))

In addition, I find it nice to explain what and why in the test name, like "test step fails so no other test steps run" or "pre step fails so no test steps run"

@bbguimaraes
Copy link
Contributor Author

Rebased to include #92, but only 185ce21^..1e7f9a4 are new commits.

@openshift-ci-robot openshift-ci-robot added the ¯\_(ツ)_/¯ ¯\\\_(ツ)_/¯ label Aug 28, 2019
Copy link
Contributor

@stevekuznetsov stevekuznetsov left a comment

Choose a reason for hiding this comment

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

Couple things seem outstanding otherwise this LGTM

break
}
}
if err := waitForPodCompletion(s.podClient.Pods(s.jobSpec.Namespace), pod.Name, nil, false); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this outstanding?

}.AsSelector().String(),
},
)
if errors.IsNotFound(err) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems outstanding?

@bbguimaraes
Copy link
Contributor Author

I think all reviews have been addressed. Rebased, squashed.

@bbguimaraes
Copy link
Contributor Author

Follow-up work identified in the review (other than those listed in the original description):

  • add tests for the validation code changed in this PR
  • log pods as JSON in dry-run mode

Copy link
Contributor

@stevekuznetsov stevekuznetsov left a comment

Choose a reason for hiding this comment

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

Let's either resolve the comment about the notifier if it's not needed or add it as a sub-task to the JIRA card so we don't lose it.

/lgtm
/approve
/hold

Whenever you do whatever is appropriate there, @bbguimaraes feel free to cancel the hold

@openshift-ci-robot openshift-ci-robot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Aug 29, 2019
@openshift-ci-robot openshift-ci-robot added the lgtm Indicates that a PR is ready to be merged. label Aug 29, 2019
@openshift-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: bbguimaraes, stevekuznetsov

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:
  • OWNERS [bbguimaraes,stevekuznetsov]

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@bbguimaraes
Copy link
Contributor Author

Ah, it took me a while to understand what the notifier question was about. The final version of the code, with artifact handling, does pass an artifact worker as a notifier — that's what I was thinking about when I read and responded to the question.

It is not necessary here because the lifetime of the pod is exactly how much we want to wait. There is even a higher-level function we could use that does exactly that, but it would add noise to the subsequent PRs.

@stevekuznetsov
Copy link
Contributor

ACK

/hold cancel

@openshift-ci-robot openshift-ci-robot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Aug 29, 2019
@openshift-merge-robot openshift-merge-robot merged commit ffe7773 into openshift:master Aug 29, 2019
@bbguimaraes bbguimaraes deleted the steps0 branch August 30, 2019 07:47
wking added a commit to wking/ci-tools that referenced this pull request Oct 8, 2020
…artPod

This seems generically useful, and not something that needs to be
restricted to multi-step, where it has been since 8cf5f87
(ci-operator multi-stage: MultiStageTestStep, 2019-07-30, openshift#100).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. lgtm Indicates that a PR is ready to be merged. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. ¯\_(ツ)_/¯ ¯\\\_(ツ)_/¯

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants