@@ -2,10 +2,16 @@ package specs
22
33import (
44 "context"
5+ "crypto/tls"
6+ "encoding/json"
57 "fmt"
8+ "io"
9+ "net/http"
10+ "os"
611 "os/exec"
712 "path/filepath"
813 "regexp"
14+ "strconv"
915 "strings"
1016 "time"
1117
@@ -322,6 +328,249 @@ var _ = g.Describe("[sig-olmv1][Jira:OLM] clustercatalog", g.Label("NonHyperShif
322328 exutil .AssertWaitPollNoErr (errWait , "Cannot get the result" )
323329 })
324330
331+ g .It ("PolarionID:85889-[OTP][Skipped:Disconnected]Validate catalogd content via port-forward [Serial]" , func () {
332+ caseID := "85889"
333+ catalogName := "catalog-" + caseID
334+ catalogImage := "quay.io/openshifttest/nginxolm-operator-index:nginxolm74924"
335+ baseDir := exutil .FixturePath ("testdata" , "olm" )
336+ catalogTemplate := filepath .Join (baseDir , "clustercatalog-with-pollinterval.yaml" )
337+ outputDir := "/tmp/" + caseID + "-" + exutil .GetRandomString ()
338+ err := os .MkdirAll (outputDir , 0755 )
339+ o .Expect (err ).NotTo (o .HaveOccurred ())
340+ defer os .RemoveAll (outputDir )
341+
342+ redhatOperatorsFile := filepath .Join (outputDir , "redhat-operators-all.json" )
343+ customCatalogFile := filepath .Join (outputDir , "all.json" )
344+
345+ type catalogEntry struct {
346+ Schema string `json:"schema"`
347+ Name string `json:"name,omitempty"`
348+ Package string `json:"package,omitempty"`
349+ }
350+
351+ parseCatalogEntries := func (data []byte ) ([]catalogEntry , error ) {
352+ var entries []catalogEntry
353+ for _ , line := range strings .Split (strings .TrimSpace (string (data )), "\n " ) {
354+ line = strings .TrimSpace (line )
355+ if line == "" || ! strings .HasPrefix (line , "{" ) {
356+ continue
357+ }
358+ var entry catalogEntry
359+ if err := json .Unmarshal ([]byte (line ), & entry ); err != nil {
360+ return nil , err
361+ }
362+ if entry .Schema != "" {
363+ entries = append (entries , entry )
364+ }
365+ }
366+ if len (entries ) == 0 {
367+ return nil , fmt .Errorf ("no catalog entries parsed" )
368+ }
369+ return entries , nil
370+ }
371+
372+ fetchToFile := func (url , path string ) ([]byte , error ) {
373+ client := & http.Client {
374+ Transport : & http.Transport {
375+ TLSClientConfig : & tls.Config {InsecureSkipVerify : true }, //nolint:gosec // uses catalogd route with test TLS
376+ },
377+ Timeout : 2 * time .Minute ,
378+ }
379+ req , err := http .NewRequestWithContext (context .TODO (), http .MethodGet , url , nil )
380+ if err != nil {
381+ return nil , err
382+ }
383+ resp , err := client .Do (req )
384+ if err != nil {
385+ return nil , err
386+ }
387+ defer resp .Body .Close ()
388+ if resp .StatusCode != http .StatusOK {
389+ return nil , fmt .Errorf ("unexpected status %s for %s" , resp .Status , url )
390+ }
391+ body , err := io .ReadAll (resp .Body )
392+ if err != nil {
393+ return nil , err
394+ }
395+ if err = os .WriteFile (path , body , 0600 ); err != nil {
396+ return nil , err
397+ }
398+ return body , nil
399+ }
400+
401+ getCatalogdLogs := func () (string , error ) {
402+ return oc .AsAdmin ().WithoutNamespace ().Run ("logs" ).Args ("-n" , "openshift-catalogd" , "deploy/catalogd-controller-manager" , "--since=10m" ).Output ()
403+ }
404+
405+ getReconcileTotal := func () (float64 , error ) {
406+ metrics , err := oc .AsAdmin ().WithoutNamespace ().Run ("get" ).Args ("--raw" , "/api/v1/namespaces/openshift-catalogd/services/catalogd-service:metrics/proxy/metrics" ).Output ()
407+ if err != nil {
408+ return 0 , err
409+ }
410+ re := regexp .MustCompile (`(?im)^.*reconcile_total.*clustercatalog.*\\s+([0-9]+(?:\\.[0-9]+)?)$` )
411+ matches := re .FindAllStringSubmatch (metrics , - 1 )
412+ if len (matches ) == 0 {
413+ return 0 , fmt .Errorf ("clustercatalog reconcile_total not found in metrics output" )
414+ }
415+ var max float64
416+ for _ , match := range matches {
417+ value , err := strconv .ParseFloat (match [1 ], 64 )
418+ if err != nil {
419+ continue
420+ }
421+ if value > max {
422+ max = value
423+ }
424+ }
425+ return max , nil
426+ }
427+
428+ clustercatalog := olmv1util.ClusterCatalogDescription {Name : catalogName }
429+ deleted := false
430+ defer func () {
431+ if ! deleted {
432+ clustercatalog .Delete (oc )
433+ }
434+ }()
435+
436+ g .By ("Check OLM namespaces in ClusterOperator" )
437+ nsOutput , err := oc .AsAdmin ().WithoutNamespace ().Run ("get" ).Args ("co" , "olm" , `-o=jsonpath={.status.relatedObjects[?(@.resource=="namespaces")].name}` ).Output ()
438+ o .Expect (err ).NotTo (o .HaveOccurred ())
439+ o .Expect (nsOutput ).To (o .ContainSubstring ("openshift-catalogd" ))
440+ o .Expect (nsOutput ).To (o .ContainSubstring ("openshift-operator-controller" ))
441+
442+ g .By ("Verify OLM CRDs referenced" )
443+ crdOutput , err := oc .AsAdmin ().WithoutNamespace ().Run ("get" ).Args ("co" , "olm" , `-o=jsonpath={.status.relatedObjects[?(@.resource=="customresourcedefinitions")].name}` ).Output ()
444+ o .Expect (err ).NotTo (o .HaveOccurred ())
445+ catalogCRDOK := strings .Contains (crdOutput , "clustercatalogs.catalogd.operatorframework.io" ) ||
446+ strings .Contains (crdOutput , "clustercatalogs.olm.operatorframework.io" )
447+ o .Expect (catalogCRDOK ).To (o .BeTrue ())
448+ o .Expect (crdOutput ).To (o .ContainSubstring ("clusterextensions.olm.operatorframework.io" ))
449+
450+ g .By ("Create test ClusterCatalog" )
451+ clustercatalog .Template = catalogTemplate
452+ clustercatalog .Imageref = catalogImage
453+ clustercatalog .PollIntervalMinutes = "300s"
454+ clustercatalog .Create (oc )
455+
456+ g .By ("Verify ClusterCatalog list and phases" )
457+ listOutput , err := oc .AsAdmin ().WithoutNamespace ().Run ("get" ).Args ("clustercatalog" , `-o=jsonpath={range .items[*]}{.metadata.name}{"="}{.status.phase}{"\n"}{end}` ).Output ()
458+ o .Expect (err ).NotTo (o .HaveOccurred ())
459+ o .Expect (listOutput ).To (o .ContainSubstring ("openshift-certified-operators=" ))
460+ o .Expect (listOutput ).To (o .ContainSubstring ("openshift-community-operators=" ))
461+ o .Expect (listOutput ).To (o .ContainSubstring ("openshift-redhat-operators=" ))
462+ o .Expect (listOutput ).To (o .ContainSubstring (catalogName + "=" ))
463+
464+ g .By ("Get catalog content URLs" )
465+ redhatCatalog := olmv1util.ClusterCatalogDescription {Name : "openshift-redhat-operators" }
466+ redhatCatalog .GetContentURL (oc )
467+
468+ g .By ("Create redhat-operators-all.json" )
469+ redhatData , err := fetchToFile (redhatCatalog .ContentURL , redhatOperatorsFile )
470+ o .Expect (err ).NotTo (o .HaveOccurred ())
471+ _ , err = os .Stat (redhatOperatorsFile )
472+ o .Expect (err ).NotTo (o .HaveOccurred ())
473+
474+ g .By ("Create all.json for catalog-85889" )
475+ customData , err := fetchToFile (clustercatalog .ContentURL , customCatalogFile )
476+ o .Expect (err ).NotTo (o .HaveOccurred ())
477+ _ , err = os .Stat (customCatalogFile )
478+ o .Expect (err ).NotTo (o .HaveOccurred ())
479+
480+ g .By ("List OLM packages from redhat-operators" )
481+ redhatEntries , err := parseCatalogEntries (redhatData )
482+ o .Expect (err ).NotTo (o .HaveOccurred ())
483+ var packageNames []string
484+ for _ , entry := range redhatEntries {
485+ if entry .Schema == "olm.package" && entry .Name != "" {
486+ packageNames = append (packageNames , entry .Name )
487+ }
488+ }
489+ o .Expect (packageNames ).NotTo (o .BeEmpty ())
490+
491+ g .By ("Get channel info for custom catalog" )
492+ customEntries , err := parseCatalogEntries (customData )
493+ o .Expect (err ).NotTo (o .HaveOccurred ())
494+ customPackages := map [string ]struct {}{}
495+ for _ , entry := range customEntries {
496+ if entry .Schema == "olm.package" && entry .Name != "" {
497+ customPackages [entry .Name ] = struct {}{}
498+ }
499+ }
500+ o .Expect (customPackages ).NotTo (o .BeEmpty ())
501+
502+ channelFound := false
503+ for _ , entry := range customEntries {
504+ if entry .Schema == "olm.channel" && entry .Package != "" {
505+ if _ , ok := customPackages [entry .Package ]; ok {
506+ channelFound = true
507+ break
508+ }
509+ }
510+ }
511+ o .Expect (channelFound ).To (o .BeTrue ())
512+
513+ g .By ("Get bundle info for custom catalog" )
514+ bundleFound := false
515+ for _ , entry := range customEntries {
516+ if entry .Schema == "olm.bundle" && entry .Package != "" && entry .Name != "" {
517+ if _ , ok := customPackages [entry .Package ]; ok {
518+ bundleFound = true
519+ break
520+ }
521+ }
522+ }
523+ o .Expect (bundleFound ).To (o .BeTrue ())
524+
525+ g .By ("Check ClusterCatalog reconciliation logs" )
526+ consoleLogs , err := getCatalogdLogs ()
527+ o .Expect (err ).NotTo (o .HaveOccurred ())
528+ consoleLogsLower := strings .ToLower (consoleLogs )
529+ o .Expect (strings .TrimSpace (consoleLogs )).NotTo (o .BeEmpty ())
530+ o .Expect (strings .Contains (consoleLogsLower , "panic" )).To (o .BeFalse ())
531+ o .Expect (strings .Contains (consoleLogsLower , "stack trace" )).To (o .BeFalse ())
532+
533+ var errWait error
534+
535+ g .By ("Check reconcile_total metrics before patch" )
536+ reconcileBefore , err := getReconcileTotal ()
537+ metricsAvailable := true
538+ if err != nil {
539+ metricsAvailable = false
540+ e2e .Logf ("Skipping reconcile_total checks: %v" , err )
541+ }
542+
543+ if metricsAvailable {
544+ g .By ("Patch catalog-85889 image" )
545+ patch := `{"spec":{"source":{"type":"Image","image":{"ref":"quay.io/operatorhubio/catalog:latest"}}}}`
546+ clustercatalog .Patch (oc , patch )
547+
548+ g .By ("Confirm reconcile_total increases after patch" )
549+ errWait := wait .PollUntilContextTimeout (context .TODO (), 10 * time .Second , 2 * time .Minute , false , func (ctx context.Context ) (bool , error ) {
550+ reconcileAfter , err := getReconcileTotal ()
551+ if err != nil {
552+ return false , nil
553+ }
554+ return reconcileAfter > reconcileBefore , nil
555+ })
556+ exutil .AssertWaitPollNoErr (errWait , "reconcile_total did not increase after patch" )
557+ }
558+
559+ g .By ("Delete test ClusterCatalog" )
560+ clustercatalog .Delete (oc )
561+ deleted = true
562+
563+ g .By ("Verify ClusterCatalog deletion" )
564+ errWait = wait .PollUntilContextTimeout (context .TODO (), 10 * time .Second , 2 * time .Minute , false , func (ctx context.Context ) (bool , error ) {
565+ output , err := oc .AsAdmin ().WithoutNamespace ().Run ("get" ).Args ("clustercatalog" , catalogName , "--ignore-not-found" ).Output ()
566+ if err != nil {
567+ return false , nil
568+ }
569+ return strings .TrimSpace (output ) == "" , nil
570+ })
571+ exutil .AssertWaitPollNoErr (errWait , "catalog-85889 was not deleted" )
572+ })
573+
325574 g .It ("PolarionID:73219-[OTP][Skipped:Disconnected]Fetch deprecation data from the catalogd http server" , func () {
326575 var (
327576 baseDir = exutil .FixturePath ("testdata" , "olm" )
0 commit comments