Skip to content

Commit 5823e70

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 5823e70

2 files changed

Lines changed: 136 additions & 43 deletions

File tree

test/cvo/cvo.go

Lines changed: 28 additions & 43 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"
@@ -118,50 +113,40 @@ var _ = g.Describe(`[Jira:"Cluster Version Operator"] cluster-version-operator`,
118113
err = util.SkipIfNetworkRestricted(ctx, restCfg, util.FauxinnatiAPIURL)
119114
o.Expect(err).NotTo(o.HaveOccurred(), "Failed to determine if cluster is network restricted")
120115

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})
116+
pods, err := util.GetPodsByNamespace(ctx, kubeClient, external.DefaultCVONamespace, map[string]string{"k8s-app": "cluster-version-operator"})
124117
o.Expect(err).NotTo(o.HaveOccurred())
118+
logger.Info("CVO pod found", "name", pods[0].Name)
125119

126-
g.By("Extracting manifests in the release")
127120
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")
121+
manifestDir := "/release-manifests/"
122+
command := []string{"find", manifestDir, "-iname", "*.yaml", "-exec", "grep", "-l", annotation, "{}", ";"}
123+
files, err := util.ListFilesInPodContainer(ctx, restCfg, command, external.DefaultCVONamespace, pods[0].Name, "cluster-version-operator")
132124
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()) {
125+
o.Expect(files).ToNot(o.BeEmpty(), "Expected to find files in manifests directory of CVO pod, but found none")
126+
127+
for _, f := range files {
128+
fileContent, err := util.GetFileContentInPodContainer(ctx, restCfg, external.DefaultCVONamespace, pods[0].Name, "cluster-version-operator", f)
129+
o.Expect(err).NotTo(o.HaveOccurred(), fmt.Sprintf("Failed to get content of file %s in CVO pod", f))
130+
o.Expect(fileContent).ToNot(o.BeEmpty(), fmt.Sprintf("Expected to get content of file %s in CVO pod, but got empty content", f))
131+
132+
if !strings.Contains(fileContent, annotation) {
146133
continue
147134
}
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))
135+
136+
manifest, err := util.ParseManifest(fileContent)
137+
o.Expect(err).NotTo(o.HaveOccurred(), fmt.Sprintf("Failed to parse manifest content of file %s in CVO pod", f))
138+
139+
ann := manifest.Obj.GetAnnotations()
140+
if ann[annotation] != "true" {
141+
continue
164142
}
143+
logger.Info("Checking file: ", f, ", GVK:", manifest.GVK.String(), ", Name: ", manifest.Obj.GetName(), ", Namespace: ", manifest.Obj.GetNamespace())
144+
client, err := dynamicclient.New(restCfg, manifest.GVK, manifest.Obj.GetNamespace())
145+
o.Expect(err).NotTo(o.HaveOccurred())
146+
_, err = client.Get(ctx, manifest.Obj.GetName(), metav1.GetOptions{})
147+
o.Expect(apierrors.IsNotFound(err)).To(o.BeTrue(),
148+
fmt.Sprintf("The deleted manifest should not be installed, but actually installed: manifest: %s %s in namespace %s from file %q, error: %v",
149+
manifest.GVK, manifest.Obj.GetName(), manifest.Obj.GetNamespace(), f, err))
165150
}
166151
})
167152
})

