diff --git a/pkg/cmd/openshift-tests/images/images_command.go b/pkg/cmd/openshift-tests/images/images_command.go index 7fda52c51b7f..2184d5867309 100644 --- a/pkg/cmd/openshift-tests/images/images_command.go +++ b/pkg/cmd/openshift-tests/images/images_command.go @@ -1,16 +1,20 @@ package images import ( + "context" "fmt" + "log" "os" "sort" "strings" + "time" k8simage "k8s.io/kubernetes/test/utils/image" "github.com/openshift/library-go/pkg/image/reference" "github.com/openshift/origin/pkg/clioptions/imagesetup" "github.com/openshift/origin/pkg/cmd" + "github.com/openshift/origin/pkg/test/externalbinary" "github.com/openshift/origin/test/extended/util/image" "github.com/spf13/cobra" "k8s.io/kube-openapi/pkg/util/sets" @@ -151,7 +155,36 @@ type imagesOptions struct { func createImageMirrorForInternalImages(prefix string, ref reference.DockerImageReference, mirrored bool) ([]string, error) { source := ref.Exact() + // This is coming from the vendored directory initialDefaults := k8simage.GetOriginalImageConfigs() + + // If ENV is set, this should come from external binary + if len(os.Getenv("OPENSHIFT_SKIP_EXTERNAL_TESTS")) == 0 { + // Extract all test binaries + extractLogger := log.New(os.Stdout, "", log.LstdFlags|log.Lmicroseconds) + extractionContext, extractionContextCancel := context.WithTimeout(context.Background(), 30*time.Minute) + defer extractionContextCancel() + cleanUpFn, externalBinaries, err := externalbinary.ExtractAllTestBinaries(extractionContext, extractLogger, 10) + if err != nil { + return nil, err + } + defer cleanUpFn() + + // List test images from all available binaries and convert them to origin's testCase format + listContext, listContextCancel := context.WithTimeout(context.Background(), time.Minute) + defer listContextCancel() + + imagesFromExternalBinaries, err := externalBinaries.ListImages(listContext, 10) + if err != nil { + return nil, err + } + + if len(imagesFromExternalBinaries) > 0 { + // FIXME: only 1 for now + initialDefaults = imagesFromExternalBinaries[0] + } + } + exceptions := image.Exceptions.List() defaults := map[k8simage.ImageID]k8simage.Config{} diff --git a/pkg/test/externalbinary/binary.go b/pkg/test/externalbinary/binary.go index edf012d19c57..0e90ac779123 100644 --- a/pkg/test/externalbinary/binary.go +++ b/pkg/test/externalbinary/binary.go @@ -18,6 +18,7 @@ import ( "github.com/pkg/errors" kapierrs "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8simage "k8s.io/kubernetes/test/utils/image" "github.com/openshift/origin/test/extended/util" ) @@ -80,6 +81,27 @@ func (b *TestBinary) ListTests(ctx context.Context) (ExtensionTestSpecs, error) return tests, nil } +func (b *TestBinary) ListImages(ctx context.Context) (map[k8simage.ImageID]k8simage.Config, error) { + start := time.Now() + binName := filepath.Base(b.path) + + b.logger.Printf("Listing images for %q", binName) + command := exec.Command(b.path, "images") + output, err := runWithTimeout(ctx, command, 10*time.Minute) + if err != nil { + return nil, fmt.Errorf("failed running '%s list': %w", b.path, err) + } + + images := make(map[k8simage.ImageID]k8simage.Config) + err = json.Unmarshal(output, &images) + if err != nil { + return nil, err + } + + b.logger.Printf("Listed %d test images for %q in %v", len(images), binName, time.Since(start)) + return images, nil +} + // ExtractAllTestBinaries determines the optimal release payload to use, and extracts all the external // test binaries from it, and returns a slice of them. func ExtractAllTestBinaries(ctx context.Context, logger *log.Logger, parallelism int) (func(), TestBinaries, error) { @@ -223,6 +245,68 @@ func ExtractAllTestBinaries(ctx context.Context, logger *log.Logger, parallelism type TestBinaries []*TestBinary +func (binaries TestBinaries) ListImages(ctx context.Context, parallelism int) ([]map[k8simage.ImageID]k8simage.Config, error) { + var ( + allImages []map[k8simage.ImageID]k8simage.Config + mu sync.Mutex + wg sync.WaitGroup + errCh = make(chan error, len(binaries)) + jobCh = make(chan *TestBinary) + ) + + // Producer: sends jobs to the jobCh channel + go func() { + defer close(jobCh) + for _, binary := range binaries { + select { + case <-ctx.Done(): + return // Exit when context is cancelled + case jobCh <- binary: + } + } + }() + + // Consumer workers: extract tests concurrently + for i := 0; i < parallelism; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for { + select { + case <-ctx.Done(): + return // Exit when context is cancelled + case binary, ok := <-jobCh: + if !ok { + return // Channel was closed + } + images, err := binary.ListImages(ctx) + if err != nil { + errCh <- err + } + mu.Lock() + allImages = append(allImages, images) + mu.Unlock() + } + } + }() + } + + // Wait for all workers to finish + wg.Wait() + close(errCh) + + // Check if any errors were reported + var errs []string + for err := range errCh { + errs = append(errs, err.Error()) + } + if len(errs) > 0 { + return nil, fmt.Errorf("encountered errors while listing tests: %s", strings.Join(errs, ";")) + } + + return allImages, nil +} + // ListTests extracts the tests from all TestBinaries using the specified parallelism. func (binaries TestBinaries) ListTests(ctx context.Context, parallelism int) (ExtensionTestSpecs, error) { var (