253 lines
7.3 KiB
Go
253 lines
7.3 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/RyanCopley/skybridge/faas/internal/domain"
|
|
"github.com/RyanCopley/skybridge/faas/internal/repository"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type functionService struct {
|
|
functionRepo repository.FunctionRepository
|
|
runtimeService RuntimeService
|
|
logger *zap.Logger
|
|
}
|
|
|
|
func NewFunctionService(functionRepo repository.FunctionRepository, runtimeService RuntimeService, logger *zap.Logger) FunctionService {
|
|
return &functionService{
|
|
functionRepo: functionRepo,
|
|
runtimeService: runtimeService,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
func (s *functionService) Create(ctx context.Context, req *domain.CreateFunctionRequest, userID string) (*domain.FunctionDefinition, error) {
|
|
s.logger.Info("Creating new function",
|
|
zap.String("name", req.Name),
|
|
zap.String("app_id", req.AppID),
|
|
zap.String("user_id", userID))
|
|
|
|
// Check if function with same name exists
|
|
_, err := s.functionRepo.GetByName(ctx, req.AppID, req.Name)
|
|
if err == nil {
|
|
return nil, fmt.Errorf("function with name '%s' already exists in app '%s'", req.Name, req.AppID)
|
|
}
|
|
|
|
// Validate runtime
|
|
if !s.isValidRuntime(string(req.Runtime)) {
|
|
return nil, fmt.Errorf("unsupported runtime: %s", req.Runtime)
|
|
}
|
|
|
|
// Create function definition
|
|
function := &domain.FunctionDefinition{
|
|
ID: uuid.New(),
|
|
Name: req.Name,
|
|
AppID: req.AppID,
|
|
Runtime: req.Runtime,
|
|
Image: req.Image,
|
|
Handler: req.Handler,
|
|
Code: req.Code,
|
|
Environment: req.Environment,
|
|
Timeout: req.Timeout,
|
|
Memory: req.Memory,
|
|
Owner: req.Owner,
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
|
|
// Validate timeout and memory limits
|
|
if function.Timeout.Duration < time.Second {
|
|
return nil, fmt.Errorf("timeout must be at least 1 second")
|
|
}
|
|
if function.Timeout.Duration > 15*time.Minute {
|
|
return nil, fmt.Errorf("timeout cannot exceed 15 minutes")
|
|
}
|
|
if function.Memory < 64 || function.Memory > 3008 {
|
|
return nil, fmt.Errorf("memory must be between 64 and 3008 MB")
|
|
}
|
|
|
|
// Store function
|
|
created, err := s.functionRepo.Create(ctx, function)
|
|
if err != nil {
|
|
s.logger.Error("Failed to create function",
|
|
zap.String("name", req.Name),
|
|
zap.Error(err))
|
|
return nil, fmt.Errorf("failed to create function: %w", err)
|
|
}
|
|
|
|
s.logger.Info("Function created successfully",
|
|
zap.String("function_id", created.ID.String()),
|
|
zap.String("name", created.Name))
|
|
|
|
return created, nil
|
|
}
|
|
|
|
func (s *functionService) GetByID(ctx context.Context, id uuid.UUID) (*domain.FunctionDefinition, error) {
|
|
function, err := s.functionRepo.GetByID(ctx, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("function not found: %w", err)
|
|
}
|
|
return function, nil
|
|
}
|
|
|
|
func (s *functionService) GetByName(ctx context.Context, appID, name string) (*domain.FunctionDefinition, error) {
|
|
function, err := s.functionRepo.GetByName(ctx, appID, name)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("function not found: %w", err)
|
|
}
|
|
return function, nil
|
|
}
|
|
|
|
func (s *functionService) Update(ctx context.Context, id uuid.UUID, req *domain.UpdateFunctionRequest, userID string) (*domain.FunctionDefinition, error) {
|
|
s.logger.Info("Updating function",
|
|
zap.String("function_id", id.String()),
|
|
zap.String("user_id", userID))
|
|
|
|
// Get existing function
|
|
_, err := s.functionRepo.GetByID(ctx, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("function not found: %w", err)
|
|
}
|
|
|
|
// Validate runtime if being updated
|
|
if req.Runtime != nil && !s.isValidRuntime(string(*req.Runtime)) {
|
|
return nil, fmt.Errorf("unsupported runtime: %s", *req.Runtime)
|
|
}
|
|
|
|
// Validate timeout and memory if being updated
|
|
if req.Timeout != nil {
|
|
if req.Timeout.Duration < time.Second {
|
|
return nil, fmt.Errorf("timeout must be at least 1 second")
|
|
}
|
|
if req.Timeout.Duration > 15*time.Minute {
|
|
return nil, fmt.Errorf("timeout cannot exceed 15 minutes")
|
|
}
|
|
}
|
|
if req.Memory != nil && (*req.Memory < 64 || *req.Memory > 3008) {
|
|
return nil, fmt.Errorf("memory must be between 64 and 3008 MB")
|
|
}
|
|
|
|
// Update function
|
|
updated, err := s.functionRepo.Update(ctx, id, req)
|
|
if err != nil {
|
|
s.logger.Error("Failed to update function",
|
|
zap.String("function_id", id.String()),
|
|
zap.Error(err))
|
|
return nil, fmt.Errorf("failed to update function: %w", err)
|
|
}
|
|
|
|
s.logger.Info("Function updated successfully",
|
|
zap.String("function_id", id.String()))
|
|
|
|
return updated, nil
|
|
}
|
|
|
|
func (s *functionService) Delete(ctx context.Context, id uuid.UUID, userID string) error {
|
|
s.logger.Info("Deleting function",
|
|
zap.String("function_id", id.String()),
|
|
zap.String("user_id", userID))
|
|
|
|
// Get function to determine runtime
|
|
function, err := s.functionRepo.GetByID(ctx, id)
|
|
if err != nil {
|
|
return fmt.Errorf("function not found: %w", err)
|
|
}
|
|
|
|
// Clean up runtime resources
|
|
backend, err := s.runtimeService.GetBackend(ctx, string(function.Runtime))
|
|
if err != nil {
|
|
s.logger.Warn("Failed to get runtime backend for cleanup", zap.Error(err))
|
|
} else {
|
|
if err := backend.Remove(ctx, id); err != nil {
|
|
s.logger.Warn("Failed to remove runtime resources",
|
|
zap.String("function_id", id.String()),
|
|
zap.Error(err))
|
|
}
|
|
}
|
|
|
|
// Delete function
|
|
if err := s.functionRepo.Delete(ctx, id); err != nil {
|
|
s.logger.Error("Failed to delete function",
|
|
zap.String("function_id", id.String()),
|
|
zap.Error(err))
|
|
return fmt.Errorf("failed to delete function: %w", err)
|
|
}
|
|
|
|
s.logger.Info("Function deleted successfully",
|
|
zap.String("function_id", id.String()))
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *functionService) List(ctx context.Context, appID string, limit, offset int) ([]*domain.FunctionDefinition, error) {
|
|
if limit <= 0 {
|
|
limit = 50 // Default limit
|
|
}
|
|
if limit > 100 {
|
|
limit = 100 // Max limit
|
|
}
|
|
|
|
return s.functionRepo.List(ctx, appID, limit, offset)
|
|
}
|
|
|
|
func (s *functionService) GetByAppID(ctx context.Context, appID string) ([]*domain.FunctionDefinition, error) {
|
|
return s.functionRepo.GetByAppID(ctx, appID)
|
|
}
|
|
|
|
func (s *functionService) Deploy(ctx context.Context, id uuid.UUID, req *domain.DeployFunctionRequest, userID string) (*domain.DeployFunctionResponse, error) {
|
|
s.logger.Info("Deploying function",
|
|
zap.String("function_id", id.String()),
|
|
zap.String("user_id", userID),
|
|
zap.Bool("force", req.Force))
|
|
|
|
// Get function
|
|
function, err := s.functionRepo.GetByID(ctx, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("function not found: %w", err)
|
|
}
|
|
|
|
// Get runtime backend
|
|
backend, err := s.runtimeService.GetBackend(ctx, string(function.Runtime))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get runtime backend: %w", err)
|
|
}
|
|
|
|
// Deploy function
|
|
if err := backend.Deploy(ctx, function); err != nil {
|
|
s.logger.Error("Failed to deploy function",
|
|
zap.String("function_id", id.String()),
|
|
zap.Error(err))
|
|
return nil, fmt.Errorf("failed to deploy function: %w", err)
|
|
}
|
|
|
|
s.logger.Info("Function deployed successfully",
|
|
zap.String("function_id", id.String()),
|
|
zap.String("image", function.Image))
|
|
|
|
return &domain.DeployFunctionResponse{
|
|
Status: "deployed",
|
|
Message: "Function deployed successfully",
|
|
Image: function.Image,
|
|
}, nil
|
|
}
|
|
|
|
func (s *functionService) isValidRuntime(runtimeType string) bool {
|
|
validRuntimes := []string{
|
|
string(domain.RuntimeNodeJS18),
|
|
string(domain.RuntimePython39),
|
|
string(domain.RuntimeGo120),
|
|
string(domain.RuntimeCustom),
|
|
}
|
|
|
|
for _, valid := range validRuntimes {
|
|
if runtimeType == valid {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
} |