test/util/util.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,30 @@ import (
55
"context"
66
"crypto/rand"
77
"fmt"
8+
"io"
89
"os"
910
"path/filepath"
1011
"strings"
1112
"time"
1213

1314
g "github.com/onsi/ginkgo/v2"
1415
o "github.com/onsi/gomega"
16+
"github.com/pkg/errors"
1517

1618
corev1 "k8s.io/api/core/v1"
1719
apierrors "k8s.io/apimachinery/pkg/api/errors"
1820
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1921
"k8s.io/apimachinery/pkg/labels"
2022
"k8s.io/apimachinery/pkg/util/wait"
23+
"k8s.io/apimachinery/pkg/util/yaml"
2124
"k8s.io/client-go/kubernetes"
2225
"k8s.io/client-go/kubernetes/scheme"
2326
"k8s.io/client-go/rest"
2427
"k8s.io/client-go/tools/clientcmd"
2528
"k8s.io/client-go/tools/remotecommand"
2629

30+
libmanifest "github.com/openshift/library-go/pkg/manifest"
31+
2732
configv1 "github.com/openshift/api/config/v1"
2833
clientconfigv1 "github.com/openshift/client-go/config/clientset/versioned"
2934

@@ -125,6 +130,109 @@ func GetAuthFile(ctx context.Context, client kubernetes.Interface, ns string, se
125130
return authFile, nil
126131
}
127132

133+
// GetPodsByNamespace retrieves the list of pods in the specified namespace with the given label selector.
134+
func GetPodsByNamespace(ctx context.Context, client kubernetes.Interface, namespace string, labelSelector map[string]string) ([]corev1.Pod, error) {
135+
podList, err := client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
136+
LabelSelector: labels.FormatLabels(labelSelector),
137+
})
138+
if err != nil {
139+
return nil, fmt.Errorf("error listing pods in namespace %s with label selector %v: %v", namespace, labelSelector, err)
140+
}
141+
return podList.Items, nil
142+
}
143+
144+
// ListFilesInPodContainer executes the given command in the specified container of a pod and returns the output as a list of strings.
145+
func ListFilesInPodContainer(ctx context.Context, restConfig *rest.Config, command []string, namespace string, podName string, containerName string) ([]string, error) {
146+
kubeClient, err := GetKubeClient(restConfig)
147+
if err != nil {
148+
return nil, err
149+
}
150+
results := []string{}
151+
req := kubeClient.CoreV1().RESTClient().Post().
152+
Resource("pods").
153+
Name(podName).
154+
Namespace(namespace).
155+
SubResource("exec").
156+
VersionedParams(&corev1.PodExecOptions{
157+
Command: command,
158+
Container: containerName,
159+
Stdout: true,
160+
Stderr: true,
161+
}, scheme.ParameterCodec)
162+
exec, err := remotecommand.NewSPDYExecutor(restConfig, "POST", req.URL())
163+
if err != nil {
164+
return nil, fmt.Errorf("error creating executor for pod %s/%s: %v", namespace, podName, err)
165+
}
166+
var stdoutBuf, stderrBuf bytes.Buffer
167+
err = exec.StreamWithContext(ctx, remotecommand.StreamOptions{
168+
Stdout: &stdoutBuf,
169+
Stderr: &stderrBuf,
170+
})
171+
if err != nil {
172+
return nil, fmt.Errorf("error executing command in pod %s/%s: %v, stderr: %s", namespace, podName, err, stderrBuf.String())
173+
}
174+
files := strings.Split(stdoutBuf.String(), "\n")
175+
for _, file := range files {
176+
if file == "" {
177+
continue
178+
}
179+
results = append(results, file)
180+
}
181+
return results, nil
182+
}
183+
184+
// 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.
185+
func GetFileContentInPodContainer(ctx context.Context, restConfig *rest.Config, namespace string, podName string, containerName string, filePath string) (string, error) {
186+
kubeClient, err := GetKubeClient(restConfig)
187+
if err != nil {
188+
return "", err
189+
}
190+
command := []string{"cat", filePath}
191+
req := kubeClient.CoreV1().RESTClient().Post().
192+
Resource("pods").
193+
Name(podName).
194+
Namespace(namespace).
195+
SubResource("exec").
196+
VersionedParams(&corev1.PodExecOptions{
197+
Command: command,
198+
Container: containerName,
199+
Stdout: true,
200+
Stderr: true,
201+
}, scheme.ParameterCodec)
202+
exec, err := remotecommand.NewSPDYExecutor(restConfig, "POST", req.URL())
203+
if err != nil {
204+
return "", fmt.Errorf("error creating executor for pod %s/%s: %v", namespace, podName, err)
205+
}
206+
var stdoutBuf, stderrBuf bytes.Buffer
207+
err = exec.StreamWithContext(ctx, remotecommand.StreamOptions{
208+
Stdout: &stdoutBuf,
209+
Stderr: &stderrBuf,
210+
})
211+
if err != nil {
212+
return "", fmt.Errorf("error executing command in pod %s/%s: %v, stderr: %s", namespace, podName, err, stderrBuf.String())
213+
}
214+
return stdoutBuf.String(), nil
215+
}
216+
217+
// ParseManifest parses the given file content as a Kubernetes manifest and returns a Manifest object.
218+
func ParseManifest(fileContent string) (libmanifest.Manifest, error) {
219+
d := yaml.NewYAMLOrJSONDecoder(strings.NewReader(fileContent), 1024)
220+
for {
221+
m := libmanifest.Manifest{}
222+
if err := d.Decode(&m); err != nil {
223+
if err == io.EOF {
224+
return m, nil
225+
}
226+
return m, errors.Wrapf(err, "error parsing")
227+
}
228+
m.Raw = bytes.TrimSpace(m.Raw)
229+
if len(m.Raw) == 0 || bytes.Equal(m.Raw, []byte("null")) {
230+
continue
231+
}
232+
return m, nil
233+
}
234+
}
235+
128236
// GetRestConfig loads the Kubernetes REST configuration from KUBECONFIG environment variable.
129237
func GetRestConfig() (*rest.Config, error) {
130238
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(clientcmd.NewDefaultClientConfigLoadingRules(), &clientcmd.ConfigOverrides{}).ClientConfig()

0 commit comments

Comments
 (0)