diff --git a/api/model.go b/api/model.go index b89361d700..78379873f4 100644 --- a/api/model.go +++ b/api/model.go @@ -73,7 +73,7 @@ type Volume struct { Conditions map[string]longhorn.Condition `json:"conditions"` KubernetesStatus longhorn.KubernetesStatus `json:"kubernetesStatus"` CloneStatus longhorn.VolumeCloneStatus `json:"cloneStatus"` - Ready bool `json:"ready"` + Ready longhorn.Condition `json:"ready"` AccessMode longhorn.AccessMode `json:"accessMode"` ShareEndpoint string `json:"shareEndpoint"` @@ -1373,6 +1373,35 @@ func toEmptyResource() *Empty { } } +func toVolumeReadyCondition(v *longhorn.Volume) longhorn.Condition { + scheduledCondition := types.GetCondition(v.Status.Conditions, longhorn.VolumeConditionTypeScheduled) + isCloningDesired := types.IsDataFromVolume(v.Spec.DataSource) + isCloningCompleted := v.Status.CloneStatus.State == longhorn.VolumeCloneStateCompleted + + status := longhorn.ConditionStatusFalse + message := "" + if v.Spec.NodeID == "" && v.Status.State != longhorn.VolumeStateDetached { + message = "volume is not attached to any node" + } else if v.Status.State == longhorn.VolumeStateDetached && scheduledCondition.Status != longhorn.ConditionStatusTrue { + message = fmt.Sprintf("volume is detached but not scheduled with reason %v", scheduledCondition.Reason) + } else if v.Status.Robustness == longhorn.VolumeRobustnessFaulted { + message = "volume is in faulted state" + } else if v.Status.RestoreRequired { + message = "volume requires restore" + } else if isCloningDesired && !isCloningCompleted { + message = "volume is cloning" + } else { + status = longhorn.ConditionStatusTrue + } + + return longhorn.Condition{ + Type: longhorn.VolumeConditionTypeReady, + Status: status, + Reason: "", + Message: message, + } +} + func toSettingResource(setting *longhorn.Setting) *Setting { definition, _ := types.GetSettingDefinition(types.SettingName(setting.Name)) @@ -1563,18 +1592,6 @@ func toVolumeResource(v *longhorn.Volume, ves []*longhorn.Engine, vrs []*longhor // 3. It's faulted. // 4. It's restore pending. // 5. It's failed to clone - ready := true - scheduledCondition := types.GetCondition(v.Status.Conditions, longhorn.VolumeConditionTypeScheduled) - isCloningDesired := types.IsDataFromVolume(v.Spec.DataSource) - isCloningCompleted := v.Status.CloneStatus.State == longhorn.VolumeCloneStateCompleted - if (v.Spec.NodeID == "" && v.Status.State != longhorn.VolumeStateDetached) || - (v.Status.State == longhorn.VolumeStateDetached && scheduledCondition.Status != longhorn.ConditionStatusTrue) || - v.Status.Robustness == longhorn.VolumeRobustnessFaulted || - v.Status.RestoreRequired || - (isCloningDesired && !isCloningCompleted) { - ready = false - } - r := &Volume{ Resource: client.Resource{ Id: v.Name, @@ -1620,7 +1637,7 @@ func toVolumeResource(v *longhorn.Volume, ves []*longhorn.Engine, vrs []*longhor ReplicaZoneSoftAntiAffinity: v.Spec.ReplicaZoneSoftAntiAffinity, ReplicaDiskSoftAntiAffinity: v.Spec.ReplicaDiskSoftAntiAffinity, DataEngine: v.Spec.DataEngine, - Ready: ready, + Ready: toVolumeReadyCondition(v), AccessMode: v.Spec.AccessMode, ShareEndpoint: v.Status.ShareEndpoint, diff --git a/client/generated_volume.go b/client/generated_volume.go index 24831af848..34a55b78b2 100644 --- a/client/generated_volume.go +++ b/client/generated_volume.go @@ -65,7 +65,7 @@ type Volume struct { PurgeStatus []PurgeStatus `json:"purgeStatus,omitempty" yaml:"purge_status,omitempty"` - Ready bool `json:"ready,omitempty" yaml:"ready,omitempty"` + Ready interface{} `json:"ready,omitempty" yaml:"ready,omitempty"` RebuildStatus []RebuildStatus `json:"rebuildStatus,omitempty" yaml:"rebuild_status,omitempty"` diff --git a/csi/controller_server.go b/csi/controller_server.go index 8d6e0f3369..fa880f740b 100644 --- a/csi/controller_server.go +++ b/csi/controller_server.go @@ -487,8 +487,9 @@ func (cs *ControllerServer) ControllerPublishVolume(ctx context.Context, req *cs // TODO: JM should readiness be handled by the caller? // Most of the readiness conditions are covered by the attach, except auto attachment which requires changes to the design // should be handled by the processing of the api return codes - if !volume.Ready { - return nil, status.Errorf(codes.Aborted, "volume %s is not ready for workloads", volumeID) + ready := volume.Ready.(longhorn.Condition) + if ready.Status != longhorn.ConditionStatusTrue { + return nil, status.Errorf(codes.Aborted, "volume %s is not ready for workloads since %s", volumeID, ready.Message) } attachmentID := generateAttachmentID(volumeID, nodeID) diff --git a/csi/node_server.go b/csi/node_server.go index 4fecb3b81f..a15249dfa7 100644 --- a/csi/node_server.go +++ b/csi/node_server.go @@ -163,8 +163,9 @@ func (ns *NodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis } } - if !volume.Ready { - return nil, status.Errorf(codes.Aborted, "volume %s is not ready for workloads", volumeID) + ready := volume.Ready.(longhorn.Condition) + if ready.Status != longhorn.ConditionStatusTrue { + return nil, status.Errorf(codes.Aborted, "volume %s is not ready for workloads since %s", volumeID, ready.Message) } podsStatus := ns.collectWorkloadPodsStatus(volume, log) @@ -454,8 +455,9 @@ func (ns *NodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVol return nil, status.Errorf(codes.InvalidArgument, "volume %s hasn't been attached yet", volumeID) } - if !volume.Ready { - return nil, status.Errorf(codes.Aborted, "volume %s is not ready for workloads", volumeID) + ready := volume.Ready.(longhorn.Condition) + if ready.Status != longhorn.ConditionStatusTrue { + return nil, status.Errorf(codes.Aborted, "volume %s is not ready for workloads since %s", volumeID, ready.Message) } if requiresSharedAccess(volume, volumeCapability) && !volume.Migratable { diff --git a/k8s/pkg/apis/longhorn/v1beta2/volume.go b/k8s/pkg/apis/longhorn/v1beta2/volume.go index 4ed7bca262..a1c8a47c09 100644 --- a/k8s/pkg/apis/longhorn/v1beta2/volume.go +++ b/k8s/pkg/apis/longhorn/v1beta2/volume.go @@ -107,6 +107,7 @@ const ( VolumeConditionTypeRestore = "Restore" VolumeConditionTypeTooManySnapshots = "TooManySnapshots" VolumeConditionTypeWaitForBackingImage = "WaitForBackingImage" + VolumeConditionTypeReady = "Ready" ) const (