Skip to content
This repository was archived by the owner on Nov 20, 2021. It is now read-only.

Commit 629fe4c

Browse files
authored
Add node controller (#4)
1 parent c1fab95 commit 629fe4c

File tree

5 files changed

+227
-5
lines changed

5 files changed

+227
-5
lines changed

api/v1alpha1/dockermachine_types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323

2424
const (
2525
MachineFinalizer = "dockermachine.infrastructure.crit.sh"
26+
27+
NodeOwnerLabelName = "infrastructure.crit.sh/dockermachine"
2628
)
2729

2830
// DockerMachineSpec defines the desired state of DockerMachine

controllers/dockermachine_controller.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,6 @@ func (r *DockerMachineReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, re
127127
dm.Spec.ProviderID = fmt.Sprintf("docker://%s", dm.Spec.ContainerName)
128128
}
129129

130-
cfg := &machinev1.Config{}
131-
if err := r.Get(ctx, client.ObjectKey{Name: m.Spec.ConfigRef.Name, Namespace: m.Namespace}, cfg); err != nil {
132-
return ctrl.Result{}, err
133-
}
134-
135130
// TODO(chrism): add label hash of spec (needs config and infra ref fields)
136131
// and diff to determine if the machine should be replaced
137132
ok, err := docker.MachineExists(ctx, dm)
@@ -143,6 +138,11 @@ func (r *DockerMachineReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, re
143138
return ctrl.Result{}, nil
144139
}
145140

141+
cfg := &machinev1.Config{}
142+
if err := r.Get(ctx, client.ObjectKey{Name: m.Spec.ConfigRef.Name, Namespace: m.Namespace}, cfg); err != nil {
143+
return ctrl.Result{}, err
144+
}
145+
146146
if err := docker.CreateMachine(ctx, dm, cfg); err != nil {
147147
dm.Status.SetFailure(mapierrors.CreateMachineError, err.Error())
148148
return ctrl.Result{}, err

controllers/node_controller.go

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
/*
2+
Copyright 2020 Critical Stack, LLC
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package controllers
15+
16+
import (
17+
"context"
18+
"encoding/json"
19+
"errors"
20+
"fmt"
21+
22+
nodeutil "github.com/criticalstack/crit/pkg/kubernetes/util/node"
23+
machinev1 "github.com/criticalstack/machine-api/api/v1alpha1"
24+
"github.com/go-logr/logr"
25+
corev1 "k8s.io/api/core/v1"
26+
apierrors "k8s.io/apimachinery/pkg/api/errors"
27+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
"k8s.io/apimachinery/pkg/runtime"
29+
"k8s.io/client-go/kubernetes"
30+
"k8s.io/client-go/rest"
31+
ctrl "sigs.k8s.io/controller-runtime"
32+
"sigs.k8s.io/controller-runtime/pkg/client"
33+
"sigs.k8s.io/controller-runtime/pkg/controller"
34+
"sigs.k8s.io/kind/pkg/cluster"
35+
36+
infrav1 "github.com/criticalstack/machine-api-provider-docker/api/v1alpha1"
37+
)
38+
39+
// NodeReconciler reconciles a corev1.Node object and creates DockerMachine
40+
// objects for nodes where one does not exist. This ensures that even nodes
41+
// that were created outside of the machine-api are described by Kubernetes
42+
// resources.
43+
type NodeReconciler struct {
44+
client.Client
45+
Log logr.Logger
46+
Scheme *runtime.Scheme
47+
48+
config *rest.Config
49+
}
50+
51+
func (r *NodeReconciler) SetupWithManager(mgr ctrl.Manager, options controller.Options) error {
52+
r.config = mgr.GetConfig()
53+
return ctrl.NewControllerManagedBy(mgr).
54+
For(&corev1.Node{}).
55+
WithOptions(options).
56+
Complete(r)
57+
}
58+
59+
// +kubebuilder:rbac:groups=infrastructure.crit.sh,resources=dockermachines,verbs=get;list;watch;create;update;patch;delete
60+
// +kubebuilder:rbac:groups=infrastructure.crit.sh,resources=dockermachines/status,verbs=get;update;patch
61+
// +kubebuilder:rbac:groups=machine.crit.sh,resources=machines;machines/status,verbs=get;list;watch
62+
// +kubebuilder:rbac:groups=machine.crit.sh,resources=configs;configs/status,verbs=get;list;watch
63+
// +kubebuilder:rbac:groups=core,resources=nodes,verbs=get;list;watch;create;update;patch;delete
64+
65+
func (r *NodeReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
66+
ctx := context.Background()
67+
log := r.Log.WithValues("node", req.NamespacedName)
68+
69+
n := &corev1.Node{}
70+
if err := r.Get(ctx, req.NamespacedName, n); err != nil {
71+
if apierrors.IsNotFound(err) {
72+
return ctrl.Result{}, nil
73+
}
74+
return ctrl.Result{}, err
75+
}
76+
77+
// TODO: branch here on node NotReady and check provider api for terminated
78+
// machines, and delete machine if necessary (since no longer valid
79+
80+
annotations := n.GetAnnotations()
81+
if _, ok := annotations[infrav1.NodeOwnerLabelName]; !ok {
82+
log.Info("dockermachine label not found")
83+
if err := r.ensureDockerMachineForNode(ctx, n); err != nil {
84+
return ctrl.Result{}, err
85+
}
86+
}
87+
if refData, ok := annotations[machinev1.NodeOwnerLabelName]; ok {
88+
var ref corev1.ObjectReference
89+
if err := json.Unmarshal([]byte(refData), &ref); err != nil {
90+
return ctrl.Result{}, err
91+
}
92+
dockerRefData, ok := annotations[infrav1.NodeOwnerLabelName]
93+
if !ok {
94+
return ctrl.Result{}, errors.New("cannot find DockerMachine, missing infra annotation")
95+
}
96+
var amRef corev1.ObjectReference
97+
if err := json.Unmarshal([]byte(dockerRefData), &amRef); err != nil {
98+
return ctrl.Result{}, err
99+
}
100+
//log.Info("machine label not found")
101+
am := &infrav1.DockerMachine{}
102+
if err := r.Get(ctx, client.ObjectKey{Namespace: metav1.NamespaceSystem, Name: amRef.Name}, am); err != nil {
103+
return ctrl.Result{}, err
104+
}
105+
if err := r.ensureMachineHasInfraRef(ctx, am, ref); err != nil {
106+
return ctrl.Result{}, err
107+
}
108+
}
109+
return ctrl.Result{}, nil
110+
}
111+
112+
func (r *NodeReconciler) ensureMachineHasInfraRef(ctx context.Context, am *infrav1.DockerMachine, ref corev1.ObjectReference) error {
113+
m := &machinev1.Machine{}
114+
if err := r.Get(ctx, client.ObjectKey{Namespace: metav1.NamespaceSystem, Name: ref.Name}, m); err != nil {
115+
return err
116+
}
117+
if m.Spec.InfrastructureRef.Kind == "DockerMachine" && m.Spec.InfrastructureRef.Name == am.Name {
118+
return nil
119+
}
120+
m.Spec.InfrastructureRef = corev1.ObjectReference{
121+
APIVersion: am.APIVersion,
122+
Kind: "DockerMachine",
123+
Name: am.ObjectMeta.Name,
124+
Namespace: am.Namespace,
125+
}
126+
if err := r.Update(ctx, m); err != nil {
127+
return err
128+
}
129+
return nil
130+
}
131+
132+
func (r *NodeReconciler) ensureDockerMachineForNode(ctx context.Context, n *corev1.Node) error {
133+
log := r.Log.WithValues("node", n.Name)
134+
135+
annotations := n.GetAnnotations()
136+
if _, ok := annotations[infrav1.NodeOwnerLabelName]; ok {
137+
return nil
138+
}
139+
machines := &infrav1.DockerMachineList{}
140+
if err := r.List(ctx, machines); err != nil {
141+
return err
142+
}
143+
for _, m := range machines.Items {
144+
if m.Spec.ProviderID == n.Spec.ProviderID {
145+
log.V(1).Info("node already has a machine associated with it, only needs an annotation")
146+
return r.setDockerMachineAnnotation(ctx, &m, n.Name)
147+
}
148+
}
149+
clusterName, err := r.findNodeCluster(n.Name)
150+
if err != nil {
151+
return err
152+
}
153+
am := &infrav1.DockerMachine{
154+
ObjectMeta: metav1.ObjectMeta{
155+
Name: n.Name,
156+
Namespace: metav1.NamespaceSystem,
157+
},
158+
Spec: infrav1.DockerMachineSpec{
159+
ProviderID: fmt.Sprintf("docker://%s", n.Name),
160+
ClusterName: clusterName,
161+
ContainerName: n.Name,
162+
Image: "",
163+
},
164+
Status: infrav1.DockerMachineStatus{},
165+
}
166+
if err := r.Create(ctx, am); err != nil {
167+
return err
168+
}
169+
return r.setDockerMachineAnnotation(ctx, am, n.Name)
170+
}
171+
172+
func (r *NodeReconciler) findNodeCluster(nodeName string) (string, error) {
173+
provider := cluster.NewProvider(cluster.ProviderWithDocker())
174+
clusters, err := provider.List()
175+
if err != nil {
176+
return "", err
177+
}
178+
for _, c := range clusters {
179+
nodes, err := provider.ListNodes(c)
180+
if err != nil {
181+
return "", err
182+
}
183+
for _, n := range nodes {
184+
if n.String() == nodeName {
185+
return c, nil
186+
}
187+
}
188+
}
189+
return "", fmt.Errorf("could not find cluster for node %q", nodeName)
190+
}
191+
192+
func (r *NodeReconciler) setDockerMachineAnnotation(ctx context.Context, m *infrav1.DockerMachine, name string) error {
193+
ref := corev1.ObjectReference{
194+
APIVersion: m.APIVersion,
195+
Kind: "DockerMachine",
196+
Name: m.ObjectMeta.Name,
197+
Namespace: m.Namespace,
198+
}
199+
data, err := json.Marshal(ref)
200+
if err != nil {
201+
return err
202+
}
203+
k, err := kubernetes.NewForConfig(r.config)
204+
if err != nil {
205+
return err
206+
}
207+
return nodeutil.PatchNode(ctx, k, name, func(n *corev1.Node) {
208+
annotations := n.GetAnnotations()
209+
annotations[infrav1.NodeOwnerLabelName] = string(data)
210+
})
211+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ require (
1616
k8s.io/client-go v0.18.5
1717
sigs.k8s.io/cluster-api v0.3.6
1818
sigs.k8s.io/controller-runtime v0.6.0
19+
sigs.k8s.io/kind v0.8.1
1920
)

main.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,14 @@ func main() {
8888
setupLog.Error(err, "unable to create controller", "controller", "DockerInfrastructureProvider")
8989
os.Exit(1)
9090
}
91+
if err = (&controllers.NodeReconciler{
92+
Client: mgr.GetClient(),
93+
Log: ctrl.Log.WithName("controllers").WithName("DockerInfrastructureProvider"),
94+
Scheme: mgr.GetScheme(),
95+
}).SetupWithManager(mgr, controller.Options{}); err != nil {
96+
setupLog.Error(err, "unable to create controller", "controller", "DockerInfrastructureProvider")
97+
os.Exit(1)
98+
}
9199
// +kubebuilder:scaffold:builder
92100

93101
setupLog.Info("starting manager")

0 commit comments

Comments
 (0)