Skip to content

Commit 6af3a47

Browse files
committed
Use server side checking for the deleted manifests
There are some new methods: 1. GetPodsByNamespace() used to get CVO pod 2. ListFilesInPodContainer() used to list files in a pod container, here I use a customized command to reduce files (get file content file takes long time) 3. GetFileContentInPodContainer() used to get file content from a pod container 4. ParseManifest() parse yaml file content to Manifest object.
1 parent 773b61b commit 6af3a47

3 files changed

Lines changed: 147 additions & 53 deletions

File tree

.openshift-tests-extension/openshift_payload_cluster-version-operator.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@
7878
"labels": {
7979
"42543": {},
8080
"Conformance": {},
81-
"ConnectedOnly": {},
8281
"High": {}
8382
},
8483
"resources": {

test/cvo/cvo.go

Lines changed: 29 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,16 @@ package cvo
55
import (
66
"context"
77
"fmt"
8-
"os"
9-
"path/filepath"
10-
"time"
8+
"strings"
119

1210
g "github.com/onsi/ginkgo/v2"
1311
o "github.com/onsi/gomega"
1412

1513
apierrors "k8s.io/apimachinery/pkg/api/errors"
1614
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17-
"k8s.io/apimachinery/pkg/util/sets"
1815
"k8s.io/client-go/kubernetes"
1916
"k8s.io/client-go/rest"
2017

21-
"github.com/openshift/library-go/pkg/manifest"
22-
2318
"github.com/openshift/cluster-version-operator/pkg/cvo/external/dynamicclient"
2419
"github.com/openshift/cluster-version-operator/pkg/external"
2520
"github.com/openshift/cluster-version-operator/test/oc"
@@ -109,59 +104,47 @@ var _ = g.Describe(`[Jira:"Cluster Version Operator"] cluster-version-operator`,
109104
o.Expect(sccAnnotation).To(o.Equal("hostaccess"), "Expected the annotation 'openshift.io/scc annotation' on pod %s to have the value 'hostaccess', but got %s", cvoPod.Name, sccAnnotation)
110105
})
111106

112-
g.It(`should not install resources annotated with release.openshift.io/delete=true`, g.Label("Conformance", "High", "42543", "ConnectedOnly"), func() {
107+
g.It(`should not install resources annotated with release.openshift.io/delete=true`, g.Label("Conformance", "High", "42543"), func() {
113108
ctx := context.Background()
114109
err := util.SkipIfHypershift(ctx, restCfg)
115110
o.Expect(err).NotTo(o.HaveOccurred(), "Failed to determine if cluster is HyperShift")
116111
err = util.SkipIfMicroshift(ctx, restCfg)
117112
o.Expect(err).NotTo(o.HaveOccurred(), "Failed to determine if cluster is MicroShift")
118-
err = util.SkipIfNetworkRestricted(ctx, restCfg, util.FauxinnatiAPIURL)
119-
o.Expect(err).NotTo(o.HaveOccurred(), "Failed to determine if cluster is network restricted")
120113

121-
// Initialize the ocapi.OC instance
122-
g.By("Setting up oc")
123-
ocClient, err := oc.NewOC(ocapi.Options{Logger: logger, Timeout: 90 * time.Second})
114+
pods, err := util.GetPodsByNamespace(ctx, kubeClient, external.DefaultCVONamespace, map[string]string{"k8s-app": "cluster-version-operator"})
124115
o.Expect(err).NotTo(o.HaveOccurred())
116+
o.Expect(pods).NotTo(o.BeEmpty(), "Expected at least one CVO pod")
125117

126-
g.By("Extracting manifests in the release")
127118
annotation := "release.openshift.io/delete"
128-
tempDir, err := os.MkdirTemp("", "OTA-42543-manifest-")
129-
o.Expect(err).NotTo(o.HaveOccurred(), "create temp manifest dir failed")
130-
131-
authFile, err := util.GetAuthFile(context.Background(), kubeClient, "openshift-config", "pull-secret", ".dockerconfigjson")
119+
manifestDir := "/release-manifests/"
120+
command := []string{"find", manifestDir, "-iname", "*.yaml", "-exec", "grep", "-l", annotation, "{}", ";"}
121+
files, err := util.ListFilesInPodContainer(ctx, restCfg, command, external.DefaultCVONamespace, pods[0].Name, "cluster-version-operator")
132122
o.Expect(err).NotTo(o.HaveOccurred())
133-
defer func() { _ = os.Remove(authFile) }()
134-
manifestDir := ocapi.ReleaseExtractOptions{To: tempDir, AuthFile: authFile}
135-
logger.Info(fmt.Sprintf("Extract manifests to: %s", manifestDir.To))
136-
defer func() { _ = os.RemoveAll(manifestDir.To) }()
137-
err = ocClient.AdmReleaseExtract(manifestDir)
138-
o.Expect(err).NotTo(o.HaveOccurred(), "extracting manifests failed")
139-
140-
files, err := os.ReadDir(manifestDir.To)
141-
o.Expect(err).NotTo(o.HaveOccurred())
142-
g.By(fmt.Sprintf("Checking if getting manifests with %s on the cluster led to not-found error", annotation))
143-
ignore := sets.New("release-metadata", "image-references")
144-
for _, manifestFile := range files {
145-
if manifestFile.IsDir() || ignore.Has(manifestFile.Name()) {
123+
o.Expect(files).ToNot(o.BeEmpty(), "Expected to find files in manifests directory of CVO pod, but found none")
124+
125+
for _, f := range files {
126+
fileContent, err := util.GetFileContentInPodContainer(ctx, restCfg, external.DefaultCVONamespace, pods[0].Name, "cluster-version-operator", f)
127+
o.Expect(err).NotTo(o.HaveOccurred(), fmt.Sprintf("Failed to get content of file %s in CVO pod", f))
128+
o.Expect(fileContent).ToNot(o.BeEmpty(), fmt.Sprintf("Expected to get content of file %s in CVO pod, but got empty content", f))
129+
130+
if !strings.Contains(fileContent, annotation) {
146131
continue
147132
}
148-
filePath := filepath.Join(manifestDir.To, manifestFile.Name())
149-
o.Expect(err).NotTo(o.HaveOccurred(), "failed to read manifest file")
150-
manifests, err := manifest.ManifestsFromFiles([]string{filePath})
151-
o.Expect(err).NotTo(o.HaveOccurred(), fmt.Sprintf("failed to parse manifest file: %s", filePath))
152-
153-
for _, ms := range manifests {
154-
ann := ms.Obj.GetAnnotations()
155-
if ann[annotation] != "true" {
156-
continue
157-
}
158-
client, err := dynamicclient.New(restCfg, ms.GVK, ms.Obj.GetNamespace())
159-
o.Expect(err).NotTo(o.HaveOccurred())
160-
_, err = client.Get(ctx, ms.Obj.GetName(), metav1.GetOptions{})
161-
o.Expect(apierrors.IsNotFound(err)).To(o.BeTrue(),
162-
fmt.Sprintf("The deleted manifest should not be installed, but actually installed: manifest: %s %s in namespace %s from file %q, error: %v",
163-
ms.GVK, ms.Obj.GetName(), ms.Obj.GetNamespace(), ms.OriginalFilename, err))
133+
134+
manifest, err := util.ParseManifest(fileContent)
135+
o.Expect(err).NotTo(o.HaveOccurred(), fmt.Sprintf("Failed to parse manifest content of file %s in CVO pod", f))
136+
137+
ann := manifest.Obj.GetAnnotations()
138+
if ann[annotation] != "true" {
139+
continue
164140
}
141+
logger.Info("Checking file: ", f, ", GVK:", manifest.GVK.String(), ", Name: ", manifest.Obj.GetName(), ", Namespace: ", manifest.Obj.GetNamespace())
142+
client, err := dynamicclient.New(restCfg, manifest.GVK, manifest.Obj.GetNamespace())
143+
o.Expect(err).NotTo(o.HaveOccurred())
144+
_, err = client.Get(ctx, manifest.Obj.GetName(), metav1.GetOptions{})
145+
o.Expect(apierrors.IsNotFound(err)).To(o.BeTrue(),
146+
fmt.Sprintf("The deleted manifest should not be installed, but actually installed: manifest: %s %s in namespace %s from file %q, error: %v",
147+
manifest.GVK, manifest.Obj.GetName(), manifest.Obj.GetNamespace(), f, err))
165148
}
166149
})
167150
})

test/util/util.go

Lines changed: 118 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,30 @@ package util
33
import (
44
"bytes"
55
"context"
6-
"crypto/rand"
76
"fmt"
7+
"io"
88
"os"
9-
"path/filepath"
109
"strings"
1110
"time"
1211

1312
g "github.com/onsi/ginkgo/v2"
1413
o "github.com/onsi/gomega"
14+
"github.com/pkg/errors"
1515

1616
corev1 "k8s.io/api/core/v1"
1717
apierrors "k8s.io/apimachinery/pkg/api/errors"
1818
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1919
"k8s.io/apimachinery/pkg/labels"
2020
"k8s.io/apimachinery/pkg/util/wait"
21+
"k8s.io/apimachinery/pkg/util/yaml"
2122
"k8s.io/client-go/kubernetes"
2223
"k8s.io/client-go/kubernetes/scheme"
2324
"k8s.io/client-go/rest"
2425
"k8s.io/client-go/tools/clientcmd"
2526
"k8s.io/client-go/tools/remotecommand"
2627

28+
libmanifest "github.com/openshift/library-go/pkg/manifest"
29+
2730
configv1 "github.com/openshift/api/config/v1"
2831
clientconfigv1 "github.com/openshift/client-go/config/clientset/versioned"
2932

@@ -117,12 +120,121 @@ func GetAuthFile(ctx context.Context, client kubernetes.Interface, ns string, se
117120
if !ok {
118121
return "", fmt.Errorf("auth key not found in secret %s/%s", ns, secretName)
119122
}
120-
authFile := filepath.Join("/tmp/", fmt.Sprintf("ota-%s", rand.Text()))
121-
err = os.WriteFile(authFile, secretData, 0644)
123+
f, err := os.CreateTemp("", "ota-*")
124+
if err != nil {
125+
return "", fmt.Errorf("error creating temp file: %v", err)
126+
}
127+
defer func() {
128+
_ = f.Close()
129+
}()
130+
if _, err := f.Write(secretData); err != nil {
131+
_ = os.Remove(f.Name())
132+
return "", fmt.Errorf("error writing to temp file: %v", err)
133+
}
134+
return f.Name(), nil
135+
}
136+
137+
// GetPodsByNamespace retrieves the list of pods in the specified namespace with the given label selector.
138+
func GetPodsByNamespace(ctx context.Context, client kubernetes.Interface, namespace string, labelSelector map[string]string) ([]corev1.Pod, error) {
139+
podList, err := client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
140+
LabelSelector: labels.FormatLabels(labelSelector),
141+
})
142+
if err != nil {
143+
return nil, fmt.Errorf("error listing pods in namespace %s with label selector %v: %v", namespace, labelSelector, err)
144+
}
145+
return podList.Items, nil
146+
}
147+
148+
// ListFilesInPodContainer executes the given command in the specified container of a pod and returns the output as a list of strings.
149+
func ListFilesInPodContainer(ctx context.Context, restConfig *rest.Config, command []string, namespace string, podName string, containerName string) ([]string, error) {
150+
kubeClient, err := GetKubeClient(restConfig)
151+
if err != nil {
152+
return nil, err
153+
}
154+
results := []string{}
155+
req := kubeClient.CoreV1().RESTClient().Post().
156+
Resource("pods").
157+
Name(podName).
158+
Namespace(namespace).
159+
SubResource("exec").
160+
VersionedParams(&corev1.PodExecOptions{
161+
Command: command,
162+
Container: containerName,
163+
Stdout: true,
164+
Stderr: true,
165+
}, scheme.ParameterCodec)
166+
exec, err := remotecommand.NewSPDYExecutor(restConfig, "POST", req.URL())
167+
if err != nil {
168+
return nil, fmt.Errorf("error creating executor for pod %s/%s: %v", namespace, podName, err)
169+
}
170+
var stdoutBuf, stderrBuf bytes.Buffer
171+
err = exec.StreamWithContext(ctx, remotecommand.StreamOptions{
172+
Stdout: &stdoutBuf,
173+
Stderr: &stderrBuf,
174+
})
122175
if err != nil {
123-
return "", fmt.Errorf("error writing file %s: %v", authFile, err)
176+
return nil, fmt.Errorf("error executing command in pod %s/%s: %v, stderr: %s", namespace, podName, err, stderrBuf.String())
177+
}
178+
files := strings.Split(stdoutBuf.String(), "\n")
179+
for _, file := range files {
180+
if file == "" {
181+
continue
182+
}
183+
results = append(results, file)
184+
}
185+
return results, nil
186+
}
187+
188+
// GetFileContentInPodContainer executes the command to read the content of a file in the specified container of a pod and returns the content as a string.
189+
func GetFileContentInPodContainer(ctx context.Context, restConfig *rest.Config, namespace string, podName string, containerName string, filePath string) (string, error) {
190+
kubeClient, err := GetKubeClient(restConfig)
191+
if err != nil {
192+
return "", err
193+
}
194+
command := []string{"cat", filePath}
195+
req := kubeClient.CoreV1().RESTClient().Post().
196+
Resource("pods").
197+
Name(podName).
198+
Namespace(namespace).
199+
SubResource("exec").
200+
VersionedParams(&corev1.PodExecOptions{
201+
Command: command,
202+
Container: containerName,
203+
Stdout: true,
204+
Stderr: true,
205+
}, scheme.ParameterCodec)
206+
exec, err := remotecommand.NewSPDYExecutor(restConfig, "POST", req.URL())
207+
if err != nil {
208+
return "", fmt.Errorf("error creating executor for pod %s/%s: %v", namespace, podName, err)
209+
}
210+
var stdoutBuf, stderrBuf bytes.Buffer
211+
err = exec.StreamWithContext(ctx, remotecommand.StreamOptions{
212+
Stdout: &stdoutBuf,
213+
Stderr: &stderrBuf,
214+
})
215+
if err != nil {
216+
return "", fmt.Errorf("error executing command in pod %s/%s: %v, stderr: %s", namespace, podName, err, stderrBuf.String())
217+
}
218+
return stdoutBuf.String(), nil
219+
}
220+
221+
// ParseManifest parses the given file content as a Kubernetes manifest and returns a Manifest object.
222+
func ParseManifest(fileContent string) (libmanifest.Manifest, error) {
223+
d := yaml.NewYAMLOrJSONDecoder(strings.NewReader(fileContent), 1024)
224+
for {
225+
m := libmanifest.Manifest{}
226+
if err := d.Decode(&m); err != nil {
227+
if err == io.EOF {
228+
return m, nil
229+
}
230+
return m, errors.Wrapf(err, "error parsing")
231+
}
232+
m.Raw = bytes.TrimSpace(m.Raw)
233+
if len(m.Raw) == 0 || bytes.Equal(m.Raw, []byte("null")) {
234+
continue
235+
}
236+
return m, nil
124237
}
125-
return authFile, nil
126238
}
127239

128240
// GetRestConfig loads the Kubernetes REST configuration from KUBECONFIG environment variable.

0 commit comments

Comments
 (0)