split container update preparation
This commit is contained in:
@@ -192,5 +192,6 @@
|
|||||||
- daemon server 将请求读取、进度回调、更新/重启分派、诊断分派拆开,保留协议错误文本与响应行为。
|
- daemon server 将请求读取、进度回调、更新/重启分派、诊断分派拆开,保留协议错误文本与响应行为。
|
||||||
- `runServe` 将 native/container 运行时构建与资源关闭拆出,避免入口函数承担全部分支。
|
- `runServe` 将 native/container 运行时构建与资源关闭拆出,避免入口函数承担全部分支。
|
||||||
- 容器槽位现场核对拆为容器检查、已提交部署校验和活动槽位选择,减少单函数分支嵌套。
|
- 容器槽位现场核对拆为容器检查、已提交部署校验和活动槽位选择,减少单函数分支嵌套。
|
||||||
|
- 容器镜像更新拆为输入校验、事务恢复、镜像拉取、槽位准备和事务创建阶段,行为与幂等键保持不变。
|
||||||
|
|
||||||
验证:`GOCACHE=/tmp/yms-go-cache go test ./...`、`git diff --check` 通过。
|
验证:`GOCACHE=/tmp/yms-go-cache go test ./...`、`git diff --check` 通过。
|
||||||
|
|||||||
@@ -72,85 +72,111 @@ type persistedContainerRequest struct {
|
|||||||
// 是否转发容器启动日志;report 用于回传实时进度,可为 nil。返回值为本次更新
|
// 是否转发容器启动日志;report 用于回传实时进度,可为 nil。返回值为本次更新
|
||||||
// 对应的事务记录以及错误。若已存在同镜像的活动事务则复用续跑。
|
// 对应的事务记录以及错误。若已存在同镜像的活动事务则复用续跑。
|
||||||
func (u *Updater) UpdateContainerImage(ctx context.Context, imageReference string, startLog bool, report ProgressReporter) (transaction.Transaction, error) {
|
func (u *Updater) UpdateContainerImage(ctx context.Context, imageReference string, startLog bool, report ProgressReporter) (transaction.Transaction, error) {
|
||||||
|
if err := u.validateImageUpdate(imageReference); err != nil {
|
||||||
|
return transaction.Transaction{}, err
|
||||||
|
}
|
||||||
|
if record, request, resumed, err := u.resumeContainerImage(ctx, imageReference, report); resumed || err != nil {
|
||||||
|
return u.finishResumedContainer(ctx, record, request, err, report)
|
||||||
|
}
|
||||||
|
resolved, err := u.pullImageForUpdate(ctx, imageReference, report)
|
||||||
|
if err != nil {
|
||||||
|
return transaction.Transaction{}, err
|
||||||
|
}
|
||||||
|
prepared, err := u.prepareContainerImage(ctx, imageReference, resolved, startLog)
|
||||||
|
if err != nil {
|
||||||
|
return transaction.Transaction{}, err
|
||||||
|
}
|
||||||
|
return u.createContainerImageTransaction(ctx, prepared, report)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Updater) validateImageUpdate(imageReference string) error {
|
||||||
if u.containerExecutor == nil || u.engine == nil {
|
if u.containerExecutor == nil || u.engine == nil {
|
||||||
return transaction.Transaction{}, errors.New("container backend updater is not configured")
|
return errors.New("container backend updater is not configured")
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(imageReference) != imageReference || imageReference == "" {
|
if strings.TrimSpace(imageReference) != imageReference || imageReference == "" {
|
||||||
return transaction.Transaction{}, errors.New("exact container image reference is required")
|
return errors.New("exact container image reference is required")
|
||||||
}
|
}
|
||||||
if err := u.validateContainerInputs(); err != nil {
|
return u.validateContainerInputs()
|
||||||
return transaction.Transaction{}, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *Updater) resumeContainerImage(ctx context.Context, imageReference string, report ProgressReporter) (transaction.Transaction, persistedContainerRequest, bool, error) {
|
||||||
active, err := u.store.ActiveTransaction(ctx)
|
active, err := u.store.ActiveTransaction(ctx)
|
||||||
if err == nil {
|
if errors.Is(err, transaction.ErrNotFound) {
|
||||||
|
return transaction.Transaction{}, persistedContainerRequest{}, false, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return transaction.Transaction{}, persistedContainerRequest{}, true, err
|
||||||
|
}
|
||||||
var request persistedContainerRequest
|
var request persistedContainerRequest
|
||||||
if decodeErr := decodeContainerRequest(active.Request, &request); decodeErr != nil {
|
if err := decodeContainerRequest(active.Request, &request); err != nil {
|
||||||
return active, decodeErr
|
return active, request, true, err
|
||||||
}
|
}
|
||||||
if active.Service != serviceBackend || request.InputType != inputTypeContainerImage {
|
if active.Service != serviceBackend || request.InputType != inputTypeContainerImage || request.ImageReference != imageReference {
|
||||||
return active, &transaction.ActiveTransactionError{TransactionID: active.ID}
|
return active, request, true, &transaction.ActiveTransactionError{TransactionID: active.ID}
|
||||||
}
|
}
|
||||||
if request.ImageReference != imageReference {
|
return active, request, true, nil
|
||||||
return active, &transaction.ActiveTransactionError{TransactionID: active.ID}
|
|
||||||
}
|
|
||||||
return u.runContainerUpdate(ctx, active, request, false, report)
|
|
||||||
}
|
|
||||||
if !errors.Is(err, transaction.ErrNotFound) {
|
|
||||||
return transaction.Transaction{}, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *Updater) finishResumedContainer(ctx context.Context, record transaction.Transaction, request persistedContainerRequest, err error, report ProgressReporter) (transaction.Transaction, error) {
|
||||||
|
if err != nil {
|
||||||
|
return record, err
|
||||||
|
}
|
||||||
|
return u.runContainerUpdate(ctx, record, request, false, report)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Updater) pullImageForUpdate(ctx context.Context, imageReference string, report ProgressReporter) (resolvedImage, error) {
|
||||||
reportProgress(report, Progress{Message: "Pulling backend image " + imageReference})
|
reportProgress(report, Progress{Message: "Pulling backend image " + imageReference})
|
||||||
resolved, err := u.pullAndResolveImage(ctx, imageReference)
|
resolved, err := u.pullAndResolveImage(ctx, imageReference)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
return transaction.Transaction{}, err
|
|
||||||
}
|
|
||||||
reportProgress(report, Progress{Message: "Backend image resolved: " + resolved.ImmutableReference})
|
reportProgress(report, Progress{Message: "Backend image resolved: " + resolved.ImmutableReference})
|
||||||
|
}
|
||||||
|
return resolved, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type preparedContainerImage struct {
|
||||||
|
transactionID string
|
||||||
|
request persistedContainerRequest
|
||||||
|
resolved resolvedImage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Updater) prepareContainerImage(ctx context.Context, imageReference string, resolved resolvedImage, startLog bool) (preparedContainerImage, error) {
|
||||||
before, err := u.gateway.Read()
|
before, err := u.gateway.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return transaction.Transaction{}, err
|
return preparedContainerImage{}, err
|
||||||
}
|
}
|
||||||
activeSlot, err := u.config.Backend.SlotForPort(before.ActivePort)
|
activeSlot, err := u.config.Backend.SlotForPort(before.ActivePort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return transaction.Transaction{}, err
|
return preparedContainerImage{}, err
|
||||||
}
|
}
|
||||||
deployment, deploymentErr := u.store.BackendContainerDeployment(ctx)
|
deployment, deploymentErr := u.store.BackendContainerDeployment(ctx)
|
||||||
hasDeployment := deploymentErr == nil
|
hasDeployment := deploymentErr == nil
|
||||||
if deploymentErr != nil && !errors.Is(deploymentErr, transaction.ErrNotFound) {
|
if deploymentErr != nil && !errors.Is(deploymentErr, transaction.ErrNotFound) {
|
||||||
return transaction.Transaction{}, deploymentErr
|
return preparedContainerImage{}, deploymentErr
|
||||||
}
|
}
|
||||||
hasHistory, err := u.store.HasCommittedBackendContainerTransactionHistory(ctx)
|
hasHistory, err := u.store.HasCommittedBackendContainerTransactionHistory(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return transaction.Transaction{}, err
|
return preparedContainerImage{}, err
|
||||||
}
|
}
|
||||||
targetPort, targetSlot, previousContainer, err := u.resolveContainerSlots(
|
targetPort, targetSlot, previousContainer, err := u.resolveContainerSlots(ctx, before.ActivePort, activeSlot, deployment, hasDeployment, hasHistory)
|
||||||
ctx,
|
|
||||||
before.ActivePort,
|
|
||||||
activeSlot,
|
|
||||||
deployment,
|
|
||||||
hasDeployment,
|
|
||||||
hasHistory,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return transaction.Transaction{}, err
|
return preparedContainerImage{}, err
|
||||||
}
|
}
|
||||||
afterContent, err := hostnginx.RenderBackendPort(before.Content, targetPort)
|
afterContent, err := hostnginx.RenderBackendPort(before.Content, targetPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return transaction.Transaction{}, err
|
return preparedContainerImage{}, err
|
||||||
}
|
}
|
||||||
transactionID := rand.Text()
|
transactionID := rand.Text()
|
||||||
transactionRoot := filepath.Join(u.workRoot, transactionID)
|
transactionRoot := filepath.Join(u.workRoot, transactionID)
|
||||||
if err := os.MkdirAll(transactionRoot, 0o750); err != nil {
|
if err := os.MkdirAll(transactionRoot, 0o750); err != nil {
|
||||||
return transaction.Transaction{}, fmt.Errorf("create container backend transaction directory: %w", err)
|
return preparedContainerImage{}, fmt.Errorf("create container backend transaction directory: %w", err)
|
||||||
}
|
}
|
||||||
beforePath := filepath.Join(transactionRoot, "gateway.before.conf")
|
beforePath := filepath.Join(transactionRoot, "gateway.before.conf")
|
||||||
afterPath := filepath.Join(transactionRoot, "gateway.after.conf")
|
afterPath := filepath.Join(transactionRoot, "gateway.after.conf")
|
||||||
if err := writeImmutableFile(beforePath, before.Content, 0o640); err != nil {
|
if err := writeImmutableFile(beforePath, before.Content, 0o640); err != nil {
|
||||||
return transaction.Transaction{}, err
|
return preparedContainerImage{}, err
|
||||||
}
|
}
|
||||||
if err := writeImmutableFile(afterPath, afterContent, 0o640); err != nil {
|
if err := writeImmutableFile(afterPath, afterContent, 0o640); err != nil {
|
||||||
return transaction.Transaction{}, err
|
return preparedContainerImage{}, err
|
||||||
}
|
}
|
||||||
request := persistedContainerRequest{
|
request := persistedContainerRequest{
|
||||||
InputType: inputTypeContainerImage, ImageReference: imageReference,
|
InputType: inputTypeContainerImage, ImageReference: imageReference,
|
||||||
@@ -162,12 +188,17 @@ func (u *Updater) UpdateContainerImage(ctx context.Context, imageReference strin
|
|||||||
GatewayBeforePath: beforePath, GatewayAfterPath: afterPath,
|
GatewayBeforePath: beforePath, GatewayAfterPath: afterPath,
|
||||||
GatewayReceiptPath: filepath.Join(transactionRoot, "gateway.applied"),
|
GatewayReceiptPath: filepath.Join(transactionRoot, "gateway.applied"),
|
||||||
}
|
}
|
||||||
|
return preparedContainerImage{transactionID: transactionID, request: request, resolved: resolved}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Updater) createContainerImageTransaction(ctx context.Context, prepared preparedContainerImage, report ProgressReporter) (transaction.Transaction, error) {
|
||||||
|
request, resolved := prepared.request, prepared.resolved
|
||||||
content, err := json.Marshal(request)
|
content, err := json.Marshal(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return transaction.Transaction{}, fmt.Errorf("encode container backend update request: %w", err)
|
return transaction.Transaction{}, fmt.Errorf("encode container backend update request: %w", err)
|
||||||
}
|
}
|
||||||
record, created, err := u.store.CreateTransaction(ctx, transaction.CreateRequest{
|
record, created, err := u.store.CreateTransaction(ctx, transaction.CreateRequest{
|
||||||
ID: transactionID, IdempotencyKey: serviceBackend + ":container:" + resolved.Digest,
|
ID: prepared.transactionID, IdempotencyKey: serviceBackend + ":container:" + resolved.Digest,
|
||||||
Source: sourceLocalCLI, Service: serviceBackend, Request: content,
|
Source: sourceLocalCLI, Service: serviceBackend, Request: content,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user