From ef642e102089afc8d5e70a804b5a27b39f228a9a Mon Sep 17 00:00:00 2001 From: Michal Skrivanek Date: Fri, 10 Oct 2025 10:23:38 +0200 Subject: [PATCH 1/4] use the right time in endTime lease's endTime was incorrectly using beginTime. This is only for the leases that effectively ended already, so not really used. But still should be fixed... --- internal/service/controller_service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/controller_service.go b/internal/service/controller_service.go index b85f4478..9cded03f 100644 --- a/internal/service/controller_service.go +++ b/internal/service/controller_service.go @@ -533,7 +533,7 @@ func (s *ControllerService) GetLease( } var endTime *timestamppb.Timestamp if lease.Status.EndTime != nil { - beginTime = timestamppb.New(lease.Status.EndTime.Time) + endTime = timestamppb.New(lease.Status.EndTime.Time) } var exporterUuid *string if lease.Status.ExporterRef != nil { From 552a3481e64356552efdd7f29a56ed03e9d89887 Mon Sep 17 00:00:00 2001 From: Michal Skrivanek Date: Fri, 10 Oct 2025 10:35:07 +0200 Subject: [PATCH 2/4] calculate the Effective times for lease properly, support scheduled lease requests This effectively implements scheduled leases. - set EffectiveBeginTime once the lease is aquired, optionally wait with acquisition for after the BeginTime passes. - populate the EffectiveDuration before lease is ended so that clients see the duration so far (equivalent to: now - EffectiveBeginTime). - add BeginTime initialization to CreateLease request. - allow updating BeginTime by UpdateLease before the lease is acquired - lease can be created with acombination of BeginTime/EndTime/Duration, where missing BeginTime means "immediately". When querying leases that are pending or active the clients can see: BeginTime - if requested in CreateLease EffectiveBeginTime - the time lease become active. Either immediately or at(after) the requested BeginTime Duration - as requested EffectiveDuration - calculated, could be affected by subsequent lease extenstions as well as it could be shorten by acquisition happening later than requested in BeginTime --- api/v1alpha1/lease_helpers.go | 109 ++++++++++++++++--- api/v1alpha1/lease_types.go | 14 ++- api/v1alpha1/zz_generated.deepcopy.go | 14 ++- internal/controller/lease_controller.go | 54 ++++++--- internal/service/client/v1/client_service.go | 23 +++- internal/service/controller_service.go | 13 ++- 6 files changed, 192 insertions(+), 35 deletions(-) diff --git a/api/v1alpha1/lease_helpers.go b/api/v1alpha1/lease_helpers.go index 04379445..f3e36727 100644 --- a/api/v1alpha1/lease_helpers.go +++ b/api/v1alpha1/lease_helpers.go @@ -20,6 +20,59 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" ) +// ReconcileLeaseTimeFields calculates missing time fields and validates consistency +// between BeginTime, EndTime, and Duration. Modifies pointers in place. +// +// Supported lease specification patterns: +// 1. Duration only (no BeginTime/EndTime): immediate start, runs for Duration +// - BeginTime set by controller when exporter acquired +// - EndTime = Status.BeginTime + Duration (calculated at runtime) +// +// 2. EndTime only: INVALID - cannot infer Duration without BeginTime or explicit Duration +// - Returns error: "duration is required (must specify Duration, or both BeginTime and EndTime)" +// +// 3. BeginTime + Duration: scheduled start at BeginTime, runs for Duration +// - Lease waits until BeginTime, then acquires exporter +// - EndTime = BeginTime + Duration (calculated at runtime) +// +// 4. BeginTime + EndTime: scheduled window, Duration computed from times +// - Duration = EndTime - BeginTime (auto-calculated here) +// - Validates EndTime > BeginTime (positive duration) +// +// 5. EndTime + Duration: scheduled end, BeginTime computed as EndTime - Duration +// - BeginTime = EndTime - Duration (auto-calculated here) +// - Useful for "finish by" scheduling +// +// 6. BeginTime + EndTime + Duration: all three specified, validates consistency +// - Validates Duration == EndTime - BeginTime +// - Returns error if inconsistent: "duration conflicts with begin_time and end_time" +// +// Note: The controller never auto-populates Spec.EndTime. It calculates expiration time +// on-demand from available fields, keeping Spec.EndTime meaningful only when explicitly +// set by the user. See lease_controller.go reconcileStatusEnded for expiration logic. +func ReconcileLeaseTimeFields(beginTime, endTime **metav1.Time, duration **metav1.Duration) error { + if *beginTime != nil && *endTime != nil { + // Calculate duration from explicit begin/end times + calculated := (*endTime).Sub((*beginTime).Time) + if *duration != nil && (*duration).Duration > 0 && (*duration).Duration != calculated { + return fmt.Errorf("duration conflicts with begin_time and end_time") + } + *duration = &metav1.Duration{Duration: calculated} + } else if *endTime != nil && *duration != nil && (*duration).Duration > 0 { + // Calculate BeginTime from EndTime - Duration (scheduled lease ending at specific time) + *beginTime = &metav1.Time{Time: (*endTime).Add(-(*duration).Duration)} + } + + // Validate final duration is positive (rejects nil, negative, zero) + if *duration == nil { + return fmt.Errorf("duration is required (must specify Duration, or both BeginTime and EndTime)") + } + if (*duration).Duration <= 0 { + return fmt.Errorf("duration must be positive, got %v", (*duration).Duration) + } + return nil +} + func LeaseFromProtobuf( req *cpb.Lease, key types.NamespacedName, @@ -30,6 +83,22 @@ func LeaseFromProtobuf( return nil, err } + var beginTime, endTime *metav1.Time + var duration *metav1.Duration + + if req.BeginTime != nil { + beginTime = &metav1.Time{Time: req.BeginTime.AsTime()} + } + if req.EndTime != nil { + endTime = &metav1.Time{Time: req.EndTime.AsTime()} + } + if req.Duration != nil { + duration = &metav1.Duration{Duration: req.Duration.AsDuration()} + } + if err := ReconcileLeaseTimeFields(&beginTime, &endTime, &duration); err != nil { + return nil, err + } + return &Lease{ ObjectMeta: metav1.ObjectMeta{ Namespace: key.Namespace, @@ -37,8 +106,10 @@ func LeaseFromProtobuf( }, Spec: LeaseSpec{ ClientRef: clientRef, - Duration: metav1.Duration{Duration: req.Duration.AsDuration()}, + Duration: duration, Selector: *selector, + BeginTime: beginTime, + EndTime: endTime, }, }, nil } @@ -60,22 +131,34 @@ func (l *Lease) ToProtobuf() *cpb.Lease { } lease := cpb.Lease{ - Name: fmt.Sprintf("namespaces/%s/leases/%s", l.Namespace, l.Name), - Selector: metav1.FormatLabelSelector(&l.Spec.Selector), - Duration: durationpb.New(l.Spec.Duration.Duration), - EffectiveDuration: durationpb.New(l.Spec.Duration.Duration), // TODO: implement lease renewal - Client: ptr.To(fmt.Sprintf("namespaces/%s/clients/%s", l.Namespace, l.Spec.ClientRef.Name)), - Conditions: conditions, - // TODO: implement scheduled leases - BeginTime: nil, - EndTime: nil, + Name: fmt.Sprintf("namespaces/%s/leases/%s", l.Namespace, l.Name), + Selector: metav1.FormatLabelSelector(&l.Spec.Selector), + Client: ptr.To(fmt.Sprintf("namespaces/%s/clients/%s", l.Namespace, l.Spec.ClientRef.Name)), + Conditions: conditions, + } + if l.Spec.Duration != nil { + lease.Duration = durationpb.New(l.Spec.Duration.Duration) } + // Requested/planned times from Spec + if l.Spec.BeginTime != nil { + lease.BeginTime = timestamppb.New(l.Spec.BeginTime.Time) + } + if l.Spec.EndTime != nil { + lease.EndTime = timestamppb.New(l.Spec.EndTime.Time) + } + + // Actual times from Status if l.Status.BeginTime != nil { lease.EffectiveBeginTime = timestamppb.New(l.Status.BeginTime.Time) - } - if l.Status.EndTime != nil { - lease.EffectiveEndTime = timestamppb.New(l.Status.EndTime.Time) + endTime := time.Now() + if l.Status.EndTime != nil { + endTime = l.Status.EndTime.Time + lease.EffectiveEndTime = timestamppb.New(endTime) + } + // Final effective duration or current one so far while active. Non-negative to handle clock skew. + effectiveDuration := max(endTime.Sub(l.Status.BeginTime.Time), 0) + lease.EffectiveDuration = durationpb.New(effectiveDuration) } if l.Status.ExporterRef != nil { lease.Exporter = ptr.To(utils.UnparseExporterIdentifier(kclient.ObjectKey{ diff --git a/api/v1alpha1/lease_types.go b/api/v1alpha1/lease_types.go index 5476c596..97d4f5e6 100644 --- a/api/v1alpha1/lease_types.go +++ b/api/v1alpha1/lease_types.go @@ -25,18 +25,26 @@ import ( type LeaseSpec struct { // The client that is requesting the lease ClientRef corev1.LocalObjectReference `json:"clientRef"` - // The desired duration of the lease - Duration metav1.Duration `json:"duration"` + // Duration of the lease. Must be positive when provided. + // Can be omitted (nil) when both BeginTime and EndTime are provided, + // in which case it's calculated as EndTime - BeginTime. + Duration *metav1.Duration `json:"duration,omitempty"` // The selector for the exporter to be used Selector metav1.LabelSelector `json:"selector"` // The release flag requests the controller to end the lease now Release bool `json:"release,omitempty"` + // Requested start time. If omitted, lease starts when exporter is acquired. + // Immutable after lease starts (cannot change the past). + BeginTime *metav1.Time `json:"beginTime,omitempty"` + // Requested end time. If specified with BeginTime, Duration is calculated. + // Can be updated to extend or shorten active leases. + EndTime *metav1.Time `json:"endTime,omitempty"` } // LeaseStatus defines the observed state of Lease type LeaseStatus struct { // If the lease has been acquired an exporter name is assigned - // and then and then it can be used, it will be empty while still pending + // and then it can be used, it will be empty while still pending BeginTime *metav1.Time `json:"beginTime,omitempty"` EndTime *metav1.Time `json:"endTime,omitempty"` ExporterRef *corev1.LocalObjectReference `json:"exporterRef,omitempty"` diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index b1b0c5a5..8af9619f 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -495,8 +495,20 @@ func (in *LeaseList) DeepCopyObject() runtime.Object { func (in *LeaseSpec) DeepCopyInto(out *LeaseSpec) { *out = *in out.ClientRef = in.ClientRef - out.Duration = in.Duration + if in.Duration != nil { + in, out := &in.Duration, &out.Duration + *out = new(metav1.Duration) + **out = **in + } in.Selector.DeepCopyInto(&out.Selector) + if in.BeginTime != nil { + in, out := &in.BeginTime, &out.BeginTime + *out = (*in).DeepCopy() + } + if in.EndTime != nil { + in, out := &in.EndTime, &out.EndTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeaseSpec. diff --git a/internal/controller/lease_controller.go b/internal/controller/lease_controller.go index 3b2b68ea..eb5c7596 100644 --- a/internal/controller/lease_controller.go +++ b/internal/controller/lease_controller.go @@ -82,7 +82,7 @@ func (r *LeaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl return result, err } - if err := r.reconcileStatusBeginTime(ctx, &lease); err != nil { + if err := r.reconcileStatusBeginEndTimes(ctx, &lease); err != nil { return result, err } @@ -141,14 +141,24 @@ func (r *LeaseReconciler) reconcileStatusEnded( lease.Release(ctx) return nil } else if lease.Status.BeginTime != nil { - expiration := lease.Status.BeginTime.Add(lease.Spec.Duration.Duration) + var expiration time.Time + if lease.Spec.EndTime != nil { + // expires at Spec.EndTime when specified + expiration = lease.Spec.EndTime.Time + } else if lease.Spec.BeginTime != nil && lease.Spec.Duration != nil { + // expires at Spec.BeginTime + Spec.Duration - scheduled lease + expiration = lease.Spec.BeginTime.Add(lease.Spec.Duration.Duration) + } else if lease.Spec.Duration != nil { + // expires at actual BeginTime + Spec.Duration - immediate lease + expiration = lease.Status.BeginTime.Add(lease.Spec.Duration.Duration) + } + if expiration.Before(now) { lease.Expire(ctx) return nil - } else { - result.RequeueAfter = expiration.Sub(now) - return nil } + result.RequeueAfter = expiration.Sub(now) + return nil } } @@ -156,19 +166,16 @@ func (r *LeaseReconciler) reconcileStatusEnded( } // nolint:unparam -func (r *LeaseReconciler) reconcileStatusBeginTime( +func (r *LeaseReconciler) reconcileStatusBeginEndTimes( ctx context.Context, lease *jumpstarterdevv1alpha1.Lease, ) error { - logger := log.FromContext(ctx) - - now := time.Now() if lease.Status.BeginTime == nil && lease.Status.ExporterRef != nil { + logger := log.FromContext(ctx) logger.Info("Updating begin time for lease", "lease", lease.Name, "exporter", lease.GetExporterName(), "client", lease.GetClientName()) + now := time.Now() + lease.Status.BeginTime = &metav1.Time{Time: now} lease.SetStatusReady(true, "Ready", "An exporter has been acquired for the client") - lease.Status.BeginTime = &metav1.Time{ - Time: now, - } } return nil @@ -188,6 +195,20 @@ func (r *LeaseReconciler) reconcileStatusExporterRef( } if lease.Status.ExporterRef == nil { + // For scheduled leases: only assign exporter if requested BeginTime has arrived + if lease.Spec.BeginTime != nil { + now := time.Now() + if lease.Spec.BeginTime.After(now) { + // Requested BeginTime is in the future, wait until then + waitDuration := lease.Spec.BeginTime.Sub(now) + logger.Info("Lease is scheduled for the future, waiting", + "lease", lease.Name, + "requestedBeginTime", lease.Spec.BeginTime, + "waitDuration", waitDuration) + result.RequeueAfter = waitDuration + return nil + } + } logger.Info("Looking for a matching exporter for lease", "lease", lease.Name, "client", lease.GetClientName(), "selector", lease.Spec.Selector) selector, err := lease.GetExporterSelector() @@ -338,7 +359,14 @@ func (r *LeaseReconciler) attachMatchingPolicies(ctx context.Context, lease *jum } if clientSelector.Matches(labels.Set(jclient.Labels)) { if p.MaximumDuration != nil { - if lease.Spec.Duration.Duration > p.MaximumDuration.Duration { + // Calculate requested duration (may be from explicit Duration or computed from times) + requestedDuration := time.Duration(0) + if lease.Spec.Duration != nil { + requestedDuration = lease.Spec.Duration.Duration + } else if lease.Spec.BeginTime != nil && lease.Spec.EndTime != nil { + requestedDuration = lease.Spec.EndTime.Sub(lease.Spec.BeginTime.Time) + } + if requestedDuration > p.MaximumDuration.Duration { // TODO: we probably should keep this on the list of approved exporters // but mark as excessive duration so we can report it on the status // of lease if no other options exist diff --git a/internal/service/client/v1/client_service.go b/internal/service/client/v1/client_service.go index 76e4f8cb..7a10fe46 100644 --- a/internal/service/client/v1/client_service.go +++ b/internal/service/client/v1/client_service.go @@ -219,7 +219,28 @@ func (s *ClientService) UpdateLease(ctx context.Context, req *cpb.UpdateLeaseReq return nil, err } - jlease.Spec.Duration = desired.Spec.Duration + // BeginTime can only be updated before lease starts; only if explicitly provided + if req.Lease.BeginTime != nil { + if jlease.Status.ExporterRef != nil { + if jlease.Spec.BeginTime == nil || !jlease.Spec.BeginTime.Equal(desired.Spec.BeginTime) { + return nil, fmt.Errorf("cannot update BeginTime: lease has already started") + } + } + jlease.Spec.BeginTime = desired.Spec.BeginTime + } + // Update Duration only if provided; preserve existing otherwise + if req.Lease.Duration != nil { + jlease.Spec.Duration = desired.Spec.Duration + } + // Update EndTime only if provided; preserve existing otherwise + if req.Lease.EndTime != nil { + jlease.Spec.EndTime = desired.Spec.EndTime + } + + // Recalculate missing field or validate consistency + if err := jumpstarterdevv1alpha1.ReconcileLeaseTimeFields(&jlease.Spec.BeginTime, &jlease.Spec.EndTime, &jlease.Spec.Duration); err != nil { + return nil, err + } if err := s.Patch(ctx, &jlease, original); err != nil { return nil, err diff --git a/internal/service/controller_service.go b/internal/service/controller_service.go index 9cded03f..d7712bac 100644 --- a/internal/service/controller_service.go +++ b/internal/service/controller_service.go @@ -563,14 +563,17 @@ func (s *ControllerService) GetLease( }) } - return &pb.GetLeaseResponse{ - Duration: durationpb.New(lease.Spec.Duration.Duration), + resp := &pb.GetLeaseResponse{ Selector: &pb.LabelSelector{MatchExpressions: matchExpressions, MatchLabels: lease.Spec.Selector.MatchLabels}, BeginTime: beginTime, EndTime: endTime, ExporterUuid: exporterUuid, Conditions: conditions, - }, nil + } + if lease.Spec.Duration != nil { + resp.Duration = durationpb.New(lease.Spec.Duration.Duration) + } + return resp, nil } func (s *ControllerService) RequestLease( @@ -609,13 +612,15 @@ func (s *ControllerService) RequestLease( ClientRef: corev1.LocalObjectReference{ Name: client.Name, }, - Duration: metav1.Duration{Duration: req.Duration.AsDuration()}, Selector: metav1.LabelSelector{ MatchLabels: matchLabels, MatchExpressions: matchExpressions, }, }, } + if req.Duration != nil { + lease.Spec.Duration = &metav1.Duration{Duration: req.Duration.AsDuration()} + } if err := s.Client.Create(ctx, &lease); err != nil { return nil, err } From a5e3403b27de48772f9b7fb65c548dfbb0c703a6 Mon Sep 17 00:00:00 2001 From: Michal Skrivanek Date: Mon, 13 Oct 2025 10:35:43 +0200 Subject: [PATCH 3/4] add lease tests validation and behavior tests for Lease creation with BeginTime,EndTime,Duration --- internal/controller/lease_controller_test.go | 1162 +++++++++++++++++- 1 file changed, 1143 insertions(+), 19 deletions(-) diff --git a/internal/controller/lease_controller_test.go b/internal/controller/lease_controller_test.go index 281f0c99..2b686dff 100644 --- a/internal/controller/lease_controller_test.go +++ b/internal/controller/lease_controller_test.go @@ -22,8 +22,11 @@ import ( jumpstarterdevv1alpha1 "github.com/jumpstarter-dev/jumpstarter-controller/api/v1alpha1" "github.com/jumpstarter-dev/jumpstarter-controller/internal/oidc" + cpb "github.com/jumpstarter-dev/jumpstarter-controller/internal/protocol/jumpstarter/client/v1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -31,6 +34,12 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" ) +const ( + lease1Name = "lease1" + lease2Name = "lease2" + lease3Name = "lease3" +) + var leaseDutA2Sec = &jumpstarterdevv1alpha1.Lease{ ObjectMeta: metav1.ObjectMeta{ Name: "lease1", @@ -45,7 +54,7 @@ var leaseDutA2Sec = &jumpstarterdevv1alpha1.Lease{ "dut": "a", }, }, - Duration: metav1.Duration{ + Duration: &metav1.Duration{ Duration: 2 * time.Second, }, }, @@ -60,7 +69,7 @@ var _ = Describe("Lease Controller", func() { AfterEach(func() { ctx := context.Background() deleteExporters(ctx, testExporter1DutA, testExporter2DutA, testExporter3DutB) - deleteLeases(ctx, "lease1", "lease2", "lease3") + deleteLeases(ctx, lease1Name, lease2Name, lease3Name) }) When("trying to lease with an empty selector", func() { @@ -102,7 +111,7 @@ var _ = Describe("Lease Controller", func() { It("should be released after the lease time", func() { lease := leaseDutA2Sec.DeepCopy() - lease.Spec.Duration.Duration = 100 * time.Millisecond + lease.Spec.Duration = &metav1.Duration{Duration: 100 * time.Millisecond} ctx := context.Background() Expect(k8sClient.Create(ctx, lease)).To(Succeed()) @@ -113,15 +122,15 @@ var _ = Describe("Lease Controller", func() { exporterName := updatedLease.Status.ExporterRef.Name - time.Sleep(200 * time.Millisecond) - _ = reconcileLease(ctx, lease) - - updatedLease = getLease(ctx, lease.Name) + // Poll until lease expires + Eventually(func() bool { + _ = reconcileLease(ctx, lease) + updatedLease = getLease(ctx, lease.Name) + return updatedLease.Status.Ended + }).WithTimeout(2000 * time.Millisecond).WithPolling(50 * time.Millisecond).Should(BeTrue()) // exporter is retained for record purposes Expect(updatedLease.Status.ExporterRef).NotTo(BeNil()) - // but the ended flag to be set - Expect(updatedLease.Status.Ended).To(BeTrue()) // the exporter should have no lease mark on status updatedExporter := getExporter(ctx, exporterName) @@ -338,12 +347,12 @@ var _ = Describe("Lease Controller", func() { // create another lease that attempts to acquire the only dut b exporter // which is already leased lease2 := leaseDutA2Sec.DeepCopy() - lease2.Name = "lease2" + lease2.Name = lease2Name lease2.Spec.Selector.MatchLabels["dut"] = "b" Expect(k8sClient.Create(ctx, lease2)).To(Succeed()) _ = reconcileLease(ctx, lease2) - updatedLease = getLease(ctx, lease2.Name) + updatedLease = getLease(ctx, lease2Name) Expect(updatedLease.Status.ExporterRef).To(BeNil()) Expect(meta.IsStatusConditionTrue( @@ -361,7 +370,7 @@ var _ = Describe("Lease Controller", func() { It("should be acquired when a valid exporter lease times out", func() { lease := leaseDutA2Sec.DeepCopy() lease.Spec.Selector.MatchLabels["dut"] = "b" - lease.Spec.Duration.Duration = 500 * time.Millisecond + lease.Spec.Duration = &metav1.Duration{Duration: 500 * time.Millisecond} ctx := context.Background() Expect(k8sClient.Create(ctx, lease)).To(Succeed()) @@ -378,20 +387,22 @@ var _ = Describe("Lease Controller", func() { // create another lease that attempts to acquire the only dut b exporter // which is already leased lease2 := leaseDutA2Sec.DeepCopy() - lease2.Name = "lease2" + lease2.Name = lease2Name lease2.Spec.Selector.MatchLabels["dut"] = "b" Expect(k8sClient.Create(ctx, lease2)).To(Succeed()) _ = reconcileLease(ctx, lease2) - updatedLease = getLease(ctx, lease2.Name) + updatedLease = getLease(ctx, lease2Name) Expect(updatedLease.Status.ExporterRef).To(BeNil()) // TODO: add and check status conditions of the lease to indicate that the lease is waiting - time.Sleep(501 * time.Millisecond) - _ = reconcileLease(ctx, lease) - _ = reconcileLease(ctx, lease2) - updatedLease = getLease(ctx, lease2.Name) - Expect(updatedLease.Status.ExporterRef).NotTo(BeNil()) + // Poll until first lease expires and second lease acquires exporter + Eventually(func() bool { + _ = reconcileLease(ctx, lease) + _ = reconcileLease(ctx, lease2) + updatedLease = getLease(ctx, lease2Name) + return updatedLease.Status.ExporterRef != nil + }).WithTimeout(2500 * time.Millisecond).WithPolling(50 * time.Millisecond).Should(BeTrue()) }) }) @@ -686,3 +697,1116 @@ var _ = Describe("orderApprovedExporters", func() { }) }) }) + +var _ = Describe("Scheduled Leases", func() { + BeforeEach(func() { + createExporters(context.Background(), testExporter1DutA, testExporter2DutA, testExporter3DutB) + setExporterOnlineConditions(context.Background(), testExporter1DutA.Name, metav1.ConditionTrue) + setExporterOnlineConditions(context.Background(), testExporter2DutA.Name, metav1.ConditionTrue) + setExporterOnlineConditions(context.Background(), testExporter3DutB.Name, metav1.ConditionTrue) + }) + AfterEach(func() { + ctx := context.Background() + deleteExporters(ctx, testExporter1DutA, testExporter2DutA, testExporter3DutB) + deleteLeases(ctx, lease1Name, lease2Name, lease3Name) + }) + + When("creating lease with Duration only (immediate lease)", func() { + It("should acquire exporter immediately and set effective begin time", func() { + lease := leaseDutA2Sec.DeepCopy() + lease.Spec.Duration = &metav1.Duration{Duration: 2 * time.Second} + lease.Spec.BeginTime = nil + lease.Spec.EndTime = nil + + ctx := context.Background() + beforeCreate := time.Now().Truncate(time.Second) + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + _ = reconcileLease(ctx, lease) + afterReconcile := time.Now().Truncate(time.Second) + + updatedLease := getLease(ctx, lease.Name) + Expect(updatedLease.Spec.BeginTime).To(BeNil(), "Spec.BeginTime should remain nil for immediate leases") + Expect(updatedLease.Spec.EndTime).To(BeNil(), "Spec.EndTime should remain nil") + Expect(updatedLease.Status.BeginTime).NotTo(BeNil(), "Status.BeginTime should be set") + Expect(updatedLease.Status.BeginTime.Time).To(BeTemporally(">=", beforeCreate)) + Expect(updatedLease.Status.BeginTime.Time).To(BeTemporally("<=", afterReconcile)) + Expect(updatedLease.Status.ExporterRef).NotTo(BeNil(), "Should have acquired exporter immediately") + }) + }) + + When("creating lease with BeginTime + Duration (scheduled lease)", func() { + It("should wait until BeginTime before acquiring exporter", func() { + lease := leaseDutA2Sec.DeepCopy() + futureTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(1 * time.Second)) + lease.Spec.BeginTime = &futureTime + lease.Spec.Duration = &metav1.Duration{Duration: 1 * time.Second} + lease.Spec.EndTime = nil + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + result := reconcileLease(ctx, lease) + + // Should requeue for future time + Expect(result.RequeueAfter).To(BeNumerically(">", 0)) + Expect(result.RequeueAfter).To(BeNumerically("<=", 2*time.Second)) + + updatedLease := getLease(ctx, lease.Name) + Expect(updatedLease.Status.ExporterRef).To(BeNil(), "Should not have acquired exporter yet") + Expect(updatedLease.Status.BeginTime).To(BeNil(), "Status.BeginTime should not be set yet") + + // Poll until BeginTime passes and exporter is acquired + Eventually(func() bool { + _ = reconcileLease(ctx, lease) + updatedLease = getLease(ctx, lease.Name) + return updatedLease.Status.ExporterRef != nil + }).WithTimeout(3*time.Second).WithPolling(50*time.Millisecond).Should(BeTrue(), "Should have acquired exporter after BeginTime") + + Expect(updatedLease.Status.BeginTime).NotTo(BeNil(), "Status.BeginTime should be set") + Expect(updatedLease.Status.BeginTime.Time).To(BeTemporally(">=", futureTime.Time)) + }) + }) + + When("creating lease with BeginTime + EndTime (without Duration)", func() { + It("should calculate Duration and wait until BeginTime", func() { + lease := leaseDutA2Sec.DeepCopy() + beginTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(1 * time.Second)) + endTime := metav1.NewTime(beginTime.Add(1 * time.Second)) + lease.Spec.BeginTime = &beginTime + lease.Spec.EndTime = &endTime + lease.Spec.Duration = nil + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + + // The Duration should be calculated by LeaseFromProtobuf or validation webhook + // For now, we need to set it manually since we're creating directly via k8s client + updatedLease := getLease(ctx, lease.Name) + updatedLease.Spec.Duration = &metav1.Duration{Duration: endTime.Sub(beginTime.Time)} + Expect(k8sClient.Update(ctx, updatedLease)).To(Succeed()) + + result := reconcileLease(ctx, updatedLease) + Expect(result.RequeueAfter).To(BeNumerically(">", 0)) + + updatedLease = getLease(ctx, lease.Name) + Expect(updatedLease.Status.ExporterRef).To(BeNil(), "Should not have acquired exporter yet") + Expect(updatedLease.Spec.Duration.Duration).To(Equal(1 * time.Second)) + + // Poll until BeginTime passes and exporter is acquired + Eventually(func() bool { + _ = reconcileLease(ctx, updatedLease) + updatedLease = getLease(ctx, lease.Name) + return updatedLease.Status.ExporterRef != nil + }).WithTimeout(3*time.Second).WithPolling(50*time.Millisecond).Should(BeTrue(), "Should have acquired exporter") + + Expect(updatedLease.Status.BeginTime).NotTo(BeNil()) + }) + }) + + When("creating lease with EndTime only (immediate lease with fixed end time)", func() { + It("should acquire exporter immediately and end at EndTime", func() { + lease := leaseDutA2Sec.DeepCopy() + endTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(1 * time.Second)) + lease.Spec.BeginTime = nil + lease.Spec.EndTime = &endTime + lease.Spec.Duration = nil + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + _ = reconcileLease(ctx, lease) + + updatedLease := getLease(ctx, lease.Name) + Expect(updatedLease.Status.ExporterRef).NotTo(BeNil(), "Should acquire exporter immediately") + Expect(updatedLease.Status.BeginTime).NotTo(BeNil(), "Status.BeginTime should be set") + Expect(updatedLease.Spec.EndTime.Time).To(Equal(endTime.Time)) + + // Poll until EndTime passes and lease ends + Eventually(func() bool { + _ = reconcileLease(ctx, lease) + updatedLease = getLease(ctx, lease.Name) + return updatedLease.Status.Ended + }).WithTimeout(3*time.Second).WithPolling(50*time.Millisecond).Should(BeTrue(), "Lease should end at specified EndTime") + Expect(updatedLease.Status.EndTime).NotTo(BeNil(), "Status.EndTime should be set") + + // Check EffectiveDuration in protobuf representation + pbLease := updatedLease.ToProtobuf() + Expect(pbLease.EffectiveBeginTime).NotTo(BeNil()) + Expect(pbLease.EffectiveEndTime).NotTo(BeNil()) + Expect(pbLease.EffectiveDuration).NotTo(BeNil()) + + effectiveDuration := pbLease.EffectiveDuration.AsDuration() + actualDuration := updatedLease.Status.EndTime.Sub(updatedLease.Status.BeginTime.Time) + Expect(effectiveDuration).To(BeNumerically("~", actualDuration, 10*time.Millisecond)) + }) + }) + + When("creating lease with EndTime + Duration (calculated future BeginTime)", func() { + It("should calculate BeginTime and wait before acquiring exporter", func() { + lease := leaseDutA2Sec.DeepCopy() + endTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(2 * time.Second)) + duration := 1 * time.Second + expectedBeginTime := endTime.Add(-duration) + + lease.Spec.BeginTime = nil + lease.Spec.EndTime = &endTime + lease.Spec.Duration = &metav1.Duration{Duration: duration} + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + + // The BeginTime should be calculated by LeaseFromProtobuf or validation + updatedLease := getLease(ctx, lease.Name) + updatedLease.Spec.BeginTime = &metav1.Time{Time: expectedBeginTime} + Expect(k8sClient.Update(ctx, updatedLease)).To(Succeed()) + + result := reconcileLease(ctx, updatedLease) + Expect(result.RequeueAfter).To(BeNumerically(">", 0)) + + updatedLease = getLease(ctx, lease.Name) + Expect(updatedLease.Status.ExporterRef).To(BeNil(), "Should not have acquired exporter yet") + Expect(updatedLease.Spec.BeginTime.Time).To(BeTemporally("~", expectedBeginTime, 10*time.Millisecond)) + + // Poll until calculated BeginTime passes and exporter is acquired + Eventually(func() bool { + _ = reconcileLease(ctx, updatedLease) + updatedLease = getLease(ctx, lease.Name) + return updatedLease.Status.ExporterRef != nil + }).WithTimeout(1200*time.Millisecond).WithPolling(50*time.Millisecond).Should(BeTrue(), "Should have acquired exporter after calculated BeginTime") + }) + + It("should start immediately when calculated BeginTime is in the past", func() { + lease := leaseDutA2Sec.DeepCopy() + // Test scenario: Explicit BeginTime in past (simulating EndTime+Duration calculation result) + // Set BeginTime well in the past to ensure it's definitely past even with delays + pastBeginTime := time.Now().Truncate(time.Second).Add(-10 * time.Second) + futureEndTime := time.Now().Truncate(time.Second).Add(20 * time.Second) + + lease.Spec.BeginTime = &metav1.Time{Time: pastBeginTime} + lease.Spec.EndTime = &metav1.Time{Time: futureEndTime} + lease.Spec.Duration = &metav1.Duration{Duration: futureEndTime.Sub(pastBeginTime)} + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + + result := reconcileLease(ctx, lease) + + // The lease should start immediately, but will requeue to check expiration at EndTime + // RequeueAfter should be approximately time until EndTime (~20 seconds) + Expect(result.RequeueAfter).To(BeNumerically(">", 15*time.Second), "Should requeue for expiration check") + Expect(result.RequeueAfter).To(BeNumerically("<=", 21*time.Second), "Requeue should be around EndTime") + + updatedLease := getLease(ctx, lease.Name) + Expect(updatedLease.Status.ExporterRef).NotTo(BeNil(), "Should acquire exporter immediately") + Expect(updatedLease.Status.BeginTime).NotTo(BeNil(), "Status.BeginTime should be set") + + // Status.BeginTime should be the actual acquisition time (now), not the calculated past time + // Allow generous tolerance for CI environments with second-precision timestamps + now := time.Now().Truncate(time.Second) + Expect(updatedLease.Status.BeginTime.Time).To(BeTemporally(">=", now.Add(-2*time.Second))) + Expect(updatedLease.Status.BeginTime.Time).To(BeTemporally("<=", now.Add(2*time.Second))) + + // EffectiveDuration should be based on actual Status.BeginTime, not Spec.BeginTime + // Since timestamps have second precision, allow up to 1 second tolerance + pbLease := updatedLease.ToProtobuf() + Expect(pbLease.EffectiveDuration).NotTo(BeNil()) + actualDuration := pbLease.EffectiveDuration.AsDuration() + // Should be small (just acquired), allowing for second-precision truncation + Expect(actualDuration).To(BeNumerically("<=", 2*time.Second)) + Expect(actualDuration).To(BeNumerically(">=", 0)) + }) + }) + + When("creating lease with BeginTime + EndTime + Duration (all three specified)", func() { + It("should validate consistency and use the values", func() { + lease := leaseDutA2Sec.DeepCopy() + beginTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(1 * time.Second)) + duration := 1 * time.Second + endTime := metav1.NewTime(beginTime.Add(duration)) + + lease.Spec.BeginTime = &beginTime + lease.Spec.EndTime = &endTime + lease.Spec.Duration = &metav1.Duration{Duration: duration} + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + result := reconcileLease(ctx, lease) + + Expect(result.RequeueAfter).To(BeNumerically(">", 0)) + + // Poll until BeginTime passes and exporter is acquired + var updatedLease *jumpstarterdevv1alpha1.Lease + Eventually(func() bool { + _ = reconcileLease(ctx, lease) + updatedLease = getLease(ctx, lease.Name) + return updatedLease.Status.ExporterRef != nil + }).WithTimeout(1200 * time.Millisecond).WithPolling(50 * time.Millisecond).Should(BeTrue()) + + Expect(updatedLease.Status.BeginTime).NotTo(BeNil()) + Expect(updatedLease.Spec.BeginTime.Time).To(Equal(beginTime.Time)) + Expect(updatedLease.Spec.EndTime.Time).To(Equal(endTime.Time)) + Expect(updatedLease.Spec.Duration.Duration).To(Equal(duration)) + }) + + It("should reject when Duration conflicts with EndTime - BeginTime", func() { + // Test through the service layer (LeaseFromProtobuf) which validates + beginTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(1 * time.Second)) + endTime := metav1.NewTime(beginTime.Add(1 * time.Second)) + conflictingDuration := 2 * time.Second // Wrong! Should be 1 second + + // Create via LeaseFromProtobuf to trigger validation + key := types.NamespacedName{Name: "test-lease", Namespace: "default"} + clientRef := corev1.LocalObjectReference{Name: testClient.Name} + + pbLease := &cpb.Lease{ + Selector: "dut=a", + } + pbLease.BeginTime = timestamppb.New(beginTime.Time) + pbLease.EndTime = timestamppb.New(endTime.Time) + pbLease.Duration = durationpb.New(conflictingDuration) + + lease, err := jumpstarterdevv1alpha1.LeaseFromProtobuf(pbLease, key, clientRef) + + // Should fail validation + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("duration conflicts")) + Expect(lease).To(BeNil()) + }) + }) + + When("creating lease with BeginTime already in the past", func() { + It("should start immediately without requeuing", func() { + lease := leaseDutA2Sec.DeepCopy() + // Set BeginTime to 2 seconds in the past to ensure it's definitely passed + nowTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(-2 * time.Second)) + lease.Spec.BeginTime = &nowTime + lease.Spec.Duration = &metav1.Duration{Duration: 1 * time.Second} + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + result := reconcileLease(ctx, lease) + + // Should not requeue (or requeue with 0) + Expect(result.RequeueAfter).To(BeNumerically("<=", 0)) + + updatedLease := getLease(ctx, lease.Name) + Expect(updatedLease.Status.ExporterRef).NotTo(BeNil(), "Should acquire exporter immediately") + Expect(updatedLease.Status.BeginTime).NotTo(BeNil()) + }) + }) + + When("lease expires based on Spec.EndTime", func() { + It("should end the lease at EndTime even if Duration would suggest later", func() { + lease := leaseDutA2Sec.DeepCopy() + endTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(1 * time.Second)) + lease.Spec.EndTime = &endTime + lease.Spec.Duration = &metav1.Duration{Duration: 10 * time.Second} // Much longer than EndTime + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + _ = reconcileLease(ctx, lease) + + updatedLease := getLease(ctx, lease.Name) + Expect(updatedLease.Status.ExporterRef).NotTo(BeNil()) + + // Poll until EndTime passes and lease ends + Eventually(func() bool { + _ = reconcileLease(ctx, lease) + updatedLease = getLease(ctx, lease.Name) + return updatedLease.Status.Ended + }).WithTimeout(3*time.Second).WithPolling(50*time.Millisecond).Should(BeTrue(), "Should respect EndTime over Duration") + Expect(updatedLease.Status.EndTime).NotTo(BeNil()) + + // Verify EffectiveDuration is calculated correctly + pbLease := updatedLease.ToProtobuf() + Expect(pbLease.EffectiveDuration).NotTo(BeNil()) + actualDuration := updatedLease.Status.EndTime.Sub(updatedLease.Status.BeginTime.Time) + // Allow tolerance for CI environments - duration is based on second-truncated times + Expect(pbLease.EffectiveDuration.AsDuration()).To(BeNumerically("~", actualDuration, 1*time.Second)) + // Verify it's shorter than the specified Duration (10s) + Expect(pbLease.EffectiveDuration.AsDuration()).To(BeNumerically("<", 3*time.Second)) + }) + }) + + When("lease with BeginTime expires based on BeginTime + Duration", func() { + It("should end the lease at BeginTime + Duration", func() { + lease := leaseDutA2Sec.DeepCopy() + beginTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(1 * time.Second)) + duration := 1 * time.Second + lease.Spec.BeginTime = &beginTime + lease.Spec.Duration = &metav1.Duration{Duration: duration} + lease.Spec.EndTime = nil + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + + // Poll until BeginTime passes and exporter is acquired + var updatedLease *jumpstarterdevv1alpha1.Lease + Eventually(func() bool { + _ = reconcileLease(ctx, lease) + updatedLease = getLease(ctx, lease.Name) + return updatedLease.Status.ExporterRef != nil + }).WithTimeout(1200 * time.Millisecond).WithPolling(50 * time.Millisecond).Should(BeTrue()) + + // Poll until lease expires (Duration after BeginTime) + Eventually(func() bool { + _ = reconcileLease(ctx, lease) + updatedLease = getLease(ctx, lease.Name) + return updatedLease.Status.Ended + }).WithTimeout(3*time.Second).WithPolling(50*time.Millisecond).Should(BeTrue(), "Should expire at BeginTime + Duration") + Expect(updatedLease.Status.EndTime).NotTo(BeNil()) + + // Verify EffectiveDuration matches the specified duration + // Allow generous tolerance for CI environments with second-precision timestamps + pbLease := updatedLease.ToProtobuf() + Expect(pbLease.EffectiveDuration).NotTo(BeNil()) + Expect(pbLease.EffectiveDuration.AsDuration()).To(BeNumerically("~", duration, 1*time.Second)) + }) + }) + + When("lease without BeginTime expires based on Status.BeginTime + Duration", func() { + It("should end the lease at Status.BeginTime + Duration", func() { + lease := leaseDutA2Sec.DeepCopy() + lease.Spec.Duration = &metav1.Duration{Duration: 1 * time.Second} + lease.Spec.BeginTime = nil + lease.Spec.EndTime = nil + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + _ = reconcileLease(ctx, lease) + + updatedLease := getLease(ctx, lease.Name) + Expect(updatedLease.Status.ExporterRef).NotTo(BeNil()) + Expect(updatedLease.Status.BeginTime).NotTo(BeNil()) + actualBeginTime := updatedLease.Status.BeginTime.Time + + // Poll until lease expires + Eventually(func() bool { + _ = reconcileLease(ctx, lease) + updatedLease = getLease(ctx, lease.Name) + return updatedLease.Status.Ended + }).WithTimeout(2 * time.Second).WithPolling(50 * time.Millisecond).Should(BeTrue()) + Expect(updatedLease.Status.EndTime).NotTo(BeNil()) + + // Verify it expired based on Status.BeginTime + Duration + expectedExpiry := actualBeginTime.Add(1 * time.Second) + Expect(time.Now().Truncate(time.Second)).To(BeTemporally(">=", expectedExpiry)) + + // Verify EffectiveDuration is calculated correctly + // Allow generous tolerance for CI environments with second-precision timestamps + pbLease := updatedLease.ToProtobuf() + Expect(pbLease.EffectiveDuration).NotTo(BeNil()) + Expect(pbLease.EffectiveDuration.AsDuration()).To(BeNumerically("~", 1*time.Second, 1*time.Second)) + }) + }) + + When("checking EffectiveDuration on active lease", func() { + It("should calculate EffectiveDuration as current time minus Status.BeginTime", func() { + lease := leaseDutA2Sec.DeepCopy() + lease.Spec.Duration = &metav1.Duration{Duration: 10 * time.Second} // Long duration so it doesn't expire + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + _ = reconcileLease(ctx, lease) + + updatedLease := getLease(ctx, lease.Name) + Expect(updatedLease.Status.ExporterRef).NotTo(BeNil()) + Expect(updatedLease.Status.BeginTime).NotTo(BeNil()) + Expect(updatedLease.Status.EndTime).To(BeNil(), "Active lease should not have EndTime") + + // Check EffectiveDuration on active lease + beforeCheck := time.Now().Truncate(time.Second) + pbLease := updatedLease.ToProtobuf() + afterCheck := time.Now().Truncate(time.Second).Add(time.Second) + + Expect(pbLease.EffectiveBeginTime).NotTo(BeNil()) + Expect(pbLease.EffectiveEndTime).To(BeNil(), "Active lease should not have EffectiveEndTime") + Expect(pbLease.EffectiveDuration).NotTo(BeNil()) + + // EffectiveDuration should be approximately now() - BeginTime + expectedMinDuration := beforeCheck.Sub(updatedLease.Status.BeginTime.Time) + expectedMaxDuration := afterCheck.Sub(updatedLease.Status.BeginTime.Time) + actualDuration := pbLease.EffectiveDuration.AsDuration() + Expect(actualDuration).To(BeNumerically(">=", expectedMinDuration)) + Expect(actualDuration).To(BeNumerically("<=", expectedMaxDuration)) + }) + }) + + When("multiple leases with different BeginTimes", func() { + It("should acquire exporters at their respective BeginTimes", func() { + ctx := context.Background() + + // Immediate lease + lease1 := leaseDutA2Sec.DeepCopy() + lease1.Name = lease1Name + lease1.Spec.Duration = &metav1.Duration{Duration: 5 * time.Second} + Expect(k8sClient.Create(ctx, lease1)).To(Succeed()) + _ = reconcileLease(ctx, lease1) + + updatedLease1 := getLease(ctx, lease1Name) + Expect(updatedLease1.Status.ExporterRef).NotTo(BeNil()) + exporter1 := updatedLease1.Status.ExporterRef.Name + + // Scheduled lease 1s in future + lease2 := leaseDutA2Sec.DeepCopy() + lease2.Name = lease2Name + futureTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(1 * time.Second)) + lease2.Spec.BeginTime = &futureTime + lease2.Spec.Duration = &metav1.Duration{Duration: 1 * time.Second} + Expect(k8sClient.Create(ctx, lease2)).To(Succeed()) + _ = reconcileLease(ctx, lease2) + + updatedLease2 := getLease(ctx, lease2Name) + Expect(updatedLease2.Status.ExporterRef).To(BeNil(), "Scheduled lease should wait") + + // Poll until lease2's BeginTime passes and exporter is acquired + Eventually(func() bool { + _ = reconcileLease(ctx, lease2) + updatedLease2 = getLease(ctx, lease2Name) + return updatedLease2.Status.ExporterRef != nil + }).WithTimeout(1200*time.Millisecond).WithPolling(50*time.Millisecond).Should(BeTrue(), "Should acquire after BeginTime") + exporter2 := updatedLease2.Status.ExporterRef.Name + + // Should have acquired different exporters (both dut:a exporters) + Expect(exporter2).NotTo(Equal(exporter1)) + Expect([]string{exporter1, exporter2}).To(ConsistOf(testExporter1DutA.Name, testExporter2DutA.Name)) + }) + }) + + // Validation error tests + When("creating lease with BeginTime after EndTime", func() { + It("should reject with validation error", func() { + beginTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(1 * time.Second)) + endTime := metav1.NewTime(beginTime.Add(-1 * time.Second)) // Before BeginTime! + + key := types.NamespacedName{Name: "invalid-lease", Namespace: "default"} + clientRef := corev1.LocalObjectReference{Name: testClient.Name} + + pbLease := &cpb.Lease{ + Selector: "dut=a", + BeginTime: timestamppb.New(beginTime.Time), + EndTime: timestamppb.New(endTime.Time), + // No duration provided - will calculate negative duration from BeginTime > EndTime + } + + lease, err := jumpstarterdevv1alpha1.LeaseFromProtobuf(pbLease, key, clientRef) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("duration must be positive")) + Expect(lease).To(BeNil()) + }) + }) + + When("creating lease with BeginTime but zero Duration and no EndTime", func() { + It("should reject with validation error", func() { + beginTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(1 * time.Second)) + + key := types.NamespacedName{Name: "invalid-lease", Namespace: "default"} + clientRef := corev1.LocalObjectReference{Name: testClient.Name} + + pbLease := &cpb.Lease{ + Selector: "dut=a", + } + pbLease.BeginTime = timestamppb.New(beginTime.Time) + // No Duration, no EndTime + + lease, err := jumpstarterdevv1alpha1.LeaseFromProtobuf(pbLease, key, clientRef) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("duration is required")) + Expect(lease).To(BeNil()) + }) + }) + + // EndTime in the past + When("creating lease with EndTime already in the past", func() { + It("should create but expire immediately", func() { + lease := leaseDutA2Sec.DeepCopy() + pastEndTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(-500 * time.Millisecond)) + lease.Spec.EndTime = &pastEndTime + lease.Spec.Duration = nil + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + _ = reconcileLease(ctx, lease) + + updatedLease := getLease(ctx, lease.Name) + // Should acquire exporter (or try to) + // Then immediately expire because EndTime is in the past + Expect(updatedLease.Status.Ended).To(BeTrue(), "Lease should be ended immediately") + Expect(updatedLease.Status.EndTime).NotTo(BeNil()) + }) + }) + + When("creating lease with BeginTime in past but EndTime in future", func() { + It("should start immediately and run until EndTime", func() { + lease := leaseDutA2Sec.DeepCopy() + pastBeginTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(-500 * time.Millisecond)) + futureEndTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(1 * time.Second)) + lease.Spec.BeginTime = &pastBeginTime + lease.Spec.EndTime = &futureEndTime + lease.Spec.Duration = &metav1.Duration{Duration: futureEndTime.Sub(pastBeginTime.Time)} + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + _ = reconcileLease(ctx, lease) + + updatedLease := getLease(ctx, lease.Name) + Expect(updatedLease.Status.ExporterRef).NotTo(BeNil(), "Should acquire immediately") + Expect(updatedLease.Status.BeginTime).NotTo(BeNil()) + Expect(updatedLease.Status.Ended).To(BeFalse(), "Should not be ended yet") + + // Poll until EndTime passes and lease ends + Eventually(func() bool { + _ = reconcileLease(ctx, lease) + updatedLease = getLease(ctx, lease.Name) + return updatedLease.Status.Ended + }).WithTimeout(3*time.Second).WithPolling(50*time.Millisecond).Should(BeTrue(), "Should expire at EndTime") + }) + }) + + // Early release scenarios + When("releasing a scheduled lease before it starts", func() { + It("should cancel the scheduled lease", func() { + lease := leaseDutA2Sec.DeepCopy() + futureTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(1 * time.Second)) + lease.Spec.BeginTime = &futureTime + lease.Spec.Duration = &metav1.Duration{Duration: 1 * time.Second} + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + _ = reconcileLease(ctx, lease) + + updatedLease := getLease(ctx, lease.Name) + Expect(updatedLease.Status.ExporterRef).To(BeNil(), "Should not have acquired yet") + Expect(updatedLease.Status.Ended).To(BeFalse()) + + // Release before BeginTime + updatedLease.Spec.Release = true + Expect(k8sClient.Update(ctx, updatedLease)).To(Succeed()) + _ = reconcileLease(ctx, updatedLease) + + updatedLease = getLease(ctx, lease.Name) + Expect(updatedLease.Status.Ended).To(BeTrue(), "Should be cancelled/ended") + Expect(updatedLease.Status.ExporterRef).To(BeNil(), "Should never have acquired exporter") + }) + }) + + When("releasing an active lease early", func() { + It("should have EffectiveDuration matching actual time held", func() { + lease := leaseDutA2Sec.DeepCopy() + lease.Spec.Duration = &metav1.Duration{Duration: 10 * time.Second} // Long duration + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + _ = reconcileLease(ctx, lease) + + updatedLease := getLease(ctx, lease.Name) + Expect(updatedLease.Status.ExporterRef).NotTo(BeNil()) + Expect(updatedLease.Status.BeginTime).NotTo(BeNil()) + beginTime := updatedLease.Status.BeginTime.Time + + // Brief wait to ensure some time has passed + time.Sleep(50 * time.Millisecond) + + // Release early + updatedLease = getLease(ctx, lease.Name) + updatedLease.Spec.Release = true + Expect(k8sClient.Update(ctx, updatedLease)).To(Succeed()) + _ = reconcileLease(ctx, updatedLease) + + updatedLease = getLease(ctx, lease.Name) + Expect(updatedLease.Status.Ended).To(BeTrue()) + Expect(updatedLease.Status.EndTime).NotTo(BeNil()) + + // EffectiveDuration should be actual time held, not 10 seconds + // Allow generous tolerance for CI environments with second-precision timestamps + pbLease := updatedLease.ToProtobuf() + Expect(pbLease.EffectiveDuration).NotTo(BeNil()) + actualDuration := pbLease.EffectiveDuration.AsDuration() + expectedDuration := updatedLease.Status.EndTime.Sub(beginTime) + Expect(actualDuration).To(BeNumerically("~", expectedDuration, 1*time.Second)) + Expect(actualDuration).To(BeNumerically("<=", 2*time.Second), "Should be much less than 10s") + }) + }) + + // Boundary conditions + When("creating lease with BeginTime very close to EndTime", func() { + It("should work with minimal duration", func() { + lease := leaseDutA2Sec.DeepCopy() + beginTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(1 * time.Second)) + endTime := metav1.NewTime(beginTime.Add(1 * time.Second)) // 1 second duration + lease.Spec.BeginTime = &beginTime + lease.Spec.EndTime = &endTime + lease.Spec.Duration = &metav1.Duration{Duration: 1 * time.Second} + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + + // Poll until BeginTime passes and exporter is acquired + var updatedLease *jumpstarterdevv1alpha1.Lease + Eventually(func() bool { + _ = reconcileLease(ctx, lease) + updatedLease = getLease(ctx, lease.Name) + return updatedLease.Status.ExporterRef != nil + }).WithTimeout(1200 * time.Millisecond).WithPolling(50 * time.Millisecond).Should(BeTrue()) + + // Poll until 1-second duration expires + Eventually(func() bool { + _ = reconcileLease(ctx, lease) + updatedLease = getLease(ctx, lease.Name) + return updatedLease.Status.Ended + }).WithTimeout(1200 * time.Millisecond).WithPolling(50 * time.Millisecond).Should(BeTrue()) + }) + }) + + When("lease expires between reconciliation calls", func() { + It("should be marked as ended in next reconcile", func() { + lease := leaseDutA2Sec.DeepCopy() + lease.Spec.Duration = &metav1.Duration{Duration: 150 * time.Millisecond} + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + _ = reconcileLease(ctx, lease) + + updatedLease := getLease(ctx, lease.Name) + Expect(updatedLease.Status.ExporterRef).NotTo(BeNil()) + Expect(updatedLease.Status.Ended).To(BeFalse()) + + // Poll until expiration is detected (lease duration is 150ms) + Eventually(func() bool { + _ = reconcileLease(ctx, lease) + updatedLease = getLease(ctx, lease.Name) + return updatedLease.Status.Ended + }).WithTimeout(500*time.Millisecond).WithPolling(50*time.Millisecond).Should(BeTrue(), "Should be marked as ended") + }) + }) + + // UpdateLease mutation tests + // Note: These tests simulate what UpdateLease does via gRPC by directly + // modifying the lease spec and calling ReconcileLeaseTimeFields + When("updating BeginTime on a lease that has already started", func() { + It("should be rejected in UpdateLease logic", func() { + // This tests the validation that exists in client_service.go UpdateLease + // We simulate it by checking the condition: ExporterRef != nil + lease := leaseDutA2Sec.DeepCopy() + lease.Spec.Duration = &metav1.Duration{Duration: 5 * time.Second} + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + _ = reconcileLease(ctx, lease) + + updatedLease := getLease(ctx, lease.Name) + Expect(updatedLease.Status.ExporterRef).NotTo(BeNil(), "Lease should be active") + + // Try to update BeginTime - this would be rejected by UpdateLease + // We verify the precondition that UpdateLease checks + Expect(updatedLease.Status.ExporterRef).NotTo(BeNil(), "Cannot update BeginTime after lease starts") + }) + }) + + When("updating EndTime on a scheduled lease before it starts", func() { + It("should update EndTime and recalculate Duration", func() { + lease := leaseDutA2Sec.DeepCopy() + beginTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(1 * time.Second)) + endTime := metav1.NewTime(beginTime.Add(1 * time.Second)) + lease.Spec.BeginTime = &beginTime + lease.Spec.EndTime = &endTime + lease.Spec.Duration = &metav1.Duration{Duration: 1 * time.Second} + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + _ = reconcileLease(ctx, lease) + + updatedLease := getLease(ctx, lease.Name) + Expect(updatedLease.Status.ExporterRef).To(BeNil(), "Should not have started yet") + + // Update EndTime (simulating UpdateLease behavior) + newEndTime := metav1.NewTime(beginTime.Add(2 * time.Second)) + updatedLease.Spec.EndTime = &newEndTime + // Clear Duration so it gets recalculated + updatedLease.Spec.Duration = nil + + // Recalculate (this is what UpdateLease does) + err := jumpstarterdevv1alpha1.ReconcileLeaseTimeFields( + &updatedLease.Spec.BeginTime, + &updatedLease.Spec.EndTime, + &updatedLease.Spec.Duration, + ) + Expect(err).NotTo(HaveOccurred()) + + // Duration should be recalculated + Expect(updatedLease.Spec.Duration.Duration).To(Equal(2 * time.Second)) + Expect(updatedLease.Spec.EndTime.Time).To(Equal(newEndTime.Time)) + }) + }) + + When("extending an active lease by updating EndTime", func() { + It("should extend the lease duration", func() { + lease := leaseDutA2Sec.DeepCopy() + endTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(1 * time.Second)) + lease.Spec.EndTime = &endTime + lease.Spec.Duration = nil + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + _ = reconcileLease(ctx, lease) + + updatedLease := getLease(ctx, lease.Name) + Expect(updatedLease.Status.ExporterRef).NotTo(BeNil(), "Should be active") + Expect(updatedLease.Status.Ended).To(BeFalse()) + + // Extend EndTime to 2 seconds from now + newEndTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(2 * time.Second)) + updatedLease.Spec.EndTime = &newEndTime + Expect(k8sClient.Update(ctx, updatedLease)).To(Succeed()) + + // Verify lease is still active after extension + _ = reconcileLease(ctx, lease) + updatedLease = getLease(ctx, lease.Name) + Expect(updatedLease.Status.Ended).To(BeFalse(), "Should not expire yet due to extension") + + // Poll until new EndTime passes and lease ends + Eventually(func() bool { + _ = reconcileLease(ctx, lease) + updatedLease = getLease(ctx, lease.Name) + return updatedLease.Status.Ended + }).WithTimeout(2200*time.Millisecond).WithPolling(50*time.Millisecond).Should(BeTrue(), "Should expire at new EndTime") + }) + }) + + When("shortening an active lease by updating Duration", func() { + It("should shorten the lease duration", func() { + lease := leaseDutA2Sec.DeepCopy() + lease.Spec.Duration = &metav1.Duration{Duration: 1 * time.Second} + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + _ = reconcileLease(ctx, lease) + + updatedLease := getLease(ctx, lease.Name) + Expect(updatedLease.Status.ExporterRef).NotTo(BeNil(), "Should be active") + Expect(updatedLease.Status.BeginTime).NotTo(BeNil()) + + // Shorten to 200ms total duration + updatedLease.Spec.Duration = &metav1.Duration{Duration: 200 * time.Millisecond} + Expect(k8sClient.Update(ctx, updatedLease)).To(Succeed()) + + // Poll until lease expires after shortened duration + Eventually(func() bool { + _ = reconcileLease(ctx, lease) + updatedLease = getLease(ctx, lease.Name) + return updatedLease.Status.Ended + }).WithTimeout(500*time.Millisecond).WithPolling(50*time.Millisecond).Should(BeTrue(), "Should expire after shortened duration") + }) + }) + + When("updating scheduled lease EndTime before it starts", func() { + It("should allow update and adjust timing", func() { + lease := leaseDutA2Sec.DeepCopy() + beginTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(1 * time.Second)) + endTime := metav1.NewTime(beginTime.Add(10 * time.Second)) // Very long lease initially + lease.Spec.BeginTime = &beginTime + lease.Spec.EndTime = &endTime + lease.Spec.Duration = &metav1.Duration{Duration: 10 * time.Second} + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + _ = reconcileLease(ctx, lease) + + updatedLease := getLease(ctx, lease.Name) + Expect(updatedLease.Status.ExporterRef).To(BeNil(), "Should not have started") + + // Shorten EndTime significantly + newEndTime := metav1.NewTime(beginTime.Add(1 * time.Second)) + updatedLease.Spec.EndTime = &newEndTime + // Clear Duration so it gets recalculated + updatedLease.Spec.Duration = nil + + // Recalculate Duration (simulating UpdateLease) + err := jumpstarterdevv1alpha1.ReconcileLeaseTimeFields( + &updatedLease.Spec.BeginTime, + &updatedLease.Spec.EndTime, + &updatedLease.Spec.Duration, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(updatedLease.Spec.Duration.Duration).To(Equal(1 * time.Second)) + + Expect(k8sClient.Update(ctx, updatedLease)).To(Succeed()) + + // Poll until BeginTime passes and exporter is acquired + Eventually(func() bool { + _ = reconcileLease(ctx, updatedLease) + updatedLease = getLease(ctx, lease.Name) + return updatedLease.Status.ExporterRef != nil + }).WithTimeout(1200 * time.Millisecond).WithPolling(50 * time.Millisecond).Should(BeTrue()) + + // Poll until lease expires at new (shortened) EndTime (1s duration) + Eventually(func() bool { + _ = reconcileLease(ctx, lease) + updatedLease = getLease(ctx, lease.Name) + return updatedLease.Status.Ended + }).WithTimeout(1200 * time.Millisecond).WithPolling(50 * time.Millisecond).Should(BeTrue()) + }) + }) + + When("updating a lease with all three fields to maintain consistency", func() { + It("should allow valid updates", func() { + lease := leaseDutA2Sec.DeepCopy() + beginTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(1 * time.Second)) + duration := 500 * time.Millisecond + endTime := metav1.NewTime(beginTime.Add(duration)) + + lease.Spec.BeginTime = &beginTime + lease.Spec.EndTime = &endTime + lease.Spec.Duration = &metav1.Duration{Duration: duration} + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + _ = reconcileLease(ctx, lease) + + updatedLease := getLease(ctx, lease.Name) + Expect(updatedLease.Status.ExporterRef).To(BeNil(), "Should not have started yet") + + // Update all three fields consistently + newDuration := 800 * time.Millisecond + newEndTime := metav1.NewTime(beginTime.Add(newDuration)) + updatedLease.Spec.Duration = &metav1.Duration{Duration: newDuration} + updatedLease.Spec.EndTime = &newEndTime + + // Validate consistency (simulating UpdateLease) + err := jumpstarterdevv1alpha1.ReconcileLeaseTimeFields( + &updatedLease.Spec.BeginTime, + &updatedLease.Spec.EndTime, + &updatedLease.Spec.Duration, + ) + Expect(err).NotTo(HaveOccurred(), "Consistent update should succeed") + Expect(updatedLease.Spec.Duration.Duration).To(Equal(newDuration)) + Expect(updatedLease.Spec.EndTime.Time).To(Equal(newEndTime.Time)) + }) + }) + + When("updating a lease with all three fields to create conflict", func() { + It("should reject updates that break consistency", func() { + // Start with consistent fields + beginTimeVal := metav1.NewTime(time.Now().Truncate(time.Second).Add(1 * time.Second)) + beginTime := &beginTimeVal + duration := 500 * time.Millisecond + endTimeVal := metav1.NewTime(beginTimeVal.Add(duration)) + endTime := &endTimeVal + + // Try to update Duration to conflict with BeginTime and EndTime + conflictingDuration := &metav1.Duration{Duration: 1 * time.Second} // Wrong! EndTime-BeginTime = 500ms + + // Simulate UpdateLease validation + err := jumpstarterdevv1alpha1.ReconcileLeaseTimeFields( + &beginTime, + &endTime, + &conflictingDuration, + ) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("duration conflicts")) + }) + }) + + When("updating active lease Duration when all three fields exist", func() { + It("should require updating both Duration and EndTime to keep them consistent", func() { + lease := leaseDutA2Sec.DeepCopy() + beginTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(1 * time.Second)) + duration := 10 * time.Second // Long duration initially + endTime := metav1.NewTime(beginTime.Add(duration)) + + lease.Spec.BeginTime = &beginTime + lease.Spec.EndTime = &endTime + lease.Spec.Duration = &metav1.Duration{Duration: duration} + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + + // Poll until lease starts + var updatedLease *jumpstarterdevv1alpha1.Lease + Eventually(func() bool { + _ = reconcileLease(ctx, lease) + updatedLease = getLease(ctx, lease.Name) + return updatedLease.Status.ExporterRef != nil + }).WithTimeout(1200*time.Millisecond).WithPolling(50*time.Millisecond).Should(BeTrue(), "Should have started") + + // Shorten the lease: Update both Duration AND EndTime together (must stay consistent) + newDuration := 800 * time.Millisecond + updatedLease.Spec.Duration = &metav1.Duration{Duration: newDuration} + newEndTime := metav1.NewTime(beginTime.Add(newDuration)) + updatedLease.Spec.EndTime = &newEndTime + + // Validate the updated fields (should pass since all three are consistent) + err := jumpstarterdevv1alpha1.ReconcileLeaseTimeFields( + &updatedLease.Spec.BeginTime, + &updatedLease.Spec.EndTime, + &updatedLease.Spec.Duration, + ) + Expect(err).NotTo(HaveOccurred()) + + Expect(k8sClient.Update(ctx, updatedLease)).To(Succeed()) + + // Poll until lease expires at new EndTime (800ms duration) + Eventually(func() bool { + _ = reconcileLease(ctx, lease) + updatedLease = getLease(ctx, lease.Name) + return updatedLease.Status.Ended + }).WithTimeout(1500 * time.Millisecond).WithPolling(50 * time.Millisecond).Should(BeTrue()) + }) + }) + + // Additional edge cases + When("two scheduled leases compete for the same exporter", func() { + It("should acquire first lease at BeginTime, then second after first is released", func() { + ctx := context.Background() + + // Use same BeginTime for both to avoid timing races + sharedBeginTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(1 * time.Second)) + + // Both leases target dut:b (only one exporter available) + lease1 := leaseDutA2Sec.DeepCopy() + lease1.Name = lease1Name + lease1.Spec.Selector.MatchLabels["dut"] = "b" + lease1.Spec.BeginTime = &sharedBeginTime + lease1.Spec.Duration = &metav1.Duration{Duration: 10 * time.Second} // Long duration, but we'll release early + + lease2 := leaseDutA2Sec.DeepCopy() + lease2.Name = lease2Name + lease2.Spec.Selector.MatchLabels["dut"] = "b" + lease2.Spec.BeginTime = &sharedBeginTime + lease2.Spec.Duration = &metav1.Duration{Duration: 10 * time.Second} + + Expect(k8sClient.Create(ctx, lease1)).To(Succeed()) + Expect(k8sClient.Create(ctx, lease2)).To(Succeed()) + + // Both should be waiting + _ = reconcileLease(ctx, lease1) + _ = reconcileLease(ctx, lease2) + + updatedLease1 := getLease(ctx, lease1Name) + updatedLease2 := getLease(ctx, lease2Name) + Expect(updatedLease1.Status.ExporterRef).To(BeNil()) + Expect(updatedLease2.Status.ExporterRef).To(BeNil()) + + // Poll until lease1's BeginTime passes and it acquires exporter + Eventually(func() bool { + _ = reconcileLease(ctx, lease1) + _ = reconcileLease(ctx, lease2) + updatedLease1 = getLease(ctx, lease1Name) + return updatedLease1.Status.ExporterRef != nil + }).WithTimeout(2*time.Second).WithPolling(50*time.Millisecond).Should(BeTrue(), "lease1 should acquire exporter") + + updatedLease2 = getLease(ctx, lease2Name) + Expect(updatedLease2.Status.ExporterRef).To(BeNil(), "lease2 should still be waiting") + + // Explicitly release lease1 + updatedLease1 = getLease(ctx, lease1Name) + updatedLease1.Spec.Release = true + Expect(k8sClient.Update(ctx, updatedLease1)).To(Succeed()) + + // Poll until lease1 is released and lease2 acquires exporter immediately + Eventually(func() bool { + _ = reconcileLease(ctx, lease1) + _ = reconcileLease(ctx, lease2) + updatedLease1 = getLease(ctx, lease1Name) + updatedLease2 = getLease(ctx, lease2Name) + return updatedLease1.Status.Ended && updatedLease2.Status.ExporterRef != nil + }).WithTimeout(1500*time.Millisecond).WithPolling(50*time.Millisecond).Should(BeTrue(), "lease1 should be released and lease2 should acquire exporter immediately") + }) + }) + + When("deleting a scheduled lease before it starts", func() { + It("should delete successfully without acquiring exporter", func() { + lease := leaseDutA2Sec.DeepCopy() + futureTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(5 * time.Second)) + lease.Spec.BeginTime = &futureTime + lease.Spec.Duration = &metav1.Duration{Duration: 1 * time.Second} + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + _ = reconcileLease(ctx, lease) + + updatedLease := getLease(ctx, lease.Name) + Expect(updatedLease.Status.ExporterRef).To(BeNil(), "Should not have acquired yet") + + // Delete before BeginTime + Expect(k8sClient.Delete(ctx, updatedLease)).To(Succeed()) + + // Verify it's deleted + err := k8sClient.Get(ctx, types.NamespacedName{ + Name: lease.Name, + Namespace: "default", + }, &jumpstarterdevv1alpha1.Lease{}) + Expect(err).To(HaveOccurred(), "Lease should be deleted") + }) + }) + + When("updating scheduled lease to make BeginTime in the past", func() { + It("should start immediately after update", func() { + lease := leaseDutA2Sec.DeepCopy() + futureTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(5 * time.Second)) + lease.Spec.BeginTime = &futureTime + lease.Spec.Duration = &metav1.Duration{Duration: 1 * time.Second} + + ctx := context.Background() + Expect(k8sClient.Create(ctx, lease)).To(Succeed()) + _ = reconcileLease(ctx, lease) + + updatedLease := getLease(ctx, lease.Name) + Expect(updatedLease.Status.ExporterRef).To(BeNil(), "Should not have started yet") + + // Update BeginTime to be in the past + pastTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(-100 * time.Millisecond)) + updatedLease.Spec.BeginTime = &pastTime + Expect(k8sClient.Update(ctx, updatedLease)).To(Succeed()) + + // Should acquire immediately now + _ = reconcileLease(ctx, updatedLease) + + updatedLease = getLease(ctx, lease.Name) + Expect(updatedLease.Status.ExporterRef).NotTo(BeNil(), "Should acquire immediately after BeginTime moved to past") + Expect(updatedLease.Status.BeginTime).NotTo(BeNil()) + + // Verify that actual BeginTime is before the original futureTime (started early) + Expect(updatedLease.Status.BeginTime.Time).To(BeTemporally("<", futureTime.Time), "Should have started before the original scheduled time") + }) + }) + + When("creating lease with negative Duration", func() { + It("should reject with validation error", func() { + key := types.NamespacedName{Name: "invalid-lease", Namespace: "default"} + clientRef := corev1.LocalObjectReference{Name: testClient.Name} + + pbLease := &cpb.Lease{ + Selector: "dut=a", + } + pbLease.Duration = durationpb.New(-1 * time.Second) // Negative! + + lease, err := jumpstarterdevv1alpha1.LeaseFromProtobuf(pbLease, key, clientRef) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("duration must be positive")) + Expect(lease).To(BeNil()) + }) + }) + + When("creating lease with EndTime and negative Duration", func() { + It("should reject with validation error", func() { + key := types.NamespacedName{Name: "invalid-lease-2", Namespace: "default"} + clientRef := corev1.LocalObjectReference{Name: testClient.Name} + + endTime := metav1.NewTime(time.Now().Truncate(time.Second).Add(1 * time.Second)) + pbLease := &cpb.Lease{ + Selector: "dut=a", + EndTime: timestamppb.New(endTime.Time), + } + pbLease.Duration = durationpb.New(-2 * time.Second) // Negative! + + lease, err := jumpstarterdevv1alpha1.LeaseFromProtobuf(pbLease, key, clientRef) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("duration must be positive")) + Expect(lease).To(BeNil()) + }) + }) +}) From cacdf9e34106385e0169cf9c029449e07b037f3f Mon Sep 17 00:00:00 2001 From: Michal Skrivanek Date: Mon, 13 Oct 2025 18:37:14 +0200 Subject: [PATCH 4/4] Regenerate protobuf --- .../jumpstarter/client/v1/client.pb.go | 129 +++-- internal/protocol/jumpstarter/v1/common.pb.go | 216 +++++++ .../protocol/jumpstarter/v1/jumpstarter.pb.go | 539 +++++++++++++----- .../jumpstarter/v1/jumpstarter_grpc.pb.go | 80 +++ .../protocol/jumpstarter/v1/kubernetes.pb.go | 2 +- internal/protocol/jumpstarter/v1/router.pb.go | 2 +- 6 files changed, 760 insertions(+), 208 deletions(-) create mode 100644 internal/protocol/jumpstarter/v1/common.pb.go diff --git a/internal/protocol/jumpstarter/client/v1/client.pb.go b/internal/protocol/jumpstarter/client/v1/client.pb.go index 87d7efa0..21445c95 100644 --- a/internal/protocol/jumpstarter/client/v1/client.pb.go +++ b/internal/protocol/jumpstarter/client/v1/client.pb.go @@ -7,13 +7,17 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.6 +// protoc-gen-go v1.36.10 // protoc (unknown) // source: jumpstarter/client/v1/client.proto package clientv1 import ( + reflect "reflect" + sync "sync" + unsafe "unsafe" + v1 "github.com/jumpstarter-dev/jumpstarter-controller/internal/protocol/jumpstarter/v1" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" @@ -21,9 +25,6 @@ import ( emptypb "google.golang.org/protobuf/types/known/emptypb" fieldmaskpb "google.golang.org/protobuf/types/known/fieldmaskpb" timestamppb "google.golang.org/protobuf/types/known/timestamppb" - reflect "reflect" - sync "sync" - unsafe "unsafe" ) const ( @@ -34,10 +35,12 @@ const ( ) type Exporter struct { - state protoimpl.MessageState `protogen:"open.v1"` - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Labels map[string]string `protobuf:"bytes,2,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - Online bool `protobuf:"varint,3,opt,name=online,proto3" json:"online,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Labels map[string]string `protobuf:"bytes,2,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Deprecated: Marked as deprecated in jumpstarter/client/v1/client.proto. + Online bool `protobuf:"varint,3,opt,name=online,proto3" json:"online,omitempty"` + Status v1.ExporterStatus `protobuf:"varint,4,opt,name=status,proto3,enum=jumpstarter.v1.ExporterStatus" json:"status,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -86,6 +89,7 @@ func (x *Exporter) GetLabels() map[string]string { return nil } +// Deprecated: Marked as deprecated in jumpstarter/client/v1/client.proto. func (x *Exporter) GetOnline() bool { if x != nil { return x.Online @@ -93,11 +97,18 @@ func (x *Exporter) GetOnline() bool { return false } +func (x *Exporter) GetStatus() v1.ExporterStatus { + if x != nil { + return x.Status + } + return v1.ExporterStatus(0) +} + type Lease struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Selector string `protobuf:"bytes,2,opt,name=selector,proto3" json:"selector,omitempty"` - Duration *durationpb.Duration `protobuf:"bytes,3,opt,name=duration,proto3" json:"duration,omitempty"` + Duration *durationpb.Duration `protobuf:"bytes,3,opt,name=duration,proto3,oneof" json:"duration,omitempty"` EffectiveDuration *durationpb.Duration `protobuf:"bytes,4,opt,name=effective_duration,json=effectiveDuration,proto3" json:"effective_duration,omitempty"` BeginTime *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=begin_time,json=beginTime,proto3,oneof" json:"begin_time,omitempty"` EffectiveBeginTime *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=effective_begin_time,json=effectiveBeginTime,proto3,oneof" json:"effective_begin_time,omitempty"` @@ -705,34 +716,36 @@ var File_jumpstarter_client_v1_client_proto protoreflect.FileDescriptor const file_jumpstarter_client_v1_client_proto_rawDesc = "" + "\n" + - "\"jumpstarter/client/v1/client.proto\x12\x15jumpstarter.client.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x17google/api/client.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/api/resource.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a google/protobuf/field_mask.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1fjumpstarter/v1/kubernetes.proto\"\xa1\x02\n" + + "\"jumpstarter/client/v1/client.proto\x12\x15jumpstarter.client.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x17google/api/client.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/api/resource.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a google/protobuf/field_mask.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1fjumpstarter/v1/kubernetes.proto\x1a\x1bjumpstarter/v1/common.proto\"\xe0\x02\n" + "\bExporter\x12\x17\n" + "\x04name\x18\x01 \x01(\tB\x03\xe0A\bR\x04name\x12C\n" + - "\x06labels\x18\x02 \x03(\v2+.jumpstarter.client.v1.Exporter.LabelsEntryR\x06labels\x12\x1b\n" + - "\x06online\x18\x03 \x01(\bB\x03\xe0A\x03R\x06online\x1a9\n" + + "\x06labels\x18\x02 \x03(\v2+.jumpstarter.client.v1.Exporter.LabelsEntryR\x06labels\x12\x1d\n" + + "\x06online\x18\x03 \x01(\bB\x05\xe0A\x03\x18\x01R\x06online\x12;\n" + + "\x06status\x18\x04 \x01(\x0e2\x1e.jumpstarter.v1.ExporterStatusB\x03\xe0A\x03R\x06status\x1a9\n" + "\vLabelsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01:_\xeaA\\\n" + - "\x18jumpstarter.dev/Exporter\x12+namespaces/{namespace}/exporters/{exporter}*\texporters2\bexporter\"\xed\x06\n" + + "\x18jumpstarter.dev/Exporter\x12+namespaces/{namespace}/exporters/{exporter}*\texporters2\bexporter\"\xfa\x06\n" + "\x05Lease\x12\x17\n" + "\x04name\x18\x01 \x01(\tB\x03\xe0A\bR\x04name\x12\"\n" + "\bselector\x18\x02 \x01(\tB\x06\xe0A\x02\xe0A\x05R\bselector\x12:\n" + - "\bduration\x18\x03 \x01(\v2\x19.google.protobuf.DurationB\x03\xe0A\x02R\bduration\x12M\n" + + "\bduration\x18\x03 \x01(\v2\x19.google.protobuf.DurationH\x00R\bduration\x88\x01\x01\x12M\n" + "\x12effective_duration\x18\x04 \x01(\v2\x19.google.protobuf.DurationB\x03\xe0A\x03R\x11effectiveDuration\x12>\n" + "\n" + - "begin_time\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampH\x00R\tbeginTime\x88\x01\x01\x12V\n" + - "\x14effective_begin_time\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampB\x03\xe0A\x03H\x01R\x12effectiveBeginTime\x88\x01\x01\x12:\n" + - "\bend_time\x18\a \x01(\v2\x1a.google.protobuf.TimestampH\x02R\aendTime\x88\x01\x01\x12R\n" + - "\x12effective_end_time\x18\b \x01(\v2\x1a.google.protobuf.TimestampB\x03\xe0A\x03H\x03R\x10effectiveEndTime\x88\x01\x01\x12;\n" + + "begin_time\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampH\x01R\tbeginTime\x88\x01\x01\x12V\n" + + "\x14effective_begin_time\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampB\x03\xe0A\x03H\x02R\x12effectiveBeginTime\x88\x01\x01\x12:\n" + + "\bend_time\x18\a \x01(\v2\x1a.google.protobuf.TimestampH\x03R\aendTime\x88\x01\x01\x12R\n" + + "\x12effective_end_time\x18\b \x01(\v2\x1a.google.protobuf.TimestampB\x03\xe0A\x03H\x04R\x10effectiveEndTime\x88\x01\x01\x12;\n" + "\x06client\x18\t \x01(\tB\x1e\xe0A\x03\xfaA\x18\n" + - "\x16jumpstarter.dev/ClientH\x04R\x06client\x88\x01\x01\x12A\n" + + "\x16jumpstarter.dev/ClientH\x05R\x06client\x88\x01\x01\x12A\n" + "\bexporter\x18\n" + " \x01(\tB \xe0A\x03\xfaA\x1a\n" + - "\x18jumpstarter.dev/ExporterH\x05R\bexporter\x88\x01\x01\x12>\n" + + "\x18jumpstarter.dev/ExporterH\x06R\bexporter\x88\x01\x01\x12>\n" + "\n" + "conditions\x18\v \x03(\v2\x19.jumpstarter.v1.ConditionB\x03\xe0A\x03R\n" + "conditions:P\xeaAM\n" + - "\x15jumpstarter.dev/Lease\x12%namespaces/{namespace}/leases/{lease}*\x06leases2\x05leaseB\r\n" + + "\x15jumpstarter.dev/Lease\x12%namespaces/{namespace}/leases/{lease}*\x06leases2\x05leaseB\v\n" + + "\t_durationB\r\n" + "\v_begin_timeB\x17\n" + "\x15_effective_begin_timeB\v\n" + "\t_end_timeB\x15\n" + @@ -811,45 +824,47 @@ var file_jumpstarter_client_v1_client_proto_goTypes = []any{ (*UpdateLeaseRequest)(nil), // 9: jumpstarter.client.v1.UpdateLeaseRequest (*DeleteLeaseRequest)(nil), // 10: jumpstarter.client.v1.DeleteLeaseRequest nil, // 11: jumpstarter.client.v1.Exporter.LabelsEntry - (*durationpb.Duration)(nil), // 12: google.protobuf.Duration - (*timestamppb.Timestamp)(nil), // 13: google.protobuf.Timestamp - (*v1.Condition)(nil), // 14: jumpstarter.v1.Condition - (*fieldmaskpb.FieldMask)(nil), // 15: google.protobuf.FieldMask - (*emptypb.Empty)(nil), // 16: google.protobuf.Empty + (v1.ExporterStatus)(0), // 12: jumpstarter.v1.ExporterStatus + (*durationpb.Duration)(nil), // 13: google.protobuf.Duration + (*timestamppb.Timestamp)(nil), // 14: google.protobuf.Timestamp + (*v1.Condition)(nil), // 15: jumpstarter.v1.Condition + (*fieldmaskpb.FieldMask)(nil), // 16: google.protobuf.FieldMask + (*emptypb.Empty)(nil), // 17: google.protobuf.Empty } var file_jumpstarter_client_v1_client_proto_depIdxs = []int32{ 11, // 0: jumpstarter.client.v1.Exporter.labels:type_name -> jumpstarter.client.v1.Exporter.LabelsEntry - 12, // 1: jumpstarter.client.v1.Lease.duration:type_name -> google.protobuf.Duration - 12, // 2: jumpstarter.client.v1.Lease.effective_duration:type_name -> google.protobuf.Duration - 13, // 3: jumpstarter.client.v1.Lease.begin_time:type_name -> google.protobuf.Timestamp - 13, // 4: jumpstarter.client.v1.Lease.effective_begin_time:type_name -> google.protobuf.Timestamp - 13, // 5: jumpstarter.client.v1.Lease.end_time:type_name -> google.protobuf.Timestamp - 13, // 6: jumpstarter.client.v1.Lease.effective_end_time:type_name -> google.protobuf.Timestamp - 14, // 7: jumpstarter.client.v1.Lease.conditions:type_name -> jumpstarter.v1.Condition - 0, // 8: jumpstarter.client.v1.ListExportersResponse.exporters:type_name -> jumpstarter.client.v1.Exporter - 1, // 9: jumpstarter.client.v1.ListLeasesResponse.leases:type_name -> jumpstarter.client.v1.Lease - 1, // 10: jumpstarter.client.v1.CreateLeaseRequest.lease:type_name -> jumpstarter.client.v1.Lease - 1, // 11: jumpstarter.client.v1.UpdateLeaseRequest.lease:type_name -> jumpstarter.client.v1.Lease - 15, // 12: jumpstarter.client.v1.UpdateLeaseRequest.update_mask:type_name -> google.protobuf.FieldMask - 2, // 13: jumpstarter.client.v1.ClientService.GetExporter:input_type -> jumpstarter.client.v1.GetExporterRequest - 3, // 14: jumpstarter.client.v1.ClientService.ListExporters:input_type -> jumpstarter.client.v1.ListExportersRequest - 5, // 15: jumpstarter.client.v1.ClientService.GetLease:input_type -> jumpstarter.client.v1.GetLeaseRequest - 6, // 16: jumpstarter.client.v1.ClientService.ListLeases:input_type -> jumpstarter.client.v1.ListLeasesRequest - 8, // 17: jumpstarter.client.v1.ClientService.CreateLease:input_type -> jumpstarter.client.v1.CreateLeaseRequest - 9, // 18: jumpstarter.client.v1.ClientService.UpdateLease:input_type -> jumpstarter.client.v1.UpdateLeaseRequest - 10, // 19: jumpstarter.client.v1.ClientService.DeleteLease:input_type -> jumpstarter.client.v1.DeleteLeaseRequest - 0, // 20: jumpstarter.client.v1.ClientService.GetExporter:output_type -> jumpstarter.client.v1.Exporter - 4, // 21: jumpstarter.client.v1.ClientService.ListExporters:output_type -> jumpstarter.client.v1.ListExportersResponse - 1, // 22: jumpstarter.client.v1.ClientService.GetLease:output_type -> jumpstarter.client.v1.Lease - 7, // 23: jumpstarter.client.v1.ClientService.ListLeases:output_type -> jumpstarter.client.v1.ListLeasesResponse - 1, // 24: jumpstarter.client.v1.ClientService.CreateLease:output_type -> jumpstarter.client.v1.Lease - 1, // 25: jumpstarter.client.v1.ClientService.UpdateLease:output_type -> jumpstarter.client.v1.Lease - 16, // 26: jumpstarter.client.v1.ClientService.DeleteLease:output_type -> google.protobuf.Empty - 20, // [20:27] is the sub-list for method output_type - 13, // [13:20] is the sub-list for method input_type - 13, // [13:13] is the sub-list for extension type_name - 13, // [13:13] is the sub-list for extension extendee - 0, // [0:13] is the sub-list for field type_name + 12, // 1: jumpstarter.client.v1.Exporter.status:type_name -> jumpstarter.v1.ExporterStatus + 13, // 2: jumpstarter.client.v1.Lease.duration:type_name -> google.protobuf.Duration + 13, // 3: jumpstarter.client.v1.Lease.effective_duration:type_name -> google.protobuf.Duration + 14, // 4: jumpstarter.client.v1.Lease.begin_time:type_name -> google.protobuf.Timestamp + 14, // 5: jumpstarter.client.v1.Lease.effective_begin_time:type_name -> google.protobuf.Timestamp + 14, // 6: jumpstarter.client.v1.Lease.end_time:type_name -> google.protobuf.Timestamp + 14, // 7: jumpstarter.client.v1.Lease.effective_end_time:type_name -> google.protobuf.Timestamp + 15, // 8: jumpstarter.client.v1.Lease.conditions:type_name -> jumpstarter.v1.Condition + 0, // 9: jumpstarter.client.v1.ListExportersResponse.exporters:type_name -> jumpstarter.client.v1.Exporter + 1, // 10: jumpstarter.client.v1.ListLeasesResponse.leases:type_name -> jumpstarter.client.v1.Lease + 1, // 11: jumpstarter.client.v1.CreateLeaseRequest.lease:type_name -> jumpstarter.client.v1.Lease + 1, // 12: jumpstarter.client.v1.UpdateLeaseRequest.lease:type_name -> jumpstarter.client.v1.Lease + 16, // 13: jumpstarter.client.v1.UpdateLeaseRequest.update_mask:type_name -> google.protobuf.FieldMask + 2, // 14: jumpstarter.client.v1.ClientService.GetExporter:input_type -> jumpstarter.client.v1.GetExporterRequest + 3, // 15: jumpstarter.client.v1.ClientService.ListExporters:input_type -> jumpstarter.client.v1.ListExportersRequest + 5, // 16: jumpstarter.client.v1.ClientService.GetLease:input_type -> jumpstarter.client.v1.GetLeaseRequest + 6, // 17: jumpstarter.client.v1.ClientService.ListLeases:input_type -> jumpstarter.client.v1.ListLeasesRequest + 8, // 18: jumpstarter.client.v1.ClientService.CreateLease:input_type -> jumpstarter.client.v1.CreateLeaseRequest + 9, // 19: jumpstarter.client.v1.ClientService.UpdateLease:input_type -> jumpstarter.client.v1.UpdateLeaseRequest + 10, // 20: jumpstarter.client.v1.ClientService.DeleteLease:input_type -> jumpstarter.client.v1.DeleteLeaseRequest + 0, // 21: jumpstarter.client.v1.ClientService.GetExporter:output_type -> jumpstarter.client.v1.Exporter + 4, // 22: jumpstarter.client.v1.ClientService.ListExporters:output_type -> jumpstarter.client.v1.ListExportersResponse + 1, // 23: jumpstarter.client.v1.ClientService.GetLease:output_type -> jumpstarter.client.v1.Lease + 7, // 24: jumpstarter.client.v1.ClientService.ListLeases:output_type -> jumpstarter.client.v1.ListLeasesResponse + 1, // 25: jumpstarter.client.v1.ClientService.CreateLease:output_type -> jumpstarter.client.v1.Lease + 1, // 26: jumpstarter.client.v1.ClientService.UpdateLease:output_type -> jumpstarter.client.v1.Lease + 17, // 27: jumpstarter.client.v1.ClientService.DeleteLease:output_type -> google.protobuf.Empty + 21, // [21:28] is the sub-list for method output_type + 14, // [14:21] is the sub-list for method input_type + 14, // [14:14] is the sub-list for extension type_name + 14, // [14:14] is the sub-list for extension extendee + 0, // [0:14] is the sub-list for field type_name } func init() { file_jumpstarter_client_v1_client_proto_init() } diff --git a/internal/protocol/jumpstarter/v1/common.pb.go b/internal/protocol/jumpstarter/v1/common.pb.go new file mode 100644 index 00000000..c9caaed5 --- /dev/null +++ b/internal/protocol/jumpstarter/v1/common.pb.go @@ -0,0 +1,216 @@ +// Copyright 2024 The Jumpstarter Authors + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.10 +// protoc (unknown) +// source: jumpstarter/v1/common.proto + +package jumpstarterv1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Exporter status information +type ExporterStatus int32 + +const ( + ExporterStatus_EXPORTER_STATUS_UNSPECIFIED ExporterStatus = 0 // Unspecified exporter status + ExporterStatus_EXPORTER_STATUS_OFFLINE ExporterStatus = 1 // Exporter is offline + ExporterStatus_EXPORTER_STATUS_AVAILABLE ExporterStatus = 2 // Exporter is available to be leased + ExporterStatus_EXPORTER_STATUS_BEFORE_LEASE_HOOK ExporterStatus = 3 // Exporter is executing before lease hook(s) + ExporterStatus_EXPORTER_STATUS_LEASE_READY ExporterStatus = 4 // Exporter is leased and ready to accept commands + ExporterStatus_EXPORTER_STATUS_AFTER_LEASE_HOOK ExporterStatus = 5 // Exporter is executing after lease hook(s) + ExporterStatus_EXPORTER_STATUS_BEFORE_LEASE_HOOK_FAILED ExporterStatus = 6 // Exporter before lease hook failed + ExporterStatus_EXPORTER_STATUS_AFTER_LEASE_HOOK_FAILED ExporterStatus = 7 // Exporter after lease hook failed +) + +// Enum value maps for ExporterStatus. +var ( + ExporterStatus_name = map[int32]string{ + 0: "EXPORTER_STATUS_UNSPECIFIED", + 1: "EXPORTER_STATUS_OFFLINE", + 2: "EXPORTER_STATUS_AVAILABLE", + 3: "EXPORTER_STATUS_BEFORE_LEASE_HOOK", + 4: "EXPORTER_STATUS_LEASE_READY", + 5: "EXPORTER_STATUS_AFTER_LEASE_HOOK", + 6: "EXPORTER_STATUS_BEFORE_LEASE_HOOK_FAILED", + 7: "EXPORTER_STATUS_AFTER_LEASE_HOOK_FAILED", + } + ExporterStatus_value = map[string]int32{ + "EXPORTER_STATUS_UNSPECIFIED": 0, + "EXPORTER_STATUS_OFFLINE": 1, + "EXPORTER_STATUS_AVAILABLE": 2, + "EXPORTER_STATUS_BEFORE_LEASE_HOOK": 3, + "EXPORTER_STATUS_LEASE_READY": 4, + "EXPORTER_STATUS_AFTER_LEASE_HOOK": 5, + "EXPORTER_STATUS_BEFORE_LEASE_HOOK_FAILED": 6, + "EXPORTER_STATUS_AFTER_LEASE_HOOK_FAILED": 7, + } +) + +func (x ExporterStatus) Enum() *ExporterStatus { + p := new(ExporterStatus) + *p = x + return p +} + +func (x ExporterStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ExporterStatus) Descriptor() protoreflect.EnumDescriptor { + return file_jumpstarter_v1_common_proto_enumTypes[0].Descriptor() +} + +func (ExporterStatus) Type() protoreflect.EnumType { + return &file_jumpstarter_v1_common_proto_enumTypes[0] +} + +func (x ExporterStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ExporterStatus.Descriptor instead. +func (ExporterStatus) EnumDescriptor() ([]byte, []int) { + return file_jumpstarter_v1_common_proto_rawDescGZIP(), []int{0} +} + +// Source of log stream messages +type LogSource int32 + +const ( + LogSource_LOG_SOURCE_UNSPECIFIED LogSource = 0 // Unspecified log source + LogSource_LOG_SOURCE_DRIVER LogSource = 1 // Driver/device logs + LogSource_LOG_SOURCE_BEFORE_LEASE_HOOK LogSource = 2 // beforeLease hook execution logs + LogSource_LOG_SOURCE_AFTER_LEASE_HOOK LogSource = 3 // afterLease hook execution logs + LogSource_LOG_SOURCE_SYSTEM LogSource = 4 // System/exporter logs +) + +// Enum value maps for LogSource. +var ( + LogSource_name = map[int32]string{ + 0: "LOG_SOURCE_UNSPECIFIED", + 1: "LOG_SOURCE_DRIVER", + 2: "LOG_SOURCE_BEFORE_LEASE_HOOK", + 3: "LOG_SOURCE_AFTER_LEASE_HOOK", + 4: "LOG_SOURCE_SYSTEM", + } + LogSource_value = map[string]int32{ + "LOG_SOURCE_UNSPECIFIED": 0, + "LOG_SOURCE_DRIVER": 1, + "LOG_SOURCE_BEFORE_LEASE_HOOK": 2, + "LOG_SOURCE_AFTER_LEASE_HOOK": 3, + "LOG_SOURCE_SYSTEM": 4, + } +) + +func (x LogSource) Enum() *LogSource { + p := new(LogSource) + *p = x + return p +} + +func (x LogSource) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (LogSource) Descriptor() protoreflect.EnumDescriptor { + return file_jumpstarter_v1_common_proto_enumTypes[1].Descriptor() +} + +func (LogSource) Type() protoreflect.EnumType { + return &file_jumpstarter_v1_common_proto_enumTypes[1] +} + +func (x LogSource) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use LogSource.Descriptor instead. +func (LogSource) EnumDescriptor() ([]byte, []int) { + return file_jumpstarter_v1_common_proto_rawDescGZIP(), []int{1} +} + +var File_jumpstarter_v1_common_proto protoreflect.FileDescriptor + +const file_jumpstarter_v1_common_proto_rawDesc = "" + + "\n" + + "\x1bjumpstarter/v1/common.proto\x12\x0ejumpstarter.v1*\xb6\x02\n" + + "\x0eExporterStatus\x12\x1f\n" + + "\x1bEXPORTER_STATUS_UNSPECIFIED\x10\x00\x12\x1b\n" + + "\x17EXPORTER_STATUS_OFFLINE\x10\x01\x12\x1d\n" + + "\x19EXPORTER_STATUS_AVAILABLE\x10\x02\x12%\n" + + "!EXPORTER_STATUS_BEFORE_LEASE_HOOK\x10\x03\x12\x1f\n" + + "\x1bEXPORTER_STATUS_LEASE_READY\x10\x04\x12$\n" + + " EXPORTER_STATUS_AFTER_LEASE_HOOK\x10\x05\x12,\n" + + "(EXPORTER_STATUS_BEFORE_LEASE_HOOK_FAILED\x10\x06\x12+\n" + + "'EXPORTER_STATUS_AFTER_LEASE_HOOK_FAILED\x10\a*\x98\x01\n" + + "\tLogSource\x12\x1a\n" + + "\x16LOG_SOURCE_UNSPECIFIED\x10\x00\x12\x15\n" + + "\x11LOG_SOURCE_DRIVER\x10\x01\x12 \n" + + "\x1cLOG_SOURCE_BEFORE_LEASE_HOOK\x10\x02\x12\x1f\n" + + "\x1bLOG_SOURCE_AFTER_LEASE_HOOK\x10\x03\x12\x15\n" + + "\x11LOG_SOURCE_SYSTEM\x10\x04B\xca\x01\n" + + "\x12com.jumpstarter.v1B\vCommonProtoP\x01ZNgithub.com/jumpstarter-dev/jumpstarter-controller/jumpstarter/v1;jumpstarterv1\xa2\x02\x03JXX\xaa\x02\x0eJumpstarter.V1\xca\x02\x0eJumpstarter\\V1\xe2\x02\x1aJumpstarter\\V1\\GPBMetadata\xea\x02\x0fJumpstarter::V1b\x06proto3" + +var ( + file_jumpstarter_v1_common_proto_rawDescOnce sync.Once + file_jumpstarter_v1_common_proto_rawDescData []byte +) + +func file_jumpstarter_v1_common_proto_rawDescGZIP() []byte { + file_jumpstarter_v1_common_proto_rawDescOnce.Do(func() { + file_jumpstarter_v1_common_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_jumpstarter_v1_common_proto_rawDesc), len(file_jumpstarter_v1_common_proto_rawDesc))) + }) + return file_jumpstarter_v1_common_proto_rawDescData +} + +var file_jumpstarter_v1_common_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_jumpstarter_v1_common_proto_goTypes = []any{ + (ExporterStatus)(0), // 0: jumpstarter.v1.ExporterStatus + (LogSource)(0), // 1: jumpstarter.v1.LogSource +} +var file_jumpstarter_v1_common_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_jumpstarter_v1_common_proto_init() } +func file_jumpstarter_v1_common_proto_init() { + if File_jumpstarter_v1_common_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_jumpstarter_v1_common_proto_rawDesc), len(file_jumpstarter_v1_common_proto_rawDesc)), + NumEnums: 2, + NumMessages: 0, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_jumpstarter_v1_common_proto_goTypes, + DependencyIndexes: file_jumpstarter_v1_common_proto_depIdxs, + EnumInfos: file_jumpstarter_v1_common_proto_enumTypes, + }.Build() + File_jumpstarter_v1_common_proto = out.File + file_jumpstarter_v1_common_proto_goTypes = nil + file_jumpstarter_v1_common_proto_depIdxs = nil +} diff --git a/internal/protocol/jumpstarter/v1/jumpstarter.pb.go b/internal/protocol/jumpstarter/v1/jumpstarter.pb.go index d15aa911..fe02eb95 100644 --- a/internal/protocol/jumpstarter/v1/jumpstarter.pb.go +++ b/internal/protocol/jumpstarter/v1/jumpstarter.pb.go @@ -2,7 +2,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.6 +// protoc-gen-go v1.36.10 // protoc (unknown) // source: jumpstarter/v1/jumpstarter.proto @@ -85,12 +85,14 @@ func (x *RegisterRequest) GetReports() []*DriverInstanceReport { } type DriverInstanceReport struct { - state protoimpl.MessageState `protogen:"open.v1"` - Uuid string `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"` // a unique id within the expoter - ParentUuid *string `protobuf:"bytes,2,opt,name=parent_uuid,json=parentUuid,proto3,oneof" json:"parent_uuid,omitempty"` // optional, if device has a parent device - Labels map[string]string `protobuf:"bytes,3,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Uuid string `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"` // a unique id within the exporter + ParentUuid *string `protobuf:"bytes,2,opt,name=parent_uuid,json=parentUuid,proto3,oneof" json:"parent_uuid,omitempty"` // optional, if device has a parent device + Labels map[string]string `protobuf:"bytes,3,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Description *string `protobuf:"bytes,4,opt,name=description,proto3,oneof" json:"description,omitempty"` // optional custom driver description for CLI + MethodsDescription map[string]string `protobuf:"bytes,5,rep,name=methods_description,json=methodsDescription,proto3" json:"methods_description,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // method name -> help text for CLI + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *DriverInstanceReport) Reset() { @@ -144,6 +146,20 @@ func (x *DriverInstanceReport) GetLabels() map[string]string { return nil } +func (x *DriverInstanceReport) GetDescription() string { + if x != nil && x.Description != nil { + return *x.Description + } + return "" +} + +func (x *DriverInstanceReport) GetMethodsDescription() map[string]string { + if x != nil { + return x.MethodsDescription + } + return nil +} + type RegisterResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Uuid string `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"` @@ -626,6 +642,94 @@ func (x *AuditStreamRequest) GetMessage() string { return "" } +type ReportStatusRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status ExporterStatus `protobuf:"varint,1,opt,name=status,proto3,enum=jumpstarter.v1.ExporterStatus" json:"status,omitempty"` + Message *string `protobuf:"bytes,2,opt,name=message,proto3,oneof" json:"message,omitempty"` // Optional human-readable status message + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReportStatusRequest) Reset() { + *x = ReportStatusRequest{} + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReportStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReportStatusRequest) ProtoMessage() {} + +func (x *ReportStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReportStatusRequest.ProtoReflect.Descriptor instead. +func (*ReportStatusRequest) Descriptor() ([]byte, []int) { + return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{12} +} + +func (x *ReportStatusRequest) GetStatus() ExporterStatus { + if x != nil { + return x.Status + } + return ExporterStatus_EXPORTER_STATUS_UNSPECIFIED +} + +func (x *ReportStatusRequest) GetMessage() string { + if x != nil && x.Message != nil { + return *x.Message + } + return "" +} + +type ReportStatusResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReportStatusResponse) Reset() { + *x = ReportStatusResponse{} + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReportStatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReportStatusResponse) ProtoMessage() {} + +func (x *ReportStatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReportStatusResponse.ProtoReflect.Descriptor instead. +func (*ReportStatusResponse) Descriptor() ([]byte, []int) { + return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{13} +} + type GetReportResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Uuid string `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"` @@ -641,7 +745,7 @@ type GetReportResponse struct { func (x *GetReportResponse) Reset() { *x = GetReportResponse{} - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[12] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -653,7 +757,7 @@ func (x *GetReportResponse) String() string { func (*GetReportResponse) ProtoMessage() {} func (x *GetReportResponse) ProtoReflect() protoreflect.Message { - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[12] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -666,7 +770,7 @@ func (x *GetReportResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetReportResponse.ProtoReflect.Descriptor instead. func (*GetReportResponse) Descriptor() ([]byte, []int) { - return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{12} + return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{14} } func (x *GetReportResponse) GetUuid() string { @@ -709,7 +813,7 @@ type Endpoint struct { func (x *Endpoint) Reset() { *x = Endpoint{} - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[13] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -721,7 +825,7 @@ func (x *Endpoint) String() string { func (*Endpoint) ProtoMessage() {} func (x *Endpoint) ProtoReflect() protoreflect.Message { - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[13] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -734,7 +838,7 @@ func (x *Endpoint) ProtoReflect() protoreflect.Message { // Deprecated: Use Endpoint.ProtoReflect.Descriptor instead. func (*Endpoint) Descriptor() ([]byte, []int) { - return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{13} + return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{15} } func (x *Endpoint) GetEndpoint() string { @@ -776,7 +880,7 @@ type DriverCallRequest struct { func (x *DriverCallRequest) Reset() { *x = DriverCallRequest{} - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[14] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -788,7 +892,7 @@ func (x *DriverCallRequest) String() string { func (*DriverCallRequest) ProtoMessage() {} func (x *DriverCallRequest) ProtoReflect() protoreflect.Message { - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[14] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -801,7 +905,7 @@ func (x *DriverCallRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DriverCallRequest.ProtoReflect.Descriptor instead. func (*DriverCallRequest) Descriptor() ([]byte, []int) { - return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{14} + return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{16} } func (x *DriverCallRequest) GetUuid() string { @@ -835,7 +939,7 @@ type DriverCallResponse struct { func (x *DriverCallResponse) Reset() { *x = DriverCallResponse{} - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[15] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -847,7 +951,7 @@ func (x *DriverCallResponse) String() string { func (*DriverCallResponse) ProtoMessage() {} func (x *DriverCallResponse) ProtoReflect() protoreflect.Message { - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[15] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -860,7 +964,7 @@ func (x *DriverCallResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DriverCallResponse.ProtoReflect.Descriptor instead. func (*DriverCallResponse) Descriptor() ([]byte, []int) { - return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{15} + return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{17} } func (x *DriverCallResponse) GetUuid() string { @@ -888,7 +992,7 @@ type StreamingDriverCallRequest struct { func (x *StreamingDriverCallRequest) Reset() { *x = StreamingDriverCallRequest{} - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[16] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -900,7 +1004,7 @@ func (x *StreamingDriverCallRequest) String() string { func (*StreamingDriverCallRequest) ProtoMessage() {} func (x *StreamingDriverCallRequest) ProtoReflect() protoreflect.Message { - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[16] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -913,7 +1017,7 @@ func (x *StreamingDriverCallRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StreamingDriverCallRequest.ProtoReflect.Descriptor instead. func (*StreamingDriverCallRequest) Descriptor() ([]byte, []int) { - return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{16} + return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{18} } func (x *StreamingDriverCallRequest) GetUuid() string { @@ -947,7 +1051,7 @@ type StreamingDriverCallResponse struct { func (x *StreamingDriverCallResponse) Reset() { *x = StreamingDriverCallResponse{} - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[17] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -959,7 +1063,7 @@ func (x *StreamingDriverCallResponse) String() string { func (*StreamingDriverCallResponse) ProtoMessage() {} func (x *StreamingDriverCallResponse) ProtoReflect() protoreflect.Message { - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[17] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -972,7 +1076,7 @@ func (x *StreamingDriverCallResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StreamingDriverCallResponse.ProtoReflect.Descriptor instead. func (*StreamingDriverCallResponse) Descriptor() ([]byte, []int) { - return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{17} + return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{19} } func (x *StreamingDriverCallResponse) GetUuid() string { @@ -994,13 +1098,14 @@ type LogStreamResponse struct { Uuid string `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"` Severity string `protobuf:"bytes,2,opt,name=severity,proto3" json:"severity,omitempty"` Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` + Source *LogSource `protobuf:"varint,4,opt,name=source,proto3,enum=jumpstarter.v1.LogSource,oneof" json:"source,omitempty"` // New optional field unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *LogStreamResponse) Reset() { *x = LogStreamResponse{} - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[18] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1012,7 +1117,7 @@ func (x *LogStreamResponse) String() string { func (*LogStreamResponse) ProtoMessage() {} func (x *LogStreamResponse) ProtoReflect() protoreflect.Message { - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[18] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1025,7 +1130,7 @@ func (x *LogStreamResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LogStreamResponse.ProtoReflect.Descriptor instead. func (*LogStreamResponse) Descriptor() ([]byte, []int) { - return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{18} + return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{20} } func (x *LogStreamResponse) GetUuid() string { @@ -1049,6 +1154,13 @@ func (x *LogStreamResponse) GetMessage() string { return "" } +func (x *LogStreamResponse) GetSource() LogSource { + if x != nil && x.Source != nil { + return *x.Source + } + return LogSource_LOG_SOURCE_UNSPECIFIED +} + type ResetRequest struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -1057,7 +1169,7 @@ type ResetRequest struct { func (x *ResetRequest) Reset() { *x = ResetRequest{} - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[19] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1069,7 +1181,7 @@ func (x *ResetRequest) String() string { func (*ResetRequest) ProtoMessage() {} func (x *ResetRequest) ProtoReflect() protoreflect.Message { - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[19] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1082,7 +1194,7 @@ func (x *ResetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ResetRequest.ProtoReflect.Descriptor instead. func (*ResetRequest) Descriptor() ([]byte, []int) { - return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{19} + return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{21} } type ResetResponse struct { @@ -1093,7 +1205,7 @@ type ResetResponse struct { func (x *ResetResponse) Reset() { *x = ResetResponse{} - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[20] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1105,7 +1217,7 @@ func (x *ResetResponse) String() string { func (*ResetResponse) ProtoMessage() {} func (x *ResetResponse) ProtoReflect() protoreflect.Message { - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[20] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1118,7 +1230,7 @@ func (x *ResetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ResetResponse.ProtoReflect.Descriptor instead. func (*ResetResponse) Descriptor() ([]byte, []int) { - return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{20} + return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{22} } type GetLeaseRequest struct { @@ -1130,7 +1242,7 @@ type GetLeaseRequest struct { func (x *GetLeaseRequest) Reset() { *x = GetLeaseRequest{} - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[21] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1142,7 +1254,7 @@ func (x *GetLeaseRequest) String() string { func (*GetLeaseRequest) ProtoMessage() {} func (x *GetLeaseRequest) ProtoReflect() protoreflect.Message { - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[21] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1155,7 +1267,7 @@ func (x *GetLeaseRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetLeaseRequest.ProtoReflect.Descriptor instead. func (*GetLeaseRequest) Descriptor() ([]byte, []int) { - return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{21} + return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{23} } func (x *GetLeaseRequest) GetName() string { @@ -1179,7 +1291,7 @@ type GetLeaseResponse struct { func (x *GetLeaseResponse) Reset() { *x = GetLeaseResponse{} - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[22] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1191,7 +1303,7 @@ func (x *GetLeaseResponse) String() string { func (*GetLeaseResponse) ProtoMessage() {} func (x *GetLeaseResponse) ProtoReflect() protoreflect.Message { - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[22] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1204,7 +1316,7 @@ func (x *GetLeaseResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetLeaseResponse.ProtoReflect.Descriptor instead. func (*GetLeaseResponse) Descriptor() ([]byte, []int) { - return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{22} + return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{24} } func (x *GetLeaseResponse) GetDuration() *durationpb.Duration { @@ -1259,7 +1371,7 @@ type RequestLeaseRequest struct { func (x *RequestLeaseRequest) Reset() { *x = RequestLeaseRequest{} - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[23] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1271,7 +1383,7 @@ func (x *RequestLeaseRequest) String() string { func (*RequestLeaseRequest) ProtoMessage() {} func (x *RequestLeaseRequest) ProtoReflect() protoreflect.Message { - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[23] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1284,7 +1396,7 @@ func (x *RequestLeaseRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RequestLeaseRequest.ProtoReflect.Descriptor instead. func (*RequestLeaseRequest) Descriptor() ([]byte, []int) { - return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{23} + return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{25} } func (x *RequestLeaseRequest) GetDuration() *durationpb.Duration { @@ -1310,7 +1422,7 @@ type RequestLeaseResponse struct { func (x *RequestLeaseResponse) Reset() { *x = RequestLeaseResponse{} - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[24] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1322,7 +1434,7 @@ func (x *RequestLeaseResponse) String() string { func (*RequestLeaseResponse) ProtoMessage() {} func (x *RequestLeaseResponse) ProtoReflect() protoreflect.Message { - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[24] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1335,7 +1447,7 @@ func (x *RequestLeaseResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RequestLeaseResponse.ProtoReflect.Descriptor instead. func (*RequestLeaseResponse) Descriptor() ([]byte, []int) { - return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{24} + return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{26} } func (x *RequestLeaseResponse) GetName() string { @@ -1354,7 +1466,7 @@ type ReleaseLeaseRequest struct { func (x *ReleaseLeaseRequest) Reset() { *x = ReleaseLeaseRequest{} - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[25] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1366,7 +1478,7 @@ func (x *ReleaseLeaseRequest) String() string { func (*ReleaseLeaseRequest) ProtoMessage() {} func (x *ReleaseLeaseRequest) ProtoReflect() protoreflect.Message { - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[25] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1379,7 +1491,7 @@ func (x *ReleaseLeaseRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReleaseLeaseRequest.ProtoReflect.Descriptor instead. func (*ReleaseLeaseRequest) Descriptor() ([]byte, []int) { - return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{25} + return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{27} } func (x *ReleaseLeaseRequest) GetName() string { @@ -1397,7 +1509,7 @@ type ReleaseLeaseResponse struct { func (x *ReleaseLeaseResponse) Reset() { *x = ReleaseLeaseResponse{} - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[26] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1409,7 +1521,7 @@ func (x *ReleaseLeaseResponse) String() string { func (*ReleaseLeaseResponse) ProtoMessage() {} func (x *ReleaseLeaseResponse) ProtoReflect() protoreflect.Message { - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[26] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1422,7 +1534,7 @@ func (x *ReleaseLeaseResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReleaseLeaseResponse.ProtoReflect.Descriptor instead. func (*ReleaseLeaseResponse) Descriptor() ([]byte, []int) { - return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{26} + return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{28} } type ListLeasesRequest struct { @@ -1433,7 +1545,7 @@ type ListLeasesRequest struct { func (x *ListLeasesRequest) Reset() { *x = ListLeasesRequest{} - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[27] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1445,7 +1557,7 @@ func (x *ListLeasesRequest) String() string { func (*ListLeasesRequest) ProtoMessage() {} func (x *ListLeasesRequest) ProtoReflect() protoreflect.Message { - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[27] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1458,7 +1570,7 @@ func (x *ListLeasesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLeasesRequest.ProtoReflect.Descriptor instead. func (*ListLeasesRequest) Descriptor() ([]byte, []int) { - return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{27} + return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{29} } type ListLeasesResponse struct { @@ -1470,7 +1582,7 @@ type ListLeasesResponse struct { func (x *ListLeasesResponse) Reset() { *x = ListLeasesResponse{} - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[28] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1482,7 +1594,7 @@ func (x *ListLeasesResponse) String() string { func (*ListLeasesResponse) ProtoMessage() {} func (x *ListLeasesResponse) ProtoReflect() protoreflect.Message { - mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[28] + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1495,7 +1607,7 @@ func (x *ListLeasesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLeasesResponse.ProtoReflect.Descriptor instead. func (*ListLeasesResponse) Descriptor() ([]byte, []int) { - return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{28} + return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{30} } func (x *ListLeasesResponse) GetNames() []string { @@ -1505,26 +1617,120 @@ func (x *ListLeasesResponse) GetNames() []string { return nil } +type GetStatusRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetStatusRequest) Reset() { + *x = GetStatusRequest{} + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetStatusRequest) ProtoMessage() {} + +func (x *GetStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[31] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetStatusRequest.ProtoReflect.Descriptor instead. +func (*GetStatusRequest) Descriptor() ([]byte, []int) { + return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{31} +} + +type GetStatusResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status ExporterStatus `protobuf:"varint,1,opt,name=status,proto3,enum=jumpstarter.v1.ExporterStatus" json:"status,omitempty"` + Message *string `protobuf:"bytes,2,opt,name=message,proto3,oneof" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetStatusResponse) Reset() { + *x = GetStatusResponse{} + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetStatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetStatusResponse) ProtoMessage() {} + +func (x *GetStatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_jumpstarter_v1_jumpstarter_proto_msgTypes[32] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetStatusResponse.ProtoReflect.Descriptor instead. +func (*GetStatusResponse) Descriptor() ([]byte, []int) { + return file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP(), []int{32} +} + +func (x *GetStatusResponse) GetStatus() ExporterStatus { + if x != nil { + return x.Status + } + return ExporterStatus_EXPORTER_STATUS_UNSPECIFIED +} + +func (x *GetStatusResponse) GetMessage() string { + if x != nil && x.Message != nil { + return *x.Message + } + return "" +} + var File_jumpstarter_v1_jumpstarter_proto protoreflect.FileDescriptor const file_jumpstarter_v1_jumpstarter_proto_rawDesc = "" + "\n" + - " jumpstarter/v1/jumpstarter.proto\x12\x0ejumpstarter.v1\x1a\x1egoogle/protobuf/duration.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1fjumpstarter/v1/kubernetes.proto\"\xd1\x01\n" + + " jumpstarter/v1/jumpstarter.proto\x12\x0ejumpstarter.v1\x1a\x1egoogle/protobuf/duration.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1fjumpstarter/v1/kubernetes.proto\x1a\x1bjumpstarter/v1/common.proto\"\xd1\x01\n" + "\x0fRegisterRequest\x12C\n" + "\x06labels\x18\x01 \x03(\v2+.jumpstarter.v1.RegisterRequest.LabelsEntryR\x06labels\x12>\n" + "\areports\x18\x02 \x03(\v2$.jumpstarter.v1.DriverInstanceReportR\areports\x1a9\n" + "\vLabelsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xe5\x01\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xd2\x03\n" + "\x14DriverInstanceReport\x12\x12\n" + "\x04uuid\x18\x01 \x01(\tR\x04uuid\x12$\n" + "\vparent_uuid\x18\x02 \x01(\tH\x00R\n" + "parentUuid\x88\x01\x01\x12H\n" + - "\x06labels\x18\x03 \x03(\v20.jumpstarter.v1.DriverInstanceReport.LabelsEntryR\x06labels\x1a9\n" + + "\x06labels\x18\x03 \x03(\v20.jumpstarter.v1.DriverInstanceReport.LabelsEntryR\x06labels\x12%\n" + + "\vdescription\x18\x04 \x01(\tH\x01R\vdescription\x88\x01\x01\x12m\n" + + "\x13methods_description\x18\x05 \x03(\v2<.jumpstarter.v1.DriverInstanceReport.MethodsDescriptionEntryR\x12methodsDescription\x1a9\n" + "\vLabelsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1aE\n" + + "\x17MethodsDescriptionEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x0e\n" + - "\f_parent_uuid\"&\n" + + "\f_parent_uuidB\x0e\n" + + "\f_description\"&\n" + "\x10RegisterResponse\x12\x12\n" + "\x04uuid\x18\x01 \x01(\tR\x04uuid\"+\n" + "\x11UnregisterRequest\x12\x16\n" + @@ -1555,7 +1761,13 @@ const file_jumpstarter_v1_jumpstarter_proto_rawDesc = "" + "\rexporter_uuid\x18\x01 \x01(\tR\fexporterUuid\x120\n" + "\x14driver_instance_uuid\x18\x02 \x01(\tR\x12driverInstanceUuid\x12\x1a\n" + "\bseverity\x18\x03 \x01(\tR\bseverity\x12\x18\n" + - "\amessage\x18\x04 \x01(\tR\amessage\"\xb8\x02\n" + + "\amessage\x18\x04 \x01(\tR\amessage\"x\n" + + "\x13ReportStatusRequest\x126\n" + + "\x06status\x18\x01 \x01(\x0e2\x1e.jumpstarter.v1.ExporterStatusR\x06status\x12\x1d\n" + + "\amessage\x18\x02 \x01(\tH\x00R\amessage\x88\x01\x01B\n" + + "\n" + + "\b_message\"\x16\n" + + "\x14ReportStatusResponse\"\xb8\x02\n" + "\x11GetReportResponse\x12\x12\n" + "\x04uuid\x18\x01 \x01(\tR\x04uuid\x12E\n" + "\x06labels\x18\x02 \x03(\v2-.jumpstarter.v1.GetReportResponse.LabelsEntryR\x06labels\x12>\n" + @@ -1582,11 +1794,13 @@ const file_jumpstarter_v1_jumpstarter_proto_rawDesc = "" + "\x04args\x18\x03 \x03(\v2\x16.google.protobuf.ValueR\x04args\"a\n" + "\x1bStreamingDriverCallResponse\x12\x12\n" + "\x04uuid\x18\x01 \x01(\tR\x04uuid\x12.\n" + - "\x06result\x18\x02 \x01(\v2\x16.google.protobuf.ValueR\x06result\"]\n" + + "\x06result\x18\x02 \x01(\v2\x16.google.protobuf.ValueR\x06result\"\xa0\x01\n" + "\x11LogStreamResponse\x12\x12\n" + "\x04uuid\x18\x01 \x01(\tR\x04uuid\x12\x1a\n" + "\bseverity\x18\x02 \x01(\tR\bseverity\x12\x18\n" + - "\amessage\x18\x03 \x01(\tR\amessage\"\x0e\n" + + "\amessage\x18\x03 \x01(\tR\amessage\x126\n" + + "\x06source\x18\x04 \x01(\x0e2\x19.jumpstarter.v1.LogSourceH\x00R\x06source\x88\x01\x01B\t\n" + + "\a_source\"\x0e\n" + "\fResetRequest\"\x0f\n" + "\rResetResponse\"%\n" + "\x0fGetLeaseRequest\x12\x12\n" + @@ -1614,11 +1828,18 @@ const file_jumpstarter_v1_jumpstarter_proto_rawDesc = "" + "\x14ReleaseLeaseResponse\"\x13\n" + "\x11ListLeasesRequest\"*\n" + "\x12ListLeasesResponse\x12\x14\n" + - "\x05names\x18\x01 \x03(\tR\x05names2\xb7\x06\n" + + "\x05names\x18\x01 \x03(\tR\x05names\"\x12\n" + + "\x10GetStatusRequest\"v\n" + + "\x11GetStatusResponse\x126\n" + + "\x06status\x18\x01 \x01(\x0e2\x1e.jumpstarter.v1.ExporterStatusR\x06status\x12\x1d\n" + + "\amessage\x18\x02 \x01(\tH\x00R\amessage\x88\x01\x01B\n" + + "\n" + + "\b_message2\x92\a\n" + "\x11ControllerService\x12M\n" + "\bRegister\x12\x1f.jumpstarter.v1.RegisterRequest\x1a .jumpstarter.v1.RegisterResponse\x12S\n" + "\n" + - "Unregister\x12!.jumpstarter.v1.UnregisterRequest\x1a\".jumpstarter.v1.UnregisterResponse\x12I\n" + + "Unregister\x12!.jumpstarter.v1.UnregisterRequest\x1a\".jumpstarter.v1.UnregisterResponse\x12Y\n" + + "\fReportStatus\x12#.jumpstarter.v1.ReportStatusRequest\x1a$.jumpstarter.v1.ReportStatusResponse\x12I\n" + "\x06Listen\x12\x1d.jumpstarter.v1.ListenRequest\x1a\x1e.jumpstarter.v1.ListenResponse0\x01\x12I\n" + "\x06Status\x12\x1d.jumpstarter.v1.StatusRequest\x1a\x1e.jumpstarter.v1.StatusResponse0\x01\x12A\n" + "\x04Dial\x12\x1b.jumpstarter.v1.DialRequest\x1a\x1c.jumpstarter.v1.DialResponse\x12K\n" + @@ -1627,14 +1848,15 @@ const file_jumpstarter_v1_jumpstarter_proto_rawDesc = "" + "\fRequestLease\x12#.jumpstarter.v1.RequestLeaseRequest\x1a$.jumpstarter.v1.RequestLeaseResponse\x12Y\n" + "\fReleaseLease\x12#.jumpstarter.v1.ReleaseLeaseRequest\x1a$.jumpstarter.v1.ReleaseLeaseResponse\x12S\n" + "\n" + - "ListLeases\x12!.jumpstarter.v1.ListLeasesRequest\x1a\".jumpstarter.v1.ListLeasesResponse2\xb0\x03\n" + + "ListLeases\x12!.jumpstarter.v1.ListLeasesRequest\x1a\".jumpstarter.v1.ListLeasesResponse2\x82\x04\n" + "\x0fExporterService\x12F\n" + "\tGetReport\x12\x16.google.protobuf.Empty\x1a!.jumpstarter.v1.GetReportResponse\x12S\n" + "\n" + "DriverCall\x12!.jumpstarter.v1.DriverCallRequest\x1a\".jumpstarter.v1.DriverCallResponse\x12p\n" + "\x13StreamingDriverCall\x12*.jumpstarter.v1.StreamingDriverCallRequest\x1a+.jumpstarter.v1.StreamingDriverCallResponse0\x01\x12H\n" + "\tLogStream\x12\x16.google.protobuf.Empty\x1a!.jumpstarter.v1.LogStreamResponse0\x01\x12D\n" + - "\x05Reset\x12\x1c.jumpstarter.v1.ResetRequest\x1a\x1d.jumpstarter.v1.ResetResponseB\xcf\x01\n" + + "\x05Reset\x12\x1c.jumpstarter.v1.ResetRequest\x1a\x1d.jumpstarter.v1.ResetResponse\x12P\n" + + "\tGetStatus\x12 .jumpstarter.v1.GetStatusRequest\x1a!.jumpstarter.v1.GetStatusResponseB\xcf\x01\n" + "\x12com.jumpstarter.v1B\x10JumpstarterProtoP\x01ZNgithub.com/jumpstarter-dev/jumpstarter-controller/jumpstarter/v1;jumpstarterv1\xa2\x02\x03JXX\xaa\x02\x0eJumpstarter.V1\xca\x02\x0eJumpstarter\\V1\xe2\x02\x1aJumpstarter\\V1\\GPBMetadata\xea\x02\x0fJumpstarter::V1b\x06proto3" var ( @@ -1649,7 +1871,7 @@ func file_jumpstarter_v1_jumpstarter_proto_rawDescGZIP() []byte { return file_jumpstarter_v1_jumpstarter_proto_rawDescData } -var file_jumpstarter_v1_jumpstarter_proto_msgTypes = make([]protoimpl.MessageInfo, 32) +var file_jumpstarter_v1_jumpstarter_proto_msgTypes = make([]protoimpl.MessageInfo, 37) var file_jumpstarter_v1_jumpstarter_proto_goTypes = []any{ (*RegisterRequest)(nil), // 0: jumpstarter.v1.RegisterRequest (*DriverInstanceReport)(nil), // 1: jumpstarter.v1.DriverInstanceReport @@ -1663,86 +1885,101 @@ var file_jumpstarter_v1_jumpstarter_proto_goTypes = []any{ (*DialRequest)(nil), // 9: jumpstarter.v1.DialRequest (*DialResponse)(nil), // 10: jumpstarter.v1.DialResponse (*AuditStreamRequest)(nil), // 11: jumpstarter.v1.AuditStreamRequest - (*GetReportResponse)(nil), // 12: jumpstarter.v1.GetReportResponse - (*Endpoint)(nil), // 13: jumpstarter.v1.Endpoint - (*DriverCallRequest)(nil), // 14: jumpstarter.v1.DriverCallRequest - (*DriverCallResponse)(nil), // 15: jumpstarter.v1.DriverCallResponse - (*StreamingDriverCallRequest)(nil), // 16: jumpstarter.v1.StreamingDriverCallRequest - (*StreamingDriverCallResponse)(nil), // 17: jumpstarter.v1.StreamingDriverCallResponse - (*LogStreamResponse)(nil), // 18: jumpstarter.v1.LogStreamResponse - (*ResetRequest)(nil), // 19: jumpstarter.v1.ResetRequest - (*ResetResponse)(nil), // 20: jumpstarter.v1.ResetResponse - (*GetLeaseRequest)(nil), // 21: jumpstarter.v1.GetLeaseRequest - (*GetLeaseResponse)(nil), // 22: jumpstarter.v1.GetLeaseResponse - (*RequestLeaseRequest)(nil), // 23: jumpstarter.v1.RequestLeaseRequest - (*RequestLeaseResponse)(nil), // 24: jumpstarter.v1.RequestLeaseResponse - (*ReleaseLeaseRequest)(nil), // 25: jumpstarter.v1.ReleaseLeaseRequest - (*ReleaseLeaseResponse)(nil), // 26: jumpstarter.v1.ReleaseLeaseResponse - (*ListLeasesRequest)(nil), // 27: jumpstarter.v1.ListLeasesRequest - (*ListLeasesResponse)(nil), // 28: jumpstarter.v1.ListLeasesResponse - nil, // 29: jumpstarter.v1.RegisterRequest.LabelsEntry - nil, // 30: jumpstarter.v1.DriverInstanceReport.LabelsEntry - nil, // 31: jumpstarter.v1.GetReportResponse.LabelsEntry - (*structpb.Value)(nil), // 32: google.protobuf.Value - (*durationpb.Duration)(nil), // 33: google.protobuf.Duration - (*LabelSelector)(nil), // 34: jumpstarter.v1.LabelSelector - (*timestamppb.Timestamp)(nil), // 35: google.protobuf.Timestamp - (*Condition)(nil), // 36: jumpstarter.v1.Condition - (*emptypb.Empty)(nil), // 37: google.protobuf.Empty + (*ReportStatusRequest)(nil), // 12: jumpstarter.v1.ReportStatusRequest + (*ReportStatusResponse)(nil), // 13: jumpstarter.v1.ReportStatusResponse + (*GetReportResponse)(nil), // 14: jumpstarter.v1.GetReportResponse + (*Endpoint)(nil), // 15: jumpstarter.v1.Endpoint + (*DriverCallRequest)(nil), // 16: jumpstarter.v1.DriverCallRequest + (*DriverCallResponse)(nil), // 17: jumpstarter.v1.DriverCallResponse + (*StreamingDriverCallRequest)(nil), // 18: jumpstarter.v1.StreamingDriverCallRequest + (*StreamingDriverCallResponse)(nil), // 19: jumpstarter.v1.StreamingDriverCallResponse + (*LogStreamResponse)(nil), // 20: jumpstarter.v1.LogStreamResponse + (*ResetRequest)(nil), // 21: jumpstarter.v1.ResetRequest + (*ResetResponse)(nil), // 22: jumpstarter.v1.ResetResponse + (*GetLeaseRequest)(nil), // 23: jumpstarter.v1.GetLeaseRequest + (*GetLeaseResponse)(nil), // 24: jumpstarter.v1.GetLeaseResponse + (*RequestLeaseRequest)(nil), // 25: jumpstarter.v1.RequestLeaseRequest + (*RequestLeaseResponse)(nil), // 26: jumpstarter.v1.RequestLeaseResponse + (*ReleaseLeaseRequest)(nil), // 27: jumpstarter.v1.ReleaseLeaseRequest + (*ReleaseLeaseResponse)(nil), // 28: jumpstarter.v1.ReleaseLeaseResponse + (*ListLeasesRequest)(nil), // 29: jumpstarter.v1.ListLeasesRequest + (*ListLeasesResponse)(nil), // 30: jumpstarter.v1.ListLeasesResponse + (*GetStatusRequest)(nil), // 31: jumpstarter.v1.GetStatusRequest + (*GetStatusResponse)(nil), // 32: jumpstarter.v1.GetStatusResponse + nil, // 33: jumpstarter.v1.RegisterRequest.LabelsEntry + nil, // 34: jumpstarter.v1.DriverInstanceReport.LabelsEntry + nil, // 35: jumpstarter.v1.DriverInstanceReport.MethodsDescriptionEntry + nil, // 36: jumpstarter.v1.GetReportResponse.LabelsEntry + (ExporterStatus)(0), // 37: jumpstarter.v1.ExporterStatus + (*structpb.Value)(nil), // 38: google.protobuf.Value + (LogSource)(0), // 39: jumpstarter.v1.LogSource + (*durationpb.Duration)(nil), // 40: google.protobuf.Duration + (*LabelSelector)(nil), // 41: jumpstarter.v1.LabelSelector + (*timestamppb.Timestamp)(nil), // 42: google.protobuf.Timestamp + (*Condition)(nil), // 43: jumpstarter.v1.Condition + (*emptypb.Empty)(nil), // 44: google.protobuf.Empty } var file_jumpstarter_v1_jumpstarter_proto_depIdxs = []int32{ - 29, // 0: jumpstarter.v1.RegisterRequest.labels:type_name -> jumpstarter.v1.RegisterRequest.LabelsEntry + 33, // 0: jumpstarter.v1.RegisterRequest.labels:type_name -> jumpstarter.v1.RegisterRequest.LabelsEntry 1, // 1: jumpstarter.v1.RegisterRequest.reports:type_name -> jumpstarter.v1.DriverInstanceReport - 30, // 2: jumpstarter.v1.DriverInstanceReport.labels:type_name -> jumpstarter.v1.DriverInstanceReport.LabelsEntry - 31, // 3: jumpstarter.v1.GetReportResponse.labels:type_name -> jumpstarter.v1.GetReportResponse.LabelsEntry - 1, // 4: jumpstarter.v1.GetReportResponse.reports:type_name -> jumpstarter.v1.DriverInstanceReport - 13, // 5: jumpstarter.v1.GetReportResponse.alternative_endpoints:type_name -> jumpstarter.v1.Endpoint - 32, // 6: jumpstarter.v1.DriverCallRequest.args:type_name -> google.protobuf.Value - 32, // 7: jumpstarter.v1.DriverCallResponse.result:type_name -> google.protobuf.Value - 32, // 8: jumpstarter.v1.StreamingDriverCallRequest.args:type_name -> google.protobuf.Value - 32, // 9: jumpstarter.v1.StreamingDriverCallResponse.result:type_name -> google.protobuf.Value - 33, // 10: jumpstarter.v1.GetLeaseResponse.duration:type_name -> google.protobuf.Duration - 34, // 11: jumpstarter.v1.GetLeaseResponse.selector:type_name -> jumpstarter.v1.LabelSelector - 35, // 12: jumpstarter.v1.GetLeaseResponse.begin_time:type_name -> google.protobuf.Timestamp - 35, // 13: jumpstarter.v1.GetLeaseResponse.end_time:type_name -> google.protobuf.Timestamp - 36, // 14: jumpstarter.v1.GetLeaseResponse.conditions:type_name -> jumpstarter.v1.Condition - 33, // 15: jumpstarter.v1.RequestLeaseRequest.duration:type_name -> google.protobuf.Duration - 34, // 16: jumpstarter.v1.RequestLeaseRequest.selector:type_name -> jumpstarter.v1.LabelSelector - 0, // 17: jumpstarter.v1.ControllerService.Register:input_type -> jumpstarter.v1.RegisterRequest - 3, // 18: jumpstarter.v1.ControllerService.Unregister:input_type -> jumpstarter.v1.UnregisterRequest - 5, // 19: jumpstarter.v1.ControllerService.Listen:input_type -> jumpstarter.v1.ListenRequest - 7, // 20: jumpstarter.v1.ControllerService.Status:input_type -> jumpstarter.v1.StatusRequest - 9, // 21: jumpstarter.v1.ControllerService.Dial:input_type -> jumpstarter.v1.DialRequest - 11, // 22: jumpstarter.v1.ControllerService.AuditStream:input_type -> jumpstarter.v1.AuditStreamRequest - 21, // 23: jumpstarter.v1.ControllerService.GetLease:input_type -> jumpstarter.v1.GetLeaseRequest - 23, // 24: jumpstarter.v1.ControllerService.RequestLease:input_type -> jumpstarter.v1.RequestLeaseRequest - 25, // 25: jumpstarter.v1.ControllerService.ReleaseLease:input_type -> jumpstarter.v1.ReleaseLeaseRequest - 27, // 26: jumpstarter.v1.ControllerService.ListLeases:input_type -> jumpstarter.v1.ListLeasesRequest - 37, // 27: jumpstarter.v1.ExporterService.GetReport:input_type -> google.protobuf.Empty - 14, // 28: jumpstarter.v1.ExporterService.DriverCall:input_type -> jumpstarter.v1.DriverCallRequest - 16, // 29: jumpstarter.v1.ExporterService.StreamingDriverCall:input_type -> jumpstarter.v1.StreamingDriverCallRequest - 37, // 30: jumpstarter.v1.ExporterService.LogStream:input_type -> google.protobuf.Empty - 19, // 31: jumpstarter.v1.ExporterService.Reset:input_type -> jumpstarter.v1.ResetRequest - 2, // 32: jumpstarter.v1.ControllerService.Register:output_type -> jumpstarter.v1.RegisterResponse - 4, // 33: jumpstarter.v1.ControllerService.Unregister:output_type -> jumpstarter.v1.UnregisterResponse - 6, // 34: jumpstarter.v1.ControllerService.Listen:output_type -> jumpstarter.v1.ListenResponse - 8, // 35: jumpstarter.v1.ControllerService.Status:output_type -> jumpstarter.v1.StatusResponse - 10, // 36: jumpstarter.v1.ControllerService.Dial:output_type -> jumpstarter.v1.DialResponse - 37, // 37: jumpstarter.v1.ControllerService.AuditStream:output_type -> google.protobuf.Empty - 22, // 38: jumpstarter.v1.ControllerService.GetLease:output_type -> jumpstarter.v1.GetLeaseResponse - 24, // 39: jumpstarter.v1.ControllerService.RequestLease:output_type -> jumpstarter.v1.RequestLeaseResponse - 26, // 40: jumpstarter.v1.ControllerService.ReleaseLease:output_type -> jumpstarter.v1.ReleaseLeaseResponse - 28, // 41: jumpstarter.v1.ControllerService.ListLeases:output_type -> jumpstarter.v1.ListLeasesResponse - 12, // 42: jumpstarter.v1.ExporterService.GetReport:output_type -> jumpstarter.v1.GetReportResponse - 15, // 43: jumpstarter.v1.ExporterService.DriverCall:output_type -> jumpstarter.v1.DriverCallResponse - 17, // 44: jumpstarter.v1.ExporterService.StreamingDriverCall:output_type -> jumpstarter.v1.StreamingDriverCallResponse - 18, // 45: jumpstarter.v1.ExporterService.LogStream:output_type -> jumpstarter.v1.LogStreamResponse - 20, // 46: jumpstarter.v1.ExporterService.Reset:output_type -> jumpstarter.v1.ResetResponse - 32, // [32:47] is the sub-list for method output_type - 17, // [17:32] is the sub-list for method input_type - 17, // [17:17] is the sub-list for extension type_name - 17, // [17:17] is the sub-list for extension extendee - 0, // [0:17] is the sub-list for field type_name + 34, // 2: jumpstarter.v1.DriverInstanceReport.labels:type_name -> jumpstarter.v1.DriverInstanceReport.LabelsEntry + 35, // 3: jumpstarter.v1.DriverInstanceReport.methods_description:type_name -> jumpstarter.v1.DriverInstanceReport.MethodsDescriptionEntry + 37, // 4: jumpstarter.v1.ReportStatusRequest.status:type_name -> jumpstarter.v1.ExporterStatus + 36, // 5: jumpstarter.v1.GetReportResponse.labels:type_name -> jumpstarter.v1.GetReportResponse.LabelsEntry + 1, // 6: jumpstarter.v1.GetReportResponse.reports:type_name -> jumpstarter.v1.DriverInstanceReport + 15, // 7: jumpstarter.v1.GetReportResponse.alternative_endpoints:type_name -> jumpstarter.v1.Endpoint + 38, // 8: jumpstarter.v1.DriverCallRequest.args:type_name -> google.protobuf.Value + 38, // 9: jumpstarter.v1.DriverCallResponse.result:type_name -> google.protobuf.Value + 38, // 10: jumpstarter.v1.StreamingDriverCallRequest.args:type_name -> google.protobuf.Value + 38, // 11: jumpstarter.v1.StreamingDriverCallResponse.result:type_name -> google.protobuf.Value + 39, // 12: jumpstarter.v1.LogStreamResponse.source:type_name -> jumpstarter.v1.LogSource + 40, // 13: jumpstarter.v1.GetLeaseResponse.duration:type_name -> google.protobuf.Duration + 41, // 14: jumpstarter.v1.GetLeaseResponse.selector:type_name -> jumpstarter.v1.LabelSelector + 42, // 15: jumpstarter.v1.GetLeaseResponse.begin_time:type_name -> google.protobuf.Timestamp + 42, // 16: jumpstarter.v1.GetLeaseResponse.end_time:type_name -> google.protobuf.Timestamp + 43, // 17: jumpstarter.v1.GetLeaseResponse.conditions:type_name -> jumpstarter.v1.Condition + 40, // 18: jumpstarter.v1.RequestLeaseRequest.duration:type_name -> google.protobuf.Duration + 41, // 19: jumpstarter.v1.RequestLeaseRequest.selector:type_name -> jumpstarter.v1.LabelSelector + 37, // 20: jumpstarter.v1.GetStatusResponse.status:type_name -> jumpstarter.v1.ExporterStatus + 0, // 21: jumpstarter.v1.ControllerService.Register:input_type -> jumpstarter.v1.RegisterRequest + 3, // 22: jumpstarter.v1.ControllerService.Unregister:input_type -> jumpstarter.v1.UnregisterRequest + 12, // 23: jumpstarter.v1.ControllerService.ReportStatus:input_type -> jumpstarter.v1.ReportStatusRequest + 5, // 24: jumpstarter.v1.ControllerService.Listen:input_type -> jumpstarter.v1.ListenRequest + 7, // 25: jumpstarter.v1.ControllerService.Status:input_type -> jumpstarter.v1.StatusRequest + 9, // 26: jumpstarter.v1.ControllerService.Dial:input_type -> jumpstarter.v1.DialRequest + 11, // 27: jumpstarter.v1.ControllerService.AuditStream:input_type -> jumpstarter.v1.AuditStreamRequest + 23, // 28: jumpstarter.v1.ControllerService.GetLease:input_type -> jumpstarter.v1.GetLeaseRequest + 25, // 29: jumpstarter.v1.ControllerService.RequestLease:input_type -> jumpstarter.v1.RequestLeaseRequest + 27, // 30: jumpstarter.v1.ControllerService.ReleaseLease:input_type -> jumpstarter.v1.ReleaseLeaseRequest + 29, // 31: jumpstarter.v1.ControllerService.ListLeases:input_type -> jumpstarter.v1.ListLeasesRequest + 44, // 32: jumpstarter.v1.ExporterService.GetReport:input_type -> google.protobuf.Empty + 16, // 33: jumpstarter.v1.ExporterService.DriverCall:input_type -> jumpstarter.v1.DriverCallRequest + 18, // 34: jumpstarter.v1.ExporterService.StreamingDriverCall:input_type -> jumpstarter.v1.StreamingDriverCallRequest + 44, // 35: jumpstarter.v1.ExporterService.LogStream:input_type -> google.protobuf.Empty + 21, // 36: jumpstarter.v1.ExporterService.Reset:input_type -> jumpstarter.v1.ResetRequest + 31, // 37: jumpstarter.v1.ExporterService.GetStatus:input_type -> jumpstarter.v1.GetStatusRequest + 2, // 38: jumpstarter.v1.ControllerService.Register:output_type -> jumpstarter.v1.RegisterResponse + 4, // 39: jumpstarter.v1.ControllerService.Unregister:output_type -> jumpstarter.v1.UnregisterResponse + 13, // 40: jumpstarter.v1.ControllerService.ReportStatus:output_type -> jumpstarter.v1.ReportStatusResponse + 6, // 41: jumpstarter.v1.ControllerService.Listen:output_type -> jumpstarter.v1.ListenResponse + 8, // 42: jumpstarter.v1.ControllerService.Status:output_type -> jumpstarter.v1.StatusResponse + 10, // 43: jumpstarter.v1.ControllerService.Dial:output_type -> jumpstarter.v1.DialResponse + 44, // 44: jumpstarter.v1.ControllerService.AuditStream:output_type -> google.protobuf.Empty + 24, // 45: jumpstarter.v1.ControllerService.GetLease:output_type -> jumpstarter.v1.GetLeaseResponse + 26, // 46: jumpstarter.v1.ControllerService.RequestLease:output_type -> jumpstarter.v1.RequestLeaseResponse + 28, // 47: jumpstarter.v1.ControllerService.ReleaseLease:output_type -> jumpstarter.v1.ReleaseLeaseResponse + 30, // 48: jumpstarter.v1.ControllerService.ListLeases:output_type -> jumpstarter.v1.ListLeasesResponse + 14, // 49: jumpstarter.v1.ExporterService.GetReport:output_type -> jumpstarter.v1.GetReportResponse + 17, // 50: jumpstarter.v1.ExporterService.DriverCall:output_type -> jumpstarter.v1.DriverCallResponse + 19, // 51: jumpstarter.v1.ExporterService.StreamingDriverCall:output_type -> jumpstarter.v1.StreamingDriverCallResponse + 20, // 52: jumpstarter.v1.ExporterService.LogStream:output_type -> jumpstarter.v1.LogStreamResponse + 22, // 53: jumpstarter.v1.ExporterService.Reset:output_type -> jumpstarter.v1.ResetResponse + 32, // 54: jumpstarter.v1.ExporterService.GetStatus:output_type -> jumpstarter.v1.GetStatusResponse + 38, // [38:55] is the sub-list for method output_type + 21, // [21:38] is the sub-list for method input_type + 21, // [21:21] is the sub-list for extension type_name + 21, // [21:21] is the sub-list for extension extendee + 0, // [0:21] is the sub-list for field type_name } func init() { file_jumpstarter_v1_jumpstarter_proto_init() } @@ -1751,16 +1988,20 @@ func file_jumpstarter_v1_jumpstarter_proto_init() { return } file_jumpstarter_v1_kubernetes_proto_init() + file_jumpstarter_v1_common_proto_init() file_jumpstarter_v1_jumpstarter_proto_msgTypes[1].OneofWrappers = []any{} file_jumpstarter_v1_jumpstarter_proto_msgTypes[8].OneofWrappers = []any{} - file_jumpstarter_v1_jumpstarter_proto_msgTypes[22].OneofWrappers = []any{} + file_jumpstarter_v1_jumpstarter_proto_msgTypes[12].OneofWrappers = []any{} + file_jumpstarter_v1_jumpstarter_proto_msgTypes[20].OneofWrappers = []any{} + file_jumpstarter_v1_jumpstarter_proto_msgTypes[24].OneofWrappers = []any{} + file_jumpstarter_v1_jumpstarter_proto_msgTypes[32].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_jumpstarter_v1_jumpstarter_proto_rawDesc), len(file_jumpstarter_v1_jumpstarter_proto_rawDesc)), NumEnums: 0, - NumMessages: 32, + NumMessages: 37, NumExtensions: 0, NumServices: 2, }, diff --git a/internal/protocol/jumpstarter/v1/jumpstarter_grpc.pb.go b/internal/protocol/jumpstarter/v1/jumpstarter_grpc.pb.go index 3aee3cfe..d0fbd1bd 100644 --- a/internal/protocol/jumpstarter/v1/jumpstarter_grpc.pb.go +++ b/internal/protocol/jumpstarter/v1/jumpstarter_grpc.pb.go @@ -24,6 +24,7 @@ const _ = grpc.SupportPackageIsVersion9 const ( ControllerService_Register_FullMethodName = "/jumpstarter.v1.ControllerService/Register" ControllerService_Unregister_FullMethodName = "/jumpstarter.v1.ControllerService/Unregister" + ControllerService_ReportStatus_FullMethodName = "/jumpstarter.v1.ControllerService/ReportStatus" ControllerService_Listen_FullMethodName = "/jumpstarter.v1.ControllerService/Listen" ControllerService_Status_FullMethodName = "/jumpstarter.v1.ControllerService/Status" ControllerService_Dial_FullMethodName = "/jumpstarter.v1.ControllerService/Dial" @@ -47,6 +48,9 @@ type ControllerServiceClient interface { // we will eventually have a mechanism to tell the router this token // has been invalidated Unregister(ctx context.Context, in *UnregisterRequest, opts ...grpc.CallOption) (*UnregisterResponse, error) + // Exporter status report + // Allows exporters to report their own status to the controller + ReportStatus(ctx context.Context, in *ReportStatusRequest, opts ...grpc.CallOption) (*ReportStatusResponse, error) // Exporter listening // Returns stream tokens for accepting incoming client connections Listen(ctx context.Context, in *ListenRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ListenResponse], error) @@ -98,6 +102,16 @@ func (c *controllerServiceClient) Unregister(ctx context.Context, in *Unregister return out, nil } +func (c *controllerServiceClient) ReportStatus(ctx context.Context, in *ReportStatusRequest, opts ...grpc.CallOption) (*ReportStatusResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ReportStatusResponse) + err := c.cc.Invoke(ctx, ControllerService_ReportStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *controllerServiceClient) Listen(ctx context.Context, in *ListenRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ListenResponse], error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) stream, err := c.cc.NewStream(ctx, &ControllerService_ServiceDesc.Streams[0], ControllerService_Listen_FullMethodName, cOpts...) @@ -212,6 +226,9 @@ type ControllerServiceServer interface { // we will eventually have a mechanism to tell the router this token // has been invalidated Unregister(context.Context, *UnregisterRequest) (*UnregisterResponse, error) + // Exporter status report + // Allows exporters to report their own status to the controller + ReportStatus(context.Context, *ReportStatusRequest) (*ReportStatusResponse, error) // Exporter listening // Returns stream tokens for accepting incoming client connections Listen(*ListenRequest, grpc.ServerStreamingServer[ListenResponse]) error @@ -249,6 +266,9 @@ func (UnimplementedControllerServiceServer) Register(context.Context, *RegisterR func (UnimplementedControllerServiceServer) Unregister(context.Context, *UnregisterRequest) (*UnregisterResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Unregister not implemented") } +func (UnimplementedControllerServiceServer) ReportStatus(context.Context, *ReportStatusRequest) (*ReportStatusResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ReportStatus not implemented") +} func (UnimplementedControllerServiceServer) Listen(*ListenRequest, grpc.ServerStreamingServer[ListenResponse]) error { return status.Errorf(codes.Unimplemented, "method Listen not implemented") } @@ -330,6 +350,24 @@ func _ControllerService_Unregister_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } +func _ControllerService_ReportStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ReportStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ControllerServiceServer).ReportStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ControllerService_ReportStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ControllerServiceServer).ReportStatus(ctx, req.(*ReportStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _ControllerService_Listen_Handler(srv interface{}, stream grpc.ServerStream) error { m := new(ListenRequest) if err := stream.RecvMsg(m); err != nil { @@ -464,6 +502,10 @@ var ControllerService_ServiceDesc = grpc.ServiceDesc{ MethodName: "Unregister", Handler: _ControllerService_Unregister_Handler, }, + { + MethodName: "ReportStatus", + Handler: _ControllerService_ReportStatus_Handler, + }, { MethodName: "Dial", Handler: _ControllerService_Dial_Handler, @@ -511,6 +553,7 @@ const ( ExporterService_StreamingDriverCall_FullMethodName = "/jumpstarter.v1.ExporterService/StreamingDriverCall" ExporterService_LogStream_FullMethodName = "/jumpstarter.v1.ExporterService/LogStream" ExporterService_Reset_FullMethodName = "/jumpstarter.v1.ExporterService/Reset" + ExporterService_GetStatus_FullMethodName = "/jumpstarter.v1.ExporterService/GetStatus" ) // ExporterServiceClient is the client API for ExporterService service. @@ -526,6 +569,7 @@ type ExporterServiceClient interface { StreamingDriverCall(ctx context.Context, in *StreamingDriverCallRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[StreamingDriverCallResponse], error) LogStream(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[LogStreamResponse], error) Reset(ctx context.Context, in *ResetRequest, opts ...grpc.CallOption) (*ResetResponse, error) + GetStatus(ctx context.Context, in *GetStatusRequest, opts ...grpc.CallOption) (*GetStatusResponse, error) } type exporterServiceClient struct { @@ -604,6 +648,16 @@ func (c *exporterServiceClient) Reset(ctx context.Context, in *ResetRequest, opt return out, nil } +func (c *exporterServiceClient) GetStatus(ctx context.Context, in *GetStatusRequest, opts ...grpc.CallOption) (*GetStatusResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetStatusResponse) + err := c.cc.Invoke(ctx, ExporterService_GetStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // ExporterServiceServer is the server API for ExporterService service. // All implementations must embed UnimplementedExporterServiceServer // for forward compatibility. @@ -617,6 +671,7 @@ type ExporterServiceServer interface { StreamingDriverCall(*StreamingDriverCallRequest, grpc.ServerStreamingServer[StreamingDriverCallResponse]) error LogStream(*emptypb.Empty, grpc.ServerStreamingServer[LogStreamResponse]) error Reset(context.Context, *ResetRequest) (*ResetResponse, error) + GetStatus(context.Context, *GetStatusRequest) (*GetStatusResponse, error) mustEmbedUnimplementedExporterServiceServer() } @@ -642,6 +697,9 @@ func (UnimplementedExporterServiceServer) LogStream(*emptypb.Empty, grpc.ServerS func (UnimplementedExporterServiceServer) Reset(context.Context, *ResetRequest) (*ResetResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Reset not implemented") } +func (UnimplementedExporterServiceServer) GetStatus(context.Context, *GetStatusRequest) (*GetStatusResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetStatus not implemented") +} func (UnimplementedExporterServiceServer) mustEmbedUnimplementedExporterServiceServer() {} func (UnimplementedExporterServiceServer) testEmbeddedByValue() {} @@ -739,6 +797,24 @@ func _ExporterService_Reset_Handler(srv interface{}, ctx context.Context, dec fu return interceptor(ctx, in, info, handler) } +func _ExporterService_GetStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ExporterServiceServer).GetStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ExporterService_GetStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ExporterServiceServer).GetStatus(ctx, req.(*GetStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + // ExporterService_ServiceDesc is the grpc.ServiceDesc for ExporterService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -758,6 +834,10 @@ var ExporterService_ServiceDesc = grpc.ServiceDesc{ MethodName: "Reset", Handler: _ExporterService_Reset_Handler, }, + { + MethodName: "GetStatus", + Handler: _ExporterService_GetStatus_Handler, + }, }, Streams: []grpc.StreamDesc{ { diff --git a/internal/protocol/jumpstarter/v1/kubernetes.pb.go b/internal/protocol/jumpstarter/v1/kubernetes.pb.go index b3ec3597..c73ef79c 100644 --- a/internal/protocol/jumpstarter/v1/kubernetes.pb.go +++ b/internal/protocol/jumpstarter/v1/kubernetes.pb.go @@ -2,7 +2,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.6 +// protoc-gen-go v1.36.10 // protoc (unknown) // source: jumpstarter/v1/kubernetes.proto diff --git a/internal/protocol/jumpstarter/v1/router.pb.go b/internal/protocol/jumpstarter/v1/router.pb.go index 4a7d21e5..03443c9a 100644 --- a/internal/protocol/jumpstarter/v1/router.pb.go +++ b/internal/protocol/jumpstarter/v1/router.pb.go @@ -2,7 +2,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.6 +// protoc-gen-go v1.36.10 // protoc (unknown) // source: jumpstarter/v1/router.proto