diff --git a/api-service/main.go b/api-service/main.go index 225979fe..35cbbc4d 100644 --- a/api-service/main.go +++ b/api-service/main.go @@ -165,7 +165,8 @@ func main() { if err != nil { log.Fatalf("Invalid cleanup interval: %v", err) } - cleanManager := service.NewAEnvCleanManager(service.NewKubeCleaner(scheduleClient), interval) + cleanManager := service.NewAEnvCleanManager(scheduleClient, interval). + WithMetrics(middleware.IncrementCleanupSuccess, middleware.IncrementCleanupFailure) go cleanManager.Start() defer cleanManager.Stop() diff --git a/api-service/middleware/metrics.go b/api-service/middleware/metrics.go index 62f851e4..866deeda 100644 --- a/api-service/middleware/metrics.go +++ b/api-service/middleware/metrics.go @@ -64,8 +64,33 @@ var ( }, []string{"method", "endpoint"}, ) + + // Auto cleanup metrics + cleanupSuccessCount = promauto.NewCounter( + prometheus.CounterOpts{ + Name: "auto_cleanup_success_total", + Help: "Total number of successfully auto-cleaned instances", + }, + ) + + cleanupFailureCount = promauto.NewCounter( + prometheus.CounterOpts{ + Name: "auto_cleanup_failure_total", + Help: "Total number of failed auto-cleanup attempts", + }, + ) ) +// IncrementCleanupSuccess increments the cleanup success counter +func IncrementCleanupSuccess() { + cleanupSuccessCount.Inc() +} + +// IncrementCleanupFailure increments the cleanup failure counter +func IncrementCleanupFailure() { + cleanupFailureCount.Inc() +} + // MetricsMiddleware metrics collection middleware func MetricsMiddleware() gin.HandlerFunc { return func(c *gin.Context) { diff --git a/api-service/service/cleanup_service.go b/api-service/service/cleanup_service.go index ff112f43..db957f4b 100644 --- a/api-service/service/cleanup_service.go +++ b/api-service/service/cleanup_service.go @@ -17,40 +17,52 @@ limitations under the License. package service import ( + "api-service/models" "context" "log" "time" ) -type AEnvCleaner interface { - cleanup() -} - type AEnvCleanManager struct { - cleaner AEnvCleaner + envInstanceService EnvInstanceService interval time.Duration ctx context.Context cancel context.CancelFunc + + // Metrics counters + incrementCleanupSuccess func() + incrementCleanupFailure func() } -func NewAEnvCleanManager(cleaner AEnvCleaner, duration time.Duration) *AEnvCleanManager { +func NewAEnvCleanManager(envInstanceService EnvInstanceService, duration time.Duration) *AEnvCleanManager { ctx, cancel := context.WithCancel(context.Background()) AEnvCleanManager := &AEnvCleanManager{ - cleaner: cleaner, + envInstanceService: envInstanceService, interval: duration, ctx: ctx, cancel: cancel, + + // Default metrics functions + incrementCleanupSuccess: func() {}, + incrementCleanupFailure: func() {}, } return AEnvCleanManager } +// WithMetrics sets the metrics functions for the clean manager +func (cm *AEnvCleanManager) WithMetrics(incrementSuccess, incrementFailure func()) *AEnvCleanManager { + cm.incrementCleanupSuccess = incrementSuccess + cm.incrementCleanupFailure = incrementFailure + return cm +} + // Start starts the cleanup service func (cm *AEnvCleanManager) Start() { log.Printf("Starting cleanup service with interval: %v", cm.interval) // Execute cleanup immediately - cm.cleaner.cleanup() + cm.performCleanup() // Start timer ticker := time.NewTicker(cm.interval) @@ -59,7 +71,7 @@ func (cm *AEnvCleanManager) Start() { for { select { case <-ticker.C: - cm.cleaner.cleanup() + cm.performCleanup() case <-cm.ctx.Done(): log.Println("Cleanup service stopped") return @@ -68,24 +80,80 @@ func (cm *AEnvCleanManager) Start() { }() } -// Stop stops the cleanup service -func (cm *AEnvCleanManager) Stop() { - cm.cancel() -} +// performCleanup performs the actual cleanup task by checking TTL expiration +func (cm *AEnvCleanManager) performCleanup() { + log.Println("Starting TTL-based cleanup task...") + + // Get all environment instances + envInstances, err := cm.envInstanceService.ListEnvInstances("") + if err != nil { + log.Printf("Failed to list environment instances: %v", err) + return + } + + if len(envInstances) == 0 { + log.Println("No environment instances found") + return + } + + var deletedCount int -// KubeCleaner cleanup service responsible for periodically cleaning expired EnvInstances -type KubeCleaner struct { - scheduleClient EnvInstanceService + // Check each instance for TTL expiration + for _, instance := range envInstances { + // Skip already terminated instances + if instance.Status == "Terminated" { + continue + } + + // Check if TTL is set and has expired + if cm.isExpired(instance) { + log.Printf("Instance %s has expired (TTL: %s), deleting...", instance.ID, instance.TTL) + err := cm.envInstanceService.DeleteEnvInstance(instance.ID) + if err != nil { + log.Printf("Failed to delete expired instance %s: %v", instance.ID, err) + cm.incrementCleanupFailure() + continue + } + deletedCount++ + cm.incrementCleanupSuccess() + log.Printf("Successfully deleted expired instance %s", instance.ID) + } + } + + log.Printf("TTL-based cleanup task completed. Deleted %d expired instances", deletedCount) } -// NewCleanupService -func NewKubeCleaner(scheduleClient EnvInstanceService) *KubeCleaner { - return &KubeCleaner{ - scheduleClient: scheduleClient, +// isExpired checks if an environment instance has expired based on its TTL and creation time +func (cm *AEnvCleanManager) isExpired(instance *models.EnvInstance) bool { + // If TTL is not set, consider it as non-expiring + if instance.TTL == "" { + return false + } + + // Parse TTL duration + ttlDuration, err := time.ParseDuration(instance.TTL) + if err != nil { + log.Printf("Failed to parse TTL '%s' for instance %s: %v", instance.TTL, instance.ID, err) + return false } + + // Parse creation time using time.DateTime format (2006-01-02 15:04:05) + createdAt, err := time.Parse(time.DateTime, instance.CreatedAt) + if err != nil { + // Fallback to RFC3339 if DateTime parsing fails + createdAt, err = time.Parse(time.RFC3339, instance.CreatedAt) + if err != nil { + log.Printf("Failed to parse creation time '%s' for instance %s: %v", instance.CreatedAt, instance.ID, err) + return false + } + } + + // Check if the instance has expired + expirationTime := createdAt.Add(ttlDuration) + return time.Now().After(expirationTime) } -// cleanup executes cleanup task -func (cs *KubeCleaner) cleanup() { - _ = cs.scheduleClient.Cleanup() +// Stop stops the cleanup service +func (cm *AEnvCleanManager) Stop() { + cm.cancel() } diff --git a/api-service/service/cleanup_service_test.go b/api-service/service/cleanup_service_test.go index a45c8535..c31071f6 100644 --- a/api-service/service/cleanup_service_test.go +++ b/api-service/service/cleanup_service_test.go @@ -17,17 +17,340 @@ limitations under the License. package service import ( - "net/http" + "api-service/models" + backend "envhub/models" + "errors" "testing" "time" ) func TestNewCleanupService(t *testing.T) { - scheduleClient := &ScheduleClient{ - baseURL: "http://6.1.224.11:8080", - httpClient: &http.Client{Timeout: 30 * time.Second}, + // This test is kept for compatibility but doesn't actually test anything meaningful + // since we can't easily instantiate a real ScheduleClient in tests + t.Skip("Skipping integration test that requires real ScheduleClient") +} + +// MockEnvInstanceService is a mock implementation of EnvInstanceService for testing +type MockEnvInstanceService struct { + ListEnvInstancesFunc func(envName string) ([]*models.EnvInstance, error) + DeleteEnvInstanceFunc func(id string) error +} + +func (m *MockEnvInstanceService) GetEnvInstance(id string) (*models.EnvInstance, error) { + return nil, nil +} + +func (m *MockEnvInstanceService) CreateEnvInstance(req *backend.Env) (*models.EnvInstance, error) { + return nil, nil +} + +func (m *MockEnvInstanceService) DeleteEnvInstance(id string) error { + if m.DeleteEnvInstanceFunc != nil { + return m.DeleteEnvInstanceFunc(id) + } + return nil +} + +func (m *MockEnvInstanceService) ListEnvInstances(envName string) ([]*models.EnvInstance, error) { + if m.ListEnvInstancesFunc != nil { + return m.ListEnvInstancesFunc(envName) + } + return nil, nil +} + +func (m *MockEnvInstanceService) Warmup(req *backend.Env) error { + return nil +} + +func (m *MockEnvInstanceService) Cleanup() error { + return nil +} + +// TestPerformCleanupNoInstances tests cleanup when there are no env instances +func TestPerformCleanupNoInstances(t *testing.T) { + // Create mock service that returns empty list + mockService := &MockEnvInstanceService{ + ListEnvInstancesFunc: func(envName string) ([]*models.EnvInstance, error) { + return []*models.EnvInstance{}, nil + }, + } + + // Create clean manager with mock service + manager := NewAEnvCleanManager(mockService, time.Minute) + + // Perform cleanup + manager.performCleanup() + + // Since there are no instances, no delete operations should be called + // The test passes if no panic occurs +} + +// TestPerformCleanupWithExpiredInstances tests cleanup with expired env instances +func TestPerformCleanupWithExpiredInstances(t *testing.T) { + // Create mock service with expired instances + expiredInstance := &models.EnvInstance{ + ID: "test-instance-1", + Status: "Running", + CreatedAt: "2025-01-01 10:00:00", + TTL: "1h", + } + + terminatedInstance := &models.EnvInstance{ + ID: "test-instance-2", + Status: "Terminated", + CreatedAt: "2025-01-01 10:00:00", + TTL: "1h", + } + + activeInstance := &models.EnvInstance{ + ID: "test-instance-3", + Status: "Running", + CreatedAt: time.Now().Format("2006-01-02 15:04:05"), + TTL: "1h", + } + + var deletedInstances []string + mockService := &MockEnvInstanceService{ + ListEnvInstancesFunc: func(envName string) ([]*models.EnvInstance, error) { + return []*models.EnvInstance{expiredInstance, terminatedInstance, activeInstance}, nil + }, + DeleteEnvInstanceFunc: func(id string) error { + deletedInstances = append(deletedInstances, id) + return nil + }, + } + + // Create clean manager with mock service + manager := NewAEnvCleanManager(mockService, time.Minute) + + // Perform cleanup + manager.performCleanup() + + // Verify that only the expired instance was deleted + if len(deletedInstances) != 1 { + t.Errorf("Expected 1 deleted instance, got %d", len(deletedInstances)) + } + + if len(deletedInstances) > 0 && deletedInstances[0] != "test-instance-1" { + t.Errorf("Expected deleted instance ID 'test-instance-1', got '%s'", deletedInstances[0]) + } +} + +// TestPerformCleanupWithDeleteError tests cleanup when delete operation fails +func TestPerformCleanupWithDeleteError(t *testing.T) { + // Create mock service with expired instance that fails to delete + expiredInstance := &models.EnvInstance{ + ID: "test-instance-1", + Status: "Running", + CreatedAt: "2025-01-01 10:00:00", + TTL: "1h", + } + + mockService := &MockEnvInstanceService{ + ListEnvInstancesFunc: func(envName string) ([]*models.EnvInstance, error) { + return []*models.EnvInstance{expiredInstance}, nil + }, + DeleteEnvInstanceFunc: func(id string) error { + return errors.New("delete failed") + }, + } + + // Create clean manager with mock service + manager := NewAEnvCleanManager(mockService, time.Minute) + + // Perform cleanup + manager.performCleanup() + + // The test passes if no panic occurs even when delete fails +} + +// TestPerformCleanupWithListError tests cleanup when listing instances fails +func TestPerformCleanupWithListError(t *testing.T) { + // Create mock service that fails to list instances + mockService := &MockEnvInstanceService{ + ListEnvInstancesFunc: func(envName string) ([]*models.EnvInstance, error) { + return nil, errors.New("list failed") + }, } - manager := NewAEnvCleanManager(NewKubeCleaner(scheduleClient), time.Minute) - manager.Start() + // Create clean manager with mock service + manager := NewAEnvCleanManager(mockService, time.Minute) + + // Perform cleanup + manager.performCleanup() + + // The test passes if no panic occurs even when list fails +} + +// TestIsExpiredWithDateTimeFormat tests isExpired with time.DateTime format +func TestIsExpiredWithDateTimeFormat(t *testing.T) { + // Create instance with DateTime format creation time + expiredInstance := &models.EnvInstance{ + ID: "test-expired-1", + Status: "Running", + CreatedAt: "2025-01-01 10:00:00", // time.DateTime format + TTL: "1h", + } + + activeInstance := &models.EnvInstance{ + ID: "test-active-1", + Status: "Running", + CreatedAt: time.Now().Format(time.DateTime), + TTL: "1h", + } + + mockService := &MockEnvInstanceService{} + manager := NewAEnvCleanManager(mockService, time.Minute) + + // Test expired instance + if !manager.isExpired(expiredInstance) { + t.Errorf("Expected instance %s to be expired", expiredInstance.ID) + } + + // Test active instance + if manager.isExpired(activeInstance) { + t.Errorf("Expected instance %s to be active", activeInstance.ID) + } +} + +// TestIsExpiredWithRFC3339Format tests isExpired with RFC3339 format (fallback) +func TestIsExpiredWithRFC3339Format(t *testing.T) { + // Create instance with RFC3339 format creation time + expiredInstance := &models.EnvInstance{ + ID: "test-expired-rfc3339-1", + Status: "Running", + CreatedAt: "2025-01-01T10:00:00+08:00", // RFC3339 format + TTL: "1h", + } + + activeInstance := &models.EnvInstance{ + ID: "test-active-rfc3339-1", + Status: "Running", + CreatedAt: time.Now().Format(time.RFC3339), + TTL: "1h", + } + + mockService := &MockEnvInstanceService{} + manager := NewAEnvCleanManager(mockService, time.Minute) + + // Test expired instance + if !manager.isExpired(expiredInstance) { + t.Errorf("Expected instance %s to be expired", expiredInstance.ID) + } + + // Test active instance + if manager.isExpired(activeInstance) { + t.Errorf("Expected instance %s to be active", activeInstance.ID) + } +} + +// TestIsExpiredWithInvalidTimeFormat tests isExpired with invalid time format +func TestIsExpiredWithInvalidTimeFormat(t *testing.T) { + // Create instance with invalid time format + invalidInstance := &models.EnvInstance{ + ID: "test-invalid-time", + Status: "Running", + CreatedAt: "invalid-time-format", + TTL: "1h", + } + + mockService := &MockEnvInstanceService{} + manager := NewAEnvCleanManager(mockService, time.Minute) + + // Should return false for invalid time format + if manager.isExpired(invalidInstance) { + t.Errorf("Expected instance %s with invalid time format to not be expired", invalidInstance.ID) + } +} + +// TestIsExpiredWithInvalidTTLFormat tests isExpired with invalid TTL format +func TestIsExpiredWithInvalidTTLFormat(t *testing.T) { + // Create instance with invalid TTL format + invalidInstance := &models.EnvInstance{ + ID: "test-invalid-ttl", + Status: "Running", + CreatedAt: time.Now().Format(time.DateTime), + TTL: "invalid-ttl", + } + + mockService := &MockEnvInstanceService{} + manager := NewAEnvCleanManager(mockService, time.Minute) + + // Should return false for invalid TTL format + if manager.isExpired(invalidInstance) { + t.Errorf("Expected instance %s with invalid TTL format to not be expired", invalidInstance.ID) + } +} + +// TestIsExpiredWithEmptyTTL tests isExpired with empty TTL +func TestIsExpiredWithEmptyTTL(t *testing.T) { + // Create instance with empty TTL + emptyTTLInstance := &models.EnvInstance{ + ID: "test-empty-ttl", + Status: "Running", + CreatedAt: "2025-01-01 10:00:00", + TTL: "", + } + + mockService := &MockEnvInstanceService{} + manager := NewAEnvCleanManager(mockService, time.Minute) + + // Should return false for empty TTL + if manager.isExpired(emptyTTLInstance) { + t.Errorf("Expected instance %s with empty TTL to not be expired", emptyTTLInstance.ID) + } +} + +// TestIsExpiredWithVariousTTLDurations tests isExpired with various TTL durations +func TestIsExpiredWithVariousTTLDurations(t *testing.T) { + testCases := []struct { + name string + createdAt string + ttl string + expected bool + }{ + { + name: "expired with seconds", + createdAt: "2025-01-01 10:00:00", + ttl: "30s", + expected: true, + }, + { + name: "expired with minutes", + createdAt: "2025-01-01 10:00:00", + ttl: "5m", + expected: true, + }, + { + name: "expired with hours", + createdAt: "2025-01-01 10:00:00", + ttl: "2h", + expected: true, + }, + { + name: "active with long TTL", + createdAt: time.Now().Format(time.DateTime), + ttl: "24h", + expected: false, + }, + } + + mockService := &MockEnvInstanceService{} + manager := NewAEnvCleanManager(mockService, time.Minute) + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + instance := &models.EnvInstance{ + ID: "test-" + tc.name, + Status: "Running", + CreatedAt: tc.createdAt, + TTL: tc.ttl, + } + + result := manager.isExpired(instance) + if result != tc.expected { + t.Errorf("Expected isExpired to be %v, got %v for instance %s", tc.expected, result, instance.ID) + } + }) + } } diff --git a/api-service/service/faas_client.go b/api-service/service/faas_client.go index cb185d61..3c937fca 100644 --- a/api-service/service/faas_client.go +++ b/api-service/service/faas_client.go @@ -40,7 +40,7 @@ func (c *FaaSClient) CreateEnvInstance(req *backend.Env) (*models.EnvInstance, e // return nil, fmt.Errorf("prepare function failed: %v", err.Error()) //} // Synchronously call the function - instanceId, err := c.CreateInstanceByFunction(functionName, dynamicRuntimeName) + instanceId, err := c.CreateInstanceByFunction(functionName, dynamicRuntimeName, req.GetTTL()) if err != nil { return nil, fmt.Errorf("failed to create env instance %s: %v", functionName, err) } @@ -99,13 +99,16 @@ func (c *FaaSClient) PrepareFunction(functionName string, req *backend.Env) erro return nil } -func (c *FaaSClient) CreateInstanceByFunction(name string, dynamicRuntimeName string) (string, error) { +func (c *FaaSClient) CreateInstanceByFunction(name string, dynamicRuntimeName string, ttl string) (string, error) { f, err := c.GetFunction(name) if err != nil { return "", err } - instanceId, err := c.InitializeFunction(f.Name, dynamicRuntimeName, faas_model.FunctionInvocationTypeSync, []byte("{}")) + instanceId, err := c.InitializeFunction(f.Name, faas_model.FunctionInitializeOptions{ + DynamicRuntimeName: dynamicRuntimeName, + TTL: ttl, + }) if err != nil { return "", fmt.Errorf("failed to create functions instance from faas server: %v", err.Error()) } @@ -124,10 +127,10 @@ func (c *FaaSClient) GetEnvInstance(id string) (*models.EnvInstance, error) { envInst := &models.EnvInstance{ ID: instance.InstanceID, IP: instance.IP, - TTL: "", // No TTL field source available yet, can be added later + TTL: instance.TTL, // No TTL field source available yet, can be added later // CreatedAt / UpdatedAt use current time or default values (should actually be returned by backend) - CreatedAt: time.Unix(instance.CreateTimestamp, 0).Format(time.RFC3339), - UpdatedAt: time.Now().Format("2006-01-02 15:04:05"), + CreatedAt: time.UnixMilli(instance.CreateTimestamp).Format(time.RFC3339), + UpdatedAt: time.Now().Format(time.RFC3339), Status: convertStatus(instance.Status), // Env field cannot be directly obtained from Instance, needs to rely on Create return or additional queries // Can only be empty here, recommend maintaining through Create/CreateFromRecord @@ -160,9 +163,9 @@ func (c *FaaSClient) ListEnvInstances(envName string) ([]*models.EnvInstance, er ID: inst.InstanceID, IP: inst.IP, Status: convertStatus(inst.Status), - CreatedAt: time.Now().Format("2006-01-02 15:04:05"), // Could consider constructing from CreateTimestamp - UpdatedAt: time.Now().Format("2006-01-02 15:04:05"), - TTL: "", + CreatedAt: time.UnixMilli(inst.CreateTimestamp).Format(time.RFC3339), // Could consider constructing from CreateTimestamp + UpdatedAt: time.Now().Format(time.RFC3339), + TTL: inst.TTL, Env: nil, // Cannot obtain full Env information from Instance }) } @@ -323,7 +326,7 @@ func (c *FaaSClient) GetRuntime(name string) (*faas_model.Runtime, error) { return runtime, nil } -func (c *FaaSClient) InitializeFunction(name string, dynamicRuntimeName string, invocationType string, invocationBody []byte) (string, error) { +func (c *FaaSClient) InitializeFunction(name string, initOptions faas_model.FunctionInitializeOptions) (string, error) { uri := fmt.Sprintf("/hapis/faas.hcs.io/v1/functions/%s/initialize", name) f, err := c.GetFunction(name) @@ -331,18 +334,7 @@ func (c *FaaSClient) InitializeFunction(name string, dynamicRuntimeName string, return "", err } - if invocationType == faas_model.FunctionInvocationTypeAsync { - invocationType = faas_model.FunctionInvocationTypeAsync - } else { - invocationType = faas_model.FunctionInvocationTypeSync - } - - req := c.client.Post(uri).BodyData(invocationBody).Timeout(time.Duration(f.Timeout)*time.Second).Query("invocationType", invocationType) - - // If dynamicRuntimeName is provided, add it to the query parameters - if dynamicRuntimeName != "" { - req = req.Query("dynamicRuntimeName", dynamicRuntimeName) - } + req := c.client.Post(uri).Body(initOptions).Timeout(time.Duration(f.Timeout) * time.Second) resp, err := req.Do().Response() if err != nil { @@ -361,52 +353,19 @@ func (c *FaaSClient) ListInstances(labels map[string]string) (*faas_model.Instan uri := "/hapis/faas.hcs.io/v1/instances" req := &faas_model.InstanceListRequest{Labels: labels} - resp := &faas_model.APIResponse{ - Data: &faas_model.InstanceListResp{}, - } - err := c.client.Post(uri).Body(*req).Do().Into(resp) + resp := &faas_model.APIInstanceListResponse{} + err := c.client.Get(uri).Body(*req).Do().Into(resp) if err != nil { return nil, fmt.Errorf("failed to list instances: %w", err) } - if !resp.Success { - return nil, fmt.Errorf("failed to list instances: %s", resp.ErrorMessage) - } - - data, ok := resp.Data.(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("invalid response type for InstanceListResp") - } - - // Convert map to InstanceListResp struct - instances := []*faas_model.Instance{} - if insts, ok := data["instances"].([]interface{}); ok { - for _, inst := range insts { - if instMap, ok := inst.(map[string]interface{}); ok { - instance := &faas_model.Instance{} - if instanceID, ok := instMap["instanceID"].(string); ok { - instance.InstanceID = instanceID - } - if ip, ok := instMap["ip"].(string); ok { - instance.IP = ip - } - if status, ok := instMap["status"].(string); ok { - instance.Status = faas_model.InstanceStatus(status) - } - instances = append(instances, instance) - } - } - } - - return &faas_model.InstanceListResp{Instances: instances}, nil + return resp.Data, nil } func (c *FaaSClient) GetInstance(name string) (*faas_model.Instance, error) { uri := fmt.Sprintf("/hapis/faas.hcs.io/v1/instances/%s", name) - resp := &faas_model.APIResponse{ - Data: &faas_model.Instance{}, - } + resp := &faas_model.APIInstanceResponse{} err := c.client.Get(uri).Do().Into(resp) if err != nil { return nil, fmt.Errorf("failed to get instance %s: %w", name, err) @@ -416,27 +375,7 @@ func (c *FaaSClient) GetInstance(name string) (*faas_model.Instance, error) { return nil, fmt.Errorf("failed to get instance %s: %s", name, resp.ErrorMessage) } - data, ok := resp.Data.(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("invalid response type for Instance") - } - - // Convert map to Instance struct - instance := &faas_model.Instance{} - if instanceID, ok := data["instanceID"].(string); ok { - instance.InstanceID = instanceID - } - if ip, ok := data["ip"].(string); ok { - instance.IP = ip - } - if status, ok := data["status"].(string); ok { - instance.Status = faas_model.InstanceStatus(status) - } - if createTimestamp, ok := data["createTimestamp"].(float64); ok { - instance.CreateTimestamp = int64(createTimestamp) - } - - return instance, nil + return &resp.Data, nil } func (c *FaaSClient) DeleteInstance(name string) error { diff --git a/api-service/service/faas_model/function.go b/api-service/service/faas_model/function.go index a75dae57..a849ea7d 100644 --- a/api-service/service/faas_model/function.go +++ b/api-service/service/faas_model/function.go @@ -51,6 +51,7 @@ type Instance struct { IP string `json:"ip"` Labels map[string]string `json:"labels"` Status InstanceStatus `json:"status"` + TTL string `json:"ttl"` } type InstanceListResp struct { @@ -67,6 +68,18 @@ type APIResponse struct { Data interface{} `json:"data,omitempty"` } +type APIInstanceResponse struct { + Success bool `json:"success"` + ErrorMessage string `json:"errorMessage,omitempty"` + Data Instance `json:"data,omitempty"` +} + +type APIInstanceListResponse struct { + Success bool `json:"success"` + ErrorMessage string `json:"errorMessage,omitempty"` + Data *InstanceListResp `json:"data,omitempty"` +} + type RuntimeCreateOrUpdateRequest struct { Name string `json:"name"` Description string `json:"description"` @@ -95,3 +108,9 @@ const ( RuntimeStatusPreparing RuntimeStatus = "preparing" RuntimeStatusError RuntimeStatus = "error" ) + +type FunctionInitializeOptions struct { + // DynamicRuntimeName 动态运行时名称,可选参数 + DynamicRuntimeName string `json:"dynamicRuntimeName,omitempty"` + TTL string `json:"ttl,omitempty"` +} diff --git a/api-service/service/schedule_client.go b/api-service/service/schedule_client.go index 1541e3e1..327f28c5 100644 --- a/api-service/service/schedule_client.go +++ b/api-service/service/schedule_client.go @@ -586,8 +586,8 @@ func (c *ScheduleClient) ListEnvInstances(envName string) ([]*models.EnvInstance } // Format CreatedAt time - createdAtStr := podData.CreatedAt.Format("2006-01-02 15:04:05") - nowStr := time.Now().Format("2006-01-02 15:04:05") + createdAtStr := podData.CreatedAt.Format(time.RFC3339) + nowStr := time.Now().Format(time.RFC3339) instances[i] = &models.EnvInstance{ ID: podData.ID, diff --git a/deploy/api-service/templates/deployment.yaml b/deploy/api-service/templates/deployment.yaml index eddc9375..aac0ee3c 100644 --- a/deploy/api-service/templates/deployment.yaml +++ b/deploy/api-service/templates/deployment.yaml @@ -43,6 +43,8 @@ spec: - {{ include "api-service.scheduleType" . }} - --qps - {{ include "api-service.qps" . | quote }} + - --cleanup-interval + - {{ .Values.cleanupInterval }} ports: - name: http containerPort: {{ .Values.service.port }} diff --git a/deploy/api-service/values.yaml b/deploy/api-service/values.yaml index 07b0fd0d..cb46d4b7 100644 --- a/deploy/api-service/values.yaml +++ b/deploy/api-service/values.yaml @@ -59,3 +59,6 @@ autoscaling: nodeSelector: {} tolerations: {} + +# Cleanup service configuration +cleanupInterval: "5m" diff --git a/deploy/redis/templates/_helpers.tpl b/deploy/redis/templates/_helpers.tpl index 6749c734..662c0826 100644 --- a/deploy/redis/templates/_helpers.tpl +++ b/deploy/redis/templates/_helpers.tpl @@ -20,7 +20,6 @@ app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} {{- define "redis.selectorLabels" -}} -app.kubernetes.io/instance: {{ .Release.Name }} {{- if .Values.global.selectorLabels }} {{ tpl (toYaml .Values.global.selectorLabels) . }} {{- end }} diff --git a/envhub/models/env.go b/envhub/models/env.go index 59739c61..beaab481 100644 --- a/envhub/models/env.go +++ b/envhub/models/env.go @@ -243,3 +243,22 @@ func (e *Env) GetCPU() string { } return "" } + +// GetTTL retrieves the ttl configuration from DeployConfig, returns empty string if not exists +func (e *Env) GetTTL() string { + if val, exists := e.DeployConfig["ttl"]; exists { + switch v := val.(type) { + case string: + return v + case int64: + return fmt.Sprintf("%d", v) + case int: + return fmt.Sprintf("%d", v) + case float64: + return fmt.Sprintf("%.0f", v) + default: + return fmt.Sprintf("%v", v) + } + } + return "" +}