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/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()) + }) + }) +}) 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 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 b85f4478..d7712bac 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 { @@ -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 }