diff --git a/apis/apps/v1alpha1/cluster_types.go b/apis/apps/v1alpha1/cluster_types.go index 9fbecdfe6ea..7ca245b1575 100644 --- a/apis/apps/v1alpha1/cluster_types.go +++ b/apis/apps/v1alpha1/cluster_types.go @@ -475,7 +475,7 @@ type InstanceTemplate struct { // Defines VolumeClaimTemplates to override. // Add new or override existing volume claim templates. // +optional - VolumeClaimTemplates []corev1.PersistentVolumeClaim `json:"volumeClaimTemplates,omitempty"` + VolumeClaimTemplates []ClusterComponentVolumeClaimTemplate `json:"volumeClaimTemplates,omitempty"` } // ClusterStatus defines the observed state of Cluster. @@ -804,6 +804,10 @@ type ClusterComponentSpec struct { // Any remaining replicas will be generated using the default template and will follow the default naming rules. // // +optional + // +patchMergeKey=name + // +patchStrategy=merge,retainKeys + // +listType=map + // +listMapKey=name Instances []InstanceTemplate `json:"instances,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"name"` // Specifies the names of instances to be transitioned to offline status. @@ -1420,6 +1424,15 @@ func (r ClusterSpec) GetComponentByName(componentName string) *ClusterComponentS return nil } +func (r ClusterSpec) GetShardingByName(shardingName string) *ShardingSpec { + for _, v := range r.ShardingSpecs { + if v.Name == shardingName { + return &v + } + } + return nil +} + // GetComponentDefRefName gets the name of referenced component definition. func (r ClusterSpec) GetComponentDefRefName(componentName string) string { for _, component := range r.ComponentSpecs { diff --git a/apis/apps/v1alpha1/opsrequest_types.go b/apis/apps/v1alpha1/opsrequest_types.go index e4771d4339e..afb7d5c95ba 100644 --- a/apis/apps/v1alpha1/opsrequest_types.go +++ b/apis/apps/v1alpha1/opsrequest_types.go @@ -61,71 +61,42 @@ type OpsRequestSpec struct { // Defines what component need to horizontal scale the specified replicas. // +optional - // +patchMergeKey=componentName - // +patchStrategy=merge,retainKeys - // +listType=map - // +listMapKey=componentName // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="forbidden to update spec.horizontalScaling" - HorizontalScalingList []HorizontalScaling `json:"horizontalScaling,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"componentName"` + HorizontalScalingList []HorizontalScaling `json:"horizontalScaling,omitempty"` // Note: Quantity struct can not do immutable check by CEL. // Defines what component and volumeClaimTemplate need to expand the specified storage. // +optional - // +patchMergeKey=componentName - // +patchStrategy=merge,retainKeys - // +listType=map - // +listMapKey=componentName - VolumeExpansionList []VolumeExpansion `json:"volumeExpansion,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"componentName"` + VolumeExpansionList []VolumeExpansion `json:"volumeExpansion,omitempty"` // Restarts the specified components. // +optional - // +patchMergeKey=componentName - // +patchStrategy=merge,retainKeys - // +listType=map - // +listMapKey=componentName // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="forbidden to update spec.restart" - RestartList []ComponentOps `json:"restart,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"componentName"` + RestartList []ComponentOps `json:"restart,omitempty"` // Switches over the specified components. // +optional - // +patchMergeKey=componentName - // +patchStrategy=merge,retainKeys - // +listType=map - // +listMapKey=componentName // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="forbidden to update spec.switchover" - SwitchoverList []Switchover `json:"switchover,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"componentName"` + SwitchoverList []Switchover `json:"switchover,omitempty"` // Note: Quantity struct can not do immutable check by CEL. // Defines what component need to vertical scale the specified compute resources. // +optional - // +patchMergeKey=componentName - // +patchStrategy=merge,retainKeys - // +listType=map - // +listMapKey=componentName - VerticalScalingList []VerticalScaling `json:"verticalScaling,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"componentName"` + VerticalScalingList []VerticalScaling `json:"verticalScaling,omitempty"` // Deprecated: replace by reconfigures. // Defines the variables that need to input when updating configuration. // +optional - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="forbidden to update spec.reconfigure" - // +kubebuilder:validation:XValidation:rule="self.configurations.size() > 0", message="Value can not be empty" Reconfigure *Reconfigure `json:"reconfigure,omitempty"` // Defines the variables that need to input when updating configuration. + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="forbidden to update spec.reconfigure" // +optional - // +patchMergeKey=componentName - // +patchStrategy=merge,retainKeys - // +listType=map - // +listMapKey=componentName Reconfigures []Reconfigure `json:"reconfigures,omitempty"` // Defines services the component needs to expose. // +optional - // +patchMergeKey=componentName - // +patchStrategy=merge,retainKeys - // +listType=map - // +listMapKey=componentName - ExposeList []Expose `json:"expose,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"componentName"` + ExposeList []Expose `json:"expose,omitempty"` // Cluster RestoreFrom backup or point in time. // +optional @@ -152,24 +123,23 @@ type OpsRequestSpec struct { RestoreSpec *RestoreSpec `json:"restoreSpec,omitempty"` // Specifies the instances that require re-creation. - // +patchMergeKey=componentName - // +patchStrategy=merge,retainKeys - // +listType=map - // +listMapKey=componentName // +optional // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="forbidden to update spec.rebuildFrom" - RebuildFrom []RebuildInstance `json:"rebuildFrom,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"componentName"` + RebuildFrom []RebuildInstance `json:"rebuildFrom,omitempty"` // Specifies a custom operation as defined by OpsDefinition. // +optional CustomSpec *CustomOpsSpec `json:"customSpec,omitempty"` } -// ComponentOps represents the common variables required for operations within the scope of a component. +// ComponentOps represents the common variables required for operations within the scope of a normal component/shard component. +// +kubebuilder:validation:XValidation:rule="(has(self.componentName) && !has(self.shardingName)) || (has(self.shardingName) && !has(self.componentName))",message="either componentName or shardingName" type ComponentOps struct { // Specifies the name of the cluster component. - // +kubebuilder:validation:Required - ComponentName string `json:"componentName"` + ComponentName string `json:"componentName,omitempty"` + + // Specifies the name of the cluster sharding component. + ShardingName string `json:"shardingName,omitempty"` } type RebuildInstance struct { @@ -240,6 +210,27 @@ type VerticalScaling struct { // Defines the computational resource size for vertical scaling. // +kubebuilder:pruning:PreserveUnknownFields corev1.ResourceRequirements `json:",inline"` + + // Specifies the instance template that need to vertical scale. + Instances []PartInstanceTemplate `json:"instances,omitempty"` +} + +type PartInstanceTemplate struct { + // Refer to the instance template name of the component or sharding. + // +kubebuilder:validation:Required + Name string `json:"name"` + + // Defines the computational resource size for vertical scaling. + // +kubebuilder:pruning:PreserveUnknownFields + corev1.ResourceRequirements `json:",inline"` + + // volumeClaimTemplates specifies the storage size and volumeClaimTemplate name. + // +kubebuilder:validation:Required + // +patchMergeKey=name + // +patchStrategy=merge,retainKeys + // +listType=map + // +listMapKey=name + VolumeClaimTemplates []OpsRequestVolumeClaimTemplate `json:"volumeClaimTemplates" patchStrategy:"merge,retainKeys" patchMergeKey:"name"` } // VolumeExpansion encapsulates the parameters required for a volume expansion operation. @@ -253,6 +244,9 @@ type VolumeExpansion struct { // +listType=map // +listMapKey=name VolumeClaimTemplates []OpsRequestVolumeClaimTemplate `json:"volumeClaimTemplates" patchStrategy:"merge,retainKeys" patchMergeKey:"name"` + + // Specifies the instance template that need to volume expand. + Instances []PartInstanceTemplate `json:"instances,omitempty"` } type OpsRequestVolumeClaimTemplate struct { @@ -355,20 +349,14 @@ type CustomOpsSpec struct { Parallelism intstr.IntOrString `json:"parallelism,omitempty"` // Defines which components need to perform the actions defined by this OpsDefinition. - // At least one component is required. The components are identified by their name and can be merged or retained. + // At least one component/shardComponent is required. The components are identified by their name and can be merged or retained. // +kubebuilder:validation:Required // +kubebuilder:validation:MinItems=1 - // +patchMergeKey=name - // +patchStrategy=merge,retainKeys - // +listType=map - // +listMapKey=name - CustomOpsComponents []CustomOpsComponent `json:"components" patchStrategy:"merge,retainKeys" patchMergeKey:"name"` + CustomOpsItems []CustomOpsItem `json:"items"` } -type CustomOpsComponent struct { - // Specifies the unique identifier of the cluster component - // +kubebuilder:validation:Required - ComponentName string `json:"name"` +type CustomOpsItem struct { + ComponentOps `json:",inline"` // Represents the parameters for this operation as declared in the opsDefinition.spec.parametersSchema. // +patchMergeKey=name @@ -428,7 +416,8 @@ const ( ) type Expose struct { - ComponentOps `json:",inline"` + // Specifies the name of the cluster component. + ComponentName string `json:"componentName,omitempty"` // Controls the expose operation. // If set to Enable, the corresponding service will be exposed. Conversely, if set to Disable, the service will be removed. @@ -437,7 +426,7 @@ type Expose struct { Switch ExposeSwitch `json:"switch"` // A list of services that are to be exposed or removed. - // If componentNamem is not specified, each `OpsService` in the list must specify ports and selectors. + // If componentName is not specified, each `OpsService` in the list must specify ports and selectors. // // +kubebuilder:validation:Required // +kubebuilder:validation:Minitems=0 @@ -696,7 +685,7 @@ type OpsRequestStatus struct { // +optional LastConfiguration LastConfiguration `json:"lastConfiguration,omitempty"` - // Records the status information of components changed due to the operation request. + // Records the status information of components, including the sharding component, that have changed due to the operation request. // +optional Components map[string]OpsRequestComponentStatus `json:"components,omitempty"` @@ -733,7 +722,7 @@ type OpsRequestStatus struct { Conditions []metav1.Condition `json:"conditions,omitempty"` } -// +kubebuilder:validation:XValidation:rule="has(self.objectKey) || has(self.actionName)", message="either objectKey and actionName." +// +kubebuilder:validation:XValidation:rule="has(self.objectKey) || has(self.actionName)", message="at least one objectKey or actionName." type ProgressStatusDetail struct { // Specifies the group to which the current object belongs. @@ -819,11 +808,11 @@ type LastComponentConfiguration struct { // Records the last instances of the component. // +optional - Instances *[]InstanceTemplate `json:"instances,omitempty"` + Instances []InstanceTemplate `json:"instances,omitempty"` // Records the last offline instances of the component. // +optional - OfflineInstances *[]string `json:"offlineInstances,omitempty"` + OfflineInstances []string `json:"offlineInstances,omitempty"` } type LastConfiguration struct { @@ -998,83 +987,12 @@ func init() { SchemeBuilder.Register(&OpsRequest{}, &OpsRequestList{}) } -// GetRestartComponentNameSet gets the component name map with restart operation. -func (r OpsRequestSpec) GetRestartComponentNameSet() ComponentNameSet { - set := make(ComponentNameSet) - for _, v := range r.RestartList { - set[v.ComponentName] = struct{}{} - } - return set -} - -// GetSwitchoverComponentNameSet gets the component name map with switchover operation. -func (r OpsRequestSpec) GetSwitchoverComponentNameSet() ComponentNameSet { - set := make(ComponentNameSet) - for _, v := range r.SwitchoverList { - set[v.ComponentName] = struct{}{} - } - return set -} - -// GetVerticalScalingComponentNameSet gets the component name map with vertical scaling operation. -func (r OpsRequestSpec) GetVerticalScalingComponentNameSet() ComponentNameSet { - set := make(ComponentNameSet) - for _, v := range r.VerticalScalingList { - set[v.ComponentName] = struct{}{} - } - return set -} - -// ToVerticalScalingListToMap converts OpsRequest.spec.verticalScaling list to map -func (r OpsRequestSpec) ToVerticalScalingListToMap() map[string]VerticalScaling { - verticalScalingMap := make(map[string]VerticalScaling) - for _, v := range r.VerticalScalingList { - verticalScalingMap[v.ComponentName] = v - } - return verticalScalingMap -} - -// GetHorizontalScalingComponentNameSet gets the component name map with horizontal scaling operation. -func (r OpsRequestSpec) GetHorizontalScalingComponentNameSet() ComponentNameSet { - set := make(ComponentNameSet) - for _, v := range r.HorizontalScalingList { - set[v.ComponentName] = struct{}{} - } - return set -} - -// ToHorizontalScalingListToMap converts OpsRequest.spec.horizontalScaling list to map -func (r OpsRequestSpec) ToHorizontalScalingListToMap() map[string]HorizontalScaling { - verticalScalingMap := make(map[string]HorizontalScaling) - for _, v := range r.HorizontalScalingList { - verticalScalingMap[v.ComponentName] = v - } - return verticalScalingMap -} - -// GetVolumeExpansionComponentNameSet gets the component name map with volume expansion operation. -func (r OpsRequestSpec) GetVolumeExpansionComponentNameSet() ComponentNameSet { - set := make(ComponentNameSet) - for _, v := range r.VolumeExpansionList { - set[v.ComponentName] = struct{}{} - } - return set -} - -// GetDataScriptComponentNameSet gets the component name map with switchover operation. -func (r OpsRequestSpec) GetDataScriptComponentNameSet() ComponentNameSet { - set := make(ComponentNameSet) - set[r.ScriptSpec.ComponentName] = struct{}{} - return set +func (c ComponentOps) GetComponentName() string { + return c.ComponentName } -// ToVolumeExpansionListToMap converts volumeExpansionList to map -func (r OpsRequestSpec) ToVolumeExpansionListToMap() map[string]VolumeExpansion { - volumeExpansionMap := make(map[string]VolumeExpansion) - for _, v := range r.VolumeExpansionList { - volumeExpansionMap[v.ComponentName] = v - } - return volumeExpansionMap +func (c ComponentOps) GetShardingName() string { + return c.ShardingName } // ToExposeListToMap build expose map @@ -1086,62 +1004,6 @@ func (r OpsRequestSpec) ToExposeListToMap() map[string]Expose { return exposeMap } -// GetReconfiguringComponentNameSet gets the component name map with reconfiguring operation. -func (r OpsRequestSpec) GetReconfiguringComponentNameSet() ComponentNameSet { - if r.Reconfigure == nil { - return nil - } - return ComponentNameSet{ - r.Reconfigure.ComponentName: {}, - } -} - -func (r OpsRequestSpec) GetExposeComponentNameSet() ComponentNameSet { - set := make(ComponentNameSet) - for _, v := range r.ExposeList { - set[v.ComponentName] = struct{}{} - } - return set -} - -// GetUpgradeComponentNameSet gets the component name map with upgrade operation. -func (r *OpsRequest) GetUpgradeComponentNameSet() ComponentNameSet { - if r == nil || r.Spec.Upgrade == nil { - return nil - } - set := make(ComponentNameSet) - for k := range r.Status.Components { - set[k] = struct{}{} - } - return set -} - -// GetComponentNameSet if the operations are within the scope of component, this function should be implemented -func (r *OpsRequest) GetComponentNameSet() ComponentNameSet { - switch r.Spec.Type { - case RestartType: - return r.Spec.GetRestartComponentNameSet() - case VerticalScalingType: - return r.Spec.GetVerticalScalingComponentNameSet() - case HorizontalScalingType: - return r.Spec.GetHorizontalScalingComponentNameSet() - case VolumeExpansionType: - return r.Spec.GetVolumeExpansionComponentNameSet() - case UpgradeType: - return r.GetUpgradeComponentNameSet() - case ReconfiguringType: - return r.Spec.GetReconfiguringComponentNameSet() - case ExposeType: - return r.Spec.GetExposeComponentNameSet() - case SwitchoverType: - return r.Spec.GetSwitchoverComponentNameSet() - case DataScriptType: - return r.Spec.GetDataScriptComponentNameSet() - default: - return nil - } -} - func (p *ProgressStatusDetail) SetStatusAndMessage(status ProgressStatus, message string) { p.Message = message p.Status = status diff --git a/apis/apps/v1alpha1/opsrequest_types_test.go b/apis/apps/v1alpha1/opsrequest_types_test.go index 86e731f117d..c9635b8e702 100644 --- a/apis/apps/v1alpha1/opsrequest_types_test.go +++ b/apis/apps/v1alpha1/opsrequest_types_test.go @@ -22,158 +22,12 @@ import ( var componentName = "mysql" -func mockRestartOps() *OpsRequest { - ops := &OpsRequest{} - ops.Spec.Type = RestartType - ops.Spec.RestartList = []ComponentOps{ - { - ComponentName: componentName, - }, - } - return ops -} - -func TestGetRestartComponentNameSet(t *testing.T) { - ops := mockRestartOps() - componentNameSet := ops.Spec.GetRestartComponentNameSet() - checkComponentMap(t, componentNameSet, len(ops.Spec.RestartList), componentName) - componentNameSet1 := ops.GetComponentNameSet() - checkComponentMap(t, componentNameSet1, len(ops.Spec.RestartList), componentName) -} - -func mockVerticalScalingOps() *OpsRequest { - ops := &OpsRequest{} - ops.Spec.Type = VerticalScalingType - ops.Spec.VerticalScalingList = []VerticalScaling{ - { - ComponentOps: ComponentOps{ - ComponentName: componentName, - }, - }, - } - return ops -} - -func TestGetVerticalScalingComponentNameSet(t *testing.T) { - ops := mockVerticalScalingOps() - componentNameSet := ops.Spec.GetVerticalScalingComponentNameSet() - checkComponentMap(t, componentNameSet, len(ops.Spec.VerticalScalingList), componentName) - componentNameSet1 := ops.GetComponentNameSet() - checkComponentMap(t, componentNameSet1, len(ops.Spec.VerticalScalingList), componentName) -} - -func mockHorizontalScalingOps() *OpsRequest { - ops := &OpsRequest{} - ops.Spec.Type = HorizontalScalingType - ops.Spec.HorizontalScalingList = []HorizontalScaling{ - { - ComponentOps: ComponentOps{ - ComponentName: componentName, - }, - }, - } - return ops -} - -func TestGetHorizontalScalingComponentNameSet(t *testing.T) { - ops := mockHorizontalScalingOps() - componentNameSet := ops.Spec.GetHorizontalScalingComponentNameSet() - checkComponentMap(t, componentNameSet, len(ops.Spec.HorizontalScalingList), componentName) - componentNameSet1 := ops.GetComponentNameSet() - checkComponentMap(t, componentNameSet1, len(ops.Spec.HorizontalScalingList), componentName) -} - -func mockVolumeExpansionOps() *OpsRequest { - ops := &OpsRequest{} - ops.Spec.Type = VolumeExpansionType - ops.Spec.VolumeExpansionList = []VolumeExpansion{ - { - ComponentOps: ComponentOps{ - ComponentName: componentName, - }, - }, - } - return ops -} - -func TestVolumeExpansioncomponentNameSet(t *testing.T) { - ops := mockVolumeExpansionOps() - componentNameSet := ops.Spec.GetVolumeExpansionComponentNameSet() - checkComponentMap(t, componentNameSet, len(ops.Spec.VolumeExpansionList), componentName) - componentNameSet1 := ops.GetComponentNameSet() - checkComponentMap(t, componentNameSet1, len(ops.Spec.VolumeExpansionList), componentName) -} - -func checkComponentMap(t *testing.T, componentNameSet map[string]struct{}, expectLen int, expectName string) { - if len(componentNameSet) != expectLen { - t.Error(`Expected component name map length equals list length`) - } - if _, ok := componentNameSet[expectName]; !ok { - t.Error(`Expected component name map exists the key of "mysql"`) - } -} - -func TestToVerticalScalingListToMap(t *testing.T) { - ops := mockVerticalScalingOps() - verticalScalingMap := ops.Spec.ToVerticalScalingListToMap() - if len(verticalScalingMap) != len(ops.Spec.VerticalScalingList) { - t.Error(`Expected vertical scaling map length equals list length`) - } - if _, ok := verticalScalingMap[componentName]; !ok { - t.Error(`Expected component name map exists the key of "mysql"`) - } -} - -func TestConvertVolumeExpansionListToMap(t *testing.T) { - ops := mockVolumeExpansionOps() - volumeExpansionMap := ops.Spec.ToVolumeExpansionListToMap() - if len(volumeExpansionMap) != len(ops.Spec.VolumeExpansionList) { - t.Error(`Expected volume expansion map length equals list length`) - } - if _, ok := volumeExpansionMap[componentName]; !ok { - t.Error(`Expected component name map exists the key of "mysql"`) - } -} - -func TestToHorizontalScalingListToMap(t *testing.T) { - ops := mockHorizontalScalingOps() - horizontalScalingMap := ops.Spec.ToHorizontalScalingListToMap() - if len(horizontalScalingMap) != len(ops.Spec.HorizontalScalingList) { - t.Error(`Expected horizontal scaling map length equals list length`) - } - if _, ok := horizontalScalingMap[componentName]; !ok { - t.Error(`Expected component name map exists the key of "mysql"`) - } -} - -func TestGetUpgradeComponentNameSet(t *testing.T) { - ops := &OpsRequest{} - ops.Spec.Type = UpgradeType - componentNameSet := ops.GetUpgradeComponentNameSet() - if componentNameSet != nil { - t.Error(`Expected component name map of upgrade ops is nil`) - } - ops.Spec.Upgrade = &Upgrade{ - ClusterVersionRef: "test-version", - } - ops.Status.Components = map[string]OpsRequestComponentStatus{ - componentName: {}, - } - - componentNameSet = ops.GetUpgradeComponentNameSet() - checkComponentMap(t, componentNameSet, len(ops.Status.Components), componentName) - componentNameSet1 := ops.GetComponentNameSet() - checkComponentMap(t, componentNameSet1, len(ops.Status.Components), componentName) -} - func mockExposeOps() *OpsRequest { ops := &OpsRequest{} ops.Spec.Type = ExposeType ops.Spec.ExposeList = []Expose{ { - ComponentOps: ComponentOps{ - ComponentName: componentName, - }, + ComponentName: componentName, }, } return ops @@ -190,25 +44,6 @@ func TestToExposeListToMap(t *testing.T) { } } -func TestGetExposeComponentNameSet(t *testing.T) { - ops := mockExposeOps() - componentNameSet := ops.Spec.GetExposeComponentNameSet() - checkComponentMap(t, componentNameSet, len(ops.Spec.ExposeList), componentName) - componentNameSet1 := ops.GetComponentNameSet() - checkComponentMap(t, componentNameSet1, len(ops.Spec.ExposeList), componentName) -} - -func TestGetReconfiguringComponentNameSet(t *testing.T) { - ops := &OpsRequest{} - ops.Spec.Type = ReconfiguringType - ops.Spec.Reconfigure = &Reconfigure{ - ComponentOps: ComponentOps{ - ComponentName: componentName, - }, - } - ops.Spec.GetReconfiguringComponentNameSet() -} - func TestSetStatusAndMessage(t *testing.T) { p := ProgressStatusDetail{} message := "handle successfully" diff --git a/apis/apps/v1alpha1/opsrequest_webhook.go b/apis/apps/v1alpha1/opsrequest_webhook.go index 40bff05aa35..af542208456 100644 --- a/apis/apps/v1alpha1/opsrequest_webhook.go +++ b/apis/apps/v1alpha1/opsrequest_webhook.go @@ -31,6 +31,7 @@ import ( "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -240,12 +241,11 @@ func (r *OpsRequest) validateExpose(_ context.Context, cluster *Cluster) error { return notEmptyError("spec.expose") } - // compNames := make([]string, len(exposeList)) - var componentNames []string + var compOpsList []ComponentOps counter := 0 for _, v := range exposeList { if len(v.ComponentName) > 0 { - componentNames = append(componentNames, v.ComponentName) + compOpsList = append(compOpsList, ComponentOps{ComponentName: v.ComponentName}) continue } else { counter++ @@ -261,7 +261,7 @@ func (r *OpsRequest) validateExpose(_ context.Context, cluster *Cluster) error { } } } - return r.checkComponentExistence(cluster, componentNames) + return r.checkComponentExistence(cluster, compOpsList) } // validateUpgrade validates spec.restart @@ -271,11 +271,7 @@ func (r *OpsRequest) validateRestart(cluster *Cluster) error { return notEmptyError("spec.restart") } - compNames := make([]string, len(restartList)) - for i, v := range restartList { - compNames[i] = v.ComponentName - } - return r.checkComponentExistence(cluster, compNames) + return r.checkComponentExistence(cluster, restartList) } // validateUpgrade validates spec.clusterOps.upgrade @@ -301,10 +297,12 @@ func (r *OpsRequest) validateVerticalScaling(cluster *Cluster) error { } // validate resources is legal and get component name slice - componentNames := make([]string, len(verticalScalingList)) + compOpsList := make([]ComponentOps, len(verticalScalingList)) for i, v := range verticalScalingList { - componentNames[i] = v.ComponentName - + compOpsList[i] = v.ComponentOps + if err := r.checkInstanceTemplate(cluster, v.ComponentOps, v.Instances); err != nil { + return err + } if invalidValue, err := validateVerticalResourceList(v.Requests); err != nil { return invalidValueError(invalidValue, err.Error()) } @@ -315,7 +313,7 @@ func (r *OpsRequest) validateVerticalScaling(cluster *Cluster) error { return invalidValueError(invalidValue, err.Error()) } } - return r.checkComponentExistence(cluster, componentNames) + return r.checkComponentExistence(cluster, compOpsList) } // validateVerticalScaling validate api is legal when spec.type is VerticalScaling @@ -391,11 +389,11 @@ func (r *OpsRequest) validateHorizontalScaling(_ context.Context, _ client.Clien return notEmptyError("spec.horizontalScaling") } - componentNames := make([]string, len(horizontalScalingList)) + compOpsList := make([]ComponentOps, len(horizontalScalingList)) for i, v := range horizontalScalingList { - componentNames[i] = v.ComponentName + compOpsList[i] = v.ComponentOps } - return r.checkComponentExistence(cluster, componentNames) + return r.checkComponentExistence(cluster, compOpsList) } // validateVolumeExpansion validates volumeExpansion api when spec.type is VolumeExpansion @@ -405,11 +403,15 @@ func (r *OpsRequest) validateVolumeExpansion(ctx context.Context, cli client.Cli return notEmptyError("spec.volumeExpansion") } - componentNames := make([]string, len(volumeExpansionList)) + compOpsList := make([]ComponentOps, len(volumeExpansionList)) for i, v := range volumeExpansionList { - componentNames[i] = v.ComponentName + // TODO: check instances + compOpsList[i] = v.ComponentOps + if err := r.checkInstanceTemplate(cluster, v.ComponentOps, v.Instances); err != nil { + return err + } } - if err := r.checkComponentExistence(cluster, componentNames); err != nil { + if err := r.checkComponentExistence(cluster, compOpsList); err != nil { return err } return r.checkVolumesAllowExpansion(ctx, cli, cluster) @@ -421,34 +423,86 @@ func (r *OpsRequest) validateSwitchover(ctx context.Context, cli client.Client, if len(switchoverList) == 0 { return notEmptyError("spec.switchover") } - componentNames := make([]string, len(switchoverList)) + compOpsList := make([]ComponentOps, len(switchoverList)) for i, v := range switchoverList { - componentNames[i] = v.ComponentName + compOpsList[i] = v.ComponentOps } - if err := r.checkComponentExistence(cluster, componentNames); err != nil { + if err := r.checkComponentExistence(cluster, compOpsList); err != nil { return err } return validateSwitchoverResourceList(ctx, cli, cluster, switchoverList) } +func (r *OpsRequest) checkInstanceTemplate(cluster *Cluster, componentOps ComponentOps, inputInstances []PartInstanceTemplate) error { + instanceNameMap := make(map[string]sets.Empty) + setInstanceMap := func(instances []InstanceTemplate) { + for i := range instances { + instanceNameMap[instances[i].Name] = sets.Empty{} + } + } + if componentOps.ShardingName != "" { + for _, shardingSpec := range cluster.Spec.ShardingSpecs { + if shardingSpec.Name != componentOps.ShardingName { + continue + } + setInstanceMap(shardingSpec.Template.Instances) + } + } else { + for _, compSpec := range cluster.Spec.ComponentSpecs { + if compSpec.Name != componentOps.ComponentName { + continue + } + setInstanceMap(compSpec.Instances) + } + } + var notFoundInstanceNames []string + for _, v := range inputInstances { + if _, ok := instanceNameMap[v.Name]; !ok { + notFoundInstanceNames = append(notFoundInstanceNames, v.Name) + } + } + if len(notFoundInstanceNames) > 0 { + return fmt.Errorf("instance: %v not found in cluster: %s", notFoundInstanceNames, r.Spec.ClusterRef) + } + return nil +} + // checkComponentExistence checks whether components to be operated exist in cluster spec. -func (r *OpsRequest) checkComponentExistence(cluster *Cluster, compNames []string) error { - compSpecNameMap := make(map[string]bool) +func (r *OpsRequest) checkComponentExistence(cluster *Cluster, compOpsList []ComponentOps) error { + compSpecNameMap := make(map[string]sets.Empty) + shardingMap := make(map[string]sets.Empty) for _, compSpec := range cluster.Spec.ComponentSpecs { - compSpecNameMap[compSpec.Name] = true + compSpecNameMap[compSpec.Name] = sets.Empty{} } - - var notFoundCompNames []string - for _, compName := range compNames { - if _, ok := compSpecNameMap[compName]; !ok { - notFoundCompNames = append(notFoundCompNames, compName) + for _, shardingSpec := range cluster.Spec.ShardingSpecs { + shardingMap[shardingSpec.Name] = sets.Empty{} + } + var ( + notFoundCompNames []string + notFoundShardingNames []string + ) + for _, compOps := range compOpsList { + if compOps.ComponentName != "" { + if _, ok := compSpecNameMap[compOps.ComponentName]; !ok { + notFoundCompNames = append(notFoundCompNames, compOps.ComponentName) + } + continue } + if compOps.ShardingName != "" { + if _, ok := shardingMap[compOps.ShardingName]; !ok { + notFoundShardingNames = append(notFoundShardingNames, compOps.ShardingName) + } + continue + } + return fmt.Errorf("shardingName or componentName can not be empty") } if len(notFoundCompNames) > 0 { - return fmt.Errorf("components: %v not found, you can view the components by command: "+ - "kbcli cluster describe %s -n %s", notFoundCompNames, cluster.Name, r.Namespace) + return fmt.Errorf("components: %v not found, in cluster.spec.componentSpecs", notFoundCompNames) + } + if len(notFoundShardingNames) > 0 { + return fmt.Errorf("shardings: %v not found in cluster.spec.shardingSpecs", notFoundShardingNames) } return nil } @@ -461,41 +515,75 @@ func (r *OpsRequest) checkVolumesAllowExpansion(ctx context.Context, cli client. requestStorage resource.Quantity } - // component name -> vct name -> entity vols := make(map[string]map[string]Entity) - for _, comp := range r.Spec.VolumeExpansionList { - for _, vct := range comp.VolumeClaimTemplates { - if _, ok := vols[comp.ComponentName]; !ok { - vols[comp.ComponentName] = make(map[string]Entity) + // component name/ sharding name -> vct name -> entity + getKey := func(compName, shardingName, templateName string) string { + templateKey := "" + if templateName != "" { + templateKey = "." + templateName + } + if compName != "" { + return fmt.Sprintf("component.%s%s", compName, templateKey) + } + return fmt.Sprintf("sharding.%s%s", shardingName, templateKey) + } + setVols := func(vcts []OpsRequestVolumeClaimTemplate, compOps ComponentOps, templateName string) { + for _, vct := range vcts { + key := getKey(compOps.ComponentName, compOps.ShardingName, templateName) + if _, ok := vols[key]; !ok { + vols[key] = make(map[string]Entity) } - vols[comp.ComponentName][vct.Name] = Entity{false, nil, false, vct.Storage} + vols[key][vct.Name] = Entity{false, nil, false, vct.Storage} } } - // traverse the spec to update volumes - for _, comp := range cluster.Spec.ComponentSpecs { - if _, ok := vols[comp.Name]; !ok { - continue // ignore not-exist component + + for _, comp := range r.Spec.VolumeExpansionList { + setVols(comp.VolumeClaimTemplates, comp.ComponentOps, "") + for _, ins := range comp.Instances { + setVols(ins.VolumeClaimTemplates, comp.ComponentOps, ins.Name) } - for _, vct := range comp.VolumeClaimTemplates { - e, ok := vols[comp.Name][vct.Name] - if !ok { - continue + } + fillVol := func(vct ClusterComponentVolumeClaimTemplate, key string) { + e, ok := vols[key][vct.Name] + if !ok { + return + } + e.existInSpec = true + e.storageClassName = vct.Spec.StorageClassName + vols[key][vct.Name] = e + } + fillCompVols := func(compSpec ClusterComponentSpec, compName, shardingName string) { + key := getKey(compName, shardingName, "") + if _, ok := vols[key]; !ok { + return // ignore not-exist component + } + for _, vct := range compSpec.VolumeClaimTemplates { + fillVol(vct, key) + } + for _, ins := range compSpec.Instances { + key = getKey(compName, shardingName, ins.Name) + for _, vct := range ins.VolumeClaimTemplates { + fillVol(vct, key) } - e.existInSpec = true - e.storageClassName = vct.Spec.StorageClassName - vols[comp.Name][vct.Name] = e } } + // traverse the spec to update volumes + for _, comp := range cluster.Spec.ComponentSpecs { + fillCompVols(comp, comp.Name, "") + } + for _, sharding := range cluster.Spec.ShardingSpecs { + fillCompVols(sharding.Template, "", sharding.Name) + } // check all used storage classes var err error - for cname, compVols := range vols { + for key, compVols := range vols { for vname := range compVols { - e := vols[cname][vname] + e := vols[key][vname] if !e.existInSpec { continue } - e.storageClassName, err = r.getSCNameByPvcAndCheckStorageSize(ctx, cli, cname, vname, e.requestStorage) + e.storageClassName, err = r.getSCNameByPvcAndCheckStorageSize(ctx, cli, key, vname, e.requestStorage) if err != nil { return err } @@ -504,11 +592,11 @@ func (r *OpsRequest) checkVolumesAllowExpansion(ctx context.Context, cli client. continue // ignore the error and take it as not-supported } e.allowExpansion = allowExpansion - vols[cname][vname] = e + vols[key][vname] = e } } - for cname, compVols := range vols { + for key, compVols := range vols { var ( notFound []string notSupport []string @@ -525,17 +613,18 @@ func (r *OpsRequest) checkVolumesAllowExpansion(ctx context.Context, cli client. } } } + keyStrs := strings.Split(key, ".") if len(notFound) > 0 { - return fmt.Errorf("volumeClaimTemplates: %v not found in component: %s, you can view infos by command: "+ - "kbcli cluster describe %s -n %s", notFound, cname, cluster.Name, r.Namespace) + return fmt.Errorf("volumeClaimTemplates: %v not found in %s: %s, you can view infos by command: "+ + "kubectl get cluster %s -n %s", notFound, keyStrs[0], keyStrs[1], cluster.Name, r.Namespace) } if len(notSupport) > 0 { var notSupportScString string if len(notSupportSc) > 0 { notSupportScString = fmt.Sprintf("storageClass: %v of ", notSupportSc) } - return fmt.Errorf(notSupportScString+"volumeClaimTemplate: %s not support volume expansion in component: %s, you can view infos by command: "+ - "kubectl get sc", notSupport, cname) + return fmt.Errorf(notSupportScString+"volumeClaimTemplate: %v not support volume expansion in %s: %s, you can view infos by command: "+ + "kubectl get sc", notSupport, keyStrs[0], keyStrs[1]) } } return nil @@ -562,31 +651,27 @@ func (r *OpsRequest) checkStorageClassAllowExpansion(ctx context.Context, // getSCNameByPvcAndCheckStorageSize gets the storageClassName by pvc and checks if the storage size is valid. func (r *OpsRequest) getSCNameByPvcAndCheckStorageSize(ctx context.Context, cli client.Client, - compName, + key, vctName string, requestStorage resource.Quantity) (*string, error) { + keyStrs := strings.Split(key, ".") + matchingLabels := client.MatchingLabels{ + constant.AppInstanceLabelKey: r.Spec.ClusterRef, + constant.VolumeClaimTemplateNameLabelKey: vctName, + } + if keyStrs[0] == "component" { + matchingLabels[constant.KBAppComponentLabelKey] = keyStrs[1] + } else { + matchingLabels[constant.KBAppShardingNameLabelKey] = keyStrs[1] + } pvcList := &corev1.PersistentVolumeClaimList{} - if err := cli.List(ctx, pvcList, client.InNamespace(r.Namespace), client.MatchingLabels{ - constant.AppInstanceLabelKey: r.Spec.ClusterRef, - constant.KBAppComponentLabelKey: compName, - }); err != nil { + if err := cli.List(ctx, pvcList, client.InNamespace(r.Namespace), matchingLabels); err != nil { return nil, err } if len(pvcList.Items) == 0 { return nil, nil } - var pvc *corev1.PersistentVolumeClaim - for _, v := range pvcList.Items { - // VolumeClaimTemplateNameLabelKeyForLegacy is deprecated: only compatible with version 0.5, will be removed in 0.7? - if v.Labels[constant.VolumeClaimTemplateNameLabelKey] == vctName || - v.Labels[constant.VolumeClaimTemplateNameLabelKeyForLegacy] == vctName { - pvc = &v - break - } - } - if pvc == nil { - return nil, nil - } + pvc := pvcList.Items[0] previousValue := *pvc.Status.Capacity.Storage() if requestStorage.Cmp(previousValue) < 0 { return nil, fmt.Errorf(`requested storage size of volumeClaimTemplate "%s" can not less than status.capacity.storage "%s" `, @@ -626,7 +711,7 @@ func (r *OpsRequest) validateDataScript(ctx context.Context, cli client.Client, return notEmptyError("spec.scriptSpec") } - if err := r.checkComponentExistence(cluster, []string{scriptSpec.ComponentName}); err != nil { + if err := r.checkComponentExistence(cluster, []ComponentOps{scriptSpec.ComponentOps}); err != nil { return err } diff --git a/apis/apps/v1alpha1/type.go b/apis/apps/v1alpha1/type.go index 6aecaae1c5d..341a5ad6d85 100644 --- a/apis/apps/v1alpha1/type.go +++ b/apis/apps/v1alpha1/type.go @@ -792,8 +792,6 @@ func RegisterWebhookManager(mgr manager.Manager) { webhookMgr = &webhookManager{mgr.GetClient()} } -type ComponentNameSet map[string]struct{} - var ( ErrWorkloadTypeIsUnknown = errors.New("workloadType is unknown") ErrWorkloadTypeIsStateless = errors.New("workloadType should not be stateless") diff --git a/apis/apps/v1alpha1/zz_generated.deepcopy.go b/apis/apps/v1alpha1/zz_generated.deepcopy.go index 158f1516479..a41cfb30499 100644 --- a/apis/apps/v1alpha1/zz_generated.deepcopy.go +++ b/apis/apps/v1alpha1/zz_generated.deepcopy.go @@ -1847,27 +1847,6 @@ func (in ComponentMessageMap) DeepCopy() ComponentMessageMap { return *out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in ComponentNameSet) DeepCopyInto(out *ComponentNameSet) { - { - in := &in - *out = make(ComponentNameSet, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentNameSet. -func (in ComponentNameSet) DeepCopy() ComponentNameSet { - if in == nil { - return nil - } - out := new(ComponentNameSet) - in.DeepCopyInto(out) - return *out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ComponentOps) DeepCopyInto(out *ComponentOps) { *out = *in @@ -2936,8 +2915,9 @@ func (in *CustomLabelSpec) DeepCopy() *CustomLabelSpec { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CustomOpsComponent) DeepCopyInto(out *CustomOpsComponent) { +func (in *CustomOpsItem) DeepCopyInto(out *CustomOpsItem) { *out = *in + out.ComponentOps = in.ComponentOps if in.Parameters != nil { in, out := &in.Parameters, &out.Parameters *out = make([]Parameter, len(*in)) @@ -2945,12 +2925,12 @@ func (in *CustomOpsComponent) DeepCopyInto(out *CustomOpsComponent) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomOpsComponent. -func (in *CustomOpsComponent) DeepCopy() *CustomOpsComponent { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomOpsItem. +func (in *CustomOpsItem) DeepCopy() *CustomOpsItem { if in == nil { return nil } - out := new(CustomOpsComponent) + out := new(CustomOpsItem) in.DeepCopyInto(out) return out } @@ -2964,9 +2944,9 @@ func (in *CustomOpsSpec) DeepCopyInto(out *CustomOpsSpec) { **out = **in } out.Parallelism = in.Parallelism - if in.CustomOpsComponents != nil { - in, out := &in.CustomOpsComponents, &out.CustomOpsComponents - *out = make([]CustomOpsComponent, len(*in)) + if in.CustomOpsItems != nil { + in, out := &in.CustomOpsItems, &out.CustomOpsItems + *out = make([]CustomOpsItem, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -3081,7 +3061,6 @@ func (in *ExecAction) DeepCopy() *ExecAction { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Expose) DeepCopyInto(out *Expose) { *out = *in - out.ComponentOps = in.ComponentOps if in.Services != nil { in, out := &in.Services, &out.Services *out = make([]OpsService, len(*in)) @@ -3318,7 +3297,7 @@ func (in *InstanceTemplate) DeepCopyInto(out *InstanceTemplate) { } if in.VolumeClaimTemplates != nil { in, out := &in.VolumeClaimTemplates, &out.VolumeClaimTemplates - *out = make([]v1.PersistentVolumeClaim, len(*in)) + *out = make([]ClusterComponentVolumeClaimTemplate, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -3410,23 +3389,15 @@ func (in *LastComponentConfiguration) DeepCopyInto(out *LastComponentConfigurati } if in.Instances != nil { in, out := &in.Instances, &out.Instances - *out = new([]InstanceTemplate) - if **in != nil { - in, out := *in, *out - *out = make([]InstanceTemplate, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } + *out = make([]InstanceTemplate, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) } } if in.OfflineInstances != nil { in, out := &in.OfflineInstances, &out.OfflineInstances - *out = new([]string) - if **in != nil { - in, out := *in, *out - *out = make([]string, len(*in)) - copy(*out, *in) - } + *out = make([]string, len(*in)) + copy(*out, *in) } } @@ -4340,6 +4311,29 @@ func (in *ParametersSchema) DeepCopy() *ParametersSchema { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PartInstanceTemplate) DeepCopyInto(out *PartInstanceTemplate) { + *out = *in + in.ResourceRequirements.DeepCopyInto(&out.ResourceRequirements) + if in.VolumeClaimTemplates != nil { + in, out := &in.VolumeClaimTemplates, &out.VolumeClaimTemplates + *out = make([]OpsRequestVolumeClaimTemplate, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PartInstanceTemplate. +func (in *PartInstanceTemplate) DeepCopy() *PartInstanceTemplate { + if in == nil { + return nil + } + out := new(PartInstanceTemplate) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PasswordConfig) DeepCopyInto(out *PasswordConfig) { *out = *in @@ -6111,6 +6105,13 @@ func (in *VerticalScaling) DeepCopyInto(out *VerticalScaling) { *out = *in out.ComponentOps = in.ComponentOps in.ResourceRequirements.DeepCopyInto(&out.ResourceRequirements) + if in.Instances != nil { + in, out := &in.Instances, &out.Instances + *out = make([]PartInstanceTemplate, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VerticalScaling. @@ -6134,6 +6135,13 @@ func (in *VolumeExpansion) DeepCopyInto(out *VolumeExpansion) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Instances != nil { + in, out := &in.Instances, &out.Instances + *out = make([]PartInstanceTemplate, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeExpansion. diff --git a/apis/workloads/v1alpha1/instanceset_types.go b/apis/workloads/v1alpha1/instanceset_types.go index dcf12512aec..1bc31586928 100644 --- a/apis/workloads/v1alpha1/instanceset_types.go +++ b/apis/workloads/v1alpha1/instanceset_types.go @@ -183,6 +183,10 @@ type InstanceSetSpec struct { // Any remaining replicas will be generated using the default template and will follow the default naming rules. // // +optional + // +patchMergeKey=name + // +patchStrategy=merge,retainKeys + // +listType=map + // +listMapKey=name Instances []InstanceTemplate `json:"instances,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"name"` // Specifies the names of instances to be transitioned to offline status. diff --git a/config/crd/bases/apps.kubeblocks.io_clusters.yaml b/config/crd/bases/apps.kubeblocks.io_clusters.yaml index b94251418b9..c67d8222d14 100644 --- a/config/crd/bases/apps.kubeblocks.io_clusters.yaml +++ b/config/crd/bases/apps.kubeblocks.io_clusters.yaml @@ -675,166 +675,40 @@ spec: description: Defines VolumeClaimTemplates to override. Add new or override existing volume claim templates. items: - description: PersistentVolumeClaim is a user's request - for and claim to a persistent volume properties: - apiVersion: - description: 'APIVersion defines the versioned schema - of this representation of an object. Servers should - convert recognized schemas to the latest internal - value, and may reject unrecognized values. More - info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing - the REST resource this object represents. Servers - may infer this from the endpoint the client submits - requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + name: + description: "Refers to the name of a volumeMount + defined in either: \n - `componentDefinition.spec.runtime.containers[*].volumeMounts` + - `clusterDefinition.spec.componentDefs[*].podSpec.containers[*].volumeMounts` + (deprecated) \n The value of `name` must match + the `name` field of a volumeMount specified in + the corresponding `volumeMounts` array." type: string - metadata: - description: 'Standard object''s metadata. More - info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata' - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object spec: - description: 'spec defines the desired characteristics - of a volume requested by a pod author. More info: - https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + description: "Defines the desired characteristics + of a PersistentVolumeClaim that will be created + for the volume with the mount name specified in + the `name` field. \n When a Pod is created for + this ClusterComponent, a new PVC will be created + based on the specification defined in the `spec` + field. The PVC will be associated with the volume + mount specified by the `name` field." properties: accessModes: - description: 'accessModes contains the desired - access modes the volume should have. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + description: 'Contains the desired access modes + the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1.' items: type: string type: array - dataSource: - description: 'dataSource field can be used to - specify either: * An existing VolumeSnapshot - object (snapshot.storage.k8s.io/VolumeSnapshot) - * An existing PVC (PersistentVolumeClaim) - If the provisioner or an external controller - can support the specified data source, it - will create a new volume based on the contents - of the specified data source. When the AnyVolumeDataSource - feature gate is enabled, dataSource contents - will be copied to dataSourceRef, and dataSourceRef - contents will be copied to dataSource when - dataSourceRef.namespace is not specified. - If the namespace is specified, then dataSourceRef - will not be copied to dataSource.' - properties: - apiGroup: - description: APIGroup is the group for the - resource being referenced. If APIGroup - is not specified, the specified Kind must - be in the core API group. For any other - third-party types, APIGroup is required. - type: string - kind: - description: Kind is the type of resource - being referenced - type: string - name: - description: Name is the name of resource - being referenced - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - dataSourceRef: - description: 'dataSourceRef specifies the object - from which to populate the volume with data, - if a non-empty volume is desired. This may - be any object from a non-empty API group (non - core object) or a PersistentVolumeClaim object. - When this field is specified, volume binding - will only succeed if the type of the specified - object matches some installed volume populator - or dynamic provisioner. This field will replace - the functionality of the dataSource field - and as such if both fields are non-empty, - they must have the same value. For backwards - compatibility, when namespace isn''t specified - in dataSourceRef, both fields (dataSource - and dataSourceRef) will be set to the same - value automatically if one of them is empty - and the other is non-empty. When namespace - is specified in dataSourceRef, dataSource - isn''t set to the same value and must be empty. - There are three important differences between - dataSource and dataSourceRef: * While dataSource - only allows two specific types of objects, - dataSourceRef allows any non-core object, - as well as PersistentVolumeClaim objects. - * While dataSource ignores disallowed values - (dropping them), dataSourceRef preserves all - values, and generates an error if a disallowed - value is specified. * While dataSource only - allows local objects, dataSourceRef allows - objects in any namespaces. (Beta) Using this - field requires the AnyVolumeDataSource feature - gate to be enabled. (Alpha) Using the namespace - field of dataSourceRef requires the CrossNamespaceVolumeDataSource - feature gate to be enabled.' - properties: - apiGroup: - description: APIGroup is the group for the - resource being referenced. If APIGroup - is not specified, the specified Kind must - be in the core API group. For any other - third-party types, APIGroup is required. - type: string - kind: - description: Kind is the type of resource - being referenced - type: string - name: - description: Name is the name of resource - being referenced - type: string - namespace: - description: Namespace is the namespace - of resource being referenced Note that - when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant - object is required in the referent namespace - to allow that namespace's owner to accept - the reference. See the ReferenceGrant - documentation for details. (Alpha) This - field requires the CrossNamespaceVolumeDataSource - feature gate to be enabled. - type: string - required: - - kind - - name - type: object + x-kubernetes-preserve-unknown-fields: true resources: - description: 'resources represents the minimum - resources the volume should have. If RecoverVolumeExpansionFailure - feature is enabled users are allowed to specify + description: 'Represents the minimum resources + the volume should have. If the RecoverVolumeExpansionFailure + feature is enabled, users are allowed to specify resource requirements that are lower than - previous value but must still be higher than - capacity recorded in the status field of the - claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + the previous value but must still be higher + than the capacity recorded in the status field + of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources.' properties: claims: description: "Claims lists the names of @@ -889,235 +763,18 @@ spec: More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object - selector: - description: selector is a label query over - volumes to consider for binding. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true storageClassName: - description: 'storageClassName is the name of - the StorageClass required by the claim. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + description: 'The name of the StorageClass required + by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1.' type: string volumeMode: - description: volumeMode defines what type of - volume is required by the claim. Value of - Filesystem is implied when not included in - claim spec. - type: string - volumeName: - description: volumeName is the binding reference - to the PersistentVolume backing this claim. - type: string - type: object - status: - description: 'status represents the current information/status - of a persistent volume claim. Read-only. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' - properties: - accessModes: - description: 'accessModes contains the actual - access modes the volume backing the PVC has. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - allocatedResourceStatuses: - additionalProperties: - description: When a controller receives persistentvolume - claim update with ClaimResourceStatus for - a resource that it does not recognizes, - then it should ignore that update and let - other controllers handle it. - type: string - description: "allocatedResourceStatuses stores - status of resource being resized for the given - PVC. Key names follow standard Kubernetes - label syntax. Valid values are either: * Un-prefixed - keys: - storage - the capacity of the volume. - * Custom resources must use implementation-defined - prefixed names such as \"example.com/my-custom-resource\" - Apart from above values - keys that are unprefixed - or have kubernetes.io prefix are considered - reserved and hence may not be used. \n ClaimResourceStatus - can be in any of following states: - ControllerResizeInProgress: - State set when resize controller starts resizing - the volume in control-plane. - ControllerResizeFailed: - State set when resize has failed in resize - controller with a terminal error. - NodeResizePending: - State set when resize controller has finished - resizing the volume but further resizing of - volume is needed on the node. - NodeResizeInProgress: - State set when kubelet starts resizing the - volume. - NodeResizeFailed: State set when - resizing has failed in kubelet with a terminal - error. Transient errors don't set NodeResizeFailed. - For example: if expanding a PVC for more capacity - - this field can be one of the following states: - - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeFailed\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizePending\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeFailed\" When this field is - not set, it means that no resize operation - is in progress for the given PVC. \n A controller - that receives PVC update with previously unknown - resourceName or ClaimResourceStatus should - ignore the update for the purpose it was designed. - For example - a controller that only is responsible - for resizing capacity of the volume, should - ignore PVC updates that change other valid - resources associated with PVC. \n This is - an alpha field and requires enabling RecoverVolumeExpansionFailure - feature." - type: object - x-kubernetes-map-type: granular - allocatedResources: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: "allocatedResources tracks the - resources allocated to a PVC including its - capacity. Key names follow standard Kubernetes - label syntax. Valid values are either: * Un-prefixed - keys: - storage - the capacity of the volume. - * Custom resources must use implementation-defined - prefixed names such as \"example.com/my-custom-resource\" - Apart from above values - keys that are unprefixed - or have kubernetes.io prefix are considered - reserved and hence may not be used. \n Capacity - reported here may be larger than the actual - capacity when a volume expansion operation - is requested. For storage quota, the larger - value from allocatedResources and PVC.spec.resources - is used. If allocatedResources is not set, - PVC.spec.resources alone is used for quota - calculation. If a volume expansion capacity - request is lowered, allocatedResources is - only lowered if there are no expansion operations - in progress and if the actual volume capacity - is equal or lower than the requested capacity. - \n A controller that receives PVC update with - previously unknown resourceName should ignore - the update for the purpose it was designed. - For example - a controller that only is responsible - for resizing capacity of the volume, should - ignore PVC updates that change other valid - resources associated with PVC. \n This is - an alpha field and requires enabling RecoverVolumeExpansionFailure - feature." - type: object - capacity: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: capacity represents the actual - resources of the underlying volume. - type: object - conditions: - description: conditions is the current Condition - of persistent volume claim. If underlying - persistent volume is being resized then the - Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition - contains details about state of pvc - properties: - lastProbeTime: - description: lastProbeTime is the time - we probed the condition. - format: date-time - type: string - lastTransitionTime: - description: lastTransitionTime is the - time the condition transitioned from - one status to another. - format: date-time - type: string - message: - description: message is the human-readable - message indicating details about last - transition. - type: string - reason: - description: reason is a unique, this - should be a short, machine understandable - string that gives the reason for condition's - last transition. If it reports "ResizeStarted" - that means the underlying persistent - volume is being resized. - type: string - status: - type: string - type: - description: PersistentVolumeClaimConditionType - is a valid value of PersistentVolumeClaimCondition.Type - type: string - required: - - status - - type - type: object - type: array - phase: - description: phase represents the current phase - of PersistentVolumeClaim. + description: Defines what type of volume is + required by the claim, either Block or Filesystem. type: string type: object + required: + - name type: object type: array volumeMounts: @@ -2967,6 +2624,9 @@ spec: - name type: object type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map issuer: description: Specifies the configuration for the TLS certificates issuer. It allows defining the issuer name and the reference @@ -7073,174 +6733,43 @@ spec: description: Defines VolumeClaimTemplates to override. Add new or override existing volume claim templates. items: - description: PersistentVolumeClaim is a user's request - for and claim to a persistent volume properties: - apiVersion: - description: 'APIVersion defines the versioned - schema of this representation of an object. - Servers should convert recognized schemas - to the latest internal value, and may reject - unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing - the REST resource this object represents. - Servers may infer this from the endpoint the - client submits requests to. Cannot be updated. - In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + name: + description: "Refers to the name of a volumeMount + defined in either: \n - `componentDefinition.spec.runtime.containers[*].volumeMounts` + - `clusterDefinition.spec.componentDefs[*].podSpec.containers[*].volumeMounts` + (deprecated) \n The value of `name` must match + the `name` field of a volumeMount specified + in the corresponding `volumeMounts` array." type: string - metadata: - description: 'Standard object''s metadata. More - info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata' - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object spec: - description: 'spec defines the desired characteristics - of a volume requested by a pod author. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + description: "Defines the desired characteristics + of a PersistentVolumeClaim that will be created + for the volume with the mount name specified + in the `name` field. \n When a Pod is created + for this ClusterComponent, a new PVC will + be created based on the specification defined + in the `spec` field. The PVC will be associated + with the volume mount specified by the `name` + field." properties: accessModes: - description: 'accessModes contains the desired - access modes the volume should have. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + description: 'Contains the desired access + modes the volume should have. More info: + https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1.' items: type: string type: array - dataSource: - description: 'dataSource field can be used - to specify either: * An existing VolumeSnapshot - object (snapshot.storage.k8s.io/VolumeSnapshot) - * An existing PVC (PersistentVolumeClaim) - If the provisioner or an external controller - can support the specified data source, - it will create a new volume based on the - contents of the specified data source. - When the AnyVolumeDataSource feature gate - is enabled, dataSource contents will be - copied to dataSourceRef, and dataSourceRef - contents will be copied to dataSource - when dataSourceRef.namespace is not specified. - If the namespace is specified, then dataSourceRef - will not be copied to dataSource.' - properties: - apiGroup: - description: APIGroup is the group for - the resource being referenced. If - APIGroup is not specified, the specified - Kind must be in the core API group. - For any other third-party types, APIGroup - is required. - type: string - kind: - description: Kind is the type of resource - being referenced - type: string - name: - description: Name is the name of resource - being referenced - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - dataSourceRef: - description: 'dataSourceRef specifies the - object from which to populate the volume - with data, if a non-empty volume is desired. - This may be any object from a non-empty - API group (non core object) or a PersistentVolumeClaim - object. When this field is specified, - volume binding will only succeed if the - type of the specified object matches some - installed volume populator or dynamic - provisioner. This field will replace the - functionality of the dataSource field - and as such if both fields are non-empty, - they must have the same value. For backwards - compatibility, when namespace isn''t specified - in dataSourceRef, both fields (dataSource - and dataSourceRef) will be set to the - same value automatically if one of them - is empty and the other is non-empty. When - namespace is specified in dataSourceRef, - dataSource isn''t set to the same value - and must be empty. There are three important - differences between dataSource and dataSourceRef: - * While dataSource only allows two specific - types of objects, dataSourceRef allows - any non-core object, as well as PersistentVolumeClaim - objects. * While dataSource ignores disallowed - values (dropping them), dataSourceRef - preserves all values, and generates an - error if a disallowed value is specified. - * While dataSource only allows local objects, - dataSourceRef allows objects in any namespaces. - (Beta) Using this field requires the AnyVolumeDataSource - feature gate to be enabled. (Alpha) Using - the namespace field of dataSourceRef requires - the CrossNamespaceVolumeDataSource feature - gate to be enabled.' - properties: - apiGroup: - description: APIGroup is the group for - the resource being referenced. If - APIGroup is not specified, the specified - Kind must be in the core API group. - For any other third-party types, APIGroup - is required. - type: string - kind: - description: Kind is the type of resource - being referenced - type: string - name: - description: Name is the name of resource - being referenced - type: string - namespace: - description: Namespace is the namespace - of resource being referenced Note - that when a namespace is specified, - a gateway.networking.k8s.io/ReferenceGrant - object is required in the referent - namespace to allow that namespace's - owner to accept the reference. See - the ReferenceGrant documentation for - details. (Alpha) This field requires - the CrossNamespaceVolumeDataSource - feature gate to be enabled. - type: string - required: - - kind - - name - type: object + x-kubernetes-preserve-unknown-fields: true resources: - description: 'resources represents the minimum - resources the volume should have. If RecoverVolumeExpansionFailure - feature is enabled users are allowed to - specify resource requirements that are - lower than previous value but must still - be higher than capacity recorded in the - status field of the claim. More info: - https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + description: 'Represents the minimum resources + the volume should have. If the RecoverVolumeExpansionFailure + feature is enabled, users are allowed + to specify resource requirements that + are lower than the previous value but + must still be higher than the capacity + recorded in the status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources.' properties: claims: description: "Claims lists the names @@ -7296,248 +6825,19 @@ spec: info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object - selector: - description: selector is a label query over - volumes to consider for binding. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The - requirements are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label - key that the selector applies - to. - type: string - operator: - description: operator represents - a key's relationship to a set - of values. Valid operators are - In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array - of string values. If the operator - is In or NotIn, the values array - must be non-empty. If the operator - is Exists or DoesNotExist, the - values array must be empty. - This array is replaced during - a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of - {key,value} pairs. A single {key,value} - in the matchLabels map is equivalent - to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are - ANDed. - type: object - type: object - x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true storageClassName: - description: 'storageClassName is the name - of the StorageClass required by the claim. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + description: 'The name of the StorageClass + required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1.' type: string volumeMode: - description: volumeMode defines what type - of volume is required by the claim. Value - of Filesystem is implied when not included - in claim spec. - type: string - volumeName: - description: volumeName is the binding reference - to the PersistentVolume backing this claim. - type: string - type: object - status: - description: 'status represents the current - information/status of a persistent volume - claim. Read-only. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' - properties: - accessModes: - description: 'accessModes contains the actual - access modes the volume backing the PVC - has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - allocatedResourceStatuses: - additionalProperties: - description: When a controller receives - persistentvolume claim update with ClaimResourceStatus - for a resource that it does not recognizes, - then it should ignore that update and - let other controllers handle it. - type: string - description: "allocatedResourceStatuses - stores status of resource being resized - for the given PVC. Key names follow standard - Kubernetes label syntax. Valid values - are either: * Un-prefixed keys: - storage - - the capacity of the volume. * Custom - resources must use implementation-defined - prefixed names such as \"example.com/my-custom-resource\" - Apart from above values - keys that are - unprefixed or have kubernetes.io prefix - are considered reserved and hence may - not be used. \n ClaimResourceStatus can - be in any of following states: - ControllerResizeInProgress: - State set when resize controller starts - resizing the volume in control-plane. - - ControllerResizeFailed: State set when - resize has failed in resize controller - with a terminal error. - NodeResizePending: - State set when resize controller has finished - resizing the volume but further resizing - of volume is needed on the node. - NodeResizeInProgress: - State set when kubelet starts resizing - the volume. - NodeResizeFailed: State - set when resizing has failed in kubelet - with a terminal error. Transient errors - don't set NodeResizeFailed. For example: - if expanding a PVC for more capacity - - this field can be one of the following - states: - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeFailed\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizePending\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeFailed\" When this field - is not set, it means that no resize operation - is in progress for the given PVC. \n A - controller that receives PVC update with - previously unknown resourceName or ClaimResourceStatus - should ignore the update for the purpose - it was designed. For example - a controller - that only is responsible for resizing - capacity of the volume, should ignore - PVC updates that change other valid resources - associated with PVC. \n This is an alpha - field and requires enabling RecoverVolumeExpansionFailure - feature." - type: object - x-kubernetes-map-type: granular - allocatedResources: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: "allocatedResources tracks - the resources allocated to a PVC including - its capacity. Key names follow standard - Kubernetes label syntax. Valid values - are either: * Un-prefixed keys: - storage - - the capacity of the volume. * Custom - resources must use implementation-defined - prefixed names such as \"example.com/my-custom-resource\" - Apart from above values - keys that are - unprefixed or have kubernetes.io prefix - are considered reserved and hence may - not be used. \n Capacity reported here - may be larger than the actual capacity - when a volume expansion operation is requested. - For storage quota, the larger value from - allocatedResources and PVC.spec.resources - is used. If allocatedResources is not - set, PVC.spec.resources alone is used - for quota calculation. If a volume expansion - capacity request is lowered, allocatedResources - is only lowered if there are no expansion - operations in progress and if the actual - volume capacity is equal or lower than - the requested capacity. \n A controller - that receives PVC update with previously - unknown resourceName should ignore the - update for the purpose it was designed. - For example - a controller that only is - responsible for resizing capacity of the - volume, should ignore PVC updates that - change other valid resources associated - with PVC. \n This is an alpha field and - requires enabling RecoverVolumeExpansionFailure - feature." - type: object - capacity: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: capacity represents the actual - resources of the underlying volume. - type: object - conditions: - description: conditions is the current Condition - of persistent volume claim. If underlying - persistent volume is being resized then - the Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition - contains details about state of pvc - properties: - lastProbeTime: - description: lastProbeTime is the - time we probed the condition. - format: date-time - type: string - lastTransitionTime: - description: lastTransitionTime is - the time the condition transitioned - from one status to another. - format: date-time - type: string - message: - description: message is the human-readable - message indicating details about - last transition. - type: string - reason: - description: reason is a unique, this - should be a short, machine understandable - string that gives the reason for - condition's last transition. If - it reports "ResizeStarted" that - means the underlying persistent - volume is being resized. - type: string - status: - type: string - type: - description: PersistentVolumeClaimConditionType - is a valid value of PersistentVolumeClaimCondition.Type - type: string - required: - - status - - type - type: object - type: array - phase: - description: phase represents the current - phase of PersistentVolumeClaim. + description: Defines what type of volume + is required by the claim, either Block + or Filesystem. type: string type: object + required: + - name type: object type: array volumeMounts: @@ -9499,6 +8799,9 @@ spec: - name type: object type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map issuer: description: Specifies the configuration for the TLS certificates issuer. It allows defining the issuer name and the reference diff --git a/config/crd/bases/apps.kubeblocks.io_components.yaml b/config/crd/bases/apps.kubeblocks.io_components.yaml index a9ec71bb4d7..747033a2eb1 100644 --- a/config/crd/bases/apps.kubeblocks.io_components.yaml +++ b/config/crd/bases/apps.kubeblocks.io_components.yaml @@ -618,154 +618,39 @@ spec: description: Defines VolumeClaimTemplates to override. Add new or override existing volume claim templates. items: - description: PersistentVolumeClaim is a user's request for - and claim to a persistent volume properties: - apiVersion: - description: 'APIVersion defines the versioned schema - of this representation of an object. Servers should - convert recognized schemas to the latest internal value, - and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the - REST resource this object represents. Servers may infer - this from the endpoint the client submits requests to. - Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + name: + description: "Refers to the name of a volumeMount defined + in either: \n - `componentDefinition.spec.runtime.containers[*].volumeMounts` + - `clusterDefinition.spec.componentDefs[*].podSpec.containers[*].volumeMounts` + (deprecated) \n The value of `name` must match the `name` + field of a volumeMount specified in the corresponding + `volumeMounts` array." type: string - metadata: - description: 'Standard object''s metadata. More info: - https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata' - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object spec: - description: 'spec defines the desired characteristics - of a volume requested by a pod author. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + description: "Defines the desired characteristics of a + PersistentVolumeClaim that will be created for the volume + with the mount name specified in the `name` field. \n + When a Pod is created for this ClusterComponent, a new + PVC will be created based on the specification defined + in the `spec` field. The PVC will be associated with + the volume mount specified by the `name` field." properties: accessModes: - description: 'accessModes contains the desired access - modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + description: 'Contains the desired access modes the + volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1.' items: type: string type: array - dataSource: - description: 'dataSource field can be used to specify - either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) - * An existing PVC (PersistentVolumeClaim) If the - provisioner or an external controller can support - the specified data source, it will create a new - volume based on the contents of the specified data - source. When the AnyVolumeDataSource feature gate - is enabled, dataSource contents will be copied to - dataSourceRef, and dataSourceRef contents will be - copied to dataSource when dataSourceRef.namespace - is not specified. If the namespace is specified, - then dataSourceRef will not be copied to dataSource.' - properties: - apiGroup: - description: APIGroup is the group for the resource - being referenced. If APIGroup is not specified, - the specified Kind must be in the core API group. - For any other third-party types, APIGroup is - required. - type: string - kind: - description: Kind is the type of resource being - referenced - type: string - name: - description: Name is the name of resource being - referenced - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - dataSourceRef: - description: 'dataSourceRef specifies the object from - which to populate the volume with data, if a non-empty - volume is desired. This may be any object from a - non-empty API group (non core object) or a PersistentVolumeClaim - object. When this field is specified, volume binding - will only succeed if the type of the specified object - matches some installed volume populator or dynamic - provisioner. This field will replace the functionality - of the dataSource field and as such if both fields - are non-empty, they must have the same value. For - backwards compatibility, when namespace isn''t specified - in dataSourceRef, both fields (dataSource and dataSourceRef) - will be set to the same value automatically if one - of them is empty and the other is non-empty. When - namespace is specified in dataSourceRef, dataSource - isn''t set to the same value and must be empty. - There are three important differences between dataSource - and dataSourceRef: * While dataSource only allows - two specific types of objects, dataSourceRef allows - any non-core object, as well as PersistentVolumeClaim - objects. * While dataSource ignores disallowed values - (dropping them), dataSourceRef preserves all values, - and generates an error if a disallowed value is - specified. * While dataSource only allows local - objects, dataSourceRef allows objects in any namespaces. - (Beta) Using this field requires the AnyVolumeDataSource - feature gate to be enabled. (Alpha) Using the namespace - field of dataSourceRef requires the CrossNamespaceVolumeDataSource - feature gate to be enabled.' - properties: - apiGroup: - description: APIGroup is the group for the resource - being referenced. If APIGroup is not specified, - the specified Kind must be in the core API group. - For any other third-party types, APIGroup is - required. - type: string - kind: - description: Kind is the type of resource being - referenced - type: string - name: - description: Name is the name of resource being - referenced - type: string - namespace: - description: Namespace is the namespace of resource - being referenced Note that when a namespace - is specified, a gateway.networking.k8s.io/ReferenceGrant - object is required in the referent namespace - to allow that namespace's owner to accept the - reference. See the ReferenceGrant documentation - for details. (Alpha) This field requires the - CrossNamespaceVolumeDataSource feature gate - to be enabled. - type: string - required: - - kind - - name - type: object + x-kubernetes-preserve-unknown-fields: true resources: - description: 'resources represents the minimum resources - the volume should have. If RecoverVolumeExpansionFailure - feature is enabled users are allowed to specify - resource requirements that are lower than previous - value but must still be higher than capacity recorded - in the status field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + description: 'Represents the minimum resources the + volume should have. If the RecoverVolumeExpansionFailure + feature is enabled, users are allowed to specify + resource requirements that are lower than the previous + value but must still be higher than the capacity + recorded in the status field of the claim. More + info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources.' properties: claims: description: "Claims lists the names of resources, @@ -816,222 +701,18 @@ spec: exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object - selector: - description: selector is a label query over volumes - to consider for binding. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true storageClassName: - description: 'storageClassName is the name of the - StorageClass required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + description: 'The name of the StorageClass required + by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1.' type: string volumeMode: - description: volumeMode defines what type of volume - is required by the claim. Value of Filesystem is - implied when not included in claim spec. - type: string - volumeName: - description: volumeName is the binding reference to - the PersistentVolume backing this claim. - type: string - type: object - status: - description: 'status represents the current information/status - of a persistent volume claim. Read-only. More info: - https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' - properties: - accessModes: - description: 'accessModes contains the actual access - modes the volume backing the PVC has. More info: - https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - allocatedResourceStatuses: - additionalProperties: - description: When a controller receives persistentvolume - claim update with ClaimResourceStatus for a resource - that it does not recognizes, then it should ignore - that update and let other controllers handle it. - type: string - description: "allocatedResourceStatuses stores status - of resource being resized for the given PVC. Key - names follow standard Kubernetes label syntax. Valid - values are either: * Un-prefixed keys: - storage - - the capacity of the volume. * Custom resources - must use implementation-defined prefixed names such - as \"example.com/my-custom-resource\" Apart from - above values - keys that are unprefixed or have - kubernetes.io prefix are considered reserved and - hence may not be used. \n ClaimResourceStatus can - be in any of following states: - ControllerResizeInProgress: - State set when resize controller starts resizing - the volume in control-plane. - ControllerResizeFailed: - State set when resize has failed in resize controller - with a terminal error. - NodeResizePending: State - set when resize controller has finished resizing - the volume but further resizing of volume is needed - on the node. - NodeResizeInProgress: State set when - kubelet starts resizing the volume. - NodeResizeFailed: - State set when resizing has failed in kubelet with - a terminal error. Transient errors don't set NodeResizeFailed. - For example: if expanding a PVC for more capacity - - this field can be one of the following states: - - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeFailed\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizePending\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeFailed\" When this field is not set, - it means that no resize operation is in progress - for the given PVC. \n A controller that receives - PVC update with previously unknown resourceName - or ClaimResourceStatus should ignore the update - for the purpose it was designed. For example - a - controller that only is responsible for resizing - capacity of the volume, should ignore PVC updates - that change other valid resources associated with - PVC. \n This is an alpha field and requires enabling - RecoverVolumeExpansionFailure feature." - type: object - x-kubernetes-map-type: granular - allocatedResources: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: "allocatedResources tracks the resources - allocated to a PVC including its capacity. Key names - follow standard Kubernetes label syntax. Valid values - are either: * Un-prefixed keys: - storage - the - capacity of the volume. * Custom resources must - use implementation-defined prefixed names such as - \"example.com/my-custom-resource\" Apart from above - values - keys that are unprefixed or have kubernetes.io - prefix are considered reserved and hence may not - be used. \n Capacity reported here may be larger - than the actual capacity when a volume expansion - operation is requested. For storage quota, the larger - value from allocatedResources and PVC.spec.resources - is used. If allocatedResources is not set, PVC.spec.resources - alone is used for quota calculation. If a volume - expansion capacity request is lowered, allocatedResources - is only lowered if there are no expansion operations - in progress and if the actual volume capacity is - equal or lower than the requested capacity. \n A - controller that receives PVC update with previously - unknown resourceName should ignore the update for - the purpose it was designed. For example - a controller - that only is responsible for resizing capacity of - the volume, should ignore PVC updates that change - other valid resources associated with PVC. \n This - is an alpha field and requires enabling RecoverVolumeExpansionFailure - feature." - type: object - capacity: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: capacity represents the actual resources - of the underlying volume. - type: object - conditions: - description: conditions is the current Condition of - persistent volume claim. If underlying persistent - volume is being resized then the Condition will - be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition contains - details about state of pvc - properties: - lastProbeTime: - description: lastProbeTime is the time we probed - the condition. - format: date-time - type: string - lastTransitionTime: - description: lastTransitionTime is the time - the condition transitioned from one status - to another. - format: date-time - type: string - message: - description: message is the human-readable message - indicating details about last transition. - type: string - reason: - description: reason is a unique, this should - be a short, machine understandable string - that gives the reason for condition's last - transition. If it reports "ResizeStarted" - that means the underlying persistent volume - is being resized. - type: string - status: - type: string - type: - description: PersistentVolumeClaimConditionType - is a valid value of PersistentVolumeClaimCondition.Type - type: string - required: - - status - - type - type: object - type: array - phase: - description: phase represents the current phase of - PersistentVolumeClaim. + description: Defines what type of volume is required + by the claim, either Block or Filesystem. type: string type: object + required: + - name type: object type: array volumeMounts: diff --git a/config/crd/bases/apps.kubeblocks.io_opsrequests.yaml b/config/crd/bases/apps.kubeblocks.io_opsrequests.yaml index 4a13b824dd7..a689e1befb6 100644 --- a/config/crd/bases/apps.kubeblocks.io_opsrequests.yaml +++ b/config/crd/bases/apps.kubeblocks.io_opsrequests.yaml @@ -114,16 +114,15 @@ spec: customSpec: description: Specifies a custom operation as defined by OpsDefinition. properties: - components: + items: description: Defines which components need to perform the actions - defined by this OpsDefinition. At least one component is required. - The components are identified by their name and can be merged - or retained. + defined by this OpsDefinition. At least one component/shardComponent + is required. The components are identified by their name and + can be merged or retained. items: properties: - name: - description: Specifies the unique identifier of the cluster - component + componentName: + description: Specifies the name of the cluster component. type: string parameters: description: Represents the parameters for this operation @@ -147,14 +146,17 @@ spec: x-kubernetes-list-map-keys: - name x-kubernetes-list-type: map - required: - - name + shardingName: + description: Specifies the name of the cluster sharding + component. + type: string type: object + x-kubernetes-validations: + - message: either componentName or shardingName + rule: (has(self.componentName) && !has(self.shardingName)) + || (has(self.shardingName) && !has(self.componentName)) minItems: 1 type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map opsDefinitionRef: description: Is a reference to an OpsDefinition. type: string @@ -173,7 +175,7 @@ spec: serviceAccountName: type: string required: - - components + - items - opsDefinitionRef type: object expose: @@ -185,7 +187,7 @@ spec: type: string services: description: A list of services that are to be exposed or removed. - If componentNamem is not specified, each `OpsService` in the + If componentName is not specified, each `OpsService` in the list must specify ports and selectors. items: properties: @@ -365,14 +367,10 @@ spec: - Disable type: string required: - - componentName - services - switch type: object type: array - x-kubernetes-list-map-keys: - - componentName - x-kubernetes-list-type: map force: description: Indicates if pre-checks should be bypassed, allowing the opsRequest to execute immediately. If set to true, pre-checks @@ -694,166 +692,40 @@ spec: description: Defines VolumeClaimTemplates to override. Add new or override existing volume claim templates. items: - description: PersistentVolumeClaim is a user's request - for and claim to a persistent volume properties: - apiVersion: - description: 'APIVersion defines the versioned schema - of this representation of an object. Servers should - convert recognized schemas to the latest internal - value, and may reject unrecognized values. More - info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing - the REST resource this object represents. Servers - may infer this from the endpoint the client submits - requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + name: + description: "Refers to the name of a volumeMount + defined in either: \n - `componentDefinition.spec.runtime.containers[*].volumeMounts` + - `clusterDefinition.spec.componentDefs[*].podSpec.containers[*].volumeMounts` + (deprecated) \n The value of `name` must match + the `name` field of a volumeMount specified in + the corresponding `volumeMounts` array." type: string - metadata: - description: 'Standard object''s metadata. More - info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata' - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object spec: - description: 'spec defines the desired characteristics - of a volume requested by a pod author. More info: - https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + description: "Defines the desired characteristics + of a PersistentVolumeClaim that will be created + for the volume with the mount name specified in + the `name` field. \n When a Pod is created for + this ClusterComponent, a new PVC will be created + based on the specification defined in the `spec` + field. The PVC will be associated with the volume + mount specified by the `name` field." properties: accessModes: - description: 'accessModes contains the desired - access modes the volume should have. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + description: 'Contains the desired access modes + the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1.' items: type: string type: array - dataSource: - description: 'dataSource field can be used to - specify either: * An existing VolumeSnapshot - object (snapshot.storage.k8s.io/VolumeSnapshot) - * An existing PVC (PersistentVolumeClaim) - If the provisioner or an external controller - can support the specified data source, it - will create a new volume based on the contents - of the specified data source. When the AnyVolumeDataSource - feature gate is enabled, dataSource contents - will be copied to dataSourceRef, and dataSourceRef - contents will be copied to dataSource when - dataSourceRef.namespace is not specified. - If the namespace is specified, then dataSourceRef - will not be copied to dataSource.' - properties: - apiGroup: - description: APIGroup is the group for the - resource being referenced. If APIGroup - is not specified, the specified Kind must - be in the core API group. For any other - third-party types, APIGroup is required. - type: string - kind: - description: Kind is the type of resource - being referenced - type: string - name: - description: Name is the name of resource - being referenced - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - dataSourceRef: - description: 'dataSourceRef specifies the object - from which to populate the volume with data, - if a non-empty volume is desired. This may - be any object from a non-empty API group (non - core object) or a PersistentVolumeClaim object. - When this field is specified, volume binding - will only succeed if the type of the specified - object matches some installed volume populator - or dynamic provisioner. This field will replace - the functionality of the dataSource field - and as such if both fields are non-empty, - they must have the same value. For backwards - compatibility, when namespace isn''t specified - in dataSourceRef, both fields (dataSource - and dataSourceRef) will be set to the same - value automatically if one of them is empty - and the other is non-empty. When namespace - is specified in dataSourceRef, dataSource - isn''t set to the same value and must be empty. - There are three important differences between - dataSource and dataSourceRef: * While dataSource - only allows two specific types of objects, - dataSourceRef allows any non-core object, - as well as PersistentVolumeClaim objects. - * While dataSource ignores disallowed values - (dropping them), dataSourceRef preserves all - values, and generates an error if a disallowed - value is specified. * While dataSource only - allows local objects, dataSourceRef allows - objects in any namespaces. (Beta) Using this - field requires the AnyVolumeDataSource feature - gate to be enabled. (Alpha) Using the namespace - field of dataSourceRef requires the CrossNamespaceVolumeDataSource - feature gate to be enabled.' - properties: - apiGroup: - description: APIGroup is the group for the - resource being referenced. If APIGroup - is not specified, the specified Kind must - be in the core API group. For any other - third-party types, APIGroup is required. - type: string - kind: - description: Kind is the type of resource - being referenced - type: string - name: - description: Name is the name of resource - being referenced - type: string - namespace: - description: Namespace is the namespace - of resource being referenced Note that - when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant - object is required in the referent namespace - to allow that namespace's owner to accept - the reference. See the ReferenceGrant - documentation for details. (Alpha) This - field requires the CrossNamespaceVolumeDataSource - feature gate to be enabled. - type: string - required: - - kind - - name - type: object + x-kubernetes-preserve-unknown-fields: true resources: - description: 'resources represents the minimum - resources the volume should have. If RecoverVolumeExpansionFailure - feature is enabled users are allowed to specify + description: 'Represents the minimum resources + the volume should have. If the RecoverVolumeExpansionFailure + feature is enabled, users are allowed to specify resource requirements that are lower than - previous value but must still be higher than - capacity recorded in the status field of the - claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + the previous value but must still be higher + than the capacity recorded in the status field + of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources.' properties: claims: description: "Claims lists the names of @@ -908,235 +780,18 @@ spec: More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object - selector: - description: selector is a label query over - volumes to consider for binding. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true storageClassName: - description: 'storageClassName is the name of - the StorageClass required by the claim. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + description: 'The name of the StorageClass required + by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1.' type: string volumeMode: - description: volumeMode defines what type of - volume is required by the claim. Value of - Filesystem is implied when not included in - claim spec. - type: string - volumeName: - description: volumeName is the binding reference - to the PersistentVolume backing this claim. - type: string - type: object - status: - description: 'status represents the current information/status - of a persistent volume claim. Read-only. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' - properties: - accessModes: - description: 'accessModes contains the actual - access modes the volume backing the PVC has. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - allocatedResourceStatuses: - additionalProperties: - description: When a controller receives persistentvolume - claim update with ClaimResourceStatus for - a resource that it does not recognizes, - then it should ignore that update and let - other controllers handle it. - type: string - description: "allocatedResourceStatuses stores - status of resource being resized for the given - PVC. Key names follow standard Kubernetes - label syntax. Valid values are either: * Un-prefixed - keys: - storage - the capacity of the volume. - * Custom resources must use implementation-defined - prefixed names such as \"example.com/my-custom-resource\" - Apart from above values - keys that are unprefixed - or have kubernetes.io prefix are considered - reserved and hence may not be used. \n ClaimResourceStatus - can be in any of following states: - ControllerResizeInProgress: - State set when resize controller starts resizing - the volume in control-plane. - ControllerResizeFailed: - State set when resize has failed in resize - controller with a terminal error. - NodeResizePending: - State set when resize controller has finished - resizing the volume but further resizing of - volume is needed on the node. - NodeResizeInProgress: - State set when kubelet starts resizing the - volume. - NodeResizeFailed: State set when - resizing has failed in kubelet with a terminal - error. Transient errors don't set NodeResizeFailed. - For example: if expanding a PVC for more capacity - - this field can be one of the following states: - - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeFailed\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizePending\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeFailed\" When this field is - not set, it means that no resize operation - is in progress for the given PVC. \n A controller - that receives PVC update with previously unknown - resourceName or ClaimResourceStatus should - ignore the update for the purpose it was designed. - For example - a controller that only is responsible - for resizing capacity of the volume, should - ignore PVC updates that change other valid - resources associated with PVC. \n This is - an alpha field and requires enabling RecoverVolumeExpansionFailure - feature." - type: object - x-kubernetes-map-type: granular - allocatedResources: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: "allocatedResources tracks the - resources allocated to a PVC including its - capacity. Key names follow standard Kubernetes - label syntax. Valid values are either: * Un-prefixed - keys: - storage - the capacity of the volume. - * Custom resources must use implementation-defined - prefixed names such as \"example.com/my-custom-resource\" - Apart from above values - keys that are unprefixed - or have kubernetes.io prefix are considered - reserved and hence may not be used. \n Capacity - reported here may be larger than the actual - capacity when a volume expansion operation - is requested. For storage quota, the larger - value from allocatedResources and PVC.spec.resources - is used. If allocatedResources is not set, - PVC.spec.resources alone is used for quota - calculation. If a volume expansion capacity - request is lowered, allocatedResources is - only lowered if there are no expansion operations - in progress and if the actual volume capacity - is equal or lower than the requested capacity. - \n A controller that receives PVC update with - previously unknown resourceName should ignore - the update for the purpose it was designed. - For example - a controller that only is responsible - for resizing capacity of the volume, should - ignore PVC updates that change other valid - resources associated with PVC. \n This is - an alpha field and requires enabling RecoverVolumeExpansionFailure - feature." - type: object - capacity: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: capacity represents the actual - resources of the underlying volume. - type: object - conditions: - description: conditions is the current Condition - of persistent volume claim. If underlying - persistent volume is being resized then the - Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition - contains details about state of pvc - properties: - lastProbeTime: - description: lastProbeTime is the time - we probed the condition. - format: date-time - type: string - lastTransitionTime: - description: lastTransitionTime is the - time the condition transitioned from - one status to another. - format: date-time - type: string - message: - description: message is the human-readable - message indicating details about last - transition. - type: string - reason: - description: reason is a unique, this - should be a short, machine understandable - string that gives the reason for condition's - last transition. If it reports "ResizeStarted" - that means the underlying persistent - volume is being resized. - type: string - status: - type: string - type: - description: PersistentVolumeClaimConditionType - is a valid value of PersistentVolumeClaimCondition.Type - type: string - required: - - status - - type - type: object - type: array - phase: - description: phase represents the current phase - of PersistentVolumeClaim. + description: Defines what type of volume is + required by the claim, either Block or Filesystem. type: string type: object + required: + - name type: object type: array volumeMounts: @@ -2997,14 +2652,17 @@ spec: format: int32 minimum: 0 type: integer + shardingName: + description: Specifies the name of the cluster sharding component. + type: string required: - - componentName - replicas type: object + x-kubernetes-validations: + - message: either componentName or shardingName + rule: (has(self.componentName) && !has(self.shardingName)) || + (has(self.shardingName) && !has(self.componentName)) type: array - x-kubernetes-list-map-keys: - - componentName - x-kubernetes-list-type: map x-kubernetes-validations: - message: forbidden to update spec.horizontalScaling rule: self == oldSelf @@ -3162,14 +2820,17 @@ spec: - name type: object type: array + shardingName: + description: Specifies the name of the cluster sharding component. + type: string required: - - componentName - instances type: object + x-kubernetes-validations: + - message: either componentName or shardingName + rule: (has(self.componentName) && !has(self.shardingName)) || + (has(self.shardingName) && !has(self.componentName)) type: array - x-kubernetes-list-map-keys: - - componentName - x-kubernetes-list-type: map x-kubernetes-validations: - message: forbidden to update spec.rebuildFrom rule: self == oldSelf @@ -3252,15 +2913,16 @@ spec: x-kubernetes-list-map-keys: - name x-kubernetes-list-type: map + shardingName: + description: Specifies the name of the cluster sharding component. + type: string required: - - componentName - configurations type: object x-kubernetes-validations: - - message: forbidden to update spec.reconfigure - rule: self == oldSelf - - message: Value can not be empty - rule: self.configurations.size() > 0 + - message: either componentName or shardingName + rule: (has(self.componentName) && !has(self.shardingName)) || (has(self.shardingName) + && !has(self.componentName)) reconfigures: description: Defines the variables that need to input when updating configuration. @@ -3345,30 +3007,38 @@ spec: x-kubernetes-list-map-keys: - name x-kubernetes-list-type: map + shardingName: + description: Specifies the name of the cluster sharding component. + type: string required: - - componentName - configurations type: object + x-kubernetes-validations: + - message: either componentName or shardingName + rule: (has(self.componentName) && !has(self.shardingName)) || + (has(self.shardingName) && !has(self.componentName)) type: array - x-kubernetes-list-map-keys: - - componentName - x-kubernetes-list-type: map + x-kubernetes-validations: + - message: forbidden to update spec.reconfigure + rule: self == oldSelf restart: description: Restarts the specified components. items: description: ComponentOps represents the common variables required - for operations within the scope of a component. + for operations within the scope of a normal component/shard component. properties: componentName: description: Specifies the name of the cluster component. type: string - required: - - componentName + shardingName: + description: Specifies the name of the cluster sharding component. + type: string type: object + x-kubernetes-validations: + - message: either componentName or shardingName + rule: (has(self.componentName) && !has(self.shardingName)) || + (has(self.shardingName) && !has(self.componentName)) type: array - x-kubernetes-list-map-keys: - - componentName - x-kubernetes-list-type: map x-kubernetes-validations: - message: forbidden to update spec.restart rule: self == oldSelf @@ -3592,9 +3262,14 @@ spec: x-kubernetes-validations: - message: forbidden to update spec.scriptSpec.script.selector rule: self == oldSelf - required: - - componentName + shardingName: + description: Specifies the name of the cluster sharding component. + type: string type: object + x-kubernetes-validations: + - message: either componentName or shardingName + rule: (has(self.componentName) && !has(self.shardingName)) || (has(self.shardingName) + && !has(self.componentName)) switchover: description: Switches over the specified components. items: @@ -3617,14 +3292,17 @@ spec: will be executed, and it is mandatory that clusterDefinition.componentDefs[x].switchoverSpec.withCandidate is not left blank." type: string + shardingName: + description: Specifies the name of the cluster sharding component. + type: string required: - - componentName - instanceName type: object + x-kubernetes-validations: + - message: either componentName or shardingName + rule: (has(self.componentName) && !has(self.shardingName)) || + (has(self.shardingName) && !has(self.componentName)) type: array - x-kubernetes-list-map-keys: - - componentName - x-kubernetes-list-type: map x-kubernetes-validations: - message: forbidden to update spec.switchover rule: self == oldSelf @@ -3706,6 +3384,91 @@ spec: componentName: description: Specifies the name of the cluster component. type: string + instances: + description: Specifies the instance template that need to vertical + scale. + items: + properties: + claims: + description: "Claims lists the names of resources, defined + in spec.resourceClaims, that are used by this container. + \n This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. \n This field + is immutable. It can only be set for containers." + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry + in pod.spec.resourceClaims of the Pod where this + field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + name: + description: Refer to the instance template name of the + component or sharding. + type: string + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of + compute resources required. If Requests is omitted for + a container, it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined value. + Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + volumeClaimTemplates: + description: volumeClaimTemplates specifies the storage + size and volumeClaimTemplate name. + items: + properties: + name: + description: A reference to the volumeClaimTemplate + name from the cluster components. + type: string + storage: + anyOf: + - type: integer + - type: string + description: Specifies the requested storage size + for the volume. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - name + - storage + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + required: + - name + - volumeClaimTemplates + type: object + x-kubernetes-preserve-unknown-fields: true + type: array limits: additionalProperties: anyOf: @@ -3729,14 +3492,16 @@ spec: to an implementation-defined value. Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object - required: - - componentName + shardingName: + description: Specifies the name of the cluster sharding component. + type: string type: object x-kubernetes-preserve-unknown-fields: true + x-kubernetes-validations: + - message: either componentName or shardingName + rule: (has(self.componentName) && !has(self.shardingName)) || + (has(self.shardingName) && !has(self.componentName)) type: array - x-kubernetes-list-map-keys: - - componentName - x-kubernetes-list-type: map volumeExpansion: description: 'Note: Quantity struct can not do immutable check by CEL. Defines what component and volumeClaimTemplate need to expand @@ -3748,6 +3513,94 @@ spec: componentName: description: Specifies the name of the cluster component. type: string + instances: + description: Specifies the instance template that need to volume + expand. + items: + properties: + claims: + description: "Claims lists the names of resources, defined + in spec.resourceClaims, that are used by this container. + \n This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. \n This field + is immutable. It can only be set for containers." + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry + in pod.spec.resourceClaims of the Pod where this + field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + name: + description: Refer to the instance template name of the + component or sharding. + type: string + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of + compute resources required. If Requests is omitted for + a container, it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined value. + Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + volumeClaimTemplates: + description: volumeClaimTemplates specifies the storage + size and volumeClaimTemplate name. + items: + properties: + name: + description: A reference to the volumeClaimTemplate + name from the cluster components. + type: string + storage: + anyOf: + - type: integer + - type: string + description: Specifies the requested storage size + for the volume. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - name + - storage + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + required: + - name + - volumeClaimTemplates + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + shardingName: + description: Specifies the name of the cluster sharding component. + type: string volumeClaimTemplates: description: volumeClaimTemplates specifies the storage size and volumeClaimTemplate name. @@ -3774,13 +3627,13 @@ spec: - name x-kubernetes-list-type: map required: - - componentName - volumeClaimTemplates type: object + x-kubernetes-validations: + - message: either componentName or shardingName + rule: (has(self.componentName) && !has(self.shardingName)) || + (has(self.shardingName) && !has(self.componentName)) type: array - x-kubernetes-list-map-keys: - - componentName - x-kubernetes-list-type: map required: - clusterRef - type @@ -4152,174 +4005,43 @@ spec: description: Defines VolumeClaimTemplates to override. Add new or override existing volume claim templates. items: - description: PersistentVolumeClaim is a user's request - for and claim to a persistent volume properties: - apiVersion: - description: 'APIVersion defines the versioned - schema of this representation of an object. - Servers should convert recognized schemas - to the latest internal value, and may reject - unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing - the REST resource this object represents. - Servers may infer this from the endpoint the - client submits requests to. Cannot be updated. - In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + name: + description: "Refers to the name of a volumeMount + defined in either: \n - `componentDefinition.spec.runtime.containers[*].volumeMounts` + - `clusterDefinition.spec.componentDefs[*].podSpec.containers[*].volumeMounts` + (deprecated) \n The value of `name` must match + the `name` field of a volumeMount specified + in the corresponding `volumeMounts` array." type: string - metadata: - description: 'Standard object''s metadata. More - info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata' - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object spec: - description: 'spec defines the desired characteristics - of a volume requested by a pod author. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + description: "Defines the desired characteristics + of a PersistentVolumeClaim that will be created + for the volume with the mount name specified + in the `name` field. \n When a Pod is created + for this ClusterComponent, a new PVC will + be created based on the specification defined + in the `spec` field. The PVC will be associated + with the volume mount specified by the `name` + field." properties: accessModes: - description: 'accessModes contains the desired - access modes the volume should have. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + description: 'Contains the desired access + modes the volume should have. More info: + https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1.' items: type: string type: array - dataSource: - description: 'dataSource field can be used - to specify either: * An existing VolumeSnapshot - object (snapshot.storage.k8s.io/VolumeSnapshot) - * An existing PVC (PersistentVolumeClaim) - If the provisioner or an external controller - can support the specified data source, - it will create a new volume based on the - contents of the specified data source. - When the AnyVolumeDataSource feature gate - is enabled, dataSource contents will be - copied to dataSourceRef, and dataSourceRef - contents will be copied to dataSource - when dataSourceRef.namespace is not specified. - If the namespace is specified, then dataSourceRef - will not be copied to dataSource.' - properties: - apiGroup: - description: APIGroup is the group for - the resource being referenced. If - APIGroup is not specified, the specified - Kind must be in the core API group. - For any other third-party types, APIGroup - is required. - type: string - kind: - description: Kind is the type of resource - being referenced - type: string - name: - description: Name is the name of resource - being referenced - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - dataSourceRef: - description: 'dataSourceRef specifies the - object from which to populate the volume - with data, if a non-empty volume is desired. - This may be any object from a non-empty - API group (non core object) or a PersistentVolumeClaim - object. When this field is specified, - volume binding will only succeed if the - type of the specified object matches some - installed volume populator or dynamic - provisioner. This field will replace the - functionality of the dataSource field - and as such if both fields are non-empty, - they must have the same value. For backwards - compatibility, when namespace isn''t specified - in dataSourceRef, both fields (dataSource - and dataSourceRef) will be set to the - same value automatically if one of them - is empty and the other is non-empty. When - namespace is specified in dataSourceRef, - dataSource isn''t set to the same value - and must be empty. There are three important - differences between dataSource and dataSourceRef: - * While dataSource only allows two specific - types of objects, dataSourceRef allows - any non-core object, as well as PersistentVolumeClaim - objects. * While dataSource ignores disallowed - values (dropping them), dataSourceRef - preserves all values, and generates an - error if a disallowed value is specified. - * While dataSource only allows local objects, - dataSourceRef allows objects in any namespaces. - (Beta) Using this field requires the AnyVolumeDataSource - feature gate to be enabled. (Alpha) Using - the namespace field of dataSourceRef requires - the CrossNamespaceVolumeDataSource feature - gate to be enabled.' - properties: - apiGroup: - description: APIGroup is the group for - the resource being referenced. If - APIGroup is not specified, the specified - Kind must be in the core API group. - For any other third-party types, APIGroup - is required. - type: string - kind: - description: Kind is the type of resource - being referenced - type: string - name: - description: Name is the name of resource - being referenced - type: string - namespace: - description: Namespace is the namespace - of resource being referenced Note - that when a namespace is specified, - a gateway.networking.k8s.io/ReferenceGrant - object is required in the referent - namespace to allow that namespace's - owner to accept the reference. See - the ReferenceGrant documentation for - details. (Alpha) This field requires - the CrossNamespaceVolumeDataSource - feature gate to be enabled. - type: string - required: - - kind - - name - type: object + x-kubernetes-preserve-unknown-fields: true resources: - description: 'resources represents the minimum - resources the volume should have. If RecoverVolumeExpansionFailure - feature is enabled users are allowed to - specify resource requirements that are - lower than previous value but must still - be higher than capacity recorded in the - status field of the claim. More info: - https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + description: 'Represents the minimum resources + the volume should have. If the RecoverVolumeExpansionFailure + feature is enabled, users are allowed + to specify resource requirements that + are lower than the previous value but + must still be higher than the capacity + recorded in the status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources.' properties: claims: description: "Claims lists the names @@ -4375,248 +4097,19 @@ spec: info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object - selector: - description: selector is a label query over - volumes to consider for binding. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The - requirements are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label - key that the selector applies - to. - type: string - operator: - description: operator represents - a key's relationship to a set - of values. Valid operators are - In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array - of string values. If the operator - is In or NotIn, the values array - must be non-empty. If the operator - is Exists or DoesNotExist, the - values array must be empty. - This array is replaced during - a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of - {key,value} pairs. A single {key,value} - in the matchLabels map is equivalent - to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are - ANDed. - type: object - type: object - x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true storageClassName: - description: 'storageClassName is the name - of the StorageClass required by the claim. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + description: 'The name of the StorageClass + required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1.' type: string volumeMode: - description: volumeMode defines what type - of volume is required by the claim. Value - of Filesystem is implied when not included - in claim spec. - type: string - volumeName: - description: volumeName is the binding reference - to the PersistentVolume backing this claim. - type: string - type: object - status: - description: 'status represents the current - information/status of a persistent volume - claim. Read-only. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' - properties: - accessModes: - description: 'accessModes contains the actual - access modes the volume backing the PVC - has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - allocatedResourceStatuses: - additionalProperties: - description: When a controller receives - persistentvolume claim update with ClaimResourceStatus - for a resource that it does not recognizes, - then it should ignore that update and - let other controllers handle it. - type: string - description: "allocatedResourceStatuses - stores status of resource being resized - for the given PVC. Key names follow standard - Kubernetes label syntax. Valid values - are either: * Un-prefixed keys: - storage - - the capacity of the volume. * Custom - resources must use implementation-defined - prefixed names such as \"example.com/my-custom-resource\" - Apart from above values - keys that are - unprefixed or have kubernetes.io prefix - are considered reserved and hence may - not be used. \n ClaimResourceStatus can - be in any of following states: - ControllerResizeInProgress: - State set when resize controller starts - resizing the volume in control-plane. - - ControllerResizeFailed: State set when - resize has failed in resize controller - with a terminal error. - NodeResizePending: - State set when resize controller has finished - resizing the volume but further resizing - of volume is needed on the node. - NodeResizeInProgress: - State set when kubelet starts resizing - the volume. - NodeResizeFailed: State - set when resizing has failed in kubelet - with a terminal error. Transient errors - don't set NodeResizeFailed. For example: - if expanding a PVC for more capacity - - this field can be one of the following - states: - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeFailed\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizePending\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeFailed\" When this field - is not set, it means that no resize operation - is in progress for the given PVC. \n A - controller that receives PVC update with - previously unknown resourceName or ClaimResourceStatus - should ignore the update for the purpose - it was designed. For example - a controller - that only is responsible for resizing - capacity of the volume, should ignore - PVC updates that change other valid resources - associated with PVC. \n This is an alpha - field and requires enabling RecoverVolumeExpansionFailure - feature." - type: object - x-kubernetes-map-type: granular - allocatedResources: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: "allocatedResources tracks - the resources allocated to a PVC including - its capacity. Key names follow standard - Kubernetes label syntax. Valid values - are either: * Un-prefixed keys: - storage - - the capacity of the volume. * Custom - resources must use implementation-defined - prefixed names such as \"example.com/my-custom-resource\" - Apart from above values - keys that are - unprefixed or have kubernetes.io prefix - are considered reserved and hence may - not be used. \n Capacity reported here - may be larger than the actual capacity - when a volume expansion operation is requested. - For storage quota, the larger value from - allocatedResources and PVC.spec.resources - is used. If allocatedResources is not - set, PVC.spec.resources alone is used - for quota calculation. If a volume expansion - capacity request is lowered, allocatedResources - is only lowered if there are no expansion - operations in progress and if the actual - volume capacity is equal or lower than - the requested capacity. \n A controller - that receives PVC update with previously - unknown resourceName should ignore the - update for the purpose it was designed. - For example - a controller that only is - responsible for resizing capacity of the - volume, should ignore PVC updates that - change other valid resources associated - with PVC. \n This is an alpha field and - requires enabling RecoverVolumeExpansionFailure - feature." - type: object - capacity: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: capacity represents the actual - resources of the underlying volume. - type: object - conditions: - description: conditions is the current Condition - of persistent volume claim. If underlying - persistent volume is being resized then - the Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition - contains details about state of pvc - properties: - lastProbeTime: - description: lastProbeTime is the - time we probed the condition. - format: date-time - type: string - lastTransitionTime: - description: lastTransitionTime is - the time the condition transitioned - from one status to another. - format: date-time - type: string - message: - description: message is the human-readable - message indicating details about - last transition. - type: string - reason: - description: reason is a unique, this - should be a short, machine understandable - string that gives the reason for - condition's last transition. If - it reports "ResizeStarted" that - means the underlying persistent - volume is being resized. - type: string - status: - type: string - type: - description: PersistentVolumeClaimConditionType - is a valid value of PersistentVolumeClaimCondition.Type - type: string - required: - - status - - type - type: object - type: array - phase: - description: phase represents the current - phase of PersistentVolumeClaim. + description: Defines what type of volume + is required by the claim, either Block + or Filesystem. type: string type: object + required: + - name type: object type: array volumeMounts: @@ -6801,7 +6294,7 @@ spec: - status type: object x-kubernetes-validations: - - message: either objectKey and actionName. + - message: at least one objectKey or actionName. rule: has(self.objectKey) || has(self.actionName) type: array reason: @@ -6817,8 +6310,8 @@ spec: - Replication type: string type: object - description: Records the status information of components changed - due to the operation request. + description: Records the status information of components, including + the sharding component, that have changed due to the operation request. type: object conditions: description: Describes the detailed status of the OpsRequest. @@ -7238,174 +6731,43 @@ spec: description: Defines VolumeClaimTemplates to override. Add new or override existing volume claim templates. items: - description: PersistentVolumeClaim is a user's request - for and claim to a persistent volume properties: - apiVersion: - description: 'APIVersion defines the versioned - schema of this representation of an object. - Servers should convert recognized schemas - to the latest internal value, and may reject - unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing - the REST resource this object represents. - Servers may infer this from the endpoint the - client submits requests to. Cannot be updated. - In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + name: + description: "Refers to the name of a volumeMount + defined in either: \n - `componentDefinition.spec.runtime.containers[*].volumeMounts` + - `clusterDefinition.spec.componentDefs[*].podSpec.containers[*].volumeMounts` + (deprecated) \n The value of `name` must match + the `name` field of a volumeMount specified + in the corresponding `volumeMounts` array." type: string - metadata: - description: 'Standard object''s metadata. More - info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata' - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object spec: - description: 'spec defines the desired characteristics - of a volume requested by a pod author. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + description: "Defines the desired characteristics + of a PersistentVolumeClaim that will be created + for the volume with the mount name specified + in the `name` field. \n When a Pod is created + for this ClusterComponent, a new PVC will + be created based on the specification defined + in the `spec` field. The PVC will be associated + with the volume mount specified by the `name` + field." properties: accessModes: - description: 'accessModes contains the desired - access modes the volume should have. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + description: 'Contains the desired access + modes the volume should have. More info: + https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1.' items: type: string type: array - dataSource: - description: 'dataSource field can be used - to specify either: * An existing VolumeSnapshot - object (snapshot.storage.k8s.io/VolumeSnapshot) - * An existing PVC (PersistentVolumeClaim) - If the provisioner or an external controller - can support the specified data source, - it will create a new volume based on the - contents of the specified data source. - When the AnyVolumeDataSource feature gate - is enabled, dataSource contents will be - copied to dataSourceRef, and dataSourceRef - contents will be copied to dataSource - when dataSourceRef.namespace is not specified. - If the namespace is specified, then dataSourceRef - will not be copied to dataSource.' - properties: - apiGroup: - description: APIGroup is the group for - the resource being referenced. If - APIGroup is not specified, the specified - Kind must be in the core API group. - For any other third-party types, APIGroup - is required. - type: string - kind: - description: Kind is the type of resource - being referenced - type: string - name: - description: Name is the name of resource - being referenced - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - dataSourceRef: - description: 'dataSourceRef specifies the - object from which to populate the volume - with data, if a non-empty volume is desired. - This may be any object from a non-empty - API group (non core object) or a PersistentVolumeClaim - object. When this field is specified, - volume binding will only succeed if the - type of the specified object matches some - installed volume populator or dynamic - provisioner. This field will replace the - functionality of the dataSource field - and as such if both fields are non-empty, - they must have the same value. For backwards - compatibility, when namespace isn''t specified - in dataSourceRef, both fields (dataSource - and dataSourceRef) will be set to the - same value automatically if one of them - is empty and the other is non-empty. When - namespace is specified in dataSourceRef, - dataSource isn''t set to the same value - and must be empty. There are three important - differences between dataSource and dataSourceRef: - * While dataSource only allows two specific - types of objects, dataSourceRef allows - any non-core object, as well as PersistentVolumeClaim - objects. * While dataSource ignores disallowed - values (dropping them), dataSourceRef - preserves all values, and generates an - error if a disallowed value is specified. - * While dataSource only allows local objects, - dataSourceRef allows objects in any namespaces. - (Beta) Using this field requires the AnyVolumeDataSource - feature gate to be enabled. (Alpha) Using - the namespace field of dataSourceRef requires - the CrossNamespaceVolumeDataSource feature - gate to be enabled.' - properties: - apiGroup: - description: APIGroup is the group for - the resource being referenced. If - APIGroup is not specified, the specified - Kind must be in the core API group. - For any other third-party types, APIGroup - is required. - type: string - kind: - description: Kind is the type of resource - being referenced - type: string - name: - description: Name is the name of resource - being referenced - type: string - namespace: - description: Namespace is the namespace - of resource being referenced Note - that when a namespace is specified, - a gateway.networking.k8s.io/ReferenceGrant - object is required in the referent - namespace to allow that namespace's - owner to accept the reference. See - the ReferenceGrant documentation for - details. (Alpha) This field requires - the CrossNamespaceVolumeDataSource - feature gate to be enabled. - type: string - required: - - kind - - name - type: object + x-kubernetes-preserve-unknown-fields: true resources: - description: 'resources represents the minimum - resources the volume should have. If RecoverVolumeExpansionFailure - feature is enabled users are allowed to - specify resource requirements that are - lower than previous value but must still - be higher than capacity recorded in the - status field of the claim. More info: - https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + description: 'Represents the minimum resources + the volume should have. If the RecoverVolumeExpansionFailure + feature is enabled, users are allowed + to specify resource requirements that + are lower than the previous value but + must still be higher than the capacity + recorded in the status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources.' properties: claims: description: "Claims lists the names @@ -7461,248 +6823,19 @@ spec: info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object - selector: - description: selector is a label query over - volumes to consider for binding. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The - requirements are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label - key that the selector applies - to. - type: string - operator: - description: operator represents - a key's relationship to a set - of values. Valid operators are - In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array - of string values. If the operator - is In or NotIn, the values array - must be non-empty. If the operator - is Exists or DoesNotExist, the - values array must be empty. - This array is replaced during - a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of - {key,value} pairs. A single {key,value} - in the matchLabels map is equivalent - to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are - ANDed. - type: object - type: object - x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true storageClassName: - description: 'storageClassName is the name - of the StorageClass required by the claim. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + description: 'The name of the StorageClass + required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1.' type: string volumeMode: - description: volumeMode defines what type - of volume is required by the claim. Value - of Filesystem is implied when not included - in claim spec. - type: string - volumeName: - description: volumeName is the binding reference - to the PersistentVolume backing this claim. - type: string - type: object - status: - description: 'status represents the current - information/status of a persistent volume - claim. Read-only. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' - properties: - accessModes: - description: 'accessModes contains the actual - access modes the volume backing the PVC - has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - allocatedResourceStatuses: - additionalProperties: - description: When a controller receives - persistentvolume claim update with ClaimResourceStatus - for a resource that it does not recognizes, - then it should ignore that update and - let other controllers handle it. - type: string - description: "allocatedResourceStatuses - stores status of resource being resized - for the given PVC. Key names follow standard - Kubernetes label syntax. Valid values - are either: * Un-prefixed keys: - storage - - the capacity of the volume. * Custom - resources must use implementation-defined - prefixed names such as \"example.com/my-custom-resource\" - Apart from above values - keys that are - unprefixed or have kubernetes.io prefix - are considered reserved and hence may - not be used. \n ClaimResourceStatus can - be in any of following states: - ControllerResizeInProgress: - State set when resize controller starts - resizing the volume in control-plane. - - ControllerResizeFailed: State set when - resize has failed in resize controller - with a terminal error. - NodeResizePending: - State set when resize controller has finished - resizing the volume but further resizing - of volume is needed on the node. - NodeResizeInProgress: - State set when kubelet starts resizing - the volume. - NodeResizeFailed: State - set when resizing has failed in kubelet - with a terminal error. Transient errors - don't set NodeResizeFailed. For example: - if expanding a PVC for more capacity - - this field can be one of the following - states: - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeFailed\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizePending\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeFailed\" When this field - is not set, it means that no resize operation - is in progress for the given PVC. \n A - controller that receives PVC update with - previously unknown resourceName or ClaimResourceStatus - should ignore the update for the purpose - it was designed. For example - a controller - that only is responsible for resizing - capacity of the volume, should ignore - PVC updates that change other valid resources - associated with PVC. \n This is an alpha - field and requires enabling RecoverVolumeExpansionFailure - feature." - type: object - x-kubernetes-map-type: granular - allocatedResources: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: "allocatedResources tracks - the resources allocated to a PVC including - its capacity. Key names follow standard - Kubernetes label syntax. Valid values - are either: * Un-prefixed keys: - storage - - the capacity of the volume. * Custom - resources must use implementation-defined - prefixed names such as \"example.com/my-custom-resource\" - Apart from above values - keys that are - unprefixed or have kubernetes.io prefix - are considered reserved and hence may - not be used. \n Capacity reported here - may be larger than the actual capacity - when a volume expansion operation is requested. - For storage quota, the larger value from - allocatedResources and PVC.spec.resources - is used. If allocatedResources is not - set, PVC.spec.resources alone is used - for quota calculation. If a volume expansion - capacity request is lowered, allocatedResources - is only lowered if there are no expansion - operations in progress and if the actual - volume capacity is equal or lower than - the requested capacity. \n A controller - that receives PVC update with previously - unknown resourceName should ignore the - update for the purpose it was designed. - For example - a controller that only is - responsible for resizing capacity of the - volume, should ignore PVC updates that - change other valid resources associated - with PVC. \n This is an alpha field and - requires enabling RecoverVolumeExpansionFailure - feature." - type: object - capacity: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: capacity represents the actual - resources of the underlying volume. - type: object - conditions: - description: conditions is the current Condition - of persistent volume claim. If underlying - persistent volume is being resized then - the Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition - contains details about state of pvc - properties: - lastProbeTime: - description: lastProbeTime is the - time we probed the condition. - format: date-time - type: string - lastTransitionTime: - description: lastTransitionTime is - the time the condition transitioned - from one status to another. - format: date-time - type: string - message: - description: message is the human-readable - message indicating details about - last transition. - type: string - reason: - description: reason is a unique, this - should be a short, machine understandable - string that gives the reason for - condition's last transition. If - it reports "ResizeStarted" that - means the underlying persistent - volume is being resized. - type: string - status: - type: string - type: - description: PersistentVolumeClaimConditionType - is a valid value of PersistentVolumeClaimCondition.Type - type: string - required: - - status - - type - type: object - type: array - phase: - description: phase represents the current - phase of PersistentVolumeClaim. + description: Defines what type of volume + is required by the claim, either Block + or Filesystem. type: string type: object + required: + - name type: object type: array volumeMounts: diff --git a/config/crd/bases/workloads.kubeblocks.io_instancesets.yaml b/config/crd/bases/workloads.kubeblocks.io_instancesets.yaml index 018a67172a4..b936792ab7b 100644 --- a/config/crd/bases/workloads.kubeblocks.io_instancesets.yaml +++ b/config/crd/bases/workloads.kubeblocks.io_instancesets.yaml @@ -3259,6 +3259,9 @@ spec: - name type: object type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map memberUpdateStrategy: description: "Members(Pods) update strategy. \n - serial: update Members one by one that guarantee minimum component unavailable time. - diff --git a/controllers/apps/operations/custom.go b/controllers/apps/operations/custom.go index f762cafb210..6d8369dd47e 100644 --- a/controllers/apps/operations/custom.go +++ b/controllers/apps/operations/custom.go @@ -74,13 +74,13 @@ func (c CustomOpsHandler) ReconcileAction(reqCtx intctrlutil.RequestCtx, cli cli opsRequestPhase = opsRes.OpsRequest.Status.Phase customSpec = opsRes.OpsRequest.Spec.CustomSpec workflowContext = NewWorkflowContext(reqCtx, cli, opsRes) - compCount = len(customSpec.CustomOpsComponents) + compCount = len(customSpec.CustomOpsItems) completedActionCount int compFailedCount int compCompleteCount int ) // TODO: support Parallelism - for _, v := range customSpec.CustomOpsComponents { + for _, v := range customSpec.CustomOpsItems { // 1. init component action progress and preCheck if the conditions for executing ops are met. passed := c.initCompActionStatusAndPreCheck(reqCtx, cli, opsRes, v) if !passed { @@ -124,17 +124,17 @@ func (c CustomOpsHandler) checkExpression(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource, rule *appsv1alpha1.Rule, - compCustomOSpec appsv1alpha1.CustomOpsComponent) error { + compCustomItem appsv1alpha1.CustomOpsItem) error { opsSpec := opsRes.OpsRequest.Spec if opsSpec.Force { return nil } - componentObjName := constant.GenerateClusterComponentName(opsSpec.ClusterRef, compCustomOSpec.ComponentName) + componentObjName := constant.GenerateClusterComponentName(opsSpec.ClusterRef, compCustomItem.ComponentName) comp := &appsv1alpha1.Component{} if err := cli.Get(reqCtx.Ctx, client.ObjectKey{Name: componentObjName, Namespace: opsRes.OpsRequest.Namespace}, comp); err != nil { return err } - params := covertParametersToMap(compCustomOSpec.Parameters) + params := covertParametersToMap(compCustomItem.Parameters) // get the built-in objects and covert the json tag getBuiltInObjs := func() (map[string]interface{}, error) { b, err := json.Marshal(map[string]interface{}{ @@ -174,19 +174,19 @@ func (c CustomOpsHandler) checkExpression(reqCtx intctrlutil.RequestCtx, func (c CustomOpsHandler) initCompActionStatusAndPreCheck(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource, - compCustomSpec appsv1alpha1.CustomOpsComponent) bool { + compCustomItem appsv1alpha1.CustomOpsItem) bool { if opsRes.OpsRequest.Status.Components == nil { opsRes.OpsRequest.Status.Components = map[string]appsv1alpha1.OpsRequestComponentStatus{} } - compStatus := opsRes.OpsRequest.Status.Components[compCustomSpec.ComponentName] - compStatus.Phase = opsRes.Cluster.Status.Components[compCustomSpec.ComponentName].Phase + compStatus := opsRes.OpsRequest.Status.Components[compCustomItem.ComponentName] + compStatus.Phase = opsRes.Cluster.Status.Components[compCustomItem.ComponentName].Phase if len(compStatus.ProgressDetails) == 0 { // 1. do preChecks for _, v := range opsRes.OpsDef.Spec.PreConditions { if v.Rule != nil { - if err := c.checkExpression(reqCtx, cli, opsRes, v.Rule, compCustomSpec); err != nil { + if err := c.checkExpression(reqCtx, cli, opsRes, v.Rule, compCustomItem); err != nil { compStatus.PreCheckResult = &appsv1alpha1.PreCheckResult{Pass: false, Message: err.Error()} - opsRes.OpsRequest.Status.Components[compCustomSpec.ComponentName] = compStatus + opsRes.OpsRequest.Status.Components[compCustomItem.ComponentName] = compStatus opsRes.Recorder.Event(opsRes.OpsRequest, corev1.EventTypeWarning, "PreCheckFailed", err.Error()) return false } @@ -200,7 +200,7 @@ func (c CustomOpsHandler) initCompActionStatusAndPreCheck(reqCtx intctrlutil.Req ActionName: opsRes.OpsDef.Spec.Actions[i].Name, }) } - opsRes.OpsRequest.Status.Components[compCustomSpec.ComponentName] = compStatus + opsRes.OpsRequest.Status.Components[compCustomItem.ComponentName] = compStatus } return true } @@ -228,7 +228,7 @@ func initOpsDefAndValidate(reqCtx intctrlutil.RequestCtx, opsRes.OpsDef = opsDef // 1. validate OpenApV3Schema parametersSchema := opsDef.Spec.ParametersSchema - for _, v := range customSpec.CustomOpsComponents { + for _, v := range customSpec.CustomOpsItems { // covert to type map[string]interface{} params, err := common.CoverStringToInterfaceBySchemaType(parametersSchema.OpenAPIV3Schema, covertParametersToMap(v.Parameters)) if err != nil { diff --git a/controllers/apps/operations/custom/action_exec.go b/controllers/apps/operations/custom/action_exec.go index 7b9ff134f5c..3b36a4334d3 100644 --- a/controllers/apps/operations/custom/action_exec.go +++ b/controllers/apps/operations/custom/action_exec.go @@ -33,7 +33,7 @@ type ExecAction struct { OpsRequest *appsv1alpha1.OpsRequest Cluster *appsv1alpha1.Cluster OpsDef *appsv1alpha1.OpsDefinition - CompCustomSpec *appsv1alpha1.CustomOpsComponent + CustomOpsItem *appsv1alpha1.CustomOpsItem Comp *appsv1alpha1.ClusterComponentSpec progressDetail appsv1alpha1.ProgressStatusDetail } @@ -41,14 +41,14 @@ type ExecAction struct { func NewExecAction(opsRequest *appsv1alpha1.OpsRequest, cluster *appsv1alpha1.Cluster, opsDef *appsv1alpha1.OpsDefinition, - comCustomSpec *appsv1alpha1.CustomOpsComponent, + customOpsItem *appsv1alpha1.CustomOpsItem, comp *appsv1alpha1.ClusterComponentSpec, progressDetail appsv1alpha1.ProgressStatusDetail) *ExecAction { return &ExecAction{ OpsRequest: opsRequest, Cluster: cluster, OpsDef: opsDef, - CompCustomSpec: comCustomSpec, + CustomOpsItem: customOpsItem, Comp: comp, progressDetail: progressDetail, } @@ -68,7 +68,7 @@ func (e *ExecAction) Execute(actionCtx ActionContext) (*ActionStatus, error) { if targetPodTemplate == nil { return nil, intctrlutil.NewFatalError("can not found the targetPodTemplate by " + podTemplateName) } - targetPods, err := getTargetPods(actionCtx.ReqCtx.Ctx, actionCtx.Client, e.Cluster, targetPodTemplate.PodSelector, e.CompCustomSpec.ComponentName) + targetPods, err := getTargetPods(actionCtx.ReqCtx.Ctx, actionCtx.Client, e.Cluster, targetPodTemplate.PodSelector, e.CustomOpsItem.ComponentName) if err != nil { return nil, err } @@ -142,7 +142,7 @@ func (e *ExecAction) buildExecPodSpec(actionCtx ActionContext, targetPod *corev1.Pod) (*corev1.PodSpec, error) { // inject component and componentDef envs env, err := buildActionPodEnv(actionCtx.ReqCtx, actionCtx.Client, e.Cluster, e.OpsDef, - e.OpsRequest, e.Comp, e.CompCustomSpec, targetPodTemplate, targetPod) + e.OpsRequest, e.Comp, e.CustomOpsItem, targetPodTemplate, targetPod) if err != nil { return nil, err } diff --git a/controllers/apps/operations/custom/action_workload.go b/controllers/apps/operations/custom/action_workload.go index 450f2a0c3b7..a32b9070860 100644 --- a/controllers/apps/operations/custom/action_workload.go +++ b/controllers/apps/operations/custom/action_workload.go @@ -33,7 +33,7 @@ type WorkloadAction struct { OpsRequest *appsv1alpha1.OpsRequest Cluster *appsv1alpha1.Cluster OpsDef *appsv1alpha1.OpsDefinition - CompCustomSpec *appsv1alpha1.CustomOpsComponent + CompCustomItem *appsv1alpha1.CustomOpsItem Comp *appsv1alpha1.ClusterComponentSpec progressDetail appsv1alpha1.ProgressStatusDetail } @@ -41,14 +41,14 @@ type WorkloadAction struct { func NewWorkloadAction(opsRequest *appsv1alpha1.OpsRequest, cluster *appsv1alpha1.Cluster, opsDef *appsv1alpha1.OpsDefinition, - comCustomSpec *appsv1alpha1.CustomOpsComponent, + compCustomItem *appsv1alpha1.CustomOpsItem, comp *appsv1alpha1.ClusterComponentSpec, progressDetail appsv1alpha1.ProgressStatusDetail) *WorkloadAction { return &WorkloadAction{ OpsRequest: opsRequest, Cluster: cluster, OpsDef: opsDef, - CompCustomSpec: comCustomSpec, + CompCustomItem: compCustomItem, Comp: comp, progressDetail: progressDetail, } @@ -71,7 +71,7 @@ func (w *WorkloadAction) Execute(actionCtx ActionContext) (*ActionStatus, error) if targetPodTemplate == nil { return nil, intctrlutil.NewFatalError("can not found the targetPodTemplate by " + podTemplateName) } - targetPods, err = getTargetPods(actionCtx.ReqCtx.Ctx, actionCtx.Client, w.Cluster, targetPodTemplate.PodSelector, w.CompCustomSpec.ComponentName) + targetPods, err = getTargetPods(actionCtx.ReqCtx.Ctx, actionCtx.Client, w.Cluster, targetPodTemplate.PodSelector, w.CompCustomItem.ComponentName) if err != nil { return nil, err } @@ -144,7 +144,7 @@ func (w *WorkloadAction) buildPodSpec(actionCtx ActionContext, ) env, err := buildActionPodEnv(actionCtx.ReqCtx, actionCtx.Client, w.Cluster, w.OpsDef, w.OpsRequest, - w.Comp, w.CompCustomSpec, targetPodTemplate, targetPod) + w.Comp, w.CompCustomItem, targetPodTemplate, targetPod) if err != nil { return nil, err } diff --git a/controllers/apps/operations/custom/utils.go b/controllers/apps/operations/custom/utils.go index e7926a1cef3..b87c41defc2 100644 --- a/controllers/apps/operations/custom/utils.go +++ b/controllers/apps/operations/custom/utils.go @@ -236,7 +236,7 @@ func buildActionPodEnv(reqCtx intctrlutil.RequestCtx, opsDef *appsv1alpha1.OpsDefinition, ops *appsv1alpha1.OpsRequest, comp *appsv1alpha1.ClusterComponentSpec, - compCustomSpec *appsv1alpha1.CustomOpsComponent, + compCustomItem *appsv1alpha1.CustomOpsItem, targetPodTemplate *appsv1alpha1.TargetPodTemplate, targetPod *corev1.Pod) ([]corev1.EnvVar, error) { var env = []corev1.EnvVar{ @@ -264,7 +264,7 @@ func buildActionPodEnv(reqCtx intctrlutil.RequestCtx, } // inject params env - params := compCustomSpec.Parameters + params := compCustomItem.Parameters for i := range params { env = append(env, corev1.EnvVar{Name: params[i].Name, Value: params[i].Value}) } diff --git a/controllers/apps/operations/custom_test.go b/controllers/apps/operations/custom_test.go index a93eb4ef149..3d889c85788 100644 --- a/controllers/apps/operations/custom_test.go +++ b/controllers/apps/operations/custom_test.go @@ -81,10 +81,12 @@ var _ = Describe("CustomOps", func() { cluster.Name, appsv1alpha1.CustomType) ops.Spec.CustomSpec = &appsv1alpha1.CustomOpsSpec{ OpsDefinitionRef: opsDef.Name, - CustomOpsComponents: []appsv1alpha1.CustomOpsComponent{ + CustomOpsItems: []appsv1alpha1.CustomOpsItem{ { - ComponentName: comp, - Parameters: params, + ComponentOps: appsv1alpha1.ComponentOps{ + ComponentName: comp, + }, + Parameters: params, }, }, } diff --git a/controllers/apps/operations/custom_workflow.go b/controllers/apps/operations/custom_workflow.go index d20364a7c0d..8d206601f50 100644 --- a/controllers/apps/operations/custom_workflow.go +++ b/controllers/apps/operations/custom_workflow.go @@ -53,7 +53,7 @@ func NewWorkflowContext( } // Run actions execution layer. -func (w *WorkflowContext) Run(compCustomSpec *appsv1alpha1.CustomOpsComponent) (*WorkflowStatus, error) { +func (w *WorkflowContext) Run(compCustomSpec *appsv1alpha1.CustomOpsItem) (*WorkflowStatus, error) { var ( err error actionStatus *custom.ActionStatus @@ -145,16 +145,16 @@ steps: } func (w *WorkflowContext) getAction(action appsv1alpha1.OpsAction, - compCustomSpec *appsv1alpha1.CustomOpsComponent, + compCustomItem *appsv1alpha1.CustomOpsItem, comp *appsv1alpha1.ClusterComponentSpec, progressDetail appsv1alpha1.ProgressStatusDetail) custom.OpsAction { switch { case action.Workload != nil: return custom.NewWorkloadAction(w.OpsRes.OpsRequest, w.OpsRes.Cluster, - w.OpsRes.OpsDef, compCustomSpec, comp, progressDetail) + w.OpsRes.OpsDef, compCustomItem, comp, progressDetail) case action.Exec != nil: return custom.NewExecAction(w.OpsRes.OpsRequest, w.OpsRes.Cluster, - w.OpsRes.OpsDef, compCustomSpec, comp, progressDetail) + w.OpsRes.OpsDef, compCustomItem, comp, progressDetail) case action.ResourceModifier != nil: // TODO: implement it. return nil diff --git a/controllers/apps/operations/expose.go b/controllers/apps/operations/expose.go index 490ba240c97..4eab15c3a39 100644 --- a/controllers/apps/operations/expose.go +++ b/controllers/apps/operations/expose.go @@ -27,6 +27,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" @@ -225,10 +226,15 @@ func (e ExposeOpsHandler) ActionStartedCondition(reqCtx intctrlutil.RequestCtx, } func (e ExposeOpsHandler) SaveLastConfiguration(reqCtx intctrlutil.RequestCtx, cli client.Client, opsResource *OpsResource) error { - componentNameSet := opsResource.OpsRequest.GetComponentNameSet() + compOpsSet := map[string]sets.Empty{} + for _, v := range opsResource.OpsRequest.Spec.ExposeList { + if v.ComponentName != "" { + compOpsSet[v.ComponentName] = sets.Empty{} + } + } lastComponentInfo := map[string]appsv1alpha1.LastComponentConfiguration{} for _, v := range opsResource.Cluster.Spec.ComponentSpecs { - if _, ok := componentNameSet[v.Name]; !ok { + if _, ok := compOpsSet[v.Name]; !ok { continue } lastComponentInfo[v.Name] = appsv1alpha1.LastComponentConfiguration{ diff --git a/controllers/apps/operations/expose_test.go b/controllers/apps/operations/expose_test.go index c27fe878c7d..3c6c3ec3370 100644 --- a/controllers/apps/operations/expose_test.go +++ b/controllers/apps/operations/expose_test.go @@ -76,8 +76,8 @@ var _ = Describe("", func() { clusterObject.Name, appsv1alpha1.ExposeType) ops.Spec.ExposeList = []appsv1alpha1.Expose{ { - ComponentOps: appsv1alpha1.ComponentOps{ComponentName: consensusCompName}, - Switch: appsv1alpha1.EnableExposeSwitch, + ComponentName: consensusCompName, + Switch: appsv1alpha1.EnableExposeSwitch, Services: []appsv1alpha1.OpsService{ { Name: testapps.ServiceVPCName, @@ -113,8 +113,7 @@ var _ = Describe("", func() { clusterObject.Name, appsv1alpha1.ExposeType) ops.Spec.ExposeList = []appsv1alpha1.Expose{ { - ComponentOps: appsv1alpha1.ComponentOps{ComponentName: ""}, - Switch: appsv1alpha1.EnableExposeSwitch, + Switch: appsv1alpha1.EnableExposeSwitch, Services: []appsv1alpha1.OpsService{ { Name: testapps.ServiceVPCName, diff --git a/controllers/apps/operations/horizontal_scaling.go b/controllers/apps/operations/horizontal_scaling.go index 5636d2d2e24..93dcba99e42 100644 --- a/controllers/apps/operations/horizontal_scaling.go +++ b/controllers/apps/operations/horizontal_scaling.go @@ -23,10 +23,10 @@ import ( "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - intctrlcomp "github.com/apecloud/kubeblocks/pkg/controller/component" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) @@ -55,23 +55,17 @@ func (hs horizontalScalingOpsHandler) ActionStartedCondition(reqCtx intctrlutil. // Action modifies Cluster.spec.components[*].replicas from the opsRequest func (hs horizontalScalingOpsHandler) Action(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) error { - var ( - horizontalScalingMap = opsRes.OpsRequest.Spec.ToHorizontalScalingListToMap() - horizontalScaling appsv1alpha1.HorizontalScaling - ok bool - ) - for index, component := range opsRes.Cluster.Spec.ComponentSpecs { - if horizontalScaling, ok = horizontalScalingMap[component.Name]; !ok { - continue - } - - instances := buildInstances(opsRes.Cluster.Spec.ComponentSpecs[index], horizontalScaling) - opsRes.Cluster.Spec.ComponentSpecs[index].Instances = instances + applyHorizontalScaling := func(compSpec *appsv1alpha1.ClusterComponentSpec, obj ComponentOpsInteface) { + horizontalScaling := obj.(appsv1alpha1.HorizontalScaling) + instances := buildInstances(*compSpec, horizontalScaling) + compSpec.Instances = instances if horizontalScaling.OfflineInstances != nil { - opsRes.Cluster.Spec.ComponentSpecs[index].OfflineInstances = horizontalScaling.OfflineInstances + compSpec.OfflineInstances = horizontalScaling.OfflineInstances } - opsRes.Cluster.Spec.ComponentSpecs[index].Replicas = horizontalScaling.Replicas + compSpec.Replicas = horizontalScaling.Replicas } + compOpsSet := newComponentOpsHelper(opsRes.OpsRequest.Spec.HorizontalScalingList) + compOpsSet.updateClusterComponentsAndShardings(opsRes.Cluster, applyHorizontalScaling) return cli.Update(reqCtx.Ctx, opsRes.Cluster) } @@ -107,79 +101,57 @@ func (hs horizontalScalingOpsHandler) ReconcileAction(reqCtx intctrlutil.Request compStatus *appsv1alpha1.OpsRequestComponentStatus) (int32, int32, error) { return handleComponentProgressForScalingReplicas(reqCtx, cli, opsRes, pgRes, compStatus, hs.getExpectReplicas) } - return reconcileActionWithComponentOps(reqCtx, cli, opsRes, "", syncOverrideByOpsForScaleReplicas, handleComponentProgress) + compOpsHelper := newComponentOpsHelper(opsRes.OpsRequest.Spec.HorizontalScalingList) + return compOpsHelper.reconcileActionWithComponentOps(reqCtx, cli, opsRes, "", syncOverrideByOpsForScaleReplicas, handleComponentProgress) } // SaveLastConfiguration records last configuration to the OpsRequest.status.lastConfiguration func (hs horizontalScalingOpsHandler) SaveLastConfiguration(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) error { - opsRequest := opsRes.OpsRequest - lastComponentInfo := map[string]appsv1alpha1.LastComponentConfiguration{} - componentNameMap := opsRequest.Spec.ToHorizontalScalingListToMap() - for _, v := range opsRes.Cluster.Spec.ComponentSpecs { - hsInfo, ok := componentNameMap[v.Name] - if !ok { - continue - } - copyReplicas := v.Replicas + compOpsHelper := newComponentOpsHelper(opsRes.OpsRequest.Spec.HorizontalScalingList) + getLastComponentInfo := func(compSpec appsv1alpha1.ClusterComponentSpec, comOps ComponentOpsInteface) appsv1alpha1.LastComponentConfiguration { var ( - copyInstances *[]appsv1alpha1.InstanceTemplate - copyOfflineInstances *[]string + copyInstances []appsv1alpha1.InstanceTemplate + copyOfflineInstances []string ) - if len(v.Instances) > 0 { + if len(compSpec.Instances) > 0 { var instances []appsv1alpha1.InstanceTemplate - instances = append(instances, v.Instances...) - copyInstances = &instances + instances = append(instances, compSpec.Instances...) + copyInstances = instances } - if len(v.OfflineInstances) > 0 { + if len(compSpec.OfflineInstances) > 0 { var offlineInstances []string - offlineInstances = append(offlineInstances, v.OfflineInstances...) - copyOfflineInstances = &offlineInstances + offlineInstances = append(offlineInstances, compSpec.OfflineInstances...) + copyOfflineInstances = offlineInstances } lastCompConfiguration := appsv1alpha1.LastComponentConfiguration{ - Replicas: ©Replicas, + Replicas: pointer.Int32(compSpec.Replicas), Instances: copyInstances, OfflineInstances: copyOfflineInstances, } - if hsInfo.Replicas < copyReplicas { - podNames, err := getCompPodNamesBeforeScaleDownReplicas(reqCtx, cli, *opsRes.Cluster, v.Name) - if err != nil { - return err - } - lastCompConfiguration.TargetResources = map[appsv1alpha1.ComponentResourceKey][]string{ - appsv1alpha1.PodsCompResourceKey: podNames, - } - } - lastComponentInfo[v.Name] = lastCompConfiguration + return lastCompConfiguration } - opsRequest.Status.LastConfiguration.Components = lastComponentInfo + compOpsHelper.saveLastConfigurations(opsRes, getLastComponentInfo) return nil } -func (hs horizontalScalingOpsHandler) getExpectReplicas(opsRequest *appsv1alpha1.OpsRequest, componentName string) *int32 { +func (hs horizontalScalingOpsHandler) getExpectReplicas(opsRequest *appsv1alpha1.OpsRequest, shardName, componentName string) *int32 { compStatus := opsRequest.Status.Components[componentName] if compStatus.OverrideBy != nil { return compStatus.OverrideBy.Replicas } for _, v := range opsRequest.Spec.HorizontalScalingList { + if shardName != "" { + if v.ShardingName == shardName { + return &v.Replicas + } + continue + } if v.ComponentName == componentName { return &v.Replicas } - } - return nil -} -// getCompPodNamesBeforeScaleDownReplicas gets the component pod names before scale down replicas. -func getCompPodNamesBeforeScaleDownReplicas(reqCtx intctrlutil.RequestCtx, - cli client.Client, cluster appsv1alpha1.Cluster, compName string) ([]string, error) { - podNames := make([]string, 0) - podList, err := intctrlcomp.GetComponentPodList(reqCtx.Ctx, cli, cluster, compName) - if err != nil { - return podNames, err - } - for _, v := range podList.Items { - podNames = append(podNames, v.Name) } - return podNames, nil + return nil } // Cancel this function defines the cancel horizontalScaling action. @@ -189,25 +161,17 @@ func (hs horizontalScalingOpsHandler) Cancel(reqCtx intctrlutil.RequestCtx, cli return intctrlutil.NewErrorf(intctrlutil.ErrorIgnoreCancel, `can not cancel the opsRequest due to another opsRequest "%s" is running`, v.OverrideBy.OpsName) } } - return cancelComponentOps(reqCtx.Ctx, cli, opsRes, func(lastConfig *appsv1alpha1.LastComponentConfiguration, comp *appsv1alpha1.ClusterComponentSpec) error { + compOpsHelper := newComponentOpsHelper(opsRes.OpsRequest.Spec.VerticalScalingList) + return compOpsHelper.cancelComponentOps(reqCtx.Ctx, cli, opsRes, func(lastConfig *appsv1alpha1.LastComponentConfiguration, comp *appsv1alpha1.ClusterComponentSpec) { if lastConfig.Replicas == nil { - return nil - } - podNames, err := getCompPodNamesBeforeScaleDownReplicas(reqCtx, cli, *opsRes.Cluster, comp.Name) - if err != nil { - return err + return } - if lastConfig.TargetResources == nil { - lastConfig.TargetResources = map[appsv1alpha1.ComponentResourceKey][]string{} - } - lastConfig.TargetResources[appsv1alpha1.PodsCompResourceKey] = podNames comp.Replicas = *lastConfig.Replicas if lastConfig.Instances != nil { - comp.Instances = *lastConfig.Instances + comp.Instances = lastConfig.Instances } if lastConfig.OfflineInstances != nil { - comp.OfflineInstances = *lastConfig.OfflineInstances + comp.OfflineInstances = lastConfig.OfflineInstances } - return nil }) } diff --git a/controllers/apps/operations/horizontal_scaling_test.go b/controllers/apps/operations/horizontal_scaling_test.go index beda8cd6238..322b4f062a6 100644 --- a/controllers/apps/operations/horizontal_scaling_test.go +++ b/controllers/apps/operations/horizontal_scaling_test.go @@ -123,15 +123,15 @@ var _ = Describe("HorizontalScaling OpsRequest", func() { Expect(err).ShouldNot(HaveOccurred()) Expect(opsRes.OpsRequest.Status.Phase).Should(Equal(appsv1alpha1.OpsCancelledPhase)) opsProgressDetails := opsRes.OpsRequest.Status.Components[consensusComp].ProgressDetails - Expect(len(opsProgressDetails)).Should(Equal(1)) - Expect(opsProgressDetails[0].Status).Should(Equal(appsv1alpha1.SucceedProgressStatus)) + Expect(opsRes.OpsRequest.Status.Progress).Should(Equal("2/2")) + Expect(len(opsProgressDetails)).Should(Equal(2)) } It("test scaling down replicas", func() { reqCtx := intctrlutil.RequestCtx{Ctx: testCtx.Ctx} opsRes, podList := commonHScaleConsensusCompTest(reqCtx, 1, nil) By("mock two pods are deleted") - for i := 0; i < 2; i++ { + for i := 1; i < 3; i++ { pod := &podList[i] pod.Kind = constant.PodKind testk8s.MockPodIsTerminating(ctx, testCtx, pod) @@ -156,7 +156,7 @@ var _ = Describe("HorizontalScaling OpsRequest", func() { opsRes, podList := commonHScaleConsensusCompTest(reqCtx, 1, nil) By("mock one pod has been deleted") - pod := &podList[0] + pod := &podList[2] pod.Kind = constant.PodKind testk8s.MockPodIsTerminating(ctx, testCtx, pod) testk8s.RemovePodFinalizer(ctx, testCtx, pod) @@ -165,12 +165,14 @@ var _ = Describe("HorizontalScaling OpsRequest", func() { cancelOpsRequest(reqCtx, opsRes, time.Now().Add(-1*time.Second)) By("re-create the deleted pod") - podName := fmt.Sprintf("%s-%s-%d", clusterName, consensusComp, 0) - testapps.MockInstanceSetPod(&testCtx, nil, clusterName, consensusComp, podName, "leader", "ReadWrite") + podName := fmt.Sprintf("%s-%s-%d", clusterName, consensusComp, 2) + testapps.MockInstanceSetPod(&testCtx, nil, clusterName, consensusComp, podName, "follower", "ReadOnly") By("expect for opsRequest phase is Succeed after pods has been scaled and component phase is Running") mockConsensusCompToRunning(opsRes) checkCancelledSucceed(reqCtx, opsRes) + Expect(findStatusProgressDetail(opsRes.OpsRequest.Status.Components[consensusComp].ProgressDetails, + getProgressObjectKey(constant.PodKind, podName)).Status).Should(Equal(appsv1alpha1.SucceedProgressStatus)) }) It("test canceling HScale opsRequest which scales out replicas of component", func() { @@ -192,6 +194,8 @@ var _ = Describe("HorizontalScaling OpsRequest", func() { By("expect for opsRequest phase is Succeed after pods has been scaled and component phase is Running") mockConsensusCompToRunning(opsRes) checkCancelledSucceed(reqCtx, opsRes) + Expect(findStatusProgressDetail(opsRes.OpsRequest.Status.Components[consensusComp].ProgressDetails, + getProgressObjectKey(constant.PodKind, pod.Name)).Status).Should(Equal(appsv1alpha1.SucceedProgressStatus)) }) It("force run horizontal scaling opsRequests ", func() { diff --git a/controllers/apps/operations/ops_comp_helper.go b/controllers/apps/operations/ops_comp_helper.go new file mode 100644 index 00000000000..680f7d4e0e8 --- /dev/null +++ b/controllers/apps/operations/ops_comp_helper.go @@ -0,0 +1,308 @@ +/* +Copyright (C) 2022-2024 ApeCloud Co., Ltd + +# This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package operations + +import ( + "context" + "fmt" + "reflect" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/constant" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" +) + +type ComponentOpsInteface interface { + GetComponentName() string + GetShardingName() string +} + +type componentOpsHelper struct { + componentOpsSet map[string]ComponentOpsInteface +} + +func newComponentOpsHelper[T ComponentOpsInteface](compOpsList []T) componentOpsHelper { + compOpsHelper := componentOpsHelper{ + componentOpsSet: make(map[string]ComponentOpsInteface), + } + for i := range compOpsList { + compOps := compOpsList[i] + compOpsKey := getCompOpsKey(compOps.GetShardingName(), compOps.GetComponentName()) + compOpsHelper.componentOpsSet[compOpsKey] = compOps + } + return compOpsHelper +} + +func (c componentOpsHelper) isSharding(compOps ComponentOpsInteface) bool { + return compOps.GetShardingName() != "" +} + +func (c componentOpsHelper) getOpsComponentAndShardStatus(opsRequest *appsv1alpha1.OpsRequest, comOps ComponentOpsInteface) appsv1alpha1.OpsRequestComponentStatus { + compKey := getCompOpsKey(comOps.GetShardingName(), comOps.GetComponentName()) + return opsRequest.Status.Components[compKey] +} + +func (c componentOpsHelper) setOpsComponentAndShardStatus(opsRequest *appsv1alpha1.OpsRequest, + opsComStatus appsv1alpha1.OpsRequestComponentStatus, + comOps ComponentOpsInteface) { + compKey := getCompOpsKey(comOps.GetShardingName(), comOps.GetComponentName()) + opsRequest.Status.Components[compKey] = opsComStatus +} + +func (c componentOpsHelper) updateClusterComponentsAndShardings(cluster *appsv1alpha1.Cluster, + updateFunc func(compSpec *appsv1alpha1.ClusterComponentSpec, compOpsItem ComponentOpsInteface)) { + updateComponentSpecs := func(compSpec *appsv1alpha1.ClusterComponentSpec, compOpsKey string) { + if obj, ok := c.componentOpsSet[compOpsKey]; ok { + updateFunc(compSpec, obj) + } + } + // 1. update the components + for index := range cluster.Spec.ComponentSpecs { + comSpec := &cluster.Spec.ComponentSpecs[index] + updateComponentSpecs(comSpec, comSpec.Name) + } + // 1. update the sharding components + for index := range cluster.Spec.ShardingSpecs { + shardingSpec := &cluster.Spec.ShardingSpecs[index] + updateComponentSpecs(&shardingSpec.Template, getShardingKey(shardingSpec.Name)) + } +} + +func (c componentOpsHelper) saveLastConfigurations(opsRes *OpsResource, + buildLastCompConfiguration func(compSpec appsv1alpha1.ClusterComponentSpec, obj ComponentOpsInteface) appsv1alpha1.LastComponentConfiguration) { + setLastCompConfiguration := func(compSpec appsv1alpha1.ClusterComponentSpec, + lastConfiguration *appsv1alpha1.LastConfiguration, + compOpsKey string) { + obj, ok := c.componentOpsSet[compOpsKey] + if !ok { + return + } + lastConfiguration.Components[compOpsKey] = buildLastCompConfiguration(compSpec, obj) + } + + // 1. record the volumeTemplate of cluster components + lastConfiguration := &opsRes.OpsRequest.Status.LastConfiguration + lastConfiguration.Components = map[string]appsv1alpha1.LastComponentConfiguration{} + for _, v := range opsRes.Cluster.Spec.ComponentSpecs { + setLastCompConfiguration(v, lastConfiguration, v.Name) + } + // 2. record the volumeTemplate of sharding components + for _, v := range opsRes.Cluster.Spec.ShardingSpecs { + setLastCompConfiguration(v.Template, lastConfiguration, getShardingKey(v.Name)) + } +} + +// cancelComponentOps the common function to cancel th opsRequest which updates the component attributes. +func (c componentOpsHelper) cancelComponentOps(ctx context.Context, + cli client.Client, + opsRes *OpsResource, + updateCompSpec func(lastConfig *appsv1alpha1.LastComponentConfiguration, comp *appsv1alpha1.ClusterComponentSpec)) error { + rollBackCompSpec := func(compSpec *appsv1alpha1.ClusterComponentSpec, + lastCompInfos map[string]appsv1alpha1.LastComponentConfiguration, + compOpsKey string) { + lastConfig, ok := lastCompInfos[compOpsKey] + if !ok { + return + } + updateCompSpec(&lastConfig, compSpec) + lastCompInfos[compOpsKey] = lastConfig + } + + // 1. rollback the clusterComponentSpecs + lastCompInfos := opsRes.OpsRequest.Status.LastConfiguration.Components + for index := range opsRes.Cluster.Spec.ComponentSpecs { + compSpec := &opsRes.Cluster.Spec.ComponentSpecs[index] + rollBackCompSpec(compSpec, lastCompInfos, compSpec.Name) + } + // 2. rollback the shardingSpecs + for index := range opsRes.Cluster.Spec.ShardingSpecs { + shardingSpec := &opsRes.Cluster.Spec.ShardingSpecs[index] + rollBackCompSpec(&shardingSpec.Template, lastCompInfos, getShardingKey(shardingSpec.Name)) + } + return cli.Update(ctx, opsRes.Cluster) +} + +// reconcileActionWithComponentOps will be performed when action is done and loops till OpsRequest.status.phase is Succeed/Failed. +// the common function to reconcile opsRequest status when the opsRequest will affect the lifecycle of the components. +func (c componentOpsHelper) reconcileActionWithComponentOps(reqCtx intctrlutil.RequestCtx, + cli client.Client, + opsRes *OpsResource, + opsMessageKey string, + syncOverrideBy syncOverrideByOps, + handleStatusProgress handleStatusProgressWithComponent, +) (appsv1alpha1.OpsPhase, time.Duration, error) { + if opsRes == nil { + return "", 0, nil + } + var ( + opsRequestPhase = appsv1alpha1.OpsRunningPhase + opsRequest = opsRes.OpsRequest + isFailed bool + expectProgressCount int32 + completedProgressCount int32 + requeueTimeAfterFailed time.Duration + err error + clusterDef *appsv1alpha1.ClusterDefinition + ) + if opsRes.Cluster.Spec.ClusterDefRef != "" { + if clusterDef, err = getClusterDefByName(reqCtx.Ctx, cli, opsRes.Cluster.Spec.ClusterDefRef); err != nil { + return opsRequestPhase, 0, err + } + } + // if no specified components, we should check the all components phase of cluster. + oldOpsRequest := opsRequest.DeepCopy() + patch := client.MergeFrom(oldOpsRequest) + if opsRequest.Status.Components == nil { + opsRequest.Status.Components = map[string]appsv1alpha1.OpsRequestComponentStatus{} + } + if syncOverrideBy != nil { + if err = syncOverrideBy(reqCtx, cli, opsRes); err != nil { + return "", 0, nil + } + } + var progressResources []progressResource + setProgressResource := func(compSpec *appsv1alpha1.ClusterComponentSpec, compOps ComponentOpsInteface, fullComponentName string) error { + var componentDefinition *appsv1alpha1.ComponentDefinition + if compSpec.ComponentDef != "" { + componentDefinition = &appsv1alpha1.ComponentDefinition{} + if err = cli.Get(reqCtx.Ctx, client.ObjectKey{Name: compSpec.ComponentDef}, componentDefinition); err != nil { + return err + } + } + progressResources = append(progressResources, progressResource{ + opsMessageKey: opsMessageKey, + clusterComponent: compSpec, + clusterDef: clusterDef, + componentDef: componentDefinition, + compOps: compOps, + fullComponentName: fullComponentName, + }) + return nil + } + getCompOps := func(shardingName, componentName string) (ComponentOpsInteface, bool) { + if len(c.componentOpsSet) == 0 { + return appsv1alpha1.ComponentOps{ComponentName: componentName, ShardingName: shardingName}, true + } + compOps, ok := c.componentOpsSet[getCompOpsKey(shardingName, componentName)] + return compOps, ok + } + // 1. handle the component status + for i := range opsRes.Cluster.Spec.ComponentSpecs { + compSpec := &opsRes.Cluster.Spec.ComponentSpecs[i] + compOps, ok := getCompOps("", compSpec.Name) + if !ok { + continue + } + if err = setProgressResource(compSpec, compOps, compSpec.Name); err != nil { + return opsRequestPhase, 0, err + } + } + + // 2. handle the sharding status. + for i := range opsRes.Cluster.Spec.ShardingSpecs { + shardingSpec := opsRes.Cluster.Spec.ShardingSpecs[i] + compOps, ok := getCompOps(shardingSpec.Name, "") + if !ok { + continue + } + // handle the progress of the components of the sharding. + shardingComps, err := intctrlutil.ListShardingComponents(reqCtx.Ctx, cli, opsRes.Cluster, &shardingSpec) + if err != nil { + return opsRequestPhase, 0, err + } + for j := range shardingComps { + if err = setProgressResource(&shardingSpec.Template, compOps, + shardingComps[j].Labels[constant.KBAppComponentLabelKey]); err != nil { + return opsRequestPhase, 0, err + } + } + } + var waitCompleted bool + for _, pgResource := range progressResources { + opsCompStatus := c.getOpsComponentAndShardStatus(opsRequest, pgResource.compOps) + expectCount, completedCount, err := handleStatusProgress(reqCtx, cli, opsRes, pgResource, &opsCompStatus) + if err != nil { + if intctrlutil.IsTargetError(err, intctrlutil.ErrorWaitCacheRefresh) { + return opsRequestPhase, time.Second, nil + } + return opsRequestPhase, 0, err + } + expectProgressCount += expectCount + completedProgressCount += completedCount + if !c.isSharding(pgResource.compOps) { + lastFailedTime := opsCompStatus.LastFailedTime + componentPhase := opsRes.Cluster.Status.Components[pgResource.compOps.GetComponentName()].Phase + if isFailedOrAbnormal(componentPhase) { + isFailed = true + if lastFailedTime.IsZero() { + lastFailedTime = metav1.Now() + } + if time.Now().Before(lastFailedTime.Add(componentFailedTimeout)) { + requeueTimeAfterFailed = componentFailedTimeout - time.Since(lastFailedTime.Time) + } + } else if !lastFailedTime.IsZero() { + // reset lastFailedTime if component is not failed + lastFailedTime = metav1.Time{} + } + if opsCompStatus.Phase != componentPhase { + opsCompStatus.Phase = componentPhase + opsCompStatus.LastFailedTime = lastFailedTime + } + // wait the component to complete + if !pgResource.noWaitComponentCompleted && !isComponentCompleted(componentPhase) { + waitCompleted = true + } + } + c.setOpsComponentAndShardStatus(opsRequest, opsCompStatus, pgResource.compOps) + } + // TODO: wait for sharding cluster to completed for next opsRequest. + opsRequest.Status.Progress = fmt.Sprintf("%d/%d", completedProgressCount, expectProgressCount) + if !reflect.DeepEqual(opsRequest.Status, oldOpsRequest.Status) { + if err = cli.Status().Patch(reqCtx.Ctx, opsRequest, patch); err != nil { + return opsRequestPhase, 0, err + } + } + if waitCompleted || completedProgressCount != expectProgressCount { + return opsRequestPhase, 0, nil + } + if isFailed { + if requeueTimeAfterFailed != 0 { + // component failure may be temporary, waiting for component failure timeout. + return opsRequestPhase, requeueTimeAfterFailed, nil + } + return appsv1alpha1.OpsFailedPhase, 0, nil + } + return appsv1alpha1.OpsSucceedPhase, 0, nil +} + +func getCompOpsKey(shardingName, componentName string) string { + if shardingName != "" { + return getShardingKey(shardingName) + } + return componentName +} + +func getShardingKey(shardingName string) string { + return fmt.Sprintf("sharding/%s", shardingName) +} diff --git a/controllers/apps/operations/ops_manager.go b/controllers/apps/operations/ops_manager.go index 0338b0959c3..cf705c5261e 100644 --- a/controllers/apps/operations/ops_manager.go +++ b/controllers/apps/operations/ops_manager.go @@ -81,6 +81,7 @@ func (opsMgr *OpsManager) Do(reqCtx intctrlutil.RequestCtx, cli client.Client, o } return &ctrl.Result{}, patchValidateErrorCondition(reqCtx.Ctx, cli, opsRes, err.Error()) } + // TODO: abort last OpsRequest if using 'force' and intersecting with cluster component name or shard name. if opsBehaviour.QueueByCluster || opsBehaviour.QueueBySelf { // if ToClusterPhase is not empty, enqueue OpsRequest to the cluster Annotation. opsRecorde, err := enqueueOpsRequestToClusterAnnotation(reqCtx.Ctx, cli, opsRes, opsBehaviour) diff --git a/controllers/apps/operations/ops_progress_util.go b/controllers/apps/operations/ops_progress_util.go index 43e7a1de56e..9144268d0d5 100644 --- a/controllers/apps/operations/ops_progress_util.go +++ b/controllers/apps/operations/ops_progress_util.go @@ -27,6 +27,7 @@ import ( "golang.org/x/exp/slices" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/tools/record" "k8s.io/kubectl/pkg/util/podutils" "sigs.k8s.io/controller-runtime/pkg/client" @@ -34,6 +35,7 @@ import ( appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" "github.com/apecloud/kubeblocks/pkg/constant" intctrlcomp "github.com/apecloud/kubeblocks/pkg/controller/component" + "github.com/apecloud/kubeblocks/pkg/controller/instanceset" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) @@ -160,51 +162,46 @@ func handleComponentStatusProgress( cli client.Client, opsRes *OpsResource, pgRes progressResource, - compStatus *appsv1alpha1.OpsRequestComponentStatus) (expectProgressCount int32, completedCount int32, err error) { + compStatus *appsv1alpha1.OpsRequestComponentStatus, + podApplyOps func(*corev1.Pod, ComponentOpsInteface, metav1.Time, string) bool) (int32, int32, error) { var ( podList *corev1.PodList clusterComponent = pgRes.clusterComponent + completedCount int32 + err error ) if clusterComponent == nil { - return + return 0, 0, nil } - if podList, err = intctrlcomp.GetComponentPodList(reqCtx.Ctx, cli, *opsRes.Cluster, clusterComponent.Name); err != nil { - return + if podList, err = intctrlcomp.GetComponentPodList(reqCtx.Ctx, cli, *opsRes.Cluster, pgRes.fullComponentName); err != nil { + return 0, completedCount, err } - completedCount, err = handleInstanceSetProgress(reqCtx, cli, opsRes, podList, pgRes, compStatus) expectReplicas := clusterComponent.Replicas - if opsRes.OpsRequest.Status.Phase == appsv1alpha1.OpsCancellingPhase { - // only rollback the actual re-created pod during cancelling. - expectReplicas = int32(len(compStatus.ProgressDetails)) + if len(pgRes.updatedPodSet) > 0 { + // pods need to updated during this operation. + var updatedPods []corev1.Pod + for i := range podList.Items { + if _, ok := pgRes.updatedPodSet[podList.Items[i].Name]; ok { + updatedPods = append(updatedPods, podList.Items[i]) + } + } + podList.Items = updatedPods + expectReplicas = int32(len(pgRes.updatedPodSet)) } - return expectReplicas, completedCount, err -} - -// handleInstanceSetProgress handles the component progressDetails which using InstanceSet workloads. -func handleInstanceSetProgress(reqCtx intctrlutil.RequestCtx, - cli client.Client, - opsRes *OpsResource, - podList *corev1.PodList, - pgRes progressResource, - compStatus *appsv1alpha1.OpsRequestComponentStatus) (int32, error) { minReadySeconds, err := intctrlcomp.GetComponentMinReadySeconds(reqCtx.Ctx, cli, *opsRes.Cluster, pgRes.clusterComponent.Name) if err != nil { - return 0, err + return expectReplicas, completedCount, err + } + if opsRes.OpsRequest.Status.Phase == appsv1alpha1.OpsCancellingPhase { + completedCount = handleCancelProgressForPodsRollingUpdate(opsRes, podList, pgRes, compStatus, minReadySeconds) + } else { + completedCount = handleProgressForPodsRollingUpdate(opsRes, podList, pgRes, compStatus, minReadySeconds, podApplyOps) } - return handleRollingUpdateProgress(opsRes, podList, pgRes, compStatus, minReadySeconds), nil -} - -// handleRollingUpdateProgress handles the component progressDetails during rolling update. -func handleRollingUpdateProgress( - opsRes *OpsResource, - podList *corev1.PodList, - pgRes progressResource, - compStatus *appsv1alpha1.OpsRequestComponentStatus, - minReadySeconds int32) int32 { if opsRes.OpsRequest.Status.Phase == appsv1alpha1.OpsCancellingPhase { - return handleCancelProgressForPodsRollingUpdate(opsRes, podList, pgRes, compStatus, minReadySeconds) + // only rollback the actual re-created pod during cancelling. + expectReplicas = int32(len(compStatus.ProgressDetails)) } - return handleProgressForPodsRollingUpdate(opsRes, podList, pgRes, compStatus, minReadySeconds) + return expectReplicas, completedCount, err } // handleProgressForPodsRollingUpdate handles the progress of pods during rolling update. @@ -213,14 +210,15 @@ func handleProgressForPodsRollingUpdate( podList *corev1.PodList, pgRes progressResource, compStatus *appsv1alpha1.OpsRequestComponentStatus, - minReadySeconds int32) int32 { + minReadySeconds int32, + podApplyOps func(*corev1.Pod, ComponentOpsInteface, metav1.Time, string) bool) int32 { opsRequest := opsRes.OpsRequest opsStartTime := opsRequest.Status.StartTimestamp var completedCount int32 for _, v := range podList.Items { - objectKey := getProgressObjectKey(v.Kind, v.Name) + objectKey := getProgressObjectKey(constant.PodKind, v.Name) progressDetail := appsv1alpha1.ProgressStatusDetail{ObjectKey: objectKey} - if podProcessedSuccessful(pgRes, opsStartTime, &v, minReadySeconds, compStatus.Phase, pgRes.opsIsCompleted) { + if podProcessedSuccessful(pgRes, opsStartTime, &v, minReadySeconds, podApplyOps) { completedCount += 1 handleSucceedProgressDetail(opsRes, pgRes, compStatus, progressDetail) continue @@ -253,7 +251,7 @@ func handleCancelProgressForPodsRollingUpdate( pgRes.opsMessageKey = fmt.Sprintf("%s with rollback", pgRes.opsMessageKey) var completedCount int32 for _, pod := range podList.Items { - objectKey := getProgressObjectKey(pod.Kind, pod.Name) + objectKey := getProgressObjectKey(constant.PodKind, pod.Name) progressDetail := appsv1alpha1.ProgressStatusDetail{ObjectKey: objectKey} if !pod.CreationTimestamp.Before(&opsCancelTime) && podIsAvailable(pgRes, &pod, minReadySeconds) { @@ -316,7 +314,7 @@ func handleFailedOrProcessingProgressDetail(opsRes *OpsResource, pod *corev1.Pod) (completedCount int32) { componentName := pgRes.clusterComponent.Name opsStartTime := opsRes.OpsRequest.Status.StartTimestamp - if podIsFailedDuringOperation(opsStartTime, pod, compStatus.Phase, pgRes.opsIsCompleted) { + if podIsFailedDuringOperation(opsStartTime, pod, compStatus.Phase) { podMessage := getFailedPodMessage(opsRes.Cluster, componentName, pod) // if the pod is not failed, return if len(podMessage) == 0 { @@ -343,12 +341,14 @@ func podIsPendingDuringOperation(opsStartTime metav1.Time, pod *corev1.Pod) bool func podIsFailedDuringOperation( opsStartTime metav1.Time, pod *corev1.Pod, - componentPhase appsv1alpha1.ClusterComponentPhase, - opsIsCompleted bool) bool { + componentPhase appsv1alpha1.ClusterComponentPhase) bool { if !isFailedOrAbnormal(componentPhase) { return false } - return !pod.CreationTimestamp.Before(&opsStartTime) || opsIsCompleted + // When the component is running and the pod has been created after opsStartTime, + // but it does not meet the success condition, it indicates that the changes made + // to the operations have been overwritten, resulting in a failed status. + return !pod.CreationTimestamp.Before(&opsStartTime) && componentPhase == appsv1alpha1.RunningClusterCompPhase } // podProcessedSuccessful checks if the pod has been processed successfully: @@ -358,12 +358,14 @@ func podProcessedSuccessful(pgRes progressResource, opsStartTime metav1.Time, pod *corev1.Pod, minReadySeconds int32, - componentPhase appsv1alpha1.ClusterComponentPhase, - opsIsCompleted bool) bool { + podApplyOps func(*corev1.Pod, ComponentOpsInteface, metav1.Time, string) bool) bool { + if !pod.DeletionTimestamp.IsZero() { + return false + } if !podIsAvailable(pgRes, pod, minReadySeconds) { return false } - return (opsIsCompleted && componentPhase == appsv1alpha1.RunningClusterCompPhase) || !pod.CreationTimestamp.Before(&opsStartTime) + return podApplyOps(pod, pgRes.compOps, opsStartTime, pgRes.updatedPodSet[pod.Name]) } func getProgressProcessingMessage(opsMessageKey, objectKey, componentName string) string { @@ -381,22 +383,7 @@ func getProgressFailedMessage(opsMessageKey, objectKey, componentName, podMessag // getFailedPodMessage gets the failed pod message from cluster component status func getFailedPodMessage(cluster *appsv1alpha1.Cluster, componentName string, pod *corev1.Pod) string { clusterCompStatus := cluster.Status.Components[componentName] - return clusterCompStatus.GetObjectMessage(pod.Kind, pod.Name) -} - -func getComponentLastReplicas(opsRequest *appsv1alpha1.OpsRequest, componentName string) *int32 { - lastCompConfiguration := opsRequest.Status.LastConfiguration.Components[componentName] - if lastCompConfiguration.Replicas == nil { - return nil - } - if lastPods, ok := lastCompConfiguration.TargetResources[appsv1alpha1.PodsCompResourceKey]; ok { - lastActualComponents := int32(len(lastPods)) - // may the actual pods not equals the component replicas - if lastActualComponents < *lastCompConfiguration.Replicas { - return &lastActualComponents - } - } - return lastCompConfiguration.Replicas + return clusterCompStatus.GetObjectMessage(constant.PodKind, pod.Name) } // handleComponentProgressDetails handles the component progressDetails when scale the replicas. @@ -408,7 +395,7 @@ func handleComponentProgressForScalingReplicas(reqCtx intctrlutil.RequestCtx, opsRes *OpsResource, pgRes progressResource, compStatus *appsv1alpha1.OpsRequestComponentStatus, - getExpectReplicas func(opsRequest *appsv1alpha1.OpsRequest, componentName string) *int32) (int32, int32, error) { + getExpectReplicas func(opsRequest *appsv1alpha1.OpsRequest, shardingName, componentName string) *int32) (int32, int32, error) { var ( podList *corev1.PodList clusterComponent = pgRes.clusterComponent @@ -418,11 +405,12 @@ func handleComponentProgressForScalingReplicas(reqCtx intctrlutil.RequestCtx, if clusterComponent == nil { return 0, 0, nil } - expectReplicas := getExpectReplicas(opsRequest, clusterComponent.Name) + expectReplicas := getExpectReplicas(opsRequest, pgRes.compOps.GetShardingName(), pgRes.fullComponentName) if expectReplicas == nil { return 0, 0, nil } - lastComponentReplicas := getComponentLastReplicas(opsRequest, clusterComponent.Name) + compOpsKey := getCompOpsKey(pgRes.compOps.GetShardingName(), pgRes.compOps.GetComponentName()) + lastComponentReplicas := opsRequest.Status.LastConfiguration.Components[compOpsKey].Replicas if lastComponentReplicas == nil { return 0, 0, nil } @@ -430,7 +418,7 @@ func handleComponentProgressForScalingReplicas(reqCtx intctrlutil.RequestCtx, if *lastComponentReplicas == *expectReplicas { return 0, 0, nil } - if podList, err = intctrlcomp.GetComponentPodList(reqCtx.Ctx, cli, *opsRes.Cluster, clusterComponent.Name); err != nil { + if podList, err = intctrlcomp.GetComponentPodList(reqCtx.Ctx, cli, *opsRes.Cluster, pgRes.fullComponentName); err != nil { return 0, 0, err } actualPodsLen := int32(len(podList.Items)) @@ -438,7 +426,8 @@ func handleComponentProgressForScalingReplicas(reqCtx intctrlutil.RequestCtx, return 0, 0, intctrlutil.NewError(intctrlutil.ErrorWaitCacheRefresh, "wait for the pods of component to be synchronized") } if opsRequest.Status.Phase == appsv1alpha1.OpsCancellingPhase { - expectReplicas = opsRequest.Status.LastConfiguration.Components[clusterComponent.Name].Replicas + lastComponentReplicas = expectReplicas + expectReplicas = opsRequest.Status.LastConfiguration.Components[compOpsKey].Replicas } var ( expectProgressCount int32 @@ -447,10 +436,10 @@ func handleComponentProgressForScalingReplicas(reqCtx intctrlutil.RequestCtx, ) if dValue > 0 { expectProgressCount = dValue - completedCount, err = handleScaleOutProgress(reqCtx, cli, opsRes, pgRes, podList, compStatus) + completedCount, err = handleScaleOutProgress(reqCtx, cli, opsRes, pgRes, podList, compStatus, *lastComponentReplicas, *expectReplicas) } else { expectProgressCount = dValue * -1 - completedCount, err = handleScaleDownProgress(reqCtx, cli, opsRes, pgRes, podList, compStatus) + completedCount, err = handleScaleDownProgress(opsRes, pgRes, podList, compStatus, *lastComponentReplicas, *expectReplicas) } return getFinalExpectCount(completedCount, expectProgressCount), completedCount, err } @@ -461,19 +450,22 @@ func handleScaleOutProgress(reqCtx intctrlutil.RequestCtx, opsRes *OpsResource, pgRes progressResource, podList *corev1.PodList, - compStatus *appsv1alpha1.OpsRequestComponentStatus) (int32, error) { + compStatus *appsv1alpha1.OpsRequestComponentStatus, + lastComponentReplicas, + expectReplicas int32) (int32, error) { var componentName = pgRes.clusterComponent.Name minReadySeconds, err := intctrlcomp.GetComponentMinReadySeconds(reqCtx.Ctx, cli, *opsRes.Cluster, componentName) if err != nil { return 0, err } + // Calculate the pods that need to be created based on replicas and handle them. + createPodSet := getUpdatedPodsForHorizontalScaling(opsRes, pgRes, lastComponentReplicas, expectReplicas, false) var completedCount int32 for _, v := range podList.Items { - // only focus on the newly created pod when scaling out the replicas. - if v.CreationTimestamp.Before(&opsRes.OpsRequest.Status.StartTimestamp) { + if _, ok := createPodSet[v.Name]; !ok { continue } - objectKey := getProgressObjectKey(v.Kind, v.Name) + objectKey := getProgressObjectKey(constant.PodKind, v.Name) progressDetail := appsv1alpha1.ProgressStatusDetail{ObjectKey: objectKey} pgRes.opsMessageKey = "create" if podIsAvailable(pgRes, &v, minReadySeconds) { @@ -488,94 +480,78 @@ func handleScaleOutProgress(reqCtx intctrlutil.RequestCtx, // handleScaleDownProgress handles the progressDetails of scaled down replicas. func handleScaleDownProgress( - reqCtx intctrlutil.RequestCtx, - cli client.Client, opsRes *OpsResource, pgRes progressResource, podList *corev1.PodList, - compStatus *appsv1alpha1.OpsRequestComponentStatus) (completedCount int32, err error) { + compStatus *appsv1alpha1.OpsRequestComponentStatus, + lastComponentReplicas, + expectReplicas int32) (completedCount int32, err error) { podMap := map[string]corev1.Pod{} - // record the deleting pod progressDetail for _, v := range podList.Items { objectKey := getProgressObjectKey(constant.PodKind, v.Name) podMap[objectKey] = v - if v.DeletionTimestamp.IsZero() { - continue - } - setComponentStatusProgressDetail(opsRes.Recorder, opsRes.OpsRequest, - &compStatus.ProgressDetails, appsv1alpha1.ProgressStatusDetail{ - ObjectKey: objectKey, - Status: appsv1alpha1.ProcessingProgressStatus, - Message: fmt.Sprintf("Start to delete pod: %s in Component: %s", objectKey, pgRes.clusterComponent.Name), - }) - } - var componentName = pgRes.clusterComponent.Name - minReadySeconds, err := intctrlcomp.GetComponentMinReadySeconds(reqCtx.Ctx, cli, *opsRes.Cluster, componentName) - if err != nil { - return 0, err } - - handleDeletionSuccessful := func(objectKey string) { - // if the pod is not in the podList, it means the pod has been deleted. + updateProgressDetail := func(objectKey string, status appsv1alpha1.ProgressStatus) { progressDetail := appsv1alpha1.ProgressStatusDetail{ + Group: pgRes.fullComponentName, ObjectKey: objectKey, - Status: appsv1alpha1.SucceedProgressStatus, - Message: fmt.Sprintf("Successfully delete pod: %s in Component: %s", objectKey, pgRes.clusterComponent.Name), + Status: status, + } + var messagePrefix string + switch status { + case appsv1alpha1.SucceedProgressStatus: + completedCount += 1 + messagePrefix = "Successfully" + case appsv1alpha1.ProcessingProgressStatus: + messagePrefix = "Start to" + case appsv1alpha1.PendingProgressStatus: + messagePrefix = "wait to" } - completedCount += 1 + progressDetail.Message = fmt.Sprintf("%s delete pod: %s in Component: %s", messagePrefix, objectKey, pgRes.clusterComponent.Name) setComponentStatusProgressDetail(opsRes.Recorder, opsRes.OpsRequest, &compStatus.ProgressDetails, progressDetail) } - - handleProgressDetails := func() { - for _, progressDetail := range compStatus.ProgressDetails { - if isCompletedProgressStatus(progressDetail.Status) { - completedCount += 1 - continue - } - // if pod not exists, means successful deletion. - pod, ok := podMap[progressDetail.ObjectKey] - if !ok { - handleDeletionSuccessful(progressDetail.ObjectKey) - continue - } - // handle the re-created pods if these pods are failed before doing horizontal scaling. - pgRes.opsMessageKey = "re-create" - if podIsAvailable(pgRes, &pod, minReadySeconds) { - completedCount += 1 - handleSucceedProgressDetail(opsRes, pgRes, compStatus, progressDetail) - continue - } - if pod.DeletionTimestamp.IsZero() { - completedCount += handleFailedOrProcessingProgressDetail(opsRes, pgRes, compStatus, progressDetail, &pod) - } + // Calculate the pods that need to be deleted based on replicas and handle them. + deletePodSet := getUpdatedPodsForHorizontalScaling(opsRes, pgRes, lastComponentReplicas, expectReplicas, true) + for k := range deletePodSet { + objectKey := getProgressObjectKey(constant.PodKind, k) + pod, ok := podMap[objectKey] + if !ok { + updateProgressDetail(objectKey, appsv1alpha1.SucceedProgressStatus) + continue } - } - - handleDeletedPodNotInProgressDetails := func() { - // pod may not be recorded in the progressDetails if deleted quickly or due to unknown reasons, but it has actually been deleted. - // compare with the last pods and current pods to check if pod is deleted. - lastComponentPodNames := getTargetResourcesOfLastComponent(opsRes.OpsRequest.Status.LastConfiguration, componentName, appsv1alpha1.PodsCompResourceKey) - for _, v := range lastComponentPodNames { - objectKey := getProgressObjectKey(constant.PodKind, v) - progressDetail := findStatusProgressDetail(compStatus.ProgressDetails, objectKey) - // if recorded in progressDetails, continue - if progressDetail != nil { - continue - } - if _, ok := podMap[objectKey]; ok { - continue - } - handleDeletionSuccessful(objectKey) + if !pod.DeletionTimestamp.IsZero() { + updateProgressDetail(objectKey, appsv1alpha1.ProcessingProgressStatus) + continue } + updateProgressDetail(objectKey, appsv1alpha1.PendingProgressStatus) } - handleProgressDetails() - handleDeletedPodNotInProgressDetails() return completedCount, nil } +// getUpdatedPodsForHorizontalScaling gets the updated pods. +// TODO: support instances operation for hscale in next PR. +func getUpdatedPodsForHorizontalScaling(opsRes *OpsResource, + pgRes progressResource, + lastComponentReplicas, + expectReplicas int32, + scaleDown bool) map[string]sets.Empty { + workloadName := constant.GenerateWorkloadNamePattern(opsRes.Cluster.Name, pgRes.fullComponentName) + lastPods, _ := instanceset.GenerateInstanceNames(workloadName, "", + lastComponentReplicas, 0, pgRes.clusterComponent.OfflineInstances) + lastPodSet := sets.New(lastPods...) + currPods, _ := instanceset.GenerateInstanceNames(workloadName, "", + expectReplicas, 0, pgRes.clusterComponent.OfflineInstances) + currPodSet := sets.New(currPods...) + if scaleDown { + return lastPodSet.Difference(currPodSet) + } + return currPodSet.Difference(lastPodSet) +} + // getFinalExpectCount gets the number of pods which has been processed by controller. func getFinalExpectCount(completedCount, expectProgressCount int32) int32 { + // completedCount maybe greater than expectProgressCount when exists failed pods. if completedCount > expectProgressCount { return completedCount } diff --git a/controllers/apps/operations/ops_progress_util_test.go b/controllers/apps/operations/ops_progress_util_test.go index f7a809835b5..c3a42bcb7bd 100644 --- a/controllers/apps/operations/ops_progress_util_test.go +++ b/controllers/apps/operations/ops_progress_util_test.go @@ -129,8 +129,8 @@ var _ = Describe("Ops ProgressDetails", func() { _, err = GetOpsManager().Do(reqCtx, k8sClient, opsRes) Expect(err).ShouldNot(HaveOccurred()) - By("mock the pod is terminating, pod[0] is target pod to delete. and mock pod[1] is failed and deleted by stateful controller") - for i := 0; i < 2; i++ { + By("mock the pod is terminating, pod[1] is target pod to delete. and mock pod[2] is failed and deleted by stateful controller") + for i := 1; i < 3; i++ { pod := &podList[i] pod.Kind = constant.PodKind testk8s.MockPodIsTerminating(ctx, testCtx, pod) @@ -139,17 +139,15 @@ var _ = Describe("Ops ProgressDetails", func() { } By("mock the target pod is deleted and progressDetail status should be succeed") - targetPod := &podList[0] + targetPod := &podList[1] testk8s.RemovePodFinalizer(ctx, testCtx, targetPod) _, _ = GetOpsManager().Reconcile(reqCtx, k8sClient, opsRes) Expect(getProgressDetailStatus(opsRes, consensusComp, targetPod)).Should(Equal(appsv1alpha1.SucceedProgressStatus)) Expect(opsRes.OpsRequest.Status.Progress).Should(Equal("1/2")) - By("mock the pod[1] to re-create") - pod := &podList[1] + By("delete the pod[2]") + pod := &podList[2] testk8s.RemovePodFinalizer(ctx, testCtx, pod) - testapps.MockInstanceSetPod(&testCtx, nil, clusterName, consensusComp, - pod.Name, "Follower", "ReadWrite") // expect the progress is 2/2 _, _ = GetOpsManager().Reconcile(reqCtx, k8sClient, opsRes) Expect(getProgressDetailStatus(opsRes, consensusComp, targetPod)).Should(Equal(appsv1alpha1.SucceedProgressStatus)) @@ -174,8 +172,9 @@ var _ = Describe("Ops ProgressDetails", func() { Expect(err).ShouldNot(HaveOccurred()) By("test the progressDetails when scaling up replicas") + targetPod = &podList[2] testapps.MockInstanceSetPod(&testCtx, nil, clusterName, consensusComp, - targetPod.Name, "leader", "ReadWrite") + targetPod.Name, "follower", "ReadWrite") Expect(k8sClient.Get(ctx, client.ObjectKey{Name: targetPod.Name, Namespace: testCtx.DefaultNamespace}, targetPod)).Should(Succeed()) _, _ = GetOpsManager().Reconcile(reqCtx, k8sClient, opsRes) Expect(getProgressDetailStatus(opsRes, consensusComp, targetPod)).Should(Equal(appsv1alpha1.SucceedProgressStatus)) @@ -185,7 +184,7 @@ var _ = Describe("Ops ProgressDetails", func() { }) func getProgressDetailStatus(opsRes *OpsResource, componentName string, pod *corev1.Pod) appsv1alpha1.ProgressStatus { - objectKey := getProgressObjectKey(pod.Kind, pod.Name) + objectKey := getProgressObjectKey(constant.PodKind, pod.Name) progressDetails := opsRes.OpsRequest.Status.Components[componentName].ProgressDetails progressDetail := findStatusProgressDetail(progressDetails, objectKey) var status appsv1alpha1.ProgressStatus diff --git a/controllers/apps/operations/ops_util.go b/controllers/apps/operations/ops_util.go index 026dc54d07e..a4f610cb3a2 100644 --- a/controllers/apps/operations/ops_util.go +++ b/controllers/apps/operations/ops_util.go @@ -22,7 +22,6 @@ package operations import ( "context" "fmt" - "reflect" "time" "golang.org/x/exp/slices" @@ -31,11 +30,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - workloads "github.com/apecloud/kubeblocks/apis/workloads/v1alpha1" opsutil "github.com/apecloud/kubeblocks/controllers/apps/operations/util" "github.com/apecloud/kubeblocks/pkg/configuration/core" - "github.com/apecloud/kubeblocks/pkg/constant" - intctrlcomp "github.com/apecloud/kubeblocks/pkg/controller/component" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) @@ -74,147 +70,8 @@ func isFailedOrAbnormal(phase appsv1alpha1.ClusterComponentPhase) bool { appsv1alpha1.AbnormalClusterCompPhase}, phase) != -1 } -// reconcileActionWithComponentOps will be performed when action is done and loops till OpsRequest.status.phase is Succeed/Failed. -// the common function to reconcile opsRequest status when the opsRequest will affect the lifecycle of the components. -func reconcileActionWithComponentOps(reqCtx intctrlutil.RequestCtx, - cli client.Client, - opsRes *OpsResource, - opsMessageKey string, - syncOverrideBy syncOverrideByOps, - handleStatusProgress handleStatusProgressWithComponent, -) (appsv1alpha1.OpsPhase, time.Duration, error) { - if opsRes == nil { - return "", 0, nil - } - var ( - opsRequestPhase = appsv1alpha1.OpsRunningPhase - opsRequest = opsRes.OpsRequest - isFailed bool - ok bool - expectProgressCount int32 - completedProgressCount int32 - checkAllClusterComponent bool - requeueTimeAfterFailed time.Duration - err error - clusterDef *appsv1alpha1.ClusterDefinition - ) - if opsRes.Cluster.Spec.ClusterDefRef != "" { - if clusterDef, err = getClusterDefByName(reqCtx.Ctx, cli, opsRes.Cluster.Spec.ClusterDefRef); err != nil { - return opsRequestPhase, 0, err - } - } - componentNameMap := opsRequest.GetComponentNameSet() - // if no specified components, we should check the all components phase of cluster. - if len(componentNameMap) == 0 { - checkAllClusterComponent = true - } - oldOpsRequest := opsRequest.DeepCopy() - patch := client.MergeFrom(oldOpsRequest) - if opsRequest.Status.Components == nil { - opsRequest.Status.Components = map[string]appsv1alpha1.OpsRequestComponentStatus{} - } - if syncOverrideBy != nil { - if err = syncOverrideBy(reqCtx, cli, opsRes); err != nil { - return "", 0, nil - } - } - opsIsCompleted := opsRequestHasProcessed(reqCtx, cli, *opsRes) - for k, v := range opsRes.Cluster.Status.Components { - if _, ok = componentNameMap[k]; !ok && !checkAllClusterComponent { - continue - } - var compStatus appsv1alpha1.OpsRequestComponentStatus - if compStatus, ok = opsRequest.Status.Components[k]; !ok { - compStatus = appsv1alpha1.OpsRequestComponentStatus{} - } - lastFailedTime := compStatus.LastFailedTime - if isFailedOrAbnormal(v.Phase) { - isFailed = true - if lastFailedTime.IsZero() { - lastFailedTime = metav1.Now() - } - if time.Now().Before(lastFailedTime.Add(componentFailedTimeout)) { - requeueTimeAfterFailed = componentFailedTimeout - time.Since(lastFailedTime.Time) - } - } else if !lastFailedTime.IsZero() { - // reset lastFailedTime if component is not failed - lastFailedTime = metav1.Time{} - } - if compStatus.Phase != v.Phase { - compStatus.Phase = v.Phase - compStatus.LastFailedTime = lastFailedTime - } - clusterComponent := opsRes.Cluster.Spec.GetComponentByName(k) - var componentDefinition *appsv1alpha1.ComponentDefinition - if clusterComponent.ComponentDef != "" { - componentDefinition, err = intctrlcomp.GetCompDefinition(reqCtx, cli, opsRes.Cluster, k) - if err != nil { - return opsRequestPhase, 0, err - } - } - expectCount, completedCount, err := handleStatusProgress(reqCtx, cli, opsRes, progressResource{ - opsMessageKey: opsMessageKey, - clusterComponent: clusterComponent, - clusterDef: clusterDef, - componentDef: componentDefinition, - opsIsCompleted: opsIsCompleted, - }, &compStatus) - if err != nil { - if intctrlutil.IsTargetError(err, intctrlutil.ErrorWaitCacheRefresh) { - return opsRequestPhase, time.Second, nil - } - return opsRequestPhase, 0, err - } - expectProgressCount += expectCount - completedProgressCount += completedCount - opsRequest.Status.Components[k] = compStatus - } - opsRequest.Status.Progress = fmt.Sprintf("%d/%d", completedProgressCount, expectProgressCount) - if !reflect.DeepEqual(opsRequest.Status, oldOpsRequest.Status) { - if err = cli.Status().Patch(reqCtx.Ctx, opsRequest, patch); err != nil { - return opsRequestPhase, 0, err - } - } - // check if the cluster has applied the changes of the opsRequest and wait for the cluster to finish processing the ops. - if !opsIsCompleted { - return opsRequestPhase, 0, nil - } - - if isFailed { - if requeueTimeAfterFailed != 0 { - // component failure may be temporary, waiting for component failure timeout. - return opsRequestPhase, requeueTimeAfterFailed, nil - } - return appsv1alpha1.OpsFailedPhase, 0, nil - } - if completedProgressCount != expectProgressCount { - return opsRequestPhase, time.Second, nil - } - return appsv1alpha1.OpsSucceedPhase, 0, nil -} - -// opsRequestHasProcessed checks if the opsRequest has been processed. -func opsRequestHasProcessed(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes OpsResource) bool { - if opsRes.ToClusterPhase == opsRes.Cluster.Status.Phase { - return false - } - // if all pods of all components are with latest revision, ops has processed - itsList := &workloads.InstanceSetList{} - if err := cli.List(reqCtx.Ctx, itsList, - client.InNamespace(opsRes.Cluster.Namespace), - client.MatchingLabels{constant.AppInstanceLabelKey: opsRes.Cluster.Name}); err != nil { - return false - } - for _, its := range itsList.Items { - isLatestRevision, err := intctrlcomp.IsComponentPodsWithLatestRevision(reqCtx.Ctx, cli, opsRes.Cluster, &its) - if err != nil { - return false - } - if !isLatestRevision { - return false - } - } - return true +func isComponentCompleted(phase appsv1alpha1.ClusterComponentPhase) bool { + return isFailedOrAbnormal(phase) || phase == appsv1alpha1.RunningClusterCompPhase } // getClusterDefByName gets the ClusterDefinition object by the name. @@ -355,35 +212,6 @@ func updateReconfigureStatusByCM(reconfiguringStatus *appsv1alpha1.Reconfiguring return handleReconfigureStatus(cmStatus) } -func getTargetResourcesOfLastComponent(lastConfiguration appsv1alpha1.LastConfiguration, compName string, resourceKey appsv1alpha1.ComponentResourceKey) []string { - lastComponentConfigs := lastConfiguration.Components[compName] - return lastComponentConfigs.TargetResources[resourceKey] -} - -// cancelComponentOps the common function to cancel th opsRequest which updates the component attributes. -func cancelComponentOps(ctx context.Context, - cli client.Client, - opsRes *OpsResource, - updateComp func(lastConfig *appsv1alpha1.LastComponentConfiguration, comp *appsv1alpha1.ClusterComponentSpec) error) error { - opsRequest := opsRes.OpsRequest - lastCompInfos := opsRequest.Status.LastConfiguration.Components - if lastCompInfos == nil { - return nil - } - for index, comp := range opsRes.Cluster.Spec.ComponentSpecs { - lastConfig, ok := lastCompInfos[comp.Name] - if !ok { - continue - } - if err := updateComp(&lastConfig, &comp); err != nil { - return err - } - opsRes.Cluster.Spec.ComponentSpecs[index] = comp - lastCompInfos[comp.Name] = lastConfig - } - return cli.Update(ctx, opsRes.Cluster) -} - // validateOpsWaitingPhase validates whether the current cluster phase is expected, and whether the waiting time exceeds the limit. // only requests with `Pending` phase will be validated. func validateOpsWaitingPhase(cluster *appsv1alpha1.Cluster, ops *appsv1alpha1.OpsRequest, opsBehaviour OpsBehaviour) error { @@ -429,11 +257,11 @@ func getRunningOpsNamesWithSameKind(cluster *appsv1alpha1.Cluster, types ...apps // getRunningOpsRequestWithSameKind gets the running opsRequests with the same kind. func getRunningOpsRequestsWithSameKind(reqCtx intctrlutil.RequestCtx, cli client.Client, cluster *appsv1alpha1.Cluster, types ...appsv1alpha1.OpsType) ([]*appsv1alpha1.OpsRequest, error) { - runningVScaleOps, err := getRunningOpsNamesWithSameKind(cluster, types...) + runningOps, err := getRunningOpsNamesWithSameKind(cluster, types...) if err != nil { return nil, err } - runningVScaleOpsLen := len(runningVScaleOps) + runningVScaleOpsLen := len(runningOps) if runningVScaleOpsLen == 1 { // If there are no concurrent executions opsRequests of the same type, return return nil, nil @@ -443,7 +271,7 @@ func getRunningOpsRequestsWithSameKind(reqCtx intctrlutil.RequestCtx, cli client var runningOpsRequests []*appsv1alpha1.OpsRequest for i := runningVScaleOpsLen - 1; i >= 0; i-- { ops := &appsv1alpha1.OpsRequest{} - if err = cli.Get(reqCtx.Ctx, client.ObjectKey{Name: runningVScaleOps[i], Namespace: cluster.Namespace}, ops); err != nil { + if err = cli.Get(reqCtx.Ctx, client.ObjectKey{Name: runningOps[i], Namespace: cluster.Namespace}, ops); err != nil { return nil, err } if ops.Status.Phase == appsv1alpha1.OpsRunningPhase { diff --git a/controllers/apps/operations/ops_util_test.go b/controllers/apps/operations/ops_util_test.go index 6e1da4f6f59..12f86b5754a 100644 --- a/controllers/apps/operations/ops_util_test.go +++ b/controllers/apps/operations/ops_util_test.go @@ -93,7 +93,19 @@ var _ = Describe("OpsUtil functions", func() { By("expect for opsRequest is running") reqCtx := intctrlutil.RequestCtx{Ctx: ctx} - opsPhase, _, err := reconcileActionWithComponentOps(reqCtx, k8sClient, opsRes, "test", syncOverrideByOpsForScaleReplicas, handleComponentStatusProgress) + compOpsHelper := newComponentOpsHelper(opsRes.OpsRequest.Spec.HorizontalScalingList) + + hs := horizontalScalingOpsHandler{} + handleComponentProgress := func( + reqCtx intctrlutil.RequestCtx, + cli client.Client, + opsRes *OpsResource, + pgRes progressResource, + compStatus *appsv1alpha1.OpsRequestComponentStatus) (int32, int32, error) { + return handleComponentProgressForScalingReplicas(reqCtx, cli, opsRes, pgRes, compStatus, hs.getExpectReplicas) + } + opsPhase, _, err := compOpsHelper.reconcileActionWithComponentOps(reqCtx, k8sClient, opsRes, + "test", syncOverrideByOpsForScaleReplicas, handleComponentProgress) Expect(err).Should(BeNil()) Expect(opsPhase).Should(Equal(appsv1alpha1.OpsRunningPhase)) @@ -101,10 +113,9 @@ var _ = Describe("OpsUtil functions", func() { compStatus := opsRes.OpsRequest.Status.Components[consensusComp] compStatus.LastFailedTime = metav1.Time{Time: compStatus.LastFailedTime.Add(-1 * componentFailedTimeout).Add(-1 * time.Second)} opsRes.OpsRequest.Status.Components[consensusComp] = compStatus - opsPhase, _, err = reconcileActionWithComponentOps(reqCtx, k8sClient, opsRes, "test", syncOverrideByOpsForScaleReplicas, handleComponentStatusProgress) + opsPhase, _, err = compOpsHelper.reconcileActionWithComponentOps(reqCtx, k8sClient, opsRes, "test", syncOverrideByOpsForScaleReplicas, handleComponentProgress) Expect(err).Should(BeNil()) Expect(opsPhase).Should(Equal(appsv1alpha1.OpsFailedPhase)) - }) It("Test opsRequest Queue functions", func() { diff --git a/controllers/apps/operations/restart.go b/controllers/apps/operations/restart.go index a3585557feb..b0d53c626d8 100644 --- a/controllers/apps/operations/restart.go +++ b/controllers/apps/operations/restart.go @@ -35,7 +35,9 @@ import ( intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) -type restartOpsHandler struct{} +type restartOpsHandler struct { + compOpsHelper componentOpsHelper +} var _ OpsHandler = restartOpsHandler{} @@ -62,14 +64,13 @@ func (r restartOpsHandler) Action(reqCtx intctrlutil.RequestCtx, cli client.Clie if opsRes.OpsRequest.Status.StartTimestamp.IsZero() { return fmt.Errorf("status.startTimestamp can not be null") } - componentNameMap := opsRes.OpsRequest.Spec.GetRestartComponentNameSet() + r.compOpsHelper = newComponentOpsHelper(opsRes.OpsRequest.Spec.RestartList) componentKindList := []client.ObjectList{ - &appv1.DeploymentList{}, &appv1.StatefulSetList{}, &workloads.InstanceSetList{}, } for _, objectList := range componentKindList { - if err := restartComponent(reqCtx, cli, opsRes, componentNameMap, objectList); err != nil { + if err := r.restartComponent(reqCtx, cli, opsRes, objectList); err != nil { return err } } @@ -79,7 +80,16 @@ func (r restartOpsHandler) Action(reqCtx intctrlutil.RequestCtx, cli client.Clie // ReconcileAction will be performed when action is done and loops till OpsRequest.status.phase is Succeed/Failed. // the Reconcile function for restart opsRequest. func (r restartOpsHandler) ReconcileAction(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) (appsv1alpha1.OpsPhase, time.Duration, error) { - return reconcileActionWithComponentOps(reqCtx, cli, opsRes, "restart", nil, handleComponentStatusProgress) + compOpsHelper := newComponentOpsHelper(opsRes.OpsRequest.Spec.RestartList) + handleRestartProgress := func(reqCtx intctrlutil.RequestCtx, + cli client.Client, + opsRes *OpsResource, + pgRes progressResource, + compStatus *appsv1alpha1.OpsRequestComponentStatus) (expectProgressCount int32, completedCount int32, err error) { + return handleComponentStatusProgress(reqCtx, cli, opsRes, pgRes, compStatus, r.podApplyCompOps) + } + return compOpsHelper.reconcileActionWithComponentOps(reqCtx, cli, opsRes, + "restart", nil, handleRestartProgress) } // SaveLastConfiguration this operation only restart the pods of the component, no changes for Cluster.spec. @@ -88,8 +98,16 @@ func (r restartOpsHandler) SaveLastConfiguration(reqCtx intctrlutil.RequestCtx, return nil } +func (r restartOpsHandler) podApplyCompOps( + pod *corev1.Pod, + compOps ComponentOpsInteface, + opsStartTime metav1.Time, + templateName string) bool { + return !pod.CreationTimestamp.Before(&opsStartTime) +} + // restartStatefulSet restarts statefulSet workload -func restartComponent(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource, componentNameMap map[string]struct{}, objList client.ObjectList) error { +func (r restartOpsHandler) restartComponent(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource, objList client.ObjectList) error { if err := cli.List(reqCtx.Ctx, objList, client.InNamespace(opsRes.Cluster.Namespace), client.MatchingLabels{constant.AppInstanceLabelKey: opsRes.Cluster.Name}); err != nil { @@ -102,7 +120,7 @@ func restartComponent(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes * // get the underlying object object := items.Index(i).Addr().Interface().(client.Object) template := items.Index(i).FieldByName("Spec").FieldByName("Template").Addr().Interface().(*corev1.PodTemplateSpec) - if isRestarted(opsRes, object, componentNameMap, template) { + if r.isRestarted(opsRes, object, template) { continue } if err := cli.Update(reqCtx.Ctx, object); err != nil { @@ -113,10 +131,17 @@ func restartComponent(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes * } // isRestarted checks whether the component has been restarted -func isRestarted(opsRes *OpsResource, object client.Object, componentNameMap map[string]struct{}, podTemplate *corev1.PodTemplateSpec) bool { +func (r restartOpsHandler) isRestarted(opsRes *OpsResource, object client.Object, podTemplate *corev1.PodTemplateSpec) bool { cName := object.GetLabels()[constant.KBAppComponentLabelKey] - if _, ok := componentNameMap[cName]; !ok { - return true + shardingName := object.GetLabels()[constant.KBAppShardingNameLabelKey] + if shardingName != "" { + if _, ok := r.compOpsHelper.componentOpsSet[getShardingKey(shardingName)]; !ok { + return true + } + } else { + if _, ok := r.compOpsHelper.componentOpsSet[cName]; !ok { + return true + } } if podTemplate.Annotations == nil { podTemplate.Annotations = map[string]string{} diff --git a/controllers/apps/operations/start.go b/controllers/apps/operations/start.go index 12e064077f6..e9ea10d1950 100644 --- a/controllers/apps/operations/start.go +++ b/controllers/apps/operations/start.go @@ -24,7 +24,6 @@ import ( "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" @@ -58,18 +57,35 @@ func (start StartOpsHandler) Action(reqCtx intctrlutil.RequestCtx, cli client.Cl cluster := opsRes.Cluster componentReplicasMap, err := getComponentReplicasSnapshot(cluster.Annotations) if err != nil { - return err + return intctrlutil.NewFatalError(err.Error()) } - for i, v := range cluster.Spec.ComponentSpecs { - replicasOfSnapshot := componentReplicasMap[v.Name] + applyReplicas := func(compSpec *appsv1alpha1.ClusterComponentSpec, shardingName string) { + componentKey := getComponentKeyForStartSnapshot(shardingName, compSpec.Name, "") + replicasOfSnapshot := componentReplicasMap[componentKey] if replicasOfSnapshot == 0 { - continue + return } // only reset the component whose replicas number is 0 - if v.Replicas == 0 { - cluster.Spec.ComponentSpecs[i].Replicas = replicasOfSnapshot + if compSpec.Replicas == 0 { + compSpec.Replicas = replicasOfSnapshot + for i := range compSpec.Instances { + componentKey = getComponentKeyForStartSnapshot(shardingName, compSpec.Name, compSpec.Instances[i].Name) + replicasOfSnapshot = componentReplicasMap[componentKey] + if replicasOfSnapshot == 0 { + continue + } + compSpec.Instances[i].Replicas = &replicasOfSnapshot + } } } + for i := range cluster.Spec.ComponentSpecs { + compSpec := &cluster.Spec.ComponentSpecs[i] + applyReplicas(compSpec, "") + } + for i := range cluster.Spec.ShardingSpecs { + shardingSpec := &cluster.Spec.ShardingSpecs[i] + applyReplicas(&shardingSpec.Template, shardingSpec.Name) + } // delete the replicas snapshot of components from the cluster. delete(cluster.Annotations, constant.SnapShotForStartAnnotationKey) return cli.Update(reqCtx.Ctx, cluster) @@ -78,13 +94,14 @@ func (start StartOpsHandler) Action(reqCtx intctrlutil.RequestCtx, cli client.Cl // ReconcileAction will be performed when action is done and loops till OpsRequest.status.phase is Succeed/Failed. // the Reconcile function for start opsRequest. func (start StartOpsHandler) ReconcileAction(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) (appsv1alpha1.OpsPhase, time.Duration, error) { - getExpectReplicas := func(opsRequest *appsv1alpha1.OpsRequest, componentName string) *int32 { + getExpectReplicas := func(opsRequest *appsv1alpha1.OpsRequest, shardingName, componentName string) *int32 { compStatus := opsRequest.Status.Components[componentName] if compStatus.OverrideBy != nil { return compStatus.OverrideBy.Replicas } componentReplicasMap, _ := getComponentReplicasSnapshot(opsRequest.Annotations) - replicas, ok := componentReplicasMap[componentName] + componentKey := getComponentKeyForStartSnapshot(shardingName, componentName, "") + replicas, ok := componentReplicasMap[componentKey] if !ok { return nil } @@ -98,32 +115,20 @@ func (start StartOpsHandler) ReconcileAction(reqCtx intctrlutil.RequestCtx, cli compStatus *appsv1alpha1.OpsRequestComponentStatus) (int32, int32, error) { return handleComponentProgressForScalingReplicas(reqCtx, cli, opsRes, pgRes, compStatus, getExpectReplicas) } - return reconcileActionWithComponentOps(reqCtx, cli, opsRes, "start", syncOverrideByOpsForScaleReplicas, handleComponentProgress) + compOpsHelper := newComponentOpsHelper([]appsv1alpha1.ComponentOps{}) + return compOpsHelper.reconcileActionWithComponentOps(reqCtx, cli, opsRes, "start", syncOverrideByOpsForScaleReplicas, handleComponentProgress) } // SaveLastConfiguration records last configuration to the OpsRequest.status.lastConfiguration func (start StartOpsHandler) SaveLastConfiguration(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) error { - opsRequest := opsRes.OpsRequest - lastComponentInfo := map[string]appsv1alpha1.LastComponentConfiguration{} componentReplicasMap, err := getComponentReplicasSnapshot(opsRes.Cluster.Annotations) if err != nil { - return err + return intctrlutil.NewFatalError(err.Error()) } if err = start.setOpsAnnotation(reqCtx, cli, opsRes, componentReplicasMap); err != nil { return err } - for _, v := range opsRes.Cluster.Spec.ComponentSpecs { - replicasOfSnapshot := componentReplicasMap[v.Name] - if replicasOfSnapshot == 0 { - continue - } - if v.Replicas == 0 { - lastComponentInfo[v.Name] = appsv1alpha1.LastComponentConfiguration{ - Replicas: pointer.Int32(v.Replicas), - } - } - } - opsRequest.Status.LastConfiguration.Components = lastComponentInfo + saveLastConfigurationForStopAndStart(opsRes) return nil } diff --git a/controllers/apps/operations/stop.go b/controllers/apps/operations/stop.go index 7b56c286b9f..7afe934345b 100644 --- a/controllers/apps/operations/stop.go +++ b/controllers/apps/operations/stop.go @@ -56,16 +56,28 @@ func (stop StopOpsHandler) ActionStartedCondition(reqCtx intctrlutil.RequestCtx, // Action modifies Cluster.spec.components[*].replicas from the opsRequest func (stop StopOpsHandler) Action(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) error { var ( - expectReplicas = int32(0) componentReplicasMap = map[string]int32{} cluster = opsRes.Cluster ) if _, ok := cluster.Annotations[constant.SnapShotForStartAnnotationKey]; ok { return nil } - for i, v := range cluster.Spec.ComponentSpecs { - componentReplicasMap[v.Name] = v.Replicas - cluster.Spec.ComponentSpecs[i].Replicas = expectReplicas + setReplicas := func(compSpec *appsv1alpha1.ClusterComponentSpec, shardingName string) { + compKey := getComponentKeyForStartSnapshot(shardingName, compSpec.Name, "") + componentReplicasMap[compKey] = compSpec.Replicas + expectReplicas := int32(0) + compSpec.Replicas = expectReplicas + for i := range compSpec.Instances { + compKey = getComponentKeyForStartSnapshot(shardingName, compSpec.Name, compSpec.Instances[i].Name) + componentReplicasMap[compKey] = intctrlutil.TemplateReplicas(compSpec.Instances[i]) + compSpec.Instances[i].Replicas = &expectReplicas + } + } + for i := range cluster.Spec.ComponentSpecs { + setReplicas(&cluster.Spec.ComponentSpecs[i], "") + } + for i, v := range cluster.Spec.ShardingSpecs { + setReplicas(&cluster.Spec.ShardingSpecs[i].Template, v.Name) } componentReplicasSnapshot, err := json.Marshal(componentReplicasMap) if err != nil { @@ -82,7 +94,7 @@ func (stop StopOpsHandler) Action(reqCtx intctrlutil.RequestCtx, cli client.Clie // ReconcileAction will be performed when action is done and loops till OpsRequest.status.phase is Succeed/Failed. // the Reconcile function for stop opsRequest. func (stop StopOpsHandler) ReconcileAction(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) (appsv1alpha1.OpsPhase, time.Duration, error) { - getExpectReplicas := func(opsRequest *appsv1alpha1.OpsRequest, componentName string) *int32 { + getExpectReplicas := func(opsRequest *appsv1alpha1.OpsRequest, shardingName, componentName string) *int32 { compStatus := opsRequest.Status.Components[componentName] if compStatus.OverrideBy != nil { return compStatus.OverrideBy.Replicas @@ -100,28 +112,44 @@ func (stop StopOpsHandler) ReconcileAction(reqCtx intctrlutil.RequestCtx, cli cl } return expectProgressCount, completedCount, nil } - return reconcileActionWithComponentOps(reqCtx, cli, opsRes, "stop", syncOverrideByOpsForScaleReplicas, handleComponentProgress) + compOpsHelper := newComponentOpsHelper([]appsv1alpha1.ComponentOps{}) + return compOpsHelper.reconcileActionWithComponentOps(reqCtx, cli, opsRes, "stop", syncOverrideByOpsForScaleReplicas, handleComponentProgress) } // SaveLastConfiguration records last configuration to the OpsRequest.status.lastConfiguration func (stop StopOpsHandler) SaveLastConfiguration(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) error { - opsRequest := opsRes.OpsRequest - lastComponentInfo := map[string]appsv1alpha1.LastComponentConfiguration{} - for _, v := range opsRes.Cluster.Spec.ComponentSpecs { - if v.Replicas != 0 { - podNames, err := getCompPodNamesBeforeScaleDownReplicas(reqCtx, cli, *opsRes.Cluster, v.Name) - if err != nil { - return err - } - copyReplicas := v.Replicas - lastComponentInfo[v.Name] = appsv1alpha1.LastComponentConfiguration{ - Replicas: ©Replicas, - TargetResources: map[appsv1alpha1.ComponentResourceKey][]string{ - appsv1alpha1.PodsCompResourceKey: podNames, - }, - } + saveLastConfigurationForStopAndStart(opsRes) + return nil +} + +func getComponentKeyForStartSnapshot(shardingName, compName, templateName string) string { + key := getCompOpsKey(shardingName, compName) + if templateName != "" { + key += "." + templateName + } + return key +} + +func saveLastConfigurationForStopAndStart(opsRes *OpsResource) { + getLastComponentConfiguration := func(compSpec appsv1alpha1.ClusterComponentSpec) appsv1alpha1.LastComponentConfiguration { + var instances []appsv1alpha1.InstanceTemplate + for _, v := range compSpec.Instances { + instances = append(instances, appsv1alpha1.InstanceTemplate{ + Name: v.Name, + Replicas: v.Replicas, + }) + } + return appsv1alpha1.LastComponentConfiguration{ + Replicas: pointer.Int32(compSpec.Replicas), + Instances: instances, } } - opsRequest.Status.LastConfiguration.Components = lastComponentInfo - return nil + lastConfiguration := &opsRes.OpsRequest.Status.LastConfiguration + lastConfiguration.Components = map[string]appsv1alpha1.LastComponentConfiguration{} + for _, v := range opsRes.Cluster.Spec.ComponentSpecs { + lastConfiguration.Components[v.Name] = getLastComponentConfiguration(v) + } + for _, v := range opsRes.Cluster.Spec.ShardingSpecs { + lastConfiguration.Components[getShardingKey(v.Name)] = getLastComponentConfiguration(v.Template) + } } diff --git a/controllers/apps/operations/type.go b/controllers/apps/operations/type.go index 28c6e61683a..9a92b849a3a 100644 --- a/controllers/apps/operations/type.go +++ b/controllers/apps/operations/type.go @@ -98,9 +98,17 @@ type OpsManager struct { type progressResource struct { // opsMessageKey progress message key of specified OpsType, it is a verb and will form the message of progressDetail // such as "vertical scale" of verticalScaling OpsRequest. - opsMessageKey string - clusterComponent *appsv1alpha1.ClusterComponentSpec - clusterDef *appsv1alpha1.ClusterDefinition - componentDef *appsv1alpha1.ComponentDefinition - opsIsCompleted bool + opsMessageKey string + // cluster component name. By default, it is the componentSpec.name. + // but if it is a sharding component, the componentName is generated randomly. + fullComponentName string + clusterComponent *appsv1alpha1.ClusterComponentSpec + clusterDef *appsv1alpha1.ClusterDefinition + componentDef *appsv1alpha1.ComponentDefinition + // record which pods need to updated during this operation. + updatedPodSet map[string]string + compOps ComponentOpsInteface + // checks if it needs to wait the component to complete. + // if only updates a part of pods, set it to false. + noWaitComponentCompleted bool } diff --git a/controllers/apps/operations/upgrade.go b/controllers/apps/operations/upgrade.go index 8f8599779d9..a94392a2649 100644 --- a/controllers/apps/operations/upgrade.go +++ b/controllers/apps/operations/upgrade.go @@ -24,6 +24,7 @@ import ( "reflect" "time" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -62,7 +63,24 @@ func (u upgradeOpsHandler) Action(reqCtx intctrlutil.RequestCtx, cli client.Clie // ReconcileAction will be performed when action is done and loops till OpsRequest.status.phase is Succeed/Failed. // the Reconcile function for upgrade opsRequest. func (u upgradeOpsHandler) ReconcileAction(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) (appsv1alpha1.OpsPhase, time.Duration, error) { - return reconcileActionWithComponentOps(reqCtx, cli, opsRes, "upgrade", nil, handleComponentStatusProgress) + compOpsHelper := newComponentOpsHelper([]appsv1alpha1.ComponentOps{}) + handleUpgradeProgress := func(reqCtx intctrlutil.RequestCtx, + cli client.Client, + opsRes *OpsResource, + pgRes progressResource, + compStatus *appsv1alpha1.OpsRequestComponentStatus) (expectProgressCount int32, completedCount int32, err error) { + return handleComponentStatusProgress(reqCtx, cli, opsRes, pgRes, compStatus, u.podApplyCompOps) + } + return compOpsHelper.reconcileActionWithComponentOps(reqCtx, cli, opsRes, "upgrade", + nil, handleUpgradeProgress) +} + +func (u upgradeOpsHandler) podApplyCompOps( + pod *corev1.Pod, + compOps ComponentOpsInteface, + opsStartTime metav1.Time, + templateName string) bool { + return !pod.CreationTimestamp.Before(&opsStartTime) } // SaveLastConfiguration records last configuration to the OpsRequest.status.lastConfiguration diff --git a/controllers/apps/operations/vertical_scaling.go b/controllers/apps/operations/vertical_scaling.go index f86b55477ca..86a02acdbf6 100644 --- a/controllers/apps/operations/vertical_scaling.go +++ b/controllers/apps/operations/vertical_scaling.go @@ -27,6 +27,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/constant" + "github.com/apecloud/kubeblocks/pkg/controller/instanceset" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) @@ -57,22 +59,112 @@ func (vs verticalScalingHandler) ActionStartedCondition(reqCtx intctrlutil.Reque // Action modifies cluster component resources according to // the definition of opsRequest with spec.componentNames and spec.componentOps.verticalScaling func (vs verticalScalingHandler) Action(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) error { - verticalScalingMap := opsRes.OpsRequest.Spec.ToVerticalScalingListToMap() - for index, component := range opsRes.Cluster.Spec.ComponentSpecs { - verticalScaling, ok := verticalScalingMap[component.Name] - if !ok { - continue + applyVerticalScaling := func(compSpec *appsv1alpha1.ClusterComponentSpec, obj ComponentOpsInteface) { + verticalScaling := obj.(appsv1alpha1.VerticalScaling) + if vs.verticalScalingComp(verticalScaling) { + compSpec.Resources = verticalScaling.ResourceRequirements + } + for _, v := range verticalScaling.Instances { + for i := range compSpec.Instances { + if compSpec.Instances[i].Name == v.Name { + compSpec.Instances[i].Resources = &v.ResourceRequirements + break + } + } } - component.Resources = verticalScaling.ResourceRequirements - opsRes.Cluster.Spec.ComponentSpecs[index] = component } + compOpsSet := newComponentOpsHelper(opsRes.OpsRequest.Spec.VerticalScalingList) + compOpsSet.updateClusterComponentsAndShardings(opsRes.Cluster, applyVerticalScaling) return cli.Update(reqCtx.Ctx, opsRes.Cluster) } // ReconcileAction will be performed when action is done and loops till OpsRequest.status.phase is Succeed/Failed. // the Reconcile function for vertical scaling opsRequest. func (vs verticalScalingHandler) ReconcileAction(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) (appsv1alpha1.OpsPhase, time.Duration, error) { - return reconcileActionWithComponentOps(reqCtx, cli, opsRes, "vertical scale", vs.syncOverrideByOps, handleComponentStatusProgress) + compOpsHelper := newComponentOpsHelper(opsRes.OpsRequest.Spec.VerticalScalingList) + handleComponentStatusProgressForVS := func( + reqCtx intctrlutil.RequestCtx, + cli client.Client, + opsRes *OpsResource, + pgRes progressResource, + compStatus *appsv1alpha1.OpsRequestComponentStatus) (expectProgressCount int32, completedCount int32, err error) { + verticalScaling := pgRes.compOps.(appsv1alpha1.VerticalScaling) + if len(pgRes.clusterComponent.Instances) != 0 { + // obtain the pods which should be updated. + updatedPodSet := map[string]string{} + insMap := map[string]int32{} + workloadName := constant.GenerateWorkloadNamePattern(opsRes.Cluster.Name, pgRes.fullComponentName) + templateReplicasCnt := int32(0) + for _, template := range pgRes.clusterComponent.Instances { + replicas := intctrlutil.TemplateReplicas(template) + insMap[template.Name] = replicas + templateReplicasCnt += replicas + } + for _, ins := range verticalScaling.Instances { + replicas, ok := insMap[ins.Name] + if !ok { + continue + } + templatePodNames := instanceset.GenerateInstanceNamesFromTemplate(workloadName, ins.Name, replicas, pgRes.clusterComponent.OfflineInstances) + for _, podName := range templatePodNames { + updatedPodSet[podName] = ins.Name + } + break + } + if vs.verticalScalingComp(verticalScaling) && templateReplicasCnt < pgRes.clusterComponent.Replicas { + podNames := instanceset.GenerateInstanceNamesFromTemplate(workloadName, "", pgRes.clusterComponent.Replicas-templateReplicasCnt, pgRes.clusterComponent.OfflineInstances) + for _, podName := range podNames { + updatedPodSet[podName] = "" + } + } else { + pgRes.noWaitComponentCompleted = true + } + pgRes.updatedPodSet = updatedPodSet + } + return handleComponentStatusProgress(reqCtx, cli, opsRes, pgRes, compStatus, vs.podApplyCompOps) + } + return compOpsHelper.reconcileActionWithComponentOps(reqCtx, cli, opsRes, "vertical scale", vs.syncOverrideByOps, handleComponentStatusProgressForVS) +} + +func (vs verticalScalingHandler) verticalScalingComp(verticalScaling appsv1alpha1.VerticalScaling) bool { + return len(verticalScaling.Requests) != 0 || len(verticalScaling.Limits) != 0 +} + +func (vs verticalScalingHandler) podApplyCompOps( + pod *corev1.Pod, + compOps ComponentOpsInteface, + opsStartTime metav1.Time, + templateName string) bool { + verticalScaling := compOps.(appsv1alpha1.VerticalScaling) + matchResources := func(podResources, vsResources corev1.ResourceRequirements) bool { + if vsResources.Requests == nil { + vsResources.Requests = corev1.ResourceList{} + } + for resName, resValue := range vsResources.Limits { + requestResource := vsResources.Requests[resName] + if requestResource.IsZero() { + vsResources.Requests[resName] = resValue + } + if !resValue.Equal(podResources.Limits[resName]) { + return false + } + } + for resName, resValue := range vsResources.Requests { + if !resValue.Equal(podResources.Requests[resName]) { + return false + } + } + return true + } + if templateName == "" { + return matchResources(pod.Spec.Containers[0].Resources, verticalScaling.ResourceRequirements) + } + for _, insTpl := range verticalScaling.Instances { + if insTpl.Name == templateName { + return matchResources(pod.Spec.Containers[0].Resources, verticalScaling.ResourceRequirements) + } + } + return false } func (vs verticalScalingHandler) syncOverrideByOps(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) error { @@ -117,18 +209,27 @@ func (vs verticalScalingHandler) syncOverrideByOps(reqCtx intctrlutil.RequestCtx // SaveLastConfiguration records last configuration to the OpsRequest.status.lastConfiguration func (vs verticalScalingHandler) SaveLastConfiguration(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) error { - componentNameSet := opsRes.OpsRequest.GetComponentNameSet() - lastComponentInfo := map[string]appsv1alpha1.LastComponentConfiguration{} - for _, v := range opsRes.Cluster.Spec.ComponentSpecs { - if _, ok := componentNameSet[v.Name]; !ok { - continue + compOpsHelper := newComponentOpsHelper(opsRes.OpsRequest.Spec.VerticalScalingList) + compOpsHelper.saveLastConfigurations(opsRes, func(compSpec appsv1alpha1.ClusterComponentSpec, comOps ComponentOpsInteface) appsv1alpha1.LastComponentConfiguration { + verticalScaling := comOps.(appsv1alpha1.VerticalScaling) + var instanceTemplates []appsv1alpha1.InstanceTemplate + for _, vIns := range verticalScaling.Instances { + for _, compIns := range compSpec.Instances { + if vIns.Name != compIns.Name { + continue + } + instanceTemplates = append(instanceTemplates, appsv1alpha1.InstanceTemplate{ + Name: compIns.Name, + Resources: compIns.Resources, + }) + break + } } - lastConfiguration := appsv1alpha1.LastComponentConfiguration{ - ResourceRequirements: v.Resources, + return appsv1alpha1.LastComponentConfiguration{ + ResourceRequirements: compSpec.Resources, + Instances: instanceTemplates, } - lastComponentInfo[v.Name] = lastConfiguration - } - opsRes.OpsRequest.Status.LastConfiguration.Components = lastComponentInfo + }) return nil } @@ -139,8 +240,17 @@ func (vs verticalScalingHandler) Cancel(reqCxt intctrlutil.RequestCtx, cli clien return intctrlutil.NewErrorf(intctrlutil.ErrorIgnoreCancel, `can not cancel the opsRequest due to another VerticalScaling opsRequest "%s" is running`, v.OverrideBy.OpsName) } } - return cancelComponentOps(reqCxt.Ctx, cli, opsRes, func(lastConfig *appsv1alpha1.LastComponentConfiguration, comp *appsv1alpha1.ClusterComponentSpec) error { + compOpsHelper := newComponentOpsHelper(opsRes.OpsRequest.Spec.VerticalScalingList) + return compOpsHelper.cancelComponentOps(reqCxt.Ctx, cli, opsRes, func(lastConfig *appsv1alpha1.LastComponentConfiguration, comp *appsv1alpha1.ClusterComponentSpec) { comp.Resources = lastConfig.ResourceRequirements - return nil + for _, lastIns := range lastConfig.Instances { + for i := range comp.Instances { + if comp.Instances[i].Name != lastIns.Name { + continue + } + comp.Instances[i].Resources = lastIns.Resources + break + } + } }) } diff --git a/controllers/apps/operations/vertical_scaling_test.go b/controllers/apps/operations/vertical_scaling_test.go index 52d8d955c38..4f07ab9e54e 100644 --- a/controllers/apps/operations/vertical_scaling_test.go +++ b/controllers/apps/operations/vertical_scaling_test.go @@ -150,7 +150,8 @@ var _ = Describe("VerticalScaling OpsRequest", func() { pod.Kind = constant.PodKind testk8s.MockPodIsTerminating(ctx, testCtx, pod) testk8s.RemovePodFinalizer(ctx, testCtx, pod) - testapps.MockInstanceSetPod(&testCtx, nil, clusterName, consensusComp, pod.Name, "leader", "ReadWrite") + testapps.MockInstanceSetPod(&testCtx, nil, clusterName, consensusComp, + pod.Name, "leader", "ReadWrite", ops.Spec.VerticalScalingList[0].ResourceRequirements) } By("mock podList[0] rolling update successfully by re-creating it") @@ -162,7 +163,7 @@ var _ = Describe("VerticalScaling OpsRequest", func() { By("the progress status of pod[0] should be Succeed ") progressDetails := opsRes.OpsRequest.Status.Components[consensusComp].ProgressDetails - progressDetail := findStatusProgressDetail(progressDetails, getProgressObjectKey("", podList[0].Name)) + progressDetail := findStatusProgressDetail(progressDetails, getProgressObjectKey(constant.PodKind, podList[0].Name)) Expect(progressDetail.Status).Should(Equal(appsv1alpha1.SucceedProgressStatus)) By("cancel verticalScaling opsRequest") @@ -182,7 +183,7 @@ var _ = Describe("VerticalScaling OpsRequest", func() { Expect(opsRequest.Status.Progress).Should(Equal("1/1")) progressDetails = opsRequest.Status.Components[consensusComp].ProgressDetails Expect(len(progressDetails)).Should(Equal(1)) - progressDetail = findStatusProgressDetail(progressDetails, getProgressObjectKey("", podList[0].Name)) + progressDetail = findStatusProgressDetail(progressDetails, getProgressObjectKey(constant.PodKind, podList[0].Name)) Expect(progressDetail.Status).Should(Equal(appsv1alpha1.SucceedProgressStatus)) Expect(progressDetail.Message).Should(ContainSubstring("with rollback")) }) diff --git a/controllers/apps/operations/volume_expansion.go b/controllers/apps/operations/volume_expansion.go index 1b2776c326d..03fa2d2216a 100644 --- a/controllers/apps/operations/volume_expansion.go +++ b/controllers/apps/operations/volume_expansion.go @@ -37,7 +37,16 @@ import ( intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) -type volumeExpansionOpsHandler struct{} +type volumeExpansionOpsHandler struct { +} + +type volumeExpansionHelper struct { + compOps ComponentOpsInteface + fullComponentName string + templateName string + vctName string + expectCount int +} var _ OpsHandler = volumeExpansionOpsHandler{} @@ -65,26 +74,32 @@ func (ve volumeExpansionOpsHandler) ActionStartedCondition(reqCtx intctrlutil.Re // Action modifies Cluster.spec.components[*].VolumeClaimTemplates[*].spec.resources func (ve volumeExpansionOpsHandler) Action(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) error { - var ( - volumeExpansionMap = opsRes.OpsRequest.Spec.ToVolumeExpansionListToMap() - volumeExpansionOps appsv1alpha1.VolumeExpansion - ok bool - ) - for index, component := range opsRes.Cluster.Spec.ComponentSpecs { - if volumeExpansionOps, ok = volumeExpansionMap[component.Name]; !ok { - continue + applyVolumeExpansion := func(compSpec *appsv1alpha1.ClusterComponentSpec, obj ComponentOpsInteface) { + setVolumeStorage := func(volumeExpansionVCTs []appsv1alpha1.OpsRequestVolumeClaimTemplate, + targetVCTs []appsv1alpha1.ClusterComponentVolumeClaimTemplate) { + for _, v := range volumeExpansionVCTs { + for i, vct := range targetVCTs { + if vct.Name != v.Name { + continue + } + targetVCTs[i].Spec.Resources.Requests[corev1.ResourceStorage] = v.Storage + } + } } - compSpec := &opsRes.Cluster.Spec.ComponentSpecs[index] - for _, v := range volumeExpansionOps.VolumeClaimTemplates { - for i, vct := range component.VolumeClaimTemplates { - if vct.Name != v.Name { - continue + volumeExpansion := obj.(appsv1alpha1.VolumeExpansion) + setVolumeStorage(volumeExpansion.VolumeClaimTemplates, compSpec.VolumeClaimTemplates) + // update the vct of the instances. + for _, v := range volumeExpansion.Instances { + for i := range compSpec.Instances { + if compSpec.Instances[i].Name == v.Name { + setVolumeStorage(v.VolumeClaimTemplates, compSpec.Instances[i].VolumeClaimTemplates) + break } - compSpec.VolumeClaimTemplates[i]. - Spec.Resources.Requests[corev1.ResourceStorage] = v.Storage } } } + compOpsSet := newComponentOpsHelper(opsRes.OpsRequest.Spec.VolumeExpansionList) + compOpsSet.updateClusterComponentsAndShardings(opsRes.Cluster, applyVolumeExpansion) return cli.Update(reqCtx.Ctx, opsRes.Cluster) } @@ -101,27 +116,84 @@ func (ve volumeExpansionOpsHandler) ReconcileAction(reqCtx intctrlutil.RequestCt succeedProgressCount int completedProgressCount int ) - + getTemplateReplicas := func(templates []appsv1alpha1.InstanceTemplate) int32 { + var replicaCount int32 + for _, v := range templates { + replicaCount += intctrlutil.TemplateReplicas(v) + } + return replicaCount + } patch := client.MergeFrom(opsRequest.DeepCopy()) if opsRequest.Status.Components == nil { ve.initComponentStatus(opsRequest) } - storageMap := ve.getRequestStorageMap(opsRequest) + compOpsHelper := newComponentOpsHelper(opsRes.OpsRequest.Spec.VolumeExpansionList) + storageMap := ve.getRequestStorageMap(opsRequest, compOpsHelper) + var veHelpers []volumeExpansionHelper + setVeHelpers := func(compSpec appsv1alpha1.ClusterComponentSpec, compOps ComponentOpsInteface, fullComponentName string) { + volumeExpansion := compOps.(appsv1alpha1.VolumeExpansion) + if len(volumeExpansion.VolumeClaimTemplates) > 0 { + expectReplicas := compSpec.Replicas - getTemplateReplicas(compSpec.Instances) + for _, vct := range volumeExpansion.VolumeClaimTemplates { + veHelpers = append(veHelpers, volumeExpansionHelper{ + compOps: compOps, + fullComponentName: fullComponentName, + expectCount: int(expectReplicas), + vctName: vct.Name, + }) + } + } + if len(volumeExpansion.Instances) > 0 { + for _, ins := range compSpec.Instances { + for _, vct := range ins.VolumeClaimTemplates { + veHelpers = append(veHelpers, volumeExpansionHelper{ + compOps: compOps, + fullComponentName: fullComponentName, + expectCount: int(intctrlutil.TemplateReplicas(ins)), + vctName: vct.Name, + }) + } + } + } + } + for _, compSpec := range opsRes.Cluster.Spec.ComponentSpecs { + compOps, ok := compOpsHelper.componentOpsSet[compSpec.Name] + if !ok { + continue + } + setVeHelpers(compSpec, compOps, compSpec.Name) + } + for _, shardingSpec := range opsRes.Cluster.Spec.ShardingSpecs { + compOps, ok := compOpsHelper.componentOpsSet[getShardingKey(shardingSpec.Name)] + if !ok { + continue + } + shardingComps, err := intctrlutil.ListShardingComponents(reqCtx.Ctx, cli, opsRes.Cluster, &shardingSpec) + if err != nil { + return opsRequestPhase, 0, err + } + for _, v := range shardingComps { + setVeHelpers(shardingSpec.Template, compOps, v.Labels[constant.KBAppComponentLabelKey]) + } + } // reconcile the status.components. when the volume expansion is successful, // sync the volumeClaimTemplate status and component phase On the OpsRequest and Cluster. - for _, v := range opsRequest.Spec.VolumeExpansionList { - compStatus := opsRequest.Status.Components[v.ComponentName] - for _, vct := range v.VolumeClaimTemplates { - succeedCount, expectCount, completedCount, err := ve.handleVCTExpansionProgress(reqCtx, cli, opsRes, - &compStatus, storageMap, v.ComponentName, vct.Name) - if err != nil { - return "", requeueAfter, err - } - expectProgressCount += expectCount - succeedProgressCount += succeedCount - completedProgressCount += completedCount + for _, veHelper := range veHelpers { + opsCompStatus := compOpsHelper.getOpsComponentAndShardStatus(opsRequest, veHelper.compOps) + key := getComponentVCTKey(veHelper.compOps.GetShardingName(), veHelper.compOps.GetComponentName(), veHelper.templateName, veHelper.vctName) + requestStorage, ok := storageMap[key] + if !ok { + continue + } + succeedCount, completedCount, err := ve.handleVCTExpansionProgress(reqCtx, cli, opsRes, + &opsCompStatus, requestStorage, veHelper) + if err != nil { + return "", requeueAfter, err } - opsRequest.Status.Components[v.ComponentName] = compStatus + expectProgressCount += veHelper.expectCount + succeedProgressCount += succeedCount + completedProgressCount += completedCount + compOpsHelper.setOpsComponentAndShardStatus(opsRequest, opsCompStatus, veHelper.compOps) } if completedProgressCount != expectProgressCount { requeueAfter = time.Minute @@ -141,13 +213,13 @@ func (ve volumeExpansionOpsHandler) ReconcileAction(reqCtx intctrlutil.RequestCt } else { opsRequestPhase = appsv1alpha1.OpsFailedPhase } - } else { - // check whether the volume expansion operation has timed out - if time.Now().After(opsRequest.Status.StartTimestamp.Add(VolumeExpansionTimeOut)) { - // if volume expansion timed out - opsRequestPhase = appsv1alpha1.OpsFailedPhase - err = errors.New(fmt.Sprintf("Timed out waiting for volume expansion to complete, the timeout value is %g minutes", VolumeExpansionTimeOut.Minutes())) - } + return opsRequestPhase, requeueAfter, err + } + // check whether the volume expansion operation has timed out + if time.Now().After(opsRequest.Status.StartTimestamp.Add(VolumeExpansionTimeOut)) { + // if volume expansion timed out + opsRequestPhase = appsv1alpha1.OpsFailedPhase + err = errors.New(fmt.Sprintf("Timed out waiting for volume expansion to complete, the timeout value is %g minutes", VolumeExpansionTimeOut.Minutes())) } return opsRequestPhase, requeueAfter, err } @@ -155,29 +227,47 @@ func (ve volumeExpansionOpsHandler) ReconcileAction(reqCtx intctrlutil.RequestCt // SaveLastConfiguration records last configuration to the OpsRequest.status.lastConfiguration func (ve volumeExpansionOpsHandler) SaveLastConfiguration(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) error { opsRequest := opsRes.OpsRequest - componentNameSet := opsRequest.GetComponentNameSet() - storageMap := ve.getRequestStorageMap(opsRequest) - lastComponentInfo := map[string]appsv1alpha1.LastComponentConfiguration{} - for _, v := range opsRes.Cluster.Spec.ComponentSpecs { - if _, ok := componentNameSet[v.Name]; !ok { - continue + compOpsHelper := newComponentOpsHelper(opsRequest.Spec.VolumeExpansionList) + storageMap := ve.getRequestStorageMap(opsRequest, compOpsHelper) + compOpsHelper.saveLastConfigurations(opsRes, func(compSpec appsv1alpha1.ClusterComponentSpec, comOps ComponentOpsInteface) appsv1alpha1.LastComponentConfiguration { + getLastVCTs := func(vcts []appsv1alpha1.ClusterComponentVolumeClaimTemplate, templateName string) []appsv1alpha1.ClusterComponentVolumeClaimTemplate { + lastVCTs := make([]appsv1alpha1.ClusterComponentVolumeClaimTemplate, 0) + for _, vct := range vcts { + key := getComponentVCTKey(comOps.GetShardingName(), comOps.GetComponentName(), templateName, vct.Name) + if _, ok := storageMap[key]; !ok { + continue + } + lastVCTs = append(lastVCTs, vct) + } + return lastVCTs } - lastVCTs := make([]appsv1alpha1.OpsRequestVolumeClaimTemplate, 0) - for _, vct := range v.VolumeClaimTemplates { - key := getComponentVCTKey(v.Name, vct.Name) - if _, ok := storageMap[key]; !ok { - continue + volumeExpansion := comOps.(appsv1alpha1.VolumeExpansion) + // save the last vcts of the instances + var instanceTemplates []appsv1alpha1.InstanceTemplate + for _, v := range volumeExpansion.Instances { + for _, ins := range compSpec.Instances { + if ins.Name != v.Name { + continue + } + instanceTemplates = append(instanceTemplates, appsv1alpha1.InstanceTemplate{ + VolumeClaimTemplates: getLastVCTs(ins.VolumeClaimTemplates, ins.Name), + }) } - lastVCTs = append(lastVCTs, appsv1alpha1.OpsRequestVolumeClaimTemplate{ - Name: vct.Name, - Storage: vct.Spec.Resources.Requests[corev1.ResourceStorage], + } + // save the last vcts of the componnet + lastVCTS := getLastVCTs(compSpec.VolumeClaimTemplates, "") + var convertedLastVCTs []appsv1alpha1.OpsRequestVolumeClaimTemplate + for _, v := range lastVCTS { + convertedLastVCTs = append(convertedLastVCTs, appsv1alpha1.OpsRequestVolumeClaimTemplate{ + Name: v.Name, + Storage: v.Spec.Resources.Requests[corev1.ResourceStorage], }) } - lastComponentInfo[v.Name] = appsv1alpha1.LastComponentConfiguration{ - VolumeClaimTemplates: lastVCTs, + return appsv1alpha1.LastComponentConfiguration{ + VolumeClaimTemplates: convertedLastVCTs, + Instances: instanceTemplates, } - } - opsRequest.Status.LastConfiguration.Components = lastComponentInfo + }) return nil } @@ -193,12 +283,20 @@ func (ve volumeExpansionOpsHandler) pvcIsResizing(pvc *corev1.PersistentVolumeCl return isResizing } -func (ve volumeExpansionOpsHandler) getRequestStorageMap(opsRequest *appsv1alpha1.OpsRequest) map[string]resource.Quantity { +func (ve volumeExpansionOpsHandler) getRequestStorageMap(opsRequest *appsv1alpha1.OpsRequest, compOpsHelper componentOpsHelper) map[string]resource.Quantity { storageMap := map[string]resource.Quantity{} + setStorageMap := func(vct appsv1alpha1.OpsRequestVolumeClaimTemplate, compOps appsv1alpha1.ComponentOps, templateName string) { + key := getComponentVCTKey(compOps.ShardingName, compOps.ComponentName, templateName, vct.Name) + storageMap[key] = vct.Storage + } for _, v := range opsRequest.Spec.VolumeExpansionList { for _, vct := range v.VolumeClaimTemplates { - key := getComponentVCTKey(v.ComponentName, vct.Name) - storageMap[key] = vct.Storage + setStorageMap(vct, v.ComponentOps, "") + } + for _, ins := range v.Instances { + for _, vct := range ins.VolumeClaimTemplates { + setStorageMap(vct, v.ComponentOps, ins.Name) + } } } return storageMap @@ -208,7 +306,7 @@ func (ve volumeExpansionOpsHandler) getRequestStorageMap(opsRequest *appsv1alpha func (ve volumeExpansionOpsHandler) initComponentStatus(opsRequest *appsv1alpha1.OpsRequest) { opsRequest.Status.Components = map[string]appsv1alpha1.OpsRequestComponentStatus{} for _, v := range opsRequest.Spec.VolumeExpansionList { - opsRequest.Status.Components[v.ComponentName] = appsv1alpha1.OpsRequestComponentStatus{} + opsRequest.Status.Components[getCompOpsKey(v.ShardingName, v.ComponentName)] = appsv1alpha1.OpsRequestComponentStatus{} } } @@ -217,48 +315,46 @@ func (ve volumeExpansionOpsHandler) handleVCTExpansionProgress(reqCtx intctrluti cli client.Client, opsRes *OpsResource, compStatus *appsv1alpha1.OpsRequestComponentStatus, - storageMap map[string]resource.Quantity, - componentName, vctName string) (int, int, int, error) { + requestStorage resource.Quantity, + veHelper volumeExpansionHelper) (int, int, error) { var ( succeedCount int - expectCount int completedCount int err error ) - pvcList := &corev1.PersistentVolumeClaimList{} - if err = cli.List(reqCtx.Ctx, pvcList, client.MatchingLabels{ - constant.AppInstanceLabelKey: opsRes.Cluster.Name, - constant.KBAppComponentLabelKey: componentName, - }, client.InNamespace(opsRes.Cluster.Namespace)); err != nil { - return 0, 0, 0, err + messageKey := fmt.Sprintf("component: %s", veHelper.compOps.GetComponentName()) + if veHelper.compOps.GetShardingName() != "" { + messageKey = fmt.Sprintf("sharding: %s", veHelper.compOps.GetShardingName()) + } + matchingLabels := client.MatchingLabels{ + constant.AppInstanceLabelKey: opsRes.Cluster.Name, + constant.VolumeClaimTemplateNameLabelKey: veHelper.vctName, + constant.KBAppComponentLabelKey: veHelper.fullComponentName, + } + if veHelper.templateName != "" { + matchingLabels[constant.KBAppComponentInstanceTemplatelabelKey] = veHelper.templateName } - comp := opsRes.Cluster.Spec.GetComponentByName(componentName) - if comp == nil { - err = fmt.Errorf("comp %s of cluster %s not found", componentName, opsRes.Cluster.Name) - return 0, 0, 0, err + pvcList := &corev1.PersistentVolumeClaimList{} + if err = cli.List(reqCtx.Ctx, pvcList, matchingLabels, client.InNamespace(opsRes.Cluster.Namespace)); err != nil { + return 0, 0, err } - expectCount = int(comp.Replicas) - vctKey := getComponentVCTKey(componentName, vctName) - requestStorage := storageMap[vctKey] var ordinal int for _, v := range pvcList.Items { - // VolumeClaimTemplateNameLabelKeyForLegacy is deprecated: only compatible with version 0.5, will be removed in 0.7? - if v.Labels[constant.VolumeClaimTemplateNameLabelKey] != vctName && - v.Labels[constant.VolumeClaimTemplateNameLabelKeyForLegacy] != vctName { - continue - } // filter PVC(s) with ordinal no larger than comp.Replicas - 1, which left by scale-in ordinal, err = getPVCOrdinal(v.Name) if err != nil { - return 0, 0, 0, err + return 0, 0, err } - if ordinal > expectCount-1 { + if ordinal > veHelper.expectCount-1 { + continue + } + if v.Labels[constant.KBAppComponentInstanceTemplatelabelKey] != veHelper.templateName { continue } objectKey := getPVCProgressObjectKey(v.Name) progressDetail := findStatusProgressDetail(compStatus.ProgressDetails, objectKey) if progressDetail == nil { - progressDetail = &appsv1alpha1.ProgressStatusDetail{ObjectKey: objectKey, Group: vctName} + progressDetail = &appsv1alpha1.ProgressStatusDetail{ObjectKey: objectKey, Group: veHelper.vctName} } if progressDetail.Status == appsv1alpha1.FailedProgressStatus { completedCount += 1 @@ -272,25 +368,32 @@ func (ve volumeExpansionOpsHandler) handleVCTExpansionProgress(reqCtx intctrluti v.Status.Phase == corev1.ClaimBound { succeedCount += 1 completedCount += 1 - message := fmt.Sprintf("Successfully expand volume: %s in Component: %s", objectKey, componentName) + message := fmt.Sprintf("Successfully expand volume: %s in %s", objectKey, messageKey) progressDetail.SetStatusAndMessage(appsv1alpha1.SucceedProgressStatus, message) setComponentStatusProgressDetail(opsRes.Recorder, opsRes.OpsRequest, &compStatus.ProgressDetails, *progressDetail) continue } if ve.pvcIsResizing(&v) { - message := fmt.Sprintf("Start expanding volume: %s in Component: %s ", objectKey, componentName) + message := fmt.Sprintf("Start expanding volume: %s in %s", objectKey, messageKey) progressDetail.SetStatusAndMessage(appsv1alpha1.ProcessingProgressStatus, message) } else { - message := fmt.Sprintf("Waiting for an external controller to process the pvc: %s in Component: %s ", objectKey, componentName) + message := fmt.Sprintf("Waiting for an external controller to process the pvc: %s in %s", objectKey, messageKey) progressDetail.SetStatusAndMessage(appsv1alpha1.PendingProgressStatus, message) } setComponentStatusProgressDetail(opsRes.Recorder, opsRes.OpsRequest, &compStatus.ProgressDetails, *progressDetail) } - return succeedCount, expectCount, completedCount, nil + return succeedCount, completedCount, nil } -func getComponentVCTKey(componentName, vctName string) string { - return fmt.Sprintf("%s/%s", componentName, vctName) +func getComponentVCTKey(shardingName, cName, insTemplateName, vctName string) string { + var instanceNameKey string + if insTemplateName != "" { + instanceNameKey = "." + insTemplateName + } + if shardingName != "" { + return fmt.Sprintf("sharding/%s%s.%s", shardingName, instanceNameKey, vctName) + } + return fmt.Sprintf("%s%s.%s", cName, insTemplateName, vctName) } func getPVCProgressObjectKey(pvcName string) string { diff --git a/controllers/apps/opsrequest_controller_test.go b/controllers/apps/opsrequest_controller_test.go index 9bc0bed39bf..cc7f986d377 100644 --- a/controllers/apps/opsrequest_controller_test.go +++ b/controllers/apps/opsrequest_controller_test.go @@ -203,6 +203,10 @@ var _ = Describe("OpsRequest Controller", func() { // checkLatestOpsHasProcessed(clusterKey) By("notice opsrequest controller to run") + testk8s.MockPodIsTerminating(ctx, testCtx, pod) + testk8s.RemovePodFinalizer(ctx, testCtx, pod) + testapps.MockInstanceSetPod(&testCtx, nil, clusterObj.Name, mysqlCompName, + pod.Name, "leader", "ReadWrite", scalingCtx.target) Expect(testapps.ChangeObj(&testCtx, verticalScalingOpsRequest, func(lopsReq *appsv1alpha1.OpsRequest) { if lopsReq.Annotations == nil { lopsReq.Annotations = map[string]string{} @@ -277,7 +281,7 @@ var _ = Describe("OpsRequest Controller", func() { return &itsList.Items[0] } - mockCompRunning := func(replicas int32) { + mockCompRunning := func(replicas int32, reCreatePod bool) { // to wait the component object becomes stable compKey := types.NamespacedName{ Namespace: clusterKey.Namespace, @@ -289,6 +293,17 @@ var _ = Describe("OpsRequest Controller", func() { wl := componentWorkload() its, _ := wl.(*workloads.InstanceSet) + if reCreatePod { + podList := &corev1.PodList{} + Expect(k8sClient.List(ctx, podList, client.MatchingLabels{ + constant.AppInstanceLabelKey: clusterKey.Name, + constant.KBAppComponentLabelKey: mysqlCompName, + })).Should(Succeed()) + for i := range podList.Items { + testk8s.MockPodIsTerminating(ctx, testCtx, &podList.Items[i]) + testk8s.RemovePodFinalizer(ctx, testCtx, &podList.Items[i]) + } + } mockPods := testapps.MockInstanceSetPods(&testCtx, its, clusterObj.Name, mysqlCompName) Expect(testapps.ChangeObjStatus(&testCtx, its, func() { testk8s.MockInstanceSetReady(its, mockPods...) @@ -319,7 +334,7 @@ var _ = Describe("OpsRequest Controller", func() { clusterKey = client.ObjectKeyFromObject(clusterObj) By("mock component is Running") - mockCompRunning(replicas) + mockCompRunning(replicas, false) By("mock pvc created") for i := 0; i < int(replicas); i++ { @@ -524,7 +539,7 @@ var _ = Describe("OpsRequest Controller", func() { Eventually(testapps.CheckObjExists(&testCtx, backupKey, backup, false)).Should(Succeed()) By("mock component workload is running and expect cluster and component are running") - mockCompRunning(replicas) + mockCompRunning(replicas, false) Eventually(testapps.CheckObj(&testCtx, clusterKey, func(g Gomega, cluster *appsv1alpha1.Cluster) { g.Expect(cluster.Status.Components[mysqlCompName].Phase).Should(Equal(appsv1alpha1.RunningClusterCompPhase)) g.Expect(cluster.Status.Phase).Should(Equal(appsv1alpha1.RunningClusterPhase)) @@ -586,10 +601,10 @@ var _ = Describe("OpsRequest Controller", func() { testapps.DeleteObject(&testCtx, dPodKeys, &corev1.Pod{}) By("expect opsRequest phase to Succeed after cluster is Running") - mockCompRunning(replicas) + mockCompRunning(replicas, false) Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(ops), func(g Gomega, ops *appsv1alpha1.OpsRequest) { g.Expect(ops.Status.Phase).Should(Equal(appsv1alpha1.OpsSucceedPhase)) - g.Expect(ops.Status.Progress).Should(Equal("1/1")) + g.Expect(ops.Status.Progress).Should(Equal("2/2")) })).Should(Succeed()) }) @@ -626,7 +641,7 @@ var _ = Describe("OpsRequest Controller", func() { testk8s.MockDisableVolumeSnapshot(&testCtx, testk8s.DefaultStorageClassName) oldReplicas := int32(3) createMysqlCluster(oldReplicas) - mockCompRunning(oldReplicas) + mockCompRunning(oldReplicas, false) By("create a horizontalScaling ops") ops := createClusterHscaleOps(5) @@ -690,9 +705,10 @@ var _ = Describe("OpsRequest Controller", func() { By("create cluster and mock it to running") replicas := int32(3) createMysqlCluster(replicas) - mockCompRunning(replicas) + mockCompRunning(replicas, false) By("create first restart ops") + time.Sleep(time.Second) ops1 := createRestartOps(clusterObj.Name, 1) Eventually(testapps.GetOpsRequestPhase(&testCtx, client.ObjectKeyFromObject(ops1))).Should(Equal(appsv1alpha1.OpsRunningPhase)) Eventually(testapps.GetClusterPhase(&testCtx, clusterKey)).Should(Equal(appsv1alpha1.UpdatingClusterPhase)) @@ -716,7 +732,7 @@ var _ = Describe("OpsRequest Controller", func() { })).Should(Succeed()) By("mock ops1 phase to Succeed") - mockCompRunning(replicas) + mockCompRunning(replicas, true) Eventually(testapps.GetOpsRequestPhase(&testCtx, client.ObjectKeyFromObject(ops1))).Should(Equal(appsv1alpha1.OpsSucceedPhase)) By("expect for next ops is Running") @@ -736,9 +752,10 @@ var _ = Describe("OpsRequest Controller", func() { By("create cluster and mock it to running") replicas := int32(3) createMysqlCluster(replicas) - mockCompRunning(replicas) + mockCompRunning(replicas, false) By("create first restart ops") + time.Sleep(time.Second) ops1 := createRestartOps(clusterObj.Name, 1) Eventually(testapps.GetOpsRequestPhase(&testCtx, client.ObjectKeyFromObject(ops1))).Should(Equal(appsv1alpha1.OpsRunningPhase)) Eventually(testapps.GetClusterPhase(&testCtx, clusterKey)).Should(Equal(appsv1alpha1.UpdatingClusterPhase)) @@ -762,7 +779,7 @@ var _ = Describe("OpsRequest Controller", func() { })).Should(Succeed()) By("mock component to running and expect ops1/op3 phase to Succeed") - mockCompRunning(replicas) + mockCompRunning(replicas, true) Eventually(testapps.GetOpsRequestPhase(&testCtx, client.ObjectKeyFromObject(ops1))).Should(Equal(appsv1alpha1.OpsSucceedPhase)) Eventually(testapps.GetOpsRequestPhase(&testCtx, client.ObjectKeyFromObject(ops3))).Should(Equal(appsv1alpha1.OpsSucceedPhase)) @@ -780,9 +797,10 @@ var _ = Describe("OpsRequest Controller", func() { By("create cluster and mock it to running") replicas := int32(3) createMysqlCluster(replicas) - mockCompRunning(replicas) + mockCompRunning(replicas, false) By("create first restart ops") + time.Sleep(time.Second) restartOps1 := createRestartOps(clusterObj.Name, 0) Eventually(testapps.GetOpsRequestPhase(&testCtx, client.ObjectKeyFromObject(restartOps1))).Should(Equal(appsv1alpha1.OpsRunningPhase)) Eventually(testapps.GetClusterPhase(&testCtx, clusterKey)).Should(Equal(appsv1alpha1.UpdatingClusterPhase)) @@ -792,10 +810,8 @@ var _ = Describe("OpsRequest Controller", func() { clusterName, appsv1alpha1.ExposeType) ops.Spec.ExposeList = []appsv1alpha1.Expose{ { - ComponentOps: appsv1alpha1.ComponentOps{ - ComponentName: mysqlCompName, - }, - Switch: exposeSwitch, + ComponentName: mysqlCompName, + Switch: exposeSwitch, Services: []appsv1alpha1.OpsService{ { Name: "svc1", @@ -836,7 +852,7 @@ var _ = Describe("OpsRequest Controller", func() { })).Should(Succeed()) By("mock component to running and expect restartOps1 phase to Succeed") - mockCompRunning(replicas) + mockCompRunning(replicas, true) Eventually(testapps.GetOpsRequestPhase(&testCtx, client.ObjectKeyFromObject(restartOps1))).Should(Equal(appsv1alpha1.OpsSucceedPhase)) By("mock loadBalance service is ready") diff --git a/controllers/apps/transformer_component_service.go b/controllers/apps/transformer_component_service.go index f0132cd124c..271e43f6b14 100644 --- a/controllers/apps/transformer_component_service.go +++ b/controllers/apps/transformer_component_service.go @@ -37,6 +37,7 @@ import ( "github.com/apecloud/kubeblocks/pkg/controller/graph" "github.com/apecloud/kubeblocks/pkg/controller/instanceset" "github.com/apecloud/kubeblocks/pkg/controller/model" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) var ( @@ -209,25 +210,18 @@ func (t *componentServiceTransformer) skipDefaultHeadlessSvc(synthesizeComp *com } func generatePodNames(synthesizeComp *component.SynthesizedComponent) []string { - templateReplicas := func(template appsv1alpha1.InstanceTemplate) int32 { - replicas := int32(1) - if template.Replicas != nil { - replicas = *template.Replicas - } - return replicas - } templateReplicasCnt := int32(0) for _, template := range synthesizeComp.Instances { if len(template.Name) > 0 { - templateReplicasCnt += templateReplicas(template) + templateReplicasCnt += intctrlutil.TemplateReplicas(template) } } podNames := make([]string, 0) workloadName := constant.GenerateWorkloadNamePattern(synthesizeComp.ClusterName, synthesizeComp.Name) for _, template := range synthesizeComp.Instances { - templateNames := instanceset.GenerateInstanceNamesFromTemplate(workloadName, template.Name, templateReplicas(template), synthesizeComp.OfflineInstances) + templateNames := instanceset.GenerateInstanceNamesFromTemplate(workloadName, template.Name, intctrlutil.TemplateReplicas(template), synthesizeComp.OfflineInstances) podNames = append(podNames, templateNames...) } if templateReplicasCnt < synthesizeComp.Replicas { diff --git a/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml b/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml index b94251418b9..c67d8222d14 100644 --- a/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml +++ b/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml @@ -675,166 +675,40 @@ spec: description: Defines VolumeClaimTemplates to override. Add new or override existing volume claim templates. items: - description: PersistentVolumeClaim is a user's request - for and claim to a persistent volume properties: - apiVersion: - description: 'APIVersion defines the versioned schema - of this representation of an object. Servers should - convert recognized schemas to the latest internal - value, and may reject unrecognized values. More - info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing - the REST resource this object represents. Servers - may infer this from the endpoint the client submits - requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + name: + description: "Refers to the name of a volumeMount + defined in either: \n - `componentDefinition.spec.runtime.containers[*].volumeMounts` + - `clusterDefinition.spec.componentDefs[*].podSpec.containers[*].volumeMounts` + (deprecated) \n The value of `name` must match + the `name` field of a volumeMount specified in + the corresponding `volumeMounts` array." type: string - metadata: - description: 'Standard object''s metadata. More - info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata' - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object spec: - description: 'spec defines the desired characteristics - of a volume requested by a pod author. More info: - https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + description: "Defines the desired characteristics + of a PersistentVolumeClaim that will be created + for the volume with the mount name specified in + the `name` field. \n When a Pod is created for + this ClusterComponent, a new PVC will be created + based on the specification defined in the `spec` + field. The PVC will be associated with the volume + mount specified by the `name` field." properties: accessModes: - description: 'accessModes contains the desired - access modes the volume should have. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + description: 'Contains the desired access modes + the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1.' items: type: string type: array - dataSource: - description: 'dataSource field can be used to - specify either: * An existing VolumeSnapshot - object (snapshot.storage.k8s.io/VolumeSnapshot) - * An existing PVC (PersistentVolumeClaim) - If the provisioner or an external controller - can support the specified data source, it - will create a new volume based on the contents - of the specified data source. When the AnyVolumeDataSource - feature gate is enabled, dataSource contents - will be copied to dataSourceRef, and dataSourceRef - contents will be copied to dataSource when - dataSourceRef.namespace is not specified. - If the namespace is specified, then dataSourceRef - will not be copied to dataSource.' - properties: - apiGroup: - description: APIGroup is the group for the - resource being referenced. If APIGroup - is not specified, the specified Kind must - be in the core API group. For any other - third-party types, APIGroup is required. - type: string - kind: - description: Kind is the type of resource - being referenced - type: string - name: - description: Name is the name of resource - being referenced - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - dataSourceRef: - description: 'dataSourceRef specifies the object - from which to populate the volume with data, - if a non-empty volume is desired. This may - be any object from a non-empty API group (non - core object) or a PersistentVolumeClaim object. - When this field is specified, volume binding - will only succeed if the type of the specified - object matches some installed volume populator - or dynamic provisioner. This field will replace - the functionality of the dataSource field - and as such if both fields are non-empty, - they must have the same value. For backwards - compatibility, when namespace isn''t specified - in dataSourceRef, both fields (dataSource - and dataSourceRef) will be set to the same - value automatically if one of them is empty - and the other is non-empty. When namespace - is specified in dataSourceRef, dataSource - isn''t set to the same value and must be empty. - There are three important differences between - dataSource and dataSourceRef: * While dataSource - only allows two specific types of objects, - dataSourceRef allows any non-core object, - as well as PersistentVolumeClaim objects. - * While dataSource ignores disallowed values - (dropping them), dataSourceRef preserves all - values, and generates an error if a disallowed - value is specified. * While dataSource only - allows local objects, dataSourceRef allows - objects in any namespaces. (Beta) Using this - field requires the AnyVolumeDataSource feature - gate to be enabled. (Alpha) Using the namespace - field of dataSourceRef requires the CrossNamespaceVolumeDataSource - feature gate to be enabled.' - properties: - apiGroup: - description: APIGroup is the group for the - resource being referenced. If APIGroup - is not specified, the specified Kind must - be in the core API group. For any other - third-party types, APIGroup is required. - type: string - kind: - description: Kind is the type of resource - being referenced - type: string - name: - description: Name is the name of resource - being referenced - type: string - namespace: - description: Namespace is the namespace - of resource being referenced Note that - when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant - object is required in the referent namespace - to allow that namespace's owner to accept - the reference. See the ReferenceGrant - documentation for details. (Alpha) This - field requires the CrossNamespaceVolumeDataSource - feature gate to be enabled. - type: string - required: - - kind - - name - type: object + x-kubernetes-preserve-unknown-fields: true resources: - description: 'resources represents the minimum - resources the volume should have. If RecoverVolumeExpansionFailure - feature is enabled users are allowed to specify + description: 'Represents the minimum resources + the volume should have. If the RecoverVolumeExpansionFailure + feature is enabled, users are allowed to specify resource requirements that are lower than - previous value but must still be higher than - capacity recorded in the status field of the - claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + the previous value but must still be higher + than the capacity recorded in the status field + of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources.' properties: claims: description: "Claims lists the names of @@ -889,235 +763,18 @@ spec: More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object - selector: - description: selector is a label query over - volumes to consider for binding. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true storageClassName: - description: 'storageClassName is the name of - the StorageClass required by the claim. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + description: 'The name of the StorageClass required + by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1.' type: string volumeMode: - description: volumeMode defines what type of - volume is required by the claim. Value of - Filesystem is implied when not included in - claim spec. - type: string - volumeName: - description: volumeName is the binding reference - to the PersistentVolume backing this claim. - type: string - type: object - status: - description: 'status represents the current information/status - of a persistent volume claim. Read-only. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' - properties: - accessModes: - description: 'accessModes contains the actual - access modes the volume backing the PVC has. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - allocatedResourceStatuses: - additionalProperties: - description: When a controller receives persistentvolume - claim update with ClaimResourceStatus for - a resource that it does not recognizes, - then it should ignore that update and let - other controllers handle it. - type: string - description: "allocatedResourceStatuses stores - status of resource being resized for the given - PVC. Key names follow standard Kubernetes - label syntax. Valid values are either: * Un-prefixed - keys: - storage - the capacity of the volume. - * Custom resources must use implementation-defined - prefixed names such as \"example.com/my-custom-resource\" - Apart from above values - keys that are unprefixed - or have kubernetes.io prefix are considered - reserved and hence may not be used. \n ClaimResourceStatus - can be in any of following states: - ControllerResizeInProgress: - State set when resize controller starts resizing - the volume in control-plane. - ControllerResizeFailed: - State set when resize has failed in resize - controller with a terminal error. - NodeResizePending: - State set when resize controller has finished - resizing the volume but further resizing of - volume is needed on the node. - NodeResizeInProgress: - State set when kubelet starts resizing the - volume. - NodeResizeFailed: State set when - resizing has failed in kubelet with a terminal - error. Transient errors don't set NodeResizeFailed. - For example: if expanding a PVC for more capacity - - this field can be one of the following states: - - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeFailed\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizePending\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeFailed\" When this field is - not set, it means that no resize operation - is in progress for the given PVC. \n A controller - that receives PVC update with previously unknown - resourceName or ClaimResourceStatus should - ignore the update for the purpose it was designed. - For example - a controller that only is responsible - for resizing capacity of the volume, should - ignore PVC updates that change other valid - resources associated with PVC. \n This is - an alpha field and requires enabling RecoverVolumeExpansionFailure - feature." - type: object - x-kubernetes-map-type: granular - allocatedResources: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: "allocatedResources tracks the - resources allocated to a PVC including its - capacity. Key names follow standard Kubernetes - label syntax. Valid values are either: * Un-prefixed - keys: - storage - the capacity of the volume. - * Custom resources must use implementation-defined - prefixed names such as \"example.com/my-custom-resource\" - Apart from above values - keys that are unprefixed - or have kubernetes.io prefix are considered - reserved and hence may not be used. \n Capacity - reported here may be larger than the actual - capacity when a volume expansion operation - is requested. For storage quota, the larger - value from allocatedResources and PVC.spec.resources - is used. If allocatedResources is not set, - PVC.spec.resources alone is used for quota - calculation. If a volume expansion capacity - request is lowered, allocatedResources is - only lowered if there are no expansion operations - in progress and if the actual volume capacity - is equal or lower than the requested capacity. - \n A controller that receives PVC update with - previously unknown resourceName should ignore - the update for the purpose it was designed. - For example - a controller that only is responsible - for resizing capacity of the volume, should - ignore PVC updates that change other valid - resources associated with PVC. \n This is - an alpha field and requires enabling RecoverVolumeExpansionFailure - feature." - type: object - capacity: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: capacity represents the actual - resources of the underlying volume. - type: object - conditions: - description: conditions is the current Condition - of persistent volume claim. If underlying - persistent volume is being resized then the - Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition - contains details about state of pvc - properties: - lastProbeTime: - description: lastProbeTime is the time - we probed the condition. - format: date-time - type: string - lastTransitionTime: - description: lastTransitionTime is the - time the condition transitioned from - one status to another. - format: date-time - type: string - message: - description: message is the human-readable - message indicating details about last - transition. - type: string - reason: - description: reason is a unique, this - should be a short, machine understandable - string that gives the reason for condition's - last transition. If it reports "ResizeStarted" - that means the underlying persistent - volume is being resized. - type: string - status: - type: string - type: - description: PersistentVolumeClaimConditionType - is a valid value of PersistentVolumeClaimCondition.Type - type: string - required: - - status - - type - type: object - type: array - phase: - description: phase represents the current phase - of PersistentVolumeClaim. + description: Defines what type of volume is + required by the claim, either Block or Filesystem. type: string type: object + required: + - name type: object type: array volumeMounts: @@ -2967,6 +2624,9 @@ spec: - name type: object type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map issuer: description: Specifies the configuration for the TLS certificates issuer. It allows defining the issuer name and the reference @@ -7073,174 +6733,43 @@ spec: description: Defines VolumeClaimTemplates to override. Add new or override existing volume claim templates. items: - description: PersistentVolumeClaim is a user's request - for and claim to a persistent volume properties: - apiVersion: - description: 'APIVersion defines the versioned - schema of this representation of an object. - Servers should convert recognized schemas - to the latest internal value, and may reject - unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing - the REST resource this object represents. - Servers may infer this from the endpoint the - client submits requests to. Cannot be updated. - In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + name: + description: "Refers to the name of a volumeMount + defined in either: \n - `componentDefinition.spec.runtime.containers[*].volumeMounts` + - `clusterDefinition.spec.componentDefs[*].podSpec.containers[*].volumeMounts` + (deprecated) \n The value of `name` must match + the `name` field of a volumeMount specified + in the corresponding `volumeMounts` array." type: string - metadata: - description: 'Standard object''s metadata. More - info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata' - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object spec: - description: 'spec defines the desired characteristics - of a volume requested by a pod author. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + description: "Defines the desired characteristics + of a PersistentVolumeClaim that will be created + for the volume with the mount name specified + in the `name` field. \n When a Pod is created + for this ClusterComponent, a new PVC will + be created based on the specification defined + in the `spec` field. The PVC will be associated + with the volume mount specified by the `name` + field." properties: accessModes: - description: 'accessModes contains the desired - access modes the volume should have. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + description: 'Contains the desired access + modes the volume should have. More info: + https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1.' items: type: string type: array - dataSource: - description: 'dataSource field can be used - to specify either: * An existing VolumeSnapshot - object (snapshot.storage.k8s.io/VolumeSnapshot) - * An existing PVC (PersistentVolumeClaim) - If the provisioner or an external controller - can support the specified data source, - it will create a new volume based on the - contents of the specified data source. - When the AnyVolumeDataSource feature gate - is enabled, dataSource contents will be - copied to dataSourceRef, and dataSourceRef - contents will be copied to dataSource - when dataSourceRef.namespace is not specified. - If the namespace is specified, then dataSourceRef - will not be copied to dataSource.' - properties: - apiGroup: - description: APIGroup is the group for - the resource being referenced. If - APIGroup is not specified, the specified - Kind must be in the core API group. - For any other third-party types, APIGroup - is required. - type: string - kind: - description: Kind is the type of resource - being referenced - type: string - name: - description: Name is the name of resource - being referenced - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - dataSourceRef: - description: 'dataSourceRef specifies the - object from which to populate the volume - with data, if a non-empty volume is desired. - This may be any object from a non-empty - API group (non core object) or a PersistentVolumeClaim - object. When this field is specified, - volume binding will only succeed if the - type of the specified object matches some - installed volume populator or dynamic - provisioner. This field will replace the - functionality of the dataSource field - and as such if both fields are non-empty, - they must have the same value. For backwards - compatibility, when namespace isn''t specified - in dataSourceRef, both fields (dataSource - and dataSourceRef) will be set to the - same value automatically if one of them - is empty and the other is non-empty. When - namespace is specified in dataSourceRef, - dataSource isn''t set to the same value - and must be empty. There are three important - differences between dataSource and dataSourceRef: - * While dataSource only allows two specific - types of objects, dataSourceRef allows - any non-core object, as well as PersistentVolumeClaim - objects. * While dataSource ignores disallowed - values (dropping them), dataSourceRef - preserves all values, and generates an - error if a disallowed value is specified. - * While dataSource only allows local objects, - dataSourceRef allows objects in any namespaces. - (Beta) Using this field requires the AnyVolumeDataSource - feature gate to be enabled. (Alpha) Using - the namespace field of dataSourceRef requires - the CrossNamespaceVolumeDataSource feature - gate to be enabled.' - properties: - apiGroup: - description: APIGroup is the group for - the resource being referenced. If - APIGroup is not specified, the specified - Kind must be in the core API group. - For any other third-party types, APIGroup - is required. - type: string - kind: - description: Kind is the type of resource - being referenced - type: string - name: - description: Name is the name of resource - being referenced - type: string - namespace: - description: Namespace is the namespace - of resource being referenced Note - that when a namespace is specified, - a gateway.networking.k8s.io/ReferenceGrant - object is required in the referent - namespace to allow that namespace's - owner to accept the reference. See - the ReferenceGrant documentation for - details. (Alpha) This field requires - the CrossNamespaceVolumeDataSource - feature gate to be enabled. - type: string - required: - - kind - - name - type: object + x-kubernetes-preserve-unknown-fields: true resources: - description: 'resources represents the minimum - resources the volume should have. If RecoverVolumeExpansionFailure - feature is enabled users are allowed to - specify resource requirements that are - lower than previous value but must still - be higher than capacity recorded in the - status field of the claim. More info: - https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + description: 'Represents the minimum resources + the volume should have. If the RecoverVolumeExpansionFailure + feature is enabled, users are allowed + to specify resource requirements that + are lower than the previous value but + must still be higher than the capacity + recorded in the status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources.' properties: claims: description: "Claims lists the names @@ -7296,248 +6825,19 @@ spec: info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object - selector: - description: selector is a label query over - volumes to consider for binding. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The - requirements are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label - key that the selector applies - to. - type: string - operator: - description: operator represents - a key's relationship to a set - of values. Valid operators are - In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array - of string values. If the operator - is In or NotIn, the values array - must be non-empty. If the operator - is Exists or DoesNotExist, the - values array must be empty. - This array is replaced during - a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of - {key,value} pairs. A single {key,value} - in the matchLabels map is equivalent - to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are - ANDed. - type: object - type: object - x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true storageClassName: - description: 'storageClassName is the name - of the StorageClass required by the claim. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + description: 'The name of the StorageClass + required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1.' type: string volumeMode: - description: volumeMode defines what type - of volume is required by the claim. Value - of Filesystem is implied when not included - in claim spec. - type: string - volumeName: - description: volumeName is the binding reference - to the PersistentVolume backing this claim. - type: string - type: object - status: - description: 'status represents the current - information/status of a persistent volume - claim. Read-only. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' - properties: - accessModes: - description: 'accessModes contains the actual - access modes the volume backing the PVC - has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - allocatedResourceStatuses: - additionalProperties: - description: When a controller receives - persistentvolume claim update with ClaimResourceStatus - for a resource that it does not recognizes, - then it should ignore that update and - let other controllers handle it. - type: string - description: "allocatedResourceStatuses - stores status of resource being resized - for the given PVC. Key names follow standard - Kubernetes label syntax. Valid values - are either: * Un-prefixed keys: - storage - - the capacity of the volume. * Custom - resources must use implementation-defined - prefixed names such as \"example.com/my-custom-resource\" - Apart from above values - keys that are - unprefixed or have kubernetes.io prefix - are considered reserved and hence may - not be used. \n ClaimResourceStatus can - be in any of following states: - ControllerResizeInProgress: - State set when resize controller starts - resizing the volume in control-plane. - - ControllerResizeFailed: State set when - resize has failed in resize controller - with a terminal error. - NodeResizePending: - State set when resize controller has finished - resizing the volume but further resizing - of volume is needed on the node. - NodeResizeInProgress: - State set when kubelet starts resizing - the volume. - NodeResizeFailed: State - set when resizing has failed in kubelet - with a terminal error. Transient errors - don't set NodeResizeFailed. For example: - if expanding a PVC for more capacity - - this field can be one of the following - states: - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeFailed\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizePending\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeFailed\" When this field - is not set, it means that no resize operation - is in progress for the given PVC. \n A - controller that receives PVC update with - previously unknown resourceName or ClaimResourceStatus - should ignore the update for the purpose - it was designed. For example - a controller - that only is responsible for resizing - capacity of the volume, should ignore - PVC updates that change other valid resources - associated with PVC. \n This is an alpha - field and requires enabling RecoverVolumeExpansionFailure - feature." - type: object - x-kubernetes-map-type: granular - allocatedResources: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: "allocatedResources tracks - the resources allocated to a PVC including - its capacity. Key names follow standard - Kubernetes label syntax. Valid values - are either: * Un-prefixed keys: - storage - - the capacity of the volume. * Custom - resources must use implementation-defined - prefixed names such as \"example.com/my-custom-resource\" - Apart from above values - keys that are - unprefixed or have kubernetes.io prefix - are considered reserved and hence may - not be used. \n Capacity reported here - may be larger than the actual capacity - when a volume expansion operation is requested. - For storage quota, the larger value from - allocatedResources and PVC.spec.resources - is used. If allocatedResources is not - set, PVC.spec.resources alone is used - for quota calculation. If a volume expansion - capacity request is lowered, allocatedResources - is only lowered if there are no expansion - operations in progress and if the actual - volume capacity is equal or lower than - the requested capacity. \n A controller - that receives PVC update with previously - unknown resourceName should ignore the - update for the purpose it was designed. - For example - a controller that only is - responsible for resizing capacity of the - volume, should ignore PVC updates that - change other valid resources associated - with PVC. \n This is an alpha field and - requires enabling RecoverVolumeExpansionFailure - feature." - type: object - capacity: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: capacity represents the actual - resources of the underlying volume. - type: object - conditions: - description: conditions is the current Condition - of persistent volume claim. If underlying - persistent volume is being resized then - the Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition - contains details about state of pvc - properties: - lastProbeTime: - description: lastProbeTime is the - time we probed the condition. - format: date-time - type: string - lastTransitionTime: - description: lastTransitionTime is - the time the condition transitioned - from one status to another. - format: date-time - type: string - message: - description: message is the human-readable - message indicating details about - last transition. - type: string - reason: - description: reason is a unique, this - should be a short, machine understandable - string that gives the reason for - condition's last transition. If - it reports "ResizeStarted" that - means the underlying persistent - volume is being resized. - type: string - status: - type: string - type: - description: PersistentVolumeClaimConditionType - is a valid value of PersistentVolumeClaimCondition.Type - type: string - required: - - status - - type - type: object - type: array - phase: - description: phase represents the current - phase of PersistentVolumeClaim. + description: Defines what type of volume + is required by the claim, either Block + or Filesystem. type: string type: object + required: + - name type: object type: array volumeMounts: @@ -9499,6 +8799,9 @@ spec: - name type: object type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map issuer: description: Specifies the configuration for the TLS certificates issuer. It allows defining the issuer name and the reference diff --git a/deploy/helm/crds/apps.kubeblocks.io_components.yaml b/deploy/helm/crds/apps.kubeblocks.io_components.yaml index a9ec71bb4d7..747033a2eb1 100644 --- a/deploy/helm/crds/apps.kubeblocks.io_components.yaml +++ b/deploy/helm/crds/apps.kubeblocks.io_components.yaml @@ -618,154 +618,39 @@ spec: description: Defines VolumeClaimTemplates to override. Add new or override existing volume claim templates. items: - description: PersistentVolumeClaim is a user's request for - and claim to a persistent volume properties: - apiVersion: - description: 'APIVersion defines the versioned schema - of this representation of an object. Servers should - convert recognized schemas to the latest internal value, - and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the - REST resource this object represents. Servers may infer - this from the endpoint the client submits requests to. - Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + name: + description: "Refers to the name of a volumeMount defined + in either: \n - `componentDefinition.spec.runtime.containers[*].volumeMounts` + - `clusterDefinition.spec.componentDefs[*].podSpec.containers[*].volumeMounts` + (deprecated) \n The value of `name` must match the `name` + field of a volumeMount specified in the corresponding + `volumeMounts` array." type: string - metadata: - description: 'Standard object''s metadata. More info: - https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata' - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object spec: - description: 'spec defines the desired characteristics - of a volume requested by a pod author. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + description: "Defines the desired characteristics of a + PersistentVolumeClaim that will be created for the volume + with the mount name specified in the `name` field. \n + When a Pod is created for this ClusterComponent, a new + PVC will be created based on the specification defined + in the `spec` field. The PVC will be associated with + the volume mount specified by the `name` field." properties: accessModes: - description: 'accessModes contains the desired access - modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + description: 'Contains the desired access modes the + volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1.' items: type: string type: array - dataSource: - description: 'dataSource field can be used to specify - either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) - * An existing PVC (PersistentVolumeClaim) If the - provisioner or an external controller can support - the specified data source, it will create a new - volume based on the contents of the specified data - source. When the AnyVolumeDataSource feature gate - is enabled, dataSource contents will be copied to - dataSourceRef, and dataSourceRef contents will be - copied to dataSource when dataSourceRef.namespace - is not specified. If the namespace is specified, - then dataSourceRef will not be copied to dataSource.' - properties: - apiGroup: - description: APIGroup is the group for the resource - being referenced. If APIGroup is not specified, - the specified Kind must be in the core API group. - For any other third-party types, APIGroup is - required. - type: string - kind: - description: Kind is the type of resource being - referenced - type: string - name: - description: Name is the name of resource being - referenced - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - dataSourceRef: - description: 'dataSourceRef specifies the object from - which to populate the volume with data, if a non-empty - volume is desired. This may be any object from a - non-empty API group (non core object) or a PersistentVolumeClaim - object. When this field is specified, volume binding - will only succeed if the type of the specified object - matches some installed volume populator or dynamic - provisioner. This field will replace the functionality - of the dataSource field and as such if both fields - are non-empty, they must have the same value. For - backwards compatibility, when namespace isn''t specified - in dataSourceRef, both fields (dataSource and dataSourceRef) - will be set to the same value automatically if one - of them is empty and the other is non-empty. When - namespace is specified in dataSourceRef, dataSource - isn''t set to the same value and must be empty. - There are three important differences between dataSource - and dataSourceRef: * While dataSource only allows - two specific types of objects, dataSourceRef allows - any non-core object, as well as PersistentVolumeClaim - objects. * While dataSource ignores disallowed values - (dropping them), dataSourceRef preserves all values, - and generates an error if a disallowed value is - specified. * While dataSource only allows local - objects, dataSourceRef allows objects in any namespaces. - (Beta) Using this field requires the AnyVolumeDataSource - feature gate to be enabled. (Alpha) Using the namespace - field of dataSourceRef requires the CrossNamespaceVolumeDataSource - feature gate to be enabled.' - properties: - apiGroup: - description: APIGroup is the group for the resource - being referenced. If APIGroup is not specified, - the specified Kind must be in the core API group. - For any other third-party types, APIGroup is - required. - type: string - kind: - description: Kind is the type of resource being - referenced - type: string - name: - description: Name is the name of resource being - referenced - type: string - namespace: - description: Namespace is the namespace of resource - being referenced Note that when a namespace - is specified, a gateway.networking.k8s.io/ReferenceGrant - object is required in the referent namespace - to allow that namespace's owner to accept the - reference. See the ReferenceGrant documentation - for details. (Alpha) This field requires the - CrossNamespaceVolumeDataSource feature gate - to be enabled. - type: string - required: - - kind - - name - type: object + x-kubernetes-preserve-unknown-fields: true resources: - description: 'resources represents the minimum resources - the volume should have. If RecoverVolumeExpansionFailure - feature is enabled users are allowed to specify - resource requirements that are lower than previous - value but must still be higher than capacity recorded - in the status field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + description: 'Represents the minimum resources the + volume should have. If the RecoverVolumeExpansionFailure + feature is enabled, users are allowed to specify + resource requirements that are lower than the previous + value but must still be higher than the capacity + recorded in the status field of the claim. More + info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources.' properties: claims: description: "Claims lists the names of resources, @@ -816,222 +701,18 @@ spec: exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object - selector: - description: selector is a label query over volumes - to consider for binding. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true storageClassName: - description: 'storageClassName is the name of the - StorageClass required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + description: 'The name of the StorageClass required + by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1.' type: string volumeMode: - description: volumeMode defines what type of volume - is required by the claim. Value of Filesystem is - implied when not included in claim spec. - type: string - volumeName: - description: volumeName is the binding reference to - the PersistentVolume backing this claim. - type: string - type: object - status: - description: 'status represents the current information/status - of a persistent volume claim. Read-only. More info: - https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' - properties: - accessModes: - description: 'accessModes contains the actual access - modes the volume backing the PVC has. More info: - https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - allocatedResourceStatuses: - additionalProperties: - description: When a controller receives persistentvolume - claim update with ClaimResourceStatus for a resource - that it does not recognizes, then it should ignore - that update and let other controllers handle it. - type: string - description: "allocatedResourceStatuses stores status - of resource being resized for the given PVC. Key - names follow standard Kubernetes label syntax. Valid - values are either: * Un-prefixed keys: - storage - - the capacity of the volume. * Custom resources - must use implementation-defined prefixed names such - as \"example.com/my-custom-resource\" Apart from - above values - keys that are unprefixed or have - kubernetes.io prefix are considered reserved and - hence may not be used. \n ClaimResourceStatus can - be in any of following states: - ControllerResizeInProgress: - State set when resize controller starts resizing - the volume in control-plane. - ControllerResizeFailed: - State set when resize has failed in resize controller - with a terminal error. - NodeResizePending: State - set when resize controller has finished resizing - the volume but further resizing of volume is needed - on the node. - NodeResizeInProgress: State set when - kubelet starts resizing the volume. - NodeResizeFailed: - State set when resizing has failed in kubelet with - a terminal error. Transient errors don't set NodeResizeFailed. - For example: if expanding a PVC for more capacity - - this field can be one of the following states: - - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeFailed\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizePending\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeFailed\" When this field is not set, - it means that no resize operation is in progress - for the given PVC. \n A controller that receives - PVC update with previously unknown resourceName - or ClaimResourceStatus should ignore the update - for the purpose it was designed. For example - a - controller that only is responsible for resizing - capacity of the volume, should ignore PVC updates - that change other valid resources associated with - PVC. \n This is an alpha field and requires enabling - RecoverVolumeExpansionFailure feature." - type: object - x-kubernetes-map-type: granular - allocatedResources: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: "allocatedResources tracks the resources - allocated to a PVC including its capacity. Key names - follow standard Kubernetes label syntax. Valid values - are either: * Un-prefixed keys: - storage - the - capacity of the volume. * Custom resources must - use implementation-defined prefixed names such as - \"example.com/my-custom-resource\" Apart from above - values - keys that are unprefixed or have kubernetes.io - prefix are considered reserved and hence may not - be used. \n Capacity reported here may be larger - than the actual capacity when a volume expansion - operation is requested. For storage quota, the larger - value from allocatedResources and PVC.spec.resources - is used. If allocatedResources is not set, PVC.spec.resources - alone is used for quota calculation. If a volume - expansion capacity request is lowered, allocatedResources - is only lowered if there are no expansion operations - in progress and if the actual volume capacity is - equal or lower than the requested capacity. \n A - controller that receives PVC update with previously - unknown resourceName should ignore the update for - the purpose it was designed. For example - a controller - that only is responsible for resizing capacity of - the volume, should ignore PVC updates that change - other valid resources associated with PVC. \n This - is an alpha field and requires enabling RecoverVolumeExpansionFailure - feature." - type: object - capacity: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: capacity represents the actual resources - of the underlying volume. - type: object - conditions: - description: conditions is the current Condition of - persistent volume claim. If underlying persistent - volume is being resized then the Condition will - be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition contains - details about state of pvc - properties: - lastProbeTime: - description: lastProbeTime is the time we probed - the condition. - format: date-time - type: string - lastTransitionTime: - description: lastTransitionTime is the time - the condition transitioned from one status - to another. - format: date-time - type: string - message: - description: message is the human-readable message - indicating details about last transition. - type: string - reason: - description: reason is a unique, this should - be a short, machine understandable string - that gives the reason for condition's last - transition. If it reports "ResizeStarted" - that means the underlying persistent volume - is being resized. - type: string - status: - type: string - type: - description: PersistentVolumeClaimConditionType - is a valid value of PersistentVolumeClaimCondition.Type - type: string - required: - - status - - type - type: object - type: array - phase: - description: phase represents the current phase of - PersistentVolumeClaim. + description: Defines what type of volume is required + by the claim, either Block or Filesystem. type: string type: object + required: + - name type: object type: array volumeMounts: diff --git a/deploy/helm/crds/apps.kubeblocks.io_opsrequests.yaml b/deploy/helm/crds/apps.kubeblocks.io_opsrequests.yaml index 4a13b824dd7..a689e1befb6 100644 --- a/deploy/helm/crds/apps.kubeblocks.io_opsrequests.yaml +++ b/deploy/helm/crds/apps.kubeblocks.io_opsrequests.yaml @@ -114,16 +114,15 @@ spec: customSpec: description: Specifies a custom operation as defined by OpsDefinition. properties: - components: + items: description: Defines which components need to perform the actions - defined by this OpsDefinition. At least one component is required. - The components are identified by their name and can be merged - or retained. + defined by this OpsDefinition. At least one component/shardComponent + is required. The components are identified by their name and + can be merged or retained. items: properties: - name: - description: Specifies the unique identifier of the cluster - component + componentName: + description: Specifies the name of the cluster component. type: string parameters: description: Represents the parameters for this operation @@ -147,14 +146,17 @@ spec: x-kubernetes-list-map-keys: - name x-kubernetes-list-type: map - required: - - name + shardingName: + description: Specifies the name of the cluster sharding + component. + type: string type: object + x-kubernetes-validations: + - message: either componentName or shardingName + rule: (has(self.componentName) && !has(self.shardingName)) + || (has(self.shardingName) && !has(self.componentName)) minItems: 1 type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map opsDefinitionRef: description: Is a reference to an OpsDefinition. type: string @@ -173,7 +175,7 @@ spec: serviceAccountName: type: string required: - - components + - items - opsDefinitionRef type: object expose: @@ -185,7 +187,7 @@ spec: type: string services: description: A list of services that are to be exposed or removed. - If componentNamem is not specified, each `OpsService` in the + If componentName is not specified, each `OpsService` in the list must specify ports and selectors. items: properties: @@ -365,14 +367,10 @@ spec: - Disable type: string required: - - componentName - services - switch type: object type: array - x-kubernetes-list-map-keys: - - componentName - x-kubernetes-list-type: map force: description: Indicates if pre-checks should be bypassed, allowing the opsRequest to execute immediately. If set to true, pre-checks @@ -694,166 +692,40 @@ spec: description: Defines VolumeClaimTemplates to override. Add new or override existing volume claim templates. items: - description: PersistentVolumeClaim is a user's request - for and claim to a persistent volume properties: - apiVersion: - description: 'APIVersion defines the versioned schema - of this representation of an object. Servers should - convert recognized schemas to the latest internal - value, and may reject unrecognized values. More - info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing - the REST resource this object represents. Servers - may infer this from the endpoint the client submits - requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + name: + description: "Refers to the name of a volumeMount + defined in either: \n - `componentDefinition.spec.runtime.containers[*].volumeMounts` + - `clusterDefinition.spec.componentDefs[*].podSpec.containers[*].volumeMounts` + (deprecated) \n The value of `name` must match + the `name` field of a volumeMount specified in + the corresponding `volumeMounts` array." type: string - metadata: - description: 'Standard object''s metadata. More - info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata' - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object spec: - description: 'spec defines the desired characteristics - of a volume requested by a pod author. More info: - https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + description: "Defines the desired characteristics + of a PersistentVolumeClaim that will be created + for the volume with the mount name specified in + the `name` field. \n When a Pod is created for + this ClusterComponent, a new PVC will be created + based on the specification defined in the `spec` + field. The PVC will be associated with the volume + mount specified by the `name` field." properties: accessModes: - description: 'accessModes contains the desired - access modes the volume should have. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + description: 'Contains the desired access modes + the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1.' items: type: string type: array - dataSource: - description: 'dataSource field can be used to - specify either: * An existing VolumeSnapshot - object (snapshot.storage.k8s.io/VolumeSnapshot) - * An existing PVC (PersistentVolumeClaim) - If the provisioner or an external controller - can support the specified data source, it - will create a new volume based on the contents - of the specified data source. When the AnyVolumeDataSource - feature gate is enabled, dataSource contents - will be copied to dataSourceRef, and dataSourceRef - contents will be copied to dataSource when - dataSourceRef.namespace is not specified. - If the namespace is specified, then dataSourceRef - will not be copied to dataSource.' - properties: - apiGroup: - description: APIGroup is the group for the - resource being referenced. If APIGroup - is not specified, the specified Kind must - be in the core API group. For any other - third-party types, APIGroup is required. - type: string - kind: - description: Kind is the type of resource - being referenced - type: string - name: - description: Name is the name of resource - being referenced - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - dataSourceRef: - description: 'dataSourceRef specifies the object - from which to populate the volume with data, - if a non-empty volume is desired. This may - be any object from a non-empty API group (non - core object) or a PersistentVolumeClaim object. - When this field is specified, volume binding - will only succeed if the type of the specified - object matches some installed volume populator - or dynamic provisioner. This field will replace - the functionality of the dataSource field - and as such if both fields are non-empty, - they must have the same value. For backwards - compatibility, when namespace isn''t specified - in dataSourceRef, both fields (dataSource - and dataSourceRef) will be set to the same - value automatically if one of them is empty - and the other is non-empty. When namespace - is specified in dataSourceRef, dataSource - isn''t set to the same value and must be empty. - There are three important differences between - dataSource and dataSourceRef: * While dataSource - only allows two specific types of objects, - dataSourceRef allows any non-core object, - as well as PersistentVolumeClaim objects. - * While dataSource ignores disallowed values - (dropping them), dataSourceRef preserves all - values, and generates an error if a disallowed - value is specified. * While dataSource only - allows local objects, dataSourceRef allows - objects in any namespaces. (Beta) Using this - field requires the AnyVolumeDataSource feature - gate to be enabled. (Alpha) Using the namespace - field of dataSourceRef requires the CrossNamespaceVolumeDataSource - feature gate to be enabled.' - properties: - apiGroup: - description: APIGroup is the group for the - resource being referenced. If APIGroup - is not specified, the specified Kind must - be in the core API group. For any other - third-party types, APIGroup is required. - type: string - kind: - description: Kind is the type of resource - being referenced - type: string - name: - description: Name is the name of resource - being referenced - type: string - namespace: - description: Namespace is the namespace - of resource being referenced Note that - when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant - object is required in the referent namespace - to allow that namespace's owner to accept - the reference. See the ReferenceGrant - documentation for details. (Alpha) This - field requires the CrossNamespaceVolumeDataSource - feature gate to be enabled. - type: string - required: - - kind - - name - type: object + x-kubernetes-preserve-unknown-fields: true resources: - description: 'resources represents the minimum - resources the volume should have. If RecoverVolumeExpansionFailure - feature is enabled users are allowed to specify + description: 'Represents the minimum resources + the volume should have. If the RecoverVolumeExpansionFailure + feature is enabled, users are allowed to specify resource requirements that are lower than - previous value but must still be higher than - capacity recorded in the status field of the - claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + the previous value but must still be higher + than the capacity recorded in the status field + of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources.' properties: claims: description: "Claims lists the names of @@ -908,235 +780,18 @@ spec: More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object - selector: - description: selector is a label query over - volumes to consider for binding. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true storageClassName: - description: 'storageClassName is the name of - the StorageClass required by the claim. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + description: 'The name of the StorageClass required + by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1.' type: string volumeMode: - description: volumeMode defines what type of - volume is required by the claim. Value of - Filesystem is implied when not included in - claim spec. - type: string - volumeName: - description: volumeName is the binding reference - to the PersistentVolume backing this claim. - type: string - type: object - status: - description: 'status represents the current information/status - of a persistent volume claim. Read-only. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' - properties: - accessModes: - description: 'accessModes contains the actual - access modes the volume backing the PVC has. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - allocatedResourceStatuses: - additionalProperties: - description: When a controller receives persistentvolume - claim update with ClaimResourceStatus for - a resource that it does not recognizes, - then it should ignore that update and let - other controllers handle it. - type: string - description: "allocatedResourceStatuses stores - status of resource being resized for the given - PVC. Key names follow standard Kubernetes - label syntax. Valid values are either: * Un-prefixed - keys: - storage - the capacity of the volume. - * Custom resources must use implementation-defined - prefixed names such as \"example.com/my-custom-resource\" - Apart from above values - keys that are unprefixed - or have kubernetes.io prefix are considered - reserved and hence may not be used. \n ClaimResourceStatus - can be in any of following states: - ControllerResizeInProgress: - State set when resize controller starts resizing - the volume in control-plane. - ControllerResizeFailed: - State set when resize has failed in resize - controller with a terminal error. - NodeResizePending: - State set when resize controller has finished - resizing the volume but further resizing of - volume is needed on the node. - NodeResizeInProgress: - State set when kubelet starts resizing the - volume. - NodeResizeFailed: State set when - resizing has failed in kubelet with a terminal - error. Transient errors don't set NodeResizeFailed. - For example: if expanding a PVC for more capacity - - this field can be one of the following states: - - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeFailed\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizePending\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeFailed\" When this field is - not set, it means that no resize operation - is in progress for the given PVC. \n A controller - that receives PVC update with previously unknown - resourceName or ClaimResourceStatus should - ignore the update for the purpose it was designed. - For example - a controller that only is responsible - for resizing capacity of the volume, should - ignore PVC updates that change other valid - resources associated with PVC. \n This is - an alpha field and requires enabling RecoverVolumeExpansionFailure - feature." - type: object - x-kubernetes-map-type: granular - allocatedResources: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: "allocatedResources tracks the - resources allocated to a PVC including its - capacity. Key names follow standard Kubernetes - label syntax. Valid values are either: * Un-prefixed - keys: - storage - the capacity of the volume. - * Custom resources must use implementation-defined - prefixed names such as \"example.com/my-custom-resource\" - Apart from above values - keys that are unprefixed - or have kubernetes.io prefix are considered - reserved and hence may not be used. \n Capacity - reported here may be larger than the actual - capacity when a volume expansion operation - is requested. For storage quota, the larger - value from allocatedResources and PVC.spec.resources - is used. If allocatedResources is not set, - PVC.spec.resources alone is used for quota - calculation. If a volume expansion capacity - request is lowered, allocatedResources is - only lowered if there are no expansion operations - in progress and if the actual volume capacity - is equal or lower than the requested capacity. - \n A controller that receives PVC update with - previously unknown resourceName should ignore - the update for the purpose it was designed. - For example - a controller that only is responsible - for resizing capacity of the volume, should - ignore PVC updates that change other valid - resources associated with PVC. \n This is - an alpha field and requires enabling RecoverVolumeExpansionFailure - feature." - type: object - capacity: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: capacity represents the actual - resources of the underlying volume. - type: object - conditions: - description: conditions is the current Condition - of persistent volume claim. If underlying - persistent volume is being resized then the - Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition - contains details about state of pvc - properties: - lastProbeTime: - description: lastProbeTime is the time - we probed the condition. - format: date-time - type: string - lastTransitionTime: - description: lastTransitionTime is the - time the condition transitioned from - one status to another. - format: date-time - type: string - message: - description: message is the human-readable - message indicating details about last - transition. - type: string - reason: - description: reason is a unique, this - should be a short, machine understandable - string that gives the reason for condition's - last transition. If it reports "ResizeStarted" - that means the underlying persistent - volume is being resized. - type: string - status: - type: string - type: - description: PersistentVolumeClaimConditionType - is a valid value of PersistentVolumeClaimCondition.Type - type: string - required: - - status - - type - type: object - type: array - phase: - description: phase represents the current phase - of PersistentVolumeClaim. + description: Defines what type of volume is + required by the claim, either Block or Filesystem. type: string type: object + required: + - name type: object type: array volumeMounts: @@ -2997,14 +2652,17 @@ spec: format: int32 minimum: 0 type: integer + shardingName: + description: Specifies the name of the cluster sharding component. + type: string required: - - componentName - replicas type: object + x-kubernetes-validations: + - message: either componentName or shardingName + rule: (has(self.componentName) && !has(self.shardingName)) || + (has(self.shardingName) && !has(self.componentName)) type: array - x-kubernetes-list-map-keys: - - componentName - x-kubernetes-list-type: map x-kubernetes-validations: - message: forbidden to update spec.horizontalScaling rule: self == oldSelf @@ -3162,14 +2820,17 @@ spec: - name type: object type: array + shardingName: + description: Specifies the name of the cluster sharding component. + type: string required: - - componentName - instances type: object + x-kubernetes-validations: + - message: either componentName or shardingName + rule: (has(self.componentName) && !has(self.shardingName)) || + (has(self.shardingName) && !has(self.componentName)) type: array - x-kubernetes-list-map-keys: - - componentName - x-kubernetes-list-type: map x-kubernetes-validations: - message: forbidden to update spec.rebuildFrom rule: self == oldSelf @@ -3252,15 +2913,16 @@ spec: x-kubernetes-list-map-keys: - name x-kubernetes-list-type: map + shardingName: + description: Specifies the name of the cluster sharding component. + type: string required: - - componentName - configurations type: object x-kubernetes-validations: - - message: forbidden to update spec.reconfigure - rule: self == oldSelf - - message: Value can not be empty - rule: self.configurations.size() > 0 + - message: either componentName or shardingName + rule: (has(self.componentName) && !has(self.shardingName)) || (has(self.shardingName) + && !has(self.componentName)) reconfigures: description: Defines the variables that need to input when updating configuration. @@ -3345,30 +3007,38 @@ spec: x-kubernetes-list-map-keys: - name x-kubernetes-list-type: map + shardingName: + description: Specifies the name of the cluster sharding component. + type: string required: - - componentName - configurations type: object + x-kubernetes-validations: + - message: either componentName or shardingName + rule: (has(self.componentName) && !has(self.shardingName)) || + (has(self.shardingName) && !has(self.componentName)) type: array - x-kubernetes-list-map-keys: - - componentName - x-kubernetes-list-type: map + x-kubernetes-validations: + - message: forbidden to update spec.reconfigure + rule: self == oldSelf restart: description: Restarts the specified components. items: description: ComponentOps represents the common variables required - for operations within the scope of a component. + for operations within the scope of a normal component/shard component. properties: componentName: description: Specifies the name of the cluster component. type: string - required: - - componentName + shardingName: + description: Specifies the name of the cluster sharding component. + type: string type: object + x-kubernetes-validations: + - message: either componentName or shardingName + rule: (has(self.componentName) && !has(self.shardingName)) || + (has(self.shardingName) && !has(self.componentName)) type: array - x-kubernetes-list-map-keys: - - componentName - x-kubernetes-list-type: map x-kubernetes-validations: - message: forbidden to update spec.restart rule: self == oldSelf @@ -3592,9 +3262,14 @@ spec: x-kubernetes-validations: - message: forbidden to update spec.scriptSpec.script.selector rule: self == oldSelf - required: - - componentName + shardingName: + description: Specifies the name of the cluster sharding component. + type: string type: object + x-kubernetes-validations: + - message: either componentName or shardingName + rule: (has(self.componentName) && !has(self.shardingName)) || (has(self.shardingName) + && !has(self.componentName)) switchover: description: Switches over the specified components. items: @@ -3617,14 +3292,17 @@ spec: will be executed, and it is mandatory that clusterDefinition.componentDefs[x].switchoverSpec.withCandidate is not left blank." type: string + shardingName: + description: Specifies the name of the cluster sharding component. + type: string required: - - componentName - instanceName type: object + x-kubernetes-validations: + - message: either componentName or shardingName + rule: (has(self.componentName) && !has(self.shardingName)) || + (has(self.shardingName) && !has(self.componentName)) type: array - x-kubernetes-list-map-keys: - - componentName - x-kubernetes-list-type: map x-kubernetes-validations: - message: forbidden to update spec.switchover rule: self == oldSelf @@ -3706,6 +3384,91 @@ spec: componentName: description: Specifies the name of the cluster component. type: string + instances: + description: Specifies the instance template that need to vertical + scale. + items: + properties: + claims: + description: "Claims lists the names of resources, defined + in spec.resourceClaims, that are used by this container. + \n This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. \n This field + is immutable. It can only be set for containers." + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry + in pod.spec.resourceClaims of the Pod where this + field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + name: + description: Refer to the instance template name of the + component or sharding. + type: string + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of + compute resources required. If Requests is omitted for + a container, it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined value. + Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + volumeClaimTemplates: + description: volumeClaimTemplates specifies the storage + size and volumeClaimTemplate name. + items: + properties: + name: + description: A reference to the volumeClaimTemplate + name from the cluster components. + type: string + storage: + anyOf: + - type: integer + - type: string + description: Specifies the requested storage size + for the volume. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - name + - storage + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + required: + - name + - volumeClaimTemplates + type: object + x-kubernetes-preserve-unknown-fields: true + type: array limits: additionalProperties: anyOf: @@ -3729,14 +3492,16 @@ spec: to an implementation-defined value. Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object - required: - - componentName + shardingName: + description: Specifies the name of the cluster sharding component. + type: string type: object x-kubernetes-preserve-unknown-fields: true + x-kubernetes-validations: + - message: either componentName or shardingName + rule: (has(self.componentName) && !has(self.shardingName)) || + (has(self.shardingName) && !has(self.componentName)) type: array - x-kubernetes-list-map-keys: - - componentName - x-kubernetes-list-type: map volumeExpansion: description: 'Note: Quantity struct can not do immutable check by CEL. Defines what component and volumeClaimTemplate need to expand @@ -3748,6 +3513,94 @@ spec: componentName: description: Specifies the name of the cluster component. type: string + instances: + description: Specifies the instance template that need to volume + expand. + items: + properties: + claims: + description: "Claims lists the names of resources, defined + in spec.resourceClaims, that are used by this container. + \n This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. \n This field + is immutable. It can only be set for containers." + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry + in pod.spec.resourceClaims of the Pod where this + field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + name: + description: Refer to the instance template name of the + component or sharding. + type: string + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of + compute resources required. If Requests is omitted for + a container, it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined value. + Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + volumeClaimTemplates: + description: volumeClaimTemplates specifies the storage + size and volumeClaimTemplate name. + items: + properties: + name: + description: A reference to the volumeClaimTemplate + name from the cluster components. + type: string + storage: + anyOf: + - type: integer + - type: string + description: Specifies the requested storage size + for the volume. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - name + - storage + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + required: + - name + - volumeClaimTemplates + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + shardingName: + description: Specifies the name of the cluster sharding component. + type: string volumeClaimTemplates: description: volumeClaimTemplates specifies the storage size and volumeClaimTemplate name. @@ -3774,13 +3627,13 @@ spec: - name x-kubernetes-list-type: map required: - - componentName - volumeClaimTemplates type: object + x-kubernetes-validations: + - message: either componentName or shardingName + rule: (has(self.componentName) && !has(self.shardingName)) || + (has(self.shardingName) && !has(self.componentName)) type: array - x-kubernetes-list-map-keys: - - componentName - x-kubernetes-list-type: map required: - clusterRef - type @@ -4152,174 +4005,43 @@ spec: description: Defines VolumeClaimTemplates to override. Add new or override existing volume claim templates. items: - description: PersistentVolumeClaim is a user's request - for and claim to a persistent volume properties: - apiVersion: - description: 'APIVersion defines the versioned - schema of this representation of an object. - Servers should convert recognized schemas - to the latest internal value, and may reject - unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing - the REST resource this object represents. - Servers may infer this from the endpoint the - client submits requests to. Cannot be updated. - In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + name: + description: "Refers to the name of a volumeMount + defined in either: \n - `componentDefinition.spec.runtime.containers[*].volumeMounts` + - `clusterDefinition.spec.componentDefs[*].podSpec.containers[*].volumeMounts` + (deprecated) \n The value of `name` must match + the `name` field of a volumeMount specified + in the corresponding `volumeMounts` array." type: string - metadata: - description: 'Standard object''s metadata. More - info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata' - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object spec: - description: 'spec defines the desired characteristics - of a volume requested by a pod author. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + description: "Defines the desired characteristics + of a PersistentVolumeClaim that will be created + for the volume with the mount name specified + in the `name` field. \n When a Pod is created + for this ClusterComponent, a new PVC will + be created based on the specification defined + in the `spec` field. The PVC will be associated + with the volume mount specified by the `name` + field." properties: accessModes: - description: 'accessModes contains the desired - access modes the volume should have. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + description: 'Contains the desired access + modes the volume should have. More info: + https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1.' items: type: string type: array - dataSource: - description: 'dataSource field can be used - to specify either: * An existing VolumeSnapshot - object (snapshot.storage.k8s.io/VolumeSnapshot) - * An existing PVC (PersistentVolumeClaim) - If the provisioner or an external controller - can support the specified data source, - it will create a new volume based on the - contents of the specified data source. - When the AnyVolumeDataSource feature gate - is enabled, dataSource contents will be - copied to dataSourceRef, and dataSourceRef - contents will be copied to dataSource - when dataSourceRef.namespace is not specified. - If the namespace is specified, then dataSourceRef - will not be copied to dataSource.' - properties: - apiGroup: - description: APIGroup is the group for - the resource being referenced. If - APIGroup is not specified, the specified - Kind must be in the core API group. - For any other third-party types, APIGroup - is required. - type: string - kind: - description: Kind is the type of resource - being referenced - type: string - name: - description: Name is the name of resource - being referenced - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - dataSourceRef: - description: 'dataSourceRef specifies the - object from which to populate the volume - with data, if a non-empty volume is desired. - This may be any object from a non-empty - API group (non core object) or a PersistentVolumeClaim - object. When this field is specified, - volume binding will only succeed if the - type of the specified object matches some - installed volume populator or dynamic - provisioner. This field will replace the - functionality of the dataSource field - and as such if both fields are non-empty, - they must have the same value. For backwards - compatibility, when namespace isn''t specified - in dataSourceRef, both fields (dataSource - and dataSourceRef) will be set to the - same value automatically if one of them - is empty and the other is non-empty. When - namespace is specified in dataSourceRef, - dataSource isn''t set to the same value - and must be empty. There are three important - differences between dataSource and dataSourceRef: - * While dataSource only allows two specific - types of objects, dataSourceRef allows - any non-core object, as well as PersistentVolumeClaim - objects. * While dataSource ignores disallowed - values (dropping them), dataSourceRef - preserves all values, and generates an - error if a disallowed value is specified. - * While dataSource only allows local objects, - dataSourceRef allows objects in any namespaces. - (Beta) Using this field requires the AnyVolumeDataSource - feature gate to be enabled. (Alpha) Using - the namespace field of dataSourceRef requires - the CrossNamespaceVolumeDataSource feature - gate to be enabled.' - properties: - apiGroup: - description: APIGroup is the group for - the resource being referenced. If - APIGroup is not specified, the specified - Kind must be in the core API group. - For any other third-party types, APIGroup - is required. - type: string - kind: - description: Kind is the type of resource - being referenced - type: string - name: - description: Name is the name of resource - being referenced - type: string - namespace: - description: Namespace is the namespace - of resource being referenced Note - that when a namespace is specified, - a gateway.networking.k8s.io/ReferenceGrant - object is required in the referent - namespace to allow that namespace's - owner to accept the reference. See - the ReferenceGrant documentation for - details. (Alpha) This field requires - the CrossNamespaceVolumeDataSource - feature gate to be enabled. - type: string - required: - - kind - - name - type: object + x-kubernetes-preserve-unknown-fields: true resources: - description: 'resources represents the minimum - resources the volume should have. If RecoverVolumeExpansionFailure - feature is enabled users are allowed to - specify resource requirements that are - lower than previous value but must still - be higher than capacity recorded in the - status field of the claim. More info: - https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + description: 'Represents the minimum resources + the volume should have. If the RecoverVolumeExpansionFailure + feature is enabled, users are allowed + to specify resource requirements that + are lower than the previous value but + must still be higher than the capacity + recorded in the status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources.' properties: claims: description: "Claims lists the names @@ -4375,248 +4097,19 @@ spec: info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object - selector: - description: selector is a label query over - volumes to consider for binding. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The - requirements are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label - key that the selector applies - to. - type: string - operator: - description: operator represents - a key's relationship to a set - of values. Valid operators are - In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array - of string values. If the operator - is In or NotIn, the values array - must be non-empty. If the operator - is Exists or DoesNotExist, the - values array must be empty. - This array is replaced during - a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of - {key,value} pairs. A single {key,value} - in the matchLabels map is equivalent - to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are - ANDed. - type: object - type: object - x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true storageClassName: - description: 'storageClassName is the name - of the StorageClass required by the claim. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + description: 'The name of the StorageClass + required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1.' type: string volumeMode: - description: volumeMode defines what type - of volume is required by the claim. Value - of Filesystem is implied when not included - in claim spec. - type: string - volumeName: - description: volumeName is the binding reference - to the PersistentVolume backing this claim. - type: string - type: object - status: - description: 'status represents the current - information/status of a persistent volume - claim. Read-only. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' - properties: - accessModes: - description: 'accessModes contains the actual - access modes the volume backing the PVC - has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - allocatedResourceStatuses: - additionalProperties: - description: When a controller receives - persistentvolume claim update with ClaimResourceStatus - for a resource that it does not recognizes, - then it should ignore that update and - let other controllers handle it. - type: string - description: "allocatedResourceStatuses - stores status of resource being resized - for the given PVC. Key names follow standard - Kubernetes label syntax. Valid values - are either: * Un-prefixed keys: - storage - - the capacity of the volume. * Custom - resources must use implementation-defined - prefixed names such as \"example.com/my-custom-resource\" - Apart from above values - keys that are - unprefixed or have kubernetes.io prefix - are considered reserved and hence may - not be used. \n ClaimResourceStatus can - be in any of following states: - ControllerResizeInProgress: - State set when resize controller starts - resizing the volume in control-plane. - - ControllerResizeFailed: State set when - resize has failed in resize controller - with a terminal error. - NodeResizePending: - State set when resize controller has finished - resizing the volume but further resizing - of volume is needed on the node. - NodeResizeInProgress: - State set when kubelet starts resizing - the volume. - NodeResizeFailed: State - set when resizing has failed in kubelet - with a terminal error. Transient errors - don't set NodeResizeFailed. For example: - if expanding a PVC for more capacity - - this field can be one of the following - states: - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeFailed\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizePending\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeFailed\" When this field - is not set, it means that no resize operation - is in progress for the given PVC. \n A - controller that receives PVC update with - previously unknown resourceName or ClaimResourceStatus - should ignore the update for the purpose - it was designed. For example - a controller - that only is responsible for resizing - capacity of the volume, should ignore - PVC updates that change other valid resources - associated with PVC. \n This is an alpha - field and requires enabling RecoverVolumeExpansionFailure - feature." - type: object - x-kubernetes-map-type: granular - allocatedResources: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: "allocatedResources tracks - the resources allocated to a PVC including - its capacity. Key names follow standard - Kubernetes label syntax. Valid values - are either: * Un-prefixed keys: - storage - - the capacity of the volume. * Custom - resources must use implementation-defined - prefixed names such as \"example.com/my-custom-resource\" - Apart from above values - keys that are - unprefixed or have kubernetes.io prefix - are considered reserved and hence may - not be used. \n Capacity reported here - may be larger than the actual capacity - when a volume expansion operation is requested. - For storage quota, the larger value from - allocatedResources and PVC.spec.resources - is used. If allocatedResources is not - set, PVC.spec.resources alone is used - for quota calculation. If a volume expansion - capacity request is lowered, allocatedResources - is only lowered if there are no expansion - operations in progress and if the actual - volume capacity is equal or lower than - the requested capacity. \n A controller - that receives PVC update with previously - unknown resourceName should ignore the - update for the purpose it was designed. - For example - a controller that only is - responsible for resizing capacity of the - volume, should ignore PVC updates that - change other valid resources associated - with PVC. \n This is an alpha field and - requires enabling RecoverVolumeExpansionFailure - feature." - type: object - capacity: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: capacity represents the actual - resources of the underlying volume. - type: object - conditions: - description: conditions is the current Condition - of persistent volume claim. If underlying - persistent volume is being resized then - the Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition - contains details about state of pvc - properties: - lastProbeTime: - description: lastProbeTime is the - time we probed the condition. - format: date-time - type: string - lastTransitionTime: - description: lastTransitionTime is - the time the condition transitioned - from one status to another. - format: date-time - type: string - message: - description: message is the human-readable - message indicating details about - last transition. - type: string - reason: - description: reason is a unique, this - should be a short, machine understandable - string that gives the reason for - condition's last transition. If - it reports "ResizeStarted" that - means the underlying persistent - volume is being resized. - type: string - status: - type: string - type: - description: PersistentVolumeClaimConditionType - is a valid value of PersistentVolumeClaimCondition.Type - type: string - required: - - status - - type - type: object - type: array - phase: - description: phase represents the current - phase of PersistentVolumeClaim. + description: Defines what type of volume + is required by the claim, either Block + or Filesystem. type: string type: object + required: + - name type: object type: array volumeMounts: @@ -6801,7 +6294,7 @@ spec: - status type: object x-kubernetes-validations: - - message: either objectKey and actionName. + - message: at least one objectKey or actionName. rule: has(self.objectKey) || has(self.actionName) type: array reason: @@ -6817,8 +6310,8 @@ spec: - Replication type: string type: object - description: Records the status information of components changed - due to the operation request. + description: Records the status information of components, including + the sharding component, that have changed due to the operation request. type: object conditions: description: Describes the detailed status of the OpsRequest. @@ -7238,174 +6731,43 @@ spec: description: Defines VolumeClaimTemplates to override. Add new or override existing volume claim templates. items: - description: PersistentVolumeClaim is a user's request - for and claim to a persistent volume properties: - apiVersion: - description: 'APIVersion defines the versioned - schema of this representation of an object. - Servers should convert recognized schemas - to the latest internal value, and may reject - unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing - the REST resource this object represents. - Servers may infer this from the endpoint the - client submits requests to. Cannot be updated. - In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + name: + description: "Refers to the name of a volumeMount + defined in either: \n - `componentDefinition.spec.runtime.containers[*].volumeMounts` + - `clusterDefinition.spec.componentDefs[*].podSpec.containers[*].volumeMounts` + (deprecated) \n The value of `name` must match + the `name` field of a volumeMount specified + in the corresponding `volumeMounts` array." type: string - metadata: - description: 'Standard object''s metadata. More - info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata' - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object spec: - description: 'spec defines the desired characteristics - of a volume requested by a pod author. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + description: "Defines the desired characteristics + of a PersistentVolumeClaim that will be created + for the volume with the mount name specified + in the `name` field. \n When a Pod is created + for this ClusterComponent, a new PVC will + be created based on the specification defined + in the `spec` field. The PVC will be associated + with the volume mount specified by the `name` + field." properties: accessModes: - description: 'accessModes contains the desired - access modes the volume should have. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + description: 'Contains the desired access + modes the volume should have. More info: + https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1.' items: type: string type: array - dataSource: - description: 'dataSource field can be used - to specify either: * An existing VolumeSnapshot - object (snapshot.storage.k8s.io/VolumeSnapshot) - * An existing PVC (PersistentVolumeClaim) - If the provisioner or an external controller - can support the specified data source, - it will create a new volume based on the - contents of the specified data source. - When the AnyVolumeDataSource feature gate - is enabled, dataSource contents will be - copied to dataSourceRef, and dataSourceRef - contents will be copied to dataSource - when dataSourceRef.namespace is not specified. - If the namespace is specified, then dataSourceRef - will not be copied to dataSource.' - properties: - apiGroup: - description: APIGroup is the group for - the resource being referenced. If - APIGroup is not specified, the specified - Kind must be in the core API group. - For any other third-party types, APIGroup - is required. - type: string - kind: - description: Kind is the type of resource - being referenced - type: string - name: - description: Name is the name of resource - being referenced - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - dataSourceRef: - description: 'dataSourceRef specifies the - object from which to populate the volume - with data, if a non-empty volume is desired. - This may be any object from a non-empty - API group (non core object) or a PersistentVolumeClaim - object. When this field is specified, - volume binding will only succeed if the - type of the specified object matches some - installed volume populator or dynamic - provisioner. This field will replace the - functionality of the dataSource field - and as such if both fields are non-empty, - they must have the same value. For backwards - compatibility, when namespace isn''t specified - in dataSourceRef, both fields (dataSource - and dataSourceRef) will be set to the - same value automatically if one of them - is empty and the other is non-empty. When - namespace is specified in dataSourceRef, - dataSource isn''t set to the same value - and must be empty. There are three important - differences between dataSource and dataSourceRef: - * While dataSource only allows two specific - types of objects, dataSourceRef allows - any non-core object, as well as PersistentVolumeClaim - objects. * While dataSource ignores disallowed - values (dropping them), dataSourceRef - preserves all values, and generates an - error if a disallowed value is specified. - * While dataSource only allows local objects, - dataSourceRef allows objects in any namespaces. - (Beta) Using this field requires the AnyVolumeDataSource - feature gate to be enabled. (Alpha) Using - the namespace field of dataSourceRef requires - the CrossNamespaceVolumeDataSource feature - gate to be enabled.' - properties: - apiGroup: - description: APIGroup is the group for - the resource being referenced. If - APIGroup is not specified, the specified - Kind must be in the core API group. - For any other third-party types, APIGroup - is required. - type: string - kind: - description: Kind is the type of resource - being referenced - type: string - name: - description: Name is the name of resource - being referenced - type: string - namespace: - description: Namespace is the namespace - of resource being referenced Note - that when a namespace is specified, - a gateway.networking.k8s.io/ReferenceGrant - object is required in the referent - namespace to allow that namespace's - owner to accept the reference. See - the ReferenceGrant documentation for - details. (Alpha) This field requires - the CrossNamespaceVolumeDataSource - feature gate to be enabled. - type: string - required: - - kind - - name - type: object + x-kubernetes-preserve-unknown-fields: true resources: - description: 'resources represents the minimum - resources the volume should have. If RecoverVolumeExpansionFailure - feature is enabled users are allowed to - specify resource requirements that are - lower than previous value but must still - be higher than capacity recorded in the - status field of the claim. More info: - https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + description: 'Represents the minimum resources + the volume should have. If the RecoverVolumeExpansionFailure + feature is enabled, users are allowed + to specify resource requirements that + are lower than the previous value but + must still be higher than the capacity + recorded in the status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources.' properties: claims: description: "Claims lists the names @@ -7461,248 +6823,19 @@ spec: info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object - selector: - description: selector is a label query over - volumes to consider for binding. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The - requirements are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label - key that the selector applies - to. - type: string - operator: - description: operator represents - a key's relationship to a set - of values. Valid operators are - In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array - of string values. If the operator - is In or NotIn, the values array - must be non-empty. If the operator - is Exists or DoesNotExist, the - values array must be empty. - This array is replaced during - a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of - {key,value} pairs. A single {key,value} - in the matchLabels map is equivalent - to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are - ANDed. - type: object - type: object - x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true storageClassName: - description: 'storageClassName is the name - of the StorageClass required by the claim. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + description: 'The name of the StorageClass + required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1.' type: string volumeMode: - description: volumeMode defines what type - of volume is required by the claim. Value - of Filesystem is implied when not included - in claim spec. - type: string - volumeName: - description: volumeName is the binding reference - to the PersistentVolume backing this claim. - type: string - type: object - status: - description: 'status represents the current - information/status of a persistent volume - claim. Read-only. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' - properties: - accessModes: - description: 'accessModes contains the actual - access modes the volume backing the PVC - has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - allocatedResourceStatuses: - additionalProperties: - description: When a controller receives - persistentvolume claim update with ClaimResourceStatus - for a resource that it does not recognizes, - then it should ignore that update and - let other controllers handle it. - type: string - description: "allocatedResourceStatuses - stores status of resource being resized - for the given PVC. Key names follow standard - Kubernetes label syntax. Valid values - are either: * Un-prefixed keys: - storage - - the capacity of the volume. * Custom - resources must use implementation-defined - prefixed names such as \"example.com/my-custom-resource\" - Apart from above values - keys that are - unprefixed or have kubernetes.io prefix - are considered reserved and hence may - not be used. \n ClaimResourceStatus can - be in any of following states: - ControllerResizeInProgress: - State set when resize controller starts - resizing the volume in control-plane. - - ControllerResizeFailed: State set when - resize has failed in resize controller - with a terminal error. - NodeResizePending: - State set when resize controller has finished - resizing the volume but further resizing - of volume is needed on the node. - NodeResizeInProgress: - State set when kubelet starts resizing - the volume. - NodeResizeFailed: State - set when resizing has failed in kubelet - with a terminal error. Transient errors - don't set NodeResizeFailed. For example: - if expanding a PVC for more capacity - - this field can be one of the following - states: - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeFailed\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizePending\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeInProgress\" - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeFailed\" When this field - is not set, it means that no resize operation - is in progress for the given PVC. \n A - controller that receives PVC update with - previously unknown resourceName or ClaimResourceStatus - should ignore the update for the purpose - it was designed. For example - a controller - that only is responsible for resizing - capacity of the volume, should ignore - PVC updates that change other valid resources - associated with PVC. \n This is an alpha - field and requires enabling RecoverVolumeExpansionFailure - feature." - type: object - x-kubernetes-map-type: granular - allocatedResources: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: "allocatedResources tracks - the resources allocated to a PVC including - its capacity. Key names follow standard - Kubernetes label syntax. Valid values - are either: * Un-prefixed keys: - storage - - the capacity of the volume. * Custom - resources must use implementation-defined - prefixed names such as \"example.com/my-custom-resource\" - Apart from above values - keys that are - unprefixed or have kubernetes.io prefix - are considered reserved and hence may - not be used. \n Capacity reported here - may be larger than the actual capacity - when a volume expansion operation is requested. - For storage quota, the larger value from - allocatedResources and PVC.spec.resources - is used. If allocatedResources is not - set, PVC.spec.resources alone is used - for quota calculation. If a volume expansion - capacity request is lowered, allocatedResources - is only lowered if there are no expansion - operations in progress and if the actual - volume capacity is equal or lower than - the requested capacity. \n A controller - that receives PVC update with previously - unknown resourceName should ignore the - update for the purpose it was designed. - For example - a controller that only is - responsible for resizing capacity of the - volume, should ignore PVC updates that - change other valid resources associated - with PVC. \n This is an alpha field and - requires enabling RecoverVolumeExpansionFailure - feature." - type: object - capacity: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: capacity represents the actual - resources of the underlying volume. - type: object - conditions: - description: conditions is the current Condition - of persistent volume claim. If underlying - persistent volume is being resized then - the Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition - contains details about state of pvc - properties: - lastProbeTime: - description: lastProbeTime is the - time we probed the condition. - format: date-time - type: string - lastTransitionTime: - description: lastTransitionTime is - the time the condition transitioned - from one status to another. - format: date-time - type: string - message: - description: message is the human-readable - message indicating details about - last transition. - type: string - reason: - description: reason is a unique, this - should be a short, machine understandable - string that gives the reason for - condition's last transition. If - it reports "ResizeStarted" that - means the underlying persistent - volume is being resized. - type: string - status: - type: string - type: - description: PersistentVolumeClaimConditionType - is a valid value of PersistentVolumeClaimCondition.Type - type: string - required: - - status - - type - type: object - type: array - phase: - description: phase represents the current - phase of PersistentVolumeClaim. + description: Defines what type of volume + is required by the claim, either Block + or Filesystem. type: string type: object + required: + - name type: object type: array volumeMounts: diff --git a/deploy/helm/crds/workloads.kubeblocks.io_instancesets.yaml b/deploy/helm/crds/workloads.kubeblocks.io_instancesets.yaml index 018a67172a4..b936792ab7b 100644 --- a/deploy/helm/crds/workloads.kubeblocks.io_instancesets.yaml +++ b/deploy/helm/crds/workloads.kubeblocks.io_instancesets.yaml @@ -3259,6 +3259,9 @@ spec: - name type: object type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map memberUpdateStrategy: description: "Members(Pods) update strategy. \n - serial: update Members one by one that guarantee minimum component unavailable time. - diff --git a/docs/developer_docs/api-reference/cluster.md b/docs/developer_docs/api-reference/cluster.md index 8b095da28a3..eff69079ea0 100644 --- a/docs/developer_docs/api-reference/cluster.md +++ b/docs/developer_docs/api-reference/cluster.md @@ -4872,7 +4872,7 @@ This overrides the image and env attributes defined in clusterDefinition.spec.co

ClusterComponentVolumeClaimTemplate

-(Appears on:ClusterComponentSpec, ComponentSpec) +(Appears on:ClusterComponentSpec, ComponentSpec, InstanceTemplate)

@@ -7932,17 +7932,13 @@ and other administrative tasks.

-

ComponentNameSet -(map[string]struct{} alias)

-
-

ComponentOps

-(Appears on:Expose, HorizontalScaling, OpsRequestSpec, RebuildInstance, Reconfigure, ScriptSpec, Switchover, VerticalScaling, VolumeExpansion) +(Appears on:CustomOpsItem, HorizontalScaling, OpsRequestSpec, RebuildInstance, Reconfigure, ScriptSpec, Switchover, VerticalScaling, VolumeExpansion)

-

ComponentOps represents the common variables required for operations within the scope of a component.

+

ComponentOps represents the common variables required for operations within the scope of a normal component/shard component.

@@ -7963,6 +7959,17 @@ string

Specifies the name of the cluster component.

+ + + +
+shardingName
+ +string + +
+

Specifies the name of the cluster sharding component.

+

ComponentRefEnv @@ -11011,7 +11018,7 @@ string -

CustomOpsComponent +

CustomOpsItem

(Appears on:CustomOpsSpec) @@ -11028,13 +11035,17 @@ string -name
+ComponentOps
-string + +ComponentOps + -

Specifies the unique identifier of the cluster component

+

+(Members of ComponentOps are embedded into this type.) +

@@ -11109,16 +11120,16 @@ the calculated number will be rounded up to 1.

-components
+items
- -[]CustomOpsComponent + +[]CustomOpsItem

Defines which components need to perform the actions defined by this OpsDefinition. -At least one component is required. The components are identified by their name and can be merged or retained.

+At least one component/shardComponent is required. The components are identified by their name and can be merged or retained.

@@ -11384,17 +11395,13 @@ If the shell is required, it must be explicitly invoked in the command.

-ComponentOps
+componentName
- -ComponentOps - +string -

-(Members of ComponentOps are embedded into this type.) -

+

Specifies the name of the cluster component.

@@ -11422,7 +11429,7 @@ If set to Enable, the corresponding service will be exposed. Conversely, if set

A list of services that are to be exposed or removed. -If componentNamem is not specified, each OpsService in the list must specify ports and selectors.

+If componentName is not specified, each OpsService in the list must specify ports and selectors.

@@ -12088,8 +12095,8 @@ Add new or override existing volume mounts of the first container in the pod.

volumeClaimTemplates
- -[]Kubernetes core/v1.PersistentVolumeClaim + +[]ClusterComponentVolumeClaimTemplate @@ -12333,7 +12340,7 @@ The resource key is in the list of [pods].

instances
-InstanceTemplate +[]InstanceTemplate @@ -12346,7 +12353,7 @@ InstanceTemplate offlineInstances
-string +[]string @@ -13940,7 +13947,7 @@ map[string]github.com/apecloud/kubeblocks/apis/apps/v1alpha1.OpsRequestComponent (Optional) -

Records the status information of components changed due to the operation request.

+

Records the status information of components, including the sharding component, that have changed due to the operation request.

@@ -14044,7 +14051,7 @@ map[string]*github.com/apecloud/kubeblocks/apis/apps/v1alpha1.ReconfiguringStatu

OpsRequestVolumeClaimTemplate

-(Appears on:LastComponentConfiguration, VolumeExpansion) +(Appears on:LastComponentConfiguration, PartInstanceTemplate, VolumeExpansion)

@@ -14524,7 +14531,7 @@ LastComponentConfiguration

Parameter

-(Appears on:CustomOpsComponent) +(Appears on:CustomOpsItem)

@@ -14694,6 +14701,63 @@ The supported property types include: +

PartInstanceTemplate +

+

+(Appears on:VerticalScaling, VolumeExpansion) +

+
+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Refer to the instance template name of the component or sharding.

+
+ResourceRequirements
+ + +Kubernetes core/v1.ResourceRequirements + + +
+

+(Members of ResourceRequirements are embedded into this type.) +

+

Defines the computational resource size for vertical scaling.

+
+volumeClaimTemplates
+ + +[]OpsRequestVolumeClaimTemplate + + +
+

volumeClaimTemplates specifies the storage size and volumeClaimTemplate name.

+

PasswordConfig

@@ -20186,6 +20250,19 @@ Kubernetes core/v1.ResourceRequirements

Defines the computational resource size for vertical scaling.

+ + +instances
+ + +[]PartInstanceTemplate + + + + +

Specifies the instance template that need to vertical scale.

+ +

VolumeExpansion @@ -20232,6 +20309,19 @@ ComponentOps

volumeClaimTemplates specifies the storage size and volumeClaimTemplate name.

+ + +instances
+ + +[]PartInstanceTemplate + + + + +

Specifies the instance template that need to volume expand.

+ +

VolumeProtectionSpec diff --git a/pkg/constant/const.go b/pkg/constant/const.go index a25c76ff902..585c6d80145 100644 --- a/pkg/constant/const.go +++ b/pkg/constant/const.go @@ -108,6 +108,7 @@ const ( KBManagedByKey = "apps.kubeblocks.io/managed-by" // KBManagedByKey marks resources that auto created PVCNameLabelKey = "apps.kubeblocks.io/pvc-name" VolumeClaimTemplateNameLabelKey = "apps.kubeblocks.io/vct-name" + KBAppComponentInstanceTemplatelabelKey = "apps.kubeblocks.io/instance-template" KBAppServiceVersionKey = "apps.kubeblocks.io/service-version" WorkloadTypeLabelKey = "apps.kubeblocks.io/workload-type" KBAppPodNameLabelKey = "apps.kubeblocks.io/pod-name" diff --git a/pkg/controller/component/rsm_convertor.go b/pkg/controller/component/rsm_convertor.go index 389486d8369..7a573b8be96 100644 --- a/pkg/controller/component/rsm_convertor.go +++ b/pkg/controller/component/rsm_convertor.go @@ -24,6 +24,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" workloads "github.com/apecloud/kubeblocks/apis/workloads/v1alpha1" @@ -166,10 +167,23 @@ func AppsInstanceToWorkloadInstance(instance *appsv1alpha1.InstanceTemplate) *wo Env: instance.Env, Volumes: instance.Volumes, VolumeMounts: instance.VolumeMounts, - VolumeClaimTemplates: instance.VolumeClaimTemplates, + VolumeClaimTemplates: toPersistentVolumeClaims(instance.VolumeClaimTemplates), } } +func toPersistentVolumeClaims(vcts []appsv1alpha1.ClusterComponentVolumeClaimTemplate) []corev1.PersistentVolumeClaim { + var pvcs []corev1.PersistentVolumeClaim + for _, v := range vcts { + pvcs = append(pvcs, corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: v.Name, + }, + Spec: v.Spec.ToV1PersistentVolumeClaimSpec(), + }) + } + return pvcs +} + // parseITSConvertorArgs parses the args of ITS convertor. func parseITSConvertorArgs(args ...any) (*SynthesizedComponent, error) { synthesizeComp, ok := args[0].(*SynthesizedComponent) diff --git a/pkg/controller/instanceset/instance_util.go b/pkg/controller/instanceset/instance_util.go index 7aea6186d2f..074755f2338 100644 --- a/pkg/controller/instanceset/instance_util.go +++ b/pkg/controller/instanceset/instance_util.go @@ -212,14 +212,14 @@ func buildInstanceName2TemplateMap(itsExt *instanceSetExt) (map[string]*instance } func GenerateInstanceNamesFromTemplate(parentName, templateName string, replicas int32, offlineInstances []string) []string { - instanceNames, _ := generateInstanceNames(parentName, templateName, replicas, 0, offlineInstances) + instanceNames, _ := GenerateInstanceNames(parentName, templateName, replicas, 0, offlineInstances) return instanceNames } -// generateInstanceNames generates instance names based on certain rules: +// GenerateInstanceNames generates instance names based on certain rules: // The naming convention for instances (pods) based on the Parent Name, InstanceTemplate Name, and ordinal. // The constructed instance name follows the pattern: $(parent.name)-$(template.name)-$(ordinal). -func generateInstanceNames(parentName, templateName string, +func GenerateInstanceNames(parentName, templateName string, replicas int32, ordinal int32, offlineInstances []string) ([]string, int32) { usedNames := sets.New(offlineInstances...) var instanceNameList []string @@ -274,6 +274,9 @@ func buildInstanceByTemplate(name string, template *instanceTemplateExt, parent AddLabels(constant.VolumeClaimTemplateNameLabelKey, claimTemplate.Name). SetSpec(*claimTemplate.Spec.DeepCopy()). GetObject() + if template.Name != "" { + pvc.Labels[constant.KBAppComponentInstanceTemplatelabelKey] = template.Name + } pvcMap[pvcName] = pvc pvcNameMap[pvcName] = claimTemplate.Name } @@ -431,12 +434,13 @@ func BuildInstanceTemplateRevision(template *corev1.PodTemplateSpec, parent *wor func buildInstanceTemplateExts(itsExt *instanceSetExt) []*instanceTemplateExt { envConfigName := rsm.GetEnvConfigMapName(itsExt.its.Name) defaultTemplate := rsm.BuildPodTemplate(itsExt.its, envConfigName) - makeInstanceTemplateExt := func() *instanceTemplateExt { + makeInstanceTemplateExt := func(templateName string) *instanceTemplateExt { var claims []corev1.PersistentVolumeClaim for _, template := range itsExt.its.Spec.VolumeClaimTemplates { claims = append(claims, *template.DeepCopy()) } return &instanceTemplateExt{ + Name: templateName, PodTemplateSpec: *defaultTemplate.DeepCopy(), VolumeClaimTemplates: claims, } @@ -444,11 +448,10 @@ func buildInstanceTemplateExts(itsExt *instanceSetExt) []*instanceTemplateExt { var instanceTemplateExtList []*instanceTemplateExt for _, template := range itsExt.instanceTemplates { - templateExt := makeInstanceTemplateExt() + templateExt := makeInstanceTemplateExt(template.Name) buildInstanceTemplateExt(*template, templateExt) instanceTemplateExtList = append(instanceTemplateExtList, templateExt) } - return instanceTemplateExtList } diff --git a/pkg/controllerutil/cluster_utils.go b/pkg/controllerutil/cluster_utils.go index 16b97d9c06c..77d2dfd5a4b 100644 --- a/pkg/controllerutil/cluster_utils.go +++ b/pkg/controllerutil/cluster_utils.go @@ -48,3 +48,11 @@ func GetOriginalOrGeneratedComponentSpecByName(ctx context.Context, cli client.R } return nil, nil } + +func TemplateReplicas(template appsv1alpha1.InstanceTemplate) int32 { + replicas := int32(1) + if template.Replicas != nil { + replicas = *template.Replicas + } + return replicas +} diff --git a/pkg/testutil/apps/cluster_instance_set_test_util.go b/pkg/testutil/apps/cluster_instance_set_test_util.go index b301709d913..9e102189680 100644 --- a/pkg/testutil/apps/cluster_instance_set_test_util.go +++ b/pkg/testutil/apps/cluster_instance_set_test_util.go @@ -163,7 +163,8 @@ func MockInstanceSetPod( clusterName, consensusCompName, podName, - podRole, accessMode string) *corev1.Pod { + podRole, accessMode string, + resources ...corev1.ResourceRequirements) *corev1.Pod { var stsUpdateRevision string if its != nil { stsUpdateRevision = its.Status.UpdateRevision @@ -192,32 +193,36 @@ func MockInstanceSetPod( ClaimName: fmt.Sprintf("%s-%s", DataVolumeName, podName), }, }, - }). - AddContainer(corev1.Container{ - Name: DefaultMySQLContainerName, - Image: ApeCloudMySQLImage, - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/hello", - Port: intstr.FromInt(1024), - }, + }) + container := corev1.Container{ + Name: DefaultMySQLContainerName, + Image: ApeCloudMySQLImage, + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/hello", + Port: intstr.FromInt(1024), }, - TimeoutSeconds: 1, - PeriodSeconds: 1, - FailureThreshold: 1, }, - StartupProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - TCPSocket: &corev1.TCPSocketAction{ - Port: intstr.FromInt(1024), - }, + TimeoutSeconds: 1, + PeriodSeconds: 1, + FailureThreshold: 1, + }, + StartupProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + TCPSocket: &corev1.TCPSocketAction{ + Port: intstr.FromInt(1024), }, }, - VolumeMounts: []corev1.VolumeMount{ - {Name: DataVolumeName, MountPath: "/test"}, - }, - }) + }, + VolumeMounts: []corev1.VolumeMount{ + {Name: DataVolumeName, MountPath: "/test"}, + }, + } + if len(resources) > 0 { + container.Resources = resources[0] + } + podFactory.AddContainer(container) if its != nil && its.Labels[constant.AppNameLabelKey] != "" { podFactory.AddAppNameLabel(its.Labels[constant.AppNameLabelKey]) }