Skip to content

Commit 63cfb5d

Browse files
committed
[api] Use TunnelPeerOffer.Status.Phase as aggregate Condition
1 parent a8e04e9 commit 63cfb5d

File tree

6 files changed

+161
-105
lines changed

6 files changed

+161
-105
lines changed

api/core/v1alpha/tunnel_types.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,22 @@ type TunnelPeerOfferSpec struct {
182182
Offer *ICEOffer `json:"iceOffer,omitempty"`
183183
}
184184

185+
type TunnelPeerOfferPhase string
186+
187+
const (
188+
TunnelPeerOfferPhaseConnecting TunnelPeerOfferPhase = "Connecting"
189+
TunnelPeerOfferPhaseConnected TunnelPeerOfferPhase = "Connected"
190+
TunnelPeerOfferPhaseFailed TunnelPeerOfferPhase = "Failed"
191+
)
192+
185193
type TunnelPeerOfferStatus struct {
186-
Conditions []metav1.Condition `json:"conditions,omitempty"`
194+
// Phase is the current aggregate phase of the tunnel peer offer.
195+
// It may be represented by one or more conditions.
196+
Phase TunnelPeerOfferPhase `json:"phase,omitempty"`
187197

188-
// PeerOffer is the offer from the remote peer.
198+
// Conditions is a list of conditions that apply to the tunnel peer offer.
189199
// +optional
190-
PeerOffer *ICEOffer `json:"peerOffer,omitempty"`
200+
Conditions []metav1.Condition `json:"conditions,omitempty"`
191201
}
192202

193203
var _ resource.StatusSubResource = &TunnelPeerOfferStatus{}

api/core/v1alpha/zz_generated.deepcopy.go

Lines changed: 0 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/generated/zz_generated.openapi.go

Lines changed: 10 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/cmd/tunnel/tunnelnode.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -379,12 +379,7 @@ func isConnected(offer *corev1alpha.TunnelPeerOffer) bool {
379379
if offer == nil {
380380
return false
381381
}
382-
for _, condition := range offer.Status.Conditions {
383-
if condition.Type == "Connected" && condition.Status == metav1.ConditionTrue {
384-
return true
385-
}
386-
}
387-
return false
382+
return offer.Status.Phase == corev1alpha.TunnelPeerOfferPhaseConnected
388383
}
389384

