Skip to content

Commit bfb2e58

Browse files
committed
test: adding integration suite
1 parent 3909af1 commit bfb2e58

File tree

8 files changed

+707
-13
lines changed

8 files changed

+707
-13
lines changed

go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@ require (
77
github.com/go-logr/logr v0.4.0
88
github.com/go-openapi/swag v0.19.15 // indirect
99
github.com/mcuadros/go-defaults v1.2.0
10+
github.com/onsi/ginkgo/v2 v2.3.1
11+
github.com/onsi/gomega v1.22.0
1012
github.com/openshift/api v3.9.0+incompatible
1113
github.com/prometheus/client_golang v1.12.0
1214
github.com/prometheus/client_model v0.2.0
15+
github.com/prometheus/common v0.32.1
1316
github.com/spf13/pflag v1.0.5
1417
github.com/spf13/viper v1.9.0
1518
go.uber.org/multierr v1.6.0

go.sum

+45-13
Large diffs are not rendered by default.

integration/deployment_test.go

+193
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
package integration
2+
3+
import (
4+
"context"
5+
"errors"
6+
"os/exec"
7+
8+
internaltesting "github.com/app-sre/deployment-validation-operator/internal/testing"
9+
io_prometheus_client "github.com/prometheus/client_model/go"
10+
11+
. "github.com/onsi/ginkgo/v2"
12+
. "github.com/onsi/gomega"
13+
. "github.com/onsi/gomega/gexec"
14+
appsv1 "k8s.io/api/apps/v1"
15+
corev1 "k8s.io/api/core/v1"
16+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17+
)
18+
19+
var ErrNilMetric = errors.New("nil metric")
20+
21+
var _ = Describe("deployments", func() {
22+
var (
23+
ctx context.Context
24+
cancel context.CancelFunc
25+
namespace string
26+
namespaceGen = nameGenerator("deployment-test-namespace")
27+
)
28+
29+
prom := internaltesting.NewPromClient()
30+
31+
BeforeEach(func() {
32+
ctx, cancel = context.WithCancel(context.Background())
33+
34+
namespace = namespaceGen()
35+
36+
By("Starting manager")
37+
38+
manager := exec.Command(_binPath,
39+
"--kubeconfig", _kubeConfigPath,
40+
)
41+
42+
manager.Env = []string{
43+
"WATCH_NAMESPACE=" + namespace,
44+
}
45+
46+
session, err := Start(manager, GinkgoWriter, GinkgoWriter)
47+
Expect(err).ToNot(HaveOccurred())
48+
49+
By("Creating the watch namespace")
50+
51+
ns := newNamespace(namespace)
52+
53+
_client.Create(ctx, &ns)
54+
55+
rbac, err := getRBAC(namespace, managerGroup)
56+
Expect(err).ToNot(HaveOccurred())
57+
58+
for _, obj := range rbac {
59+
_client.Create(ctx, obj)
60+
}
61+
62+
DeferCleanup(func() {
63+
cancel()
64+
65+
By("Stopping the managers")
66+
67+
session.Interrupt()
68+
69+
if usingExistingCluster() {
70+
By("Deleting watch namspace")
71+
72+
_client.Delete(ctx, &ns)
73+
}
74+
})
75+
})
76+
77+
When("created with missing resource requirements", func() {
78+
It("should generate metrics", func() {
79+
_client.Create(ctx, newDeployment("test", namespace))
80+
81+
var (
82+
deploymentMemReqMetric *io_prometheus_client.Metric
83+
deploymentCPUReqMetric *io_prometheus_client.Metric
84+
)
85+
86+
Eventually(func() error {
87+
metrics, err := prom.GetDVOMetrics(ctx, "http://localhost:8383/metrics")
88+
if err != nil {
89+
return err
90+
}
91+
92+
memReqMetrics := metrics["unset_memory_requirements"]
93+
deploymentMemReqMetric = searchableMetrics(
94+
memReqMetrics,
95+
).FindDVOMetric("Deployment", "test", namespace)
96+
if deploymentMemReqMetric == nil {
97+
return ErrNilMetric
98+
}
99+
100+
cpuReqMetrics := metrics["unset_cpu_requirements"]
101+
deploymentCPUReqMetric = searchableMetrics(
102+
cpuReqMetrics,
103+
).FindDVOMetric("Deployment", "test", namespace)
104+
if deploymentCPUReqMetric == nil {
105+
return ErrNilMetric
106+
}
107+
108+
return nil
109+
}, "10s").Should(Succeed())
110+
111+
Expect(deploymentMemReqMetric.Gauge.GetValue()).To(Equal(float64(1)))
112+
Expect(deploymentCPUReqMetric.Gauge.GetValue()).To(Equal(float64(1)))
113+
})
114+
})
115+
116+
})
117+
118+
func newNamespace(name string) corev1.Namespace {
119+
return corev1.Namespace{
120+
ObjectMeta: metav1.ObjectMeta{
121+
Name: name,
122+
},
123+
}
124+
}
125+
126+
func newDeployment(name, namespace string) *appsv1.Deployment {
127+
return &appsv1.Deployment{
128+
ObjectMeta: metav1.ObjectMeta{
129+
Name: name,
130+
Namespace: namespace,
131+
Labels: map[string]string{
132+
"app": "test",
133+
},
134+
},
135+
Spec: appsv1.DeploymentSpec{
136+
Replicas: int32Ptr(1),
137+
Selector: &metav1.LabelSelector{
138+
MatchLabels: map[string]string{
139+
"app": "test",
140+
},
141+
},
142+
Template: corev1.PodTemplateSpec{
143+
ObjectMeta: metav1.ObjectMeta{
144+
Labels: map[string]string{
145+
"app": "test",
146+
},
147+
},
148+
Spec: corev1.PodSpec{
149+
Containers: []corev1.Container{
150+
{
151+
Name: "test",
152+
Image: "test",
153+
},
154+
},
155+
},
156+
},
157+
},
158+
}
159+
}
160+
161+
func int32Ptr(i int32) *int32 { return &i }
162+
163+
type searchableMetrics []*io_prometheus_client.Metric
164+
165+
func (ms searchableMetrics) FindDVOMetric(kind, name, namespace string) *io_prometheus_client.Metric {
166+
for _, m := range ms {
167+
labels := searchableLables(m.GetLabel())
168+
169+
if labels.Contains("kind", kind) &&
170+
labels.Contains("name", name) &&
171+
labels.Contains("namespace", namespace) {
172+
return m
173+
}
174+
}
175+
176+
return nil
177+
}
178+
179+
type searchableLables []*io_prometheus_client.LabelPair
180+
181+
func (ls searchableLables) Contains(name, val string) bool {
182+
for _, l := range ls {
183+
if l.GetName() != name {
184+
continue
185+
}
186+
187+
if l.GetValue() == val {
188+
return true
189+
}
190+
}
191+
192+
return false
193+
}

integration/helpers.go

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package integration
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"fmt"
7+
"io"
8+
"os"
9+
"os/exec"
10+
"path/filepath"
11+
"strings"
12+
13+
internaltesting "github.com/app-sre/deployment-validation-operator/internal/testing"
14+
15+
rbacv1 "k8s.io/api/rbac/v1"
16+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17+
"sigs.k8s.io/controller-runtime/pkg/client"
18+
)
19+
20+
func nameGenerator(pfx string) func() string {
21+
i := 0
22+
23+
return func() string {
24+
name := fmt.Sprintf("%s-%d", pfx, i)
25+
26+
i++
27+
28+
return name
29+
}
30+
}
31+
32+
func getClusterRBAC(group string) ([]client.Object, error) {
33+
root, err := projectRoot()
34+
if err != nil {
35+
return nil, err
36+
}
37+
38+
path := filepath.Join(root, "deploy", "openshift", "cluster-role.yaml")
39+
40+
role, err := internaltesting.LoadUnstructuredFromFile(path)
41+
if err != nil {
42+
return nil, fmt.Errorf("loading role: %w", err)
43+
}
44+
45+
return []client.Object{
46+
role,
47+
&rbacv1.ClusterRoleBinding{
48+
ObjectMeta: metav1.ObjectMeta{
49+
Name: role.GetName(),
50+
},
51+
Subjects: []rbacv1.Subject{
52+
{
53+
Kind: "Group",
54+
Name: group,
55+
},
56+
},
57+
RoleRef: rbacv1.RoleRef{
58+
Kind: "ClusterRole",
59+
Name: role.GetName(),
60+
},
61+
},
62+
}, nil
63+
}
64+
65+
func getRBAC(namespace, group string) ([]client.Object, error) {
66+
root, err := projectRoot()
67+
if err != nil {
68+
return nil, err
69+
}
70+
71+
role, err := internaltesting.LoadUnstructuredFromFile(filepath.Join(root, "deploy", "openshift", "role.yaml"))
72+
if err != nil {
73+
return nil, fmt.Errorf("loading role: %w", err)
74+
}
75+
76+
role.SetNamespace(namespace)
77+
78+
return []client.Object{
79+
role,
80+
&rbacv1.RoleBinding{
81+
ObjectMeta: metav1.ObjectMeta{
82+
Name: role.GetName(),
83+
Namespace: namespace,
84+
},
85+
Subjects: []rbacv1.Subject{
86+
{
87+
Kind: "Group",
88+
Name: group,
89+
},
90+
},
91+
RoleRef: rbacv1.RoleRef{
92+
Kind: "Role",
93+
Name: role.GetName(),
94+
},
95+
},
96+
}, nil
97+
}
98+
99+
func projectRoot() (string, error) {
100+
var buf bytes.Buffer
101+
102+
cmd := exec.Command("git", "rev-parse", "--show-toplevel")
103+
cmd.Stdout = &buf
104+
cmd.Stderr = io.Discard
105+
106+
if err := cmd.Run(); err != nil {
107+
return "", fmt.Errorf("determining top level directory from git: %w", errSetup)
108+
}
109+
110+
return strings.TrimSpace(buf.String()), nil
111+
}
112+
113+
var errSetup = errors.New("test setup failed")
114+
115+
func remove(path string) error {
116+
if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
117+
return nil
118+
}
119+
120+
return os.Remove(path)
121+
}

0 commit comments

Comments
 (0)