# RU Config Validation for Scale Operations Design

## Problem Statement

Currently, when performing scale operations (`ScaleSharedPool` and `UpdateTiDBGroup`), the system does not validate whether the requested node specification meets the minimum requirements defined in the RU (Request Unit) configuration. This can lead to:

1. Users scaling down to node specs that are insufficient for the configured RU limits
2. Potential performance issues or resource starvation
3. Inconsistent state between RU configuration and actual resource allocation

## Goals

1. Enforce RU config minimum requirements during scale operations
2. Allow administrators to bypass validation using `force` flag when necessary
3. Maintain backward compatibility

## Non-Goals

1. Changing the RU config itself
2. Modifying how RU config is applied during initial creation
3. Automatic correction of node specs

## Design

### Overview

Add validation logic in scale operations to check if the requested node specification meets the minimum requirements based on the current RU config. The validation will:

1. Retrieve the RU config for the shared pool
2. Determine the appropriate RU value based on component type
3. Get the minimum required node spec from RU config
4. Validate the requested node spec against the minimum
5. Reject the request if validation fails (unless `force=true`)

### RU Resolution

**Key Point**: RU is defined at TiDBCluster level, not component level.

For validation, we need to determine which TiDBCluster's RU to use based on the component:

| Component Type | RU Source | Notes |
|----------------|-----------|-------|
| TiDB | Associated TiDBCluster's min RU | Component id = tidb group id, need to lookup tidb group to get cluster id |
| TiProxy | Associated TiDBCluster's min RU | Component id = cluster id |
| TiKV | Sum of all TiDBClusters' min RU | Shared pool level resource |
| TiFlash | Sum of all TiDBClusters' min RU | Shared pool level resource |
| PD | Sum of all TiDBClusters' min RU | Shared pool level resource |
| PD_TSO | Sum of all TiDBClusters' min RU | Shared pool level resource |
| PD_SCHEDULING | Sum of all TiDBClusters' min RU | Shared pool level resource |
| TIKV_WORKER | Sum of all TiDBClusters' min RU | Shared pool level resource |
| COPROCESSOR_WORKER | Sum of all TiDBClusters' min RU | Shared pool level resource |
| TIDB_WORKER | Sum of all TiDBClusters' min RU | Shared pool level resource |
| RESOURCE_MANAGER | Sum of all TiDBClusters' min RU | Shared pool level resource |

**Why min RU?**
- Min RU represents the guaranteed/paid capacity that must be supported at all times
- Max RU is the elastic upper limit; actual capacity depends on node spec

### Validation Logic

```go
// validateNodeSpecAgainstRUConfig validates that the requested node spec can support the given min RU.
// The minRU should be determined by the caller based on component type:
// - TiProxy: tidbCluster.RequestUnit.Min (component id = cluster id)
// - TiDB: tidbCluster.RequestUnit.Min (need tidb group lookup to get cluster id)
// - Shared pool components: sum of all TiDBClusters' min RU in the shared pool
func validateNodeSpecAgainstRUConfig(
    ctx context.Context,
    componentType apiv1.ComponentType,
    requestedNodeSpec *entity.NodeSpec,
    ruConfig *entity.RuConfig,
    minRU uint32,
    componentId string,
    force bool,
) error {
    // Get minimum required node spec from RU config based on min RU
    minNodeSpec, err := ruConfig.GetNodeSpec(componentType, minRU)
    if err != nil {
        return err
    }
    
    // Validate requested spec >= minimum spec
    if requestedNodeSpec.GetCpu() < minNodeSpec.GetCpu() ||
       requestedNodeSpec.GetMemoryGi() < minNodeSpec.GetMemoryGi() {
        if force {
            tilog.Warn(ctx, "node spec below RU config minimum for min RU, force flag set, proceeding",
                zap.String("componentType", componentType.String()),
                zap.String("componentId", componentId),
                zap.Uint32("requestedCpu", requestedNodeSpec.GetCpu()),
                zap.Uint32("minCpu", minNodeSpec.GetCpu()),
                zap.Uint32("requestedMemory", requestedNodeSpec.GetMemoryGi()),
                zap.Uint32("minMemory", minNodeSpec.GetMemoryGi()),
                zap.Uint32("minRU", minRU),
            )
            return nil
        }
        return status.Errorf(codes.InvalidArgument, 
            "component %s node spec (cpu: %d, memory: %dGi) cannot support minimum RU %d, required spec: cpu >= %d, memory >= %dGi",
            componentType.String(),
            requestedNodeSpec.GetCpu(), requestedNodeSpec.GetMemoryGi(),
            minRU,
            minNodeSpec.GetCpu(), minNodeSpec.GetMemoryGi())
    }
    
    return nil
}
```

