Skip to content

Commit e6088e1

Browse files
Merge pull request #429 from harishsurf/parallelize-export
Bug 1880127: Parallelize opm index export
2 parents 8025636 + 338b4fa commit e6088e1

File tree

5 files changed

+125
-54
lines changed

5 files changed

+125
-54
lines changed

cmd/opm/index/export.go

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import (
1212
var exportLong = templates.LongDesc(`
1313
Export an operator from an index image into the appregistry format.
1414
15-
This command will take an index image (specified by the --index option), parse it for the given operator (set by
16-
the --operator option) and export the operator metadata into an appregistry compliant format (a package.yaml file).
15+
This command will take an index image (specified by the --index option), parse it for the given operator(s) (set by
16+
the --package option) and export the operator metadata into an appregistry compliant format (a package.yaml file).
1717
This command requires access to docker or podman to complete successfully.
1818
1919
Note: the appregistry format is being deprecated in favor of the new index image and image bundle format.
@@ -40,10 +40,7 @@ func newIndexExportCmd() *cobra.Command {
4040
if err := indexCmd.MarkFlagRequired("index"); err != nil {
4141
logrus.Panic("Failed to set required `index` flag for `index export`")
4242
}
43-
indexCmd.Flags().StringP("package", "o", "", "the package to export")
44-
if err := indexCmd.MarkFlagRequired("package"); err != nil {
45-
logrus.Panic("Failed to set required `package` flag for `index export`")
46-
}
43+
indexCmd.Flags().StringSliceP("package", "p", nil, "comma separated list of packages to export")
4744
indexCmd.Flags().StringP("download-folder", "f", "downloaded", "directory where downloaded operator bundle(s) will be stored")
4845
indexCmd.Flags().StringP("container-tool", "c", "none", "tool to interact with container images (save, build, etc.). One of: [none, docker, podman]")
4946
if err := indexCmd.Flags().MarkHidden("debug"); err != nil {
@@ -60,7 +57,7 @@ func runIndexExportCmdFunc(cmd *cobra.Command, args []string) error {
6057
return err
6158
}
6259

63-
packageName, err := cmd.Flags().GetString("package")
60+
packages, err := cmd.Flags().GetStringSlice("package")
6461
if err != nil {
6562
return err
6663
}
@@ -80,15 +77,15 @@ func runIndexExportCmdFunc(cmd *cobra.Command, args []string) error {
8077
return err
8178
}
8279

83-
logger := logrus.WithFields(logrus.Fields{"index": index, "package": packageName})
80+
logger := logrus.WithFields(logrus.Fields{"index": index, "package": packages})
8481

8582
logger.Info("export from the index")
8683

8784
indexExporter := indexer.NewIndexExporter(containertools.NewContainerTool(containerTool, containertools.NoneTool), logger)
8885

8986
request := indexer.ExportFromIndexRequest{
9087
Index: index,
91-
Package: packageName,
88+
Packages: packages,
9289
DownloadPath: downloadPath,
9390
ContainerTool: containertools.NewContainerTool(containerTool, containertools.NoneTool),
9491
SkipTLS: skipTLS,

pkg/lib/bundle/exporter.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func (i *BundleExporter) Export() error {
4444
var rerr error
4545
switch i.containerTool {
4646
case containertools.NoneTool:
47-
reg, rerr = containerdregistry.NewRegistry(containerdregistry.WithLog(log))
47+
reg, rerr = containerdregistry.NewRegistry(containerdregistry.WithLog(log), containerdregistry.WithCacheDir(filepath.Join(tmpDir, "cacheDir")))
4848
case containertools.PodmanTool:
4949
fallthrough
5050
case containertools.DockerTool:

pkg/lib/indexer/indexer.go

Lines changed: 74 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ import (
66
"fmt"
77
"io"
88
"io/ioutil"
9+
"math/rand"
910
"os"
1011
"path"
1112
"path/filepath"
13+
"strconv"
14+
"sync"
1215

1316
"github.com/sirupsen/logrus"
1417
"gopkg.in/yaml.v2"
@@ -26,12 +29,13 @@ import (
2629
)
2730

2831
const (
29-
defaultDockerfileName = "index.Dockerfile"
30-
defaultImageTag = "operator-registry-index:latest"
31-
defaultDatabaseFolder = "database"
32-
defaultDatabaseFile = "index.db"
33-
tmpDirPrefix = "index_tmp_"
34-
tmpBuildDirPrefix = "index_build_tmp"
32+
defaultDockerfileName = "index.Dockerfile"
33+
defaultImageTag = "operator-registry-index:latest"
34+
defaultDatabaseFolder = "database"
35+
defaultDatabaseFile = "index.db"
36+
tmpDirPrefix = "index_tmp_"
37+
tmpBuildDirPrefix = "index_build_tmp"
38+
concurrencyLimitForExport = 10
3539
)
3640

3741
// ImageIndexer is a struct implementation of the Indexer interface
@@ -467,7 +471,7 @@ func write(dockerfileText, outDockerfile string, logger *logrus.Entry) error {
467471
// ExportFromIndexRequest defines the parameters to send to the ExportFromIndex API
468472
type ExportFromIndexRequest struct {
469473
Index string
470-
Package string
474+
Packages []string
471475
DownloadPath string
472476
ContainerTool containertools.ContainerTool
473477
CaFile string
@@ -497,14 +501,20 @@ func (i ImageIndexer) ExportFromIndex(request ExportFromIndexRequest) error {
497501
defer db.Close()
498502

499503
dbQuerier := sqlite.NewSQLLiteQuerierFromDb(db)
500-
if err != nil {
501-
return err
504+
505+
// fetch all packages from the index image if packages is empty
506+
if len(request.Packages) == 0 {
507+
request.Packages, err = dbQuerier.ListPackages(context.TODO())
508+
if err != nil {
509+
return err
510+
}
502511
}
503512

504-
bundles, err := getBundlesToExport(dbQuerier, request.Package)
513+
bundles, err := getBundlesToExport(dbQuerier, request.Packages)
505514
if err != nil {
506515
return err
507516
}
517+
508518
i.Logger.Infof("Preparing to pull bundles %+q", bundles)
509519

510520
// Creating downloadPath dir
@@ -513,40 +523,68 @@ func (i ImageIndexer) ExportFromIndex(request ExportFromIndexRequest) error {
513523
}
514524

515525
var errs []error
516-
for _, bundleImage := range bundles {
517-
// try to name the folder
518-
folderName, err := dbQuerier.GetBundleVersion(context.TODO(), bundleImage)
519-
if err != nil {
520-
return err
521-
}
522-
if folderName == "" {
523-
// operator-registry does not care about the folder name
524-
folderName = bundleImage
525-
}
526-
exporter := bundle.NewExporterForBundle(bundleImage, filepath.Join(request.DownloadPath, folderName), request.ContainerTool)
527-
if err := exporter.Export(); err != nil {
528-
err = fmt.Errorf("error exporting bundle from image: %s", err)
529-
errs = append(errs, err)
530-
}
531-
}
532-
if err != nil {
533-
errs = append(errs, err)
526+
var wg sync.WaitGroup
527+
wg.Add(len(bundles))
528+
var mu = &sync.Mutex{}
529+
530+
sem := make(chan struct{}, concurrencyLimitForExport)
531+
532+
for bundleImage, bundleDir := range bundles {
533+
go func(bundleImage string, bundleDir bundleDirPrefix) {
534+
defer wg.Done()
535+
536+
sem <- struct{}{}
537+
defer func() {
538+
<-sem
539+
}()
540+
541+
// generate a random folder name if bundle version is empty
542+
if bundleDir.bundleVersion == "" {
543+
bundleDir.bundleVersion = strconv.Itoa(rand.Intn(10000))
544+
}
545+
exporter := bundle.NewExporterForBundle(bundleImage, filepath.Join(request.DownloadPath, bundleDir.pkgName, bundleDir.bundleVersion), request.ContainerTool)
546+
if err := exporter.Export(); err != nil {
547+
err = fmt.Errorf("exporting bundle image:%s failed with %s", bundleImage, err)
548+
mu.Lock()
549+
errs = append(errs, err)
550+
mu.Unlock()
551+
}
552+
}(bundleImage, bundleDir)
553+
}
554+
// Wait for all the go routines to finish export
555+
wg.Wait()
556+
557+
if errs != nil {
534558
return utilerrors.NewAggregate(errs)
535559
}
536560

537-
err = generatePackageYaml(dbQuerier, request.Package, request.DownloadPath)
538-
if err != nil {
539-
errs = append(errs, err)
561+
for _, packageName := range request.Packages {
562+
err := generatePackageYaml(dbQuerier, packageName, filepath.Join(request.DownloadPath, packageName))
563+
if err != nil {
564+
errs = append(errs, err)
565+
}
540566
}
541567
return utilerrors.NewAggregate(errs)
542568
}
543569

544-
func getBundlesToExport(dbQuerier pregistry.Query, packageName string) ([]string, error) {
545-
bundles, err := dbQuerier.GetBundlePathsForPackage(context.TODO(), packageName)
546-
if err != nil {
547-
return nil, err
570+
type bundleDirPrefix struct {
571+
pkgName, bundleVersion string
572+
}
573+
574+
func getBundlesToExport(dbQuerier pregistry.Query, packages []string) (map[string]bundleDirPrefix, error) {
575+
bundleMap := make(map[string]bundleDirPrefix)
576+
577+
for _, packageName := range packages {
578+
bundlesForPackage, err := dbQuerier.GetBundlesForPackage(context.TODO(), packageName)
579+
if err != nil {
580+
return nil, err
581+
}
582+
for k, _ := range bundlesForPackage {
583+
bundleMap[k.BundlePath] = bundleDirPrefix{pkgName: packageName, bundleVersion: k.Version}
584+
}
548585
}
549-
return bundles, nil
586+
587+
return bundleMap, nil
550588
}
551589

552590
func generatePackageYaml(dbQuerier pregistry.Query, packageName, downloadPath string) error {

pkg/lib/indexer/indexer_test.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,16 @@ func TestGetBundlesToExport(t *testing.T) {
3030
t.Fatalf("creating querier: %s", err)
3131
}
3232

33-
bundleImages, err := getBundlesToExport(dbQuerier, "etcd")
33+
bundleMap, err := getBundlesToExport(dbQuerier, []string {"etcd"})
3434
if err != nil {
3535
t.Fatalf("exporting bundles from db: %s", err)
3636
}
37+
38+
var bundleImages []string
39+
for bundlePath, _ := range bundleMap {
40+
bundleImages = append(bundleImages, bundlePath)
41+
}
42+
3743
sort.Strings(bundleImages)
3844

3945
if !reflect.DeepEqual(expected, bundleImages) {

test/e2e/opm_test.go

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -137,13 +137,30 @@ func pushWith(containerTool, image string) error {
137137
return dockerpush.Run()
138138
}
139139

140-
func exportWith(containerTool string) error {
141-
logger := logrus.WithFields(logrus.Fields{"package": packageName})
140+
func exportPackageWith(containerTool string) error {
141+
packages := []string{packageName}
142+
logger := logrus.WithFields(logrus.Fields{"package": packages})
142143
indexExporter := indexer.NewIndexExporter(containertools.NewContainerTool(containerTool, containertools.NoneTool), logger)
143144

144145
request := indexer.ExportFromIndexRequest{
145146
Index: indexImage2,
146-
Package: packageName,
147+
Packages: packages,
148+
DownloadPath: "downloaded",
149+
ContainerTool: containertools.NewContainerTool(containerTool, containertools.NoneTool),
150+
}
151+
152+
return indexExporter.ExportFromIndex(request)
153+
}
154+
155+
156+
func exportIndexImageWith(containerTool string) error {
157+
158+
logger := logrus.NewEntry(logrus.New())
159+
indexExporter := indexer.NewIndexExporter(containertools.NewContainerTool(containerTool, containertools.NoneTool), logger)
160+
161+
request := indexer.ExportFromIndexRequest{
162+
Index: indexImage2,
163+
Packages: []string{},
147164
DownloadPath: "downloaded",
148165
ContainerTool: containertools.NewContainerTool(containerTool, containertools.NoneTool),
149166
}
@@ -272,8 +289,8 @@ var _ = Describe("opm", func() {
272289
err = pushWith(containerTool, indexImage3)
273290
Expect(err).NotTo(HaveOccurred())
274291

275-
By("exporting an index to disk")
276-
err = exportWith(containerTool)
292+
By("exporting a package from an index to disk")
293+
err = exportPackageWith(containerTool)
277294
Expect(err).NotTo(HaveOccurred())
278295

279296
By("loading manifests from a directory")
@@ -284,13 +301,26 @@ var _ = Describe("opm", func() {
284301
err = os.RemoveAll("downloaded")
285302
Expect(err).NotTo(HaveOccurred())
286303

287-
By("exporting an index to disk with containerd")
288-
err = exportWith(containertools.NoneTool.String())
304+
By("exporting a package from an index to disk with containerd")
305+
err = exportPackageWith(containertools.NoneTool.String())
289306
Expect(err).NotTo(HaveOccurred())
290307

291308
By("loading manifests from a containerd-extracted directory")
292309
err = initialize()
293310
Expect(err).NotTo(HaveOccurred())
311+
312+
// clean containerd-extracted directory
313+
err = os.RemoveAll("downloaded")
314+
Expect(err).NotTo(HaveOccurred())
315+
316+
By("exporting an entire index to disk")
317+
err = exportIndexImageWith(containerTool)
318+
Expect(err).NotTo(HaveOccurred())
319+
320+
By("loading manifests from a directory")
321+
err = initialize()
322+
Expect(err).NotTo(HaveOccurred())
323+
294324
})
295325

296326
It("build bundles and index via inference", func() {

0 commit comments

Comments
 (0)