diff --git a/config.yaml b/config.yaml index 107ec8bd3b9ce98d895a025b2b2ee8dd8726f02e..ce8f9288c4f7b23d86d561ea0b03cb9dd400bbcf 100644 --- a/config.yaml +++ b/config.yaml @@ -33,3 +33,9 @@ cors: allow_credentials: true redis: url: redis://127.0.0.1:6379 +setup: + async_deactivation: + enabled: true + max_retries: 3 + delay_ms: 200 + diff --git a/internal/app/app.go b/internal/app/app.go index 8cb4713e997049deb54d94fe29039dc9aaf15d37..3a7bcf4da8901152cd22a4ffc5281a909831e23e 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -61,7 +61,7 @@ func NewApp(conf *config.Config) *App { ruleService := services.NewRuleService(ruleRepository, deviceService, logger) setupService := services.NewSetupService(setupRepository, deviceService, ruleService, logger) deviceControlService := services.NewDeviceControlService(setupService, deviceService, ruleService, conf.WatcherConfig, logger) - activeSetupService := services.NewActiveSetupService(setupService, deviceService, ruleService, deviceControlService, logger) + activeSetupService := services.NewActiveSetupService(setupService, deviceService, ruleService, deviceControlService, &conf.SetupConfig, logger) err = activeSetupService.ReloadActiveSetup(ctx) if err != nil { diff --git a/internal/config/config.go b/internal/config/config.go index 2acbc3f6bf14274db5e9bbda95522a300de25be7..015f247afa7020c4b928791b1baee6ec878fd139 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -31,6 +31,17 @@ type Config struct { HTTPPort int `yaml:"http_port"` DBPath string `yaml:"db_path"` RedisConfig RedisConfig `yaml:"redis"` + SetupConfig SetupConfig `yaml:"setup"` +} + +type SetupConfig struct { + SetupDeactivateConfig SetupDeactivateConfig `yaml:"async_deactivation"` +} + +type SetupDeactivateConfig struct { + Enabled bool `yaml:"enabled"` + MaxRetries int `yaml:"max_retries"` + DelayMillisecond int `yaml:"delay_ms"` } func NewConfig(path string) (*Config, error) { diff --git a/internal/services/active_setup_service.go b/internal/services/active_setup_service.go index bfaa44f5b81b37c535222ee7b4c342a24612aa2e..cb12fa942d8d438d3762c47dbceb0daad1218343 100644 --- a/internal/services/active_setup_service.go +++ b/internal/services/active_setup_service.go @@ -4,9 +4,13 @@ import ( "context" "encoding/json" "fmt" + "git.miem.hse.ru/hubman/configurator/internal/config" "git.miem.hse.ru/hubman/configurator/internal/entity" "git.miem.hse.ru/hubman/configurator/pkg/errs" + hubapi "git.miem.hse.ru/hubman/hubman-lib/api" + "github.com/google/uuid" "go.uber.org/zap" + "time" ) type ActiveSetupService struct { @@ -14,6 +18,7 @@ type ActiveSetupService struct { deviceService *DeviceService ruleService *RuleService deviceControlService *DeviceControlService + setupConfig *config.SetupConfig log *zap.Logger } @@ -22,6 +27,7 @@ func NewActiveSetupService( deviceService *DeviceService, ruleService *RuleService, deviceControlService *DeviceControlService, + setupConfig *config.SetupConfig, log *zap.Logger, ) *ActiveSetupService { return &ActiveSetupService{ @@ -29,6 +35,7 @@ func NewActiveSetupService( deviceService: deviceService, ruleService: ruleService, deviceControlService: deviceControlService, + setupConfig: setupConfig, log: log, } } @@ -66,7 +73,19 @@ func (s *ActiveSetupService) ClearSetup(ctx context.Context, id int) error { return err } err = s.deviceService.DeleteBySetupId(ctx, id) - return err + if err != nil { + return err + } + + devices, err := s.deviceService.GetList(ctx, id) + if err != nil { + return err + } + + if s.setupConfig.SetupDeactivateConfig.Enabled { + go s.clearDevicesRulesBindings(context.Background(), id, devices) + } + return nil } func (s *ActiveSetupService) DeleteDevice(ctx context.Context, setupId, deviceId int) error { @@ -144,6 +163,15 @@ func (s *ActiveSetupService) DeactivateSetup(ctx context.Context, setupId int) ( if err != nil { return nil, err } + devices, err := s.deviceService.GetList(ctx, setupId) + if err != nil { + return nil, err + } + + if s.setupConfig.SetupDeactivateConfig.Enabled { + go s.clearDevicesRulesBindings(context.Background(), setupId, devices) + } + err = s.ReloadActiveSetup(ctx) if err != nil { return nil, err @@ -237,3 +265,79 @@ func (s *ActiveSetupService) Export(ctx context.Context, id int) (*entity.Export Rules: rules, }, nil } + +func (s *ActiveSetupService) clearDevicesRulesBindings(ctx context.Context, setupId int, devices []*entity.Device) { + var err error + + for _, dev := range devices { + nowTries := 0 + for nowTries <= s.setupConfig.SetupDeactivateConfig.MaxRetries { + err = s.clearDevice(ctx, setupId, dev) + if err == nil { + break + } + nowTries++ + time.Sleep(time.Duration(s.setupConfig.SetupDeactivateConfig.DelayMillisecond) * time.Millisecond) + } + if nowTries > s.setupConfig.SetupDeactivateConfig.MaxRetries { + s.log.Warn("max retries for clearing device exceeded", zap.Int("device_id", dev.Id), zap.Error(err)) + } + } +} + +func (s *ActiveSetupService) clearDevice(ctx context.Context, setupId int, dev *entity.Device) error { + client, err := s.deviceService.getClient(ctx, dev) + if err != nil { + s.log.Warn("can not get client for device", zap.Int("device_id", dev.Id), zap.Error(err)) + return err + } + + if dev.IsExecutor { + err = s.clearDeviceBindings(ctx, setupId, dev, client) + if err != nil { + s.log.Warn("error while clearing device bindings", zap.Error(err)) + return err + } + } + + if dev.IsManipulator { + err = s.clearDeviceRules(ctx, setupId, dev, client) + if err != nil { + s.log.Warn("error while clearing device rules", zap.Error(err)) + return err + } + } + return nil +} + +func (s *ActiveSetupService) clearDeviceBindings(ctx context.Context, setupId int, device *entity.Device, deviceClient *hubapi.ClientWithResponses) error { + newRequestId := uuid.New() + + s.log.Info("try to put empty bindings to device", zap.Int("device_id", device.Id)) + _, err := deviceClient.PutBindings(ctx, &hubapi.PutBindingsParams{XRequestID: &newRequestId}, []hubapi.Binding{}) + if err != nil { + s.log.Warn("can not set empty bindings to device", zap.Int("device_id", device.Id)) + return err + } + + _, err = s.deviceService.Update(ctx, setupId, device.Id, &entity.UpdDevice{ + BindingsRequestId: &newRequestId, + }) + return err +} + +func (s *ActiveSetupService) clearDeviceRules(ctx context.Context, setupId int, device *entity.Device, deviceClient *hubapi.ClientWithResponses) error { + newRequestId := uuid.New() + + s.log.Info("try to put empty rules to device", zap.Int("device_id", device.Id)) + _, err := deviceClient.PutRules(ctx, &hubapi.PutRulesParams{XRequestID: &newRequestId}, []hubapi.DeviceRule{}) + if err != nil { + s.log.Warn("can not set empty rules to device", zap.Int("device_id", device.Id)) + return err + } + + _, err = s.deviceService.Update(ctx, setupId, device.Id, &entity.UpdDevice{ + RulesRequestId: &newRequestId, + }) + return err +}