diff --git a/api/v1alpha1/lvmcluster_types.go b/api/v1alpha1/lvmcluster_types.go index cdf04a32b..d6d99b485 100644 --- a/api/v1alpha1/lvmcluster_types.go +++ b/api/v1alpha1/lvmcluster_types.go @@ -45,11 +45,11 @@ type DeviceClass struct { // +kubebuilder:validation:Pattern="^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" Name string `json:"name,omitempty"` - // DeviceSelector is a set of rules that should match for a device to be included in this TopoLVMCluster + // DeviceSelector is a set of rules that should match for a device to be included in the LVMCluster // +optional DeviceSelector *DeviceSelector `json:"deviceSelector,omitempty"` - // NodeSelector chooses nodes + // NodeSelector chooses nodes on which to create the deviceclass // +optional NodeSelector *corev1.NodeSelector `json:"nodeSelector,omitempty"` @@ -79,9 +79,27 @@ type LVMClusterStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "make" to regenerate code after modifying this file - // ready describes if the LvmCluster is ready. + // Ready describes if the LVMCluster is ready. // +optional Ready bool `json:"ready,omitempty"` + // DeviceClassStatuses describes the status of all deviceClasses + DeviceClassStatuses []DeviceClassStatus `json:"deviceClassStatuses,omitempty"` +} + +// DeviceClassStatus defines the observed status of the deviceclass across all nodes +type DeviceClassStatus struct { + // Name is the name of the deviceclass + Name string `json:"name,omitempty"` + // NodeStatus tells if the deviceclass was created on the node + NodeStatus []NodeStatus `json:"nodeStatus,omitempty"` +} + +// NodeStatus defines the observed state of the deviceclass on the node +type NodeStatus struct { + // Node is the name of the node + Node string `json:"node,omitempty"` + // Status is the status of the VG on the node + Status VGStatusType `json:"status,omitempty"` } //+kubebuilder:object:root=true diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index f85fc13bb..e8a7a22c8 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -50,6 +50,26 @@ func (in *DeviceClass) DeepCopy() *DeviceClass { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeviceClassStatus) DeepCopyInto(out *DeviceClassStatus) { + *out = *in + if in.NodeStatus != nil { + in, out := &in.NodeStatus, &out.NodeStatus + *out = make([]NodeStatus, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeviceClassStatus. +func (in *DeviceClassStatus) DeepCopy() *DeviceClassStatus { + if in == nil { + return nil + } + out := new(DeviceClassStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DeviceSelector) DeepCopyInto(out *DeviceSelector) { *out = *in @@ -71,7 +91,7 @@ func (in *LVMCluster) DeepCopyInto(out *LVMCluster) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LVMCluster. @@ -156,6 +176,13 @@ func (in *LVMClusterSpec) DeepCopy() *LVMClusterSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LVMClusterStatus) DeepCopyInto(out *LVMClusterStatus) { *out = *in + if in.DeviceClassStatuses != nil { + in, out := &in.DeviceClassStatuses, &out.DeviceClassStatuses + *out = make([]DeviceClassStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LVMClusterStatus. @@ -361,6 +388,21 @@ func (in *LVMVolumeGroupStatus) DeepCopy() *LVMVolumeGroupStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeStatus) DeepCopyInto(out *NodeStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeStatus. +func (in *NodeStatus) DeepCopy() *NodeStatus { + if in == nil { + return nil + } + out := new(NodeStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VGStatus) DeepCopyInto(out *VGStatus) { *out = *in diff --git a/config/crd/bases/lvm.topolvm.io_lvmclusters.yaml b/config/crd/bases/lvm.topolvm.io_lvmclusters.yaml index c36ce8876..5c2694795 100644 --- a/config/crd/bases/lvm.topolvm.io_lvmclusters.yaml +++ b/config/crd/bases/lvm.topolvm.io_lvmclusters.yaml @@ -43,7 +43,7 @@ spec: properties: deviceSelector: description: DeviceSelector is a set of rules that should match - for a device to be included in this TopoLVMCluster + for a device to be included in the LVMCluster type: object name: description: 'Name of the class, the VG and possibly the storageclass. @@ -54,7 +54,8 @@ spec: pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string nodeSelector: - description: NodeSelector chooses nodes + description: NodeSelector chooses nodes on which to create the + deviceclass properties: nodeSelectorTerms: description: Required. A list of node selector terms. The @@ -184,8 +185,34 @@ spec: status: description: LVMClusterStatus defines the observed state of LVMCluster properties: + deviceClassStatuses: + description: DeviceClassStatuses describes the status of all deviceClasses + items: + description: DeviceClassStatus defines the observed status of the + deviceclass across all nodes + properties: + name: + description: Name is the name of the deviceclass + type: string + nodeStatus: + description: NodeStatus tells if the deviceclass was created + on the node + items: + description: NodeStatus defines the observed state of the + deviceclass on the node + properties: + node: + description: Node is the name of the node + type: string + status: + description: Status is the status of the VG on the node + type: string + type: object + type: array + type: object + type: array ready: - description: ready describes if the LvmCluster is ready. + description: Ready describes if the LVMCluster is ready. type: boolean type: object type: object diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index b3a4f512a..cfc01b366 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -63,6 +63,32 @@ rules: - get - patch - update +- apiGroups: + - lvm.topolvm.io + resources: + - lvmvolumegroupnodestatuses + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - lvm.topolvm.io + resources: + - lvmvolumegroupnodestatuses/finalizers + verbs: + - update +- apiGroups: + - lvm.topolvm.io + resources: + - lvmvolumegroupnodestatuses/status + verbs: + - get + - patch + - update - apiGroups: - lvm.topolvm.io resources: diff --git a/controllers/lvmcluster_controller.go b/controllers/lvmcluster_controller.go index 46267846f..17abd860f 100644 --- a/controllers/lvmcluster_controller.go +++ b/controllers/lvmcluster_controller.go @@ -66,6 +66,9 @@ type LVMClusterReconciler struct { //+kubebuilder:rbac:groups=lvm.topolvm.io,resources=lvmvolumegroups,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=lvm.topolvm.io,resources=lvmvolumegroups/status,verbs=get;update;patch //+kubebuilder:rbac:groups=lvm.topolvm.io,resources=lvmvolumegroups/finalizers,verbs=update +//+kubebuilder:rbac:groups=lvm.topolvm.io,resources=lvmvolumegroupnodestatuses,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=lvm.topolvm.io,resources=lvmvolumegroupnodestatuses/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=lvm.topolvm.io,resources=lvmvolumegroupnodestatuses/finalizers,verbs=update //+kubebuilder:rbac:groups=security.openshift.io,resources=securitycontextconstraints,verbs=get;create;update;delete //+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch @@ -100,12 +103,9 @@ func (r *LVMClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) } result, reconcileError := r.reconcile(ctx, lvmCluster) - // Apply status changes - statusError := r.Client.Status().Update(ctx, lvmCluster) + statusError := r.updateLVMClusterStatus(ctx, lvmCluster) if statusError != nil { - if errors.IsNotFound(err) { - r.Log.Error(statusError, "failed to update status") - } + r.Log.Error(statusError, "failed to update VG Node status") } // Reconcile errors have higher priority than status update errors @@ -197,6 +197,53 @@ func (r *LVMClusterReconciler) reconcile(ctx context.Context, instance *lvmv1alp return ctrl.Result{}, nil } +func (r *LVMClusterReconciler) updateLVMClusterStatus(ctx context.Context, instance *lvmv1alpha1.LVMCluster) error { + + vgNodeMap := make(map[string][]lvmv1alpha1.NodeStatus) + + vgNodeStatusList := &lvmv1alpha1.LVMVolumeGroupNodeStatusList{} + err := r.Client.List(ctx, vgNodeStatusList, client.InNamespace(r.Namespace)) + if err != nil { + r.Log.Error(err, "failed to list LVMVolumeGroupNodeStatus") + return err + } + + for _, nodeItem := range vgNodeStatusList.Items { + for _, item := range nodeItem.Spec.LVMVGStatus { + val, ok := vgNodeMap[item.Name] + if !ok { + vgNodeMap[item.Name] = []lvmv1alpha1.NodeStatus{ + { + Node: nodeItem.Name, + Status: item.Status, + }, + } + } else { + new := lvmv1alpha1.NodeStatus{Node: nodeItem.Name, Status: item.Status} + val = append(val, new) + vgNodeMap[item.Name] = val + } + } + } + + allVgStatuses := []lvmv1alpha1.DeviceClassStatus{} + for k := range vgNodeMap { + //r.Log.Info("vgnode map ", "key", k, "NodeStatus", vgNodeMap[k]) + new := lvmv1alpha1.DeviceClassStatus{Name: k, NodeStatus: vgNodeMap[k]} + allVgStatuses = append(allVgStatuses, new) + } + + instance.Status.DeviceClassStatuses = allVgStatuses + // Apply status changes + err = r.Client.Status().Update(ctx, instance) + if err != nil { + if errors.IsNotFound(err) { + r.Log.Error(err, "failed to update status") + } + } + return err +} + // NOTE: when updating this, please also update doc/design/operator.md type resourceManager interface { diff --git a/controllers/lvmcluster_controller_watches.go b/controllers/lvmcluster_controller_watches.go index acf8c26df..6ab93997c 100644 --- a/controllers/lvmcluster_controller_watches.go +++ b/controllers/lvmcluster_controller_watches.go @@ -17,9 +17,16 @@ limitations under the License. package controllers import ( + "context" + lvmv1alpha1 "github.com/red-hat-storage/lvm-operator/api/v1alpha1" appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" ) // SetupWithManager sets up the controller with the Manager. @@ -27,5 +34,36 @@ func (r *LVMClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&lvmv1alpha1.LVMCluster{}). Owns(&appsv1.DaemonSet{}). + Owns(&lvmv1alpha1.LVMVolumeGroup{}). + Owns(&lvmv1alpha1.LVMVolumeGroupNodeStatus{}). + Watches( + &source.Kind{Type: &lvmv1alpha1.LVMVolumeGroupNodeStatus{}}, + handler.EnqueueRequestsFromMapFunc(r.getLVMClusterObjsForReconcile), + // builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). Complete(r) } + +func (r *LVMClusterReconciler) getLVMClusterObjsForReconcile(obj client.Object) []reconcile.Request { + foundLVMClusterList := &lvmv1alpha1.LVMClusterList{} + listOps := &client.ListOptions{ + Namespace: obj.GetNamespace(), + } + + err := r.Client.List(context.TODO(), foundLVMClusterList, listOps) + if err != nil { + r.Log.Error(err, "getLVMClusterObjsForReconcile: Failed to get LVMCluster objs") + return []reconcile.Request{} + } + + requests := make([]reconcile.Request, len(foundLVMClusterList.Items)) + for i, item := range foundLVMClusterList.Items { + requests[i] = reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: item.GetName(), + Namespace: item.GetNamespace(), + }, + } + } + return requests +}