### Affected APIs

1. **ScaleSharedPool** (`POST /sharedPools/{shared_pool_id}:scale`)
   - Modify `mergeScaleComponent` function
   - Get minRU based on component type before validation:
     ```go
     var minRU uint32
     switch component.GetComponentType() {
     case apiv1.ComponentType_TIPROXY:
         tidbCluster, err := s.tidbClusterRepo.Load(ctx, component.GetId(), false)
         minRU = tidbCluster.GetRequestUnit().GetMin()
     case apiv1.ComponentType_TIDB:
         tidbGroup, err := s.tidbGroupRepo.Load(ctx, component.GetId(), false)
         tidbCluster, err := s.tidbClusterRepo.Load(ctx, tidbGroup.GetClusterId(), false)
         minRU = tidbCluster.GetRequestUnit().GetMin()
     default:
         minRU, err = s.getTotalMinRUForSharedPool(ctx, sharedPool.GetId())
     }
     
     err = validateNodeSpecAgainstRUConfig(ctx, componentType, requestedNodeSpec, ruConfig, minRU, component.GetId(), force)
     ```

2. **UpdateTiDBGroup** (`PATCH /clusters/{cluster_id}/tidbGroups/{tidb_group_id}`)
   - Add validation when updating `nodeQuota.nodeSpec`
   - For TiDB component, use `tidbCluster.GetRequestUnit().GetMin()` as minRU
   - Use existing `force` flag

### Error Handling

- **Validation Failure**: Return `InvalidArgument` error with clear message indicating required minimum specs
- **Force Override**: Log warning and proceed
- **RU Config Not Found**: Use default RU config (existing behavior)
- **TiDBCluster Not Found**: Return `Internal` error (should not happen during normal operation)

### Backward Compatibility

- New validation only affects scale operations that specify node spec changes
- Existing scale operations without node spec changes are unaffected
- `force` flag allows bypassing validation for emergency scenarios

## Implementation Plan

### Phase 1: ScaleSharedPool
1. Add helper function `validateNodeSpecAgainstRUConfig`
2. Add helper function `getTotalMinRUForSharedPool` to calculate total min RU for all TiDBClusters in a shared pool
3. Modify `mergeScaleComponent` to:
   - Determine minRU based on component type before calling validation
   - Call `validateNodeSpecAgainstRUConfig` with pre-computed minRU
4. Update unit tests

### Phase 2: UpdateTiDBGroup
1. Add validation in `UpdateTiDBGroup` when updating node spec
2. Update unit tests

### Phase 3: Integration Testing
1. Test with various RU configs
2. Test force flag bypass
3. Test error messages

## Testing Strategy

### Unit Tests

1. **Valid Scale**: Request spec >= minimum spec, should succeed
2. **Invalid Scale**: Request spec < minimum spec, should fail with InvalidArgument
3. **Force Override**: Request spec < minimum spec with force=true, should succeed with warning log
4. **No Spec Change**: Scale without node spec change, validation should be skipped
5. **RU Config Unavailable**: Use default RU config

### Integration Tests

1. Scale TiKV with insufficient spec should fail
2. Scale TiDB with insufficient spec should fail
3. Update TiDBGroup with insufficient spec should fail
4. All above with force=true should succeed

## Rollback Plan

Since this is a validation-only change that can be bypassed with `force` flag, no rollback mechanism is needed. If issues arise:

1. Instruct users to use `force=true` to bypass validation
2. Fix the validation logic
3. Deploy updated version

## Future Work

1. Consider adding similar validation for storage specs (IOPS, throughput)
2. Consider adding validation for node count based on RU config max node count
3. Consider adding dry-run mode to preview validation results
