~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
113 lines · 4.4 KB Raw History
package requester

import (
	"context"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	commonv1 "github.com/johnandersen777/socialweb-computer-kcp/api/common/v1alpha1"
	"github.com/johnandersen777/socialweb-computer-kcp/api/v1alpha1"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/atproto/cloudinit"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/atproto/market"
)

const GuestRequesterReleaseTimeout = cloudinit.GuestRequesterReleaseTimeout

func GuestRequesterReleaseEventName(contract string) string {
	name := "r2-release-" + contract
	if len(name) > 253 {
		return name[:253]
	}
	return name
}

func (r *contractRunner) releaseGuestRequester(ctx context.Context, state *v1alpha1.NodeClaimContract) {
	q := r.q
	contract := r.snapshot()
	if !fromGuest(contract) || state == nil || state.GuestRequester == nil ||
		(state.GuestRequester.State != v1alpha1.ContractGuestRequesterVerified && state.GuestRequester.State != v1alpha1.ContractGuestRequesterPending) ||
		q.deps.ContractEvents == nil || contract.UID == "" {
		return
	}
	if state.R2Release != nil && state.R2Release.Phase != v1alpha1.ContractR2ReleaseRequested {
		return
	}
	receiptURI := receiptURIOf(state)
	if receiptURI == "" {
		return
	}
	events, pending, closeEvents := q.registerEventSink(receiptURI)
	defer closeEvents()
	name := GuestRequesterReleaseEventName(contract.Name)
	event := v1alpha1.ContractEvent{
		ObjectMeta: metav1.ObjectMeta{Name: name, OwnerReferences: []metav1.OwnerReference{contractOwnerReference(contract)}},
		Spec: v1alpha1.ContractEventSpec{
			ContractRef: commonv1.LocalObjectRef{Name: contract.Name},
			Direction:   v1alpha1.ContractEventOutbound,
			NSID:        market.ComputeEventsGuestR2ReleaseNSID,
		},
	}
	if err := q.deps.ContractEvents.ApplyContractEvent(ctx, event); err != nil {
		r.log().Warn("requester_guest_requester_release_failed", "step", "the r2Release ContractEvent", "err", err.Error())
		return
	}
	q.contractEvents.Put(name, event)
	if state.R2Release == nil || state.R2Release.Phase != v1alpha1.ContractR2ReleaseRequested {
		state.R2Release = &v1alpha1.ContractR2Release{Phase: v1alpha1.ContractR2ReleaseRequested, RequestedAt: q.nowISO()}
	}
	settle := func(phase v1alpha1.ContractR2ReleasePhase, ok bool, message string) {
		state.R2Release.Phase, state.R2Release.Ok, state.R2Release.Message, state.R2Release.AnsweredAt = phase, ok, message, q.nowISO()
		r.writeStatus(ctx, v1alpha1.ComputeContractReleasing, "the guest requester's release: "+string(phase)+" "+message, state,
			r.currentBids(), r.currentChosen(), "releasingGuestRequester", 1)
	}
	r.writeStatus(ctx, v1alpha1.ComputeContractReleasing, "asking the guest requester to release its own contracts before the guest goes", state,
		r.currentBids(), r.currentChosen(), "releasingGuestRequester", 1)
	r.log().Info("requester_guest_requester_release", "contractEvent", name, "timeout", GuestRequesterReleaseTimeout.String())
	answered := func(e MarketEvent) bool {
		if market.NSIDFromURI(e.Payload.URI) != market.ComputeEventsGuestR2ReleasedNSID {
			return false
		}
		parsed, err := q.parseGuestEvent(ctx, e, receiptURI)
		if err != nil || parsed.R2Released == nil {
			if err != nil {
				r.log().Warn("requester_guest_event_refused", "uri", e.Event.URI, "err", err)
			}
			return false
		}
		r.log().Info("requester_guest_requester_released", "ok", *parsed.R2Released, "message", parsed.R2Message)
		phase := v1alpha1.ContractR2ReleaseReleased
		if !*parsed.R2Released {
			phase = v1alpha1.ContractR2ReleaseFailed
		}
		settle(phase, *parsed.R2Released, parsed.R2Message)
		return true
	}
	for _, e := range pending {
		if answered(e) {
			return
		}
	}
	wait := GuestRequesterReleaseTimeout
	if at, err := market.ParseTime(state.R2Release.RequestedAt); err == nil {
		wait = max(at.Add(GuestRequesterReleaseTimeout).Sub(q.now()), 0)
	}
	timer := time.NewTimer(wait)
	defer timer.Stop()
	for {
		select {
		case <-ctx.Done():
			return
		case <-timer.C:
			r.log().Warn("requester_guest_requester_release_timeout", "after", GuestRequesterReleaseTimeout.String(),
				"next", "the guest is destroyed; the guest requester's bidders end its contracts when its association goes")
			settle(v1alpha1.ContractR2ReleaseTimedOut, false, "no guest.r2Released within "+GuestRequesterReleaseTimeout.String()+
				"; the guest requester's bidders release its VMs at its association expiresAt plus 10m")
			return
		case e := <-events:
			if answered(e) {
				return
			}
		}
	}
}