390385
func (t *tunnelNodeReconciler) offerPeer(

pkg/cmd/tunnel/tunnelpeeroffer.go

Lines changed: 115 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@ package tunnel
22

33
import (
44
"context"
5-
"errors"
5+
goerrors "errors"
66
"fmt"
77
"slices"
88
"sync"
99

1010
"github.com/pion/ice/v4"
11+
"k8s.io/apimachinery/pkg/api/errors"
1112
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1213
"k8s.io/client-go/util/retry"
1314
"k8s.io/utils/ptr"
1415
ctrl "sigs.k8s.io/controller-runtime"
1516
"sigs.k8s.io/controller-runtime/pkg/builder"
1617
"sigs.k8s.io/controller-runtime/pkg/client"
1718
"sigs.k8s.io/controller-runtime/pkg/controller"
18-
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
1919
clog "sigs.k8s.io/controller-runtime/pkg/log"
2020
"sigs.k8s.io/controller-runtime/pkg/predicate"
2121

@@ -24,10 +24,6 @@ import (
2424
corev1alpha "github.com/apoxy-dev/apoxy-cli/api/core/v1alpha"
2525
)
2626

27-
const (
28-
tunnelPeerOfferFinalizer = "tunnelpeeroffer.apoxy.dev/finalizer"
29-
)
30-
3127
type tunnelPeerOfferReconciler struct {
3228
client.Client
3329

@@ -53,73 +49,81 @@ func (r *tunnelPeerOfferReconciler) Reconcile(ctx context.Context, req ctrl.Requ
5349
log.Info("Reconciling TunnelPeerOffer")
5450

5551
tunnelPeerOffer := &corev1alpha.TunnelPeerOffer{}
56-
if err := r.Get(ctx, req.NamespacedName, tunnelPeerOffer); err != nil {
57-
return ctrl.Result{}, client.IgnoreNotFound(err)
58-
}
59-
60-
if tunnelPeerOffer.Spec.RemoteTunnelNodeName == r.localTunnelNodeName { // Remote offer.
61-
remoteName, err := getOfferOwner(ctx, r.Client, tunnelPeerOffer)
62-
if err != nil {
63-
return ctrl.Result{}, err
64-
}
65-
log.Info("Offer controlled by remote node, starting ICE negotiation", "RemotePeer", remoteName)
66-
52+
if err := r.Get(ctx, req.NamespacedName, tunnelPeerOffer); client.IgnoreNotFound(err) != nil {
53+
return ctrl.Result{}, err
54+
} else if errors.IsNotFound(err) {
55+
remoteName := req.Name
6756
r.mu.Lock()
68-
defer r.mu.Unlock()
69-
peer, ok := r.peers[remoteName]
70-
if !ok { // Haven't started our end of the ICE negotiation yet.
71-
return ctrl.Result{Requeue: true}, nil
72-
}
73-
74-
remoteOffer := tunnelPeerOffer.Spec.Offer
75-
if remoteOffer == nil {
76-
log.Info("ICE offer not yet created")
77-
return ctrl.Result{}, nil // Will re-trigger when offer is created.
57+
if peer, ok := r.peers[remoteName]; ok {
58+
peer.Close()
59+
delete(r.peers, remoteName)
60+
log.Info("Deleted TunnelPeerOffer")
7861
}
62+
r.mu.Unlock()
7963

80-
log.Info("Connecting to remote peer", "RemotePeer", remoteName)
81-
82-
return r.connect(ctx, remoteName, req, peer, remoteOffer)
64+
return ctrl.Result{}, nil
8365
}
8466

85-
remoteName := tunnelPeerOffer.Spec.RemoteTunnelNodeName
86-
87-
if tunnelPeerOffer.ObjectMeta.DeletionTimestamp.IsZero() {
88-
if !controllerutil.ContainsFinalizer(tunnelPeerOffer, tunnelPeerOfferFinalizer) {
89-
controllerutil.AddFinalizer(tunnelPeerOffer, tunnelPeerOfferFinalizer)
90-
if err := r.Update(ctx, tunnelPeerOffer); err != nil {
67+
if tunnelPeerOffer.Spec.RemoteTunnelNodeName == r.localTunnelNodeName { // Remote offer.
68+
switch tunnelPeerOffer.Status.Phase {
69+
case corev1alpha.TunnelPeerOfferPhaseConnected:
70+
log.Info("Already connected, ignoring")
71+
return ctrl.Result{}, nil
72+
case corev1alpha.TunnelPeerOfferPhaseFailed:
73+
log.Info("Remote peer offer is in failed, ignoring")
74+
return ctrl.Result{}, nil
75+
case corev1alpha.TunnelPeerOfferPhaseConnecting:
76+
remoteName, err := getOfferOwner(ctx, r.Client, tunnelPeerOffer)
77+
if err != nil {
9178
return ctrl.Result{}, err
9279
}
93-
}
94-
} else {
95-
if controllerutil.ContainsFinalizer(tunnelPeerOffer, tunnelPeerOfferFinalizer) {
80+
81+
log.Info("Offer controlled by remote node, starting ICE negotiation", "RemotePeer", remoteName)
82+
9683
r.mu.Lock()
97-
if peer, ok := r.peers[remoteName]; ok {
98-
peer.Close()
99-
delete(r.peers, remoteName)
84+
defer r.mu.Unlock()
85+
peer, ok := r.peers[remoteName]
86+
if !ok { // Haven't started our end of the ICE negotiation yet.
87+
return ctrl.Result{Requeue: true}, nil
10088
}
101-
r.mu.Unlock()
102-
controllerutil.RemoveFinalizer(tunnelPeerOffer, tunnelPeerOfferFinalizer)
103-
if err := r.Update(ctx, tunnelPeerOffer); err != nil {
104-
return ctrl.Result{}, err
89+
90+
remoteOffer := tunnelPeerOffer.Spec.Offer
91+
if remoteOffer == nil {
92+
log.Info("ICE offer not yet created")
93+
return ctrl.Result{}, nil // Will re-trigger when offer is created.
10594
}
106-
log.Info("Deleted TunnelPeerOffer")
107-
}
10895

109-
log.V(1).Info("TunnelPeerOffer is being deleted")
96+
log.Info("Connecting to remote peer", "RemotePeer", remoteName)
11097

111-
return ctrl.Result{}, nil // Already deleted, nothing to do.
98+
return r.connect(ctx, remoteName, req, peer, remoteOffer)
99+
default:
100+
log.Info("Remote offer is in unknown state, ignoring")
101+
return ctrl.Result{}, nil
102+
}
112103
}
113104

114-
log.Info("Offer controlled by local node, starting ICE negotiation", "RemotePeer", remoteName)
105+
remoteName := tunnelPeerOffer.Spec.RemoteTunnelNodeName
106+
log.Info("Offer controlled by local node", "RemotePeer", remoteName)
115107

116108
r.mu.Lock()
117109
defer r.mu.Unlock()
118110
peer, ok := r.peers[remoteName]
119-
if ok { // Already connected, just return.
120-
return ctrl.Result{}, nil
111+
if ok {
112+
switch tunnelPeerOffer.Status.Phase {
113+
case corev1alpha.TunnelPeerOfferPhaseConnected, corev1alpha.TunnelPeerOfferPhaseConnecting:
114+
return ctrl.Result{}, nil // Already connected, or connecting, just return.
115+
case corev1alpha.TunnelPeerOfferPhaseFailed:
116+
log.Info("Failed state, cleaning up peer")
117+
peer.Close()
118+
delete(r.peers, remoteName)
119+
return ctrl.Result{}, nil
120+
default:
121+
return ctrl.Result{}, nil
122+
}
121123
}
122124

125+
log.Info("Starting ICE negotiation", "RemotePeer", remoteName)
126+
123127
var err error
124128
isControlling := r.localTunnelNodeName > remoteName
125129
peer, err = r.bind.NewPeer(ctx, isControlling)
@@ -149,6 +153,45 @@ func (r *tunnelPeerOfferReconciler) Reconcile(ctx context.Context, req ctrl.Requ
149153
log.Error(err, "Failed to update tunnel peer offer status")
150154
}
151155
}
156+
peer.OnConnected = func() {
157+
log.Info("ICE connection established")
158+
if err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
159+
var tn corev1alpha.TunnelPeerOffer
160+
if err := r.Get(ctx, req.NamespacedName, &tn); err != nil {
161+
return err
162+
}
163+
tn.Status.Conditions = append(tn.Status.Conditions, metav1.Condition{
164+
Type: "Connected",
165+
Status: metav1.ConditionTrue,
166+
Reason: "IceConnected",
167+
LastTransitionTime: metav1.Now(),
168+
})
169+
tn.Status.Phase = corev1alpha.TunnelPeerOfferPhaseConnected
170+
return r.Status().Update(ctx, &tn)
171+
}); err != nil {
172+
log.Error(err, "Failed to update tunnel peer offer status")
173+
}
174+
}
175+
peer.OnDisconnected = func(msg string) {
176+
log.Info("ICE connection disconnected", "Reason", msg)
177+
if err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
178+
var tn corev1alpha.TunnelPeerOffer
179+
if err := r.Get(ctx, req.NamespacedName, &tn); err != nil {
180+
return err
181+
}
182+
tn.Status.Phase = corev1alpha.TunnelPeerOfferPhaseFailed
183+
tn.Status.Conditions = append(tn.Status.Conditions, metav1.Condition{
184+
Type: "ICEConnected",
185+
Status: metav1.ConditionFalse,
186+
Reason: "Failed",
187+
Message: fmt.Sprintf("Peer %s failed to connect: %v", remoteName, msg),
188+
LastTransitionTime: metav1.Now(),
189+
})
190+
return r.Status().Update(ctx, &tn)
191+
}); err != nil {
192+
log.Error(err, "Failed to update tunnel peer offer status")
193+
}
194+
}
152195
if err := peer.Init(ctx); err != nil {
153196
log.Error(err, "Failed to initialize ICE peer")
154197
peer.Close()
@@ -157,6 +200,17 @@ func (r *tunnelPeerOfferReconciler) Reconcile(ctx context.Context, req ctrl.Requ
157200

158201
r.peers[remoteName] = peer
159202

203+
if err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
204+
var tn corev1alpha.TunnelPeerOffer
205+
if err := r.Get(ctx, req.NamespacedName, &tn); err != nil {
206+
return err
207+
}
208+
tn.Status.Phase = corev1alpha.TunnelPeerOfferPhaseConnecting
209+
return r.Status().Update(ctx, &tn)
210+
}); err != nil {
211+
log.Error(err, "Failed to update tunnel peer offer status")
212+
}
213+
160214
return ctrl.Result{}, nil // Wait until the remote offer is created.
161215
}
162216

@@ -180,19 +234,21 @@ func (r *tunnelPeerOfferReconciler) connect(
180234
}
181235

182236
go func() {
183-
if err := peer.Connect(ctx, remoteName); err != nil && !errors.Is(err, ice.ErrMultipleStart) {
237+
if err := peer.Connect(ctx, remoteName); err != nil && !goerrors.Is(err, ice.ErrMultipleStart) {
184238
log.Error(err, "Failed to connect to ICE peer")
185239

186240
if err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
187241
var tunnelPeerOffer corev1alpha.TunnelPeerOffer
188242
if err := r.Get(ctx, req.NamespacedName, &tunnelPeerOffer); err != nil {
189243
return err
190244
}
245+
tunnelPeerOffer.Status.Phase = corev1alpha.TunnelPeerOfferPhaseFailed
191246
tunnelPeerOffer.Status.Conditions = append(tunnelPeerOffer.Status.Conditions, metav1.Condition{
192-
Type: "Connected",
193-
Status: metav1.ConditionFalse,
194-
Reason: "Failed",
195-
Message: fmt.Sprintf("Peer %s failed to connect: %v", r.localTunnelNodeName, err),
247+
Type: "Connected",
248+
Status: metav1.ConditionFalse,
249+
Reason: "Failed",
250+
Message: fmt.Sprintf("Peer %s failed to connect: %v", r.localTunnelNodeName, err),
251+
LastTransitionTime: metav1.Now(),
196252
})
197253
return r.Status().Update(ctx, &tunnelPeerOffer)
198254
}); err != nil {
@@ -208,26 +264,10 @@ func (r *tunnelPeerOfferReconciler) connect(
208264
peer.Close()
209265

210266
return
211-
} else if errors.Is(err, ice.ErrMultipleStart) {
267+
} else if goerrors.Is(err, ice.ErrMultipleStart) {
212268
log.Info("ICE connection already established, ignoring")
213269
return
214270
}
215-
216-
if err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
217-
var tunnelPeerOffer corev1alpha.TunnelPeerOffer
218-
if err := r.Get(ctx, req.NamespacedName, &tunnelPeerOffer); err != nil {
219-
return err
220-
}
221-
tunnelPeerOffer.Status.Conditions = append(tunnelPeerOffer.Status.Conditions, metav1.Condition{
222-
Type: "Connected",
223-
Status: metav1.ConditionTrue,
224-
Reason: "Success",
225-
Message: fmt.Sprintf("Peer %s successfully connected", r.localTunnelNodeName),
226-
})
227-
return r.Status().Update(ctx, &tunnelPeerOffer)
228-
}); err != nil {
229-
log.Error(err, "Failed to update tunnel peer offer status")
230-
}
231271
}()
232272

233273
return ctrl.Result{}, nil

0 commit comments

Comments
 (0)