Skip to content

Commit a9869a5

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 a9869a5

3 files changed

Lines changed: 146 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: 117 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,22 @@ 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"
@@ -26,6 +27,7 @@ import (
2627

2728
configv1 "github.com/openshift/api/config/v1"
2829
clientconfigv1 "github.com/openshift/client-go/config/clientset/versioned"
30+
libmanifest "github.com/openshift/library-go/pkg/manifest"
2931

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

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

0 commit comments

Comments
 (0)