86 lines
2.5 KiB
Go
86 lines
2.5 KiB
Go
package deployment
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
)
|
|
|
|
// acquireOperation 覆盖整个部署、站点核验及收尾;人工执行器必须共用此入口。
|
|
// 它不替代数据库持久化占用和远端文件锁,等待可以取消,不能提前释放。
|
|
func (s *Service) acquireOperation(ctx context.Context) (func(), error) {
|
|
if s == nil {
|
|
return nil, ErrRecoveryDispositionUnavailable
|
|
}
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
s.operationOnce.Do(func() { s.operationGate = make(chan struct{}, 1) })
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
case s.operationGate <- struct{}{}:
|
|
if err := ctx.Err(); err != nil {
|
|
<-s.operationGate
|
|
return nil, err
|
|
}
|
|
return func() { <-s.operationGate }, nil
|
|
}
|
|
}
|
|
|
|
func operationFailure(targets []Target, err error) Report {
|
|
report := Report{Total: len(targets), Failed: len(targets), Status: "failed", Results: make([]Result, 0, len(targets))}
|
|
for _, target := range targets {
|
|
report.Results = append(report.Results, Result{
|
|
ServerID: target.Server.ID, Server: target.Server.Name, Status: "failed",
|
|
FailureCode: FailureUnavailable, Message: fmt.Sprintf("未获得部署操作许可:%v", err),
|
|
})
|
|
}
|
|
return report
|
|
}
|
|
|
|
type recoveryClaimReader interface {
|
|
HasRecoveryClaim(context.Context, string) (bool, error)
|
|
}
|
|
|
|
func (s *Service) checkRecoveryClaim(ctx context.Context, id string) error {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
reader, ok := s.journal.(recoveryClaimReader)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
claimed, err := reader.HasRecoveryClaim(ctx, id)
|
|
if err != nil {
|
|
return ErrRecoveryDispositionUnavailable
|
|
}
|
|
if claimed {
|
|
return ErrRecoveryDispositionBusy
|
|
}
|
|
return ctx.Err()
|
|
}
|
|
|
|
// checkDeploymentClaims 在连接远端前拦截仍有人工占用的服务器。
|
|
// 同地址同端口的别名也须检查;凭据或路径修改不能绕过原服务器占用。
|
|
func (s *Service) checkDeploymentClaims(ctx context.Context, server Server) error {
|
|
if _, ok := s.journal.(recoveryClaimReader); !ok {
|
|
return nil
|
|
}
|
|
records, err := s.journal.ListPendingDeploymentTransactions(ctx)
|
|
if err != nil {
|
|
return ErrRecoveryDispositionUnavailable
|
|
}
|
|
for _, record := range records {
|
|
sameServer := record.ServerID == server.ID
|
|
if record.Evidence != nil {
|
|
sameServer = sameServer || (record.Evidence.Address == server.Address && record.Evidence.Port == server.Port)
|
|
}
|
|
if sameServer {
|
|
if err := s.checkRecoveryClaim(ctx, record.ID); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return ctx.Err()
|
|
}
|