github.com/kotalco/kotal@v0.3.0/controllers/filecoin/node_controller.go (about) 1 package controllers 2 3 import ( 4 "context" 5 _ "embed" 6 "fmt" 7 8 filecoinv1alpha1 "github.com/kotalco/kotal/apis/filecoin/v1alpha1" 9 filecoinClients "github.com/kotalco/kotal/clients/filecoin" 10 "github.com/kotalco/kotal/controllers/shared" 11 appsv1 "k8s.io/api/apps/v1" 12 corev1 "k8s.io/api/core/v1" 13 "k8s.io/apimachinery/pkg/api/resource" 14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 "k8s.io/apimachinery/pkg/util/intstr" 16 ctrl "sigs.k8s.io/controller-runtime" 17 "sigs.k8s.io/controller-runtime/pkg/client" 18 "sigs.k8s.io/controller-runtime/pkg/log" 19 "sigs.k8s.io/controller-runtime/pkg/predicate" 20 ) 21 22 // NodeReconciler reconciles a Node object 23 type NodeReconciler struct { 24 shared.Reconciler 25 } 26 27 var ( 28 //go:embed copy_config_toml.sh 29 CopyConfigToml string 30 ) 31 32 // +kubebuilder:rbac:groups=filecoin.kotal.io,resources=nodes,verbs=get;list;watch;create;update;patch;delete 33 // +kubebuilder:rbac:groups=filecoin.kotal.io,resources=nodes/status,verbs=get;update;patch 34 // +kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=watch;get;list;create;update;delete 35 // +kubebuilder:rbac:groups=core,resources=configmaps;services;persistentvolumeclaims,verbs=watch;get;create;update;list;delete 36 37 // Reconcile reconciles Filecoin network node 38 func (r *NodeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) { 39 defer shared.IgnoreConflicts(&err) 40 41 var node filecoinv1alpha1.Node 42 43 if err = r.Client.Get(ctx, req.NamespacedName, &node); err != nil { 44 err = client.IgnoreNotFound(err) 45 return 46 } 47 48 // default the node if webhooks are disabled 49 if !shared.IsWebhookEnabled() { 50 node.Default() 51 } 52 53 shared.UpdateLabels(&node, "lotus", string(node.Spec.Network)) 54 55 // reconcile service 56 if err = r.ReconcileOwned(ctx, &node, &corev1.Service{}, func(obj client.Object) error { 57 r.specService(&node, obj.(*corev1.Service)) 58 return nil 59 }); err != nil { 60 return 61 } 62 63 // reconcile ConfigMap 64 if err = r.ReconcileOwned(ctx, &node, &corev1.ConfigMap{}, func(obj client.Object) error { 65 // generates filecoin config.toml file from node spec 66 configToml, err := ConfigFromSpec(&node) 67 if err != nil { 68 return err 69 } 70 r.specConfigmap(&node, obj.(*corev1.ConfigMap), configToml) 71 return nil 72 }); err != nil { 73 return 74 } 75 76 // reconcile persistent volume claim 77 if err = r.ReconcileOwned(ctx, &node, &corev1.PersistentVolumeClaim{}, func(obj client.Object) error { 78 r.specPVC(&node, obj.(*corev1.PersistentVolumeClaim)) 79 return nil 80 }); err != nil { 81 return 82 } 83 84 // reconcile stateful set 85 if err = r.ReconcileOwned(ctx, &node, &appsv1.StatefulSet{}, func(obj client.Object) error { 86 client := filecoinClients.NewClient(&node) 87 args := client.Args() 88 args = append(args, node.Spec.ExtraArgs.Encode(false)...) 89 env := client.Env() 90 cmd := client.Command() 91 homeDir := client.HomeDir() 92 return r.specStatefulSet(&node, obj.(*appsv1.StatefulSet), homeDir, cmd, args, env) 93 }); err != nil { 94 return 95 } 96 97 if err = r.updateStatus(ctx, &node); err != nil { 98 return 99 } 100 101 return 102 } 103 104 // updateStatus updates filecoin node status 105 func (r *NodeReconciler) updateStatus(ctx context.Context, node *filecoinv1alpha1.Node) error { 106 node.Status.Client = "lotus" 107 108 if err := r.Status().Update(ctx, node); err != nil { 109 log.FromContext(ctx).Error(err, "unable to update filecoin node status") 110 return err 111 } 112 113 return nil 114 } 115 116 // specPVC updates node PVC spec 117 func (r *NodeReconciler) specPVC(node *filecoinv1alpha1.Node, pvc *corev1.PersistentVolumeClaim) { 118 request := corev1.ResourceList{ 119 corev1.ResourceStorage: resource.MustParse(node.Spec.Resources.Storage), 120 } 121 122 // spec is immutable after creation except resources.requests for bound claims 123 if !pvc.CreationTimestamp.IsZero() { 124 pvc.Spec.Resources.Requests = request 125 return 126 } 127 128 pvc.ObjectMeta.Labels = node.Labels 129 130 pvc.Spec = corev1.PersistentVolumeClaimSpec{ 131 AccessModes: []corev1.PersistentVolumeAccessMode{ 132 corev1.ReadWriteOnce, 133 }, 134 Resources: corev1.VolumeResourceRequirements{ 135 Requests: request, 136 }, 137 StorageClassName: node.Spec.Resources.StorageClass, 138 } 139 } 140 141 // specConfigmap updates node statefulset spec 142 func (r *NodeReconciler) specConfigmap(node *filecoinv1alpha1.Node, configmap *corev1.ConfigMap, configToml string) { 143 configmap.ObjectMeta.Labels = node.Labels 144 145 if configmap.Data == nil { 146 configmap.Data = map[string]string{} 147 } 148 149 configmap.Data["config.toml"] = configToml 150 configmap.Data["copy_config_toml.sh"] = CopyConfigToml 151 152 } 153 154 // specService updates node statefulset spec 155 func (r *NodeReconciler) specService(node *filecoinv1alpha1.Node, svc *corev1.Service) { 156 labels := node.Labels 157 158 svc.ObjectMeta.Labels = labels 159 160 svc.Spec.Ports = []corev1.ServicePort{ 161 { 162 Name: "p2p", 163 Port: int32(node.Spec.P2PPort), 164 TargetPort: intstr.FromString("p2p"), 165 }, 166 } 167 168 if node.Spec.API { 169 svc.Spec.Ports = append(svc.Spec.Ports, corev1.ServicePort{ 170 Name: "api", 171 Port: int32(node.Spec.APIPort), 172 TargetPort: intstr.FromString("api"), 173 }) 174 } 175 176 svc.Spec.Selector = labels 177 } 178 179 // specStatefulSet updates node statefulset spec 180 func (r *NodeReconciler) specStatefulSet(node *filecoinv1alpha1.Node, sts *appsv1.StatefulSet, homeDir string, cmd, args []string, env []corev1.EnvVar) error { 181 labels := node.Labels 182 183 sts.ObjectMeta.Labels = labels 184 185 ports := []corev1.ContainerPort{ 186 { 187 Name: "p2p", 188 ContainerPort: int32(node.Spec.P2PPort), 189 }, 190 } 191 192 if node.Spec.API { 193 ports = append(ports, corev1.ContainerPort{ 194 Name: "api", 195 ContainerPort: int32(node.Spec.APIPort), 196 }) 197 } 198 199 replicas := int32(*node.Spec.Replicas) 200 201 sts.Spec = appsv1.StatefulSetSpec{ 202 Selector: &metav1.LabelSelector{ 203 MatchLabels: labels, 204 }, 205 ServiceName: node.Name, 206 Replicas: &replicas, 207 Template: corev1.PodTemplateSpec{ 208 ObjectMeta: metav1.ObjectMeta{ 209 Labels: labels, 210 }, 211 Spec: corev1.PodSpec{ 212 SecurityContext: shared.SecurityContext(), 213 InitContainers: []corev1.Container{ 214 { 215 Name: "copy-config-toml", 216 Image: shared.BusyboxImage, 217 Env: []corev1.EnvVar{ 218 { 219 Name: shared.EnvDataPath, 220 Value: shared.PathData(homeDir), 221 }, 222 { 223 Name: shared.EnvConfigPath, 224 Value: shared.PathConfig(homeDir), 225 }, 226 }, 227 Command: []string{"/bin/sh"}, 228 Args: []string{fmt.Sprintf("%s/copy_config_toml.sh", shared.PathConfig(homeDir))}, 229 VolumeMounts: []corev1.VolumeMount{ 230 { 231 Name: "data", 232 MountPath: shared.PathData(homeDir), 233 }, 234 { 235 Name: "config", 236 MountPath: shared.PathConfig(homeDir), 237 }, 238 }, 239 }, 240 }, 241 Containers: []corev1.Container{ 242 { 243 Name: "node", 244 Image: node.Spec.Image, 245 Args: args, 246 Command: cmd, 247 Env: env, 248 Ports: ports, 249 VolumeMounts: []corev1.VolumeMount{ 250 { 251 Name: "data", 252 MountPath: shared.PathData(homeDir), 253 }, 254 { 255 Name: "proof-parameters", 256 MountPath: "/var/tmp/filecoin-proof-parameters", 257 }, 258 }, 259 Resources: corev1.ResourceRequirements{ 260 Requests: corev1.ResourceList{ 261 corev1.ResourceCPU: resource.MustParse(node.Spec.Resources.CPU), 262 corev1.ResourceMemory: resource.MustParse(node.Spec.Resources.Memory), 263 }, 264 Limits: corev1.ResourceList{ 265 corev1.ResourceCPU: resource.MustParse(node.Spec.Resources.CPULimit), 266 corev1.ResourceMemory: resource.MustParse(node.Spec.Resources.MemoryLimit), 267 }, 268 }, 269 }, 270 }, 271 Volumes: []corev1.Volume{ 272 { 273 Name: "data", 274 VolumeSource: corev1.VolumeSource{ 275 PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ 276 ClaimName: node.Name, 277 }, 278 }, 279 }, 280 { 281 Name: "config", 282 VolumeSource: corev1.VolumeSource{ 283 ConfigMap: &corev1.ConfigMapVolumeSource{ 284 LocalObjectReference: corev1.LocalObjectReference{ 285 Name: node.Name, 286 }, 287 }, 288 }, 289 }, 290 { 291 Name: "proof-parameters", 292 VolumeSource: corev1.VolumeSource{ 293 EmptyDir: &corev1.EmptyDirVolumeSource{}, 294 }, 295 }, 296 }, 297 }, 298 }, 299 } 300 301 return nil 302 } 303 304 // SetupWithManager adds reconciler to the manager 305 func (r *NodeReconciler) SetupWithManager(mgr ctrl.Manager) error { 306 pred := predicate.GenerationChangedPredicate{} 307 return ctrl.NewControllerManagedBy(mgr). 308 For(&filecoinv1alpha1.Node{}). 309 WithEventFilter(pred). 310 Owns(&appsv1.StatefulSet{}). 311 Owns(&corev1.ConfigMap{}). 312 Owns(&corev1.Service{}). 313 Owns(&corev1.PersistentVolumeClaim{}). 314 Complete(r) 315 }