Skip to content

Commit ade4e0e

Browse files
authored
CLOUDP-58090: Create StatefulSet that starts agent image (#12)
1 parent 6a9d283 commit ade4e0e

File tree

7 files changed

+177
-28
lines changed

7 files changed

+177
-28
lines changed

cmd/manager/main.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"github.com/mongodb/mongodb-kubernetes-operator/pkg/controller"
77
"go.uber.org/zap"
88
"os"
9-
109
"sigs.k8s.io/controller-runtime/pkg/client/config"
1110
"sigs.k8s.io/controller-runtime/pkg/manager"
1211
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
@@ -31,6 +30,13 @@ func main() {
3130
if err != nil {
3231
os.Exit(1)
3332
}
33+
34+
// TODO: implement mechanism to specify required/optional environment variables
35+
if _, agentImageSpecified := os.LookupEnv("AGENT_IMAGE"); !agentImageSpecified {
36+
log.Error("required environment variable AGENT_IMAGE not found")
37+
os.Exit(1)
38+
}
39+
3440
// get watch namespace from environment variable
3541
namespace, nsSpecified := os.LookupEnv("WATCH_NAMESPACE")
3642
if !nsSpecified {
@@ -49,7 +55,7 @@ func main() {
4955
mgr, err := manager.New(cfg, manager.Options{
5056
Namespace: namespace,
5157
})
52-
58+
5359
if err != nil {
5460
os.Exit(1)
5561
}

deploy/crds/mongodb.com_v1_mongodb_cr.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ metadata:
44
name: example-mongodb
55
spec:
66
members: 3
7-
kind: ReplicaSet
7+
type: ReplicaSet
88
version: "4.0.6"

deploy/operator.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,5 @@ spec:
3131
fieldPath: metadata.name
3232
- name: OPERATOR_NAME
3333
value: "mongodb-kubernetes-operator"
34+
- name: AGENT_IMAGE # The MongoDB Agent the operator will deploy to manage MongoDB deployments
35+
value: REPLACE_IMAGE

pkg/controller/mongodb/mongodb_controller.go

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"os"
8+
79
mdbv1 "github.com/mongodb/mongodb-kubernetes-operator/pkg/apis/mongodb/v1"
810
"github.com/mongodb/mongodb-kubernetes-operator/pkg/automationconfig"
911
mdbClient "github.com/mongodb/mongodb-kubernetes-operator/pkg/kube/client"
1012
"github.com/mongodb/mongodb-kubernetes-operator/pkg/kube/configmap"
13+
"github.com/mongodb/mongodb-kubernetes-operator/pkg/kube/resourcerequirements"
1114
"github.com/mongodb/mongodb-kubernetes-operator/pkg/kube/statefulset"
1215
"go.uber.org/zap"
16+
appsv1 "k8s.io/api/apps/v1"
1317
corev1 "k8s.io/api/core/v1"
1418
"k8s.io/apimachinery/pkg/api/errors"
1519
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -22,7 +26,9 @@ import (
2226
)
2327

2428
const (
25-
automationConfigKey = "automation-config"
29+
automationConfigKey = "automation-config"
30+
agentName = "mongodb-agent"
31+
agentImageEnvVariable = "AGENT_IMAGE"
2632
)
2733

2834
// Add creates a new MongoDB Controller and adds it to the Manager. The Manager will set fields on the Controller
@@ -92,32 +98,20 @@ func (r *ReplicaSetReconciler) Reconcile(request reconcile.Request) (reconcile.R
9298
// TODO: Read current automation config version from config map
9399

94100
if err := r.ensureAutomationConfig(mdb); err != nil {
95-
log.Errorf("failed creating config map: %s", err)
101+
log.Warnf("failed creating config map: %s", err)
96102
return reconcile.Result{}, err
97103
}
98104

99105
// TODO: Create the service for the MDB resource
100106

101-
labels := map[string]string{
102-
"dummy": "label",
107+
sts, err := buildStatefulSet(mdb)
108+
if err != nil {
109+
log.Warnf("error building StatefulSet: %s", err)
110+
return reconcile.Result{}, nil
103111
}
104112

105-
sts, err := statefulset.NewBuilder().
106-
SetPodTemplateSpec(corev1.PodTemplateSpec{
107-
ObjectMeta: metav1.ObjectMeta{
108-
Labels: labels,
109-
},
110-
Spec: corev1.PodSpec{},
111-
}).
112-
SetNamespace(request.NamespacedName.Namespace).
113-
SetName(request.NamespacedName.Name).
114-
SetReplicas(mdb.Spec.Members).
115-
SetLabels(labels).
116-
SetMatchLabels(labels).
117-
Build()
118-
119113
if err = r.client.CreateOrUpdate(&sts); err != nil {
120-
log.Errorf("error creating/updating StatefulSet: %s", err)
114+
log.Warnf("error creating/updating StatefulSet: %s", err)
121115
return reconcile.Result{}, err
122116
}
123117

@@ -162,6 +156,40 @@ func buildAutomationConfigConfigMap(mdb mdbv1.MongoDB) (corev1.ConfigMap, error)
162156
Build(), nil
163157
}
164158

159+
// buildStatefulSet takes a MongoDB resource and converts it into
160+
// the corresponding stateful set
161+
func buildStatefulSet(mdb mdbv1.MongoDB) (appsv1.StatefulSet, error) {
162+
labels := map[string]string{
163+
"dummy": "label",
164+
}
165+
agentContainer := corev1.Container{
166+
Name: agentName,
167+
Image: os.Getenv(agentImageEnvVariable),
168+
Resources: resourcerequirements.Defaults(),
169+
Command: []string{"agent/mongodb-agent", "-cluster=/var/lib/automation/config/automation-config.json"},
170+
}
171+
172+
podSpecTemplate := corev1.PodTemplateSpec{
173+
ObjectMeta: metav1.ObjectMeta{
174+
Labels: labels,
175+
},
176+
Spec: corev1.PodSpec{
177+
Containers: []corev1.Container{
178+
agentContainer,
179+
},
180+
},
181+
}
182+
183+
return statefulset.NewBuilder().
184+
SetPodTemplateSpec(podSpecTemplate).
185+
SetNamespace(mdb.Namespace).
186+
SetName(mdb.Name).
187+
SetReplicas(mdb.Spec.Members).
188+
SetLabels(labels).
189+
SetMatchLabels(labels).
190+
Build()
191+
}
192+
165193
func getDomain(service, namespace, clusterName string) string {
166194
if clusterName == "" {
167195
clusterName = "cluster.local"

pkg/controller/mongodb/replicaset_controller_test.go

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,39 @@ package mongodb
22

33
import (
44
"context"
5+
"os"
6+
"testing"
7+
58
mdbv1 "github.com/mongodb/mongodb-kubernetes-operator/pkg/apis/mongodb/v1"
69
"github.com/mongodb/mongodb-kubernetes-operator/pkg/kube/client"
10+
"github.com/mongodb/mongodb-kubernetes-operator/pkg/kube/resourcerequirements"
711
"github.com/stretchr/testify/assert"
12+
appsv1 "k8s.io/api/apps/v1"
813
corev1 "k8s.io/api/core/v1"
914
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1015
"k8s.io/apimachinery/pkg/types"
1116
"sigs.k8s.io/controller-runtime/pkg/reconcile"
12-
"testing"
1317
)
1418

15-
func TestKubernetesResources_AreCreated(t *testing.T) {
16-
// TODO: Create builder/yaml fixture of some type to construct MDB objects for unit tests
17-
mdb := mdbv1.MongoDB{
19+
func init() {
20+
os.Setenv("AGENT_IMAGE", "agent-image")
21+
}
22+
23+
func newTestReplicaSet() mdbv1.MongoDB {
24+
return mdbv1.MongoDB{
1825
ObjectMeta: metav1.ObjectMeta{
1926
Name: "my-rs",
2027
Namespace: "my-ns",
2128
},
22-
Spec: mdbv1.MongoDBSpec{},
23-
Status: mdbv1.MongoDBStatus{},
29+
Spec: mdbv1.MongoDBSpec{
30+
Members: 3,
31+
},
2432
}
33+
}
34+
35+
func TestKubernetesResources_AreCreated(t *testing.T) {
36+
// TODO: Create builder/yaml fixture of some type to construct MDB objects for unit tests
37+
mdb := newTestReplicaSet()
2538

2639
mgr := client.NewManager(&mdb)
2740
r := newReconciler(mgr)
@@ -37,3 +50,21 @@ func TestKubernetesResources_AreCreated(t *testing.T) {
3750
assert.Contains(t, cm.Data, automationConfigKey)
3851
assert.NotEmpty(t, cm.Data[automationConfigKey])
3952
}
53+
54+
func TestStatefulSet_IsCorrectlyConfigured(t *testing.T) {
55+
mdb := newTestReplicaSet()
56+
mgr := client.NewManager(&mdb)
57+
r := newReconciler(mgr)
58+
res, err := r.Reconcile(reconcile.Request{NamespacedName: types.NamespacedName{Namespace: mdb.Namespace, Name: mdb.Name}})
59+
assertReconciliationSuccessful(t, res, err)
60+
61+
sts := appsv1.StatefulSet{}
62+
err = mgr.GetClient().Get(context.TODO(), types.NamespacedName{Name: mdb.Name, Namespace: mdb.Namespace}, &sts)
63+
assert.NoError(t, err)
64+
65+
agentContainer := sts.Spec.Template.Spec.Containers[0]
66+
assert.Equal(t, agentName, agentContainer.Name)
67+
assert.Equal(t, os.Getenv(agentImageEnvVariable), agentContainer.Image)
68+
69+
assert.Equal(t, resourcerequirements.Defaults(), agentContainer.Resources)
70+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package resourcerequirements
2+
3+
import (
4+
corev1 "k8s.io/api/core/v1"
5+
"k8s.io/apimachinery/pkg/api/resource"
6+
)
7+
8+
const (
9+
resourceMemory = "memory"
10+
resourceCpu = "cpu"
11+
)
12+
13+
// Defaults returns the default resource requirements for a container
14+
func Defaults() corev1.ResourceRequirements {
15+
// we can safely ignore the error as we are passing all valid values
16+
req, _ := newDefaultRequirements()
17+
return req
18+
}
19+
20+
func newDefaultRequirements() (corev1.ResourceRequirements, error) {
21+
return newRequirements("1.0", "500M", "0.5", "400M")
22+
}
23+
24+
// newRequirements returns a new corev1.ResourceRequirements with the specified arguments, and an error
25+
// which indicates if there was a problem parsing the input
26+
func newRequirements(limitsCpu, limitsMemory, requestsCpu, requestsMemory string) (corev1.ResourceRequirements, error) {
27+
limits, err := buildResourceList(limitsCpu, limitsMemory)
28+
if err != nil {
29+
return corev1.ResourceRequirements{}, err
30+
}
31+
32+
requests, err := buildResourceList(requestsCpu, requestsMemory)
33+
if err != nil {
34+
return corev1.ResourceRequirements{}, err
35+
}
36+
return corev1.ResourceRequirements{
37+
Limits: limits,
38+
Requests: requests,
39+
}, nil
40+
}
41+
42+
func buildResourceList(cpu, memory string) (corev1.ResourceList, error) {
43+
cpuQuantity, err := resource.ParseQuantity(cpu)
44+
if err != nil {
45+
return nil, err
46+
}
47+
memoryQuantity, err := resource.ParseQuantity(memory)
48+
if err != nil {
49+
return nil, err
50+
}
51+
return corev1.ResourceList{
52+
resourceCpu: cpuQuantity,
53+
resourceMemory: memoryQuantity,
54+
}, nil
55+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package resourcerequirements
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"k8s.io/apimachinery/pkg/api/resource"
8+
)
9+
10+
func TestNewResourceRequirements_GetsConstructedCorrectly(t *testing.T) {
11+
requirements, err := newRequirements("0.5", "2.0", "500", "1000")
12+
assert.NoError(t, err)
13+
assert.Equal(t, resource.MustParse("0.5"), *requirements.Limits.Cpu())
14+
assert.Equal(t, resource.MustParse("2.0"), *requirements.Limits.Memory())
15+
assert.Equal(t, resource.MustParse("500"), *requirements.Requests.Cpu())
16+
assert.Equal(t, resource.MustParse("1000"), *requirements.Requests.Memory())
17+
}
18+
19+
func TestBadInput_ReturnsError(t *testing.T) {
20+
_, err := newRequirements("BAD_INPUT", "2.0", "500", "1000")
21+
assert.Error(t, err)
22+
}
23+
24+
func TestDefaultValues_DontReturnError(t *testing.T) {
25+
_, err := newDefaultRequirements()
26+
assert.NoError(t, err, "default requirements should never result in an error")
27+
}

0 commit comments

Comments
 (0)