From ec48e73ebccd033c5df89a32381414c707de59fa Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Thu, 19 Feb 2026 15:07:57 +0100 Subject: [PATCH 01/63] feat: update Firecracker client to v1.14 Bump the swagger file for Firecracker to v1.14 and regenerate APIs/models. Signed-off-by: Babis Chalios --- .../operations/create_snapshot_parameters.go | 2 +- .../describe_balloon_hinting_parameters.go | 125 +++++ .../describe_balloon_hinting_responses.go | 261 ++++++++++ .../operations/get_memory_dirty_parameters.go | 125 ----- .../operations/get_memory_dirty_responses.go | 261 ---------- .../get_memory_hotplug_parameters.go | 125 +++++ .../get_memory_hotplug_responses.go | 185 +++++++ .../operations/load_snapshot_parameters.go | 2 +- .../fc/client/operations/operations_client.go | 362 ++++++++++++++ .../patch_memory_hotplug_parameters.go | 150 ++++++ .../patch_memory_hotplug_responses.go | 171 +++++++ .../put_guest_pmem_by_id_parameters.go | 172 +++++++ .../put_guest_pmem_by_id_responses.go | 247 ++++++++++ .../put_memory_hotplug_parameters.go | 150 ++++++ .../put_memory_hotplug_responses.go | 171 +++++++ .../put_serial_device_parameters.go | 150 ++++++ .../operations/put_serial_device_responses.go | 171 +++++++ .../start_balloon_hinting_parameters.go | 150 ++++++ .../start_balloon_hinting_responses.go | 247 ++++++++++ .../stop_balloon_hinting_parameters.go | 125 +++++ .../stop_balloon_hinting_responses.go | 247 ++++++++++ packages/shared/pkg/fc/firecracker.yml | 458 +++++++++++++++++- .../pkg/fc/models/arm_register_modifier.go | 85 ++++ packages/shared/pkg/fc/models/balloon.go | 6 + .../pkg/fc/models/balloon_hinting_status.go | 71 +++ .../shared/pkg/fc/models/balloon_start_cmd.go | 47 ++ .../shared/pkg/fc/models/balloon_stats.go | 18 + packages/shared/pkg/fc/models/cpu_config.go | 303 +++++++++++- .../pkg/fc/models/cpuid_leaf_modifier.go | 181 +++++++ .../pkg/fc/models/cpuid_register_modifier.go | 127 +++++ .../pkg/fc/models/full_vm_configuration.go | 129 +++++ .../pkg/fc/models/memory_hotplug_config.go | 94 ++++ .../fc/models/memory_hotplug_size_update.go | 47 ++ .../pkg/fc/models/memory_hotplug_status.go | 59 +++ packages/shared/pkg/fc/models/mmds_config.go | 3 + packages/shared/pkg/fc/models/msr_modifier.go | 85 ++++ packages/shared/pkg/fc/models/pmem.go | 91 ++++ .../shared/pkg/fc/models/serial_device.go | 47 ++ .../pkg/fc/models/snapshot_create_params.go | 2 +- .../pkg/fc/models/snapshot_load_params.go | 5 +- .../shared/pkg/fc/models/vcpu_features.go | 85 ++++ 41 files changed, 5124 insertions(+), 418 deletions(-) create mode 100644 packages/shared/pkg/fc/client/operations/describe_balloon_hinting_parameters.go create mode 100644 packages/shared/pkg/fc/client/operations/describe_balloon_hinting_responses.go delete mode 100644 packages/shared/pkg/fc/client/operations/get_memory_dirty_parameters.go delete mode 100644 packages/shared/pkg/fc/client/operations/get_memory_dirty_responses.go create mode 100644 packages/shared/pkg/fc/client/operations/get_memory_hotplug_parameters.go create mode 100644 packages/shared/pkg/fc/client/operations/get_memory_hotplug_responses.go create mode 100644 packages/shared/pkg/fc/client/operations/patch_memory_hotplug_parameters.go create mode 100644 packages/shared/pkg/fc/client/operations/patch_memory_hotplug_responses.go create mode 100644 packages/shared/pkg/fc/client/operations/put_guest_pmem_by_id_parameters.go create mode 100644 packages/shared/pkg/fc/client/operations/put_guest_pmem_by_id_responses.go create mode 100644 packages/shared/pkg/fc/client/operations/put_memory_hotplug_parameters.go create mode 100644 packages/shared/pkg/fc/client/operations/put_memory_hotplug_responses.go create mode 100644 packages/shared/pkg/fc/client/operations/put_serial_device_parameters.go create mode 100644 packages/shared/pkg/fc/client/operations/put_serial_device_responses.go create mode 100644 packages/shared/pkg/fc/client/operations/start_balloon_hinting_parameters.go create mode 100644 packages/shared/pkg/fc/client/operations/start_balloon_hinting_responses.go create mode 100644 packages/shared/pkg/fc/client/operations/stop_balloon_hinting_parameters.go create mode 100644 packages/shared/pkg/fc/client/operations/stop_balloon_hinting_responses.go create mode 100644 packages/shared/pkg/fc/models/arm_register_modifier.go create mode 100644 packages/shared/pkg/fc/models/balloon_hinting_status.go create mode 100644 packages/shared/pkg/fc/models/balloon_start_cmd.go create mode 100644 packages/shared/pkg/fc/models/cpuid_leaf_modifier.go create mode 100644 packages/shared/pkg/fc/models/cpuid_register_modifier.go create mode 100644 packages/shared/pkg/fc/models/memory_hotplug_config.go create mode 100644 packages/shared/pkg/fc/models/memory_hotplug_size_update.go create mode 100644 packages/shared/pkg/fc/models/memory_hotplug_status.go create mode 100644 packages/shared/pkg/fc/models/msr_modifier.go create mode 100644 packages/shared/pkg/fc/models/pmem.go create mode 100644 packages/shared/pkg/fc/models/serial_device.go create mode 100644 packages/shared/pkg/fc/models/vcpu_features.go diff --git a/packages/shared/pkg/fc/client/operations/create_snapshot_parameters.go b/packages/shared/pkg/fc/client/operations/create_snapshot_parameters.go index d5a2d163d9..945ad30987 100644 --- a/packages/shared/pkg/fc/client/operations/create_snapshot_parameters.go +++ b/packages/shared/pkg/fc/client/operations/create_snapshot_parameters.go @@ -62,7 +62,7 @@ type CreateSnapshotParams struct { /* Body. - The configuration used for creating a snaphot. + The configuration used for creating a snapshot. */ Body *models.SnapshotCreateParams diff --git a/packages/shared/pkg/fc/client/operations/describe_balloon_hinting_parameters.go b/packages/shared/pkg/fc/client/operations/describe_balloon_hinting_parameters.go new file mode 100644 index 0000000000..2a954ceae1 --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/describe_balloon_hinting_parameters.go @@ -0,0 +1,125 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewDescribeBalloonHintingParams creates a new DescribeBalloonHintingParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewDescribeBalloonHintingParams() *DescribeBalloonHintingParams { + return &DescribeBalloonHintingParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewDescribeBalloonHintingParamsWithTimeout creates a new DescribeBalloonHintingParams object +// with the ability to set a timeout on a request. +func NewDescribeBalloonHintingParamsWithTimeout(timeout time.Duration) *DescribeBalloonHintingParams { + return &DescribeBalloonHintingParams{ + timeout: timeout, + } +} + +// NewDescribeBalloonHintingParamsWithContext creates a new DescribeBalloonHintingParams object +// with the ability to set a context for a request. +func NewDescribeBalloonHintingParamsWithContext(ctx context.Context) *DescribeBalloonHintingParams { + return &DescribeBalloonHintingParams{ + Context: ctx, + } +} + +// NewDescribeBalloonHintingParamsWithHTTPClient creates a new DescribeBalloonHintingParams object +// with the ability to set a custom HTTPClient for a request. +func NewDescribeBalloonHintingParamsWithHTTPClient(client *http.Client) *DescribeBalloonHintingParams { + return &DescribeBalloonHintingParams{ + HTTPClient: client, + } +} + +/* +DescribeBalloonHintingParams contains all the parameters to send to the API endpoint + + for the describe balloon hinting operation. + + Typically these are written to a http.Request. +*/ +type DescribeBalloonHintingParams struct { + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the describe balloon hinting params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *DescribeBalloonHintingParams) WithDefaults() *DescribeBalloonHintingParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the describe balloon hinting params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *DescribeBalloonHintingParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the describe balloon hinting params +func (o *DescribeBalloonHintingParams) WithTimeout(timeout time.Duration) *DescribeBalloonHintingParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the describe balloon hinting params +func (o *DescribeBalloonHintingParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the describe balloon hinting params +func (o *DescribeBalloonHintingParams) WithContext(ctx context.Context) *DescribeBalloonHintingParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the describe balloon hinting params +func (o *DescribeBalloonHintingParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the describe balloon hinting params +func (o *DescribeBalloonHintingParams) WithHTTPClient(client *http.Client) *DescribeBalloonHintingParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the describe balloon hinting params +func (o *DescribeBalloonHintingParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WriteToRequest writes these params to a swagger request +func (o *DescribeBalloonHintingParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/describe_balloon_hinting_responses.go b/packages/shared/pkg/fc/client/operations/describe_balloon_hinting_responses.go new file mode 100644 index 0000000000..c5be936f3b --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/describe_balloon_hinting_responses.go @@ -0,0 +1,261 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "encoding/json" + stderrors "errors" + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// DescribeBalloonHintingReader is a Reader for the DescribeBalloonHinting structure. +type DescribeBalloonHintingReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *DescribeBalloonHintingReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 200: + result := NewDescribeBalloonHintingOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + case 400: + result := NewDescribeBalloonHintingBadRequest() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + default: + result := NewDescribeBalloonHintingDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewDescribeBalloonHintingOK creates a DescribeBalloonHintingOK with default headers values +func NewDescribeBalloonHintingOK() *DescribeBalloonHintingOK { + return &DescribeBalloonHintingOK{} +} + +/* +DescribeBalloonHintingOK describes a response with status code 200, with default header values. + +The balloon free page hinting statistics +*/ +type DescribeBalloonHintingOK struct { + Payload *models.BalloonHintingStatus +} + +// IsSuccess returns true when this describe balloon hinting o k response has a 2xx status code +func (o *DescribeBalloonHintingOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this describe balloon hinting o k response has a 3xx status code +func (o *DescribeBalloonHintingOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this describe balloon hinting o k response has a 4xx status code +func (o *DescribeBalloonHintingOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this describe balloon hinting o k response has a 5xx status code +func (o *DescribeBalloonHintingOK) IsServerError() bool { + return false +} + +// IsCode returns true when this describe balloon hinting o k response a status code equal to that given +func (o *DescribeBalloonHintingOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the describe balloon hinting o k response +func (o *DescribeBalloonHintingOK) Code() int { + return 200 +} + +func (o *DescribeBalloonHintingOK) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /balloon/hinting/status][%d] describeBalloonHintingOK %s", 200, payload) +} + +func (o *DescribeBalloonHintingOK) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /balloon/hinting/status][%d] describeBalloonHintingOK %s", 200, payload) +} + +func (o *DescribeBalloonHintingOK) GetPayload() *models.BalloonHintingStatus { + return o.Payload +} + +func (o *DescribeBalloonHintingOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.BalloonHintingStatus) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewDescribeBalloonHintingBadRequest creates a DescribeBalloonHintingBadRequest with default headers values +func NewDescribeBalloonHintingBadRequest() *DescribeBalloonHintingBadRequest { + return &DescribeBalloonHintingBadRequest{} +} + +/* +DescribeBalloonHintingBadRequest describes a response with status code 400, with default header values. + +The balloon free hinting was not enabled when the device was configured. +*/ +type DescribeBalloonHintingBadRequest struct { + Payload *models.Error +} + +// IsSuccess returns true when this describe balloon hinting bad request response has a 2xx status code +func (o *DescribeBalloonHintingBadRequest) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this describe balloon hinting bad request response has a 3xx status code +func (o *DescribeBalloonHintingBadRequest) IsRedirect() bool { + return false +} + +// IsClientError returns true when this describe balloon hinting bad request response has a 4xx status code +func (o *DescribeBalloonHintingBadRequest) IsClientError() bool { + return true +} + +// IsServerError returns true when this describe balloon hinting bad request response has a 5xx status code +func (o *DescribeBalloonHintingBadRequest) IsServerError() bool { + return false +} + +// IsCode returns true when this describe balloon hinting bad request response a status code equal to that given +func (o *DescribeBalloonHintingBadRequest) IsCode(code int) bool { + return code == 400 +} + +// Code gets the status code for the describe balloon hinting bad request response +func (o *DescribeBalloonHintingBadRequest) Code() int { + return 400 +} + +func (o *DescribeBalloonHintingBadRequest) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /balloon/hinting/status][%d] describeBalloonHintingBadRequest %s", 400, payload) +} + +func (o *DescribeBalloonHintingBadRequest) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /balloon/hinting/status][%d] describeBalloonHintingBadRequest %s", 400, payload) +} + +func (o *DescribeBalloonHintingBadRequest) GetPayload() *models.Error { + return o.Payload +} + +func (o *DescribeBalloonHintingBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewDescribeBalloonHintingDefault creates a DescribeBalloonHintingDefault with default headers values +func NewDescribeBalloonHintingDefault(code int) *DescribeBalloonHintingDefault { + return &DescribeBalloonHintingDefault{ + _statusCode: code, + } +} + +/* +DescribeBalloonHintingDefault describes a response with status code -1, with default header values. + +Internal Server Error +*/ +type DescribeBalloonHintingDefault struct { + _statusCode int + + Payload *models.Error +} + +// IsSuccess returns true when this describe balloon hinting default response has a 2xx status code +func (o *DescribeBalloonHintingDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this describe balloon hinting default response has a 3xx status code +func (o *DescribeBalloonHintingDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this describe balloon hinting default response has a 4xx status code +func (o *DescribeBalloonHintingDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this describe balloon hinting default response has a 5xx status code +func (o *DescribeBalloonHintingDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this describe balloon hinting default response a status code equal to that given +func (o *DescribeBalloonHintingDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the describe balloon hinting default response +func (o *DescribeBalloonHintingDefault) Code() int { + return o._statusCode +} + +func (o *DescribeBalloonHintingDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /balloon/hinting/status][%d] describeBalloonHinting default %s", o._statusCode, payload) +} + +func (o *DescribeBalloonHintingDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /balloon/hinting/status][%d] describeBalloonHinting default %s", o._statusCode, payload) +} + +func (o *DescribeBalloonHintingDefault) GetPayload() *models.Error { + return o.Payload +} + +func (o *DescribeBalloonHintingDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/get_memory_dirty_parameters.go b/packages/shared/pkg/fc/client/operations/get_memory_dirty_parameters.go deleted file mode 100644 index 2838328ece..0000000000 --- a/packages/shared/pkg/fc/client/operations/get_memory_dirty_parameters.go +++ /dev/null @@ -1,125 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package operations - -import ( - "context" - "net/http" - "time" - - "github.com/go-openapi/errors" - "github.com/go-openapi/runtime" - cr "github.com/go-openapi/runtime/client" - "github.com/go-openapi/strfmt" -) - -// NewGetMemoryDirtyParams creates a new GetMemoryDirtyParams object, -// with the default timeout for this client. -// -// Default values are not hydrated, since defaults are normally applied by the API server side. -// -// To enforce default values in parameter, use SetDefaults or WithDefaults. -func NewGetMemoryDirtyParams() *GetMemoryDirtyParams { - return &GetMemoryDirtyParams{ - timeout: cr.DefaultTimeout, - } -} - -// NewGetMemoryDirtyParamsWithTimeout creates a new GetMemoryDirtyParams object -// with the ability to set a timeout on a request. -func NewGetMemoryDirtyParamsWithTimeout(timeout time.Duration) *GetMemoryDirtyParams { - return &GetMemoryDirtyParams{ - timeout: timeout, - } -} - -// NewGetMemoryDirtyParamsWithContext creates a new GetMemoryDirtyParams object -// with the ability to set a context for a request. -func NewGetMemoryDirtyParamsWithContext(ctx context.Context) *GetMemoryDirtyParams { - return &GetMemoryDirtyParams{ - Context: ctx, - } -} - -// NewGetMemoryDirtyParamsWithHTTPClient creates a new GetMemoryDirtyParams object -// with the ability to set a custom HTTPClient for a request. -func NewGetMemoryDirtyParamsWithHTTPClient(client *http.Client) *GetMemoryDirtyParams { - return &GetMemoryDirtyParams{ - HTTPClient: client, - } -} - -/* -GetMemoryDirtyParams contains all the parameters to send to the API endpoint - - for the get memory dirty operation. - - Typically these are written to a http.Request. -*/ -type GetMemoryDirtyParams struct { - timeout time.Duration - Context context.Context - HTTPClient *http.Client -} - -// WithDefaults hydrates default values in the get memory dirty params (not the query body). -// -// All values with no default are reset to their zero value. -func (o *GetMemoryDirtyParams) WithDefaults() *GetMemoryDirtyParams { - o.SetDefaults() - return o -} - -// SetDefaults hydrates default values in the get memory dirty params (not the query body). -// -// All values with no default are reset to their zero value. -func (o *GetMemoryDirtyParams) SetDefaults() { - // no default values defined for this parameter -} - -// WithTimeout adds the timeout to the get memory dirty params -func (o *GetMemoryDirtyParams) WithTimeout(timeout time.Duration) *GetMemoryDirtyParams { - o.SetTimeout(timeout) - return o -} - -// SetTimeout adds the timeout to the get memory dirty params -func (o *GetMemoryDirtyParams) SetTimeout(timeout time.Duration) { - o.timeout = timeout -} - -// WithContext adds the context to the get memory dirty params -func (o *GetMemoryDirtyParams) WithContext(ctx context.Context) *GetMemoryDirtyParams { - o.SetContext(ctx) - return o -} - -// SetContext adds the context to the get memory dirty params -func (o *GetMemoryDirtyParams) SetContext(ctx context.Context) { - o.Context = ctx -} - -// WithHTTPClient adds the HTTPClient to the get memory dirty params -func (o *GetMemoryDirtyParams) WithHTTPClient(client *http.Client) *GetMemoryDirtyParams { - o.SetHTTPClient(client) - return o -} - -// SetHTTPClient adds the HTTPClient to the get memory dirty params -func (o *GetMemoryDirtyParams) SetHTTPClient(client *http.Client) { - o.HTTPClient = client -} - -// WriteToRequest writes these params to a swagger request -func (o *GetMemoryDirtyParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { - - if err := r.SetTimeout(o.timeout); err != nil { - return err - } - var res []error - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} diff --git a/packages/shared/pkg/fc/client/operations/get_memory_dirty_responses.go b/packages/shared/pkg/fc/client/operations/get_memory_dirty_responses.go deleted file mode 100644 index c3df69bfb4..0000000000 --- a/packages/shared/pkg/fc/client/operations/get_memory_dirty_responses.go +++ /dev/null @@ -1,261 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package operations - -import ( - "encoding/json" - stderrors "errors" - "fmt" - "io" - - "github.com/go-openapi/runtime" - "github.com/go-openapi/strfmt" - - "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" -) - -// GetMemoryDirtyReader is a Reader for the GetMemoryDirty structure. -type GetMemoryDirtyReader struct { - formats strfmt.Registry -} - -// ReadResponse reads a server response into the received o. -func (o *GetMemoryDirtyReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { - switch response.Code() { - case 200: - result := NewGetMemoryDirtyOK() - if err := result.readResponse(response, consumer, o.formats); err != nil { - return nil, err - } - return result, nil - case 400: - result := NewGetMemoryDirtyBadRequest() - if err := result.readResponse(response, consumer, o.formats); err != nil { - return nil, err - } - return nil, result - default: - result := NewGetMemoryDirtyDefault(response.Code()) - if err := result.readResponse(response, consumer, o.formats); err != nil { - return nil, err - } - if response.Code()/100 == 2 { - return result, nil - } - return nil, result - } -} - -// NewGetMemoryDirtyOK creates a GetMemoryDirtyOK with default headers values -func NewGetMemoryDirtyOK() *GetMemoryDirtyOK { - return &GetMemoryDirtyOK{} -} - -/* -GetMemoryDirtyOK describes a response with status code 200, with default header values. - -OK -*/ -type GetMemoryDirtyOK struct { - Payload *models.MemoryDirty -} - -// IsSuccess returns true when this get memory dirty o k response has a 2xx status code -func (o *GetMemoryDirtyOK) IsSuccess() bool { - return true -} - -// IsRedirect returns true when this get memory dirty o k response has a 3xx status code -func (o *GetMemoryDirtyOK) IsRedirect() bool { - return false -} - -// IsClientError returns true when this get memory dirty o k response has a 4xx status code -func (o *GetMemoryDirtyOK) IsClientError() bool { - return false -} - -// IsServerError returns true when this get memory dirty o k response has a 5xx status code -func (o *GetMemoryDirtyOK) IsServerError() bool { - return false -} - -// IsCode returns true when this get memory dirty o k response a status code equal to that given -func (o *GetMemoryDirtyOK) IsCode(code int) bool { - return code == 200 -} - -// Code gets the status code for the get memory dirty o k response -func (o *GetMemoryDirtyOK) Code() int { - return 200 -} - -func (o *GetMemoryDirtyOK) Error() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[GET /memory/dirty][%d] getMemoryDirtyOK %s", 200, payload) -} - -func (o *GetMemoryDirtyOK) String() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[GET /memory/dirty][%d] getMemoryDirtyOK %s", 200, payload) -} - -func (o *GetMemoryDirtyOK) GetPayload() *models.MemoryDirty { - return o.Payload -} - -func (o *GetMemoryDirtyOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { - - o.Payload = new(models.MemoryDirty) - - // response payload - if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { - return err - } - - return nil -} - -// NewGetMemoryDirtyBadRequest creates a GetMemoryDirtyBadRequest with default headers values -func NewGetMemoryDirtyBadRequest() *GetMemoryDirtyBadRequest { - return &GetMemoryDirtyBadRequest{} -} - -/* -GetMemoryDirtyBadRequest describes a response with status code 400, with default header values. - -The microVM is not paused. -*/ -type GetMemoryDirtyBadRequest struct { - Payload *models.Error -} - -// IsSuccess returns true when this get memory dirty bad request response has a 2xx status code -func (o *GetMemoryDirtyBadRequest) IsSuccess() bool { - return false -} - -// IsRedirect returns true when this get memory dirty bad request response has a 3xx status code -func (o *GetMemoryDirtyBadRequest) IsRedirect() bool { - return false -} - -// IsClientError returns true when this get memory dirty bad request response has a 4xx status code -func (o *GetMemoryDirtyBadRequest) IsClientError() bool { - return true -} - -// IsServerError returns true when this get memory dirty bad request response has a 5xx status code -func (o *GetMemoryDirtyBadRequest) IsServerError() bool { - return false -} - -// IsCode returns true when this get memory dirty bad request response a status code equal to that given -func (o *GetMemoryDirtyBadRequest) IsCode(code int) bool { - return code == 400 -} - -// Code gets the status code for the get memory dirty bad request response -func (o *GetMemoryDirtyBadRequest) Code() int { - return 400 -} - -func (o *GetMemoryDirtyBadRequest) Error() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[GET /memory/dirty][%d] getMemoryDirtyBadRequest %s", 400, payload) -} - -func (o *GetMemoryDirtyBadRequest) String() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[GET /memory/dirty][%d] getMemoryDirtyBadRequest %s", 400, payload) -} - -func (o *GetMemoryDirtyBadRequest) GetPayload() *models.Error { - return o.Payload -} - -func (o *GetMemoryDirtyBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { - - o.Payload = new(models.Error) - - // response payload - if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { - return err - } - - return nil -} - -// NewGetMemoryDirtyDefault creates a GetMemoryDirtyDefault with default headers values -func NewGetMemoryDirtyDefault(code int) *GetMemoryDirtyDefault { - return &GetMemoryDirtyDefault{ - _statusCode: code, - } -} - -/* -GetMemoryDirtyDefault describes a response with status code -1, with default header values. - -Internal server error -*/ -type GetMemoryDirtyDefault struct { - _statusCode int - - Payload *models.Error -} - -// IsSuccess returns true when this get memory dirty default response has a 2xx status code -func (o *GetMemoryDirtyDefault) IsSuccess() bool { - return o._statusCode/100 == 2 -} - -// IsRedirect returns true when this get memory dirty default response has a 3xx status code -func (o *GetMemoryDirtyDefault) IsRedirect() bool { - return o._statusCode/100 == 3 -} - -// IsClientError returns true when this get memory dirty default response has a 4xx status code -func (o *GetMemoryDirtyDefault) IsClientError() bool { - return o._statusCode/100 == 4 -} - -// IsServerError returns true when this get memory dirty default response has a 5xx status code -func (o *GetMemoryDirtyDefault) IsServerError() bool { - return o._statusCode/100 == 5 -} - -// IsCode returns true when this get memory dirty default response a status code equal to that given -func (o *GetMemoryDirtyDefault) IsCode(code int) bool { - return o._statusCode == code -} - -// Code gets the status code for the get memory dirty default response -func (o *GetMemoryDirtyDefault) Code() int { - return o._statusCode -} - -func (o *GetMemoryDirtyDefault) Error() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[GET /memory/dirty][%d] getMemoryDirty default %s", o._statusCode, payload) -} - -func (o *GetMemoryDirtyDefault) String() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[GET /memory/dirty][%d] getMemoryDirty default %s", o._statusCode, payload) -} - -func (o *GetMemoryDirtyDefault) GetPayload() *models.Error { - return o.Payload -} - -func (o *GetMemoryDirtyDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { - - o.Payload = new(models.Error) - - // response payload - if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { - return err - } - - return nil -} diff --git a/packages/shared/pkg/fc/client/operations/get_memory_hotplug_parameters.go b/packages/shared/pkg/fc/client/operations/get_memory_hotplug_parameters.go new file mode 100644 index 0000000000..225d750e0f --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/get_memory_hotplug_parameters.go @@ -0,0 +1,125 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewGetMemoryHotplugParams creates a new GetMemoryHotplugParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewGetMemoryHotplugParams() *GetMemoryHotplugParams { + return &GetMemoryHotplugParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewGetMemoryHotplugParamsWithTimeout creates a new GetMemoryHotplugParams object +// with the ability to set a timeout on a request. +func NewGetMemoryHotplugParamsWithTimeout(timeout time.Duration) *GetMemoryHotplugParams { + return &GetMemoryHotplugParams{ + timeout: timeout, + } +} + +// NewGetMemoryHotplugParamsWithContext creates a new GetMemoryHotplugParams object +// with the ability to set a context for a request. +func NewGetMemoryHotplugParamsWithContext(ctx context.Context) *GetMemoryHotplugParams { + return &GetMemoryHotplugParams{ + Context: ctx, + } +} + +// NewGetMemoryHotplugParamsWithHTTPClient creates a new GetMemoryHotplugParams object +// with the ability to set a custom HTTPClient for a request. +func NewGetMemoryHotplugParamsWithHTTPClient(client *http.Client) *GetMemoryHotplugParams { + return &GetMemoryHotplugParams{ + HTTPClient: client, + } +} + +/* +GetMemoryHotplugParams contains all the parameters to send to the API endpoint + + for the get memory hotplug operation. + + Typically these are written to a http.Request. +*/ +type GetMemoryHotplugParams struct { + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the get memory hotplug params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *GetMemoryHotplugParams) WithDefaults() *GetMemoryHotplugParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the get memory hotplug params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *GetMemoryHotplugParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the get memory hotplug params +func (o *GetMemoryHotplugParams) WithTimeout(timeout time.Duration) *GetMemoryHotplugParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the get memory hotplug params +func (o *GetMemoryHotplugParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the get memory hotplug params +func (o *GetMemoryHotplugParams) WithContext(ctx context.Context) *GetMemoryHotplugParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the get memory hotplug params +func (o *GetMemoryHotplugParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the get memory hotplug params +func (o *GetMemoryHotplugParams) WithHTTPClient(client *http.Client) *GetMemoryHotplugParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the get memory hotplug params +func (o *GetMemoryHotplugParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WriteToRequest writes these params to a swagger request +func (o *GetMemoryHotplugParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/get_memory_hotplug_responses.go b/packages/shared/pkg/fc/client/operations/get_memory_hotplug_responses.go new file mode 100644 index 0000000000..69799f3747 --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/get_memory_hotplug_responses.go @@ -0,0 +1,185 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "encoding/json" + stderrors "errors" + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// GetMemoryHotplugReader is a Reader for the GetMemoryHotplug structure. +type GetMemoryHotplugReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *GetMemoryHotplugReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 200: + result := NewGetMemoryHotplugOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewGetMemoryHotplugDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewGetMemoryHotplugOK creates a GetMemoryHotplugOK with default headers values +func NewGetMemoryHotplugOK() *GetMemoryHotplugOK { + return &GetMemoryHotplugOK{} +} + +/* +GetMemoryHotplugOK describes a response with status code 200, with default header values. + +OK +*/ +type GetMemoryHotplugOK struct { + Payload *models.MemoryHotplugStatus +} + +// IsSuccess returns true when this get memory hotplug o k response has a 2xx status code +func (o *GetMemoryHotplugOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this get memory hotplug o k response has a 3xx status code +func (o *GetMemoryHotplugOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this get memory hotplug o k response has a 4xx status code +func (o *GetMemoryHotplugOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this get memory hotplug o k response has a 5xx status code +func (o *GetMemoryHotplugOK) IsServerError() bool { + return false +} + +// IsCode returns true when this get memory hotplug o k response a status code equal to that given +func (o *GetMemoryHotplugOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the get memory hotplug o k response +func (o *GetMemoryHotplugOK) Code() int { + return 200 +} + +func (o *GetMemoryHotplugOK) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /hotplug/memory][%d] getMemoryHotplugOK %s", 200, payload) +} + +func (o *GetMemoryHotplugOK) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /hotplug/memory][%d] getMemoryHotplugOK %s", 200, payload) +} + +func (o *GetMemoryHotplugOK) GetPayload() *models.MemoryHotplugStatus { + return o.Payload +} + +func (o *GetMemoryHotplugOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.MemoryHotplugStatus) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewGetMemoryHotplugDefault creates a GetMemoryHotplugDefault with default headers values +func NewGetMemoryHotplugDefault(code int) *GetMemoryHotplugDefault { + return &GetMemoryHotplugDefault{ + _statusCode: code, + } +} + +/* +GetMemoryHotplugDefault describes a response with status code -1, with default header values. + +Internal server error +*/ +type GetMemoryHotplugDefault struct { + _statusCode int + + Payload *models.Error +} + +// IsSuccess returns true when this get memory hotplug default response has a 2xx status code +func (o *GetMemoryHotplugDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this get memory hotplug default response has a 3xx status code +func (o *GetMemoryHotplugDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this get memory hotplug default response has a 4xx status code +func (o *GetMemoryHotplugDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this get memory hotplug default response has a 5xx status code +func (o *GetMemoryHotplugDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this get memory hotplug default response a status code equal to that given +func (o *GetMemoryHotplugDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the get memory hotplug default response +func (o *GetMemoryHotplugDefault) Code() int { + return o._statusCode +} + +func (o *GetMemoryHotplugDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /hotplug/memory][%d] getMemoryHotplug default %s", o._statusCode, payload) +} + +func (o *GetMemoryHotplugDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /hotplug/memory][%d] getMemoryHotplug default %s", o._statusCode, payload) +} + +func (o *GetMemoryHotplugDefault) GetPayload() *models.Error { + return o.Payload +} + +func (o *GetMemoryHotplugDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/load_snapshot_parameters.go b/packages/shared/pkg/fc/client/operations/load_snapshot_parameters.go index 5067fee113..df1f826f67 100644 --- a/packages/shared/pkg/fc/client/operations/load_snapshot_parameters.go +++ b/packages/shared/pkg/fc/client/operations/load_snapshot_parameters.go @@ -62,7 +62,7 @@ type LoadSnapshotParams struct { /* Body. - The configuration used for loading a snaphot. + The configuration used for loading a snapshot. */ Body *models.SnapshotLoadParams diff --git a/packages/shared/pkg/fc/client/operations/operations_client.go b/packages/shared/pkg/fc/client/operations/operations_client.go index ead938ce2e..d29a2e37f1 100644 --- a/packages/shared/pkg/fc/client/operations/operations_client.go +++ b/packages/shared/pkg/fc/client/operations/operations_client.go @@ -57,6 +57,8 @@ type ClientService interface { DescribeBalloonConfig(params *DescribeBalloonConfigParams, opts ...ClientOption) (*DescribeBalloonConfigOK, error) + DescribeBalloonHinting(params *DescribeBalloonHintingParams, opts ...ClientOption) (*DescribeBalloonHintingOK, error) + DescribeBalloonStats(params *DescribeBalloonStatsParams, opts ...ClientOption) (*DescribeBalloonStatsOK, error) DescribeInstance(params *DescribeInstanceParams, opts ...ClientOption) (*DescribeInstanceOK, error) @@ -71,6 +73,8 @@ type ClientService interface { GetMemory(params *GetMemoryParams, opts ...ClientOption) (*GetMemoryOK, error) + GetMemoryHotplug(params *GetMemoryHotplugParams, opts ...ClientOption) (*GetMemoryHotplugOK, error) + GetMemoryMappings(params *GetMemoryMappingsParams, opts ...ClientOption) (*GetMemoryMappingsOK, error) GetMmds(params *GetMmdsParams, opts ...ClientOption) (*GetMmdsOK, error) @@ -87,6 +91,8 @@ type ClientService interface { PatchMachineConfiguration(params *PatchMachineConfigurationParams, opts ...ClientOption) (*PatchMachineConfigurationNoContent, error) + PatchMemoryHotplug(params *PatchMemoryHotplugParams, opts ...ClientOption) (*PatchMemoryHotplugNoContent, error) + PatchMmds(params *PatchMmdsParams, opts ...ClientOption) (*PatchMmdsNoContent, error) PatchVM(params *PatchVMParams, opts ...ClientOption) (*PatchVMNoContent, error) @@ -103,18 +109,28 @@ type ClientService interface { PutGuestNetworkInterfaceByID(params *PutGuestNetworkInterfaceByIDParams, opts ...ClientOption) (*PutGuestNetworkInterfaceByIDNoContent, error) + PutGuestPmemByID(params *PutGuestPmemByIDParams, opts ...ClientOption) (*PutGuestPmemByIDNoContent, error) + PutGuestVsock(params *PutGuestVsockParams, opts ...ClientOption) (*PutGuestVsockNoContent, error) PutLogger(params *PutLoggerParams, opts ...ClientOption) (*PutLoggerNoContent, error) PutMachineConfiguration(params *PutMachineConfigurationParams, opts ...ClientOption) (*PutMachineConfigurationNoContent, error) + PutMemoryHotplug(params *PutMemoryHotplugParams, opts ...ClientOption) (*PutMemoryHotplugNoContent, error) + PutMetrics(params *PutMetricsParams, opts ...ClientOption) (*PutMetricsNoContent, error) PutMmds(params *PutMmdsParams, opts ...ClientOption) (*PutMmdsNoContent, error) PutMmdsConfig(params *PutMmdsConfigParams, opts ...ClientOption) (*PutMmdsConfigNoContent, error) + PutSerialDevice(params *PutSerialDeviceParams, opts ...ClientOption) (*PutSerialDeviceNoContent, error) + + StartBalloonHinting(params *StartBalloonHintingParams, opts ...ClientOption) (*StartBalloonHintingOK, error) + + StopBalloonHinting(params *StopBalloonHintingParams, opts ...ClientOption) (*StopBalloonHintingOK, error) + SetTransport(transport runtime.ClientTransport) } @@ -246,6 +262,48 @@ func (a *Client) DescribeBalloonConfig(params *DescribeBalloonConfigParams, opts return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) } +/* +DescribeBalloonHinting returns the balloon hinting statistics only if enabled pre boot +*/ +func (a *Client) DescribeBalloonHinting(params *DescribeBalloonHintingParams, opts ...ClientOption) (*DescribeBalloonHintingOK, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewDescribeBalloonHintingParams() + } + op := &runtime.ClientOperation{ + ID: "describeBalloonHinting", + Method: "GET", + PathPattern: "/balloon/hinting/status", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &DescribeBalloonHintingReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*DescribeBalloonHintingOK) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*DescribeBalloonHintingDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + /* DescribeBalloonStats returns the latest balloon device statistics only if enabled pre boot */ @@ -548,6 +606,50 @@ func (a *Client) GetMemory(params *GetMemoryParams, opts ...ClientOption) (*GetM return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) } +/* +GetMemoryHotplug retrieves the status of the hotpluggable memory + +Reuturn the status of the hotpluggable memory. This can be used to follow the progress of the guest after a PATCH API. +*/ +func (a *Client) GetMemoryHotplug(params *GetMemoryHotplugParams, opts ...ClientOption) (*GetMemoryHotplugOK, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewGetMemoryHotplugParams() + } + op := &runtime.ClientOperation{ + ID: "getMemoryHotplug", + Method: "GET", + PathPattern: "/hotplug/memory", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &GetMemoryHotplugReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*GetMemoryHotplugOK) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*GetMemoryHotplugDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + /* GetMemoryMappings gets the memory mappings with skippable pages bitmap */ @@ -896,6 +998,50 @@ func (a *Client) PatchMachineConfiguration(params *PatchMachineConfigurationPara return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) } +/* +PatchMemoryHotplug updates the size of the hotpluggable memory region + +Updates the size of the hotpluggable memory region. The guest will plug and unplug memory to hit the requested memory. +*/ +func (a *Client) PatchMemoryHotplug(params *PatchMemoryHotplugParams, opts ...ClientOption) (*PatchMemoryHotplugNoContent, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewPatchMemoryHotplugParams() + } + op := &runtime.ClientOperation{ + ID: "patchMemoryHotplug", + Method: "PATCH", + PathPattern: "/hotplug/memory", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &PatchMemoryHotplugReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*PatchMemoryHotplugNoContent) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*PatchMemoryHotplugDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + /* PatchMmds updates the m m d s data store */ @@ -1246,6 +1392,50 @@ func (a *Client) PutGuestNetworkInterfaceByID(params *PutGuestNetworkInterfaceBy return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) } +/* +PutGuestPmemByID creates or updates a pmem device pre boot only + +Creates new pmem device with ID specified by id parameter. If a pmem device with the specified ID already exists, updates its state based on new input. Will fail if update is not possible. +*/ +func (a *Client) PutGuestPmemByID(params *PutGuestPmemByIDParams, opts ...ClientOption) (*PutGuestPmemByIDNoContent, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewPutGuestPmemByIDParams() + } + op := &runtime.ClientOperation{ + ID: "putGuestPmemByID", + Method: "PUT", + PathPattern: "/pmem/{id}", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &PutGuestPmemByIDReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*PutGuestPmemByIDNoContent) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*PutGuestPmemByIDDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + /* PutGuestVsock creates updates a vsock device pre boot only @@ -1376,6 +1566,50 @@ func (a *Client) PutMachineConfiguration(params *PutMachineConfigurationParams, return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) } +/* +PutMemoryHotplug configures the hotpluggable memory + +Configure the hotpluggable memory, which is a virtio-mem device, with an associated memory area that can be hot(un)plugged in the guest on demand using the PATCH API. +*/ +func (a *Client) PutMemoryHotplug(params *PutMemoryHotplugParams, opts ...ClientOption) (*PutMemoryHotplugNoContent, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewPutMemoryHotplugParams() + } + op := &runtime.ClientOperation{ + ID: "putMemoryHotplug", + Method: "PUT", + PathPattern: "/hotplug/memory", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &PutMemoryHotplugReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*PutMemoryHotplugNoContent) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*PutMemoryHotplugDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + /* PutMetrics initializes the metrics system by specifying a named pipe or a file for the metrics output */ @@ -1504,6 +1738,134 @@ func (a *Client) PutMmdsConfig(params *PutMmdsConfigParams, opts ...ClientOption return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) } +/* +PutSerialDevice configures the serial console + +Configure the serial console, which the guest can write its kernel logs to. Has no effect if the serial console is not also enabled on the guest kernel command line +*/ +func (a *Client) PutSerialDevice(params *PutSerialDeviceParams, opts ...ClientOption) (*PutSerialDeviceNoContent, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewPutSerialDeviceParams() + } + op := &runtime.ClientOperation{ + ID: "putSerialDevice", + Method: "PUT", + PathPattern: "/serial", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &PutSerialDeviceReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*PutSerialDeviceNoContent) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*PutSerialDeviceDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + +/* +StartBalloonHinting starts a free page hinting run only if enabled pre boot +*/ +func (a *Client) StartBalloonHinting(params *StartBalloonHintingParams, opts ...ClientOption) (*StartBalloonHintingOK, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewStartBalloonHintingParams() + } + op := &runtime.ClientOperation{ + ID: "startBalloonHinting", + Method: "PATCH", + PathPattern: "/balloon/hinting/start", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &StartBalloonHintingReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*StartBalloonHintingOK) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*StartBalloonHintingDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + +/* +StopBalloonHinting stops a free page hinting run only if enabled pre boot +*/ +func (a *Client) StopBalloonHinting(params *StopBalloonHintingParams, opts ...ClientOption) (*StopBalloonHintingOK, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewStopBalloonHintingParams() + } + op := &runtime.ClientOperation{ + ID: "stopBalloonHinting", + Method: "PATCH", + PathPattern: "/balloon/hinting/stop", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &StopBalloonHintingReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*StopBalloonHintingOK) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*StopBalloonHintingDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + // SetTransport changes the transport on the client func (a *Client) SetTransport(transport runtime.ClientTransport) { a.transport = transport diff --git a/packages/shared/pkg/fc/client/operations/patch_memory_hotplug_parameters.go b/packages/shared/pkg/fc/client/operations/patch_memory_hotplug_parameters.go new file mode 100644 index 0000000000..ef741faebb --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/patch_memory_hotplug_parameters.go @@ -0,0 +1,150 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// NewPatchMemoryHotplugParams creates a new PatchMemoryHotplugParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewPatchMemoryHotplugParams() *PatchMemoryHotplugParams { + return &PatchMemoryHotplugParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewPatchMemoryHotplugParamsWithTimeout creates a new PatchMemoryHotplugParams object +// with the ability to set a timeout on a request. +func NewPatchMemoryHotplugParamsWithTimeout(timeout time.Duration) *PatchMemoryHotplugParams { + return &PatchMemoryHotplugParams{ + timeout: timeout, + } +} + +// NewPatchMemoryHotplugParamsWithContext creates a new PatchMemoryHotplugParams object +// with the ability to set a context for a request. +func NewPatchMemoryHotplugParamsWithContext(ctx context.Context) *PatchMemoryHotplugParams { + return &PatchMemoryHotplugParams{ + Context: ctx, + } +} + +// NewPatchMemoryHotplugParamsWithHTTPClient creates a new PatchMemoryHotplugParams object +// with the ability to set a custom HTTPClient for a request. +func NewPatchMemoryHotplugParamsWithHTTPClient(client *http.Client) *PatchMemoryHotplugParams { + return &PatchMemoryHotplugParams{ + HTTPClient: client, + } +} + +/* +PatchMemoryHotplugParams contains all the parameters to send to the API endpoint + + for the patch memory hotplug operation. + + Typically these are written to a http.Request. +*/ +type PatchMemoryHotplugParams struct { + + /* Body. + + Hotpluggable memory size update + */ + Body *models.MemoryHotplugSizeUpdate + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the patch memory hotplug params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *PatchMemoryHotplugParams) WithDefaults() *PatchMemoryHotplugParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the patch memory hotplug params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *PatchMemoryHotplugParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the patch memory hotplug params +func (o *PatchMemoryHotplugParams) WithTimeout(timeout time.Duration) *PatchMemoryHotplugParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the patch memory hotplug params +func (o *PatchMemoryHotplugParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the patch memory hotplug params +func (o *PatchMemoryHotplugParams) WithContext(ctx context.Context) *PatchMemoryHotplugParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the patch memory hotplug params +func (o *PatchMemoryHotplugParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the patch memory hotplug params +func (o *PatchMemoryHotplugParams) WithHTTPClient(client *http.Client) *PatchMemoryHotplugParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the patch memory hotplug params +func (o *PatchMemoryHotplugParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithBody adds the body to the patch memory hotplug params +func (o *PatchMemoryHotplugParams) WithBody(body *models.MemoryHotplugSizeUpdate) *PatchMemoryHotplugParams { + o.SetBody(body) + return o +} + +// SetBody adds the body to the patch memory hotplug params +func (o *PatchMemoryHotplugParams) SetBody(body *models.MemoryHotplugSizeUpdate) { + o.Body = body +} + +// WriteToRequest writes these params to a swagger request +func (o *PatchMemoryHotplugParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + if o.Body != nil { + if err := r.SetBodyParam(o.Body); err != nil { + return err + } + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/patch_memory_hotplug_responses.go b/packages/shared/pkg/fc/client/operations/patch_memory_hotplug_responses.go new file mode 100644 index 0000000000..2f4f3808a0 --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/patch_memory_hotplug_responses.go @@ -0,0 +1,171 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "encoding/json" + stderrors "errors" + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// PatchMemoryHotplugReader is a Reader for the PatchMemoryHotplug structure. +type PatchMemoryHotplugReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *PatchMemoryHotplugReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 204: + result := NewPatchMemoryHotplugNoContent() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewPatchMemoryHotplugDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewPatchMemoryHotplugNoContent creates a PatchMemoryHotplugNoContent with default headers values +func NewPatchMemoryHotplugNoContent() *PatchMemoryHotplugNoContent { + return &PatchMemoryHotplugNoContent{} +} + +/* +PatchMemoryHotplugNoContent describes a response with status code 204, with default header values. + +Hotpluggable memory configured +*/ +type PatchMemoryHotplugNoContent struct { +} + +// IsSuccess returns true when this patch memory hotplug no content response has a 2xx status code +func (o *PatchMemoryHotplugNoContent) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this patch memory hotplug no content response has a 3xx status code +func (o *PatchMemoryHotplugNoContent) IsRedirect() bool { + return false +} + +// IsClientError returns true when this patch memory hotplug no content response has a 4xx status code +func (o *PatchMemoryHotplugNoContent) IsClientError() bool { + return false +} + +// IsServerError returns true when this patch memory hotplug no content response has a 5xx status code +func (o *PatchMemoryHotplugNoContent) IsServerError() bool { + return false +} + +// IsCode returns true when this patch memory hotplug no content response a status code equal to that given +func (o *PatchMemoryHotplugNoContent) IsCode(code int) bool { + return code == 204 +} + +// Code gets the status code for the patch memory hotplug no content response +func (o *PatchMemoryHotplugNoContent) Code() int { + return 204 +} + +func (o *PatchMemoryHotplugNoContent) Error() string { + return fmt.Sprintf("[PATCH /hotplug/memory][%d] patchMemoryHotplugNoContent", 204) +} + +func (o *PatchMemoryHotplugNoContent) String() string { + return fmt.Sprintf("[PATCH /hotplug/memory][%d] patchMemoryHotplugNoContent", 204) +} + +func (o *PatchMemoryHotplugNoContent) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +// NewPatchMemoryHotplugDefault creates a PatchMemoryHotplugDefault with default headers values +func NewPatchMemoryHotplugDefault(code int) *PatchMemoryHotplugDefault { + return &PatchMemoryHotplugDefault{ + _statusCode: code, + } +} + +/* +PatchMemoryHotplugDefault describes a response with status code -1, with default header values. + +Internal server error +*/ +type PatchMemoryHotplugDefault struct { + _statusCode int + + Payload *models.Error +} + +// IsSuccess returns true when this patch memory hotplug default response has a 2xx status code +func (o *PatchMemoryHotplugDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this patch memory hotplug default response has a 3xx status code +func (o *PatchMemoryHotplugDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this patch memory hotplug default response has a 4xx status code +func (o *PatchMemoryHotplugDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this patch memory hotplug default response has a 5xx status code +func (o *PatchMemoryHotplugDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this patch memory hotplug default response a status code equal to that given +func (o *PatchMemoryHotplugDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the patch memory hotplug default response +func (o *PatchMemoryHotplugDefault) Code() int { + return o._statusCode +} + +func (o *PatchMemoryHotplugDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PATCH /hotplug/memory][%d] patchMemoryHotplug default %s", o._statusCode, payload) +} + +func (o *PatchMemoryHotplugDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PATCH /hotplug/memory][%d] patchMemoryHotplug default %s", o._statusCode, payload) +} + +func (o *PatchMemoryHotplugDefault) GetPayload() *models.Error { + return o.Payload +} + +func (o *PatchMemoryHotplugDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/put_guest_pmem_by_id_parameters.go b/packages/shared/pkg/fc/client/operations/put_guest_pmem_by_id_parameters.go new file mode 100644 index 0000000000..77c2d0911c --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/put_guest_pmem_by_id_parameters.go @@ -0,0 +1,172 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// NewPutGuestPmemByIDParams creates a new PutGuestPmemByIDParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewPutGuestPmemByIDParams() *PutGuestPmemByIDParams { + return &PutGuestPmemByIDParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewPutGuestPmemByIDParamsWithTimeout creates a new PutGuestPmemByIDParams object +// with the ability to set a timeout on a request. +func NewPutGuestPmemByIDParamsWithTimeout(timeout time.Duration) *PutGuestPmemByIDParams { + return &PutGuestPmemByIDParams{ + timeout: timeout, + } +} + +// NewPutGuestPmemByIDParamsWithContext creates a new PutGuestPmemByIDParams object +// with the ability to set a context for a request. +func NewPutGuestPmemByIDParamsWithContext(ctx context.Context) *PutGuestPmemByIDParams { + return &PutGuestPmemByIDParams{ + Context: ctx, + } +} + +// NewPutGuestPmemByIDParamsWithHTTPClient creates a new PutGuestPmemByIDParams object +// with the ability to set a custom HTTPClient for a request. +func NewPutGuestPmemByIDParamsWithHTTPClient(client *http.Client) *PutGuestPmemByIDParams { + return &PutGuestPmemByIDParams{ + HTTPClient: client, + } +} + +/* +PutGuestPmemByIDParams contains all the parameters to send to the API endpoint + + for the put guest pmem by ID operation. + + Typically these are written to a http.Request. +*/ +type PutGuestPmemByIDParams struct { + + /* Body. + + Guest pmem device properties + */ + Body *models.Pmem + + /* ID. + + The id of the guest pmem device + */ + ID string + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the put guest pmem by ID params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *PutGuestPmemByIDParams) WithDefaults() *PutGuestPmemByIDParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the put guest pmem by ID params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *PutGuestPmemByIDParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the put guest pmem by ID params +func (o *PutGuestPmemByIDParams) WithTimeout(timeout time.Duration) *PutGuestPmemByIDParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the put guest pmem by ID params +func (o *PutGuestPmemByIDParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the put guest pmem by ID params +func (o *PutGuestPmemByIDParams) WithContext(ctx context.Context) *PutGuestPmemByIDParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the put guest pmem by ID params +func (o *PutGuestPmemByIDParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the put guest pmem by ID params +func (o *PutGuestPmemByIDParams) WithHTTPClient(client *http.Client) *PutGuestPmemByIDParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the put guest pmem by ID params +func (o *PutGuestPmemByIDParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithBody adds the body to the put guest pmem by ID params +func (o *PutGuestPmemByIDParams) WithBody(body *models.Pmem) *PutGuestPmemByIDParams { + o.SetBody(body) + return o +} + +// SetBody adds the body to the put guest pmem by ID params +func (o *PutGuestPmemByIDParams) SetBody(body *models.Pmem) { + o.Body = body +} + +// WithID adds the id to the put guest pmem by ID params +func (o *PutGuestPmemByIDParams) WithID(id string) *PutGuestPmemByIDParams { + o.SetID(id) + return o +} + +// SetID adds the id to the put guest pmem by ID params +func (o *PutGuestPmemByIDParams) SetID(id string) { + o.ID = id +} + +// WriteToRequest writes these params to a swagger request +func (o *PutGuestPmemByIDParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + if o.Body != nil { + if err := r.SetBodyParam(o.Body); err != nil { + return err + } + } + + // path param id + if err := r.SetPathParam("id", o.ID); err != nil { + return err + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/put_guest_pmem_by_id_responses.go b/packages/shared/pkg/fc/client/operations/put_guest_pmem_by_id_responses.go new file mode 100644 index 0000000000..106440e244 --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/put_guest_pmem_by_id_responses.go @@ -0,0 +1,247 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "encoding/json" + stderrors "errors" + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// PutGuestPmemByIDReader is a Reader for the PutGuestPmemByID structure. +type PutGuestPmemByIDReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *PutGuestPmemByIDReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 204: + result := NewPutGuestPmemByIDNoContent() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + case 400: + result := NewPutGuestPmemByIDBadRequest() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + default: + result := NewPutGuestPmemByIDDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewPutGuestPmemByIDNoContent creates a PutGuestPmemByIDNoContent with default headers values +func NewPutGuestPmemByIDNoContent() *PutGuestPmemByIDNoContent { + return &PutGuestPmemByIDNoContent{} +} + +/* +PutGuestPmemByIDNoContent describes a response with status code 204, with default header values. + +Pmem device is created/updated +*/ +type PutGuestPmemByIDNoContent struct { +} + +// IsSuccess returns true when this put guest pmem by Id no content response has a 2xx status code +func (o *PutGuestPmemByIDNoContent) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this put guest pmem by Id no content response has a 3xx status code +func (o *PutGuestPmemByIDNoContent) IsRedirect() bool { + return false +} + +// IsClientError returns true when this put guest pmem by Id no content response has a 4xx status code +func (o *PutGuestPmemByIDNoContent) IsClientError() bool { + return false +} + +// IsServerError returns true when this put guest pmem by Id no content response has a 5xx status code +func (o *PutGuestPmemByIDNoContent) IsServerError() bool { + return false +} + +// IsCode returns true when this put guest pmem by Id no content response a status code equal to that given +func (o *PutGuestPmemByIDNoContent) IsCode(code int) bool { + return code == 204 +} + +// Code gets the status code for the put guest pmem by Id no content response +func (o *PutGuestPmemByIDNoContent) Code() int { + return 204 +} + +func (o *PutGuestPmemByIDNoContent) Error() string { + return fmt.Sprintf("[PUT /pmem/{id}][%d] putGuestPmemByIdNoContent", 204) +} + +func (o *PutGuestPmemByIDNoContent) String() string { + return fmt.Sprintf("[PUT /pmem/{id}][%d] putGuestPmemByIdNoContent", 204) +} + +func (o *PutGuestPmemByIDNoContent) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +// NewPutGuestPmemByIDBadRequest creates a PutGuestPmemByIDBadRequest with default headers values +func NewPutGuestPmemByIDBadRequest() *PutGuestPmemByIDBadRequest { + return &PutGuestPmemByIDBadRequest{} +} + +/* +PutGuestPmemByIDBadRequest describes a response with status code 400, with default header values. + +Pmem device cannot be created/updated due to bad input +*/ +type PutGuestPmemByIDBadRequest struct { + Payload *models.Error +} + +// IsSuccess returns true when this put guest pmem by Id bad request response has a 2xx status code +func (o *PutGuestPmemByIDBadRequest) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this put guest pmem by Id bad request response has a 3xx status code +func (o *PutGuestPmemByIDBadRequest) IsRedirect() bool { + return false +} + +// IsClientError returns true when this put guest pmem by Id bad request response has a 4xx status code +func (o *PutGuestPmemByIDBadRequest) IsClientError() bool { + return true +} + +// IsServerError returns true when this put guest pmem by Id bad request response has a 5xx status code +func (o *PutGuestPmemByIDBadRequest) IsServerError() bool { + return false +} + +// IsCode returns true when this put guest pmem by Id bad request response a status code equal to that given +func (o *PutGuestPmemByIDBadRequest) IsCode(code int) bool { + return code == 400 +} + +// Code gets the status code for the put guest pmem by Id bad request response +func (o *PutGuestPmemByIDBadRequest) Code() int { + return 400 +} + +func (o *PutGuestPmemByIDBadRequest) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PUT /pmem/{id}][%d] putGuestPmemByIdBadRequest %s", 400, payload) +} + +func (o *PutGuestPmemByIDBadRequest) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PUT /pmem/{id}][%d] putGuestPmemByIdBadRequest %s", 400, payload) +} + +func (o *PutGuestPmemByIDBadRequest) GetPayload() *models.Error { + return o.Payload +} + +func (o *PutGuestPmemByIDBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewPutGuestPmemByIDDefault creates a PutGuestPmemByIDDefault with default headers values +func NewPutGuestPmemByIDDefault(code int) *PutGuestPmemByIDDefault { + return &PutGuestPmemByIDDefault{ + _statusCode: code, + } +} + +/* +PutGuestPmemByIDDefault describes a response with status code -1, with default header values. + +Internal server error. +*/ +type PutGuestPmemByIDDefault struct { + _statusCode int + + Payload *models.Error +} + +// IsSuccess returns true when this put guest pmem by ID default response has a 2xx status code +func (o *PutGuestPmemByIDDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this put guest pmem by ID default response has a 3xx status code +func (o *PutGuestPmemByIDDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this put guest pmem by ID default response has a 4xx status code +func (o *PutGuestPmemByIDDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this put guest pmem by ID default response has a 5xx status code +func (o *PutGuestPmemByIDDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this put guest pmem by ID default response a status code equal to that given +func (o *PutGuestPmemByIDDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the put guest pmem by ID default response +func (o *PutGuestPmemByIDDefault) Code() int { + return o._statusCode +} + +func (o *PutGuestPmemByIDDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PUT /pmem/{id}][%d] putGuestPmemByID default %s", o._statusCode, payload) +} + +func (o *PutGuestPmemByIDDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PUT /pmem/{id}][%d] putGuestPmemByID default %s", o._statusCode, payload) +} + +func (o *PutGuestPmemByIDDefault) GetPayload() *models.Error { + return o.Payload +} + +func (o *PutGuestPmemByIDDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/put_memory_hotplug_parameters.go b/packages/shared/pkg/fc/client/operations/put_memory_hotplug_parameters.go new file mode 100644 index 0000000000..5a1e21323b --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/put_memory_hotplug_parameters.go @@ -0,0 +1,150 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// NewPutMemoryHotplugParams creates a new PutMemoryHotplugParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewPutMemoryHotplugParams() *PutMemoryHotplugParams { + return &PutMemoryHotplugParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewPutMemoryHotplugParamsWithTimeout creates a new PutMemoryHotplugParams object +// with the ability to set a timeout on a request. +func NewPutMemoryHotplugParamsWithTimeout(timeout time.Duration) *PutMemoryHotplugParams { + return &PutMemoryHotplugParams{ + timeout: timeout, + } +} + +// NewPutMemoryHotplugParamsWithContext creates a new PutMemoryHotplugParams object +// with the ability to set a context for a request. +func NewPutMemoryHotplugParamsWithContext(ctx context.Context) *PutMemoryHotplugParams { + return &PutMemoryHotplugParams{ + Context: ctx, + } +} + +// NewPutMemoryHotplugParamsWithHTTPClient creates a new PutMemoryHotplugParams object +// with the ability to set a custom HTTPClient for a request. +func NewPutMemoryHotplugParamsWithHTTPClient(client *http.Client) *PutMemoryHotplugParams { + return &PutMemoryHotplugParams{ + HTTPClient: client, + } +} + +/* +PutMemoryHotplugParams contains all the parameters to send to the API endpoint + + for the put memory hotplug operation. + + Typically these are written to a http.Request. +*/ +type PutMemoryHotplugParams struct { + + /* Body. + + Hotpluggable memory configuration + */ + Body *models.MemoryHotplugConfig + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the put memory hotplug params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *PutMemoryHotplugParams) WithDefaults() *PutMemoryHotplugParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the put memory hotplug params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *PutMemoryHotplugParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the put memory hotplug params +func (o *PutMemoryHotplugParams) WithTimeout(timeout time.Duration) *PutMemoryHotplugParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the put memory hotplug params +func (o *PutMemoryHotplugParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the put memory hotplug params +func (o *PutMemoryHotplugParams) WithContext(ctx context.Context) *PutMemoryHotplugParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the put memory hotplug params +func (o *PutMemoryHotplugParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the put memory hotplug params +func (o *PutMemoryHotplugParams) WithHTTPClient(client *http.Client) *PutMemoryHotplugParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the put memory hotplug params +func (o *PutMemoryHotplugParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithBody adds the body to the put memory hotplug params +func (o *PutMemoryHotplugParams) WithBody(body *models.MemoryHotplugConfig) *PutMemoryHotplugParams { + o.SetBody(body) + return o +} + +// SetBody adds the body to the put memory hotplug params +func (o *PutMemoryHotplugParams) SetBody(body *models.MemoryHotplugConfig) { + o.Body = body +} + +// WriteToRequest writes these params to a swagger request +func (o *PutMemoryHotplugParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + if o.Body != nil { + if err := r.SetBodyParam(o.Body); err != nil { + return err + } + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/put_memory_hotplug_responses.go b/packages/shared/pkg/fc/client/operations/put_memory_hotplug_responses.go new file mode 100644 index 0000000000..7bb3ea5061 --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/put_memory_hotplug_responses.go @@ -0,0 +1,171 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "encoding/json" + stderrors "errors" + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// PutMemoryHotplugReader is a Reader for the PutMemoryHotplug structure. +type PutMemoryHotplugReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *PutMemoryHotplugReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 204: + result := NewPutMemoryHotplugNoContent() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewPutMemoryHotplugDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewPutMemoryHotplugNoContent creates a PutMemoryHotplugNoContent with default headers values +func NewPutMemoryHotplugNoContent() *PutMemoryHotplugNoContent { + return &PutMemoryHotplugNoContent{} +} + +/* +PutMemoryHotplugNoContent describes a response with status code 204, with default header values. + +Hotpluggable memory configured +*/ +type PutMemoryHotplugNoContent struct { +} + +// IsSuccess returns true when this put memory hotplug no content response has a 2xx status code +func (o *PutMemoryHotplugNoContent) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this put memory hotplug no content response has a 3xx status code +func (o *PutMemoryHotplugNoContent) IsRedirect() bool { + return false +} + +// IsClientError returns true when this put memory hotplug no content response has a 4xx status code +func (o *PutMemoryHotplugNoContent) IsClientError() bool { + return false +} + +// IsServerError returns true when this put memory hotplug no content response has a 5xx status code +func (o *PutMemoryHotplugNoContent) IsServerError() bool { + return false +} + +// IsCode returns true when this put memory hotplug no content response a status code equal to that given +func (o *PutMemoryHotplugNoContent) IsCode(code int) bool { + return code == 204 +} + +// Code gets the status code for the put memory hotplug no content response +func (o *PutMemoryHotplugNoContent) Code() int { + return 204 +} + +func (o *PutMemoryHotplugNoContent) Error() string { + return fmt.Sprintf("[PUT /hotplug/memory][%d] putMemoryHotplugNoContent", 204) +} + +func (o *PutMemoryHotplugNoContent) String() string { + return fmt.Sprintf("[PUT /hotplug/memory][%d] putMemoryHotplugNoContent", 204) +} + +func (o *PutMemoryHotplugNoContent) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +// NewPutMemoryHotplugDefault creates a PutMemoryHotplugDefault with default headers values +func NewPutMemoryHotplugDefault(code int) *PutMemoryHotplugDefault { + return &PutMemoryHotplugDefault{ + _statusCode: code, + } +} + +/* +PutMemoryHotplugDefault describes a response with status code -1, with default header values. + +Internal server error +*/ +type PutMemoryHotplugDefault struct { + _statusCode int + + Payload *models.Error +} + +// IsSuccess returns true when this put memory hotplug default response has a 2xx status code +func (o *PutMemoryHotplugDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this put memory hotplug default response has a 3xx status code +func (o *PutMemoryHotplugDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this put memory hotplug default response has a 4xx status code +func (o *PutMemoryHotplugDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this put memory hotplug default response has a 5xx status code +func (o *PutMemoryHotplugDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this put memory hotplug default response a status code equal to that given +func (o *PutMemoryHotplugDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the put memory hotplug default response +func (o *PutMemoryHotplugDefault) Code() int { + return o._statusCode +} + +func (o *PutMemoryHotplugDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PUT /hotplug/memory][%d] putMemoryHotplug default %s", o._statusCode, payload) +} + +func (o *PutMemoryHotplugDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PUT /hotplug/memory][%d] putMemoryHotplug default %s", o._statusCode, payload) +} + +func (o *PutMemoryHotplugDefault) GetPayload() *models.Error { + return o.Payload +} + +func (o *PutMemoryHotplugDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/put_serial_device_parameters.go b/packages/shared/pkg/fc/client/operations/put_serial_device_parameters.go new file mode 100644 index 0000000000..dbbd288e92 --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/put_serial_device_parameters.go @@ -0,0 +1,150 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// NewPutSerialDeviceParams creates a new PutSerialDeviceParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewPutSerialDeviceParams() *PutSerialDeviceParams { + return &PutSerialDeviceParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewPutSerialDeviceParamsWithTimeout creates a new PutSerialDeviceParams object +// with the ability to set a timeout on a request. +func NewPutSerialDeviceParamsWithTimeout(timeout time.Duration) *PutSerialDeviceParams { + return &PutSerialDeviceParams{ + timeout: timeout, + } +} + +// NewPutSerialDeviceParamsWithContext creates a new PutSerialDeviceParams object +// with the ability to set a context for a request. +func NewPutSerialDeviceParamsWithContext(ctx context.Context) *PutSerialDeviceParams { + return &PutSerialDeviceParams{ + Context: ctx, + } +} + +// NewPutSerialDeviceParamsWithHTTPClient creates a new PutSerialDeviceParams object +// with the ability to set a custom HTTPClient for a request. +func NewPutSerialDeviceParamsWithHTTPClient(client *http.Client) *PutSerialDeviceParams { + return &PutSerialDeviceParams{ + HTTPClient: client, + } +} + +/* +PutSerialDeviceParams contains all the parameters to send to the API endpoint + + for the put serial device operation. + + Typically these are written to a http.Request. +*/ +type PutSerialDeviceParams struct { + + /* Body. + + Serial console properties + */ + Body *models.SerialDevice + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the put serial device params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *PutSerialDeviceParams) WithDefaults() *PutSerialDeviceParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the put serial device params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *PutSerialDeviceParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the put serial device params +func (o *PutSerialDeviceParams) WithTimeout(timeout time.Duration) *PutSerialDeviceParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the put serial device params +func (o *PutSerialDeviceParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the put serial device params +func (o *PutSerialDeviceParams) WithContext(ctx context.Context) *PutSerialDeviceParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the put serial device params +func (o *PutSerialDeviceParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the put serial device params +func (o *PutSerialDeviceParams) WithHTTPClient(client *http.Client) *PutSerialDeviceParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the put serial device params +func (o *PutSerialDeviceParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithBody adds the body to the put serial device params +func (o *PutSerialDeviceParams) WithBody(body *models.SerialDevice) *PutSerialDeviceParams { + o.SetBody(body) + return o +} + +// SetBody adds the body to the put serial device params +func (o *PutSerialDeviceParams) SetBody(body *models.SerialDevice) { + o.Body = body +} + +// WriteToRequest writes these params to a swagger request +func (o *PutSerialDeviceParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + if o.Body != nil { + if err := r.SetBodyParam(o.Body); err != nil { + return err + } + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/put_serial_device_responses.go b/packages/shared/pkg/fc/client/operations/put_serial_device_responses.go new file mode 100644 index 0000000000..ac09a76f57 --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/put_serial_device_responses.go @@ -0,0 +1,171 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "encoding/json" + stderrors "errors" + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// PutSerialDeviceReader is a Reader for the PutSerialDevice structure. +type PutSerialDeviceReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *PutSerialDeviceReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 204: + result := NewPutSerialDeviceNoContent() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewPutSerialDeviceDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewPutSerialDeviceNoContent creates a PutSerialDeviceNoContent with default headers values +func NewPutSerialDeviceNoContent() *PutSerialDeviceNoContent { + return &PutSerialDeviceNoContent{} +} + +/* +PutSerialDeviceNoContent describes a response with status code 204, with default header values. + +Serial device configured +*/ +type PutSerialDeviceNoContent struct { +} + +// IsSuccess returns true when this put serial device no content response has a 2xx status code +func (o *PutSerialDeviceNoContent) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this put serial device no content response has a 3xx status code +func (o *PutSerialDeviceNoContent) IsRedirect() bool { + return false +} + +// IsClientError returns true when this put serial device no content response has a 4xx status code +func (o *PutSerialDeviceNoContent) IsClientError() bool { + return false +} + +// IsServerError returns true when this put serial device no content response has a 5xx status code +func (o *PutSerialDeviceNoContent) IsServerError() bool { + return false +} + +// IsCode returns true when this put serial device no content response a status code equal to that given +func (o *PutSerialDeviceNoContent) IsCode(code int) bool { + return code == 204 +} + +// Code gets the status code for the put serial device no content response +func (o *PutSerialDeviceNoContent) Code() int { + return 204 +} + +func (o *PutSerialDeviceNoContent) Error() string { + return fmt.Sprintf("[PUT /serial][%d] putSerialDeviceNoContent", 204) +} + +func (o *PutSerialDeviceNoContent) String() string { + return fmt.Sprintf("[PUT /serial][%d] putSerialDeviceNoContent", 204) +} + +func (o *PutSerialDeviceNoContent) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +// NewPutSerialDeviceDefault creates a PutSerialDeviceDefault with default headers values +func NewPutSerialDeviceDefault(code int) *PutSerialDeviceDefault { + return &PutSerialDeviceDefault{ + _statusCode: code, + } +} + +/* +PutSerialDeviceDefault describes a response with status code -1, with default header values. + +Internal server error +*/ +type PutSerialDeviceDefault struct { + _statusCode int + + Payload *models.Error +} + +// IsSuccess returns true when this put serial device default response has a 2xx status code +func (o *PutSerialDeviceDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this put serial device default response has a 3xx status code +func (o *PutSerialDeviceDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this put serial device default response has a 4xx status code +func (o *PutSerialDeviceDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this put serial device default response has a 5xx status code +func (o *PutSerialDeviceDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this put serial device default response a status code equal to that given +func (o *PutSerialDeviceDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the put serial device default response +func (o *PutSerialDeviceDefault) Code() int { + return o._statusCode +} + +func (o *PutSerialDeviceDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PUT /serial][%d] putSerialDevice default %s", o._statusCode, payload) +} + +func (o *PutSerialDeviceDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PUT /serial][%d] putSerialDevice default %s", o._statusCode, payload) +} + +func (o *PutSerialDeviceDefault) GetPayload() *models.Error { + return o.Payload +} + +func (o *PutSerialDeviceDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/start_balloon_hinting_parameters.go b/packages/shared/pkg/fc/client/operations/start_balloon_hinting_parameters.go new file mode 100644 index 0000000000..e11fe84d31 --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/start_balloon_hinting_parameters.go @@ -0,0 +1,150 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// NewStartBalloonHintingParams creates a new StartBalloonHintingParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewStartBalloonHintingParams() *StartBalloonHintingParams { + return &StartBalloonHintingParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewStartBalloonHintingParamsWithTimeout creates a new StartBalloonHintingParams object +// with the ability to set a timeout on a request. +func NewStartBalloonHintingParamsWithTimeout(timeout time.Duration) *StartBalloonHintingParams { + return &StartBalloonHintingParams{ + timeout: timeout, + } +} + +// NewStartBalloonHintingParamsWithContext creates a new StartBalloonHintingParams object +// with the ability to set a context for a request. +func NewStartBalloonHintingParamsWithContext(ctx context.Context) *StartBalloonHintingParams { + return &StartBalloonHintingParams{ + Context: ctx, + } +} + +// NewStartBalloonHintingParamsWithHTTPClient creates a new StartBalloonHintingParams object +// with the ability to set a custom HTTPClient for a request. +func NewStartBalloonHintingParamsWithHTTPClient(client *http.Client) *StartBalloonHintingParams { + return &StartBalloonHintingParams{ + HTTPClient: client, + } +} + +/* +StartBalloonHintingParams contains all the parameters to send to the API endpoint + + for the start balloon hinting operation. + + Typically these are written to a http.Request. +*/ +type StartBalloonHintingParams struct { + + /* Body. + + When the device completes the hinting whether we should automatically ack this. + */ + Body *models.BalloonStartCmd + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the start balloon hinting params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *StartBalloonHintingParams) WithDefaults() *StartBalloonHintingParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the start balloon hinting params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *StartBalloonHintingParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the start balloon hinting params +func (o *StartBalloonHintingParams) WithTimeout(timeout time.Duration) *StartBalloonHintingParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the start balloon hinting params +func (o *StartBalloonHintingParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the start balloon hinting params +func (o *StartBalloonHintingParams) WithContext(ctx context.Context) *StartBalloonHintingParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the start balloon hinting params +func (o *StartBalloonHintingParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the start balloon hinting params +func (o *StartBalloonHintingParams) WithHTTPClient(client *http.Client) *StartBalloonHintingParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the start balloon hinting params +func (o *StartBalloonHintingParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithBody adds the body to the start balloon hinting params +func (o *StartBalloonHintingParams) WithBody(body *models.BalloonStartCmd) *StartBalloonHintingParams { + o.SetBody(body) + return o +} + +// SetBody adds the body to the start balloon hinting params +func (o *StartBalloonHintingParams) SetBody(body *models.BalloonStartCmd) { + o.Body = body +} + +// WriteToRequest writes these params to a swagger request +func (o *StartBalloonHintingParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + if o.Body != nil { + if err := r.SetBodyParam(o.Body); err != nil { + return err + } + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/start_balloon_hinting_responses.go b/packages/shared/pkg/fc/client/operations/start_balloon_hinting_responses.go new file mode 100644 index 0000000000..ab0c595034 --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/start_balloon_hinting_responses.go @@ -0,0 +1,247 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "encoding/json" + stderrors "errors" + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// StartBalloonHintingReader is a Reader for the StartBalloonHinting structure. +type StartBalloonHintingReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *StartBalloonHintingReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 200: + result := NewStartBalloonHintingOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + case 400: + result := NewStartBalloonHintingBadRequest() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + default: + result := NewStartBalloonHintingDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewStartBalloonHintingOK creates a StartBalloonHintingOK with default headers values +func NewStartBalloonHintingOK() *StartBalloonHintingOK { + return &StartBalloonHintingOK{} +} + +/* +StartBalloonHintingOK describes a response with status code 200, with default header values. + +Free page hinting run started. +*/ +type StartBalloonHintingOK struct { +} + +// IsSuccess returns true when this start balloon hinting o k response has a 2xx status code +func (o *StartBalloonHintingOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this start balloon hinting o k response has a 3xx status code +func (o *StartBalloonHintingOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this start balloon hinting o k response has a 4xx status code +func (o *StartBalloonHintingOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this start balloon hinting o k response has a 5xx status code +func (o *StartBalloonHintingOK) IsServerError() bool { + return false +} + +// IsCode returns true when this start balloon hinting o k response a status code equal to that given +func (o *StartBalloonHintingOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the start balloon hinting o k response +func (o *StartBalloonHintingOK) Code() int { + return 200 +} + +func (o *StartBalloonHintingOK) Error() string { + return fmt.Sprintf("[PATCH /balloon/hinting/start][%d] startBalloonHintingOK", 200) +} + +func (o *StartBalloonHintingOK) String() string { + return fmt.Sprintf("[PATCH /balloon/hinting/start][%d] startBalloonHintingOK", 200) +} + +func (o *StartBalloonHintingOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +// NewStartBalloonHintingBadRequest creates a StartBalloonHintingBadRequest with default headers values +func NewStartBalloonHintingBadRequest() *StartBalloonHintingBadRequest { + return &StartBalloonHintingBadRequest{} +} + +/* +StartBalloonHintingBadRequest describes a response with status code 400, with default header values. + +The balloon free hinting was not enabled when the device was configured. +*/ +type StartBalloonHintingBadRequest struct { + Payload *models.Error +} + +// IsSuccess returns true when this start balloon hinting bad request response has a 2xx status code +func (o *StartBalloonHintingBadRequest) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this start balloon hinting bad request response has a 3xx status code +func (o *StartBalloonHintingBadRequest) IsRedirect() bool { + return false +} + +// IsClientError returns true when this start balloon hinting bad request response has a 4xx status code +func (o *StartBalloonHintingBadRequest) IsClientError() bool { + return true +} + +// IsServerError returns true when this start balloon hinting bad request response has a 5xx status code +func (o *StartBalloonHintingBadRequest) IsServerError() bool { + return false +} + +// IsCode returns true when this start balloon hinting bad request response a status code equal to that given +func (o *StartBalloonHintingBadRequest) IsCode(code int) bool { + return code == 400 +} + +// Code gets the status code for the start balloon hinting bad request response +func (o *StartBalloonHintingBadRequest) Code() int { + return 400 +} + +func (o *StartBalloonHintingBadRequest) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PATCH /balloon/hinting/start][%d] startBalloonHintingBadRequest %s", 400, payload) +} + +func (o *StartBalloonHintingBadRequest) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PATCH /balloon/hinting/start][%d] startBalloonHintingBadRequest %s", 400, payload) +} + +func (o *StartBalloonHintingBadRequest) GetPayload() *models.Error { + return o.Payload +} + +func (o *StartBalloonHintingBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewStartBalloonHintingDefault creates a StartBalloonHintingDefault with default headers values +func NewStartBalloonHintingDefault(code int) *StartBalloonHintingDefault { + return &StartBalloonHintingDefault{ + _statusCode: code, + } +} + +/* +StartBalloonHintingDefault describes a response with status code -1, with default header values. + +Internal Server Error +*/ +type StartBalloonHintingDefault struct { + _statusCode int + + Payload *models.Error +} + +// IsSuccess returns true when this start balloon hinting default response has a 2xx status code +func (o *StartBalloonHintingDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this start balloon hinting default response has a 3xx status code +func (o *StartBalloonHintingDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this start balloon hinting default response has a 4xx status code +func (o *StartBalloonHintingDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this start balloon hinting default response has a 5xx status code +func (o *StartBalloonHintingDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this start balloon hinting default response a status code equal to that given +func (o *StartBalloonHintingDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the start balloon hinting default response +func (o *StartBalloonHintingDefault) Code() int { + return o._statusCode +} + +func (o *StartBalloonHintingDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PATCH /balloon/hinting/start][%d] startBalloonHinting default %s", o._statusCode, payload) +} + +func (o *StartBalloonHintingDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PATCH /balloon/hinting/start][%d] startBalloonHinting default %s", o._statusCode, payload) +} + +func (o *StartBalloonHintingDefault) GetPayload() *models.Error { + return o.Payload +} + +func (o *StartBalloonHintingDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/stop_balloon_hinting_parameters.go b/packages/shared/pkg/fc/client/operations/stop_balloon_hinting_parameters.go new file mode 100644 index 0000000000..8cb0986560 --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/stop_balloon_hinting_parameters.go @@ -0,0 +1,125 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewStopBalloonHintingParams creates a new StopBalloonHintingParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewStopBalloonHintingParams() *StopBalloonHintingParams { + return &StopBalloonHintingParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewStopBalloonHintingParamsWithTimeout creates a new StopBalloonHintingParams object +// with the ability to set a timeout on a request. +func NewStopBalloonHintingParamsWithTimeout(timeout time.Duration) *StopBalloonHintingParams { + return &StopBalloonHintingParams{ + timeout: timeout, + } +} + +// NewStopBalloonHintingParamsWithContext creates a new StopBalloonHintingParams object +// with the ability to set a context for a request. +func NewStopBalloonHintingParamsWithContext(ctx context.Context) *StopBalloonHintingParams { + return &StopBalloonHintingParams{ + Context: ctx, + } +} + +// NewStopBalloonHintingParamsWithHTTPClient creates a new StopBalloonHintingParams object +// with the ability to set a custom HTTPClient for a request. +func NewStopBalloonHintingParamsWithHTTPClient(client *http.Client) *StopBalloonHintingParams { + return &StopBalloonHintingParams{ + HTTPClient: client, + } +} + +/* +StopBalloonHintingParams contains all the parameters to send to the API endpoint + + for the stop balloon hinting operation. + + Typically these are written to a http.Request. +*/ +type StopBalloonHintingParams struct { + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the stop balloon hinting params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *StopBalloonHintingParams) WithDefaults() *StopBalloonHintingParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the stop balloon hinting params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *StopBalloonHintingParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the stop balloon hinting params +func (o *StopBalloonHintingParams) WithTimeout(timeout time.Duration) *StopBalloonHintingParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the stop balloon hinting params +func (o *StopBalloonHintingParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the stop balloon hinting params +func (o *StopBalloonHintingParams) WithContext(ctx context.Context) *StopBalloonHintingParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the stop balloon hinting params +func (o *StopBalloonHintingParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the stop balloon hinting params +func (o *StopBalloonHintingParams) WithHTTPClient(client *http.Client) *StopBalloonHintingParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the stop balloon hinting params +func (o *StopBalloonHintingParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WriteToRequest writes these params to a swagger request +func (o *StopBalloonHintingParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/stop_balloon_hinting_responses.go b/packages/shared/pkg/fc/client/operations/stop_balloon_hinting_responses.go new file mode 100644 index 0000000000..d08561d4fd --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/stop_balloon_hinting_responses.go @@ -0,0 +1,247 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "encoding/json" + stderrors "errors" + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// StopBalloonHintingReader is a Reader for the StopBalloonHinting structure. +type StopBalloonHintingReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *StopBalloonHintingReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 200: + result := NewStopBalloonHintingOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + case 400: + result := NewStopBalloonHintingBadRequest() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + default: + result := NewStopBalloonHintingDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewStopBalloonHintingOK creates a StopBalloonHintingOK with default headers values +func NewStopBalloonHintingOK() *StopBalloonHintingOK { + return &StopBalloonHintingOK{} +} + +/* +StopBalloonHintingOK describes a response with status code 200, with default header values. + +Free page hinting run stopped. +*/ +type StopBalloonHintingOK struct { +} + +// IsSuccess returns true when this stop balloon hinting o k response has a 2xx status code +func (o *StopBalloonHintingOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this stop balloon hinting o k response has a 3xx status code +func (o *StopBalloonHintingOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this stop balloon hinting o k response has a 4xx status code +func (o *StopBalloonHintingOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this stop balloon hinting o k response has a 5xx status code +func (o *StopBalloonHintingOK) IsServerError() bool { + return false +} + +// IsCode returns true when this stop balloon hinting o k response a status code equal to that given +func (o *StopBalloonHintingOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the stop balloon hinting o k response +func (o *StopBalloonHintingOK) Code() int { + return 200 +} + +func (o *StopBalloonHintingOK) Error() string { + return fmt.Sprintf("[PATCH /balloon/hinting/stop][%d] stopBalloonHintingOK", 200) +} + +func (o *StopBalloonHintingOK) String() string { + return fmt.Sprintf("[PATCH /balloon/hinting/stop][%d] stopBalloonHintingOK", 200) +} + +func (o *StopBalloonHintingOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +// NewStopBalloonHintingBadRequest creates a StopBalloonHintingBadRequest with default headers values +func NewStopBalloonHintingBadRequest() *StopBalloonHintingBadRequest { + return &StopBalloonHintingBadRequest{} +} + +/* +StopBalloonHintingBadRequest describes a response with status code 400, with default header values. + +The balloon free hinting was not enabled when the device was configured. +*/ +type StopBalloonHintingBadRequest struct { + Payload *models.Error +} + +// IsSuccess returns true when this stop balloon hinting bad request response has a 2xx status code +func (o *StopBalloonHintingBadRequest) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this stop balloon hinting bad request response has a 3xx status code +func (o *StopBalloonHintingBadRequest) IsRedirect() bool { + return false +} + +// IsClientError returns true when this stop balloon hinting bad request response has a 4xx status code +func (o *StopBalloonHintingBadRequest) IsClientError() bool { + return true +} + +// IsServerError returns true when this stop balloon hinting bad request response has a 5xx status code +func (o *StopBalloonHintingBadRequest) IsServerError() bool { + return false +} + +// IsCode returns true when this stop balloon hinting bad request response a status code equal to that given +func (o *StopBalloonHintingBadRequest) IsCode(code int) bool { + return code == 400 +} + +// Code gets the status code for the stop balloon hinting bad request response +func (o *StopBalloonHintingBadRequest) Code() int { + return 400 +} + +func (o *StopBalloonHintingBadRequest) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PATCH /balloon/hinting/stop][%d] stopBalloonHintingBadRequest %s", 400, payload) +} + +func (o *StopBalloonHintingBadRequest) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PATCH /balloon/hinting/stop][%d] stopBalloonHintingBadRequest %s", 400, payload) +} + +func (o *StopBalloonHintingBadRequest) GetPayload() *models.Error { + return o.Payload +} + +func (o *StopBalloonHintingBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewStopBalloonHintingDefault creates a StopBalloonHintingDefault with default headers values +func NewStopBalloonHintingDefault(code int) *StopBalloonHintingDefault { + return &StopBalloonHintingDefault{ + _statusCode: code, + } +} + +/* +StopBalloonHintingDefault describes a response with status code -1, with default header values. + +Internal Server Error +*/ +type StopBalloonHintingDefault struct { + _statusCode int + + Payload *models.Error +} + +// IsSuccess returns true when this stop balloon hinting default response has a 2xx status code +func (o *StopBalloonHintingDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this stop balloon hinting default response has a 3xx status code +func (o *StopBalloonHintingDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this stop balloon hinting default response has a 4xx status code +func (o *StopBalloonHintingDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this stop balloon hinting default response has a 5xx status code +func (o *StopBalloonHintingDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this stop balloon hinting default response a status code equal to that given +func (o *StopBalloonHintingDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the stop balloon hinting default response +func (o *StopBalloonHintingDefault) Code() int { + return o._statusCode +} + +func (o *StopBalloonHintingDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PATCH /balloon/hinting/stop][%d] stopBalloonHinting default %s", o._statusCode, payload) +} + +func (o *StopBalloonHintingDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PATCH /balloon/hinting/stop][%d] stopBalloonHinting default %s", o._statusCode, payload) +} + +func (o *StopBalloonHintingDefault) GetPayload() *models.Error { + return o.Payload +} + +func (o *StopBalloonHintingDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} diff --git a/packages/shared/pkg/fc/firecracker.yml b/packages/shared/pkg/fc/firecracker.yml index 61ed7a61fd..af1cfb806c 100644 --- a/packages/shared/pkg/fc/firecracker.yml +++ b/packages/shared/pkg/fc/firecracker.yml @@ -5,7 +5,7 @@ info: The API is accessible through HTTP calls on specific URLs carrying JSON modeled data. The transport medium is a Unix Domain Socket. - version: 1.12.1 + version: 1.14.1 termsOfService: "" contact: email: "firecracker-maintainers@amazon.com" @@ -169,6 +169,63 @@ paths: schema: $ref: "#/definitions/Error" + /balloon/hinting/start: + patch: + summary: Starts a free page hinting run only if enabled pre-boot. + operationId: startBalloonHinting + parameters: + - name: body + in: body + description: When the device completes the hinting whether we should automatically ack this. + required: false + schema: + $ref: "#/definitions/BalloonStartCmd" + responses: + 200: + description: Free page hinting run started. + 400: + description: The balloon free hinting was not enabled when the device was configured. + schema: + $ref: "#/definitions/Error" + default: + description: Internal Server Error + schema: + $ref: "#/definitions/Error" + + /balloon/hinting/status: + get: + summary: Returns the balloon hinting statistics, only if enabled pre-boot. + operationId: describeBalloonHinting + responses: + 200: + description: The balloon free page hinting statistics + schema: + $ref: "#/definitions/BalloonHintingStatus" + 400: + description: The balloon free hinting was not enabled when the device was configured. + schema: + $ref: "#/definitions/Error" + default: + description: Internal Server Error + schema: + $ref: "#/definitions/Error" + + /balloon/hinting/stop: + patch: + summary: Stops a free page hinting run only if enabled pre-boot. + operationId: stopBalloonHinting + responses: + 200: + description: Free page hinting run stopped. + 400: + description: The balloon free hinting was not enabled when the device was configured. + schema: + $ref: "#/definitions/Error" + default: + description: Internal Server Error + schema: + $ref: "#/definitions/Error" + /boot-source: put: summary: Creates or updates the boot source. Pre-boot only. @@ -282,6 +339,38 @@ paths: schema: $ref: "#/definitions/Error" + /pmem/{id}: + put: + summary: Creates or updates a pmem device. Pre-boot only. + description: + Creates new pmem device with ID specified by id parameter. + If a pmem device with the specified ID already exists, updates its state based on new input. + Will fail if update is not possible. + operationId: putGuestPmemByID + parameters: + - name: id + in: path + description: The id of the guest pmem device + required: true + type: string + - name: body + in: body + description: Guest pmem device properties + required: true + schema: + $ref: "#/definitions/Pmem" + responses: + 204: + description: Pmem device is created/updated + 400: + description: Pmem device cannot be created/updated due to bad input + schema: + $ref: "#/definitions/Error" + default: + description: Internal server error. + schema: + $ref: "#/definitions/Error" + /logger: put: summary: Initializes the logger by specifying a named pipe or a file for the logs output. @@ -450,6 +539,7 @@ paths: description: The MMDS data store JSON. schema: type: object + additionalProperties: true 404: description: The MMDS data store content can not be found. schema: @@ -506,6 +596,84 @@ paths: schema: $ref: "#/definitions/Error" + /serial: + put: + summary: Configures the serial console + operationId: putSerialDevice + description: + Configure the serial console, which the guest can write its kernel logs to. Has no effect if + the serial console is not also enabled on the guest kernel command line + parameters: + - name: body + in: body + description: Serial console properties + required: true + schema: + $ref: "#/definitions/SerialDevice" + responses: + 204: + description: Serial device configured + default: + description: Internal server error + schema: + $ref: "#/definitions/Error" + + /hotplug/memory: + put: + summary: Configures the hotpluggable memory + operationId: putMemoryHotplug + description: + Configure the hotpluggable memory, which is a virtio-mem device, with an associated memory area + that can be hot(un)plugged in the guest on demand using the PATCH API. + parameters: + - name: body + in: body + description: Hotpluggable memory configuration + required: true + schema: + $ref: "#/definitions/MemoryHotplugConfig" + responses: + 204: + description: Hotpluggable memory configured + default: + description: Internal server error + schema: + $ref: "#/definitions/Error" + patch: + summary: Updates the size of the hotpluggable memory region + operationId: patchMemoryHotplug + description: + Updates the size of the hotpluggable memory region. The guest will plug and unplug memory to + hit the requested memory. + parameters: + - name: body + in: body + description: Hotpluggable memory size update + required: true + schema: + $ref: "#/definitions/MemoryHotplugSizeUpdate" + responses: + 204: + description: Hotpluggable memory configured + default: + description: Internal server error + schema: + $ref: "#/definitions/Error" + get: + summary: Retrieves the status of the hotpluggable memory + operationId: getMemoryHotplug + description: + Reuturn the status of the hotpluggable memory. This can be used to follow the progress of the guest + after a PATCH API. + responses: + 200: + description: OK + schema: + $ref: "#/definitions/MemoryHotplugStatus" + default: + description: Internal server error + schema: + $ref: "#/definitions/Error" /network-interfaces/{iface_id}: put: @@ -575,7 +743,7 @@ paths: parameters: - name: body in: body - description: The configuration used for creating a snaphot. + description: The configuration used for creating a snapshot. required: true schema: $ref: "#/definitions/SnapshotCreateParams" @@ -602,7 +770,7 @@ paths: parameters: - name: body in: body - description: The configuration used for loading a snaphot. + description: The configuration used for loading a snapshot. required: true schema: $ref: "#/definitions/SnapshotLoadParams" @@ -767,6 +935,12 @@ definitions: stats_polling_interval_s: type: integer description: Interval in seconds between refreshing statistics. A non-zero value will enable the statistics. Defaults to 0. + free_page_hinting: + type: boolean + description: Whether the free page hinting feature is enabled. + free_page_reporting: + type: boolean + description: Whether the free page reporting feature is enabled. BalloonUpdate: type: object @@ -841,6 +1015,53 @@ definitions: description: The number of failed hugetlb page allocations in the guest. type: integer format: int64 + oom_kill: + description: OOM killer invocations, indicating critical memory pressure. + type: integer + format: int64 + alloc_stall: + description: Counter of Allocation enter a slow path to gain more memory page. The reclaim/scan metrics can reveal what is actually happening. + type: integer + format: int64 + async_scan: + description: Amount of memory scanned asynchronously. + type: integer + format: int64 + direct_scan: + description: Amount of memory scanned directly. + type: integer + format: int64 + async_reclaim: + description: Amount of memory reclaimed asynchronously. + type: integer + format: int64 + direct_reclaim: + description: Amount of memory reclaimed directly. + type: integer + format: int64 + + BalloonStartCmd: + type: object + description: + Command used to start a free page hinting run. + properties: + acknowledge_on_stop: + description: If Firecracker should automatically acknowledge when the guest submits a done cmd. + type: boolean + + BalloonHintingStatus: + type: object + description: + Describes the free page hinting status. + required: + - host_cmd + properties: + host_cmd: + description: The last command issued by the host. + type: integer + guest_cmd: + description: The last command provided by the guest. + type: integer BalloonStatsUpdate: type: object @@ -893,21 +1114,119 @@ definitions: The CPU configuration template defines a set of bit maps as modifiers of flags accessed by register to be disabled/enabled for the microvm. properties: + kvm_capabilities: + type: array + description: A collection of KVM capabilities to be added or removed (both x86_64 and aarch64) + items: + type: string + description: KVM capability as a numeric string. Prefix with '!' to remove capability. Example "121" (add) or "!121" (remove) cpuid_modifiers: - type: object - description: A collection of CPUIDs to be modified. (x86_64) + type: array + description: A collection of CPUID leaf modifiers (x86_64 only) + items: + $ref: "#/definitions/CpuidLeafModifier" msr_modifiers: - type: object - description: A collection of model specific registers to be modified. (x86_64) + type: array + description: A collection of model specific register modifiers (x86_64 only) + items: + $ref: "#/definitions/MsrModifier" reg_modifiers: - type: object - description: A collection of registers to be modified. (aarch64) + type: array + description: A collection of register modifiers (aarch64 only) + items: + $ref: "#/definitions/ArmRegisterModifier" vcpu_features: - type: object - description: A collection of vcpu features to be modified. (aarch64) - kvm_capabilities: - type: object - description: A collection of kvm capabilities to be modified. (aarch64) + type: array + description: A collection of vCPU features to be modified (aarch64 only) + items: + $ref: "#/definitions/VcpuFeatures" + + CpuidLeafModifier: + type: object + description: Modifier for a CPUID leaf and subleaf (x86_64) + required: + - leaf + - subleaf + - flags + - modifiers + properties: + leaf: + type: string + description: CPUID leaf index as hex, binary, or decimal string (e.g., "0x0", "0b0", "0")) + subleaf: + type: string + description: CPUID subleaf index as hex, binary, or decimal string (e.g., "0x0", "0b0", "0") + flags: + type: integer + format: int32 + description: KVM feature flags for this leaf-subleaf + modifiers: + type: array + description: Register modifiers for this CPUID leaf + items: + $ref: "#/definitions/CpuidRegisterModifier" + + CpuidRegisterModifier: + type: object + description: Modifier for a specific CPUID register within a leaf (x86_64) + required: + - register + - bitmap + properties: + register: + type: string + description: Target CPUID register name + enum: + - eax + - ebx + - ecx + - edx + bitmap: + type: string + description: 32-bit bitmap string defining which bits to modify. Format is "0b" followed by 32 characters where '0' = clear bit, '1' = set bit, 'x' = don't modify. Example "0b00000000000000000000000000000001" or "0bxxxxxxxxxxxxxxxxxxxxxxxxxxxx0001" + + MsrModifier: + type: object + description: Modifier for a model specific register (x86_64) + required: + - addr + - bitmap + properties: + addr: + type: string + description: 32-bit MSR address as hex, binary, or decimal string (e.g., "0x10a", "0b100001010", "266") + bitmap: + type: string + description: 64-bit bitmap string defining which bits to modify. Format is "0b" followed by 64 characters where '0' = clear bit, '1' = set bit, 'x' = don't modify. Underscores can be used for readability. Example "0b0000000000000000000000000000000000000000000000000000000000000001" + + ArmRegisterModifier: + type: object + description: Modifier for an ARM register (aarch64) + required: + - addr + - bitmap + properties: + addr: + type: string + description: 64-bit register address as hex, binary, or decimal string (e.g., "0x0", "0b0", "0") + bitmap: + type: string + description: 128-bit bitmap string defining which bits to modify. Format is "0b" followed by up to 128 characters where '0' = clear bit, '1' = set bit, 'x' = don't modify. Underscores can be used for readability. Example "0b0000000000000000000000000000000000000000000000000000000000000001" + + VcpuFeatures: + type: object + description: vCPU feature modifier (aarch64) + required: + - index + - bitmap + properties: + index: + type: integer + format: int32 + description: Index in the kvm_vcpu_init.features array + bitmap: + type: string + description: 32-bit bitmap string defining which bits to modify. Format is "0b" followed by 32 characters where '0' = clear bit, '1' = set bit, 'x' = don't modify. Example "0b00000000000000000000000001100000" Drive: type: object @@ -936,7 +1255,7 @@ definitions: is_read_only: type: boolean description: - Is block read only. + Is block read only. This field is required for virtio-block config and should be omitted for vhost-user-block configuration. path_on_host: type: string @@ -961,6 +1280,30 @@ definitions: Path to the socket of vhost-user-block backend. This field is required for vhost-user-block config should be omitted for virtio-block configuration. + Pmem: + type: object + required: + - id + - path_on_host + properties: + id: + type: string + description: + Identificator for this device. + path_on_host: + type: string + description: + Host level path for the virtio-pmem device to use as a backing file. + root_device: + type: boolean + description: + Flag to make this device be the root device for VM boot. + Setting this flag will fail if there is another device configured to be a root device already. + read_only: + type: boolean + description: + Flag to map backing file in read-only mode. + Error: type: object properties: @@ -989,6 +1332,8 @@ definitions: $ref: "#/definitions/MachineConfiguration" metrics: $ref: "#/definitions/Metrics" + memory-hotplug: + $ref: "#/definitions/MemoryHotplugConfig" mmds-config: $ref: "#/definitions/MmdsConfig" network-interfaces: @@ -996,6 +1341,11 @@ definitions: description: Configurations for all net devices. items: $ref: "#/definitions/NetworkInterface" + pmem: + type: array + description: Configurations for all pmem devices. + items: + $ref: "#/definitions/Pmem" vsock: $ref: "#/definitions/Vsock" entropy: @@ -1239,11 +1589,18 @@ definitions: format: "169.254.([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-4]).([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])" default: "169.254.169.254" description: A valid IPv4 link-local address. + imds_compat: + type: boolean + description: + MMDS operates compatibly with EC2 IMDS (i.e. responds "text/plain" + content regardless of Accept header in requests). + default: false MmdsContentsObject: type: object description: Describes the contents of MMDS in JSON format. + additionalProperties: true NetworkInterface: type: object @@ -1316,7 +1673,10 @@ definitions: properties: mem_file_path: type: string - description: Path to the file that will contain the guest memory. + description: + Path to the file that will contain the guest memory. It is optional. + In case that a user doesn't provide a path, they are responsible to + ensure they store the microVM's memory state via external means. snapshot_path: type: string description: Path to the file that will contain the microVM state. @@ -1358,7 +1718,11 @@ definitions: enable_diff_snapshots: type: boolean description: - Enable support for incremental (diff) snapshots by tracking dirty guest pages. + (Deprecated) Enable dirty page tracking to improve space efficiency of diff snapshots + track_dirty_pages: + type: boolean + description: + Enable dirty page tracking to improve space efficiency of diff snapshots mem_file_path: type: string description: @@ -1438,6 +1802,66 @@ definitions: rate_limiter: $ref: "#/definitions/RateLimiter" + SerialDevice: + type: object + description: + The configuration of the serial device + properties: + serial_out_path: + type: string + description: Path to a file or named pipe on the host to which serial output should be written. + + MemoryHotplugConfig: + type: object + description: + The configuration of the hotpluggable memory device (virtio-mem) + properties: + total_size_mib: + type: integer + description: Total size of the hotpluggable memory in MiB. + slot_size_mib: + type: integer + default: 128 + minimum: 128 + description: Slot size for the hotpluggable memory in MiB. This will determine the granularity of + hot-plug memory from the host. Refer to the device documentation on how to tune this value. + block_size_mib: + type: integer + default: 2 + minimum: 2 + description: (Logical) Block size for the hotpluggable memory in MiB. This will determine the logical + granularity of hot-plug memory for the guest. Refer to the device documentation on how to tune this value. + + MemoryHotplugSizeUpdate: + type: object + description: + An update to the size of the hotpluggable memory region. + properties: + requested_size_mib: + type: integer + description: New target region size. + + MemoryHotplugStatus: + type: object + description: + The status of the hotpluggable memory device (virtio-mem) + properties: + total_size_mib: + type: integer + description: Total size of the hotpluggable memory in MiB. + slot_size_mib: + type: integer + description: Slot size for the hotpluggable memory in MiB. + block_size_mib: + type: integer + description: (Logical) Block size for the hotpluggable memory in MiB. + plugged_size_mib: + type: integer + description: Plugged size for the hotpluggable memory in MiB. + requested_size_mib: + type: integer + description: Requested size for the hotpluggable memory in MiB. + FirecrackerVersion: type: object description: diff --git a/packages/shared/pkg/fc/models/arm_register_modifier.go b/packages/shared/pkg/fc/models/arm_register_modifier.go new file mode 100644 index 0000000000..1c6c79119f --- /dev/null +++ b/packages/shared/pkg/fc/models/arm_register_modifier.go @@ -0,0 +1,85 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// ArmRegisterModifier Modifier for an ARM register (aarch64) +// +// swagger:model ArmRegisterModifier +type ArmRegisterModifier struct { + + // 64-bit register address as hex, binary, or decimal string (e.g., "0x0", "0b0", "0") + // Required: true + Addr *string `json:"addr"` + + // 128-bit bitmap string defining which bits to modify. Format is "0b" followed by up to 128 characters where '0' = clear bit, '1' = set bit, 'x' = don't modify. Underscores can be used for readability. Example "0b0000000000000000000000000000000000000000000000000000000000000001" + // Required: true + Bitmap *string `json:"bitmap"` +} + +// Validate validates this arm register modifier +func (m *ArmRegisterModifier) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateAddr(formats); err != nil { + res = append(res, err) + } + + if err := m.validateBitmap(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ArmRegisterModifier) validateAddr(formats strfmt.Registry) error { + + if err := validate.Required("addr", "body", m.Addr); err != nil { + return err + } + + return nil +} + +func (m *ArmRegisterModifier) validateBitmap(formats strfmt.Registry) error { + + if err := validate.Required("bitmap", "body", m.Bitmap); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this arm register modifier based on context it is used +func (m *ArmRegisterModifier) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *ArmRegisterModifier) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ArmRegisterModifier) UnmarshalBinary(b []byte) error { + var res ArmRegisterModifier + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/packages/shared/pkg/fc/models/balloon.go b/packages/shared/pkg/fc/models/balloon.go index ce73d06a8b..b209c8f1cd 100644 --- a/packages/shared/pkg/fc/models/balloon.go +++ b/packages/shared/pkg/fc/models/balloon.go @@ -24,6 +24,12 @@ type Balloon struct { // Required: true DeflateOnOom *bool `json:"deflate_on_oom"` + // Whether the free page hinting feature is enabled. + FreePageHinting bool `json:"free_page_hinting,omitempty"` + + // Whether the free page reporting feature is enabled. + FreePageReporting bool `json:"free_page_reporting,omitempty"` + // Interval in seconds between refreshing statistics. A non-zero value will enable the statistics. Defaults to 0. StatsPollingIntervals int64 `json:"stats_polling_interval_s,omitempty"` } diff --git a/packages/shared/pkg/fc/models/balloon_hinting_status.go b/packages/shared/pkg/fc/models/balloon_hinting_status.go new file mode 100644 index 0000000000..4b7c0b456e --- /dev/null +++ b/packages/shared/pkg/fc/models/balloon_hinting_status.go @@ -0,0 +1,71 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// BalloonHintingStatus Describes the free page hinting status. +// +// swagger:model BalloonHintingStatus +type BalloonHintingStatus struct { + + // The last command provided by the guest. + GuestCmd int64 `json:"guest_cmd,omitempty"` + + // The last command issued by the host. + // Required: true + HostCmd *int64 `json:"host_cmd"` +} + +// Validate validates this balloon hinting status +func (m *BalloonHintingStatus) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateHostCmd(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *BalloonHintingStatus) validateHostCmd(formats strfmt.Registry) error { + + if err := validate.Required("host_cmd", "body", m.HostCmd); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this balloon hinting status based on context it is used +func (m *BalloonHintingStatus) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *BalloonHintingStatus) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *BalloonHintingStatus) UnmarshalBinary(b []byte) error { + var res BalloonHintingStatus + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/packages/shared/pkg/fc/models/balloon_start_cmd.go b/packages/shared/pkg/fc/models/balloon_start_cmd.go new file mode 100644 index 0000000000..d0b38243cc --- /dev/null +++ b/packages/shared/pkg/fc/models/balloon_start_cmd.go @@ -0,0 +1,47 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// BalloonStartCmd Command used to start a free page hinting run. +// +// swagger:model BalloonStartCmd +type BalloonStartCmd struct { + + // If Firecracker should automatically acknowledge when the guest submits a done cmd. + AcknowledgeOnStop bool `json:"acknowledge_on_stop,omitempty"` +} + +// Validate validates this balloon start cmd +func (m *BalloonStartCmd) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this balloon start cmd based on context it is used +func (m *BalloonStartCmd) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *BalloonStartCmd) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *BalloonStartCmd) UnmarshalBinary(b []byte) error { + var res BalloonStartCmd + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/packages/shared/pkg/fc/models/balloon_stats.go b/packages/shared/pkg/fc/models/balloon_stats.go index e32e9d8e58..a80c80867a 100644 --- a/packages/shared/pkg/fc/models/balloon_stats.go +++ b/packages/shared/pkg/fc/models/balloon_stats.go @@ -24,9 +24,24 @@ type BalloonStats struct { // Required: true ActualPages *int64 `json:"actual_pages"` + // Counter of Allocation enter a slow path to gain more memory page. The reclaim/scan metrics can reveal what is actually happening. + AllocStall int64 `json:"alloc_stall,omitempty"` + + // Amount of memory reclaimed asynchronously. + AsyncReclaim int64 `json:"async_reclaim,omitempty"` + + // Amount of memory scanned asynchronously. + AsyncScan int64 `json:"async_scan,omitempty"` + // An estimate of how much memory is available (in bytes) for starting new applications, without pushing the system to swap. AvailableMemory int64 `json:"available_memory,omitempty"` + // Amount of memory reclaimed directly. + DirectReclaim int64 `json:"direct_reclaim,omitempty"` + + // Amount of memory scanned directly. + DirectScan int64 `json:"direct_scan,omitempty"` + // The amount of memory, in bytes, that can be quickly reclaimed without additional I/O. Typically these pages are used for caching files from disk. DiskCaches int64 `json:"disk_caches,omitempty"` @@ -45,6 +60,9 @@ type BalloonStats struct { // The number of minor page faults that have occurred. MinorFaults int64 `json:"minor_faults,omitempty"` + // OOM killer invocations, indicating critical memory pressure. + OomKill int64 `json:"oom_kill,omitempty"` + // The amount of memory that has been swapped in (in bytes). SwapIn int64 `json:"swap_in,omitempty"` diff --git a/packages/shared/pkg/fc/models/cpu_config.go b/packages/shared/pkg/fc/models/cpu_config.go index c3c9927f90..7b5f5d03b6 100644 --- a/packages/shared/pkg/fc/models/cpu_config.go +++ b/packages/shared/pkg/fc/models/cpu_config.go @@ -4,7 +4,10 @@ package models import ( "context" + stderrors "errors" + "strconv" + "github.com/go-openapi/errors" "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" ) @@ -14,29 +17,307 @@ import ( // swagger:model CpuConfig type CPUConfig struct { - // A collection of CPUIDs to be modified. (x86_64) - CpuidModifiers any `json:"cpuid_modifiers,omitempty"` + // A collection of CPUID leaf modifiers (x86_64 only) + CpuidModifiers []*CpuidLeafModifier `json:"cpuid_modifiers"` - // A collection of kvm capabilities to be modified. (aarch64) - KvmCapabilities any `json:"kvm_capabilities,omitempty"` + // A collection of KVM capabilities to be added or removed (both x86_64 and aarch64) + KvmCapabilities []string `json:"kvm_capabilities"` - // A collection of model specific registers to be modified. (x86_64) - MsrModifiers any `json:"msr_modifiers,omitempty"` + // A collection of model specific register modifiers (x86_64 only) + MsrModifiers []*MsrModifier `json:"msr_modifiers"` - // A collection of registers to be modified. (aarch64) - RegModifiers any `json:"reg_modifiers,omitempty"` + // A collection of register modifiers (aarch64 only) + RegModifiers []*ArmRegisterModifier `json:"reg_modifiers"` - // A collection of vcpu features to be modified. (aarch64) - VcpuFeatures any `json:"vcpu_features,omitempty"` + // A collection of vCPU features to be modified (aarch64 only) + VcpuFeatures []*VcpuFeatures `json:"vcpu_features"` } // Validate validates this Cpu config func (m *CPUConfig) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateCpuidModifiers(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMsrModifiers(formats); err != nil { + res = append(res, err) + } + + if err := m.validateRegModifiers(formats); err != nil { + res = append(res, err) + } + + if err := m.validateVcpuFeatures(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *CPUConfig) validateCpuidModifiers(formats strfmt.Registry) error { + if swag.IsZero(m.CpuidModifiers) { // not required + return nil + } + + for i := 0; i < len(m.CpuidModifiers); i++ { + if swag.IsZero(m.CpuidModifiers[i]) { // not required + continue + } + + if m.CpuidModifiers[i] != nil { + if err := m.CpuidModifiers[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("cpuid_modifiers" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("cpuid_modifiers" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +func (m *CPUConfig) validateMsrModifiers(formats strfmt.Registry) error { + if swag.IsZero(m.MsrModifiers) { // not required + return nil + } + + for i := 0; i < len(m.MsrModifiers); i++ { + if swag.IsZero(m.MsrModifiers[i]) { // not required + continue + } + + if m.MsrModifiers[i] != nil { + if err := m.MsrModifiers[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("msr_modifiers" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("msr_modifiers" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +func (m *CPUConfig) validateRegModifiers(formats strfmt.Registry) error { + if swag.IsZero(m.RegModifiers) { // not required + return nil + } + + for i := 0; i < len(m.RegModifiers); i++ { + if swag.IsZero(m.RegModifiers[i]) { // not required + continue + } + + if m.RegModifiers[i] != nil { + if err := m.RegModifiers[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("reg_modifiers" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("reg_modifiers" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + return nil } -// ContextValidate validates this Cpu config based on context it is used +func (m *CPUConfig) validateVcpuFeatures(formats strfmt.Registry) error { + if swag.IsZero(m.VcpuFeatures) { // not required + return nil + } + + for i := 0; i < len(m.VcpuFeatures); i++ { + if swag.IsZero(m.VcpuFeatures[i]) { // not required + continue + } + + if m.VcpuFeatures[i] != nil { + if err := m.VcpuFeatures[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("vcpu_features" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("vcpu_features" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this Cpu config based on the context it is used func (m *CPUConfig) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateCpuidModifiers(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateMsrModifiers(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateRegModifiers(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateVcpuFeatures(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *CPUConfig) contextValidateCpuidModifiers(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.CpuidModifiers); i++ { + + if m.CpuidModifiers[i] != nil { + + if swag.IsZero(m.CpuidModifiers[i]) { // not required + return nil + } + + if err := m.CpuidModifiers[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("cpuid_modifiers" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("cpuid_modifiers" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +func (m *CPUConfig) contextValidateMsrModifiers(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.MsrModifiers); i++ { + + if m.MsrModifiers[i] != nil { + + if swag.IsZero(m.MsrModifiers[i]) { // not required + return nil + } + + if err := m.MsrModifiers[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("msr_modifiers" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("msr_modifiers" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +func (m *CPUConfig) contextValidateRegModifiers(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.RegModifiers); i++ { + + if m.RegModifiers[i] != nil { + + if swag.IsZero(m.RegModifiers[i]) { // not required + return nil + } + + if err := m.RegModifiers[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("reg_modifiers" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("reg_modifiers" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +func (m *CPUConfig) contextValidateVcpuFeatures(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.VcpuFeatures); i++ { + + if m.VcpuFeatures[i] != nil { + + if swag.IsZero(m.VcpuFeatures[i]) { // not required + return nil + } + + if err := m.VcpuFeatures[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("vcpu_features" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("vcpu_features" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + return nil } diff --git a/packages/shared/pkg/fc/models/cpuid_leaf_modifier.go b/packages/shared/pkg/fc/models/cpuid_leaf_modifier.go new file mode 100644 index 0000000000..9ace041fef --- /dev/null +++ b/packages/shared/pkg/fc/models/cpuid_leaf_modifier.go @@ -0,0 +1,181 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + stderrors "errors" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// CpuidLeafModifier Modifier for a CPUID leaf and subleaf (x86_64) +// +// swagger:model CpuidLeafModifier +type CpuidLeafModifier struct { + + // KVM feature flags for this leaf-subleaf + // Required: true + Flags *int32 `json:"flags"` + + // CPUID leaf index as hex, binary, or decimal string (e.g., "0x0", "0b0", "0")) + // Required: true + Leaf *string `json:"leaf"` + + // Register modifiers for this CPUID leaf + // Required: true + Modifiers []*CpuidRegisterModifier `json:"modifiers"` + + // CPUID subleaf index as hex, binary, or decimal string (e.g., "0x0", "0b0", "0") + // Required: true + Subleaf *string `json:"subleaf"` +} + +// Validate validates this cpuid leaf modifier +func (m *CpuidLeafModifier) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateFlags(formats); err != nil { + res = append(res, err) + } + + if err := m.validateLeaf(formats); err != nil { + res = append(res, err) + } + + if err := m.validateModifiers(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSubleaf(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *CpuidLeafModifier) validateFlags(formats strfmt.Registry) error { + + if err := validate.Required("flags", "body", m.Flags); err != nil { + return err + } + + return nil +} + +func (m *CpuidLeafModifier) validateLeaf(formats strfmt.Registry) error { + + if err := validate.Required("leaf", "body", m.Leaf); err != nil { + return err + } + + return nil +} + +func (m *CpuidLeafModifier) validateModifiers(formats strfmt.Registry) error { + + if err := validate.Required("modifiers", "body", m.Modifiers); err != nil { + return err + } + + for i := 0; i < len(m.Modifiers); i++ { + if swag.IsZero(m.Modifiers[i]) { // not required + continue + } + + if m.Modifiers[i] != nil { + if err := m.Modifiers[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("modifiers" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("modifiers" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +func (m *CpuidLeafModifier) validateSubleaf(formats strfmt.Registry) error { + + if err := validate.Required("subleaf", "body", m.Subleaf); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this cpuid leaf modifier based on the context it is used +func (m *CpuidLeafModifier) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateModifiers(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *CpuidLeafModifier) contextValidateModifiers(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Modifiers); i++ { + + if m.Modifiers[i] != nil { + + if swag.IsZero(m.Modifiers[i]) { // not required + return nil + } + + if err := m.Modifiers[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("modifiers" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("modifiers" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// MarshalBinary interface implementation +func (m *CpuidLeafModifier) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *CpuidLeafModifier) UnmarshalBinary(b []byte) error { + var res CpuidLeafModifier + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/packages/shared/pkg/fc/models/cpuid_register_modifier.go b/packages/shared/pkg/fc/models/cpuid_register_modifier.go new file mode 100644 index 0000000000..9c77a5b28a --- /dev/null +++ b/packages/shared/pkg/fc/models/cpuid_register_modifier.go @@ -0,0 +1,127 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// CpuidRegisterModifier Modifier for a specific CPUID register within a leaf (x86_64) +// +// swagger:model CpuidRegisterModifier +type CpuidRegisterModifier struct { + + // 32-bit bitmap string defining which bits to modify. Format is "0b" followed by 32 characters where '0' = clear bit, '1' = set bit, 'x' = don't modify. Example "0b00000000000000000000000000000001" or "0bxxxxxxxxxxxxxxxxxxxxxxxxxxxx0001" + // Required: true + Bitmap *string `json:"bitmap"` + + // Target CPUID register name + // Required: true + // Enum: ["eax","ebx","ecx","edx"] + Register *string `json:"register"` +} + +// Validate validates this cpuid register modifier +func (m *CpuidRegisterModifier) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateBitmap(formats); err != nil { + res = append(res, err) + } + + if err := m.validateRegister(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *CpuidRegisterModifier) validateBitmap(formats strfmt.Registry) error { + + if err := validate.Required("bitmap", "body", m.Bitmap); err != nil { + return err + } + + return nil +} + +var cpuidRegisterModifierTypeRegisterPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["eax","ebx","ecx","edx"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + cpuidRegisterModifierTypeRegisterPropEnum = append(cpuidRegisterModifierTypeRegisterPropEnum, v) + } +} + +const ( + + // CpuidRegisterModifierRegisterEax captures enum value "eax" + CpuidRegisterModifierRegisterEax string = "eax" + + // CpuidRegisterModifierRegisterEbx captures enum value "ebx" + CpuidRegisterModifierRegisterEbx string = "ebx" + + // CpuidRegisterModifierRegisterEcx captures enum value "ecx" + CpuidRegisterModifierRegisterEcx string = "ecx" + + // CpuidRegisterModifierRegisterEdx captures enum value "edx" + CpuidRegisterModifierRegisterEdx string = "edx" +) + +// prop value enum +func (m *CpuidRegisterModifier) validateRegisterEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, cpuidRegisterModifierTypeRegisterPropEnum, true); err != nil { + return err + } + return nil +} + +func (m *CpuidRegisterModifier) validateRegister(formats strfmt.Registry) error { + + if err := validate.Required("register", "body", m.Register); err != nil { + return err + } + + // value enum + if err := m.validateRegisterEnum("register", "body", *m.Register); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this cpuid register modifier based on context it is used +func (m *CpuidRegisterModifier) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *CpuidRegisterModifier) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *CpuidRegisterModifier) UnmarshalBinary(b []byte) error { + var res CpuidRegisterModifier + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/packages/shared/pkg/fc/models/full_vm_configuration.go b/packages/shared/pkg/fc/models/full_vm_configuration.go index 89131f61ed..b53f74760e 100644 --- a/packages/shared/pkg/fc/models/full_vm_configuration.go +++ b/packages/shared/pkg/fc/models/full_vm_configuration.go @@ -38,6 +38,9 @@ type FullVMConfiguration struct { // machine config MachineConfig *MachineConfiguration `json:"machine-config,omitempty"` + // memory hotplug + MemoryHotplug *MemoryHotplugConfig `json:"memory-hotplug,omitempty"` + // metrics Metrics *Metrics `json:"metrics,omitempty"` @@ -47,6 +50,9 @@ type FullVMConfiguration struct { // Configurations for all net devices. NetworkInterfaces []*NetworkInterface `json:"network-interfaces"` + // Configurations for all pmem devices. + Pmem []*Pmem `json:"pmem"` + // vsock Vsock *Vsock `json:"vsock,omitempty"` } @@ -83,6 +89,10 @@ func (m *FullVMConfiguration) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateMemoryHotplug(formats); err != nil { + res = append(res, err) + } + if err := m.validateMetrics(formats); err != nil { res = append(res, err) } @@ -95,6 +105,10 @@ func (m *FullVMConfiguration) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validatePmem(formats); err != nil { + res = append(res, err) + } + if err := m.validateVsock(formats); err != nil { res = append(res, err) } @@ -273,6 +287,29 @@ func (m *FullVMConfiguration) validateMachineConfig(formats strfmt.Registry) err return nil } +func (m *FullVMConfiguration) validateMemoryHotplug(formats strfmt.Registry) error { + if swag.IsZero(m.MemoryHotplug) { // not required + return nil + } + + if m.MemoryHotplug != nil { + if err := m.MemoryHotplug.Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("memory-hotplug") + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("memory-hotplug") + } + + return err + } + } + + return nil +} + func (m *FullVMConfiguration) validateMetrics(formats strfmt.Registry) error { if swag.IsZero(m.Metrics) { // not required return nil @@ -349,6 +386,36 @@ func (m *FullVMConfiguration) validateNetworkInterfaces(formats strfmt.Registry) return nil } +func (m *FullVMConfiguration) validatePmem(formats strfmt.Registry) error { + if swag.IsZero(m.Pmem) { // not required + return nil + } + + for i := 0; i < len(m.Pmem); i++ { + if swag.IsZero(m.Pmem[i]) { // not required + continue + } + + if m.Pmem[i] != nil { + if err := m.Pmem[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("pmem" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("pmem" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + func (m *FullVMConfiguration) validateVsock(formats strfmt.Registry) error { if swag.IsZero(m.Vsock) { // not required return nil @@ -404,6 +471,10 @@ func (m *FullVMConfiguration) ContextValidate(ctx context.Context, formats strfm res = append(res, err) } + if err := m.contextValidateMemoryHotplug(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateMetrics(ctx, formats); err != nil { res = append(res, err) } @@ -416,6 +487,10 @@ func (m *FullVMConfiguration) ContextValidate(ctx context.Context, formats strfm res = append(res, err) } + if err := m.contextValidatePmem(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateVsock(ctx, formats); err != nil { res = append(res, err) } @@ -605,6 +680,31 @@ func (m *FullVMConfiguration) contextValidateMachineConfig(ctx context.Context, return nil } +func (m *FullVMConfiguration) contextValidateMemoryHotplug(ctx context.Context, formats strfmt.Registry) error { + + if m.MemoryHotplug != nil { + + if swag.IsZero(m.MemoryHotplug) { // not required + return nil + } + + if err := m.MemoryHotplug.ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("memory-hotplug") + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("memory-hotplug") + } + + return err + } + } + + return nil +} + func (m *FullVMConfiguration) contextValidateMetrics(ctx context.Context, formats strfmt.Registry) error { if m.Metrics != nil { @@ -684,6 +784,35 @@ func (m *FullVMConfiguration) contextValidateNetworkInterfaces(ctx context.Conte return nil } +func (m *FullVMConfiguration) contextValidatePmem(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Pmem); i++ { + + if m.Pmem[i] != nil { + + if swag.IsZero(m.Pmem[i]) { // not required + return nil + } + + if err := m.Pmem[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("pmem" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("pmem" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + func (m *FullVMConfiguration) contextValidateVsock(ctx context.Context, formats strfmt.Registry) error { if m.Vsock != nil { diff --git a/packages/shared/pkg/fc/models/memory_hotplug_config.go b/packages/shared/pkg/fc/models/memory_hotplug_config.go new file mode 100644 index 0000000000..adbbcd1605 --- /dev/null +++ b/packages/shared/pkg/fc/models/memory_hotplug_config.go @@ -0,0 +1,94 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// MemoryHotplugConfig The configuration of the hotpluggable memory device (virtio-mem) +// +// swagger:model MemoryHotplugConfig +type MemoryHotplugConfig struct { + + // (Logical) Block size for the hotpluggable memory in MiB. This will determine the logical granularity of hot-plug memory for the guest. Refer to the device documentation on how to tune this value. + // Minimum: 2 + BlockSizeMib int64 `json:"block_size_mib,omitempty"` + + // Slot size for the hotpluggable memory in MiB. This will determine the granularity of hot-plug memory from the host. Refer to the device documentation on how to tune this value. + // Minimum: 128 + SlotSizeMib int64 `json:"slot_size_mib,omitempty"` + + // Total size of the hotpluggable memory in MiB. + TotalSizeMib int64 `json:"total_size_mib,omitempty"` +} + +// Validate validates this memory hotplug config +func (m *MemoryHotplugConfig) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateBlockSizeMib(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSlotSizeMib(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MemoryHotplugConfig) validateBlockSizeMib(formats strfmt.Registry) error { + if swag.IsZero(m.BlockSizeMib) { // not required + return nil + } + + if err := validate.MinimumInt("block_size_mib", "body", m.BlockSizeMib, 2, false); err != nil { + return err + } + + return nil +} + +func (m *MemoryHotplugConfig) validateSlotSizeMib(formats strfmt.Registry) error { + if swag.IsZero(m.SlotSizeMib) { // not required + return nil + } + + if err := validate.MinimumInt("slot_size_mib", "body", m.SlotSizeMib, 128, false); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this memory hotplug config based on context it is used +func (m *MemoryHotplugConfig) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *MemoryHotplugConfig) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MemoryHotplugConfig) UnmarshalBinary(b []byte) error { + var res MemoryHotplugConfig + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/packages/shared/pkg/fc/models/memory_hotplug_size_update.go b/packages/shared/pkg/fc/models/memory_hotplug_size_update.go new file mode 100644 index 0000000000..c071f79dd0 --- /dev/null +++ b/packages/shared/pkg/fc/models/memory_hotplug_size_update.go @@ -0,0 +1,47 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// MemoryHotplugSizeUpdate An update to the size of the hotpluggable memory region. +// +// swagger:model MemoryHotplugSizeUpdate +type MemoryHotplugSizeUpdate struct { + + // New target region size. + RequestedSizeMib int64 `json:"requested_size_mib,omitempty"` +} + +// Validate validates this memory hotplug size update +func (m *MemoryHotplugSizeUpdate) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this memory hotplug size update based on context it is used +func (m *MemoryHotplugSizeUpdate) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *MemoryHotplugSizeUpdate) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MemoryHotplugSizeUpdate) UnmarshalBinary(b []byte) error { + var res MemoryHotplugSizeUpdate + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/packages/shared/pkg/fc/models/memory_hotplug_status.go b/packages/shared/pkg/fc/models/memory_hotplug_status.go new file mode 100644 index 0000000000..795817bf1e --- /dev/null +++ b/packages/shared/pkg/fc/models/memory_hotplug_status.go @@ -0,0 +1,59 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// MemoryHotplugStatus The status of the hotpluggable memory device (virtio-mem) +// +// swagger:model MemoryHotplugStatus +type MemoryHotplugStatus struct { + + // (Logical) Block size for the hotpluggable memory in MiB. + BlockSizeMib int64 `json:"block_size_mib,omitempty"` + + // Plugged size for the hotpluggable memory in MiB. + PluggedSizeMib int64 `json:"plugged_size_mib,omitempty"` + + // Requested size for the hotpluggable memory in MiB. + RequestedSizeMib int64 `json:"requested_size_mib,omitempty"` + + // Slot size for the hotpluggable memory in MiB. + SlotSizeMib int64 `json:"slot_size_mib,omitempty"` + + // Total size of the hotpluggable memory in MiB. + TotalSizeMib int64 `json:"total_size_mib,omitempty"` +} + +// Validate validates this memory hotplug status +func (m *MemoryHotplugStatus) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this memory hotplug status based on context it is used +func (m *MemoryHotplugStatus) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *MemoryHotplugStatus) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MemoryHotplugStatus) UnmarshalBinary(b []byte) error { + var res MemoryHotplugStatus + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/packages/shared/pkg/fc/models/mmds_config.go b/packages/shared/pkg/fc/models/mmds_config.go index 718e73be09..b42fdc644f 100644 --- a/packages/shared/pkg/fc/models/mmds_config.go +++ b/packages/shared/pkg/fc/models/mmds_config.go @@ -17,6 +17,9 @@ import ( // swagger:model MmdsConfig type MmdsConfig struct { + // MMDS operates compatibly with EC2 IMDS (i.e. responds "text/plain" content regardless of Accept header in requests). + ImdsCompat *bool `json:"imds_compat,omitempty"` + // A valid IPv4 link-local address. IPv4Address *string `json:"ipv4_address,omitempty"` diff --git a/packages/shared/pkg/fc/models/msr_modifier.go b/packages/shared/pkg/fc/models/msr_modifier.go new file mode 100644 index 0000000000..120964ea9e --- /dev/null +++ b/packages/shared/pkg/fc/models/msr_modifier.go @@ -0,0 +1,85 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// MsrModifier Modifier for a model specific register (x86_64) +// +// swagger:model MsrModifier +type MsrModifier struct { + + // 32-bit MSR address as hex, binary, or decimal string (e.g., "0x10a", "0b100001010", "266") + // Required: true + Addr *string `json:"addr"` + + // 64-bit bitmap string defining which bits to modify. Format is "0b" followed by 64 characters where '0' = clear bit, '1' = set bit, 'x' = don't modify. Underscores can be used for readability. Example "0b0000000000000000000000000000000000000000000000000000000000000001" + // Required: true + Bitmap *string `json:"bitmap"` +} + +// Validate validates this msr modifier +func (m *MsrModifier) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateAddr(formats); err != nil { + res = append(res, err) + } + + if err := m.validateBitmap(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MsrModifier) validateAddr(formats strfmt.Registry) error { + + if err := validate.Required("addr", "body", m.Addr); err != nil { + return err + } + + return nil +} + +func (m *MsrModifier) validateBitmap(formats strfmt.Registry) error { + + if err := validate.Required("bitmap", "body", m.Bitmap); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this msr modifier based on context it is used +func (m *MsrModifier) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *MsrModifier) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MsrModifier) UnmarshalBinary(b []byte) error { + var res MsrModifier + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/packages/shared/pkg/fc/models/pmem.go b/packages/shared/pkg/fc/models/pmem.go new file mode 100644 index 0000000000..aad5d79d21 --- /dev/null +++ b/packages/shared/pkg/fc/models/pmem.go @@ -0,0 +1,91 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// Pmem pmem +// +// swagger:model Pmem +type Pmem struct { + + // Identificator for this device. + // Required: true + ID *string `json:"id"` + + // Host level path for the virtio-pmem device to use as a backing file. + // Required: true + PathOnHost *string `json:"path_on_host"` + + // Flag to map backing file in read-only mode. + ReadOnly bool `json:"read_only,omitempty"` + + // Flag to make this device be the root device for VM boot. Setting this flag will fail if there is another device configured to be a root device already. + RootDevice bool `json:"root_device,omitempty"` +} + +// Validate validates this pmem +func (m *Pmem) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateID(formats); err != nil { + res = append(res, err) + } + + if err := m.validatePathOnHost(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *Pmem) validateID(formats strfmt.Registry) error { + + if err := validate.Required("id", "body", m.ID); err != nil { + return err + } + + return nil +} + +func (m *Pmem) validatePathOnHost(formats strfmt.Registry) error { + + if err := validate.Required("path_on_host", "body", m.PathOnHost); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this pmem based on context it is used +func (m *Pmem) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *Pmem) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *Pmem) UnmarshalBinary(b []byte) error { + var res Pmem + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/packages/shared/pkg/fc/models/serial_device.go b/packages/shared/pkg/fc/models/serial_device.go new file mode 100644 index 0000000000..dde495fc1f --- /dev/null +++ b/packages/shared/pkg/fc/models/serial_device.go @@ -0,0 +1,47 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// SerialDevice The configuration of the serial device +// +// swagger:model SerialDevice +type SerialDevice struct { + + // Path to a file or named pipe on the host to which serial output should be written. + SerialOutPath string `json:"serial_out_path,omitempty"` +} + +// Validate validates this serial device +func (m *SerialDevice) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this serial device based on context it is used +func (m *SerialDevice) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *SerialDevice) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *SerialDevice) UnmarshalBinary(b []byte) error { + var res SerialDevice + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/packages/shared/pkg/fc/models/snapshot_create_params.go b/packages/shared/pkg/fc/models/snapshot_create_params.go index b839c19252..45e435d01b 100644 --- a/packages/shared/pkg/fc/models/snapshot_create_params.go +++ b/packages/shared/pkg/fc/models/snapshot_create_params.go @@ -17,7 +17,7 @@ import ( // swagger:model SnapshotCreateParams type SnapshotCreateParams struct { - // Path to the file that will contain the guest memory. + // Path to the file that will contain the guest memory. It is optional. In case that a user doesn't provide a path, they are responsible to ensure they store the microVM's memory state via external means. MemFilePath string `json:"mem_file_path,omitempty"` // Path to the file that will contain the microVM state. diff --git a/packages/shared/pkg/fc/models/snapshot_load_params.go b/packages/shared/pkg/fc/models/snapshot_load_params.go index 1c313b00e3..a1ae7d90b9 100644 --- a/packages/shared/pkg/fc/models/snapshot_load_params.go +++ b/packages/shared/pkg/fc/models/snapshot_load_params.go @@ -18,7 +18,7 @@ import ( // swagger:model SnapshotLoadParams type SnapshotLoadParams struct { - // Enable support for incremental (diff) snapshots by tracking dirty guest pages. + // (Deprecated) Enable dirty page tracking to improve space efficiency of diff snapshots EnableDiffSnapshots bool `json:"enable_diff_snapshots,omitempty"` // Configuration for the backend that handles memory load. If this field is specified, `mem_file_path` is forbidden. Either `mem_backend` or `mem_file_path` must be present at a time. @@ -36,6 +36,9 @@ type SnapshotLoadParams struct { // Path to the file that contains the microVM state to be loaded. // Required: true SnapshotPath *string `json:"snapshot_path"` + + // Enable dirty page tracking to improve space efficiency of diff snapshots + TrackDirtyPages bool `json:"track_dirty_pages,omitempty"` } // Validate validates this snapshot load params diff --git a/packages/shared/pkg/fc/models/vcpu_features.go b/packages/shared/pkg/fc/models/vcpu_features.go new file mode 100644 index 0000000000..dd9f71f81b --- /dev/null +++ b/packages/shared/pkg/fc/models/vcpu_features.go @@ -0,0 +1,85 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// VcpuFeatures vCPU feature modifier (aarch64) +// +// swagger:model VcpuFeatures +type VcpuFeatures struct { + + // 32-bit bitmap string defining which bits to modify. Format is "0b" followed by 32 characters where '0' = clear bit, '1' = set bit, 'x' = don't modify. Example "0b00000000000000000000000001100000" + // Required: true + Bitmap *string `json:"bitmap"` + + // Index in the kvm_vcpu_init.features array + // Required: true + Index *int32 `json:"index"` +} + +// Validate validates this vcpu features +func (m *VcpuFeatures) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateBitmap(formats); err != nil { + res = append(res, err) + } + + if err := m.validateIndex(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *VcpuFeatures) validateBitmap(formats strfmt.Registry) error { + + if err := validate.Required("bitmap", "body", m.Bitmap); err != nil { + return err + } + + return nil +} + +func (m *VcpuFeatures) validateIndex(formats strfmt.Registry) error { + + if err := validate.Required("index", "body", m.Index); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this vcpu features based on context it is used +func (m *VcpuFeatures) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *VcpuFeatures) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *VcpuFeatures) UnmarshalBinary(b []byte) error { + var res VcpuFeatures + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} From 665f0c6873df122599eb9a721b0b2e4cec3f3f33 Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Fri, 13 Feb 2026 11:00:04 -0800 Subject: [PATCH 02/63] orchestrator: add support for Firecracker v1.14 Add support for Firecracker v1.14 and make it the default. Signed-off-by: Babis Chalios --- .github/actions/build-sandbox-template/action.yml | 2 +- packages/orchestrator/README.md | 2 +- packages/shared/pkg/featureflags/flags.go | 4 +++- tests/integration/seed.go | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/actions/build-sandbox-template/action.yml b/.github/actions/build-sandbox-template/action.yml index 9482eefd40..f42d2b02b8 100644 --- a/.github/actions/build-sandbox-template/action.yml +++ b/.github/actions/build-sandbox-template/action.yml @@ -8,7 +8,7 @@ runs: env: TEMPLATE_ID: "2j6ly824owf4awgai1xo" KERNEL_VERSION: "vmlinux-6.1.158" - FIRECRACKER_VERSION: "v1.12.1_210cbac" + FIRECRACKER_VERSION: "v1.14.1_458ca91" run: | # Generate an unique build ID for the template for this run export BUILD_ID=$(uuidgen) diff --git a/packages/orchestrator/README.md b/packages/orchestrator/README.md index f4188b6a8c..d51767b63a 100644 --- a/packages/orchestrator/README.md +++ b/packages/orchestrator/README.md @@ -37,7 +37,7 @@ Flags: - `-storage ` - Local path or `gs://bucket` (enables local mode with auto-download of kernel/FC) - `-sandbox-dir ` - Override `SANDBOX_DIR` (the rootfs path baked into the snapshot) - `-kernel ` - Kernel version (default: `vmlinux-6.1.158`) -- `-firecracker ` - Firecracker version (default: `v1.12.1_210cbac`) +- `-firecracker ` - Firecracker version (default: `v1.14.1_458ca91`) - `-vcpu ` - vCPUs (default: `1`) - `-memory ` - Memory in MB (default: `512`) - `-disk ` - Disk in MB (default: `1000`) diff --git a/packages/shared/pkg/featureflags/flags.go b/packages/shared/pkg/featureflags/flags.go index 84416f6201..79f9327760 100644 --- a/packages/shared/pkg/featureflags/flags.go +++ b/packages/shared/pkg/featureflags/flags.go @@ -241,12 +241,14 @@ const ( const ( DefaultFirecackerV1_10Version = "v1.10.1_30cbb07" DefaultFirecackerV1_12Version = "v1.12.1_210cbac" - DefaultFirecrackerVersion = DefaultFirecackerV1_12Version + DefaultFirecackerV1_14Version = "v1.14.1_458ca91" + DefaultFirecrackerVersion = DefaultFirecackerV1_14Version ) var FirecrackerVersionMap = map[string]string{ "v1.10": DefaultFirecackerV1_10Version, "v1.12": DefaultFirecackerV1_12Version, + "v1.14": DefaultFirecackerV1_14Version, } // BuildIoEngine Sync is used by default as there seems to be a bad interaction between Async and a lot of io operations. diff --git a/tests/integration/seed.go b/tests/integration/seed.go index f550ca6c3f..aeabe4ea8a 100644 --- a/tests/integration/seed.go +++ b/tests/integration/seed.go @@ -208,7 +208,7 @@ INSERT INTO env_builds ( cluster_node_id, version, created_at, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, CURRENT_TIMESTAMP) `, build.id, "FROM e2bdev/base:latest", dbtypes.BuildStatusUploaded, - 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.12.1_210cbac", pkg.Version, + 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.14.1_458ca91", pkg.Version, "integration-test-node", templates.TemplateV1Version, build.createdAt) } else { err = db.TestsRawSQL(ctx, ` @@ -218,7 +218,7 @@ INSERT INTO env_builds ( cluster_node_id, version, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, CURRENT_TIMESTAMP) `, build.id, "FROM e2bdev/base:latest", dbtypes.BuildStatusUploaded, - 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.12.1_210cbac", pkg.Version, + 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.14.1_458ca91", pkg.Version, "integration-test-node", templates.TemplateV1Version) } if err != nil { From 737f60112a2a0cc30b05a3971e9911ae00cfaf6d Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Thu, 16 Apr 2026 14:32:45 -0700 Subject: [PATCH 03/63] chore: update Firecracker v1.14 to latest build (76f16f0) --- .github/actions/build-sandbox-template/action.yml | 2 +- packages/orchestrator/README.md | 2 +- packages/shared/pkg/featureflags/flags.go | 2 +- tests/integration/seed.go | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/actions/build-sandbox-template/action.yml b/.github/actions/build-sandbox-template/action.yml index f42d2b02b8..d2b3c8b538 100644 --- a/.github/actions/build-sandbox-template/action.yml +++ b/.github/actions/build-sandbox-template/action.yml @@ -8,7 +8,7 @@ runs: env: TEMPLATE_ID: "2j6ly824owf4awgai1xo" KERNEL_VERSION: "vmlinux-6.1.158" - FIRECRACKER_VERSION: "v1.14.1_458ca91" + FIRECRACKER_VERSION: "v1.14.1_76f16f0" run: | # Generate an unique build ID for the template for this run export BUILD_ID=$(uuidgen) diff --git a/packages/orchestrator/README.md b/packages/orchestrator/README.md index d51767b63a..7bdec0f2ff 100644 --- a/packages/orchestrator/README.md +++ b/packages/orchestrator/README.md @@ -37,7 +37,7 @@ Flags: - `-storage ` - Local path or `gs://bucket` (enables local mode with auto-download of kernel/FC) - `-sandbox-dir ` - Override `SANDBOX_DIR` (the rootfs path baked into the snapshot) - `-kernel ` - Kernel version (default: `vmlinux-6.1.158`) -- `-firecracker ` - Firecracker version (default: `v1.14.1_458ca91`) +- `-firecracker ` - Firecracker version (default: `v1.14.1_76f16f0`) - `-vcpu ` - vCPUs (default: `1`) - `-memory ` - Memory in MB (default: `512`) - `-disk ` - Disk in MB (default: `1000`) diff --git a/packages/shared/pkg/featureflags/flags.go b/packages/shared/pkg/featureflags/flags.go index 79f9327760..cc07f95a7f 100644 --- a/packages/shared/pkg/featureflags/flags.go +++ b/packages/shared/pkg/featureflags/flags.go @@ -241,7 +241,7 @@ const ( const ( DefaultFirecackerV1_10Version = "v1.10.1_30cbb07" DefaultFirecackerV1_12Version = "v1.12.1_210cbac" - DefaultFirecackerV1_14Version = "v1.14.1_458ca91" + DefaultFirecackerV1_14Version = "v1.14.1_76f16f0" DefaultFirecrackerVersion = DefaultFirecackerV1_14Version ) diff --git a/tests/integration/seed.go b/tests/integration/seed.go index aeabe4ea8a..4db805560a 100644 --- a/tests/integration/seed.go +++ b/tests/integration/seed.go @@ -208,7 +208,7 @@ INSERT INTO env_builds ( cluster_node_id, version, created_at, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, CURRENT_TIMESTAMP) `, build.id, "FROM e2bdev/base:latest", dbtypes.BuildStatusUploaded, - 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.14.1_458ca91", pkg.Version, + 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.14.1_76f16f0", pkg.Version, "integration-test-node", templates.TemplateV1Version, build.createdAt) } else { err = db.TestsRawSQL(ctx, ` @@ -218,7 +218,7 @@ INSERT INTO env_builds ( cluster_node_id, version, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, CURRENT_TIMESTAMP) `, build.id, "FROM e2bdev/base:latest", dbtypes.BuildStatusUploaded, - 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.14.1_458ca91", pkg.Version, + 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.14.1_76f16f0", pkg.Version, "integration-test-node", templates.TemplateV1Version) } if err != nil { From 9f1f8a515c36ab28a3a397fd809da6c470bb4d70 Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Thu, 19 Feb 2026 15:07:57 +0100 Subject: [PATCH 04/63] feat: update Firecracker client to v1.14 Bump the swagger file for Firecracker to v1.14 and regenerate APIs/models. Signed-off-by: Babis Chalios --- .../operations/create_snapshot_parameters.go | 2 +- .../describe_balloon_hinting_parameters.go | 125 +++++ .../describe_balloon_hinting_responses.go | 261 ++++++++++ .../operations/get_memory_dirty_parameters.go | 125 ----- .../operations/get_memory_dirty_responses.go | 261 ---------- .../get_memory_hotplug_parameters.go | 125 +++++ .../get_memory_hotplug_responses.go | 185 +++++++ .../operations/load_snapshot_parameters.go | 2 +- .../fc/client/operations/operations_client.go | 362 ++++++++++++++ .../patch_memory_hotplug_parameters.go | 150 ++++++ .../patch_memory_hotplug_responses.go | 171 +++++++ .../put_guest_pmem_by_id_parameters.go | 172 +++++++ .../put_guest_pmem_by_id_responses.go | 247 ++++++++++ .../put_memory_hotplug_parameters.go | 150 ++++++ .../put_memory_hotplug_responses.go | 171 +++++++ .../put_serial_device_parameters.go | 150 ++++++ .../operations/put_serial_device_responses.go | 171 +++++++ .../start_balloon_hinting_parameters.go | 150 ++++++ .../start_balloon_hinting_responses.go | 247 ++++++++++ .../stop_balloon_hinting_parameters.go | 125 +++++ .../stop_balloon_hinting_responses.go | 247 ++++++++++ packages/shared/pkg/fc/firecracker.yml | 458 +++++++++++++++++- .../pkg/fc/models/arm_register_modifier.go | 85 ++++ packages/shared/pkg/fc/models/balloon.go | 6 + .../pkg/fc/models/balloon_hinting_status.go | 71 +++ .../shared/pkg/fc/models/balloon_start_cmd.go | 47 ++ .../shared/pkg/fc/models/balloon_stats.go | 18 + packages/shared/pkg/fc/models/cpu_config.go | 303 +++++++++++- .../pkg/fc/models/cpuid_leaf_modifier.go | 181 +++++++ .../pkg/fc/models/cpuid_register_modifier.go | 127 +++++ .../pkg/fc/models/full_vm_configuration.go | 129 +++++ .../pkg/fc/models/memory_hotplug_config.go | 94 ++++ .../fc/models/memory_hotplug_size_update.go | 47 ++ .../pkg/fc/models/memory_hotplug_status.go | 59 +++ packages/shared/pkg/fc/models/mmds_config.go | 3 + packages/shared/pkg/fc/models/msr_modifier.go | 85 ++++ packages/shared/pkg/fc/models/pmem.go | 91 ++++ .../shared/pkg/fc/models/serial_device.go | 47 ++ .../pkg/fc/models/snapshot_create_params.go | 2 +- .../pkg/fc/models/snapshot_load_params.go | 5 +- .../shared/pkg/fc/models/vcpu_features.go | 85 ++++ 41 files changed, 5124 insertions(+), 418 deletions(-) create mode 100644 packages/shared/pkg/fc/client/operations/describe_balloon_hinting_parameters.go create mode 100644 packages/shared/pkg/fc/client/operations/describe_balloon_hinting_responses.go delete mode 100644 packages/shared/pkg/fc/client/operations/get_memory_dirty_parameters.go delete mode 100644 packages/shared/pkg/fc/client/operations/get_memory_dirty_responses.go create mode 100644 packages/shared/pkg/fc/client/operations/get_memory_hotplug_parameters.go create mode 100644 packages/shared/pkg/fc/client/operations/get_memory_hotplug_responses.go create mode 100644 packages/shared/pkg/fc/client/operations/patch_memory_hotplug_parameters.go create mode 100644 packages/shared/pkg/fc/client/operations/patch_memory_hotplug_responses.go create mode 100644 packages/shared/pkg/fc/client/operations/put_guest_pmem_by_id_parameters.go create mode 100644 packages/shared/pkg/fc/client/operations/put_guest_pmem_by_id_responses.go create mode 100644 packages/shared/pkg/fc/client/operations/put_memory_hotplug_parameters.go create mode 100644 packages/shared/pkg/fc/client/operations/put_memory_hotplug_responses.go create mode 100644 packages/shared/pkg/fc/client/operations/put_serial_device_parameters.go create mode 100644 packages/shared/pkg/fc/client/operations/put_serial_device_responses.go create mode 100644 packages/shared/pkg/fc/client/operations/start_balloon_hinting_parameters.go create mode 100644 packages/shared/pkg/fc/client/operations/start_balloon_hinting_responses.go create mode 100644 packages/shared/pkg/fc/client/operations/stop_balloon_hinting_parameters.go create mode 100644 packages/shared/pkg/fc/client/operations/stop_balloon_hinting_responses.go create mode 100644 packages/shared/pkg/fc/models/arm_register_modifier.go create mode 100644 packages/shared/pkg/fc/models/balloon_hinting_status.go create mode 100644 packages/shared/pkg/fc/models/balloon_start_cmd.go create mode 100644 packages/shared/pkg/fc/models/cpuid_leaf_modifier.go create mode 100644 packages/shared/pkg/fc/models/cpuid_register_modifier.go create mode 100644 packages/shared/pkg/fc/models/memory_hotplug_config.go create mode 100644 packages/shared/pkg/fc/models/memory_hotplug_size_update.go create mode 100644 packages/shared/pkg/fc/models/memory_hotplug_status.go create mode 100644 packages/shared/pkg/fc/models/msr_modifier.go create mode 100644 packages/shared/pkg/fc/models/pmem.go create mode 100644 packages/shared/pkg/fc/models/serial_device.go create mode 100644 packages/shared/pkg/fc/models/vcpu_features.go diff --git a/packages/shared/pkg/fc/client/operations/create_snapshot_parameters.go b/packages/shared/pkg/fc/client/operations/create_snapshot_parameters.go index d5a2d163d9..945ad30987 100644 --- a/packages/shared/pkg/fc/client/operations/create_snapshot_parameters.go +++ b/packages/shared/pkg/fc/client/operations/create_snapshot_parameters.go @@ -62,7 +62,7 @@ type CreateSnapshotParams struct { /* Body. - The configuration used for creating a snaphot. + The configuration used for creating a snapshot. */ Body *models.SnapshotCreateParams diff --git a/packages/shared/pkg/fc/client/operations/describe_balloon_hinting_parameters.go b/packages/shared/pkg/fc/client/operations/describe_balloon_hinting_parameters.go new file mode 100644 index 0000000000..2a954ceae1 --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/describe_balloon_hinting_parameters.go @@ -0,0 +1,125 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewDescribeBalloonHintingParams creates a new DescribeBalloonHintingParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewDescribeBalloonHintingParams() *DescribeBalloonHintingParams { + return &DescribeBalloonHintingParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewDescribeBalloonHintingParamsWithTimeout creates a new DescribeBalloonHintingParams object +// with the ability to set a timeout on a request. +func NewDescribeBalloonHintingParamsWithTimeout(timeout time.Duration) *DescribeBalloonHintingParams { + return &DescribeBalloonHintingParams{ + timeout: timeout, + } +} + +// NewDescribeBalloonHintingParamsWithContext creates a new DescribeBalloonHintingParams object +// with the ability to set a context for a request. +func NewDescribeBalloonHintingParamsWithContext(ctx context.Context) *DescribeBalloonHintingParams { + return &DescribeBalloonHintingParams{ + Context: ctx, + } +} + +// NewDescribeBalloonHintingParamsWithHTTPClient creates a new DescribeBalloonHintingParams object +// with the ability to set a custom HTTPClient for a request. +func NewDescribeBalloonHintingParamsWithHTTPClient(client *http.Client) *DescribeBalloonHintingParams { + return &DescribeBalloonHintingParams{ + HTTPClient: client, + } +} + +/* +DescribeBalloonHintingParams contains all the parameters to send to the API endpoint + + for the describe balloon hinting operation. + + Typically these are written to a http.Request. +*/ +type DescribeBalloonHintingParams struct { + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the describe balloon hinting params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *DescribeBalloonHintingParams) WithDefaults() *DescribeBalloonHintingParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the describe balloon hinting params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *DescribeBalloonHintingParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the describe balloon hinting params +func (o *DescribeBalloonHintingParams) WithTimeout(timeout time.Duration) *DescribeBalloonHintingParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the describe balloon hinting params +func (o *DescribeBalloonHintingParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the describe balloon hinting params +func (o *DescribeBalloonHintingParams) WithContext(ctx context.Context) *DescribeBalloonHintingParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the describe balloon hinting params +func (o *DescribeBalloonHintingParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the describe balloon hinting params +func (o *DescribeBalloonHintingParams) WithHTTPClient(client *http.Client) *DescribeBalloonHintingParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the describe balloon hinting params +func (o *DescribeBalloonHintingParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WriteToRequest writes these params to a swagger request +func (o *DescribeBalloonHintingParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/describe_balloon_hinting_responses.go b/packages/shared/pkg/fc/client/operations/describe_balloon_hinting_responses.go new file mode 100644 index 0000000000..c5be936f3b --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/describe_balloon_hinting_responses.go @@ -0,0 +1,261 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "encoding/json" + stderrors "errors" + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// DescribeBalloonHintingReader is a Reader for the DescribeBalloonHinting structure. +type DescribeBalloonHintingReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *DescribeBalloonHintingReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 200: + result := NewDescribeBalloonHintingOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + case 400: + result := NewDescribeBalloonHintingBadRequest() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + default: + result := NewDescribeBalloonHintingDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewDescribeBalloonHintingOK creates a DescribeBalloonHintingOK with default headers values +func NewDescribeBalloonHintingOK() *DescribeBalloonHintingOK { + return &DescribeBalloonHintingOK{} +} + +/* +DescribeBalloonHintingOK describes a response with status code 200, with default header values. + +The balloon free page hinting statistics +*/ +type DescribeBalloonHintingOK struct { + Payload *models.BalloonHintingStatus +} + +// IsSuccess returns true when this describe balloon hinting o k response has a 2xx status code +func (o *DescribeBalloonHintingOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this describe balloon hinting o k response has a 3xx status code +func (o *DescribeBalloonHintingOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this describe balloon hinting o k response has a 4xx status code +func (o *DescribeBalloonHintingOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this describe balloon hinting o k response has a 5xx status code +func (o *DescribeBalloonHintingOK) IsServerError() bool { + return false +} + +// IsCode returns true when this describe balloon hinting o k response a status code equal to that given +func (o *DescribeBalloonHintingOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the describe balloon hinting o k response +func (o *DescribeBalloonHintingOK) Code() int { + return 200 +} + +func (o *DescribeBalloonHintingOK) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /balloon/hinting/status][%d] describeBalloonHintingOK %s", 200, payload) +} + +func (o *DescribeBalloonHintingOK) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /balloon/hinting/status][%d] describeBalloonHintingOK %s", 200, payload) +} + +func (o *DescribeBalloonHintingOK) GetPayload() *models.BalloonHintingStatus { + return o.Payload +} + +func (o *DescribeBalloonHintingOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.BalloonHintingStatus) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewDescribeBalloonHintingBadRequest creates a DescribeBalloonHintingBadRequest with default headers values +func NewDescribeBalloonHintingBadRequest() *DescribeBalloonHintingBadRequest { + return &DescribeBalloonHintingBadRequest{} +} + +/* +DescribeBalloonHintingBadRequest describes a response with status code 400, with default header values. + +The balloon free hinting was not enabled when the device was configured. +*/ +type DescribeBalloonHintingBadRequest struct { + Payload *models.Error +} + +// IsSuccess returns true when this describe balloon hinting bad request response has a 2xx status code +func (o *DescribeBalloonHintingBadRequest) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this describe balloon hinting bad request response has a 3xx status code +func (o *DescribeBalloonHintingBadRequest) IsRedirect() bool { + return false +} + +// IsClientError returns true when this describe balloon hinting bad request response has a 4xx status code +func (o *DescribeBalloonHintingBadRequest) IsClientError() bool { + return true +} + +// IsServerError returns true when this describe balloon hinting bad request response has a 5xx status code +func (o *DescribeBalloonHintingBadRequest) IsServerError() bool { + return false +} + +// IsCode returns true when this describe balloon hinting bad request response a status code equal to that given +func (o *DescribeBalloonHintingBadRequest) IsCode(code int) bool { + return code == 400 +} + +// Code gets the status code for the describe balloon hinting bad request response +func (o *DescribeBalloonHintingBadRequest) Code() int { + return 400 +} + +func (o *DescribeBalloonHintingBadRequest) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /balloon/hinting/status][%d] describeBalloonHintingBadRequest %s", 400, payload) +} + +func (o *DescribeBalloonHintingBadRequest) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /balloon/hinting/status][%d] describeBalloonHintingBadRequest %s", 400, payload) +} + +func (o *DescribeBalloonHintingBadRequest) GetPayload() *models.Error { + return o.Payload +} + +func (o *DescribeBalloonHintingBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewDescribeBalloonHintingDefault creates a DescribeBalloonHintingDefault with default headers values +func NewDescribeBalloonHintingDefault(code int) *DescribeBalloonHintingDefault { + return &DescribeBalloonHintingDefault{ + _statusCode: code, + } +} + +/* +DescribeBalloonHintingDefault describes a response with status code -1, with default header values. + +Internal Server Error +*/ +type DescribeBalloonHintingDefault struct { + _statusCode int + + Payload *models.Error +} + +// IsSuccess returns true when this describe balloon hinting default response has a 2xx status code +func (o *DescribeBalloonHintingDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this describe balloon hinting default response has a 3xx status code +func (o *DescribeBalloonHintingDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this describe balloon hinting default response has a 4xx status code +func (o *DescribeBalloonHintingDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this describe balloon hinting default response has a 5xx status code +func (o *DescribeBalloonHintingDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this describe balloon hinting default response a status code equal to that given +func (o *DescribeBalloonHintingDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the describe balloon hinting default response +func (o *DescribeBalloonHintingDefault) Code() int { + return o._statusCode +} + +func (o *DescribeBalloonHintingDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /balloon/hinting/status][%d] describeBalloonHinting default %s", o._statusCode, payload) +} + +func (o *DescribeBalloonHintingDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /balloon/hinting/status][%d] describeBalloonHinting default %s", o._statusCode, payload) +} + +func (o *DescribeBalloonHintingDefault) GetPayload() *models.Error { + return o.Payload +} + +func (o *DescribeBalloonHintingDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/get_memory_dirty_parameters.go b/packages/shared/pkg/fc/client/operations/get_memory_dirty_parameters.go deleted file mode 100644 index 2838328ece..0000000000 --- a/packages/shared/pkg/fc/client/operations/get_memory_dirty_parameters.go +++ /dev/null @@ -1,125 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package operations - -import ( - "context" - "net/http" - "time" - - "github.com/go-openapi/errors" - "github.com/go-openapi/runtime" - cr "github.com/go-openapi/runtime/client" - "github.com/go-openapi/strfmt" -) - -// NewGetMemoryDirtyParams creates a new GetMemoryDirtyParams object, -// with the default timeout for this client. -// -// Default values are not hydrated, since defaults are normally applied by the API server side. -// -// To enforce default values in parameter, use SetDefaults or WithDefaults. -func NewGetMemoryDirtyParams() *GetMemoryDirtyParams { - return &GetMemoryDirtyParams{ - timeout: cr.DefaultTimeout, - } -} - -// NewGetMemoryDirtyParamsWithTimeout creates a new GetMemoryDirtyParams object -// with the ability to set a timeout on a request. -func NewGetMemoryDirtyParamsWithTimeout(timeout time.Duration) *GetMemoryDirtyParams { - return &GetMemoryDirtyParams{ - timeout: timeout, - } -} - -// NewGetMemoryDirtyParamsWithContext creates a new GetMemoryDirtyParams object -// with the ability to set a context for a request. -func NewGetMemoryDirtyParamsWithContext(ctx context.Context) *GetMemoryDirtyParams { - return &GetMemoryDirtyParams{ - Context: ctx, - } -} - -// NewGetMemoryDirtyParamsWithHTTPClient creates a new GetMemoryDirtyParams object -// with the ability to set a custom HTTPClient for a request. -func NewGetMemoryDirtyParamsWithHTTPClient(client *http.Client) *GetMemoryDirtyParams { - return &GetMemoryDirtyParams{ - HTTPClient: client, - } -} - -/* -GetMemoryDirtyParams contains all the parameters to send to the API endpoint - - for the get memory dirty operation. - - Typically these are written to a http.Request. -*/ -type GetMemoryDirtyParams struct { - timeout time.Duration - Context context.Context - HTTPClient *http.Client -} - -// WithDefaults hydrates default values in the get memory dirty params (not the query body). -// -// All values with no default are reset to their zero value. -func (o *GetMemoryDirtyParams) WithDefaults() *GetMemoryDirtyParams { - o.SetDefaults() - return o -} - -// SetDefaults hydrates default values in the get memory dirty params (not the query body). -// -// All values with no default are reset to their zero value. -func (o *GetMemoryDirtyParams) SetDefaults() { - // no default values defined for this parameter -} - -// WithTimeout adds the timeout to the get memory dirty params -func (o *GetMemoryDirtyParams) WithTimeout(timeout time.Duration) *GetMemoryDirtyParams { - o.SetTimeout(timeout) - return o -} - -// SetTimeout adds the timeout to the get memory dirty params -func (o *GetMemoryDirtyParams) SetTimeout(timeout time.Duration) { - o.timeout = timeout -} - -// WithContext adds the context to the get memory dirty params -func (o *GetMemoryDirtyParams) WithContext(ctx context.Context) *GetMemoryDirtyParams { - o.SetContext(ctx) - return o -} - -// SetContext adds the context to the get memory dirty params -func (o *GetMemoryDirtyParams) SetContext(ctx context.Context) { - o.Context = ctx -} - -// WithHTTPClient adds the HTTPClient to the get memory dirty params -func (o *GetMemoryDirtyParams) WithHTTPClient(client *http.Client) *GetMemoryDirtyParams { - o.SetHTTPClient(client) - return o -} - -// SetHTTPClient adds the HTTPClient to the get memory dirty params -func (o *GetMemoryDirtyParams) SetHTTPClient(client *http.Client) { - o.HTTPClient = client -} - -// WriteToRequest writes these params to a swagger request -func (o *GetMemoryDirtyParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { - - if err := r.SetTimeout(o.timeout); err != nil { - return err - } - var res []error - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} diff --git a/packages/shared/pkg/fc/client/operations/get_memory_dirty_responses.go b/packages/shared/pkg/fc/client/operations/get_memory_dirty_responses.go deleted file mode 100644 index c3df69bfb4..0000000000 --- a/packages/shared/pkg/fc/client/operations/get_memory_dirty_responses.go +++ /dev/null @@ -1,261 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package operations - -import ( - "encoding/json" - stderrors "errors" - "fmt" - "io" - - "github.com/go-openapi/runtime" - "github.com/go-openapi/strfmt" - - "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" -) - -// GetMemoryDirtyReader is a Reader for the GetMemoryDirty structure. -type GetMemoryDirtyReader struct { - formats strfmt.Registry -} - -// ReadResponse reads a server response into the received o. -func (o *GetMemoryDirtyReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { - switch response.Code() { - case 200: - result := NewGetMemoryDirtyOK() - if err := result.readResponse(response, consumer, o.formats); err != nil { - return nil, err - } - return result, nil - case 400: - result := NewGetMemoryDirtyBadRequest() - if err := result.readResponse(response, consumer, o.formats); err != nil { - return nil, err - } - return nil, result - default: - result := NewGetMemoryDirtyDefault(response.Code()) - if err := result.readResponse(response, consumer, o.formats); err != nil { - return nil, err - } - if response.Code()/100 == 2 { - return result, nil - } - return nil, result - } -} - -// NewGetMemoryDirtyOK creates a GetMemoryDirtyOK with default headers values -func NewGetMemoryDirtyOK() *GetMemoryDirtyOK { - return &GetMemoryDirtyOK{} -} - -/* -GetMemoryDirtyOK describes a response with status code 200, with default header values. - -OK -*/ -type GetMemoryDirtyOK struct { - Payload *models.MemoryDirty -} - -// IsSuccess returns true when this get memory dirty o k response has a 2xx status code -func (o *GetMemoryDirtyOK) IsSuccess() bool { - return true -} - -// IsRedirect returns true when this get memory dirty o k response has a 3xx status code -func (o *GetMemoryDirtyOK) IsRedirect() bool { - return false -} - -// IsClientError returns true when this get memory dirty o k response has a 4xx status code -func (o *GetMemoryDirtyOK) IsClientError() bool { - return false -} - -// IsServerError returns true when this get memory dirty o k response has a 5xx status code -func (o *GetMemoryDirtyOK) IsServerError() bool { - return false -} - -// IsCode returns true when this get memory dirty o k response a status code equal to that given -func (o *GetMemoryDirtyOK) IsCode(code int) bool { - return code == 200 -} - -// Code gets the status code for the get memory dirty o k response -func (o *GetMemoryDirtyOK) Code() int { - return 200 -} - -func (o *GetMemoryDirtyOK) Error() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[GET /memory/dirty][%d] getMemoryDirtyOK %s", 200, payload) -} - -func (o *GetMemoryDirtyOK) String() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[GET /memory/dirty][%d] getMemoryDirtyOK %s", 200, payload) -} - -func (o *GetMemoryDirtyOK) GetPayload() *models.MemoryDirty { - return o.Payload -} - -func (o *GetMemoryDirtyOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { - - o.Payload = new(models.MemoryDirty) - - // response payload - if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { - return err - } - - return nil -} - -// NewGetMemoryDirtyBadRequest creates a GetMemoryDirtyBadRequest with default headers values -func NewGetMemoryDirtyBadRequest() *GetMemoryDirtyBadRequest { - return &GetMemoryDirtyBadRequest{} -} - -/* -GetMemoryDirtyBadRequest describes a response with status code 400, with default header values. - -The microVM is not paused. -*/ -type GetMemoryDirtyBadRequest struct { - Payload *models.Error -} - -// IsSuccess returns true when this get memory dirty bad request response has a 2xx status code -func (o *GetMemoryDirtyBadRequest) IsSuccess() bool { - return false -} - -// IsRedirect returns true when this get memory dirty bad request response has a 3xx status code -func (o *GetMemoryDirtyBadRequest) IsRedirect() bool { - return false -} - -// IsClientError returns true when this get memory dirty bad request response has a 4xx status code -func (o *GetMemoryDirtyBadRequest) IsClientError() bool { - return true -} - -// IsServerError returns true when this get memory dirty bad request response has a 5xx status code -func (o *GetMemoryDirtyBadRequest) IsServerError() bool { - return false -} - -// IsCode returns true when this get memory dirty bad request response a status code equal to that given -func (o *GetMemoryDirtyBadRequest) IsCode(code int) bool { - return code == 400 -} - -// Code gets the status code for the get memory dirty bad request response -func (o *GetMemoryDirtyBadRequest) Code() int { - return 400 -} - -func (o *GetMemoryDirtyBadRequest) Error() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[GET /memory/dirty][%d] getMemoryDirtyBadRequest %s", 400, payload) -} - -func (o *GetMemoryDirtyBadRequest) String() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[GET /memory/dirty][%d] getMemoryDirtyBadRequest %s", 400, payload) -} - -func (o *GetMemoryDirtyBadRequest) GetPayload() *models.Error { - return o.Payload -} - -func (o *GetMemoryDirtyBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { - - o.Payload = new(models.Error) - - // response payload - if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { - return err - } - - return nil -} - -// NewGetMemoryDirtyDefault creates a GetMemoryDirtyDefault with default headers values -func NewGetMemoryDirtyDefault(code int) *GetMemoryDirtyDefault { - return &GetMemoryDirtyDefault{ - _statusCode: code, - } -} - -/* -GetMemoryDirtyDefault describes a response with status code -1, with default header values. - -Internal server error -*/ -type GetMemoryDirtyDefault struct { - _statusCode int - - Payload *models.Error -} - -// IsSuccess returns true when this get memory dirty default response has a 2xx status code -func (o *GetMemoryDirtyDefault) IsSuccess() bool { - return o._statusCode/100 == 2 -} - -// IsRedirect returns true when this get memory dirty default response has a 3xx status code -func (o *GetMemoryDirtyDefault) IsRedirect() bool { - return o._statusCode/100 == 3 -} - -// IsClientError returns true when this get memory dirty default response has a 4xx status code -func (o *GetMemoryDirtyDefault) IsClientError() bool { - return o._statusCode/100 == 4 -} - -// IsServerError returns true when this get memory dirty default response has a 5xx status code -func (o *GetMemoryDirtyDefault) IsServerError() bool { - return o._statusCode/100 == 5 -} - -// IsCode returns true when this get memory dirty default response a status code equal to that given -func (o *GetMemoryDirtyDefault) IsCode(code int) bool { - return o._statusCode == code -} - -// Code gets the status code for the get memory dirty default response -func (o *GetMemoryDirtyDefault) Code() int { - return o._statusCode -} - -func (o *GetMemoryDirtyDefault) Error() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[GET /memory/dirty][%d] getMemoryDirty default %s", o._statusCode, payload) -} - -func (o *GetMemoryDirtyDefault) String() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[GET /memory/dirty][%d] getMemoryDirty default %s", o._statusCode, payload) -} - -func (o *GetMemoryDirtyDefault) GetPayload() *models.Error { - return o.Payload -} - -func (o *GetMemoryDirtyDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { - - o.Payload = new(models.Error) - - // response payload - if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { - return err - } - - return nil -} diff --git a/packages/shared/pkg/fc/client/operations/get_memory_hotplug_parameters.go b/packages/shared/pkg/fc/client/operations/get_memory_hotplug_parameters.go new file mode 100644 index 0000000000..225d750e0f --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/get_memory_hotplug_parameters.go @@ -0,0 +1,125 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewGetMemoryHotplugParams creates a new GetMemoryHotplugParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewGetMemoryHotplugParams() *GetMemoryHotplugParams { + return &GetMemoryHotplugParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewGetMemoryHotplugParamsWithTimeout creates a new GetMemoryHotplugParams object +// with the ability to set a timeout on a request. +func NewGetMemoryHotplugParamsWithTimeout(timeout time.Duration) *GetMemoryHotplugParams { + return &GetMemoryHotplugParams{ + timeout: timeout, + } +} + +// NewGetMemoryHotplugParamsWithContext creates a new GetMemoryHotplugParams object +// with the ability to set a context for a request. +func NewGetMemoryHotplugParamsWithContext(ctx context.Context) *GetMemoryHotplugParams { + return &GetMemoryHotplugParams{ + Context: ctx, + } +} + +// NewGetMemoryHotplugParamsWithHTTPClient creates a new GetMemoryHotplugParams object +// with the ability to set a custom HTTPClient for a request. +func NewGetMemoryHotplugParamsWithHTTPClient(client *http.Client) *GetMemoryHotplugParams { + return &GetMemoryHotplugParams{ + HTTPClient: client, + } +} + +/* +GetMemoryHotplugParams contains all the parameters to send to the API endpoint + + for the get memory hotplug operation. + + Typically these are written to a http.Request. +*/ +type GetMemoryHotplugParams struct { + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the get memory hotplug params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *GetMemoryHotplugParams) WithDefaults() *GetMemoryHotplugParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the get memory hotplug params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *GetMemoryHotplugParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the get memory hotplug params +func (o *GetMemoryHotplugParams) WithTimeout(timeout time.Duration) *GetMemoryHotplugParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the get memory hotplug params +func (o *GetMemoryHotplugParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the get memory hotplug params +func (o *GetMemoryHotplugParams) WithContext(ctx context.Context) *GetMemoryHotplugParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the get memory hotplug params +func (o *GetMemoryHotplugParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the get memory hotplug params +func (o *GetMemoryHotplugParams) WithHTTPClient(client *http.Client) *GetMemoryHotplugParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the get memory hotplug params +func (o *GetMemoryHotplugParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WriteToRequest writes these params to a swagger request +func (o *GetMemoryHotplugParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/get_memory_hotplug_responses.go b/packages/shared/pkg/fc/client/operations/get_memory_hotplug_responses.go new file mode 100644 index 0000000000..69799f3747 --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/get_memory_hotplug_responses.go @@ -0,0 +1,185 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "encoding/json" + stderrors "errors" + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// GetMemoryHotplugReader is a Reader for the GetMemoryHotplug structure. +type GetMemoryHotplugReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *GetMemoryHotplugReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 200: + result := NewGetMemoryHotplugOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewGetMemoryHotplugDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewGetMemoryHotplugOK creates a GetMemoryHotplugOK with default headers values +func NewGetMemoryHotplugOK() *GetMemoryHotplugOK { + return &GetMemoryHotplugOK{} +} + +/* +GetMemoryHotplugOK describes a response with status code 200, with default header values. + +OK +*/ +type GetMemoryHotplugOK struct { + Payload *models.MemoryHotplugStatus +} + +// IsSuccess returns true when this get memory hotplug o k response has a 2xx status code +func (o *GetMemoryHotplugOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this get memory hotplug o k response has a 3xx status code +func (o *GetMemoryHotplugOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this get memory hotplug o k response has a 4xx status code +func (o *GetMemoryHotplugOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this get memory hotplug o k response has a 5xx status code +func (o *GetMemoryHotplugOK) IsServerError() bool { + return false +} + +// IsCode returns true when this get memory hotplug o k response a status code equal to that given +func (o *GetMemoryHotplugOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the get memory hotplug o k response +func (o *GetMemoryHotplugOK) Code() int { + return 200 +} + +func (o *GetMemoryHotplugOK) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /hotplug/memory][%d] getMemoryHotplugOK %s", 200, payload) +} + +func (o *GetMemoryHotplugOK) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /hotplug/memory][%d] getMemoryHotplugOK %s", 200, payload) +} + +func (o *GetMemoryHotplugOK) GetPayload() *models.MemoryHotplugStatus { + return o.Payload +} + +func (o *GetMemoryHotplugOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.MemoryHotplugStatus) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewGetMemoryHotplugDefault creates a GetMemoryHotplugDefault with default headers values +func NewGetMemoryHotplugDefault(code int) *GetMemoryHotplugDefault { + return &GetMemoryHotplugDefault{ + _statusCode: code, + } +} + +/* +GetMemoryHotplugDefault describes a response with status code -1, with default header values. + +Internal server error +*/ +type GetMemoryHotplugDefault struct { + _statusCode int + + Payload *models.Error +} + +// IsSuccess returns true when this get memory hotplug default response has a 2xx status code +func (o *GetMemoryHotplugDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this get memory hotplug default response has a 3xx status code +func (o *GetMemoryHotplugDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this get memory hotplug default response has a 4xx status code +func (o *GetMemoryHotplugDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this get memory hotplug default response has a 5xx status code +func (o *GetMemoryHotplugDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this get memory hotplug default response a status code equal to that given +func (o *GetMemoryHotplugDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the get memory hotplug default response +func (o *GetMemoryHotplugDefault) Code() int { + return o._statusCode +} + +func (o *GetMemoryHotplugDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /hotplug/memory][%d] getMemoryHotplug default %s", o._statusCode, payload) +} + +func (o *GetMemoryHotplugDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /hotplug/memory][%d] getMemoryHotplug default %s", o._statusCode, payload) +} + +func (o *GetMemoryHotplugDefault) GetPayload() *models.Error { + return o.Payload +} + +func (o *GetMemoryHotplugDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/load_snapshot_parameters.go b/packages/shared/pkg/fc/client/operations/load_snapshot_parameters.go index 5067fee113..df1f826f67 100644 --- a/packages/shared/pkg/fc/client/operations/load_snapshot_parameters.go +++ b/packages/shared/pkg/fc/client/operations/load_snapshot_parameters.go @@ -62,7 +62,7 @@ type LoadSnapshotParams struct { /* Body. - The configuration used for loading a snaphot. + The configuration used for loading a snapshot. */ Body *models.SnapshotLoadParams diff --git a/packages/shared/pkg/fc/client/operations/operations_client.go b/packages/shared/pkg/fc/client/operations/operations_client.go index ead938ce2e..d29a2e37f1 100644 --- a/packages/shared/pkg/fc/client/operations/operations_client.go +++ b/packages/shared/pkg/fc/client/operations/operations_client.go @@ -57,6 +57,8 @@ type ClientService interface { DescribeBalloonConfig(params *DescribeBalloonConfigParams, opts ...ClientOption) (*DescribeBalloonConfigOK, error) + DescribeBalloonHinting(params *DescribeBalloonHintingParams, opts ...ClientOption) (*DescribeBalloonHintingOK, error) + DescribeBalloonStats(params *DescribeBalloonStatsParams, opts ...ClientOption) (*DescribeBalloonStatsOK, error) DescribeInstance(params *DescribeInstanceParams, opts ...ClientOption) (*DescribeInstanceOK, error) @@ -71,6 +73,8 @@ type ClientService interface { GetMemory(params *GetMemoryParams, opts ...ClientOption) (*GetMemoryOK, error) + GetMemoryHotplug(params *GetMemoryHotplugParams, opts ...ClientOption) (*GetMemoryHotplugOK, error) + GetMemoryMappings(params *GetMemoryMappingsParams, opts ...ClientOption) (*GetMemoryMappingsOK, error) GetMmds(params *GetMmdsParams, opts ...ClientOption) (*GetMmdsOK, error) @@ -87,6 +91,8 @@ type ClientService interface { PatchMachineConfiguration(params *PatchMachineConfigurationParams, opts ...ClientOption) (*PatchMachineConfigurationNoContent, error) + PatchMemoryHotplug(params *PatchMemoryHotplugParams, opts ...ClientOption) (*PatchMemoryHotplugNoContent, error) + PatchMmds(params *PatchMmdsParams, opts ...ClientOption) (*PatchMmdsNoContent, error) PatchVM(params *PatchVMParams, opts ...ClientOption) (*PatchVMNoContent, error) @@ -103,18 +109,28 @@ type ClientService interface { PutGuestNetworkInterfaceByID(params *PutGuestNetworkInterfaceByIDParams, opts ...ClientOption) (*PutGuestNetworkInterfaceByIDNoContent, error) + PutGuestPmemByID(params *PutGuestPmemByIDParams, opts ...ClientOption) (*PutGuestPmemByIDNoContent, error) + PutGuestVsock(params *PutGuestVsockParams, opts ...ClientOption) (*PutGuestVsockNoContent, error) PutLogger(params *PutLoggerParams, opts ...ClientOption) (*PutLoggerNoContent, error) PutMachineConfiguration(params *PutMachineConfigurationParams, opts ...ClientOption) (*PutMachineConfigurationNoContent, error) + PutMemoryHotplug(params *PutMemoryHotplugParams, opts ...ClientOption) (*PutMemoryHotplugNoContent, error) + PutMetrics(params *PutMetricsParams, opts ...ClientOption) (*PutMetricsNoContent, error) PutMmds(params *PutMmdsParams, opts ...ClientOption) (*PutMmdsNoContent, error) PutMmdsConfig(params *PutMmdsConfigParams, opts ...ClientOption) (*PutMmdsConfigNoContent, error) + PutSerialDevice(params *PutSerialDeviceParams, opts ...ClientOption) (*PutSerialDeviceNoContent, error) + + StartBalloonHinting(params *StartBalloonHintingParams, opts ...ClientOption) (*StartBalloonHintingOK, error) + + StopBalloonHinting(params *StopBalloonHintingParams, opts ...ClientOption) (*StopBalloonHintingOK, error) + SetTransport(transport runtime.ClientTransport) } @@ -246,6 +262,48 @@ func (a *Client) DescribeBalloonConfig(params *DescribeBalloonConfigParams, opts return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) } +/* +DescribeBalloonHinting returns the balloon hinting statistics only if enabled pre boot +*/ +func (a *Client) DescribeBalloonHinting(params *DescribeBalloonHintingParams, opts ...ClientOption) (*DescribeBalloonHintingOK, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewDescribeBalloonHintingParams() + } + op := &runtime.ClientOperation{ + ID: "describeBalloonHinting", + Method: "GET", + PathPattern: "/balloon/hinting/status", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &DescribeBalloonHintingReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*DescribeBalloonHintingOK) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*DescribeBalloonHintingDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + /* DescribeBalloonStats returns the latest balloon device statistics only if enabled pre boot */ @@ -548,6 +606,50 @@ func (a *Client) GetMemory(params *GetMemoryParams, opts ...ClientOption) (*GetM return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) } +/* +GetMemoryHotplug retrieves the status of the hotpluggable memory + +Reuturn the status of the hotpluggable memory. This can be used to follow the progress of the guest after a PATCH API. +*/ +func (a *Client) GetMemoryHotplug(params *GetMemoryHotplugParams, opts ...ClientOption) (*GetMemoryHotplugOK, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewGetMemoryHotplugParams() + } + op := &runtime.ClientOperation{ + ID: "getMemoryHotplug", + Method: "GET", + PathPattern: "/hotplug/memory", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &GetMemoryHotplugReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*GetMemoryHotplugOK) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*GetMemoryHotplugDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + /* GetMemoryMappings gets the memory mappings with skippable pages bitmap */ @@ -896,6 +998,50 @@ func (a *Client) PatchMachineConfiguration(params *PatchMachineConfigurationPara return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) } +/* +PatchMemoryHotplug updates the size of the hotpluggable memory region + +Updates the size of the hotpluggable memory region. The guest will plug and unplug memory to hit the requested memory. +*/ +func (a *Client) PatchMemoryHotplug(params *PatchMemoryHotplugParams, opts ...ClientOption) (*PatchMemoryHotplugNoContent, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewPatchMemoryHotplugParams() + } + op := &runtime.ClientOperation{ + ID: "patchMemoryHotplug", + Method: "PATCH", + PathPattern: "/hotplug/memory", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &PatchMemoryHotplugReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*PatchMemoryHotplugNoContent) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*PatchMemoryHotplugDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + /* PatchMmds updates the m m d s data store */ @@ -1246,6 +1392,50 @@ func (a *Client) PutGuestNetworkInterfaceByID(params *PutGuestNetworkInterfaceBy return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) } +/* +PutGuestPmemByID creates or updates a pmem device pre boot only + +Creates new pmem device with ID specified by id parameter. If a pmem device with the specified ID already exists, updates its state based on new input. Will fail if update is not possible. +*/ +func (a *Client) PutGuestPmemByID(params *PutGuestPmemByIDParams, opts ...ClientOption) (*PutGuestPmemByIDNoContent, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewPutGuestPmemByIDParams() + } + op := &runtime.ClientOperation{ + ID: "putGuestPmemByID", + Method: "PUT", + PathPattern: "/pmem/{id}", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &PutGuestPmemByIDReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*PutGuestPmemByIDNoContent) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*PutGuestPmemByIDDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + /* PutGuestVsock creates updates a vsock device pre boot only @@ -1376,6 +1566,50 @@ func (a *Client) PutMachineConfiguration(params *PutMachineConfigurationParams, return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) } +/* +PutMemoryHotplug configures the hotpluggable memory + +Configure the hotpluggable memory, which is a virtio-mem device, with an associated memory area that can be hot(un)plugged in the guest on demand using the PATCH API. +*/ +func (a *Client) PutMemoryHotplug(params *PutMemoryHotplugParams, opts ...ClientOption) (*PutMemoryHotplugNoContent, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewPutMemoryHotplugParams() + } + op := &runtime.ClientOperation{ + ID: "putMemoryHotplug", + Method: "PUT", + PathPattern: "/hotplug/memory", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &PutMemoryHotplugReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*PutMemoryHotplugNoContent) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*PutMemoryHotplugDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + /* PutMetrics initializes the metrics system by specifying a named pipe or a file for the metrics output */ @@ -1504,6 +1738,134 @@ func (a *Client) PutMmdsConfig(params *PutMmdsConfigParams, opts ...ClientOption return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) } +/* +PutSerialDevice configures the serial console + +Configure the serial console, which the guest can write its kernel logs to. Has no effect if the serial console is not also enabled on the guest kernel command line +*/ +func (a *Client) PutSerialDevice(params *PutSerialDeviceParams, opts ...ClientOption) (*PutSerialDeviceNoContent, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewPutSerialDeviceParams() + } + op := &runtime.ClientOperation{ + ID: "putSerialDevice", + Method: "PUT", + PathPattern: "/serial", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &PutSerialDeviceReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*PutSerialDeviceNoContent) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*PutSerialDeviceDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + +/* +StartBalloonHinting starts a free page hinting run only if enabled pre boot +*/ +func (a *Client) StartBalloonHinting(params *StartBalloonHintingParams, opts ...ClientOption) (*StartBalloonHintingOK, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewStartBalloonHintingParams() + } + op := &runtime.ClientOperation{ + ID: "startBalloonHinting", + Method: "PATCH", + PathPattern: "/balloon/hinting/start", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &StartBalloonHintingReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*StartBalloonHintingOK) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*StartBalloonHintingDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + +/* +StopBalloonHinting stops a free page hinting run only if enabled pre boot +*/ +func (a *Client) StopBalloonHinting(params *StopBalloonHintingParams, opts ...ClientOption) (*StopBalloonHintingOK, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewStopBalloonHintingParams() + } + op := &runtime.ClientOperation{ + ID: "stopBalloonHinting", + Method: "PATCH", + PathPattern: "/balloon/hinting/stop", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &StopBalloonHintingReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*StopBalloonHintingOK) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*StopBalloonHintingDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + // SetTransport changes the transport on the client func (a *Client) SetTransport(transport runtime.ClientTransport) { a.transport = transport diff --git a/packages/shared/pkg/fc/client/operations/patch_memory_hotplug_parameters.go b/packages/shared/pkg/fc/client/operations/patch_memory_hotplug_parameters.go new file mode 100644 index 0000000000..ef741faebb --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/patch_memory_hotplug_parameters.go @@ -0,0 +1,150 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// NewPatchMemoryHotplugParams creates a new PatchMemoryHotplugParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewPatchMemoryHotplugParams() *PatchMemoryHotplugParams { + return &PatchMemoryHotplugParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewPatchMemoryHotplugParamsWithTimeout creates a new PatchMemoryHotplugParams object +// with the ability to set a timeout on a request. +func NewPatchMemoryHotplugParamsWithTimeout(timeout time.Duration) *PatchMemoryHotplugParams { + return &PatchMemoryHotplugParams{ + timeout: timeout, + } +} + +// NewPatchMemoryHotplugParamsWithContext creates a new PatchMemoryHotplugParams object +// with the ability to set a context for a request. +func NewPatchMemoryHotplugParamsWithContext(ctx context.Context) *PatchMemoryHotplugParams { + return &PatchMemoryHotplugParams{ + Context: ctx, + } +} + +// NewPatchMemoryHotplugParamsWithHTTPClient creates a new PatchMemoryHotplugParams object +// with the ability to set a custom HTTPClient for a request. +func NewPatchMemoryHotplugParamsWithHTTPClient(client *http.Client) *PatchMemoryHotplugParams { + return &PatchMemoryHotplugParams{ + HTTPClient: client, + } +} + +/* +PatchMemoryHotplugParams contains all the parameters to send to the API endpoint + + for the patch memory hotplug operation. + + Typically these are written to a http.Request. +*/ +type PatchMemoryHotplugParams struct { + + /* Body. + + Hotpluggable memory size update + */ + Body *models.MemoryHotplugSizeUpdate + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the patch memory hotplug params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *PatchMemoryHotplugParams) WithDefaults() *PatchMemoryHotplugParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the patch memory hotplug params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *PatchMemoryHotplugParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the patch memory hotplug params +func (o *PatchMemoryHotplugParams) WithTimeout(timeout time.Duration) *PatchMemoryHotplugParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the patch memory hotplug params +func (o *PatchMemoryHotplugParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the patch memory hotplug params +func (o *PatchMemoryHotplugParams) WithContext(ctx context.Context) *PatchMemoryHotplugParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the patch memory hotplug params +func (o *PatchMemoryHotplugParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the patch memory hotplug params +func (o *PatchMemoryHotplugParams) WithHTTPClient(client *http.Client) *PatchMemoryHotplugParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the patch memory hotplug params +func (o *PatchMemoryHotplugParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithBody adds the body to the patch memory hotplug params +func (o *PatchMemoryHotplugParams) WithBody(body *models.MemoryHotplugSizeUpdate) *PatchMemoryHotplugParams { + o.SetBody(body) + return o +} + +// SetBody adds the body to the patch memory hotplug params +func (o *PatchMemoryHotplugParams) SetBody(body *models.MemoryHotplugSizeUpdate) { + o.Body = body +} + +// WriteToRequest writes these params to a swagger request +func (o *PatchMemoryHotplugParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + if o.Body != nil { + if err := r.SetBodyParam(o.Body); err != nil { + return err + } + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/patch_memory_hotplug_responses.go b/packages/shared/pkg/fc/client/operations/patch_memory_hotplug_responses.go new file mode 100644 index 0000000000..2f4f3808a0 --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/patch_memory_hotplug_responses.go @@ -0,0 +1,171 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "encoding/json" + stderrors "errors" + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// PatchMemoryHotplugReader is a Reader for the PatchMemoryHotplug structure. +type PatchMemoryHotplugReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *PatchMemoryHotplugReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 204: + result := NewPatchMemoryHotplugNoContent() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewPatchMemoryHotplugDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewPatchMemoryHotplugNoContent creates a PatchMemoryHotplugNoContent with default headers values +func NewPatchMemoryHotplugNoContent() *PatchMemoryHotplugNoContent { + return &PatchMemoryHotplugNoContent{} +} + +/* +PatchMemoryHotplugNoContent describes a response with status code 204, with default header values. + +Hotpluggable memory configured +*/ +type PatchMemoryHotplugNoContent struct { +} + +// IsSuccess returns true when this patch memory hotplug no content response has a 2xx status code +func (o *PatchMemoryHotplugNoContent) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this patch memory hotplug no content response has a 3xx status code +func (o *PatchMemoryHotplugNoContent) IsRedirect() bool { + return false +} + +// IsClientError returns true when this patch memory hotplug no content response has a 4xx status code +func (o *PatchMemoryHotplugNoContent) IsClientError() bool { + return false +} + +// IsServerError returns true when this patch memory hotplug no content response has a 5xx status code +func (o *PatchMemoryHotplugNoContent) IsServerError() bool { + return false +} + +// IsCode returns true when this patch memory hotplug no content response a status code equal to that given +func (o *PatchMemoryHotplugNoContent) IsCode(code int) bool { + return code == 204 +} + +// Code gets the status code for the patch memory hotplug no content response +func (o *PatchMemoryHotplugNoContent) Code() int { + return 204 +} + +func (o *PatchMemoryHotplugNoContent) Error() string { + return fmt.Sprintf("[PATCH /hotplug/memory][%d] patchMemoryHotplugNoContent", 204) +} + +func (o *PatchMemoryHotplugNoContent) String() string { + return fmt.Sprintf("[PATCH /hotplug/memory][%d] patchMemoryHotplugNoContent", 204) +} + +func (o *PatchMemoryHotplugNoContent) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +// NewPatchMemoryHotplugDefault creates a PatchMemoryHotplugDefault with default headers values +func NewPatchMemoryHotplugDefault(code int) *PatchMemoryHotplugDefault { + return &PatchMemoryHotplugDefault{ + _statusCode: code, + } +} + +/* +PatchMemoryHotplugDefault describes a response with status code -1, with default header values. + +Internal server error +*/ +type PatchMemoryHotplugDefault struct { + _statusCode int + + Payload *models.Error +} + +// IsSuccess returns true when this patch memory hotplug default response has a 2xx status code +func (o *PatchMemoryHotplugDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this patch memory hotplug default response has a 3xx status code +func (o *PatchMemoryHotplugDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this patch memory hotplug default response has a 4xx status code +func (o *PatchMemoryHotplugDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this patch memory hotplug default response has a 5xx status code +func (o *PatchMemoryHotplugDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this patch memory hotplug default response a status code equal to that given +func (o *PatchMemoryHotplugDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the patch memory hotplug default response +func (o *PatchMemoryHotplugDefault) Code() int { + return o._statusCode +} + +func (o *PatchMemoryHotplugDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PATCH /hotplug/memory][%d] patchMemoryHotplug default %s", o._statusCode, payload) +} + +func (o *PatchMemoryHotplugDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PATCH /hotplug/memory][%d] patchMemoryHotplug default %s", o._statusCode, payload) +} + +func (o *PatchMemoryHotplugDefault) GetPayload() *models.Error { + return o.Payload +} + +func (o *PatchMemoryHotplugDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/put_guest_pmem_by_id_parameters.go b/packages/shared/pkg/fc/client/operations/put_guest_pmem_by_id_parameters.go new file mode 100644 index 0000000000..77c2d0911c --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/put_guest_pmem_by_id_parameters.go @@ -0,0 +1,172 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// NewPutGuestPmemByIDParams creates a new PutGuestPmemByIDParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewPutGuestPmemByIDParams() *PutGuestPmemByIDParams { + return &PutGuestPmemByIDParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewPutGuestPmemByIDParamsWithTimeout creates a new PutGuestPmemByIDParams object +// with the ability to set a timeout on a request. +func NewPutGuestPmemByIDParamsWithTimeout(timeout time.Duration) *PutGuestPmemByIDParams { + return &PutGuestPmemByIDParams{ + timeout: timeout, + } +} + +// NewPutGuestPmemByIDParamsWithContext creates a new PutGuestPmemByIDParams object +// with the ability to set a context for a request. +func NewPutGuestPmemByIDParamsWithContext(ctx context.Context) *PutGuestPmemByIDParams { + return &PutGuestPmemByIDParams{ + Context: ctx, + } +} + +// NewPutGuestPmemByIDParamsWithHTTPClient creates a new PutGuestPmemByIDParams object +// with the ability to set a custom HTTPClient for a request. +func NewPutGuestPmemByIDParamsWithHTTPClient(client *http.Client) *PutGuestPmemByIDParams { + return &PutGuestPmemByIDParams{ + HTTPClient: client, + } +} + +/* +PutGuestPmemByIDParams contains all the parameters to send to the API endpoint + + for the put guest pmem by ID operation. + + Typically these are written to a http.Request. +*/ +type PutGuestPmemByIDParams struct { + + /* Body. + + Guest pmem device properties + */ + Body *models.Pmem + + /* ID. + + The id of the guest pmem device + */ + ID string + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the put guest pmem by ID params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *PutGuestPmemByIDParams) WithDefaults() *PutGuestPmemByIDParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the put guest pmem by ID params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *PutGuestPmemByIDParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the put guest pmem by ID params +func (o *PutGuestPmemByIDParams) WithTimeout(timeout time.Duration) *PutGuestPmemByIDParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the put guest pmem by ID params +func (o *PutGuestPmemByIDParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the put guest pmem by ID params +func (o *PutGuestPmemByIDParams) WithContext(ctx context.Context) *PutGuestPmemByIDParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the put guest pmem by ID params +func (o *PutGuestPmemByIDParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the put guest pmem by ID params +func (o *PutGuestPmemByIDParams) WithHTTPClient(client *http.Client) *PutGuestPmemByIDParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the put guest pmem by ID params +func (o *PutGuestPmemByIDParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithBody adds the body to the put guest pmem by ID params +func (o *PutGuestPmemByIDParams) WithBody(body *models.Pmem) *PutGuestPmemByIDParams { + o.SetBody(body) + return o +} + +// SetBody adds the body to the put guest pmem by ID params +func (o *PutGuestPmemByIDParams) SetBody(body *models.Pmem) { + o.Body = body +} + +// WithID adds the id to the put guest pmem by ID params +func (o *PutGuestPmemByIDParams) WithID(id string) *PutGuestPmemByIDParams { + o.SetID(id) + return o +} + +// SetID adds the id to the put guest pmem by ID params +func (o *PutGuestPmemByIDParams) SetID(id string) { + o.ID = id +} + +// WriteToRequest writes these params to a swagger request +func (o *PutGuestPmemByIDParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + if o.Body != nil { + if err := r.SetBodyParam(o.Body); err != nil { + return err + } + } + + // path param id + if err := r.SetPathParam("id", o.ID); err != nil { + return err + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/put_guest_pmem_by_id_responses.go b/packages/shared/pkg/fc/client/operations/put_guest_pmem_by_id_responses.go new file mode 100644 index 0000000000..106440e244 --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/put_guest_pmem_by_id_responses.go @@ -0,0 +1,247 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "encoding/json" + stderrors "errors" + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// PutGuestPmemByIDReader is a Reader for the PutGuestPmemByID structure. +type PutGuestPmemByIDReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *PutGuestPmemByIDReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 204: + result := NewPutGuestPmemByIDNoContent() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + case 400: + result := NewPutGuestPmemByIDBadRequest() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + default: + result := NewPutGuestPmemByIDDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewPutGuestPmemByIDNoContent creates a PutGuestPmemByIDNoContent with default headers values +func NewPutGuestPmemByIDNoContent() *PutGuestPmemByIDNoContent { + return &PutGuestPmemByIDNoContent{} +} + +/* +PutGuestPmemByIDNoContent describes a response with status code 204, with default header values. + +Pmem device is created/updated +*/ +type PutGuestPmemByIDNoContent struct { +} + +// IsSuccess returns true when this put guest pmem by Id no content response has a 2xx status code +func (o *PutGuestPmemByIDNoContent) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this put guest pmem by Id no content response has a 3xx status code +func (o *PutGuestPmemByIDNoContent) IsRedirect() bool { + return false +} + +// IsClientError returns true when this put guest pmem by Id no content response has a 4xx status code +func (o *PutGuestPmemByIDNoContent) IsClientError() bool { + return false +} + +// IsServerError returns true when this put guest pmem by Id no content response has a 5xx status code +func (o *PutGuestPmemByIDNoContent) IsServerError() bool { + return false +} + +// IsCode returns true when this put guest pmem by Id no content response a status code equal to that given +func (o *PutGuestPmemByIDNoContent) IsCode(code int) bool { + return code == 204 +} + +// Code gets the status code for the put guest pmem by Id no content response +func (o *PutGuestPmemByIDNoContent) Code() int { + return 204 +} + +func (o *PutGuestPmemByIDNoContent) Error() string { + return fmt.Sprintf("[PUT /pmem/{id}][%d] putGuestPmemByIdNoContent", 204) +} + +func (o *PutGuestPmemByIDNoContent) String() string { + return fmt.Sprintf("[PUT /pmem/{id}][%d] putGuestPmemByIdNoContent", 204) +} + +func (o *PutGuestPmemByIDNoContent) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +// NewPutGuestPmemByIDBadRequest creates a PutGuestPmemByIDBadRequest with default headers values +func NewPutGuestPmemByIDBadRequest() *PutGuestPmemByIDBadRequest { + return &PutGuestPmemByIDBadRequest{} +} + +/* +PutGuestPmemByIDBadRequest describes a response with status code 400, with default header values. + +Pmem device cannot be created/updated due to bad input +*/ +type PutGuestPmemByIDBadRequest struct { + Payload *models.Error +} + +// IsSuccess returns true when this put guest pmem by Id bad request response has a 2xx status code +func (o *PutGuestPmemByIDBadRequest) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this put guest pmem by Id bad request response has a 3xx status code +func (o *PutGuestPmemByIDBadRequest) IsRedirect() bool { + return false +} + +// IsClientError returns true when this put guest pmem by Id bad request response has a 4xx status code +func (o *PutGuestPmemByIDBadRequest) IsClientError() bool { + return true +} + +// IsServerError returns true when this put guest pmem by Id bad request response has a 5xx status code +func (o *PutGuestPmemByIDBadRequest) IsServerError() bool { + return false +} + +// IsCode returns true when this put guest pmem by Id bad request response a status code equal to that given +func (o *PutGuestPmemByIDBadRequest) IsCode(code int) bool { + return code == 400 +} + +// Code gets the status code for the put guest pmem by Id bad request response +func (o *PutGuestPmemByIDBadRequest) Code() int { + return 400 +} + +func (o *PutGuestPmemByIDBadRequest) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PUT /pmem/{id}][%d] putGuestPmemByIdBadRequest %s", 400, payload) +} + +func (o *PutGuestPmemByIDBadRequest) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PUT /pmem/{id}][%d] putGuestPmemByIdBadRequest %s", 400, payload) +} + +func (o *PutGuestPmemByIDBadRequest) GetPayload() *models.Error { + return o.Payload +} + +func (o *PutGuestPmemByIDBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewPutGuestPmemByIDDefault creates a PutGuestPmemByIDDefault with default headers values +func NewPutGuestPmemByIDDefault(code int) *PutGuestPmemByIDDefault { + return &PutGuestPmemByIDDefault{ + _statusCode: code, + } +} + +/* +PutGuestPmemByIDDefault describes a response with status code -1, with default header values. + +Internal server error. +*/ +type PutGuestPmemByIDDefault struct { + _statusCode int + + Payload *models.Error +} + +// IsSuccess returns true when this put guest pmem by ID default response has a 2xx status code +func (o *PutGuestPmemByIDDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this put guest pmem by ID default response has a 3xx status code +func (o *PutGuestPmemByIDDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this put guest pmem by ID default response has a 4xx status code +func (o *PutGuestPmemByIDDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this put guest pmem by ID default response has a 5xx status code +func (o *PutGuestPmemByIDDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this put guest pmem by ID default response a status code equal to that given +func (o *PutGuestPmemByIDDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the put guest pmem by ID default response +func (o *PutGuestPmemByIDDefault) Code() int { + return o._statusCode +} + +func (o *PutGuestPmemByIDDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PUT /pmem/{id}][%d] putGuestPmemByID default %s", o._statusCode, payload) +} + +func (o *PutGuestPmemByIDDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PUT /pmem/{id}][%d] putGuestPmemByID default %s", o._statusCode, payload) +} + +func (o *PutGuestPmemByIDDefault) GetPayload() *models.Error { + return o.Payload +} + +func (o *PutGuestPmemByIDDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/put_memory_hotplug_parameters.go b/packages/shared/pkg/fc/client/operations/put_memory_hotplug_parameters.go new file mode 100644 index 0000000000..5a1e21323b --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/put_memory_hotplug_parameters.go @@ -0,0 +1,150 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// NewPutMemoryHotplugParams creates a new PutMemoryHotplugParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewPutMemoryHotplugParams() *PutMemoryHotplugParams { + return &PutMemoryHotplugParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewPutMemoryHotplugParamsWithTimeout creates a new PutMemoryHotplugParams object +// with the ability to set a timeout on a request. +func NewPutMemoryHotplugParamsWithTimeout(timeout time.Duration) *PutMemoryHotplugParams { + return &PutMemoryHotplugParams{ + timeout: timeout, + } +} + +// NewPutMemoryHotplugParamsWithContext creates a new PutMemoryHotplugParams object +// with the ability to set a context for a request. +func NewPutMemoryHotplugParamsWithContext(ctx context.Context) *PutMemoryHotplugParams { + return &PutMemoryHotplugParams{ + Context: ctx, + } +} + +// NewPutMemoryHotplugParamsWithHTTPClient creates a new PutMemoryHotplugParams object +// with the ability to set a custom HTTPClient for a request. +func NewPutMemoryHotplugParamsWithHTTPClient(client *http.Client) *PutMemoryHotplugParams { + return &PutMemoryHotplugParams{ + HTTPClient: client, + } +} + +/* +PutMemoryHotplugParams contains all the parameters to send to the API endpoint + + for the put memory hotplug operation. + + Typically these are written to a http.Request. +*/ +type PutMemoryHotplugParams struct { + + /* Body. + + Hotpluggable memory configuration + */ + Body *models.MemoryHotplugConfig + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the put memory hotplug params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *PutMemoryHotplugParams) WithDefaults() *PutMemoryHotplugParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the put memory hotplug params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *PutMemoryHotplugParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the put memory hotplug params +func (o *PutMemoryHotplugParams) WithTimeout(timeout time.Duration) *PutMemoryHotplugParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the put memory hotplug params +func (o *PutMemoryHotplugParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the put memory hotplug params +func (o *PutMemoryHotplugParams) WithContext(ctx context.Context) *PutMemoryHotplugParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the put memory hotplug params +func (o *PutMemoryHotplugParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the put memory hotplug params +func (o *PutMemoryHotplugParams) WithHTTPClient(client *http.Client) *PutMemoryHotplugParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the put memory hotplug params +func (o *PutMemoryHotplugParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithBody adds the body to the put memory hotplug params +func (o *PutMemoryHotplugParams) WithBody(body *models.MemoryHotplugConfig) *PutMemoryHotplugParams { + o.SetBody(body) + return o +} + +// SetBody adds the body to the put memory hotplug params +func (o *PutMemoryHotplugParams) SetBody(body *models.MemoryHotplugConfig) { + o.Body = body +} + +// WriteToRequest writes these params to a swagger request +func (o *PutMemoryHotplugParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + if o.Body != nil { + if err := r.SetBodyParam(o.Body); err != nil { + return err + } + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/put_memory_hotplug_responses.go b/packages/shared/pkg/fc/client/operations/put_memory_hotplug_responses.go new file mode 100644 index 0000000000..7bb3ea5061 --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/put_memory_hotplug_responses.go @@ -0,0 +1,171 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "encoding/json" + stderrors "errors" + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// PutMemoryHotplugReader is a Reader for the PutMemoryHotplug structure. +type PutMemoryHotplugReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *PutMemoryHotplugReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 204: + result := NewPutMemoryHotplugNoContent() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewPutMemoryHotplugDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewPutMemoryHotplugNoContent creates a PutMemoryHotplugNoContent with default headers values +func NewPutMemoryHotplugNoContent() *PutMemoryHotplugNoContent { + return &PutMemoryHotplugNoContent{} +} + +/* +PutMemoryHotplugNoContent describes a response with status code 204, with default header values. + +Hotpluggable memory configured +*/ +type PutMemoryHotplugNoContent struct { +} + +// IsSuccess returns true when this put memory hotplug no content response has a 2xx status code +func (o *PutMemoryHotplugNoContent) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this put memory hotplug no content response has a 3xx status code +func (o *PutMemoryHotplugNoContent) IsRedirect() bool { + return false +} + +// IsClientError returns true when this put memory hotplug no content response has a 4xx status code +func (o *PutMemoryHotplugNoContent) IsClientError() bool { + return false +} + +// IsServerError returns true when this put memory hotplug no content response has a 5xx status code +func (o *PutMemoryHotplugNoContent) IsServerError() bool { + return false +} + +// IsCode returns true when this put memory hotplug no content response a status code equal to that given +func (o *PutMemoryHotplugNoContent) IsCode(code int) bool { + return code == 204 +} + +// Code gets the status code for the put memory hotplug no content response +func (o *PutMemoryHotplugNoContent) Code() int { + return 204 +} + +func (o *PutMemoryHotplugNoContent) Error() string { + return fmt.Sprintf("[PUT /hotplug/memory][%d] putMemoryHotplugNoContent", 204) +} + +func (o *PutMemoryHotplugNoContent) String() string { + return fmt.Sprintf("[PUT /hotplug/memory][%d] putMemoryHotplugNoContent", 204) +} + +func (o *PutMemoryHotplugNoContent) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +// NewPutMemoryHotplugDefault creates a PutMemoryHotplugDefault with default headers values +func NewPutMemoryHotplugDefault(code int) *PutMemoryHotplugDefault { + return &PutMemoryHotplugDefault{ + _statusCode: code, + } +} + +/* +PutMemoryHotplugDefault describes a response with status code -1, with default header values. + +Internal server error +*/ +type PutMemoryHotplugDefault struct { + _statusCode int + + Payload *models.Error +} + +// IsSuccess returns true when this put memory hotplug default response has a 2xx status code +func (o *PutMemoryHotplugDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this put memory hotplug default response has a 3xx status code +func (o *PutMemoryHotplugDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this put memory hotplug default response has a 4xx status code +func (o *PutMemoryHotplugDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this put memory hotplug default response has a 5xx status code +func (o *PutMemoryHotplugDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this put memory hotplug default response a status code equal to that given +func (o *PutMemoryHotplugDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the put memory hotplug default response +func (o *PutMemoryHotplugDefault) Code() int { + return o._statusCode +} + +func (o *PutMemoryHotplugDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PUT /hotplug/memory][%d] putMemoryHotplug default %s", o._statusCode, payload) +} + +func (o *PutMemoryHotplugDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PUT /hotplug/memory][%d] putMemoryHotplug default %s", o._statusCode, payload) +} + +func (o *PutMemoryHotplugDefault) GetPayload() *models.Error { + return o.Payload +} + +func (o *PutMemoryHotplugDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/put_serial_device_parameters.go b/packages/shared/pkg/fc/client/operations/put_serial_device_parameters.go new file mode 100644 index 0000000000..dbbd288e92 --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/put_serial_device_parameters.go @@ -0,0 +1,150 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// NewPutSerialDeviceParams creates a new PutSerialDeviceParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewPutSerialDeviceParams() *PutSerialDeviceParams { + return &PutSerialDeviceParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewPutSerialDeviceParamsWithTimeout creates a new PutSerialDeviceParams object +// with the ability to set a timeout on a request. +func NewPutSerialDeviceParamsWithTimeout(timeout time.Duration) *PutSerialDeviceParams { + return &PutSerialDeviceParams{ + timeout: timeout, + } +} + +// NewPutSerialDeviceParamsWithContext creates a new PutSerialDeviceParams object +// with the ability to set a context for a request. +func NewPutSerialDeviceParamsWithContext(ctx context.Context) *PutSerialDeviceParams { + return &PutSerialDeviceParams{ + Context: ctx, + } +} + +// NewPutSerialDeviceParamsWithHTTPClient creates a new PutSerialDeviceParams object +// with the ability to set a custom HTTPClient for a request. +func NewPutSerialDeviceParamsWithHTTPClient(client *http.Client) *PutSerialDeviceParams { + return &PutSerialDeviceParams{ + HTTPClient: client, + } +} + +/* +PutSerialDeviceParams contains all the parameters to send to the API endpoint + + for the put serial device operation. + + Typically these are written to a http.Request. +*/ +type PutSerialDeviceParams struct { + + /* Body. + + Serial console properties + */ + Body *models.SerialDevice + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the put serial device params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *PutSerialDeviceParams) WithDefaults() *PutSerialDeviceParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the put serial device params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *PutSerialDeviceParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the put serial device params +func (o *PutSerialDeviceParams) WithTimeout(timeout time.Duration) *PutSerialDeviceParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the put serial device params +func (o *PutSerialDeviceParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the put serial device params +func (o *PutSerialDeviceParams) WithContext(ctx context.Context) *PutSerialDeviceParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the put serial device params +func (o *PutSerialDeviceParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the put serial device params +func (o *PutSerialDeviceParams) WithHTTPClient(client *http.Client) *PutSerialDeviceParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the put serial device params +func (o *PutSerialDeviceParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithBody adds the body to the put serial device params +func (o *PutSerialDeviceParams) WithBody(body *models.SerialDevice) *PutSerialDeviceParams { + o.SetBody(body) + return o +} + +// SetBody adds the body to the put serial device params +func (o *PutSerialDeviceParams) SetBody(body *models.SerialDevice) { + o.Body = body +} + +// WriteToRequest writes these params to a swagger request +func (o *PutSerialDeviceParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + if o.Body != nil { + if err := r.SetBodyParam(o.Body); err != nil { + return err + } + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/put_serial_device_responses.go b/packages/shared/pkg/fc/client/operations/put_serial_device_responses.go new file mode 100644 index 0000000000..ac09a76f57 --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/put_serial_device_responses.go @@ -0,0 +1,171 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "encoding/json" + stderrors "errors" + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// PutSerialDeviceReader is a Reader for the PutSerialDevice structure. +type PutSerialDeviceReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *PutSerialDeviceReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 204: + result := NewPutSerialDeviceNoContent() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewPutSerialDeviceDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewPutSerialDeviceNoContent creates a PutSerialDeviceNoContent with default headers values +func NewPutSerialDeviceNoContent() *PutSerialDeviceNoContent { + return &PutSerialDeviceNoContent{} +} + +/* +PutSerialDeviceNoContent describes a response with status code 204, with default header values. + +Serial device configured +*/ +type PutSerialDeviceNoContent struct { +} + +// IsSuccess returns true when this put serial device no content response has a 2xx status code +func (o *PutSerialDeviceNoContent) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this put serial device no content response has a 3xx status code +func (o *PutSerialDeviceNoContent) IsRedirect() bool { + return false +} + +// IsClientError returns true when this put serial device no content response has a 4xx status code +func (o *PutSerialDeviceNoContent) IsClientError() bool { + return false +} + +// IsServerError returns true when this put serial device no content response has a 5xx status code +func (o *PutSerialDeviceNoContent) IsServerError() bool { + return false +} + +// IsCode returns true when this put serial device no content response a status code equal to that given +func (o *PutSerialDeviceNoContent) IsCode(code int) bool { + return code == 204 +} + +// Code gets the status code for the put serial device no content response +func (o *PutSerialDeviceNoContent) Code() int { + return 204 +} + +func (o *PutSerialDeviceNoContent) Error() string { + return fmt.Sprintf("[PUT /serial][%d] putSerialDeviceNoContent", 204) +} + +func (o *PutSerialDeviceNoContent) String() string { + return fmt.Sprintf("[PUT /serial][%d] putSerialDeviceNoContent", 204) +} + +func (o *PutSerialDeviceNoContent) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +// NewPutSerialDeviceDefault creates a PutSerialDeviceDefault with default headers values +func NewPutSerialDeviceDefault(code int) *PutSerialDeviceDefault { + return &PutSerialDeviceDefault{ + _statusCode: code, + } +} + +/* +PutSerialDeviceDefault describes a response with status code -1, with default header values. + +Internal server error +*/ +type PutSerialDeviceDefault struct { + _statusCode int + + Payload *models.Error +} + +// IsSuccess returns true when this put serial device default response has a 2xx status code +func (o *PutSerialDeviceDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this put serial device default response has a 3xx status code +func (o *PutSerialDeviceDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this put serial device default response has a 4xx status code +func (o *PutSerialDeviceDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this put serial device default response has a 5xx status code +func (o *PutSerialDeviceDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this put serial device default response a status code equal to that given +func (o *PutSerialDeviceDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the put serial device default response +func (o *PutSerialDeviceDefault) Code() int { + return o._statusCode +} + +func (o *PutSerialDeviceDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PUT /serial][%d] putSerialDevice default %s", o._statusCode, payload) +} + +func (o *PutSerialDeviceDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PUT /serial][%d] putSerialDevice default %s", o._statusCode, payload) +} + +func (o *PutSerialDeviceDefault) GetPayload() *models.Error { + return o.Payload +} + +func (o *PutSerialDeviceDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/start_balloon_hinting_parameters.go b/packages/shared/pkg/fc/client/operations/start_balloon_hinting_parameters.go new file mode 100644 index 0000000000..e11fe84d31 --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/start_balloon_hinting_parameters.go @@ -0,0 +1,150 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// NewStartBalloonHintingParams creates a new StartBalloonHintingParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewStartBalloonHintingParams() *StartBalloonHintingParams { + return &StartBalloonHintingParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewStartBalloonHintingParamsWithTimeout creates a new StartBalloonHintingParams object +// with the ability to set a timeout on a request. +func NewStartBalloonHintingParamsWithTimeout(timeout time.Duration) *StartBalloonHintingParams { + return &StartBalloonHintingParams{ + timeout: timeout, + } +} + +// NewStartBalloonHintingParamsWithContext creates a new StartBalloonHintingParams object +// with the ability to set a context for a request. +func NewStartBalloonHintingParamsWithContext(ctx context.Context) *StartBalloonHintingParams { + return &StartBalloonHintingParams{ + Context: ctx, + } +} + +// NewStartBalloonHintingParamsWithHTTPClient creates a new StartBalloonHintingParams object +// with the ability to set a custom HTTPClient for a request. +func NewStartBalloonHintingParamsWithHTTPClient(client *http.Client) *StartBalloonHintingParams { + return &StartBalloonHintingParams{ + HTTPClient: client, + } +} + +/* +StartBalloonHintingParams contains all the parameters to send to the API endpoint + + for the start balloon hinting operation. + + Typically these are written to a http.Request. +*/ +type StartBalloonHintingParams struct { + + /* Body. + + When the device completes the hinting whether we should automatically ack this. + */ + Body *models.BalloonStartCmd + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the start balloon hinting params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *StartBalloonHintingParams) WithDefaults() *StartBalloonHintingParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the start balloon hinting params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *StartBalloonHintingParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the start balloon hinting params +func (o *StartBalloonHintingParams) WithTimeout(timeout time.Duration) *StartBalloonHintingParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the start balloon hinting params +func (o *StartBalloonHintingParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the start balloon hinting params +func (o *StartBalloonHintingParams) WithContext(ctx context.Context) *StartBalloonHintingParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the start balloon hinting params +func (o *StartBalloonHintingParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the start balloon hinting params +func (o *StartBalloonHintingParams) WithHTTPClient(client *http.Client) *StartBalloonHintingParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the start balloon hinting params +func (o *StartBalloonHintingParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithBody adds the body to the start balloon hinting params +func (o *StartBalloonHintingParams) WithBody(body *models.BalloonStartCmd) *StartBalloonHintingParams { + o.SetBody(body) + return o +} + +// SetBody adds the body to the start balloon hinting params +func (o *StartBalloonHintingParams) SetBody(body *models.BalloonStartCmd) { + o.Body = body +} + +// WriteToRequest writes these params to a swagger request +func (o *StartBalloonHintingParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + if o.Body != nil { + if err := r.SetBodyParam(o.Body); err != nil { + return err + } + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/start_balloon_hinting_responses.go b/packages/shared/pkg/fc/client/operations/start_balloon_hinting_responses.go new file mode 100644 index 0000000000..ab0c595034 --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/start_balloon_hinting_responses.go @@ -0,0 +1,247 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "encoding/json" + stderrors "errors" + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// StartBalloonHintingReader is a Reader for the StartBalloonHinting structure. +type StartBalloonHintingReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *StartBalloonHintingReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 200: + result := NewStartBalloonHintingOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + case 400: + result := NewStartBalloonHintingBadRequest() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + default: + result := NewStartBalloonHintingDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewStartBalloonHintingOK creates a StartBalloonHintingOK with default headers values +func NewStartBalloonHintingOK() *StartBalloonHintingOK { + return &StartBalloonHintingOK{} +} + +/* +StartBalloonHintingOK describes a response with status code 200, with default header values. + +Free page hinting run started. +*/ +type StartBalloonHintingOK struct { +} + +// IsSuccess returns true when this start balloon hinting o k response has a 2xx status code +func (o *StartBalloonHintingOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this start balloon hinting o k response has a 3xx status code +func (o *StartBalloonHintingOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this start balloon hinting o k response has a 4xx status code +func (o *StartBalloonHintingOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this start balloon hinting o k response has a 5xx status code +func (o *StartBalloonHintingOK) IsServerError() bool { + return false +} + +// IsCode returns true when this start balloon hinting o k response a status code equal to that given +func (o *StartBalloonHintingOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the start balloon hinting o k response +func (o *StartBalloonHintingOK) Code() int { + return 200 +} + +func (o *StartBalloonHintingOK) Error() string { + return fmt.Sprintf("[PATCH /balloon/hinting/start][%d] startBalloonHintingOK", 200) +} + +func (o *StartBalloonHintingOK) String() string { + return fmt.Sprintf("[PATCH /balloon/hinting/start][%d] startBalloonHintingOK", 200) +} + +func (o *StartBalloonHintingOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +// NewStartBalloonHintingBadRequest creates a StartBalloonHintingBadRequest with default headers values +func NewStartBalloonHintingBadRequest() *StartBalloonHintingBadRequest { + return &StartBalloonHintingBadRequest{} +} + +/* +StartBalloonHintingBadRequest describes a response with status code 400, with default header values. + +The balloon free hinting was not enabled when the device was configured. +*/ +type StartBalloonHintingBadRequest struct { + Payload *models.Error +} + +// IsSuccess returns true when this start balloon hinting bad request response has a 2xx status code +func (o *StartBalloonHintingBadRequest) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this start balloon hinting bad request response has a 3xx status code +func (o *StartBalloonHintingBadRequest) IsRedirect() bool { + return false +} + +// IsClientError returns true when this start balloon hinting bad request response has a 4xx status code +func (o *StartBalloonHintingBadRequest) IsClientError() bool { + return true +} + +// IsServerError returns true when this start balloon hinting bad request response has a 5xx status code +func (o *StartBalloonHintingBadRequest) IsServerError() bool { + return false +} + +// IsCode returns true when this start balloon hinting bad request response a status code equal to that given +func (o *StartBalloonHintingBadRequest) IsCode(code int) bool { + return code == 400 +} + +// Code gets the status code for the start balloon hinting bad request response +func (o *StartBalloonHintingBadRequest) Code() int { + return 400 +} + +func (o *StartBalloonHintingBadRequest) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PATCH /balloon/hinting/start][%d] startBalloonHintingBadRequest %s", 400, payload) +} + +func (o *StartBalloonHintingBadRequest) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PATCH /balloon/hinting/start][%d] startBalloonHintingBadRequest %s", 400, payload) +} + +func (o *StartBalloonHintingBadRequest) GetPayload() *models.Error { + return o.Payload +} + +func (o *StartBalloonHintingBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewStartBalloonHintingDefault creates a StartBalloonHintingDefault with default headers values +func NewStartBalloonHintingDefault(code int) *StartBalloonHintingDefault { + return &StartBalloonHintingDefault{ + _statusCode: code, + } +} + +/* +StartBalloonHintingDefault describes a response with status code -1, with default header values. + +Internal Server Error +*/ +type StartBalloonHintingDefault struct { + _statusCode int + + Payload *models.Error +} + +// IsSuccess returns true when this start balloon hinting default response has a 2xx status code +func (o *StartBalloonHintingDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this start balloon hinting default response has a 3xx status code +func (o *StartBalloonHintingDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this start balloon hinting default response has a 4xx status code +func (o *StartBalloonHintingDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this start balloon hinting default response has a 5xx status code +func (o *StartBalloonHintingDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this start balloon hinting default response a status code equal to that given +func (o *StartBalloonHintingDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the start balloon hinting default response +func (o *StartBalloonHintingDefault) Code() int { + return o._statusCode +} + +func (o *StartBalloonHintingDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PATCH /balloon/hinting/start][%d] startBalloonHinting default %s", o._statusCode, payload) +} + +func (o *StartBalloonHintingDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PATCH /balloon/hinting/start][%d] startBalloonHinting default %s", o._statusCode, payload) +} + +func (o *StartBalloonHintingDefault) GetPayload() *models.Error { + return o.Payload +} + +func (o *StartBalloonHintingDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/stop_balloon_hinting_parameters.go b/packages/shared/pkg/fc/client/operations/stop_balloon_hinting_parameters.go new file mode 100644 index 0000000000..8cb0986560 --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/stop_balloon_hinting_parameters.go @@ -0,0 +1,125 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewStopBalloonHintingParams creates a new StopBalloonHintingParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewStopBalloonHintingParams() *StopBalloonHintingParams { + return &StopBalloonHintingParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewStopBalloonHintingParamsWithTimeout creates a new StopBalloonHintingParams object +// with the ability to set a timeout on a request. +func NewStopBalloonHintingParamsWithTimeout(timeout time.Duration) *StopBalloonHintingParams { + return &StopBalloonHintingParams{ + timeout: timeout, + } +} + +// NewStopBalloonHintingParamsWithContext creates a new StopBalloonHintingParams object +// with the ability to set a context for a request. +func NewStopBalloonHintingParamsWithContext(ctx context.Context) *StopBalloonHintingParams { + return &StopBalloonHintingParams{ + Context: ctx, + } +} + +// NewStopBalloonHintingParamsWithHTTPClient creates a new StopBalloonHintingParams object +// with the ability to set a custom HTTPClient for a request. +func NewStopBalloonHintingParamsWithHTTPClient(client *http.Client) *StopBalloonHintingParams { + return &StopBalloonHintingParams{ + HTTPClient: client, + } +} + +/* +StopBalloonHintingParams contains all the parameters to send to the API endpoint + + for the stop balloon hinting operation. + + Typically these are written to a http.Request. +*/ +type StopBalloonHintingParams struct { + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the stop balloon hinting params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *StopBalloonHintingParams) WithDefaults() *StopBalloonHintingParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the stop balloon hinting params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *StopBalloonHintingParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the stop balloon hinting params +func (o *StopBalloonHintingParams) WithTimeout(timeout time.Duration) *StopBalloonHintingParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the stop balloon hinting params +func (o *StopBalloonHintingParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the stop balloon hinting params +func (o *StopBalloonHintingParams) WithContext(ctx context.Context) *StopBalloonHintingParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the stop balloon hinting params +func (o *StopBalloonHintingParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the stop balloon hinting params +func (o *StopBalloonHintingParams) WithHTTPClient(client *http.Client) *StopBalloonHintingParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the stop balloon hinting params +func (o *StopBalloonHintingParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WriteToRequest writes these params to a swagger request +func (o *StopBalloonHintingParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/packages/shared/pkg/fc/client/operations/stop_balloon_hinting_responses.go b/packages/shared/pkg/fc/client/operations/stop_balloon_hinting_responses.go new file mode 100644 index 0000000000..d08561d4fd --- /dev/null +++ b/packages/shared/pkg/fc/client/operations/stop_balloon_hinting_responses.go @@ -0,0 +1,247 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +import ( + "encoding/json" + stderrors "errors" + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/e2b-dev/infra/packages/shared/pkg/fc/models" +) + +// StopBalloonHintingReader is a Reader for the StopBalloonHinting structure. +type StopBalloonHintingReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *StopBalloonHintingReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 200: + result := NewStopBalloonHintingOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + case 400: + result := NewStopBalloonHintingBadRequest() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + default: + result := NewStopBalloonHintingDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewStopBalloonHintingOK creates a StopBalloonHintingOK with default headers values +func NewStopBalloonHintingOK() *StopBalloonHintingOK { + return &StopBalloonHintingOK{} +} + +/* +StopBalloonHintingOK describes a response with status code 200, with default header values. + +Free page hinting run stopped. +*/ +type StopBalloonHintingOK struct { +} + +// IsSuccess returns true when this stop balloon hinting o k response has a 2xx status code +func (o *StopBalloonHintingOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this stop balloon hinting o k response has a 3xx status code +func (o *StopBalloonHintingOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this stop balloon hinting o k response has a 4xx status code +func (o *StopBalloonHintingOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this stop balloon hinting o k response has a 5xx status code +func (o *StopBalloonHintingOK) IsServerError() bool { + return false +} + +// IsCode returns true when this stop balloon hinting o k response a status code equal to that given +func (o *StopBalloonHintingOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the stop balloon hinting o k response +func (o *StopBalloonHintingOK) Code() int { + return 200 +} + +func (o *StopBalloonHintingOK) Error() string { + return fmt.Sprintf("[PATCH /balloon/hinting/stop][%d] stopBalloonHintingOK", 200) +} + +func (o *StopBalloonHintingOK) String() string { + return fmt.Sprintf("[PATCH /balloon/hinting/stop][%d] stopBalloonHintingOK", 200) +} + +func (o *StopBalloonHintingOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +// NewStopBalloonHintingBadRequest creates a StopBalloonHintingBadRequest with default headers values +func NewStopBalloonHintingBadRequest() *StopBalloonHintingBadRequest { + return &StopBalloonHintingBadRequest{} +} + +/* +StopBalloonHintingBadRequest describes a response with status code 400, with default header values. + +The balloon free hinting was not enabled when the device was configured. +*/ +type StopBalloonHintingBadRequest struct { + Payload *models.Error +} + +// IsSuccess returns true when this stop balloon hinting bad request response has a 2xx status code +func (o *StopBalloonHintingBadRequest) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this stop balloon hinting bad request response has a 3xx status code +func (o *StopBalloonHintingBadRequest) IsRedirect() bool { + return false +} + +// IsClientError returns true when this stop balloon hinting bad request response has a 4xx status code +func (o *StopBalloonHintingBadRequest) IsClientError() bool { + return true +} + +// IsServerError returns true when this stop balloon hinting bad request response has a 5xx status code +func (o *StopBalloonHintingBadRequest) IsServerError() bool { + return false +} + +// IsCode returns true when this stop balloon hinting bad request response a status code equal to that given +func (o *StopBalloonHintingBadRequest) IsCode(code int) bool { + return code == 400 +} + +// Code gets the status code for the stop balloon hinting bad request response +func (o *StopBalloonHintingBadRequest) Code() int { + return 400 +} + +func (o *StopBalloonHintingBadRequest) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PATCH /balloon/hinting/stop][%d] stopBalloonHintingBadRequest %s", 400, payload) +} + +func (o *StopBalloonHintingBadRequest) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PATCH /balloon/hinting/stop][%d] stopBalloonHintingBadRequest %s", 400, payload) +} + +func (o *StopBalloonHintingBadRequest) GetPayload() *models.Error { + return o.Payload +} + +func (o *StopBalloonHintingBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewStopBalloonHintingDefault creates a StopBalloonHintingDefault with default headers values +func NewStopBalloonHintingDefault(code int) *StopBalloonHintingDefault { + return &StopBalloonHintingDefault{ + _statusCode: code, + } +} + +/* +StopBalloonHintingDefault describes a response with status code -1, with default header values. + +Internal Server Error +*/ +type StopBalloonHintingDefault struct { + _statusCode int + + Payload *models.Error +} + +// IsSuccess returns true when this stop balloon hinting default response has a 2xx status code +func (o *StopBalloonHintingDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this stop balloon hinting default response has a 3xx status code +func (o *StopBalloonHintingDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this stop balloon hinting default response has a 4xx status code +func (o *StopBalloonHintingDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this stop balloon hinting default response has a 5xx status code +func (o *StopBalloonHintingDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this stop balloon hinting default response a status code equal to that given +func (o *StopBalloonHintingDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the stop balloon hinting default response +func (o *StopBalloonHintingDefault) Code() int { + return o._statusCode +} + +func (o *StopBalloonHintingDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PATCH /balloon/hinting/stop][%d] stopBalloonHinting default %s", o._statusCode, payload) +} + +func (o *StopBalloonHintingDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PATCH /balloon/hinting/stop][%d] stopBalloonHinting default %s", o._statusCode, payload) +} + +func (o *StopBalloonHintingDefault) GetPayload() *models.Error { + return o.Payload +} + +func (o *StopBalloonHintingDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} diff --git a/packages/shared/pkg/fc/firecracker.yml b/packages/shared/pkg/fc/firecracker.yml index 61ed7a61fd..af1cfb806c 100644 --- a/packages/shared/pkg/fc/firecracker.yml +++ b/packages/shared/pkg/fc/firecracker.yml @@ -5,7 +5,7 @@ info: The API is accessible through HTTP calls on specific URLs carrying JSON modeled data. The transport medium is a Unix Domain Socket. - version: 1.12.1 + version: 1.14.1 termsOfService: "" contact: email: "firecracker-maintainers@amazon.com" @@ -169,6 +169,63 @@ paths: schema: $ref: "#/definitions/Error" + /balloon/hinting/start: + patch: + summary: Starts a free page hinting run only if enabled pre-boot. + operationId: startBalloonHinting + parameters: + - name: body + in: body + description: When the device completes the hinting whether we should automatically ack this. + required: false + schema: + $ref: "#/definitions/BalloonStartCmd" + responses: + 200: + description: Free page hinting run started. + 400: + description: The balloon free hinting was not enabled when the device was configured. + schema: + $ref: "#/definitions/Error" + default: + description: Internal Server Error + schema: + $ref: "#/definitions/Error" + + /balloon/hinting/status: + get: + summary: Returns the balloon hinting statistics, only if enabled pre-boot. + operationId: describeBalloonHinting + responses: + 200: + description: The balloon free page hinting statistics + schema: + $ref: "#/definitions/BalloonHintingStatus" + 400: + description: The balloon free hinting was not enabled when the device was configured. + schema: + $ref: "#/definitions/Error" + default: + description: Internal Server Error + schema: + $ref: "#/definitions/Error" + + /balloon/hinting/stop: + patch: + summary: Stops a free page hinting run only if enabled pre-boot. + operationId: stopBalloonHinting + responses: + 200: + description: Free page hinting run stopped. + 400: + description: The balloon free hinting was not enabled when the device was configured. + schema: + $ref: "#/definitions/Error" + default: + description: Internal Server Error + schema: + $ref: "#/definitions/Error" + /boot-source: put: summary: Creates or updates the boot source. Pre-boot only. @@ -282,6 +339,38 @@ paths: schema: $ref: "#/definitions/Error" + /pmem/{id}: + put: + summary: Creates or updates a pmem device. Pre-boot only. + description: + Creates new pmem device with ID specified by id parameter. + If a pmem device with the specified ID already exists, updates its state based on new input. + Will fail if update is not possible. + operationId: putGuestPmemByID + parameters: + - name: id + in: path + description: The id of the guest pmem device + required: true + type: string + - name: body + in: body + description: Guest pmem device properties + required: true + schema: + $ref: "#/definitions/Pmem" + responses: + 204: + description: Pmem device is created/updated + 400: + description: Pmem device cannot be created/updated due to bad input + schema: + $ref: "#/definitions/Error" + default: + description: Internal server error. + schema: + $ref: "#/definitions/Error" + /logger: put: summary: Initializes the logger by specifying a named pipe or a file for the logs output. @@ -450,6 +539,7 @@ paths: description: The MMDS data store JSON. schema: type: object + additionalProperties: true 404: description: The MMDS data store content can not be found. schema: @@ -506,6 +596,84 @@ paths: schema: $ref: "#/definitions/Error" + /serial: + put: + summary: Configures the serial console + operationId: putSerialDevice + description: + Configure the serial console, which the guest can write its kernel logs to. Has no effect if + the serial console is not also enabled on the guest kernel command line + parameters: + - name: body + in: body + description: Serial console properties + required: true + schema: + $ref: "#/definitions/SerialDevice" + responses: + 204: + description: Serial device configured + default: + description: Internal server error + schema: + $ref: "#/definitions/Error" + + /hotplug/memory: + put: + summary: Configures the hotpluggable memory + operationId: putMemoryHotplug + description: + Configure the hotpluggable memory, which is a virtio-mem device, with an associated memory area + that can be hot(un)plugged in the guest on demand using the PATCH API. + parameters: + - name: body + in: body + description: Hotpluggable memory configuration + required: true + schema: + $ref: "#/definitions/MemoryHotplugConfig" + responses: + 204: + description: Hotpluggable memory configured + default: + description: Internal server error + schema: + $ref: "#/definitions/Error" + patch: + summary: Updates the size of the hotpluggable memory region + operationId: patchMemoryHotplug + description: + Updates the size of the hotpluggable memory region. The guest will plug and unplug memory to + hit the requested memory. + parameters: + - name: body + in: body + description: Hotpluggable memory size update + required: true + schema: + $ref: "#/definitions/MemoryHotplugSizeUpdate" + responses: + 204: + description: Hotpluggable memory configured + default: + description: Internal server error + schema: + $ref: "#/definitions/Error" + get: + summary: Retrieves the status of the hotpluggable memory + operationId: getMemoryHotplug + description: + Reuturn the status of the hotpluggable memory. This can be used to follow the progress of the guest + after a PATCH API. + responses: + 200: + description: OK + schema: + $ref: "#/definitions/MemoryHotplugStatus" + default: + description: Internal server error + schema: + $ref: "#/definitions/Error" /network-interfaces/{iface_id}: put: @@ -575,7 +743,7 @@ paths: parameters: - name: body in: body - description: The configuration used for creating a snaphot. + description: The configuration used for creating a snapshot. required: true schema: $ref: "#/definitions/SnapshotCreateParams" @@ -602,7 +770,7 @@ paths: parameters: - name: body in: body - description: The configuration used for loading a snaphot. + description: The configuration used for loading a snapshot. required: true schema: $ref: "#/definitions/SnapshotLoadParams" @@ -767,6 +935,12 @@ definitions: stats_polling_interval_s: type: integer description: Interval in seconds between refreshing statistics. A non-zero value will enable the statistics. Defaults to 0. + free_page_hinting: + type: boolean + description: Whether the free page hinting feature is enabled. + free_page_reporting: + type: boolean + description: Whether the free page reporting feature is enabled. BalloonUpdate: type: object @@ -841,6 +1015,53 @@ definitions: description: The number of failed hugetlb page allocations in the guest. type: integer format: int64 + oom_kill: + description: OOM killer invocations, indicating critical memory pressure. + type: integer + format: int64 + alloc_stall: + description: Counter of Allocation enter a slow path to gain more memory page. The reclaim/scan metrics can reveal what is actually happening. + type: integer + format: int64 + async_scan: + description: Amount of memory scanned asynchronously. + type: integer + format: int64 + direct_scan: + description: Amount of memory scanned directly. + type: integer + format: int64 + async_reclaim: + description: Amount of memory reclaimed asynchronously. + type: integer + format: int64 + direct_reclaim: + description: Amount of memory reclaimed directly. + type: integer + format: int64 + + BalloonStartCmd: + type: object + description: + Command used to start a free page hinting run. + properties: + acknowledge_on_stop: + description: If Firecracker should automatically acknowledge when the guest submits a done cmd. + type: boolean + + BalloonHintingStatus: + type: object + description: + Describes the free page hinting status. + required: + - host_cmd + properties: + host_cmd: + description: The last command issued by the host. + type: integer + guest_cmd: + description: The last command provided by the guest. + type: integer BalloonStatsUpdate: type: object @@ -893,21 +1114,119 @@ definitions: The CPU configuration template defines a set of bit maps as modifiers of flags accessed by register to be disabled/enabled for the microvm. properties: + kvm_capabilities: + type: array + description: A collection of KVM capabilities to be added or removed (both x86_64 and aarch64) + items: + type: string + description: KVM capability as a numeric string. Prefix with '!' to remove capability. Example "121" (add) or "!121" (remove) cpuid_modifiers: - type: object - description: A collection of CPUIDs to be modified. (x86_64) + type: array + description: A collection of CPUID leaf modifiers (x86_64 only) + items: + $ref: "#/definitions/CpuidLeafModifier" msr_modifiers: - type: object - description: A collection of model specific registers to be modified. (x86_64) + type: array + description: A collection of model specific register modifiers (x86_64 only) + items: + $ref: "#/definitions/MsrModifier" reg_modifiers: - type: object - description: A collection of registers to be modified. (aarch64) + type: array + description: A collection of register modifiers (aarch64 only) + items: + $ref: "#/definitions/ArmRegisterModifier" vcpu_features: - type: object - description: A collection of vcpu features to be modified. (aarch64) - kvm_capabilities: - type: object - description: A collection of kvm capabilities to be modified. (aarch64) + type: array + description: A collection of vCPU features to be modified (aarch64 only) + items: + $ref: "#/definitions/VcpuFeatures" + + CpuidLeafModifier: + type: object + description: Modifier for a CPUID leaf and subleaf (x86_64) + required: + - leaf + - subleaf + - flags + - modifiers + properties: + leaf: + type: string + description: CPUID leaf index as hex, binary, or decimal string (e.g., "0x0", "0b0", "0")) + subleaf: + type: string + description: CPUID subleaf index as hex, binary, or decimal string (e.g., "0x0", "0b0", "0") + flags: + type: integer + format: int32 + description: KVM feature flags for this leaf-subleaf + modifiers: + type: array + description: Register modifiers for this CPUID leaf + items: + $ref: "#/definitions/CpuidRegisterModifier" + + CpuidRegisterModifier: + type: object + description: Modifier for a specific CPUID register within a leaf (x86_64) + required: + - register + - bitmap + properties: + register: + type: string + description: Target CPUID register name + enum: + - eax + - ebx + - ecx + - edx + bitmap: + type: string + description: 32-bit bitmap string defining which bits to modify. Format is "0b" followed by 32 characters where '0' = clear bit, '1' = set bit, 'x' = don't modify. Example "0b00000000000000000000000000000001" or "0bxxxxxxxxxxxxxxxxxxxxxxxxxxxx0001" + + MsrModifier: + type: object + description: Modifier for a model specific register (x86_64) + required: + - addr + - bitmap + properties: + addr: + type: string + description: 32-bit MSR address as hex, binary, or decimal string (e.g., "0x10a", "0b100001010", "266") + bitmap: + type: string + description: 64-bit bitmap string defining which bits to modify. Format is "0b" followed by 64 characters where '0' = clear bit, '1' = set bit, 'x' = don't modify. Underscores can be used for readability. Example "0b0000000000000000000000000000000000000000000000000000000000000001" + + ArmRegisterModifier: + type: object + description: Modifier for an ARM register (aarch64) + required: + - addr + - bitmap + properties: + addr: + type: string + description: 64-bit register address as hex, binary, or decimal string (e.g., "0x0", "0b0", "0") + bitmap: + type: string + description: 128-bit bitmap string defining which bits to modify. Format is "0b" followed by up to 128 characters where '0' = clear bit, '1' = set bit, 'x' = don't modify. Underscores can be used for readability. Example "0b0000000000000000000000000000000000000000000000000000000000000001" + + VcpuFeatures: + type: object + description: vCPU feature modifier (aarch64) + required: + - index + - bitmap + properties: + index: + type: integer + format: int32 + description: Index in the kvm_vcpu_init.features array + bitmap: + type: string + description: 32-bit bitmap string defining which bits to modify. Format is "0b" followed by 32 characters where '0' = clear bit, '1' = set bit, 'x' = don't modify. Example "0b00000000000000000000000001100000" Drive: type: object @@ -936,7 +1255,7 @@ definitions: is_read_only: type: boolean description: - Is block read only. + Is block read only. This field is required for virtio-block config and should be omitted for vhost-user-block configuration. path_on_host: type: string @@ -961,6 +1280,30 @@ definitions: Path to the socket of vhost-user-block backend. This field is required for vhost-user-block config should be omitted for virtio-block configuration. + Pmem: + type: object + required: + - id + - path_on_host + properties: + id: + type: string + description: + Identificator for this device. + path_on_host: + type: string + description: + Host level path for the virtio-pmem device to use as a backing file. + root_device: + type: boolean + description: + Flag to make this device be the root device for VM boot. + Setting this flag will fail if there is another device configured to be a root device already. + read_only: + type: boolean + description: + Flag to map backing file in read-only mode. + Error: type: object properties: @@ -989,6 +1332,8 @@ definitions: $ref: "#/definitions/MachineConfiguration" metrics: $ref: "#/definitions/Metrics" + memory-hotplug: + $ref: "#/definitions/MemoryHotplugConfig" mmds-config: $ref: "#/definitions/MmdsConfig" network-interfaces: @@ -996,6 +1341,11 @@ definitions: description: Configurations for all net devices. items: $ref: "#/definitions/NetworkInterface" + pmem: + type: array + description: Configurations for all pmem devices. + items: + $ref: "#/definitions/Pmem" vsock: $ref: "#/definitions/Vsock" entropy: @@ -1239,11 +1589,18 @@ definitions: format: "169.254.([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-4]).([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])" default: "169.254.169.254" description: A valid IPv4 link-local address. + imds_compat: + type: boolean + description: + MMDS operates compatibly with EC2 IMDS (i.e. responds "text/plain" + content regardless of Accept header in requests). + default: false MmdsContentsObject: type: object description: Describes the contents of MMDS in JSON format. + additionalProperties: true NetworkInterface: type: object @@ -1316,7 +1673,10 @@ definitions: properties: mem_file_path: type: string - description: Path to the file that will contain the guest memory. + description: + Path to the file that will contain the guest memory. It is optional. + In case that a user doesn't provide a path, they are responsible to + ensure they store the microVM's memory state via external means. snapshot_path: type: string description: Path to the file that will contain the microVM state. @@ -1358,7 +1718,11 @@ definitions: enable_diff_snapshots: type: boolean description: - Enable support for incremental (diff) snapshots by tracking dirty guest pages. + (Deprecated) Enable dirty page tracking to improve space efficiency of diff snapshots + track_dirty_pages: + type: boolean + description: + Enable dirty page tracking to improve space efficiency of diff snapshots mem_file_path: type: string description: @@ -1438,6 +1802,66 @@ definitions: rate_limiter: $ref: "#/definitions/RateLimiter" + SerialDevice: + type: object + description: + The configuration of the serial device + properties: + serial_out_path: + type: string + description: Path to a file or named pipe on the host to which serial output should be written. + + MemoryHotplugConfig: + type: object + description: + The configuration of the hotpluggable memory device (virtio-mem) + properties: + total_size_mib: + type: integer + description: Total size of the hotpluggable memory in MiB. + slot_size_mib: + type: integer + default: 128 + minimum: 128 + description: Slot size for the hotpluggable memory in MiB. This will determine the granularity of + hot-plug memory from the host. Refer to the device documentation on how to tune this value. + block_size_mib: + type: integer + default: 2 + minimum: 2 + description: (Logical) Block size for the hotpluggable memory in MiB. This will determine the logical + granularity of hot-plug memory for the guest. Refer to the device documentation on how to tune this value. + + MemoryHotplugSizeUpdate: + type: object + description: + An update to the size of the hotpluggable memory region. + properties: + requested_size_mib: + type: integer + description: New target region size. + + MemoryHotplugStatus: + type: object + description: + The status of the hotpluggable memory device (virtio-mem) + properties: + total_size_mib: + type: integer + description: Total size of the hotpluggable memory in MiB. + slot_size_mib: + type: integer + description: Slot size for the hotpluggable memory in MiB. + block_size_mib: + type: integer + description: (Logical) Block size for the hotpluggable memory in MiB. + plugged_size_mib: + type: integer + description: Plugged size for the hotpluggable memory in MiB. + requested_size_mib: + type: integer + description: Requested size for the hotpluggable memory in MiB. + FirecrackerVersion: type: object description: diff --git a/packages/shared/pkg/fc/models/arm_register_modifier.go b/packages/shared/pkg/fc/models/arm_register_modifier.go new file mode 100644 index 0000000000..1c6c79119f --- /dev/null +++ b/packages/shared/pkg/fc/models/arm_register_modifier.go @@ -0,0 +1,85 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// ArmRegisterModifier Modifier for an ARM register (aarch64) +// +// swagger:model ArmRegisterModifier +type ArmRegisterModifier struct { + + // 64-bit register address as hex, binary, or decimal string (e.g., "0x0", "0b0", "0") + // Required: true + Addr *string `json:"addr"` + + // 128-bit bitmap string defining which bits to modify. Format is "0b" followed by up to 128 characters where '0' = clear bit, '1' = set bit, 'x' = don't modify. Underscores can be used for readability. Example "0b0000000000000000000000000000000000000000000000000000000000000001" + // Required: true + Bitmap *string `json:"bitmap"` +} + +// Validate validates this arm register modifier +func (m *ArmRegisterModifier) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateAddr(formats); err != nil { + res = append(res, err) + } + + if err := m.validateBitmap(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ArmRegisterModifier) validateAddr(formats strfmt.Registry) error { + + if err := validate.Required("addr", "body", m.Addr); err != nil { + return err + } + + return nil +} + +func (m *ArmRegisterModifier) validateBitmap(formats strfmt.Registry) error { + + if err := validate.Required("bitmap", "body", m.Bitmap); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this arm register modifier based on context it is used +func (m *ArmRegisterModifier) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *ArmRegisterModifier) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ArmRegisterModifier) UnmarshalBinary(b []byte) error { + var res ArmRegisterModifier + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/packages/shared/pkg/fc/models/balloon.go b/packages/shared/pkg/fc/models/balloon.go index ce73d06a8b..b209c8f1cd 100644 --- a/packages/shared/pkg/fc/models/balloon.go +++ b/packages/shared/pkg/fc/models/balloon.go @@ -24,6 +24,12 @@ type Balloon struct { // Required: true DeflateOnOom *bool `json:"deflate_on_oom"` + // Whether the free page hinting feature is enabled. + FreePageHinting bool `json:"free_page_hinting,omitempty"` + + // Whether the free page reporting feature is enabled. + FreePageReporting bool `json:"free_page_reporting,omitempty"` + // Interval in seconds between refreshing statistics. A non-zero value will enable the statistics. Defaults to 0. StatsPollingIntervals int64 `json:"stats_polling_interval_s,omitempty"` } diff --git a/packages/shared/pkg/fc/models/balloon_hinting_status.go b/packages/shared/pkg/fc/models/balloon_hinting_status.go new file mode 100644 index 0000000000..4b7c0b456e --- /dev/null +++ b/packages/shared/pkg/fc/models/balloon_hinting_status.go @@ -0,0 +1,71 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// BalloonHintingStatus Describes the free page hinting status. +// +// swagger:model BalloonHintingStatus +type BalloonHintingStatus struct { + + // The last command provided by the guest. + GuestCmd int64 `json:"guest_cmd,omitempty"` + + // The last command issued by the host. + // Required: true + HostCmd *int64 `json:"host_cmd"` +} + +// Validate validates this balloon hinting status +func (m *BalloonHintingStatus) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateHostCmd(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *BalloonHintingStatus) validateHostCmd(formats strfmt.Registry) error { + + if err := validate.Required("host_cmd", "body", m.HostCmd); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this balloon hinting status based on context it is used +func (m *BalloonHintingStatus) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *BalloonHintingStatus) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *BalloonHintingStatus) UnmarshalBinary(b []byte) error { + var res BalloonHintingStatus + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/packages/shared/pkg/fc/models/balloon_start_cmd.go b/packages/shared/pkg/fc/models/balloon_start_cmd.go new file mode 100644 index 0000000000..d0b38243cc --- /dev/null +++ b/packages/shared/pkg/fc/models/balloon_start_cmd.go @@ -0,0 +1,47 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// BalloonStartCmd Command used to start a free page hinting run. +// +// swagger:model BalloonStartCmd +type BalloonStartCmd struct { + + // If Firecracker should automatically acknowledge when the guest submits a done cmd. + AcknowledgeOnStop bool `json:"acknowledge_on_stop,omitempty"` +} + +// Validate validates this balloon start cmd +func (m *BalloonStartCmd) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this balloon start cmd based on context it is used +func (m *BalloonStartCmd) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *BalloonStartCmd) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *BalloonStartCmd) UnmarshalBinary(b []byte) error { + var res BalloonStartCmd + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/packages/shared/pkg/fc/models/balloon_stats.go b/packages/shared/pkg/fc/models/balloon_stats.go index e32e9d8e58..a80c80867a 100644 --- a/packages/shared/pkg/fc/models/balloon_stats.go +++ b/packages/shared/pkg/fc/models/balloon_stats.go @@ -24,9 +24,24 @@ type BalloonStats struct { // Required: true ActualPages *int64 `json:"actual_pages"` + // Counter of Allocation enter a slow path to gain more memory page. The reclaim/scan metrics can reveal what is actually happening. + AllocStall int64 `json:"alloc_stall,omitempty"` + + // Amount of memory reclaimed asynchronously. + AsyncReclaim int64 `json:"async_reclaim,omitempty"` + + // Amount of memory scanned asynchronously. + AsyncScan int64 `json:"async_scan,omitempty"` + // An estimate of how much memory is available (in bytes) for starting new applications, without pushing the system to swap. AvailableMemory int64 `json:"available_memory,omitempty"` + // Amount of memory reclaimed directly. + DirectReclaim int64 `json:"direct_reclaim,omitempty"` + + // Amount of memory scanned directly. + DirectScan int64 `json:"direct_scan,omitempty"` + // The amount of memory, in bytes, that can be quickly reclaimed without additional I/O. Typically these pages are used for caching files from disk. DiskCaches int64 `json:"disk_caches,omitempty"` @@ -45,6 +60,9 @@ type BalloonStats struct { // The number of minor page faults that have occurred. MinorFaults int64 `json:"minor_faults,omitempty"` + // OOM killer invocations, indicating critical memory pressure. + OomKill int64 `json:"oom_kill,omitempty"` + // The amount of memory that has been swapped in (in bytes). SwapIn int64 `json:"swap_in,omitempty"` diff --git a/packages/shared/pkg/fc/models/cpu_config.go b/packages/shared/pkg/fc/models/cpu_config.go index c3c9927f90..7b5f5d03b6 100644 --- a/packages/shared/pkg/fc/models/cpu_config.go +++ b/packages/shared/pkg/fc/models/cpu_config.go @@ -4,7 +4,10 @@ package models import ( "context" + stderrors "errors" + "strconv" + "github.com/go-openapi/errors" "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" ) @@ -14,29 +17,307 @@ import ( // swagger:model CpuConfig type CPUConfig struct { - // A collection of CPUIDs to be modified. (x86_64) - CpuidModifiers any `json:"cpuid_modifiers,omitempty"` + // A collection of CPUID leaf modifiers (x86_64 only) + CpuidModifiers []*CpuidLeafModifier `json:"cpuid_modifiers"` - // A collection of kvm capabilities to be modified. (aarch64) - KvmCapabilities any `json:"kvm_capabilities,omitempty"` + // A collection of KVM capabilities to be added or removed (both x86_64 and aarch64) + KvmCapabilities []string `json:"kvm_capabilities"` - // A collection of model specific registers to be modified. (x86_64) - MsrModifiers any `json:"msr_modifiers,omitempty"` + // A collection of model specific register modifiers (x86_64 only) + MsrModifiers []*MsrModifier `json:"msr_modifiers"` - // A collection of registers to be modified. (aarch64) - RegModifiers any `json:"reg_modifiers,omitempty"` + // A collection of register modifiers (aarch64 only) + RegModifiers []*ArmRegisterModifier `json:"reg_modifiers"` - // A collection of vcpu features to be modified. (aarch64) - VcpuFeatures any `json:"vcpu_features,omitempty"` + // A collection of vCPU features to be modified (aarch64 only) + VcpuFeatures []*VcpuFeatures `json:"vcpu_features"` } // Validate validates this Cpu config func (m *CPUConfig) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateCpuidModifiers(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMsrModifiers(formats); err != nil { + res = append(res, err) + } + + if err := m.validateRegModifiers(formats); err != nil { + res = append(res, err) + } + + if err := m.validateVcpuFeatures(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *CPUConfig) validateCpuidModifiers(formats strfmt.Registry) error { + if swag.IsZero(m.CpuidModifiers) { // not required + return nil + } + + for i := 0; i < len(m.CpuidModifiers); i++ { + if swag.IsZero(m.CpuidModifiers[i]) { // not required + continue + } + + if m.CpuidModifiers[i] != nil { + if err := m.CpuidModifiers[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("cpuid_modifiers" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("cpuid_modifiers" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +func (m *CPUConfig) validateMsrModifiers(formats strfmt.Registry) error { + if swag.IsZero(m.MsrModifiers) { // not required + return nil + } + + for i := 0; i < len(m.MsrModifiers); i++ { + if swag.IsZero(m.MsrModifiers[i]) { // not required + continue + } + + if m.MsrModifiers[i] != nil { + if err := m.MsrModifiers[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("msr_modifiers" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("msr_modifiers" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +func (m *CPUConfig) validateRegModifiers(formats strfmt.Registry) error { + if swag.IsZero(m.RegModifiers) { // not required + return nil + } + + for i := 0; i < len(m.RegModifiers); i++ { + if swag.IsZero(m.RegModifiers[i]) { // not required + continue + } + + if m.RegModifiers[i] != nil { + if err := m.RegModifiers[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("reg_modifiers" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("reg_modifiers" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + return nil } -// ContextValidate validates this Cpu config based on context it is used +func (m *CPUConfig) validateVcpuFeatures(formats strfmt.Registry) error { + if swag.IsZero(m.VcpuFeatures) { // not required + return nil + } + + for i := 0; i < len(m.VcpuFeatures); i++ { + if swag.IsZero(m.VcpuFeatures[i]) { // not required + continue + } + + if m.VcpuFeatures[i] != nil { + if err := m.VcpuFeatures[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("vcpu_features" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("vcpu_features" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this Cpu config based on the context it is used func (m *CPUConfig) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateCpuidModifiers(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateMsrModifiers(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateRegModifiers(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateVcpuFeatures(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *CPUConfig) contextValidateCpuidModifiers(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.CpuidModifiers); i++ { + + if m.CpuidModifiers[i] != nil { + + if swag.IsZero(m.CpuidModifiers[i]) { // not required + return nil + } + + if err := m.CpuidModifiers[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("cpuid_modifiers" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("cpuid_modifiers" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +func (m *CPUConfig) contextValidateMsrModifiers(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.MsrModifiers); i++ { + + if m.MsrModifiers[i] != nil { + + if swag.IsZero(m.MsrModifiers[i]) { // not required + return nil + } + + if err := m.MsrModifiers[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("msr_modifiers" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("msr_modifiers" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +func (m *CPUConfig) contextValidateRegModifiers(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.RegModifiers); i++ { + + if m.RegModifiers[i] != nil { + + if swag.IsZero(m.RegModifiers[i]) { // not required + return nil + } + + if err := m.RegModifiers[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("reg_modifiers" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("reg_modifiers" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +func (m *CPUConfig) contextValidateVcpuFeatures(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.VcpuFeatures); i++ { + + if m.VcpuFeatures[i] != nil { + + if swag.IsZero(m.VcpuFeatures[i]) { // not required + return nil + } + + if err := m.VcpuFeatures[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("vcpu_features" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("vcpu_features" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + return nil } diff --git a/packages/shared/pkg/fc/models/cpuid_leaf_modifier.go b/packages/shared/pkg/fc/models/cpuid_leaf_modifier.go new file mode 100644 index 0000000000..9ace041fef --- /dev/null +++ b/packages/shared/pkg/fc/models/cpuid_leaf_modifier.go @@ -0,0 +1,181 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + stderrors "errors" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// CpuidLeafModifier Modifier for a CPUID leaf and subleaf (x86_64) +// +// swagger:model CpuidLeafModifier +type CpuidLeafModifier struct { + + // KVM feature flags for this leaf-subleaf + // Required: true + Flags *int32 `json:"flags"` + + // CPUID leaf index as hex, binary, or decimal string (e.g., "0x0", "0b0", "0")) + // Required: true + Leaf *string `json:"leaf"` + + // Register modifiers for this CPUID leaf + // Required: true + Modifiers []*CpuidRegisterModifier `json:"modifiers"` + + // CPUID subleaf index as hex, binary, or decimal string (e.g., "0x0", "0b0", "0") + // Required: true + Subleaf *string `json:"subleaf"` +} + +// Validate validates this cpuid leaf modifier +func (m *CpuidLeafModifier) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateFlags(formats); err != nil { + res = append(res, err) + } + + if err := m.validateLeaf(formats); err != nil { + res = append(res, err) + } + + if err := m.validateModifiers(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSubleaf(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *CpuidLeafModifier) validateFlags(formats strfmt.Registry) error { + + if err := validate.Required("flags", "body", m.Flags); err != nil { + return err + } + + return nil +} + +func (m *CpuidLeafModifier) validateLeaf(formats strfmt.Registry) error { + + if err := validate.Required("leaf", "body", m.Leaf); err != nil { + return err + } + + return nil +} + +func (m *CpuidLeafModifier) validateModifiers(formats strfmt.Registry) error { + + if err := validate.Required("modifiers", "body", m.Modifiers); err != nil { + return err + } + + for i := 0; i < len(m.Modifiers); i++ { + if swag.IsZero(m.Modifiers[i]) { // not required + continue + } + + if m.Modifiers[i] != nil { + if err := m.Modifiers[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("modifiers" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("modifiers" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +func (m *CpuidLeafModifier) validateSubleaf(formats strfmt.Registry) error { + + if err := validate.Required("subleaf", "body", m.Subleaf); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this cpuid leaf modifier based on the context it is used +func (m *CpuidLeafModifier) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateModifiers(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *CpuidLeafModifier) contextValidateModifiers(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Modifiers); i++ { + + if m.Modifiers[i] != nil { + + if swag.IsZero(m.Modifiers[i]) { // not required + return nil + } + + if err := m.Modifiers[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("modifiers" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("modifiers" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// MarshalBinary interface implementation +func (m *CpuidLeafModifier) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *CpuidLeafModifier) UnmarshalBinary(b []byte) error { + var res CpuidLeafModifier + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/packages/shared/pkg/fc/models/cpuid_register_modifier.go b/packages/shared/pkg/fc/models/cpuid_register_modifier.go new file mode 100644 index 0000000000..9c77a5b28a --- /dev/null +++ b/packages/shared/pkg/fc/models/cpuid_register_modifier.go @@ -0,0 +1,127 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// CpuidRegisterModifier Modifier for a specific CPUID register within a leaf (x86_64) +// +// swagger:model CpuidRegisterModifier +type CpuidRegisterModifier struct { + + // 32-bit bitmap string defining which bits to modify. Format is "0b" followed by 32 characters where '0' = clear bit, '1' = set bit, 'x' = don't modify. Example "0b00000000000000000000000000000001" or "0bxxxxxxxxxxxxxxxxxxxxxxxxxxxx0001" + // Required: true + Bitmap *string `json:"bitmap"` + + // Target CPUID register name + // Required: true + // Enum: ["eax","ebx","ecx","edx"] + Register *string `json:"register"` +} + +// Validate validates this cpuid register modifier +func (m *CpuidRegisterModifier) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateBitmap(formats); err != nil { + res = append(res, err) + } + + if err := m.validateRegister(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *CpuidRegisterModifier) validateBitmap(formats strfmt.Registry) error { + + if err := validate.Required("bitmap", "body", m.Bitmap); err != nil { + return err + } + + return nil +} + +var cpuidRegisterModifierTypeRegisterPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["eax","ebx","ecx","edx"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + cpuidRegisterModifierTypeRegisterPropEnum = append(cpuidRegisterModifierTypeRegisterPropEnum, v) + } +} + +const ( + + // CpuidRegisterModifierRegisterEax captures enum value "eax" + CpuidRegisterModifierRegisterEax string = "eax" + + // CpuidRegisterModifierRegisterEbx captures enum value "ebx" + CpuidRegisterModifierRegisterEbx string = "ebx" + + // CpuidRegisterModifierRegisterEcx captures enum value "ecx" + CpuidRegisterModifierRegisterEcx string = "ecx" + + // CpuidRegisterModifierRegisterEdx captures enum value "edx" + CpuidRegisterModifierRegisterEdx string = "edx" +) + +// prop value enum +func (m *CpuidRegisterModifier) validateRegisterEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, cpuidRegisterModifierTypeRegisterPropEnum, true); err != nil { + return err + } + return nil +} + +func (m *CpuidRegisterModifier) validateRegister(formats strfmt.Registry) error { + + if err := validate.Required("register", "body", m.Register); err != nil { + return err + } + + // value enum + if err := m.validateRegisterEnum("register", "body", *m.Register); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this cpuid register modifier based on context it is used +func (m *CpuidRegisterModifier) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *CpuidRegisterModifier) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *CpuidRegisterModifier) UnmarshalBinary(b []byte) error { + var res CpuidRegisterModifier + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/packages/shared/pkg/fc/models/full_vm_configuration.go b/packages/shared/pkg/fc/models/full_vm_configuration.go index 89131f61ed..b53f74760e 100644 --- a/packages/shared/pkg/fc/models/full_vm_configuration.go +++ b/packages/shared/pkg/fc/models/full_vm_configuration.go @@ -38,6 +38,9 @@ type FullVMConfiguration struct { // machine config MachineConfig *MachineConfiguration `json:"machine-config,omitempty"` + // memory hotplug + MemoryHotplug *MemoryHotplugConfig `json:"memory-hotplug,omitempty"` + // metrics Metrics *Metrics `json:"metrics,omitempty"` @@ -47,6 +50,9 @@ type FullVMConfiguration struct { // Configurations for all net devices. NetworkInterfaces []*NetworkInterface `json:"network-interfaces"` + // Configurations for all pmem devices. + Pmem []*Pmem `json:"pmem"` + // vsock Vsock *Vsock `json:"vsock,omitempty"` } @@ -83,6 +89,10 @@ func (m *FullVMConfiguration) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateMemoryHotplug(formats); err != nil { + res = append(res, err) + } + if err := m.validateMetrics(formats); err != nil { res = append(res, err) } @@ -95,6 +105,10 @@ func (m *FullVMConfiguration) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validatePmem(formats); err != nil { + res = append(res, err) + } + if err := m.validateVsock(formats); err != nil { res = append(res, err) } @@ -273,6 +287,29 @@ func (m *FullVMConfiguration) validateMachineConfig(formats strfmt.Registry) err return nil } +func (m *FullVMConfiguration) validateMemoryHotplug(formats strfmt.Registry) error { + if swag.IsZero(m.MemoryHotplug) { // not required + return nil + } + + if m.MemoryHotplug != nil { + if err := m.MemoryHotplug.Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("memory-hotplug") + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("memory-hotplug") + } + + return err + } + } + + return nil +} + func (m *FullVMConfiguration) validateMetrics(formats strfmt.Registry) error { if swag.IsZero(m.Metrics) { // not required return nil @@ -349,6 +386,36 @@ func (m *FullVMConfiguration) validateNetworkInterfaces(formats strfmt.Registry) return nil } +func (m *FullVMConfiguration) validatePmem(formats strfmt.Registry) error { + if swag.IsZero(m.Pmem) { // not required + return nil + } + + for i := 0; i < len(m.Pmem); i++ { + if swag.IsZero(m.Pmem[i]) { // not required + continue + } + + if m.Pmem[i] != nil { + if err := m.Pmem[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("pmem" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("pmem" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + func (m *FullVMConfiguration) validateVsock(formats strfmt.Registry) error { if swag.IsZero(m.Vsock) { // not required return nil @@ -404,6 +471,10 @@ func (m *FullVMConfiguration) ContextValidate(ctx context.Context, formats strfm res = append(res, err) } + if err := m.contextValidateMemoryHotplug(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateMetrics(ctx, formats); err != nil { res = append(res, err) } @@ -416,6 +487,10 @@ func (m *FullVMConfiguration) ContextValidate(ctx context.Context, formats strfm res = append(res, err) } + if err := m.contextValidatePmem(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateVsock(ctx, formats); err != nil { res = append(res, err) } @@ -605,6 +680,31 @@ func (m *FullVMConfiguration) contextValidateMachineConfig(ctx context.Context, return nil } +func (m *FullVMConfiguration) contextValidateMemoryHotplug(ctx context.Context, formats strfmt.Registry) error { + + if m.MemoryHotplug != nil { + + if swag.IsZero(m.MemoryHotplug) { // not required + return nil + } + + if err := m.MemoryHotplug.ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("memory-hotplug") + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("memory-hotplug") + } + + return err + } + } + + return nil +} + func (m *FullVMConfiguration) contextValidateMetrics(ctx context.Context, formats strfmt.Registry) error { if m.Metrics != nil { @@ -684,6 +784,35 @@ func (m *FullVMConfiguration) contextValidateNetworkInterfaces(ctx context.Conte return nil } +func (m *FullVMConfiguration) contextValidatePmem(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Pmem); i++ { + + if m.Pmem[i] != nil { + + if swag.IsZero(m.Pmem[i]) { // not required + return nil + } + + if err := m.Pmem[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("pmem" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("pmem" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + func (m *FullVMConfiguration) contextValidateVsock(ctx context.Context, formats strfmt.Registry) error { if m.Vsock != nil { diff --git a/packages/shared/pkg/fc/models/memory_hotplug_config.go b/packages/shared/pkg/fc/models/memory_hotplug_config.go new file mode 100644 index 0000000000..adbbcd1605 --- /dev/null +++ b/packages/shared/pkg/fc/models/memory_hotplug_config.go @@ -0,0 +1,94 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// MemoryHotplugConfig The configuration of the hotpluggable memory device (virtio-mem) +// +// swagger:model MemoryHotplugConfig +type MemoryHotplugConfig struct { + + // (Logical) Block size for the hotpluggable memory in MiB. This will determine the logical granularity of hot-plug memory for the guest. Refer to the device documentation on how to tune this value. + // Minimum: 2 + BlockSizeMib int64 `json:"block_size_mib,omitempty"` + + // Slot size for the hotpluggable memory in MiB. This will determine the granularity of hot-plug memory from the host. Refer to the device documentation on how to tune this value. + // Minimum: 128 + SlotSizeMib int64 `json:"slot_size_mib,omitempty"` + + // Total size of the hotpluggable memory in MiB. + TotalSizeMib int64 `json:"total_size_mib,omitempty"` +} + +// Validate validates this memory hotplug config +func (m *MemoryHotplugConfig) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateBlockSizeMib(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSlotSizeMib(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MemoryHotplugConfig) validateBlockSizeMib(formats strfmt.Registry) error { + if swag.IsZero(m.BlockSizeMib) { // not required + return nil + } + + if err := validate.MinimumInt("block_size_mib", "body", m.BlockSizeMib, 2, false); err != nil { + return err + } + + return nil +} + +func (m *MemoryHotplugConfig) validateSlotSizeMib(formats strfmt.Registry) error { + if swag.IsZero(m.SlotSizeMib) { // not required + return nil + } + + if err := validate.MinimumInt("slot_size_mib", "body", m.SlotSizeMib, 128, false); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this memory hotplug config based on context it is used +func (m *MemoryHotplugConfig) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *MemoryHotplugConfig) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MemoryHotplugConfig) UnmarshalBinary(b []byte) error { + var res MemoryHotplugConfig + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/packages/shared/pkg/fc/models/memory_hotplug_size_update.go b/packages/shared/pkg/fc/models/memory_hotplug_size_update.go new file mode 100644 index 0000000000..c071f79dd0 --- /dev/null +++ b/packages/shared/pkg/fc/models/memory_hotplug_size_update.go @@ -0,0 +1,47 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// MemoryHotplugSizeUpdate An update to the size of the hotpluggable memory region. +// +// swagger:model MemoryHotplugSizeUpdate +type MemoryHotplugSizeUpdate struct { + + // New target region size. + RequestedSizeMib int64 `json:"requested_size_mib,omitempty"` +} + +// Validate validates this memory hotplug size update +func (m *MemoryHotplugSizeUpdate) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this memory hotplug size update based on context it is used +func (m *MemoryHotplugSizeUpdate) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *MemoryHotplugSizeUpdate) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MemoryHotplugSizeUpdate) UnmarshalBinary(b []byte) error { + var res MemoryHotplugSizeUpdate + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/packages/shared/pkg/fc/models/memory_hotplug_status.go b/packages/shared/pkg/fc/models/memory_hotplug_status.go new file mode 100644 index 0000000000..795817bf1e --- /dev/null +++ b/packages/shared/pkg/fc/models/memory_hotplug_status.go @@ -0,0 +1,59 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// MemoryHotplugStatus The status of the hotpluggable memory device (virtio-mem) +// +// swagger:model MemoryHotplugStatus +type MemoryHotplugStatus struct { + + // (Logical) Block size for the hotpluggable memory in MiB. + BlockSizeMib int64 `json:"block_size_mib,omitempty"` + + // Plugged size for the hotpluggable memory in MiB. + PluggedSizeMib int64 `json:"plugged_size_mib,omitempty"` + + // Requested size for the hotpluggable memory in MiB. + RequestedSizeMib int64 `json:"requested_size_mib,omitempty"` + + // Slot size for the hotpluggable memory in MiB. + SlotSizeMib int64 `json:"slot_size_mib,omitempty"` + + // Total size of the hotpluggable memory in MiB. + TotalSizeMib int64 `json:"total_size_mib,omitempty"` +} + +// Validate validates this memory hotplug status +func (m *MemoryHotplugStatus) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this memory hotplug status based on context it is used +func (m *MemoryHotplugStatus) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *MemoryHotplugStatus) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MemoryHotplugStatus) UnmarshalBinary(b []byte) error { + var res MemoryHotplugStatus + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/packages/shared/pkg/fc/models/mmds_config.go b/packages/shared/pkg/fc/models/mmds_config.go index 718e73be09..b42fdc644f 100644 --- a/packages/shared/pkg/fc/models/mmds_config.go +++ b/packages/shared/pkg/fc/models/mmds_config.go @@ -17,6 +17,9 @@ import ( // swagger:model MmdsConfig type MmdsConfig struct { + // MMDS operates compatibly with EC2 IMDS (i.e. responds "text/plain" content regardless of Accept header in requests). + ImdsCompat *bool `json:"imds_compat,omitempty"` + // A valid IPv4 link-local address. IPv4Address *string `json:"ipv4_address,omitempty"` diff --git a/packages/shared/pkg/fc/models/msr_modifier.go b/packages/shared/pkg/fc/models/msr_modifier.go new file mode 100644 index 0000000000..120964ea9e --- /dev/null +++ b/packages/shared/pkg/fc/models/msr_modifier.go @@ -0,0 +1,85 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// MsrModifier Modifier for a model specific register (x86_64) +// +// swagger:model MsrModifier +type MsrModifier struct { + + // 32-bit MSR address as hex, binary, or decimal string (e.g., "0x10a", "0b100001010", "266") + // Required: true + Addr *string `json:"addr"` + + // 64-bit bitmap string defining which bits to modify. Format is "0b" followed by 64 characters where '0' = clear bit, '1' = set bit, 'x' = don't modify. Underscores can be used for readability. Example "0b0000000000000000000000000000000000000000000000000000000000000001" + // Required: true + Bitmap *string `json:"bitmap"` +} + +// Validate validates this msr modifier +func (m *MsrModifier) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateAddr(formats); err != nil { + res = append(res, err) + } + + if err := m.validateBitmap(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MsrModifier) validateAddr(formats strfmt.Registry) error { + + if err := validate.Required("addr", "body", m.Addr); err != nil { + return err + } + + return nil +} + +func (m *MsrModifier) validateBitmap(formats strfmt.Registry) error { + + if err := validate.Required("bitmap", "body", m.Bitmap); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this msr modifier based on context it is used +func (m *MsrModifier) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *MsrModifier) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MsrModifier) UnmarshalBinary(b []byte) error { + var res MsrModifier + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/packages/shared/pkg/fc/models/pmem.go b/packages/shared/pkg/fc/models/pmem.go new file mode 100644 index 0000000000..aad5d79d21 --- /dev/null +++ b/packages/shared/pkg/fc/models/pmem.go @@ -0,0 +1,91 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// Pmem pmem +// +// swagger:model Pmem +type Pmem struct { + + // Identificator for this device. + // Required: true + ID *string `json:"id"` + + // Host level path for the virtio-pmem device to use as a backing file. + // Required: true + PathOnHost *string `json:"path_on_host"` + + // Flag to map backing file in read-only mode. + ReadOnly bool `json:"read_only,omitempty"` + + // Flag to make this device be the root device for VM boot. Setting this flag will fail if there is another device configured to be a root device already. + RootDevice bool `json:"root_device,omitempty"` +} + +// Validate validates this pmem +func (m *Pmem) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateID(formats); err != nil { + res = append(res, err) + } + + if err := m.validatePathOnHost(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *Pmem) validateID(formats strfmt.Registry) error { + + if err := validate.Required("id", "body", m.ID); err != nil { + return err + } + + return nil +} + +func (m *Pmem) validatePathOnHost(formats strfmt.Registry) error { + + if err := validate.Required("path_on_host", "body", m.PathOnHost); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this pmem based on context it is used +func (m *Pmem) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *Pmem) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *Pmem) UnmarshalBinary(b []byte) error { + var res Pmem + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/packages/shared/pkg/fc/models/serial_device.go b/packages/shared/pkg/fc/models/serial_device.go new file mode 100644 index 0000000000..dde495fc1f --- /dev/null +++ b/packages/shared/pkg/fc/models/serial_device.go @@ -0,0 +1,47 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// SerialDevice The configuration of the serial device +// +// swagger:model SerialDevice +type SerialDevice struct { + + // Path to a file or named pipe on the host to which serial output should be written. + SerialOutPath string `json:"serial_out_path,omitempty"` +} + +// Validate validates this serial device +func (m *SerialDevice) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this serial device based on context it is used +func (m *SerialDevice) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *SerialDevice) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *SerialDevice) UnmarshalBinary(b []byte) error { + var res SerialDevice + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/packages/shared/pkg/fc/models/snapshot_create_params.go b/packages/shared/pkg/fc/models/snapshot_create_params.go index b839c19252..45e435d01b 100644 --- a/packages/shared/pkg/fc/models/snapshot_create_params.go +++ b/packages/shared/pkg/fc/models/snapshot_create_params.go @@ -17,7 +17,7 @@ import ( // swagger:model SnapshotCreateParams type SnapshotCreateParams struct { - // Path to the file that will contain the guest memory. + // Path to the file that will contain the guest memory. It is optional. In case that a user doesn't provide a path, they are responsible to ensure they store the microVM's memory state via external means. MemFilePath string `json:"mem_file_path,omitempty"` // Path to the file that will contain the microVM state. diff --git a/packages/shared/pkg/fc/models/snapshot_load_params.go b/packages/shared/pkg/fc/models/snapshot_load_params.go index 1c313b00e3..a1ae7d90b9 100644 --- a/packages/shared/pkg/fc/models/snapshot_load_params.go +++ b/packages/shared/pkg/fc/models/snapshot_load_params.go @@ -18,7 +18,7 @@ import ( // swagger:model SnapshotLoadParams type SnapshotLoadParams struct { - // Enable support for incremental (diff) snapshots by tracking dirty guest pages. + // (Deprecated) Enable dirty page tracking to improve space efficiency of diff snapshots EnableDiffSnapshots bool `json:"enable_diff_snapshots,omitempty"` // Configuration for the backend that handles memory load. If this field is specified, `mem_file_path` is forbidden. Either `mem_backend` or `mem_file_path` must be present at a time. @@ -36,6 +36,9 @@ type SnapshotLoadParams struct { // Path to the file that contains the microVM state to be loaded. // Required: true SnapshotPath *string `json:"snapshot_path"` + + // Enable dirty page tracking to improve space efficiency of diff snapshots + TrackDirtyPages bool `json:"track_dirty_pages,omitempty"` } // Validate validates this snapshot load params diff --git a/packages/shared/pkg/fc/models/vcpu_features.go b/packages/shared/pkg/fc/models/vcpu_features.go new file mode 100644 index 0000000000..dd9f71f81b --- /dev/null +++ b/packages/shared/pkg/fc/models/vcpu_features.go @@ -0,0 +1,85 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// VcpuFeatures vCPU feature modifier (aarch64) +// +// swagger:model VcpuFeatures +type VcpuFeatures struct { + + // 32-bit bitmap string defining which bits to modify. Format is "0b" followed by 32 characters where '0' = clear bit, '1' = set bit, 'x' = don't modify. Example "0b00000000000000000000000001100000" + // Required: true + Bitmap *string `json:"bitmap"` + + // Index in the kvm_vcpu_init.features array + // Required: true + Index *int32 `json:"index"` +} + +// Validate validates this vcpu features +func (m *VcpuFeatures) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateBitmap(formats); err != nil { + res = append(res, err) + } + + if err := m.validateIndex(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *VcpuFeatures) validateBitmap(formats strfmt.Registry) error { + + if err := validate.Required("bitmap", "body", m.Bitmap); err != nil { + return err + } + + return nil +} + +func (m *VcpuFeatures) validateIndex(formats strfmt.Registry) error { + + if err := validate.Required("index", "body", m.Index); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this vcpu features based on context it is used +func (m *VcpuFeatures) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *VcpuFeatures) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *VcpuFeatures) UnmarshalBinary(b []byte) error { + var res VcpuFeatures + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} From 4a963029e5c762c1bb48f9454ffef908160587ad Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Fri, 13 Feb 2026 11:00:04 -0800 Subject: [PATCH 05/63] orchestrator: add support for Firecracker v1.14 Add support for Firecracker v1.14 and make it the default. Signed-off-by: Babis Chalios --- .github/actions/build-sandbox-template/action.yml | 2 +- packages/orchestrator/README.md | 2 +- packages/shared/pkg/featureflags/flags.go | 4 +++- tests/integration/seed.go | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/actions/build-sandbox-template/action.yml b/.github/actions/build-sandbox-template/action.yml index 9482eefd40..f42d2b02b8 100644 --- a/.github/actions/build-sandbox-template/action.yml +++ b/.github/actions/build-sandbox-template/action.yml @@ -8,7 +8,7 @@ runs: env: TEMPLATE_ID: "2j6ly824owf4awgai1xo" KERNEL_VERSION: "vmlinux-6.1.158" - FIRECRACKER_VERSION: "v1.12.1_210cbac" + FIRECRACKER_VERSION: "v1.14.1_458ca91" run: | # Generate an unique build ID for the template for this run export BUILD_ID=$(uuidgen) diff --git a/packages/orchestrator/README.md b/packages/orchestrator/README.md index f4188b6a8c..d51767b63a 100644 --- a/packages/orchestrator/README.md +++ b/packages/orchestrator/README.md @@ -37,7 +37,7 @@ Flags: - `-storage ` - Local path or `gs://bucket` (enables local mode with auto-download of kernel/FC) - `-sandbox-dir ` - Override `SANDBOX_DIR` (the rootfs path baked into the snapshot) - `-kernel ` - Kernel version (default: `vmlinux-6.1.158`) -- `-firecracker ` - Firecracker version (default: `v1.12.1_210cbac`) +- `-firecracker ` - Firecracker version (default: `v1.14.1_458ca91`) - `-vcpu ` - vCPUs (default: `1`) - `-memory ` - Memory in MB (default: `512`) - `-disk ` - Disk in MB (default: `1000`) diff --git a/packages/shared/pkg/featureflags/flags.go b/packages/shared/pkg/featureflags/flags.go index 84416f6201..79f9327760 100644 --- a/packages/shared/pkg/featureflags/flags.go +++ b/packages/shared/pkg/featureflags/flags.go @@ -241,12 +241,14 @@ const ( const ( DefaultFirecackerV1_10Version = "v1.10.1_30cbb07" DefaultFirecackerV1_12Version = "v1.12.1_210cbac" - DefaultFirecrackerVersion = DefaultFirecackerV1_12Version + DefaultFirecackerV1_14Version = "v1.14.1_458ca91" + DefaultFirecrackerVersion = DefaultFirecackerV1_14Version ) var FirecrackerVersionMap = map[string]string{ "v1.10": DefaultFirecackerV1_10Version, "v1.12": DefaultFirecackerV1_12Version, + "v1.14": DefaultFirecackerV1_14Version, } // BuildIoEngine Sync is used by default as there seems to be a bad interaction between Async and a lot of io operations. diff --git a/tests/integration/seed.go b/tests/integration/seed.go index f550ca6c3f..aeabe4ea8a 100644 --- a/tests/integration/seed.go +++ b/tests/integration/seed.go @@ -208,7 +208,7 @@ INSERT INTO env_builds ( cluster_node_id, version, created_at, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, CURRENT_TIMESTAMP) `, build.id, "FROM e2bdev/base:latest", dbtypes.BuildStatusUploaded, - 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.12.1_210cbac", pkg.Version, + 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.14.1_458ca91", pkg.Version, "integration-test-node", templates.TemplateV1Version, build.createdAt) } else { err = db.TestsRawSQL(ctx, ` @@ -218,7 +218,7 @@ INSERT INTO env_builds ( cluster_node_id, version, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, CURRENT_TIMESTAMP) `, build.id, "FROM e2bdev/base:latest", dbtypes.BuildStatusUploaded, - 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.12.1_210cbac", pkg.Version, + 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.14.1_458ca91", pkg.Version, "integration-test-node", templates.TemplateV1Version) } if err != nil { From acd254db36d1475a277fdf8cd11c153ef8c6456f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 23 Feb 2026 14:48:12 +0000 Subject: [PATCH 06/63] chore: auto-commit generated changes Signed-off-by: Babis Chalios --- .../template-manager/template-manager.pb.go | 248 +++++++++--------- 1 file changed, 127 insertions(+), 121 deletions(-) diff --git a/packages/shared/pkg/grpc/template-manager/template-manager.pb.go b/packages/shared/pkg/grpc/template-manager/template-manager.pb.go index ebc032f7dc..f6196158db 100644 --- a/packages/shared/pkg/grpc/template-manager/template-manager.pb.go +++ b/packages/shared/pkg/grpc/template-manager/template-manager.pb.go @@ -699,6 +699,7 @@ type TemplateConfig struct { Source isTemplateConfig_Source `protobuf_oneof:"source"` FromImageRegistry *FromImageRegistry `protobuf:"bytes,15,opt,name=fromImageRegistry,proto3,oneof" json:"fromImageRegistry,omitempty"` TeamID string `protobuf:"bytes,16,opt,name=teamID,proto3" json:"teamID,omitempty"` + FreePageReporting *bool `protobuf:"varint,17,opt,name=freePageReporting,proto3,oneof" json:"freePageReporting,omitempty"` } func (x *TemplateConfig) Reset() { @@ -1404,7 +1405,7 @@ var file_template_manager_proto_rawDesc = []byte{ 0x03, 0x67, 0x63, 0x70, 0x12, 0x2c, 0x0a, 0x07, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x48, 0x00, 0x52, 0x07, 0x67, 0x65, 0x6e, 0x65, 0x72, - 0x61, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x84, 0x05, 0x0a, 0x0e, 0x54, + 0x61, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xcd, 0x05, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x12, 0x18, 0x0a, @@ -1442,129 +1443,134 @@ var file_template_manager_proto_rawDesc = []byte{ 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x48, 0x02, 0x52, 0x11, 0x66, 0x72, 0x6f, 0x6d, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x88, 0x01, 0x01, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x65, 0x61, 0x6d, 0x49, 0x44, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x74, 0x65, 0x61, 0x6d, 0x49, 0x44, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x42, 0x14, 0x0a, 0x12, 0x5f, - 0x66, 0x72, 0x6f, 0x6d, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x79, 0x22, 0xa3, 0x01, 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x08, 0x74, - 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, - 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, - 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x23, 0x0a, 0x0a, 0x63, 0x61, 0x63, 0x68, - 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, - 0x63, 0x61, 0x63, 0x68, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, - 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, - 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, - 0x5f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x8b, 0x03, 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, - 0x6c, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, - 0x44, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x12, 0x1b, 0x0a, 0x06, 0x6f, - 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x06, 0x6f, - 0x66, 0x66, 0x73, 0x65, 0x74, 0x88, 0x01, 0x01, 0x12, 0x24, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, - 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, - 0x65, 0x6c, 0x48, 0x01, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x19, - 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x02, 0x52, - 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x88, 0x01, 0x01, 0x12, 0x35, 0x0a, 0x05, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x48, 0x03, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x88, 0x01, 0x01, - 0x12, 0x31, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x48, 0x04, 0x52, 0x03, 0x65, 0x6e, 0x64, - 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x4c, 0x6f, 0x67, 0x73, 0x44, 0x69, 0x72, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x05, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, - 0x74, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x42, 0x08, 0x0a, 0x06, 0x5f, - 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, - 0x06, 0x0a, 0x04, 0x5f, 0x65, 0x6e, 0x64, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x64, 0x69, 0x72, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x56, 0x0a, 0x1a, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, - 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x12, 0x1e, 0x0a, - 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x22, 0x65, 0x0a, - 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x74, 0x66, 0x73, - 0x53, 0x69, 0x7a, 0x65, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x72, - 0x6f, 0x6f, 0x74, 0x66, 0x73, 0x53, 0x69, 0x7a, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x0e, - 0x65, 0x6e, 0x76, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x6e, 0x76, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x4b, 0x65, 0x79, 0x22, 0x83, 0x02, 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, - 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x38, - 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x06, 0x74, 0x65, 0x61, 0x6d, 0x49, 0x44, 0x12, 0x31, 0x0a, 0x11, 0x66, 0x72, 0x65, 0x65, 0x50, + 0x61, 0x67, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x11, 0x20, 0x01, + 0x28, 0x08, 0x48, 0x03, 0x52, 0x11, 0x66, 0x72, 0x65, 0x65, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x42, 0x14, + 0x0a, 0x12, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x79, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x66, 0x72, 0x65, 0x65, 0x50, 0x61, 0x67, + 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x22, 0xa3, 0x01, 0x0a, 0x15, 0x54, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x12, 0x23, 0x0a, 0x0a, 0x63, 0x61, 0x63, 0x68, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x63, 0x61, 0x63, 0x68, 0x65, 0x53, 0x63, + 0x6f, 0x70, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x53, + 0x63, 0x6f, 0x70, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x22, 0x8b, 0x03, 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x75, + 0x69, 0x6c, 0x64, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x75, 0x69, + 0x6c, 0x64, 0x49, 0x44, 0x12, 0x1b, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x88, 0x01, + 0x01, 0x12, 0x24, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x09, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x48, 0x01, 0x52, 0x05, 0x6c, + 0x65, 0x76, 0x65, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x02, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x88, + 0x01, 0x01, 0x12, 0x35, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x12, 0x1f, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x09, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, - 0x76, 0x65, 0x6c, 0x12, 0x3a, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x04, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, - 0x69, 0x6c, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x65, 0x6c, - 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x1a, - 0x39, 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x57, 0x0a, 0x19, 0x54, 0x65, - 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x12, 0x17, 0x0a, 0x04, 0x73, 0x74, 0x65, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, - 0x00, 0x52, 0x04, 0x73, 0x74, 0x65, 0x70, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x73, - 0x74, 0x65, 0x70, 0x22, 0x86, 0x02, 0x0a, 0x1b, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, - 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, - 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x32, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, - 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x12, 0x36, 0x0a, 0x0a, 0x6c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x69, - 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x0a, 0x6c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x37, 0x0a, 0x06, - 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x54, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x48, 0x03, 0x52, + 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x03, 0x65, 0x6e, 0x64, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x48, 0x04, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x09, + 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x0e, 0x2e, 0x4c, 0x6f, 0x67, 0x73, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, + 0x05, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, + 0x09, 0x0a, 0x07, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, + 0x65, 0x76, 0x65, 0x6c, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x42, 0x08, + 0x0a, 0x06, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x65, 0x6e, 0x64, + 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x56, + 0x0a, 0x1a, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, + 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, + 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x22, 0x65, 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, + 0x24, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x74, 0x66, 0x73, 0x53, 0x69, 0x7a, 0x65, 0x4b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x72, 0x6f, 0x6f, 0x74, 0x66, 0x73, 0x53, 0x69, + 0x7a, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x64, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, + 0x6e, 0x76, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x22, 0x83, 0x02, + 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4c, + 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x0a, 0x05, 0x6c, + 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, 0x2e, 0x4c, 0x6f, 0x67, + 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x3a, 0x0a, 0x06, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x54, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4c, 0x6f, 0x67, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x57, 0x0a, 0x19, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, + 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x17, 0x0a, 0x04, 0x73, 0x74, + 0x65, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x73, 0x74, 0x65, 0x70, + 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x73, 0x74, 0x65, 0x70, 0x22, 0x86, 0x02, 0x0a, + 0x1b, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, - 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, - 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x2a, 0x34, 0x0a, 0x08, - 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x65, 0x62, 0x75, - 0x67, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x6e, 0x66, 0x6f, 0x10, 0x01, 0x12, 0x08, 0x0a, - 0x04, 0x57, 0x61, 0x72, 0x6e, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x10, 0x03, 0x2a, 0x2a, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x73, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x10, 0x00, - 0x12, 0x0c, 0x0a, 0x08, 0x42, 0x61, 0x63, 0x6b, 0x77, 0x61, 0x72, 0x64, 0x10, 0x01, 0x2a, 0x3d, - 0x0a, 0x12, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, - 0x74, 0x61, 0x74, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, - 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x10, 0x01, 0x12, 0x0d, - 0x0a, 0x09, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x10, 0x02, 0x32, 0xbe, 0x02, - 0x0a, 0x0f, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x40, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x12, 0x4b, 0x0a, 0x13, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, - 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x2e, 0x54, 0x65, 0x6d, - 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, - 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x4a, 0x0a, 0x13, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, - 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, - 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, + 0x65, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x32, 0x0a, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x54, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x36, 0x0a, + 0x0a, 0x6c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x16, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, + 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x6c, 0x6f, 0x67, 0x45, 0x6e, + 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x37, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x48, 0x00, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x09, + 0x0a, 0x07, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, + 0x04, 0x08, 0x04, 0x10, 0x05, 0x2a, 0x34, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, + 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x65, 0x62, 0x75, 0x67, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, + 0x49, 0x6e, 0x66, 0x6f, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x61, 0x72, 0x6e, 0x10, 0x02, + 0x12, 0x09, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x03, 0x2a, 0x2a, 0x0a, 0x0d, 0x4c, + 0x6f, 0x67, 0x73, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, 0x07, + 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x42, 0x61, 0x63, + 0x6b, 0x77, 0x61, 0x72, 0x64, 0x10, 0x01, 0x2a, 0x3d, 0x0a, 0x12, 0x54, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0c, 0x0a, + 0x08, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x46, + 0x61, 0x69, 0x6c, 0x65, 0x64, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x6f, 0x6d, 0x70, 0x6c, + 0x65, 0x74, 0x65, 0x64, 0x10, 0x02, 0x32, 0xbe, 0x02, 0x0a, 0x0f, 0x54, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x40, 0x0a, 0x0e, 0x54, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x54, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x50, 0x0a, 0x13, - 0x49, 0x6e, 0x69, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, - 0x6f, 0x61, 0x64, 0x12, 0x1b, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x46, - 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1c, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x65, - 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, - 0x5a, 0x31, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x32, 0x62, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x69, 0x6e, 0x66, - 0x72, 0x61, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x2d, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4b, 0x0a, 0x13, + 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x16, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x54, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x13, 0x54, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x12, 0x1b, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x50, 0x0a, 0x13, 0x49, 0x6e, 0x69, 0x74, 0x4c, 0x61, 0x79, + 0x65, 0x72, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1b, 0x2e, 0x49, + 0x6e, 0x69, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, + 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x49, 0x6e, 0x69, 0x74, + 0x4c, 0x61, 0x79, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x68, 0x74, 0x74, 0x70, 0x73, + 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x32, + 0x62, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x69, 0x6e, 0x66, 0x72, 0x61, 0x2f, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( From 930a6911f918609ba82ea16a9d3a46a35a147352 Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Fri, 20 Feb 2026 12:35:01 +0100 Subject: [PATCH 07/63] feat(uffd): add page state tracking for guest memory As we're going to handle UFFD_EVENT_REMOVE events triggerred by Firecracker, we need to keep track of the state of all the guest memory pages. Theis commit introduces 3 states: * Unfaulted - A page that has not been faulted yet. * Faulted - A page that we have previously faulted in. * Removed - A page that we have received a remove event for and haven't faulted in since. It also adds the necessary book keeping of page state in all the memory regions of the guest, along with methods for retrieving and setting the state of pages. Co-authored-by: Tomas Valenta Signed-off-by: Babis Chalios --- .../pkg/sandbox/uffd/memory/mapping.go | 16 +++---- .../uffd/memory/mapping_offset_test.go | 36 ++++++--------- .../sandbox/uffd/userfaultfd/page_tracker.go | 46 +++++++++++++++++++ .../sandbox/uffd/userfaultfd/userfaultfd.go | 24 ++++++---- 4 files changed, 83 insertions(+), 39 deletions(-) create mode 100644 packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go diff --git a/packages/orchestrator/pkg/sandbox/uffd/memory/mapping.go b/packages/orchestrator/pkg/sandbox/uffd/memory/mapping.go index 48a284e8d5..a773c0d0cc 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/memory/mapping.go +++ b/packages/orchestrator/pkg/sandbox/uffd/memory/mapping.go @@ -50,15 +50,15 @@ func NewMappingFromFc(regions []*models.GuestMemoryRegionMapping) (*Mapping, err return NewMapping(r), nil } -// GetOffset returns the relative offset and the pagesize of the mapped range for a given address. -func (m *Mapping) GetOffset(hostVirtAddr uintptr) (int64, uintptr, error) { +// GetOffset returns the relative offset of the mapped range for a given address. +func (m *Mapping) GetOffset(hostVirtAddr uintptr) (int64, error) { for _, r := range m.Regions { if hostVirtAddr >= r.BaseHostVirtAddr && hostVirtAddr < r.endHostVirtAddr() { - return r.shiftedOffset(hostVirtAddr), r.PageSize, nil + return r.shiftedOffset(hostVirtAddr), nil } } - return 0, 0, AddressNotFoundError{hostVirtAddr: hostVirtAddr} + return 0, AddressNotFoundError{hostVirtAddr: hostVirtAddr} } // GetHostVirtRanges returns the host virtual addresses and sizes (ranges) that cover exactly the given [offset, offset+length) range in the host virtual address space. @@ -95,12 +95,12 @@ func (m *Mapping) getHostVirtRegion(off int64) (*Region, error) { return nil, OffsetNotFoundError{offset: off} } -// GetHostVirtAddr returns the host virtual address and page size for the given offset. -func (m *Mapping) GetHostVirtAddr(off int64) (uintptr, uintptr, error) { +// GetHostVirtAddr returns the host virtual address the given offset. +func (m *Mapping) GetHostVirtAddr(off int64) (uintptr, error) { region, err := m.getHostVirtRegion(off) if err != nil { - return 0, 0, err + return 0, err } - return region.shiftedHostVirtAddr(off), region.PageSize, nil + return region.shiftedHostVirtAddr(off), nil } diff --git a/packages/orchestrator/pkg/sandbox/uffd/memory/mapping_offset_test.go b/packages/orchestrator/pkg/sandbox/uffd/memory/mapping_offset_test.go index 4be1c1e6e0..c2841885d8 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/memory/mapping_offset_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/memory/mapping_offset_test.go @@ -103,13 +103,12 @@ func TestMapping_GetOffset(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - offset, pagesize, err := mapping.GetOffset(tt.hostVirtAddr) + offset, err := mapping.GetOffset(tt.hostVirtAddr) if tt.expectError != nil { require.ErrorIs(t, err, tt.expectError) } else { require.NoError(t, err) assert.Equal(t, tt.expectedOffset, offset) - assert.Equal(t, tt.expectedPagesize, pagesize) } }) } @@ -121,7 +120,7 @@ func TestMapping_EmptyRegions(t *testing.T) { mapping := NewMapping([]Region{}) // Test GetOffset with empty regions - _, _, err := mapping.GetOffset(0x1000) + _, err := mapping.GetOffset(0x1000) require.ErrorIs(t, err, AddressNotFoundError{hostVirtAddr: 0x1000}) } @@ -140,23 +139,21 @@ func TestMapping_BoundaryConditions(t *testing.T) { mapping := NewMapping(regions) // Test exact start boundary - offset, pagesize, err := mapping.GetOffset(0x1000) + offset, err := mapping.GetOffset(0x1000) require.NoError(t, err) assert.Equal(t, int64(0x5000), offset) // 0x5000 + (0x1000 - 0x1000) - assert.Equal(t, uintptr(header.PageSize), pagesize) // Test just before end boundary (exclusive) - offset, pagesize, err = mapping.GetOffset(0x2FFF) // 0x1000 + 0x2000 - 1 + offset, err = mapping.GetOffset(0x2FFF) // 0x1000 + 0x2000 - 1 require.NoError(t, err) assert.Equal(t, int64(0x5000+(0x2FFF-0x1000)), offset) // 0x6FFF - assert.Equal(t, uintptr(header.PageSize), pagesize) // Test exact end boundary (should fail - exclusive) - _, _, err = mapping.GetOffset(0x3000) // 0x1000 + 0x2000 + _, err = mapping.GetOffset(0x3000) // 0x1000 + 0x2000 require.ErrorIs(t, err, AddressNotFoundError{hostVirtAddr: 0x3000}) // Test below start boundary (should fail) - _, _, err = mapping.GetOffset(0x0FFF) // 0x1000 - 0x1000 + _, err = mapping.GetOffset(0x0FFF) // 0x1000 - 0x1000 require.ErrorIs(t, err, AddressNotFoundError{hostVirtAddr: 0x0FFF}) } @@ -174,10 +171,9 @@ func TestMapping_SingleLargeRegion(t *testing.T) { } mapping := NewMapping(regions) - offset, pagesize, err := mapping.GetOffset(0xABCDEF) + offset, err := mapping.GetOffset(0xABCDEF) require.NoError(t, err) assert.Equal(t, int64(0x100+0xABCDEF), offset) - assert.Equal(t, uintptr(header.PageSize), pagesize) } func TestMapping_ZeroSizeRegion(t *testing.T) { @@ -194,7 +190,7 @@ func TestMapping_ZeroSizeRegion(t *testing.T) { mapping := NewMapping(regions) - _, _, err := mapping.GetOffset(0x2000) + _, err := mapping.GetOffset(0x2000) require.ErrorIs(t, err, AddressNotFoundError{hostVirtAddr: 0x2000}) } @@ -218,19 +214,17 @@ func TestMapping_MultipleRegionsSparse(t *testing.T) { mapping := NewMapping(regions) // Should succeed for start of first region - offset, pagesize, err := mapping.GetOffset(0x100) + offset, err := mapping.GetOffset(0x100) require.NoError(t, err) assert.Equal(t, int64(0x1000), offset) - assert.Equal(t, uintptr(header.PageSize), pagesize) // Should succeed for start of second region - offset, pagesize, err = mapping.GetOffset(0x10000) + offset, err = mapping.GetOffset(0x10000) require.NoError(t, err) assert.Equal(t, int64(0x2000), offset) - assert.Equal(t, uintptr(header.PageSize), pagesize) // In gap - _, _, err = mapping.GetOffset(0x5000) + _, err = mapping.GetOffset(0x5000) require.ErrorIs(t, err, AddressNotFoundError{hostVirtAddr: 0x5000}) } @@ -250,18 +244,16 @@ func TestMapping_HugepagePagesize(t *testing.T) { mapping := NewMapping(regions) // Test valid address in region using hugepages - offset, pagesize, err := mapping.GetOffset(0x401000) + offset, err := mapping.GetOffset(0x401000) require.NoError(t, err) assert.Equal(t, int64(0x800000+(0x401000-0x400000)), offset) - assert.Equal(t, uintptr(hugepageSize), pagesize) // Test start of region - offset, pagesize, err = mapping.GetOffset(0x400000) + offset, err = mapping.GetOffset(0x400000) require.NoError(t, err) assert.Equal(t, int64(0x800000), offset) - assert.Equal(t, uintptr(hugepageSize), pagesize) // Test end of region (exclusive, should fail) - _, _, err = mapping.GetOffset(0x400000 + uintptr(hugepageSize)) + _, err = mapping.GetOffset(0x400000 + uintptr(hugepageSize)) require.ErrorIs(t, err, AddressNotFoundError{hostVirtAddr: 0x400000 + uintptr(hugepageSize)}) } diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go new file mode 100644 index 0000000000..b3ba62176f --- /dev/null +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go @@ -0,0 +1,46 @@ +package userfaultfd + +import "sync" + +type pageState uint8 + +const ( + unfaulted pageState = iota + faulted + removed +) + +type pageTracker struct { + pageSize uintptr + + m map[uintptr]pageState + mu sync.RWMutex +} + +func newPageTracker(pageSize uintptr) pageTracker { + return pageTracker{ + pageSize: pageSize, + m: make(map[uintptr]pageState), + } +} + +func (pt *pageTracker) get(addr uintptr) pageState { + pt.mu.RLock() + defer pt.mu.RUnlock() + + state, ok := pt.m[addr] + if !ok { + return unfaulted + } + + return state +} + +func (pt *pageTracker) setState(start, end uintptr, state pageState) { + pt.mu.Lock() + defer pt.mu.Unlock() + + for addr := start; addr < end; addr += pt.pageSize { + pt.m[addr] = state + } +} diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index c69b65781b..ca2da4a353 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -48,8 +48,10 @@ func hasEvent(revents, event int16) bool { type Userfaultfd struct { fd Fd - src block.Slicer - ma *memory.Mapping + src block.Slicer + ma *memory.Mapping + pageSize uintptr + pageTracker pageTracker // We don't skip the already mapped pages, because if the memory is swappable the page *might* under some conditions be mapped out. // For hugepages this should not be a problem, but might theoretically happen to normal pages with swap @@ -77,6 +79,8 @@ func NewUserfaultfdFromFd(fd uintptr, src block.Slicer, m *memory.Mapping, logge u := &Userfaultfd{ fd: Fd(fd), src: src, + pageSize: uintptr(blockSize), + pageTracker: newPageTracker(uintptr(blockSize)), missingRequests: block.NewTracker(blockSize), prefetchTracker: block.NewPrefetchTracker(blockSize), ma: m, @@ -233,7 +237,7 @@ outerLoop: addr := getPagefaultAddress(&pagefault) - offset, pagesize, err := u.ma.GetOffset(addr) + offset, err := u.ma.GetOffset(addr) if err != nil { u.logger.Error(ctx, "UFFD serve get mapping error", zap.Error(err)) @@ -245,7 +249,7 @@ outerLoop: // For the write to be executed, we first need to copy the page from the source to the guest memory. if flags&UFFD_PAGEFAULT_FLAG_WRITE != 0 { u.wg.Go(func() error { - return u.faultPage(ctx, addr, offset, pagesize, u.src, fdExit.SignalExit, block.Write) + return u.faultPage(ctx, addr, offset, u.pageSize, u.src, fdExit.SignalExit, block.Write) }) continue @@ -255,7 +259,7 @@ outerLoop: // If the event has no flags, it was a read to a missing page and we need to copy the page from the source to the guest memory. if flags == 0 { u.wg.Go(func() error { - return u.faultPage(ctx, addr, offset, pagesize, u.src, fdExit.SignalExit, block.Read) + return u.faultPage(ctx, addr, offset, u.pageSize, u.src, fdExit.SignalExit, block.Read) }) continue @@ -294,16 +298,16 @@ func (u *Userfaultfd) Prefault(ctx context.Context, offset int64, data []byte) e defer span.End() // Get host virtual address and page size for this offset - addr, pagesize, err := u.ma.GetHostVirtAddr(offset) + addr, err := u.ma.GetHostVirtAddr(offset) if err != nil { return fmt.Errorf("failed to get host virtual address: %w", err) } - if len(data) != int(pagesize) { - return fmt.Errorf("data length (%d) is less than pagesize (%d)", len(data), pagesize) + if len(data) != int(u.pageSize) { + return fmt.Errorf("data length (%d) is less than pagesize (%d)", len(data), u.pageSize) } - return u.faultPage(ctx, addr, offset, pagesize, directDataSource{data, int64(pagesize)}, nil, block.Prefetch) + return u.faultPage(ctx, addr, offset, u.pageSize, directDataSource{data, int64(u.pageSize)}, nil, block.Prefetch) } // directDataSource wraps a byte slice to implement block.Slicer for prefaulting. @@ -411,6 +415,7 @@ retryLoop: if errors.Is(copyErr, unix.EEXIST) { // Page is already mapped span.SetAttributes(attribute.Bool("uffd.already_mapped", true)) + u.pageTracker.setState(faulted, addr, addr+pagesize) return nil } @@ -432,6 +437,7 @@ retryLoop: // Add the offset to the missing requests tracker with metadata. u.missingRequests.Add(offset) u.prefetchTracker.Add(offset, accessType) + u.pageTracker.setState(faulted, addr, addr+pagesize) return nil } From 930725e5e3a51816dfce5b6a546f7324529d0b3d Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Wed, 11 Feb 2026 17:32:25 -0800 Subject: [PATCH 08/63] feat(uffd): export more CGO bindings for UFFD Import a few more bindings that we'll need for handling remove events. Signed-off-by: Babis Chalios --- .../pkg/sandbox/uffd/userfaultfd/fd.go | 78 +++++++++++++++++-- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/fd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/fd.go index 75a464c95c..2425a74647 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/fd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/fd.go @@ -21,6 +21,10 @@ struct uffd_pagefault { #define UFFD_FEATURE_WP_ASYNC (1 << 15) #endif +struct uffd_remove { + __u64 start; + __u64 end; +}; */ import "C" @@ -33,22 +37,34 @@ import ( const ( NR_userfaultfd = C.__NR_userfaultfd - UFFD_API = C.UFFD_API + UFFD_API = C.UFFD_API + UFFD_EVENT_PAGEFAULT = C.UFFD_EVENT_PAGEFAULT + UFFD_EVENT_REMOVE = C.UFFD_EVENT_REMOVE UFFDIO_REGISTER_MODE_MISSING = C.UFFDIO_REGISTER_MODE_MISSING UFFDIO_REGISTER_MODE_WP = C.UFFDIO_REGISTER_MODE_WP UFFDIO_COPY_MODE_WP = C.UFFDIO_COPY_MODE_WP - UFFDIO_API = C.UFFDIO_API - UFFDIO_REGISTER = C.UFFDIO_REGISTER - UFFDIO_UNREGISTER = C.UFFDIO_UNREGISTER - UFFDIO_COPY = C.UFFDIO_COPY + UFFDIO_WRITEPROTECT_MODE_WP = C.UFFDIO_WRITEPROTECT_MODE_WP + + UFFDIO_ZEROPAGE_MODE_DONTWAKE = C.UFFDIO_ZEROPAGE_MODE_DONTWAKE + + UFFDIO_API = C.UFFDIO_API + UFFDIO_REGISTER = C.UFFDIO_REGISTER + UFFDIO_UNREGISTER = C.UFFDIO_UNREGISTER + UFFDIO_COPY = C.UFFDIO_COPY + UFFDIO_ZEROPAGE = C.UFFDIO_ZEROPAGE + UFFDIO_WRITEPROTECT = C.UFFDIO_WRITEPROTECT + UFFDIO_WAKE = C.UFFDIO_WAKE UFFD_PAGEFAULT_FLAG_WRITE = C.UFFD_PAGEFAULT_FLAG_WRITE + UFFD_PAGEFAULT_FLAG_MINOR = C.UFFD_PAGEFAULT_FLAG_MINOR + UFFD_PAGEFAULT_FLAG_WP = C.UFFD_PAGEFAULT_FLAG_WP UFFD_FEATURE_MISSING_HUGETLBFS = C.UFFD_FEATURE_MISSING_HUGETLBFS + UFFD_FEATURE_EVENT_REMOVE = C.UFFD_FEATURE_EVENT_REMOVE UFFD_FEATURE_WP_ASYNC = C.UFFD_FEATURE_WP_ASYNC ) @@ -59,11 +75,13 @@ type ( UffdMsg = C.struct_uffd_msg UffdPagefault = C.struct_uffd_pagefault + UffdRemove = C.struct_uffd_remove UffdioAPI = C.struct_uffdio_api UffdioRegister = C.struct_uffdio_register UffdioRange = C.struct_uffdio_range UffdioCopy = C.struct_uffdio_copy + UffdioZero = C.struct_uffdio_zeropage UffdioWriteProtect = C.struct_uffdio_writeprotect ) @@ -98,6 +116,21 @@ func newUffdioCopy(b []byte, address CULong, pagesize CULong, mode CULong, bytes } } +func newUffdioZero(address, pagesize, mode CULong) UffdioZero { + return UffdioZero{ + _range: newUffdioRange(address, pagesize), + mode: mode, + zeropage: 0, + } +} + +func newUffdioWriteProtect(address, pagesize, mode CULong) UffdioWriteProtect { + return UffdioWriteProtect{ + _range: newUffdioRange(address, pagesize), + mode: mode, + } +} + func getMsgEvent(msg *UffdMsg) CUChar { return msg.event } @@ -130,6 +163,41 @@ func (f Fd) copy(addr, pagesize uintptr, data []byte, mode CULong) error { return nil } +func (f Fd) zero(addr, pagesize uintptr, mode CULong) error { + zero := newUffdioZero(CULong(addr)&^CULong(pagesize-1), CULong(pagesize), mode) + + if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(f), UFFDIO_ZEROPAGE, uintptr(unsafe.Pointer(&zero))); errno != 0 { + return errno + } + + // Check if the bytes actually zeroed out by the kernel match the page size + if zero.zeropage != CLong(pagesize) { + return fmt.Errorf("UFFDIO_ZEROPAGE copied %d bytes, expected %d", zero.zeropage, pagesize) + } + + return nil +} + +func (f Fd) writeProtect(addr, pagesize uintptr, mode CULong) error { + writeProtect := newUffdioWriteProtect(CULong(addr)&^CULong(pagesize-1), CULong(pagesize), mode) + + if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(f), UFFDIO_WRITEPROTECT, uintptr(unsafe.Pointer(&writeProtect))); errno != 0 { + return errno + } + + return nil +} + +func (f Fd) wake(addr, pagesize uintptr) error { + uffdRange := newUffdioRange(CULong(addr)&^CULong(pagesize-1), CULong(pagesize)) + + if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(f), UFFDIO_WAKE, uintptr(unsafe.Pointer(&uffdRange))); errno != 0 { + return errno + } + + return nil +} + func (f Fd) close() error { return syscall.Close(int(f)) } From bc94cdf93b63035ae94bf7d7d87b4306677a9b21 Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Fri, 20 Feb 2026 18:38:27 +0100 Subject: [PATCH 09/63] feat(uffd): handle remove events from the file descriptor Handle UFFD_EVENT_REMOVE events from the file descriptor. These events are triggerred when Firecracker calls madvise() with MADV_DONTNEED on some memory range that we are tracking. This Firecracker behaviour is support with version 1.14.0 onwards using the free page reporting and hinting features of balloon devices. What this means for us is that, we need to track removed pages because subsequent page faults need to be served with a zero page. Co-authored-by: Tomas Valenta Signed-off-by: Babis Chalios --- .../orchestrator/pkg/sandbox/block/tracker.go | 72 --- .../pkg/sandbox/block/tracker_test.go | 173 ------- .../userfaultfd/cross_process_helpers_test.go | 44 +- .../pkg/sandbox/uffd/userfaultfd/deferred.go | 26 ++ .../pkg/sandbox/uffd/userfaultfd/invoke.go | 10 + .../pkg/sandbox/uffd/userfaultfd/prefault.go | 83 ++++ .../sandbox/uffd/userfaultfd/userfaultfd.go | 425 ++++++++++-------- 7 files changed, 389 insertions(+), 444 deletions(-) delete mode 100644 packages/orchestrator/pkg/sandbox/block/tracker.go delete mode 100644 packages/orchestrator/pkg/sandbox/block/tracker_test.go create mode 100644 packages/orchestrator/pkg/sandbox/uffd/userfaultfd/deferred.go create mode 100644 packages/orchestrator/pkg/sandbox/uffd/userfaultfd/invoke.go create mode 100644 packages/orchestrator/pkg/sandbox/uffd/userfaultfd/prefault.go diff --git a/packages/orchestrator/pkg/sandbox/block/tracker.go b/packages/orchestrator/pkg/sandbox/block/tracker.go deleted file mode 100644 index 91c4975e9b..0000000000 --- a/packages/orchestrator/pkg/sandbox/block/tracker.go +++ /dev/null @@ -1,72 +0,0 @@ -package block - -import ( - "iter" - "sync" - - "github.com/RoaringBitmap/roaring/v2" - - "github.com/e2b-dev/infra/packages/shared/pkg/storage/header" -) - -type Tracker struct { - b *roaring.Bitmap - mu sync.RWMutex - - blockSize int64 -} - -func NewTracker(blockSize int64) *Tracker { - return &Tracker{ - b: roaring.New(), - blockSize: blockSize, - } -} - -func (t *Tracker) Has(off int64) bool { - t.mu.RLock() - defer t.mu.RUnlock() - - return t.b.Contains(uint32(header.BlockIdx(off, t.blockSize))) -} - -func (t *Tracker) Add(off int64) { - t.mu.Lock() - defer t.mu.Unlock() - - t.b.Add(uint32(header.BlockIdx(off, t.blockSize))) -} - -func (t *Tracker) Reset() { - t.mu.Lock() - defer t.mu.Unlock() - - t.b.Clear() -} - -func (t *Tracker) BlockSize() int64 { - return t.blockSize -} - -func (t *Tracker) Clone() *Tracker { - t.mu.RLock() - defer t.mu.RUnlock() - - return &Tracker{ - b: t.b.Clone(), - blockSize: t.BlockSize(), - } -} - -func (t *Tracker) Offsets() iter.Seq[int64] { - t.mu.RLock() - defer t.mu.RUnlock() - - snapshot := t.b.Clone() - - return func(yield func(int64) bool) { - snapshot.Iterate(func(idx uint32) bool { - return yield(header.BlockOffset(int64(idx), t.blockSize)) - }) - } -} diff --git a/packages/orchestrator/pkg/sandbox/block/tracker_test.go b/packages/orchestrator/pkg/sandbox/block/tracker_test.go deleted file mode 100644 index f872baad93..0000000000 --- a/packages/orchestrator/pkg/sandbox/block/tracker_test.go +++ /dev/null @@ -1,173 +0,0 @@ -package block - -import ( - "maps" - "math/rand" - "slices" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestTracker_AddAndHas(t *testing.T) { - t.Parallel() - const pageSize = 4096 - tr := NewTracker(pageSize) - - offset := int64(pageSize * 4) - - // Initially should not be marked - assert.False(t, tr.Has(offset), "Expected offset %d not to be marked initially", offset) - - // After adding, should be marked - tr.Add(offset) - assert.True(t, tr.Has(offset), "Expected offset %d to be marked after Add", offset) - - // Other offsets should not be marked - otherOffsets := []int64{ - 0, pageSize, 2 * pageSize, 3 * pageSize, 5 * pageSize, 10 * pageSize, - } - for _, other := range otherOffsets { - if other == offset { - continue - } - assert.False(t, tr.Has(other), "Did not expect offset %d to be marked (only %d should be marked)", other, offset) - } -} - -func TestTracker_Reset(t *testing.T) { - t.Parallel() - const pageSize = 4096 - tr := NewTracker(pageSize) - - offset := int64(pageSize * 4) - - // Add offset and verify it's marked - tr.Add(offset) - assert.True(t, tr.Has(offset), "Expected offset %d to be marked after Add", offset) - - // After reset, should not be marked - tr.Reset() - assert.False(t, tr.Has(offset), "Expected offset %d to be cleared after Reset", offset) - - // Offsets that were never set should also remain unset - otherOffsets := []int64{0, pageSize, 2 * pageSize, pageSize * 10} - for _, other := range otherOffsets { - assert.False(t, tr.Has(other), "Expected offset %d to not be marked after Reset", other) - } -} - -func TestTracker_MultipleOffsets(t *testing.T) { - t.Parallel() - const pageSize = 4096 - tr := NewTracker(pageSize) - - offsets := []int64{0, pageSize, 2 * pageSize, 10 * pageSize} - - // Add multiple offsets - for _, o := range offsets { - tr.Add(o) - } - - // Verify all offsets are marked - for _, o := range offsets { - assert.True(t, tr.Has(o), "Expected offset %d to be marked", o) - } - - // Check offsets in between added offsets are not set - // (Offsets that aren't inside any marked block should not be marked) - nonSetOffsets := []int64{ - 3 * pageSize, - 4 * pageSize, - 5 * pageSize, - 6 * pageSize, - 7 * pageSize, - 8 * pageSize, - 9 * pageSize, - 11 * pageSize, - } - for _, off := range nonSetOffsets { - assert.False(t, tr.Has(off), "Expected offset %d to not be marked (only explicit blocks added)", off) - } -} - -func TestTracker_ResetClearsAll(t *testing.T) { - t.Parallel() - const pageSize = 4096 - tr := NewTracker(pageSize) - - offsets := []int64{0, pageSize, 2 * pageSize, 10 * pageSize} - - // Add multiple offsets - for _, o := range offsets { - tr.Add(o) - } - - // Reset should clear all - tr.Reset() - - // Verify all offsets are cleared - for _, o := range offsets { - assert.False(t, tr.Has(o), "Expected offset %d to be cleared after Reset", o) - } - // Check unrelated offsets also not marked - moreOffsets := []int64{3 * pageSize, 7 * pageSize, 100, 4095} - for _, o := range moreOffsets { - assert.False(t, tr.Has(o), "Expected offset %d to not be marked after Reset", o) - } -} - -func TestTracker_MisalignedOffset(t *testing.T) { - t.Parallel() - const pageSize = 4096 - tr := NewTracker(pageSize) - - // Test with misaligned offset - misalignedOffset := int64(123) - tr.Add(misalignedOffset) - - // Should be set for the block containing the offset—that is, block 0 (0..4095) - assert.True(t, tr.Has(misalignedOffset), "Expected misaligned offset %d to be marked (should mark its containing block)", misalignedOffset) - - // Now check that any offset in the same block is also considered marked - anotherOffsetInSameBlock := int64(1000) - assert.True(t, tr.Has(anotherOffsetInSameBlock), "Expected offset %d to be marked as in same block as %d", anotherOffsetInSameBlock, misalignedOffset) - - // But not for a different block - offsetInNextBlock := int64(pageSize) - assert.False(t, tr.Has(offsetInNextBlock), "Did not expect offset %d to be marked", offsetInNextBlock) - - // And not far outside any set block - offsetFar := int64(2 * pageSize) - assert.False(t, tr.Has(offsetFar), "Did not expect offset %d to be marked", offsetFar) -} - -func TestTracker_Offsets(t *testing.T) { - t.Parallel() - const pageSize = 4096 - tr := NewTracker(pageSize) - - numOffsets := 300 - - offsetsMap := map[int64]struct{}{} - - for range numOffsets { - select { - case <-t.Context().Done(): - t.FailNow() - default: - } - - base := int64(rand.Intn(121)) // 0..120 - offset := base * pageSize - - offsetsMap[offset] = struct{}{} - tr.Add(offset) - } - - expectedOffsets := slices.Collect(maps.Keys(offsetsMap)) - actualOffsets := slices.Collect(tr.Offsets()) - - assert.Len(t, actualOffsets, len(expectedOffsets)) - assert.ElementsMatch(t, expectedOffsets, actualOffsets) -} diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go index db20d5f5a0..100d40fd0a 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go @@ -16,6 +16,7 @@ import ( "os" "os/exec" "os/signal" + "slices" "strconv" "strings" "syscall" @@ -307,8 +308,19 @@ func crossProcessServe() error { case <-ctx.Done(): return case <-offsetsSignal: - for offset := range uffd.faulted().Offsets() { - writeErr := binary.Write(offsetsFile, binary.LittleEndian, uint64(offset)) + faultedOffsets, faultedErr := uffd.faulted() + if faultedErr != nil { + msg := fmt.Errorf("error getting faulted offsets: %w", faultedErr) + + fmt.Fprint(os.Stderr, msg.Error()) + + cancel(msg) + + return + } + + for _, offset := range faultedOffsets { + writeErr := binary.Write(offsetsFile, binary.LittleEndian, offset) if writeErr != nil { msg := fmt.Errorf("error writing offsets to file: %w", writeErr) @@ -383,3 +395,31 @@ func crossProcessServe() error { return nil } } + +func (u *Userfaultfd) faulted() ([]uint64, error) { + // This will be at worst cancelled when the uffd is closed. + u.settleRequests.Lock() + u.settleRequests.Unlock() //nolint:staticcheck // SA2001: intentional — we just need to settle the read locks. + + return u.pageTracker.faultedOffsets(u.ma) +} + +func (pt *pageTracker) faultedOffsets(ma *memory.Mapping) ([]uint64, error) { + pt.mu.RLock() + defer pt.mu.RUnlock() + + offsets := make([]uint64, 0, len(pt.m)) + for addr := range pt.m { + offset, err := ma.GetOffset(addr) + if err != nil { + return nil, fmt.Errorf("address %#x not in mapping: %w", addr, err) + } + offsets = append(offsets, uint64(offset)) + } + + if len(offsets) > 1 { + slices.Sort(offsets) + } + + return offsets, nil +} diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/deferred.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/deferred.go new file mode 100644 index 0000000000..6089ad7660 --- /dev/null +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/deferred.go @@ -0,0 +1,26 @@ +package userfaultfd + +import "sync" + +// deferredFaults collects pagefaults that couldn't be handled (EAGAIN) +// and need to be retried on the next poll iteration. Safe for concurrent push. +type deferredFaults struct { + mu sync.Mutex + pf []*UffdPagefault +} + +func (d *deferredFaults) push(pf *UffdPagefault) { + d.mu.Lock() + d.pf = append(d.pf, pf) + d.mu.Unlock() +} + +// drain returns all accumulated pagefaults and resets the internal list. +func (d *deferredFaults) drain() []*UffdPagefault { + d.mu.Lock() + out := d.pf + d.pf = nil + d.mu.Unlock() + + return out +} diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/invoke.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/invoke.go new file mode 100644 index 0000000000..71205ca263 --- /dev/null +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/invoke.go @@ -0,0 +1,10 @@ +package userfaultfd + +// safeInvoke calls fn and returns its result, or nil if fn is nil. +func safeInvoke(fn func() error) error { + if fn == nil { + return nil + } + + return fn() +} diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/prefault.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/prefault.go new file mode 100644 index 0000000000..44724b81ac --- /dev/null +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/prefault.go @@ -0,0 +1,83 @@ +package userfaultfd + +import ( + "context" + "fmt" + + "github.com/e2b-dev/infra/packages/orchestrator/pkg/sandbox/block" +) + +// Prefault proactively copies a page to guest memory at the given offset. +// This is used to speed up sandbox starts by prefetching pages that are known to be needed. +// Returns nil on success, or if the page is already mapped (EEXIST is handled gracefully). +func (u *Userfaultfd) Prefault(ctx context.Context, offset int64, data []byte) error { + u.settleRequests.RLock() + defer u.settleRequests.RUnlock() + + ctx, span := tracer.Start(ctx, "prefault page") + defer span.End() + + addr, err := u.ma.GetHostVirtAddr(offset) + if err != nil { + return fmt.Errorf("failed to get host virtual address: %w", err) + } + + if len(data) != int(u.pageSize) { + return fmt.Errorf("data length (%d) does not match pagesize (%d)", len(data), u.pageSize) + } + + // If page has already been faulted in due to on-demand page fault handling or removed because + // Firecracker called madvise() on it, skip it. + state := u.pageTracker.get(addr) + if state == faulted || state == removed { + return nil + } + + // We're treating prefault handling as if it was caused by a read access. + // This way, we will fault the page with UFFD_COPY_MODE_WP which will set + // the WP bit for the page. This works even in the case of a race with a + // concurrent on-demand write access. + // + // If the on-demand fault handler beats us, we will get an EEXIST here. + // If we beat the on-demand handler, it will get the EEXIST. + // + // In both cases, the WP bit will be cleared because it is handled asynchronously + // by the kernel. + handled, err := u.faultPage( + ctx, + addr, + offset, + directDataSource{data, int64(u.pageSize)}, + nil, + // TODO: What mode should we pass here? + 0, + ) + if err != nil { + span.RecordError(fmt.Errorf("could not prefault page")) + + return fmt.Errorf("failed to fault page: %w", err) + } + + if !handled { + span.AddEvent("prefault: page already faulted or write returned EAGAIN") + } else { + u.pageTracker.setState(addr, addr+u.pageSize, faulted) + u.prefetchTracker.Add(offset, block.Prefetch) + } + + return nil +} + +// directDataSource wraps a byte slice to implement block.Slicer for prefaulting. +type directDataSource struct { + data []byte + pagesize int64 +} + +func (d directDataSource) Slice(_ context.Context, _, _ int64) ([]byte, error) { + return d.data, nil +} + +func (d directDataSource) BlockSize() int64 { + return d.pagesize +} diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index ca2da4a353..27ba843a1d 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -21,6 +21,7 @@ import ( "github.com/e2b-dev/infra/packages/orchestrator/pkg/sandbox/uffd/fdexit" "github.com/e2b-dev/infra/packages/orchestrator/pkg/sandbox/uffd/memory" "github.com/e2b-dev/infra/packages/shared/pkg/logger" + "github.com/e2b-dev/infra/packages/shared/pkg/storage/header" ) var tracer = otel.Tracer("github.com/e2b-dev/infra/packages/orchestrator/pkg/sandbox/uffd/userfaultfd") @@ -53,12 +54,8 @@ type Userfaultfd struct { pageSize uintptr pageTracker pageTracker - // We don't skip the already mapped pages, because if the memory is swappable the page *might* under some conditions be mapped out. - // For hugepages this should not be a problem, but might theoretically happen to normal pages with swap - missingRequests *block.Tracker - // We use the settleRequests to guard the missingRequests so we can access a consistent state of the missingRequests after the requests are finished. - settleRequests sync.RWMutex - + // We use the settleRequests to guard the prefetchTracker so we can access a consistent state of the prefetchTracker after the requests are finished. + settleRequests sync.RWMutex prefetchTracker *block.PrefetchTracker wg errgroup.Group @@ -81,7 +78,6 @@ func NewUserfaultfdFromFd(fd uintptr, src block.Slicer, m *memory.Mapping, logge src: src, pageSize: uintptr(blockSize), pageTracker: newPageTracker(uintptr(blockSize)), - missingRequests: block.NewTracker(blockSize), prefetchTracker: block.NewPrefetchTracker(blockSize), ma: m, logger: logger, @@ -95,8 +91,59 @@ func NewUserfaultfdFromFd(fd uintptr, src block.Slicer, m *memory.Mapping, logge return u, nil } -func (u *Userfaultfd) Close() error { - return u.fd.close() +// readEvents reads all available UFFD events from the file descriptor, +// returning removes and pagefaults separately. +func (u *Userfaultfd) readEvents(ctx context.Context) ([]*UffdRemove, []*UffdPagefault, error) { + // We are reusing the same buffer for all events, but that's fine, + // because getMsgArg, will make a copy of the actual event from `buf` + // and it's a pointer to this copy that we are returning to caller. + buf := make([]byte, unsafe.Sizeof(UffdMsg{})) + + var removes []*UffdRemove + var pagefaults []*UffdPagefault + + for { + n, err := syscall.Read(int(u.fd), buf) + if errors.Is(err, syscall.EINTR) { + u.logger.Debug(ctx, "uffd: interrupted read. Reading again") + + continue + } + + if errors.Is(err, syscall.EAGAIN) { + // EAGAIN means that we have drained all the available events for the file descriptor. + // We are done. + break + } + + if err != nil { + return nil, nil, fmt.Errorf("failed reading uffd: %w", err) + } + + // `Read` returned with 0 bytes actually read. No more events to read + // and the writing end has been closed. This should never happen, unless + // something (us or Firecracker) closes the file descriptor + // TODO: Ignore it for now, but maybe we should return an error(?) + if n == 0 { + break + } + + msg := (*UffdMsg)(unsafe.Pointer(&buf[0])) + + event := getMsgEvent(msg) + arg := getMsgArg(msg) + + switch event { + case UFFD_EVENT_PAGEFAULT: + pagefaults = append(pagefaults, (*UffdPagefault)(unsafe.Pointer(&arg[0]))) + case UFFD_EVENT_REMOVE: + removes = append(removes, (*UffdRemove)(unsafe.Pointer(&arg[0]))) + default: + return nil, nil, ErrUnexpectedEventType + } + } + + return removes, pagefaults, nil } func (u *Userfaultfd) Serve( @@ -126,7 +173,8 @@ func (u *Userfaultfd) Serve( unix.POLLNVAL: "POLLNVAL", } -outerLoop: + var deferred deferredFaults + for { if _, err := unix.Poll( pollFds, @@ -192,252 +240,235 @@ outerLoop: continue } - buf := make([]byte, unsafe.Sizeof(UffdMsg{})) - - for { - _, err := syscall.Read(int(u.fd), buf) - if err == syscall.EINTR { - u.logger.Debug(ctx, "uffd: interrupted read, reading again") - - continue - } - - if err == nil { - // There is no error so we can proceed. - - eagainCounter.Log(ctx) - noDataCounter.Log(ctx) - - break - } - - if err == syscall.EAGAIN { - eagainCounter.Increase("EAGAIN") - - // Continue polling the fd. - continue outerLoop - } - + removes, pagefaults, err := u.readEvents(ctx) + if err != nil { u.logger.Error(ctx, "uffd: read error", zap.Error(err)) return fmt.Errorf("failed to read: %w", err) } - msg := *(*UffdMsg)(unsafe.Pointer(&buf[0])) - - if msgEvent := getMsgEvent(&msg); msgEvent != UFFD_EVENT_PAGEFAULT { - u.logger.Error(ctx, "UFFD serve unexpected event type", zap.Any("event_type", msgEvent)) - - return ErrUnexpectedEventType - } - - arg := getMsgArg(&msg) - pagefault := (*(*UffdPagefault)(unsafe.Pointer(&arg[0]))) - flags := pagefault.flags - - addr := getPagefaultAddress(&pagefault) - - offset, err := u.ma.GetOffset(addr) - if err != nil { - u.logger.Error(ctx, "UFFD serve get mapping error", zap.Error(err)) - - return fmt.Errorf("failed to map: %w", err) - } - - // Handle write to missing page (WRITE flag) - // If the event has WRITE flag, it was a write to a missing page. - // For the write to be executed, we first need to copy the page from the source to the guest memory. - if flags&UFFD_PAGEFAULT_FLAG_WRITE != 0 { - u.wg.Go(func() error { - return u.faultPage(ctx, addr, offset, u.pageSize, u.src, fdExit.SignalExit, block.Write) - }) + // No events were found which is weird since, if we are here, + // poll() returned with an event indicating that UFFD had something + // for us to read. Log an error and continue + if len(removes) == 0 && len(pagefaults) == 0 { + eagainCounter.Increase("EAGAIN") continue } - // Handle read to missing page ("MISSING" flag) - // If the event has no flags, it was a read to a missing page and we need to copy the page from the source to the guest memory. - if flags == 0 { - u.wg.Go(func() error { - return u.faultPage(ctx, addr, offset, u.pageSize, u.src, fdExit.SignalExit, block.Read) - }) + // We successfully read all available UFFD events. + noDataCounter.Log(ctx) + eagainCounter.Log(ctx) - continue + // First handle the UFFD_EVENT_REMOVE events + for _, rm := range removes { + u.pageTracker.setState(uintptr(rm.start), uintptr(rm.end), removed) } - // MINOR and WP flags are not expected as we don't register the uffd with these flags. - return fmt.Errorf("unexpected event type: %d, closing uffd", flags) - } -} - -func (u *Userfaultfd) faulted() *block.Tracker { - // This will be at worst cancelled when the uffd is closed. - u.settleRequests.Lock() - // The locking here would work even without using defer (just lock-then-unlock the mutex), but at this point let's make it lock to the clone, - // so it is consistent even if there is a another uffd call after. - defer u.settleRequests.Unlock() - - return u.missingRequests.Clone() -} - -func (u *Userfaultfd) PrefetchData() block.PrefetchData { - // This will be at worst cancelled when the uffd is closed. - u.settleRequests.Lock() - // The locking here would work even without using defer (just lock-then-unlock the mutex), but at this point let's make it lock to the clone, - // so it is consistent even if there is a another uffd call after. - defer u.settleRequests.Unlock() + // Collect deferred pagefaults from previous iteration's goroutines. + pagefaults = append(deferred.drain(), pagefaults...) - return u.prefetchTracker.PrefetchData() -} + for _, pf := range pagefaults { + // We don't handle minor page faults. + if pf.flags&UFFD_PAGEFAULT_FLAG_MINOR != 0 { + return fmt.Errorf("unexpected MINOR pagefault event, closing UFFD") + } -// Prefault proactively copies a page to guest memory at the given offset. -// This is used to speed up sandbox starts by prefetching pages that are known to be needed. -// Returns nil on success, or if the page is already mapped (EEXIST is handled gracefully). -func (u *Userfaultfd) Prefault(ctx context.Context, offset int64, data []byte) error { - ctx, span := tracer.Start(ctx, "prefault page") - defer span.End() - - // Get host virtual address and page size for this offset - addr, err := u.ma.GetHostVirtAddr(offset) - if err != nil { - return fmt.Errorf("failed to get host virtual address: %w", err) - } + // We don't handle write-protection page faults, we're using asynchronous write protection. + if pf.flags&UFFD_PAGEFAULT_FLAG_WP != 0 { + return fmt.Errorf("unexpected WP pagefault event, closing UFFD") + } - if len(data) != int(u.pageSize) { - return fmt.Errorf("data length (%d) is less than pagesize (%d)", len(data), u.pageSize) - } + addr := getPagefaultAddress(pf) + offset, err := u.ma.GetOffset(addr) + if err != nil { + u.logger.Error(ctx, "UFFD serve got mapping error", zap.Error(err)) - return u.faultPage(ctx, addr, offset, u.pageSize, directDataSource{data, int64(u.pageSize)}, nil, block.Prefetch) -} + return fmt.Errorf("failed to map: %w", err) + } -// directDataSource wraps a byte slice to implement block.Slicer for prefaulting. -type directDataSource struct { - data []byte - pagesize int64 -} + var source block.Slicer -func (d directDataSource) Slice(_ context.Context, _, _ int64) ([]byte, error) { - return d.data, nil -} + switch state := u.pageTracker.get(addr); state { + case faulted: + // Skip faulting the page. This has already been faulted, either during pre-faulting + // or because we handled another page fault on the same address in the current + // iteration. It can only be removed via a a UFFD_EVENT_REMOVE, which will mark the + // page as `unfaulted`. + continue + case removed: + // Fault the page as empty. + case unfaulted: + source = u.src + default: + return fmt.Errorf("unexpected pageState: %#v", state) + } -func (d directDataSource) BlockSize() int64 { - return d.pagesize + u.wg.Go(func() error { + // The RLock must be called inside the goroutine to ensure RUnlock runs via defer, + // even if the errgroup is cancelled or the goroutine returns early. + // This check protects us against race condition between marking the request for prefetching and accessing the prefetchTracker. + u.settleRequests.RLock() + defer u.settleRequests.RUnlock() + + var copyMode CULong + var accessType block.AccessType + + // Performing copy() on UFFD clears the WP bit unless we explicitly tell + // it not to. We do that for faults caused by a read access. Write accesses + // would anyways cause clear the write-protection bit. + if pf.flags&UFFD_PAGEFAULT_FLAG_WRITE == 0 { + copyMode = UFFDIO_COPY_MODE_WP + accessType = block.Read + } else { + accessType = block.Write + } + + handled, err := u.faultPage( + ctx, + addr, + offset, + source, + fdExit.SignalExit, + copyMode, + ) + if err != nil { + return err + } + + if handled { + u.pageTracker.setState(addr, addr+u.pageSize, faulted) + u.prefetchTracker.Add(offset, accessType) + } else { + deferred.push(pf) + } + + return nil + }) + } + } } func (u *Userfaultfd) faultPage( ctx context.Context, addr uintptr, offset int64, - pagesize uintptr, source block.Slicer, onFailure func() error, - accessType block.AccessType, -) error { + mode CULong, +) (bool, error) { span := trace.SpanFromContext(ctx) - // The RLock must be called inside the goroutine to ensure RUnlock runs via defer, - // even if the errgroup is cancelled or the goroutine returns early. - // This check protects us against race condition between marking the request as missing and accessing the missingRequests tracker. - // The Firecracker pause should return only after the requested memory is faulted in, so we don't need to guard the pagefault from the moment it is created. - u.settleRequests.RLock() - defer u.settleRequests.RUnlock() - defer func() { if r := recover(); r != nil { - u.logger.Error(ctx, "UFFD serve panic", zap.Any("pagesize", pagesize), zap.Any("panic", r)) + u.logger.Error(ctx, "UFFD serve panic", zap.Any("pagesize", u.pageSize), zap.Any("panic", r)) } }() - var b []byte - var dataErr error - var attempt int - -retryLoop: - for attempt = range sliceMaxRetries + 1 { - b, dataErr = source.Slice(ctx, offset, int64(pagesize)) - if dataErr == nil { - break - } + var writeErr error + + // Write to guest memory. nil data means zero-fill + switch { + case source == nil && u.pageSize == header.PageSize: + writeErr = u.fd.zero(addr, u.pageSize, mode) + case source == nil && u.pageSize == header.HugepageSize: + writeErr = u.fd.copy(addr, u.pageSize, header.EmptyHugePage, mode) + default: + var b []byte + var dataErr error + var attempt int + + retryLoop: + for attempt = range sliceMaxRetries + 1 { + b, dataErr = source.Slice(ctx, offset, int64(u.pageSize)) + if dataErr == nil { + break + } - if attempt >= sliceMaxRetries || ctx.Err() != nil { - break - } + if attempt >= sliceMaxRetries || ctx.Err() != nil { + break + } - u.logger.Warn(ctx, "UFFD serve slice error, retrying", - zap.Int("attempt", attempt+1), - zap.Int("max_attempts", sliceMaxRetries+1), - zap.Error(dataErr), - ) + u.logger.Warn(ctx, "UFFD serve slice error, retrying", + zap.Int("attempt", attempt+1), + zap.Int("max_attempts", sliceMaxRetries+1), + zap.Error(dataErr), + ) - delay := min(sliceRetryBaseDelay< Date: Mon, 9 Mar 2026 15:20:52 +0100 Subject: [PATCH 10/63] fix(uffd): prefault with UFFD_COPY_MODE_WP Do that, so we maintain the dirty page tracking on. Prefaulting does not occur due to write faults, so tracking must be maintained. Writing will clear the bit asynchronously in the kernel. Signed-off-by: Babis Chalios --- packages/orchestrator/pkg/sandbox/uffd/userfaultfd/prefault.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/prefault.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/prefault.go index 44724b81ac..feb3da2a1e 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/prefault.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/prefault.go @@ -49,8 +49,7 @@ func (u *Userfaultfd) Prefault(ctx context.Context, offset int64, data []byte) e offset, directDataSource{data, int64(u.pageSize)}, nil, - // TODO: What mode should we pass here? - 0, + UFFDIO_COPY_MODE_WP, ) if err != nil { span.RecordError(fmt.Errorf("could not prefault page")) From 6fca058daeb8a7c958f323d1baa93d35ffd10a90 Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Mon, 9 Mar 2026 17:23:22 +0100 Subject: [PATCH 11/63] fix(uffd): don't pass UFFD_COPY_MODE_WP when zeroing a page userfaultfd_zeropage() does not understand UFFD_COPY_MODE_WP. When zeroing a page we should always be passing 0 in mode (since we always want to unblock the faulting thread). For userfaultfd_copy() we want to provide UFFD_COPY_MODE_WP only when we are copying due to a read. This includes read-triggerred page faults from Firecracker and us pre-faulting. Signed-off-by: Babis Chalios --- .../pkg/sandbox/uffd/userfaultfd/prefault.go | 4 ++-- .../sandbox/uffd/userfaultfd/userfaultfd.go | 21 ++++++++++++------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/prefault.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/prefault.go index feb3da2a1e..1df9937206 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/prefault.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/prefault.go @@ -34,7 +34,7 @@ func (u *Userfaultfd) Prefault(ctx context.Context, offset int64, data []byte) e } // We're treating prefault handling as if it was caused by a read access. - // This way, we will fault the page with UFFD_COPY_MODE_WP which will set + // This way, we will fault the page with UFFD_COPY_MODE_WP which will preserve // the WP bit for the page. This works even in the case of a race with a // concurrent on-demand write access. // @@ -47,9 +47,9 @@ func (u *Userfaultfd) Prefault(ctx context.Context, offset int64, data []byte) e ctx, addr, offset, + block.Read, directDataSource{data, int64(u.pageSize)}, nil, - UFFDIO_COPY_MODE_WP, ) if err != nil { span.RecordError(fmt.Errorf("could not prefault page")) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index 27ba843a1d..5f881cb2a0 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -311,14 +311,9 @@ func (u *Userfaultfd) Serve( u.settleRequests.RLock() defer u.settleRequests.RUnlock() - var copyMode CULong var accessType block.AccessType - // Performing copy() on UFFD clears the WP bit unless we explicitly tell - // it not to. We do that for faults caused by a read access. Write accesses - // would anyways cause clear the write-protection bit. if pf.flags&UFFD_PAGEFAULT_FLAG_WRITE == 0 { - copyMode = UFFDIO_COPY_MODE_WP accessType = block.Read } else { accessType = block.Write @@ -328,9 +323,9 @@ func (u *Userfaultfd) Serve( ctx, addr, offset, + accessType, source, fdExit.SignalExit, - copyMode, ) if err != nil { return err @@ -353,9 +348,9 @@ func (u *Userfaultfd) faultPage( ctx context.Context, addr uintptr, offset int64, + accessType block.AccessType, source block.Slicer, onFailure func() error, - mode CULong, ) (bool, error) { span := trace.SpanFromContext(ctx) @@ -366,11 +361,21 @@ func (u *Userfaultfd) faultPage( }() var writeErr error + var mode CULong + + // Performing copy() on UFFD clears the WP bit unless we explicitly tell + // it not to. We do that for faults caused by a read access. Write accesses + // would anyways clear the write-protection bit. + if accessType == block.Read { + mode = UFFDIO_COPY_MODE_WP + } // Write to guest memory. nil data means zero-fill switch { case source == nil && u.pageSize == header.PageSize: - writeErr = u.fd.zero(addr, u.pageSize, mode) + // Here, `mode` is always 0. The only mode `userfaultfd_zeropage()` understands + // is UFFDIO_ZEROPAGE_DONT_WAKE, which we never use. + writeErr = u.fd.zero(addr, u.pageSize, 0) case source == nil && u.pageSize == header.HugepageSize: writeErr = u.fd.copy(addr, u.pageSize, header.EmptyHugePage, mode) default: From cc779f4119324bc6f509c90a50fc2d10918cbc66 Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Tue, 10 Mar 2026 14:26:54 +0100 Subject: [PATCH 12/63] fix(uffd): handle 4K write protection correctly When guest memory is backed by 4K pages, Firecracker backs it with anonymous pages. Anonymous pages can only be write-protected when there is page entry present. This means that until we receive a fault for it a page is not write-protected. This is fine. Not present (not faulted) pages cannot be dirty, by definition. When handling page faults for known pages, we use UFFDIO_COPY with write-protection mode, which automatically sets write-protection for the faulted page. However, imagine this sequence: 1. We resume from a snapshot 2. We receive a UFFD_EVENT_REMOVE for a range that was not previously faulted in. 3. We receive a page fault for a page within the removed region. The correct way to handle this is providing the zero page and that's what we do. However, UFFDIO_ZERO does not have a write-protected mode, so the newly setup page is not write-protected tracked. Such a page will always be reported dirty from Firecracker (present and !write-protected) and it will be needlessly included in the snapshot. Handle this by explicitly marking such pages as write-protected. Handle the race condition by providing the zero page without waking up the thread, marking it write protected and then waking up the thread. Note, that huge pages don't have this issue because: 1. Hugetlbfs-backed pages are write-protected by Firecracker 2. we always handle huge page faults using UFFDIO_COPY with write-protection mode. Signed-off-by: Babis Chalios --- .../sandbox/uffd/userfaultfd/userfaultfd.go | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index 5f881cb2a0..7f4fc88877 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -372,9 +372,26 @@ func (u *Userfaultfd) faultPage( // Write to guest memory. nil data means zero-fill switch { - case source == nil && u.pageSize == header.PageSize: - // Here, `mode` is always 0. The only mode `userfaultfd_zeropage()` understands - // is UFFDIO_ZEROPAGE_DONT_WAKE, which we never use. + case source == nil && u.pageSize == header.PageSize && accessType == block.Read: + // Firecracker uses anonymous mappings for 4K pages. Anonymous mappings can only + // be write protected once pages are populated. We need to enable write-protection + // *after* we serve the page fault. + // + // To avoid the race condition, first serve the page without waking the thread + writeErr = u.fd.zero(addr, u.pageSize, UFFDIO_ZEROPAGE_MODE_DONTWAKE) + if writeErr != nil { + break + } + // Then, write-protect the page + writeErr = u.fd.writeProtect(addr, u.pageSize, UFFDIO_WRITEPROTECT_MODE_WP) + if writeErr != nil { + break + } + // And, finally, wake up the faulting thread + writeErr = u.fd.wake(addr, u.pageSize) + case source == nil && u.pageSize == header.PageSize && accessType == block.Write: + // If this was a write access to a 4K page simply provide the zero page (clearing the WP bit) + // and wake up the thread in one step. writeErr = u.fd.zero(addr, u.pageSize, 0) case source == nil && u.pageSize == header.HugepageSize: writeErr = u.fd.copy(addr, u.pageSize, header.EmptyHugePage, mode) From ba20a69a864e261c0cf7e36b847449699693bb90 Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Thu, 12 Feb 2026 00:33:05 -0800 Subject: [PATCH 13/63] feat: enable free page reporting Balloon devices provide memory reclamation facilities through free page reporting, as a more efficient mechanism (in terms of latency and CPU time) than traditional ballooning. Free page reporting instructs the guest to periodically report memory that has been freed, so that we can reclaim it in the host side. It is enabled before starting the sandbox and does not require any further host-side orchestration. Enable free page reporting for all new templates using Firecracker versions >= v1.14.0. Also, allow users to optionally disable it for these versions. Older Firecracker versions don't support the feature. Trying to enable it for those, will return an error. Co-authored-by: Valenta Tomas Signed-off-by: Babis Chalios --- .../api/internal/sandbox/sandbox_features.go | 10 ++++++- .../template-manager/create_template.go | 3 ++ .../orchestrator/cmd/create-build/main.go | 30 +++++++++++++++++-- .../orchestrator/pkg/sandbox/fc/client.go | 24 +++++++++++++++ .../orchestrator/pkg/sandbox/fc/process.go | 11 +++++++ packages/orchestrator/pkg/sandbox/sandbox.go | 4 +++ .../pkg/template/build/config/config.go | 3 ++ .../pkg/template/build/phases/base/builder.go | 7 +++-- .../template/build/phases/finalize/builder.go | 7 +++-- .../template/build/phases/steps/builder.go | 7 +++-- .../pkg/template/server/create_template.go | 1 + packages/orchestrator/template-manager.proto | 1 + .../template-manager/template-manager.pb.go | 7 +++++ 13 files changed, 102 insertions(+), 13 deletions(-) diff --git a/packages/api/internal/sandbox/sandbox_features.go b/packages/api/internal/sandbox/sandbox_features.go index e8eb0e7d0d..cf3fb933a8 100644 --- a/packages/api/internal/sandbox/sandbox_features.go +++ b/packages/api/internal/sandbox/sandbox_features.go @@ -39,7 +39,15 @@ func (v *VersionInfo) Version() semver.Version { } func (v *VersionInfo) HasHugePages() bool { - if v.lastReleaseVersion.Major() >= 1 && v.lastReleaseVersion.Minor() >= 7 { + if v.lastReleaseVersion.Major() == 1 && v.lastReleaseVersion.Minor() >= 7 { + return true + } + + return false +} + +func (v *VersionInfo) HasFreePageReporting() bool { + if v.lastReleaseVersion.Major() == 1 && v.lastReleaseVersion.Minor() >= 14 { return true } diff --git a/packages/api/internal/template-manager/create_template.go b/packages/api/internal/template-manager/create_template.go index 9e5ac7224e..373313982a 100644 --- a/packages/api/internal/template-manager/create_template.go +++ b/packages/api/internal/template-manager/create_template.go @@ -109,6 +109,8 @@ func (tm *TemplateManager) CreateTemplate( return fmt.Errorf("failed to convert image registry: %w", err) } + freePageReporting := features.HasFreePageReporting() + template := &templatemanagergrpc.TemplateConfig{ TeamID: teamID.String(), TemplateID: templateID, @@ -119,6 +121,7 @@ func (tm *TemplateManager) CreateTemplate( KernelVersion: kernelVersion, FirecrackerVersion: firecrackerVersion, HugePages: features.HasHugePages(), + FreePageReporting: &freePageReporting, StartCommand: startCmd, ReadyCommand: readyCmd, Force: force, diff --git a/packages/orchestrator/cmd/create-build/main.go b/packages/orchestrator/cmd/create-build/main.go index 5b1e576795..d0701abbf9 100644 --- a/packages/orchestrator/cmd/create-build/main.go +++ b/packages/orchestrator/cmd/create-build/main.go @@ -63,6 +63,7 @@ func main() { memory := flag.Int("memory", 1024, "memory MB") disk := flag.Int("disk", 1024, "disk MB") hugePages := flag.Bool("hugepages", true, "use 2MB huge pages for memory (false = 4KB pages)") + freePageReporting := flag.Bool("free-page-reporting", false, "enable free page reporting via balloon device (requires Firecracker v1.14+)") startCmd := flag.String("start-cmd", "", "start command") setupCmd := flag.String("setup-cmd", "", "setup command to run during build (e.g., install deps)") readyCmd := flag.String("ready-cmd", "", "ready check command") @@ -100,7 +101,16 @@ func main() { log.Fatalf("network config: %v", err) } - err = doBuild(ctx, *templateID, *toBuild, *fromBuild, *kernel, *fc, *vcpu, *memory, *disk, *hugePages, *startCmd, *setupCmd, *readyCmd, localMode, *verbose, *timeout, builderConfig, networkConfig) + // Detect if --free-page-reporting was explicitly set; if not, pass nil so + // doBuild can default based on the Firecracker version. + var fprOverride *bool + flag.Visit(func(f *flag.Flag) { + if f.Name == "free-page-reporting" { + fprOverride = freePageReporting + } + }) + + err = doBuild(ctx, *templateID, *toBuild, *fromBuild, *kernel, *fc, *vcpu, *memory, *disk, *hugePages, fprOverride, *startCmd, *setupCmd, *readyCmd, localMode, *verbose, *timeout, builderConfig, networkConfig) if err != nil { log.Fatal(err) } @@ -199,9 +209,10 @@ func setupEnv(ctx context.Context, storagePath, sandboxDir, kernel, fc string, l func doBuild( parentCtx context.Context, - templateID, buildID, fromBuild, kernel, fc string, + templateID, buildID, fromBuild, kernel, fcVersion string, vcpu, memory, disk int, hugePages bool, + freePageReporting *bool, startCmd, setupCmd, readyCmd string, localMode, verbose bool, timeout int, @@ -341,6 +352,18 @@ func doBuild( }) } + // Default FPR to enabled when the FC version supports it (v1.14+); explicit flag overrides. + var fprEnabled bool + if freePageReporting != nil { + fprEnabled = *freePageReporting + } else { + versionOnly, _, _ := strings.Cut(fcVersion, "_") + supported, err := utils.IsGTEVersion(versionOnly, "v1.14.0") + if err == nil { + fprEnabled = supported + } + } + tmpl := config.TemplateConfig{ Version: templates.TemplateV2LatestVersion, TemplateID: templateID, @@ -349,10 +372,11 @@ func doBuild( MemoryMB: int64(memory), DiskSizeMB: int64(disk), HugePages: hugePages, + FreePageReporting: fprEnabled, StartCmd: startCmd, ReadyCmd: readyCmd, KernelVersion: kernel, - FirecrackerVersion: fc, + FirecrackerVersion: fcVersion, Steps: steps, } diff --git a/packages/orchestrator/pkg/sandbox/fc/client.go b/packages/orchestrator/pkg/sandbox/fc/client.go index f5c9eb30d0..d35d28040c 100644 --- a/packages/orchestrator/pkg/sandbox/fc/client.go +++ b/packages/orchestrator/pkg/sandbox/fc/client.go @@ -427,6 +427,30 @@ func (c *apiClient) startVM(ctx context.Context) error { return nil } +func (c *apiClient) enableFreePageReporting(ctx context.Context) error { + ctx, span := tracer.Start(ctx, "enable-free-page-reporting") + defer span.End() + + amountMib := int64(0) + deflateOnOom := false + + balloonConfig := operations.PutBalloonParams{ + Context: ctx, + Body: &models.Balloon{ + AmountMib: &amountMib, + DeflateOnOom: &deflateOnOom, + FreePageReporting: true, + }, + } + + _, err := c.client.Operations.PutBalloon(&balloonConfig) + if err != nil { + return fmt.Errorf("error setting up balloon device: %w", err) + } + + return nil +} + func (c *apiClient) memoryMapping(ctx context.Context) (*memory.Mapping, error) { params := operations.GetMemoryMappingsParams{ Context: ctx, diff --git a/packages/orchestrator/pkg/sandbox/fc/process.go b/packages/orchestrator/pkg/sandbox/fc/process.go index e4ea20fcbe..ea472656ee 100644 --- a/packages/orchestrator/pkg/sandbox/fc/process.go +++ b/packages/orchestrator/pkg/sandbox/fc/process.go @@ -299,6 +299,7 @@ func (p *Process) Create( vCPUCount int64, memoryMB int64, hugePages bool, + freePageReporting bool, options ProcessOptions, txRateLimit RateLimiterConfig, driveRateLimit RateLimiterConfig, @@ -438,6 +439,16 @@ func (p *Process) Create( } telemetry.ReportEvent(ctx, "set fc entropy config") + if freePageReporting { + err = p.client.enableFreePageReporting(ctx) + if err != nil { + fcStopErr := p.Stop(ctx) + + return errors.Join(fmt.Errorf("error enabling free page reporting: %w", err), fcStopErr) + } + telemetry.ReportEvent(ctx, "enabled free page reporting") + } + err = p.client.startVM(ctx) if err != nil { fcStopErr := p.Stop(ctx) diff --git a/packages/orchestrator/pkg/sandbox/sandbox.go b/packages/orchestrator/pkg/sandbox/sandbox.go index ff33417337..b958d60432 100644 --- a/packages/orchestrator/pkg/sandbox/sandbox.go +++ b/packages/orchestrator/pkg/sandbox/sandbox.go @@ -72,6 +72,9 @@ type Config struct { TotalDiskSizeMB int64 HugePages bool + // Enable free page reporting + FreePageReporting bool + Envd EnvdMetadata FirecrackerConfig fc.Config @@ -509,6 +512,7 @@ func (f *Factory) CreateSandbox( config.Vcpu, config.RamMB, config.HugePages, + config.FreePageReporting, processOptions, fc.RateLimiterConfig{ Ops: fc.TokenBucketConfig(throttleConfig.Ops), diff --git a/packages/orchestrator/pkg/template/build/config/config.go b/packages/orchestrator/pkg/template/build/config/config.go index 713de95ec0..f780b1ca3d 100644 --- a/packages/orchestrator/pkg/template/build/config/config.go +++ b/packages/orchestrator/pkg/template/build/config/config.go @@ -41,6 +41,9 @@ type TemplateConfig struct { // HugePages sets whether the VM use huge pages. HugePages bool + // FreePageReporting enables the corresponding feature in Firecracker + FreePageReporting bool + // Command to run to check if the template is ready. ReadyCmd string diff --git a/packages/orchestrator/pkg/template/build/phases/base/builder.go b/packages/orchestrator/pkg/template/build/phases/base/builder.go index 2a4dfcd2d8..5a1149b51b 100644 --- a/packages/orchestrator/pkg/template/build/phases/base/builder.go +++ b/packages/orchestrator/pkg/template/build/phases/base/builder.go @@ -200,9 +200,10 @@ func (bb *BaseBuilder) buildLayerFromOCI( // Allow sandbox internet access during provisioning (nil network = no restrictions). baseSbxConfig := sandbox.NewConfig(sandbox.Config{ - Vcpu: bb.Config.VCpuCount, - RamMB: bb.Config.MemoryMB, - HugePages: bb.Config.HugePages, + Vcpu: bb.Config.VCpuCount, + RamMB: bb.Config.MemoryMB, + HugePages: bb.Config.HugePages, + FreePageReporting: bb.Config.FreePageReporting, Envd: sandbox.EnvdMetadata{ Version: bb.EnvdVersion, diff --git a/packages/orchestrator/pkg/template/build/phases/finalize/builder.go b/packages/orchestrator/pkg/template/build/phases/finalize/builder.go index 998aca413f..1cc0cb19cd 100644 --- a/packages/orchestrator/pkg/template/build/phases/finalize/builder.go +++ b/packages/orchestrator/pkg/template/build/phases/finalize/builder.go @@ -149,9 +149,10 @@ func (ppb *PostProcessingBuilder) Build( // Configure sandbox for final layer sbxConfig := sandbox.NewConfig(sandbox.Config{ - Vcpu: ppb.Config.VCpuCount, - RamMB: ppb.Config.MemoryMB, - HugePages: ppb.Config.HugePages, + Vcpu: ppb.Config.VCpuCount, + RamMB: ppb.Config.MemoryMB, + HugePages: ppb.Config.HugePages, + FreePageReporting: ppb.Config.FreePageReporting, Envd: sandbox.EnvdMetadata{ Version: ppb.EnvdVersion, diff --git a/packages/orchestrator/pkg/template/build/phases/steps/builder.go b/packages/orchestrator/pkg/template/build/phases/steps/builder.go index 0ff423e0b8..507312c5ac 100644 --- a/packages/orchestrator/pkg/template/build/phases/steps/builder.go +++ b/packages/orchestrator/pkg/template/build/phases/steps/builder.go @@ -163,9 +163,10 @@ func (sb *StepBuilder) Build( step := sb.step sbxConfig := sandbox.NewConfig(sandbox.Config{ - Vcpu: sb.Config.VCpuCount, - RamMB: sb.Config.MemoryMB, - HugePages: sb.Config.HugePages, + Vcpu: sb.Config.VCpuCount, + RamMB: sb.Config.MemoryMB, + HugePages: sb.Config.HugePages, + FreePageReporting: sb.Config.FreePageReporting, Envd: sandbox.EnvdMetadata{ Version: sb.EnvdVersion, diff --git a/packages/orchestrator/pkg/template/server/create_template.go b/packages/orchestrator/pkg/template/server/create_template.go index 7b61ffe868..d40118e81e 100644 --- a/packages/orchestrator/pkg/template/server/create_template.go +++ b/packages/orchestrator/pkg/template/server/create_template.go @@ -70,6 +70,7 @@ func (s *ServerStore) TemplateCreate(ctx context.Context, templateRequest *templ ReadyCmd: cfg.GetReadyCommand(), DiskSizeMB: int64(cfg.GetDiskSizeMB()), HugePages: cfg.GetHugePages(), + FreePageReporting: cfg.GetFreePageReporting(), FromImage: cfg.GetFromImage(), FromTemplate: cfg.GetFromTemplate(), RegistryAuthProvider: authProvider, diff --git a/packages/orchestrator/template-manager.proto b/packages/orchestrator/template-manager.proto index 4ef795fb59..95dd17131e 100644 --- a/packages/orchestrator/template-manager.proto +++ b/packages/orchestrator/template-manager.proto @@ -84,6 +84,7 @@ message TemplateConfig { optional FromImageRegistry fromImageRegistry = 15; string teamID = 16; + optional bool freePageReporting = 17; } message TemplateCreateRequest { diff --git a/packages/shared/pkg/grpc/template-manager/template-manager.pb.go b/packages/shared/pkg/grpc/template-manager/template-manager.pb.go index f6196158db..3c3fe06023 100644 --- a/packages/shared/pkg/grpc/template-manager/template-manager.pb.go +++ b/packages/shared/pkg/grpc/template-manager/template-manager.pb.go @@ -853,6 +853,13 @@ func (x *TemplateConfig) GetTeamID() string { return "" } +func (x *TemplateConfig) GetFreePageReporting() bool { + if x != nil && x.FreePageReporting != nil { + return *x.FreePageReporting + } + return false +} + type isTemplateConfig_Source interface { isTemplateConfig_Source() } From e02c53a25f8394a701c5612997c4af5b49eb7b58 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Tue, 10 Mar 2026 13:50:54 -0700 Subject: [PATCH 14/63] Add feature flag for free page reporting Signed-off-by: Babis Chalios --- packages/api/internal/template-manager/create_template.go | 3 ++- packages/shared/pkg/featureflags/flags.go | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/api/internal/template-manager/create_template.go b/packages/api/internal/template-manager/create_template.go index 373313982a..2a060f2095 100644 --- a/packages/api/internal/template-manager/create_template.go +++ b/packages/api/internal/template-manager/create_template.go @@ -16,6 +16,7 @@ import ( "github.com/e2b-dev/infra/packages/api/internal/utils" "github.com/e2b-dev/infra/packages/db/pkg/types" "github.com/e2b-dev/infra/packages/db/queries" + "github.com/e2b-dev/infra/packages/shared/pkg/featureflags" templatemanagergrpc "github.com/e2b-dev/infra/packages/shared/pkg/grpc/template-manager" "github.com/e2b-dev/infra/packages/shared/pkg/id" "github.com/e2b-dev/infra/packages/shared/pkg/logger" @@ -109,7 +110,7 @@ func (tm *TemplateManager) CreateTemplate( return fmt.Errorf("failed to convert image registry: %w", err) } - freePageReporting := features.HasFreePageReporting() + freePageReporting := features.HasFreePageReporting() && tm.featureFlags.BoolFlag(ctx, featureflags.FreePageReportingFlag, featureflags.TeamContext(teamID.String())) template := &templatemanagergrpc.TemplateConfig{ TeamID: teamID.String(), diff --git a/packages/shared/pkg/featureflags/flags.go b/packages/shared/pkg/featureflags/flags.go index 79f9327760..a557186e5d 100644 --- a/packages/shared/pkg/featureflags/flags.go +++ b/packages/shared/pkg/featureflags/flags.go @@ -119,6 +119,7 @@ var ( ExecutionMetricsOnWebhooksFlag = newBoolFlag("execution-metrics-on-webhooks", false) // TODO: Remove NLT 20250315 SandboxLabelBasedSchedulingFlag = newBoolFlag("sandbox-label-based-scheduling", false) OptimisticResourceAccountingFlag = newBoolFlag("sandbox-placement-optimistic-resource-accounting", false) + FreePageReportingFlag = newBoolFlag("free-page-reporting", false) ) type IntFlag struct { From 7dcd66a6bc560a6babd7e798096e31fa1be4548c Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Tue, 10 Mar 2026 21:46:44 -0700 Subject: [PATCH 15/63] feat(uffd): add WP mode test for prefetch Test that if we always copy with WP on, write-protection will be handled correctly by the kernel. Signed-off-by: Babis Chalios --- .../sandbox/uffd/userfaultfd/async_wp_test.go | 55 +++++++++++++++++++ .../userfaultfd/cross_process_helpers_test.go | 7 +++ .../sandbox/uffd/userfaultfd/helpers_test.go | 2 + .../pkg/sandbox/uffd/userfaultfd/prefault.go | 2 +- .../sandbox/uffd/userfaultfd/userfaultfd.go | 11 ++-- 5 files changed, 72 insertions(+), 5 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/async_wp_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/async_wp_test.go index 22ede7e2c1..aa3f6177bc 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/async_wp_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/async_wp_test.go @@ -41,6 +41,7 @@ func TestAsyncWriteProtection(t *testing.T) { operations []operation expectedDirty []int expectedClean []int + alwaysWP bool }{ { name: "4k read then write same page", @@ -229,6 +230,59 @@ func TestAsyncWriteProtection(t *testing.T) { expectedDirty: []int{0, 2}, expectedClean: []int{1, 3}, }, + + // alwaysWP tests: handler copies with UFFDIO_COPY_MODE_WP for all faults, + // including writes. WP_ASYNC must automatically clear the WP bit when the + // original access was a write. This validates the assumption that independent + // prefaulting (always copy with WP) works correctly. + { + name: "4k alwaysWP write to missing page", + pagesize: header.PageSize, + numberOfPages: 4, + alwaysWP: true, + operations: []operation{ + {offset: 0, mode: operationModeWrite}, + }, + expectedDirty: []int{0}, + }, + { + name: "4k alwaysWP mixed writes and reads", + pagesize: header.PageSize, + numberOfPages: 4, + alwaysWP: true, + operations: []operation{ + {offset: 0 * header.PageSize, mode: operationModeWrite}, + {offset: 1 * header.PageSize, mode: operationModeRead}, + {offset: 2 * header.PageSize, mode: operationModeWrite}, + {offset: 3 * header.PageSize, mode: operationModeRead}, + }, + expectedDirty: []int{0, 2}, + expectedClean: []int{1, 3}, + }, + { + name: "hugepage alwaysWP write to missing page", + pagesize: header.HugepageSize, + numberOfPages: 4, + alwaysWP: true, + operations: []operation{ + {offset: 0, mode: operationModeWrite}, + }, + expectedDirty: []int{0}, + }, + { + name: "hugepage alwaysWP mixed writes and reads", + pagesize: header.HugepageSize, + numberOfPages: 4, + alwaysWP: true, + operations: []operation{ + {offset: 0 * header.HugepageSize, mode: operationModeWrite}, + {offset: 1 * header.HugepageSize, mode: operationModeRead}, + {offset: 2 * header.HugepageSize, mode: operationModeWrite}, + {offset: 3 * header.HugepageSize, mode: operationModeRead}, + }, + expectedDirty: []int{0, 2}, + expectedClean: []int{1, 3}, + }, } for _, tt := range tests { @@ -238,6 +292,7 @@ func TestAsyncWriteProtection(t *testing.T) { h, err := configureCrossProcessTest(t, testConfig{ pagesize: tt.pagesize, numberOfPages: tt.numberOfPages, + alwaysWP: tt.alwaysWP, }) require.NoError(t, err) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go index 100d40fd0a..704f830686 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go @@ -107,6 +107,9 @@ func configureCrossProcessTest(t *testing.T, tt testConfig) (*testHandler, error cmd.Env = append(os.Environ(), "GO_TEST_HELPER_PROCESS=1") cmd.Env = append(cmd.Env, fmt.Sprintf("GO_MMAP_START=%d", memoryStart)) cmd.Env = append(cmd.Env, fmt.Sprintf("GO_MMAP_PAGE_SIZE=%d", tt.pagesize)) + if tt.alwaysWP { + cmd.Env = append(cmd.Env, "GO_ALWAYS_WP=1") + } dup, err := syscall.Dup(int(uffdFd)) require.NoError(t, err) @@ -294,6 +297,10 @@ func crossProcessServe() error { return fmt.Errorf("exit creating uffd: %w", err) } + if os.Getenv("GO_ALWAYS_WP") == "1" { + uffd.defaultCopyMode = UFFDIO_COPY_MODE_WP + } + offsetsFile := os.NewFile(uintptr(5), "offsets") offsetsSignal := make(chan os.Signal, 1) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/helpers_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/helpers_test.go index 3e84101ad3..5ee293ebe9 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/helpers_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/helpers_test.go @@ -19,6 +19,8 @@ type testConfig struct { numberOfPages uint64 // Operations to trigger on the memory area. operations []operation + // alwaysWP makes the handler copy with UFFDIO_COPY_MODE_WP for all faults. + alwaysWP bool } type operationMode uint32 diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/prefault.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/prefault.go index 1df9937206..1159f231d0 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/prefault.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/prefault.go @@ -34,7 +34,7 @@ func (u *Userfaultfd) Prefault(ctx context.Context, offset int64, data []byte) e } // We're treating prefault handling as if it was caused by a read access. - // This way, we will fault the page with UFFD_COPY_MODE_WP which will preserve + // This way, we will fault the page with UFFD_COPY_MODE_WP which will set // the WP bit for the page. This works even in the case of a race with a // concurrent on-demand write access. // diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index 7f4fc88877..880d500be8 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -58,6 +58,12 @@ type Userfaultfd struct { settleRequests sync.RWMutex prefetchTracker *block.PrefetchTracker + // defaultCopyMode overrides the UFFDIO_COPY mode for all faults. + // Zero means use the default behavior (WP for reads, no WP for writes). + // Set to UFFDIO_COPY_MODE_WP to always write-protect — used in tests to + // verify WP_ASYNC handles write-first faults correctly for prefaulting. + defaultCopyMode CULong + wg errgroup.Group logger logger.Logger @@ -361,11 +367,8 @@ func (u *Userfaultfd) faultPage( }() var writeErr error - var mode CULong - // Performing copy() on UFFD clears the WP bit unless we explicitly tell - // it not to. We do that for faults caused by a read access. Write accesses - // would anyways clear the write-protection bit. + mode := u.defaultCopyMode if accessType == block.Read { mode = UFFDIO_COPY_MODE_WP } From e2e49e815dc8bf1b20d61242ca3c30ecc7e1f80c Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Wed, 11 Mar 2026 02:17:55 -0700 Subject: [PATCH 16/63] feat(uffd): add tests for UFFD remove events Add machinery for being able to coordinate the steps of the helper process that operates the UFFD serve loop. Then add tests for various scenarios of UFFD remove and page fault events. Signed-off-by: Babis Chalios --- .../sandbox/uffd/userfaultfd/async_wp_test.go | 11 +- .../userfaultfd/cross_process_helpers_test.go | 222 ++++++++++---- .../uffd/userfaultfd/fd_helpers_test.go | 12 + .../sandbox/uffd/userfaultfd/helpers_test.go | 164 +++++++++- .../sandbox/uffd/userfaultfd/missing_test.go | 25 +- .../uffd/userfaultfd/missing_write_test.go | 30 +- .../sandbox/uffd/userfaultfd/remove_test.go | 284 ++++++++++++++++++ .../sandbox/uffd/userfaultfd/userfaultfd.go | 6 +- 8 files changed, 635 insertions(+), 119 deletions(-) create mode 100644 packages/orchestrator/pkg/sandbox/uffd/userfaultfd/remove_test.go diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/async_wp_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/async_wp_test.go index aa3f6177bc..ab8941abea 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/async_wp_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/async_wp_test.go @@ -296,16 +296,7 @@ func TestAsyncWriteProtection(t *testing.T) { }) require.NoError(t, err) - for i, op := range tt.operations { - switch op.mode { - case operationModeRead: - err := h.executeRead(t.Context(), op) - require.NoError(t, err, "step %d: read at offset %d", i, op.offset) - case operationModeWrite: - err := h.executeWrite(t.Context(), op) - require.NoError(t, err, "step %d: write at offset %d", i, op.offset) - } - } + h.executeAll(t, tt.operations) pagemap, err := testutils.NewPagemapReader() require.NoError(t, err) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go index 704f830686..4565496172 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go @@ -93,10 +93,6 @@ func configureCrossProcessTest(t *testing.T, tt testConfig) (*testHandler, error uffdFd, err := newFd(syscall.O_CLOEXEC | syscall.O_NONBLOCK) require.NoError(t, err) - t.Cleanup(func() { - uffdFd.close() - }) - err = configureApi(uffdFd, tt.pagesize) require.NoError(t, err) @@ -110,6 +106,9 @@ func configureCrossProcessTest(t *testing.T, tt testConfig) (*testHandler, error if tt.alwaysWP { cmd.Env = append(cmd.Env, "GO_ALWAYS_WP=1") } + if tt.gated { + cmd.Env = append(cmd.Env, "GO_GATED=1") + } dup, err := syscall.Dup(int(uffdFd)) require.NoError(t, err) @@ -153,12 +152,34 @@ func configureCrossProcessTest(t *testing.T, tt testConfig) (*testHandler, error readySignal <- struct{}{} }() - cmd.ExtraFiles = []*os.File{ + extraFiles := []*os.File{ uffdFile, contentReader, offsetsWriter, readyWriter, } + + var gateCmdWriter *os.File + var gateSyncReader *os.File + if tt.gated { + var gateCmdReader *os.File + gateCmdReader, gateCmdWriter, err = os.Pipe() + require.NoError(t, err) + + var gateSyncWriter *os.File + gateSyncReader, gateSyncWriter, err = os.Pipe() + require.NoError(t, err) + + t.Cleanup(func() { + gateCmdWriter.Close() + gateSyncReader.Close() + }) + + extraFiles = append(extraFiles, gateCmdReader) // fd 7 + extraFiles = append(extraFiles, gateSyncWriter) // fd 8 + } + + cmd.ExtraFiles = extraFiles cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -169,6 +190,10 @@ func configureCrossProcessTest(t *testing.T, tt testConfig) (*testHandler, error offsetsWriter.Close() readyWriter.Close() uffdFile.Close() + if tt.gated { + extraFiles[4].Close() // gateCmdReader + extraFiles[5].Close() // gateSyncWriter + } t.Cleanup(func() { signalErr := cmd.Process.Signal(syscall.SIGUSR1) @@ -187,30 +212,46 @@ func configureCrossProcessTest(t *testing.T, tt testConfig) (*testHandler, error waitErr == nil, "unexpected error: %v", waitErr, ) + + // Unregister the uffd range so munmap won't block on REMOVE events. + unregister(uffdFd, memoryStart, uint64(size)) + uffdFd.close() }) - offsetsOnce := func() ([]uint, error) { + pageStatesOnce := func() (handlerPageStates, error) { err := cmd.Process.Signal(syscall.SIGUSR2) if err != nil { - return nil, err + return handlerPageStates{}, err } - offsetsBytes, err := io.ReadAll(offsetsReader) + data, err := io.ReadAll(offsetsReader) if err != nil { - return nil, err + return handlerPageStates{}, err } - var offsetList []uint - - if len(offsetsBytes)%8 != 0 { - return nil, fmt.Errorf("invalid offsets bytes length: %d", len(offsetsBytes)) + const entrySize = 1 + 8 // uint8 state + uint64 offset + if len(data)%entrySize != 0 { + return handlerPageStates{}, fmt.Errorf("invalid page state data length: %d", len(data)) } - for i := 0; i < len(offsetsBytes); i += 8 { - offsetList = append(offsetList, uint(binary.LittleEndian.Uint64(offsetsBytes[i:i+8]))) + var result handlerPageStates + + for i := 0; i < len(data); i += entrySize { + state := pageState(data[i]) + offset := uint(binary.LittleEndian.Uint64(data[i+1 : i+entrySize])) + + switch state { + case faulted: + result.faulted = append(result.faulted, offset) + case removed: + result.removed = append(result.removed, offset) + } } - return offsetList, nil + slices.Sort(result.faulted) + slices.Sort(result.removed) + + return result, nil } select { @@ -219,12 +260,31 @@ func configureCrossProcessTest(t *testing.T, tt testConfig) (*testHandler, error case <-readySignal: } - return &testHandler{ - memoryArea: &memoryArea, - pagesize: tt.pagesize, - data: data, - offsetsOnce: offsetsOnce, - }, nil + h := &testHandler{ + memoryArea: &memoryArea, + pagesize: tt.pagesize, + data: data, + pageStatesOnce: pageStatesOnce, + } + + if tt.gated { + h.servePause = func() error { + if _, err := gateCmdWriter.Write([]byte{'P'}); err != nil { + return err + } + var buf [1]byte + _, err := gateSyncReader.Read(buf[:]) + + return err + } + h.serveResume = func() error { + _, err := gateCmdWriter.Write([]byte{'R'}) + + return err + } + } + + return h, nil } // Secondary process, orchestrator in our case @@ -284,9 +344,6 @@ func crossProcessServe() error { }, }) - exitUffd := make(chan struct{}, 1) - defer close(exitUffd) - l, err := logger.NewDevelopmentLogger() if err != nil { return fmt.Errorf("exit creating logger: %w", err) @@ -315,9 +372,9 @@ func crossProcessServe() error { case <-ctx.Done(): return case <-offsetsSignal: - faultedOffsets, faultedErr := uffd.faulted() - if faultedErr != nil { - msg := fmt.Errorf("error getting faulted offsets: %w", faultedErr) + entries, entriesErr := uffd.pageStateEntries() + if entriesErr != nil { + msg := fmt.Errorf("error getting page state entries: %w", entriesErr) fmt.Fprint(os.Stderr, msg.Error()) @@ -326,10 +383,10 @@ func crossProcessServe() error { return } - for _, offset := range faultedOffsets { - writeErr := binary.Write(offsetsFile, binary.LittleEndian, offset) + for _, entry := range entries { + writeErr := binary.Write(offsetsFile, binary.LittleEndian, entry) if writeErr != nil { - msg := fmt.Errorf("error writing offsets to file: %w", writeErr) + msg := fmt.Errorf("error writing page state entry: %w", writeErr) fmt.Fprint(os.Stderr, msg.Error()) @@ -350,39 +407,78 @@ func crossProcessServe() error { } defer fdExit.Close() + exitUffd := make(chan struct{}, 1) + go func() { - defer func() { - exitUffd <- struct{}{} - }() + defer func() { exitUffd <- struct{}{} }() serverErr := uffd.Serve(ctx, fdExit) if serverErr != nil { msg := fmt.Errorf("error serving: %w", serverErr) - fmt.Fprint(os.Stderr, msg.Error()) - cancel(msg) - - return } }() cleanup := func() { - err := fdExit.SignalExit() - if err != nil { - msg := fmt.Errorf("error signaling exit: %w", err) + fdExit.SignalExit() + <-exitUffd + } + defer cleanup() - fmt.Fprint(os.Stderr, msg.Error()) + if os.Getenv("GO_GATED") == "1" { + gateCmdFile := os.NewFile(uintptr(7), "gate-cmd") + defer gateCmdFile.Close() - cancel(msg) + gateSyncFile := os.NewFile(uintptr(8), "gate-sync") + defer gateSyncFile.Close() + + startServe := func() func() { + newExit, fdErr := fdexit.New() + if fdErr != nil { + cancel(fmt.Errorf("error creating fd exit: %w", fdErr)) + + return func() {} + } - return + done := make(chan struct{}) + go func() { + defer close(done) + if err := uffd.Serve(ctx, newExit); err != nil { + cancel(fmt.Errorf("error serving: %w", err)) + } + }() + + return func() { + newExit.SignalExit() + <-done + newExit.Close() + } } - <-exitUffd - } + stopServe := func() { + cleanup() + } - defer cleanup() + go func() { + var buf [1]byte + for { + if _, err := gateCmdFile.Read(buf[:]); err != nil { + return + } + + switch buf[0] { + case 'P': + stopServe() + gateSyncFile.Write([]byte{1}) + case 'R': + newStop := startServe() + stopServe = newStop + cleanup = newStop + } + } + }() + } exitSignal := make(chan os.Signal, 1) signal.Notify(exitSignal, syscall.SIGUSR1) @@ -403,30 +499,26 @@ func crossProcessServe() error { } } -func (u *Userfaultfd) faulted() ([]uint64, error) { - // This will be at worst cancelled when the uffd is closed. - u.settleRequests.Lock() - u.settleRequests.Unlock() //nolint:staticcheck // SA2001: intentional — we just need to settle the read locks. - - return u.pageTracker.faultedOffsets(u.ma) +type pageStateEntry struct { + State uint8 + Offset uint64 } -func (pt *pageTracker) faultedOffsets(ma *memory.Mapping) ([]uint64, error) { - pt.mu.RLock() - defer pt.mu.RUnlock() +func (u *Userfaultfd) pageStateEntries() ([]pageStateEntry, error) { + u.settleRequests.Lock() + u.settleRequests.Unlock() //nolint:staticcheck // SA2001: intentional — settle the read locks. + + u.pageTracker.mu.RLock() + defer u.pageTracker.mu.RUnlock() - offsets := make([]uint64, 0, len(pt.m)) - for addr := range pt.m { - offset, err := ma.GetOffset(addr) + var entries []pageStateEntry + for addr, state := range u.pageTracker.m { + offset, err := u.ma.GetOffset(addr) if err != nil { return nil, fmt.Errorf("address %#x not in mapping: %w", addr, err) } - offsets = append(offsets, uint64(offset)) - } - - if len(offsets) > 1 { - slices.Sort(offsets) + entries = append(entries, pageStateEntry{uint8(state), uint64(offset)}) } - return offsets, nil + return entries, nil } diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/fd_helpers_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/fd_helpers_test.go index fbd98f1ce2..7e4be81b39 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/fd_helpers_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/fd_helpers_test.go @@ -31,6 +31,7 @@ func configureApi(f Fd, pagesize uint64) error { } features |= UFFD_FEATURE_WP_ASYNC + features |= UFFD_FEATURE_EVENT_REMOVE api := newUffdioAPI(UFFD_API, features) ret, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(f), UFFDIO_API, uintptr(unsafe.Pointer(&api))) @@ -41,6 +42,17 @@ func configureApi(f Fd, pagesize uint64) error { return nil } +func unregister(f Fd, addr uintptr, size uint64) error { + r := newUffdioRange(CULong(addr), CULong(size)) + + ret, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(f), UFFDIO_UNREGISTER, uintptr(unsafe.Pointer(&r))) + if errno != 0 { + return fmt.Errorf("UFFDIO_UNREGISTER ioctl failed: %w (ret=%d)", errno, ret) + } + + return nil +} + // mode: UFFDIO_REGISTER_MODE_WP|UFFDIO_REGISTER_MODE_MISSING // This is already called by the FC, but only with the UFFDIO_REGISTER_MODE_MISSING // We need to call it with UFFDIO_REGISTER_MODE_WP when we use both missing and wp diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/helpers_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/helpers_test.go index 5ee293ebe9..fde8886b5f 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/helpers_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/helpers_test.go @@ -4,9 +4,17 @@ import ( "bytes" "context" "fmt" + "slices" "sync" + "testing" + "time" + "unsafe" "github.com/RoaringBitmap/roaring/v2" + "github.com/bits-and-blooms/bitset" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/sys/unix" "github.com/e2b-dev/infra/packages/orchestrator/pkg/sandbox/uffd/testutils" ) @@ -21,6 +29,8 @@ type testConfig struct { operations []operation // alwaysWP makes the handler copy with UFFDIO_COPY_MODE_WP for all faults. alwaysWP bool + // gated enables pause/resume control over the handler's serve loop. + gated bool } type operationMode uint32 @@ -28,22 +38,162 @@ type operationMode uint32 const ( operationModeRead operationMode = 1 << iota operationModeWrite + operationModeRemove + operationModeServePause + operationModeServeResume + // operationModeSleep pauses for a short duration to let async goroutines + // enter their blocking syscalls before proceeding. + operationModeSleep ) type operation struct { // Offset in bytes. Must be smaller than the (numberOfPages-1) * pagesize as it reads a page and it must be aligned to the pagesize from the testConfig. offset int64 mode operationMode + // async runs the operation in a background goroutine. + async bool +} + +type handlerPageStates struct { + faulted []uint + removed []uint +} + +func (s handlerPageStates) allAccessed() []uint { + b := bitset.New(0) + for _, o := range s.faulted { + b.Set(o) + } + for _, o := range s.removed { + b.Set(o) + } + + return slices.Collect(b.EachSet()) } type testHandler struct { - memoryArea *[]byte - pagesize uint64 - data *MemorySlicer - // Returns offsets of the pages that were faulted. - // It can only be called once. - offsetsOnce func() ([]uint, error) - mutex sync.Mutex + memoryArea *[]byte + pagesize uint64 + data *MemorySlicer + pageStatesOnce func() (handlerPageStates, error) + servePause func() error + serveResume func() error + mutex sync.Mutex +} + +func (h *testHandler) executeAll(t *testing.T, operations []operation) { + t.Helper() + + var asyncErrors []chan error + + for i, op := range operations { + if op.async { + errCh := make(chan error, 1) + asyncErrors = append(asyncErrors, errCh) + + go func() { + errCh <- h.executeOperation(t.Context(), op) + }() + + continue + } + + err := h.executeOperation(t.Context(), op) + require.NoError(t, err, "step %d: %v at offset %d", i, op.mode, op.offset) + } + + for _, errCh := range asyncErrors { + select { + case err := <-errCh: + require.NoError(t, err, "async operation") + case <-t.Context().Done(): + t.Fatal("timed out waiting for async operation") + } + } +} + +type pageExpectation uint8 + +const ( + expectClean pageExpectation = iota // read-only: present + WP set + expectDirty // written: present + WP cleared + expectRemoved // removed: not present +) + +func (h *testHandler) checkDirtiness(t *testing.T, operations []operation) { + t.Helper() + + pagemap, err := testutils.NewPagemapReader() + require.NoError(t, err) + defer pagemap.Close() + + memStart := uintptr(unsafe.Pointer(&(*h.memoryArea)[0])) + + // Track the final expected state per offset by replaying operations in order. + // A remove after a read/write makes the page not present. + // A read/write after a remove makes it present again. + expected := make(map[uint]pageExpectation) + + for _, op := range operations { + off := uint(op.offset) + switch op.mode { + case operationModeRead: + curr, seen := expected[off] + // If we haven't seen this page before or the page + // has previously been removed then the page should be clean + // after this read operation. + if !seen || curr == expectRemoved { + expected[off] = expectClean + } + case operationModeWrite: + expected[off] = expectDirty + case operationModeRemove: + expected[off] = expectRemoved + } + } + + for off, expect := range expected { + entry, err := pagemap.ReadEntry(memStart + uintptr(off)) + require.NoError(t, err, "pagemap read at offset %d", off) + + switch expect { + case expectRemoved: + assert.False(t, entry.IsPresent(), "removed page at offset %d should not be present", off) + case expectDirty: + assert.True(t, entry.IsPresent(), "written page at offset %d should be present", off) + assert.False(t, entry.IsWriteProtected(), "written page at offset %d should be dirty", off) + case expectClean: + assert.True(t, entry.IsPresent(), "read-only page at offset %d should be present", off) + assert.True(t, entry.IsWriteProtected(), "read-only page at offset %d should be clean", off) + } + } +} + +func (h *testHandler) executeOperation(ctx context.Context, op operation) error { + switch op.mode { + case operationModeRead: + return h.executeRead(ctx, op) + case operationModeWrite: + return h.executeWrite(ctx, op) + case operationModeRemove: + return h.executeRemove(op) + case operationModeServePause: + return h.servePause() + case operationModeServeResume: + return h.serveResume() + case operationModeSleep: + time.Sleep(50 * time.Millisecond) + + return nil + default: + return fmt.Errorf("invalid operation mode: %d", op.mode) + } +} + +func (h *testHandler) executeRemove(op operation) error { + page := (*h.memoryArea)[op.offset : op.offset+int64(h.pagesize)] + + return unix.Madvise(page, unix.MADV_DONTNEED) } func (h *testHandler) executeRead(ctx context.Context, op operation) error { diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_test.go index f080191526..f39a4fb892 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_test.go @@ -124,19 +124,16 @@ func TestMissing(t *testing.T) { h, err := configureCrossProcessTest(t, tt) require.NoError(t, err) - for _, operation := range tt.operations { - if operation.mode == operationModeRead { - err := h.executeRead(t.Context(), operation) - require.NoError(t, err, "for operation %+v", operation) - } - } + h.executeAll(t, tt.operations) expectedAccessedOffsets := getOperationsOffsets(tt.operations, operationModeRead|operationModeWrite) - accessedOffsets, err := h.offsetsOnce() + states, err := h.pageStatesOnce() require.NoError(t, err) - assert.Equal(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") + assert.Equal(t, expectedAccessedOffsets, states.allAccessed(), "checking which pages were faulted") + + h.checkDirtiness(t, tt.operations) }) } } @@ -172,10 +169,10 @@ func TestParallelMissing(t *testing.T) { expectedAccessedOffsets := getOperationsOffsets([]operation{readOp}, operationModeRead) - accessedOffsets, err := h.offsetsOnce() + states, err := h.pageStatesOnce() require.NoError(t, err) - assert.Equal(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") + assert.Equal(t, expectedAccessedOffsets, states.allAccessed(), "checking which pages were faulted") } func TestParallelMissingWithPrefault(t *testing.T) { @@ -212,10 +209,10 @@ func TestParallelMissingWithPrefault(t *testing.T) { expectedAccessedOffsets := getOperationsOffsets([]operation{readOp}, operationModeRead) - accessedOffsets, err := h.offsetsOnce() + states, err := h.pageStatesOnce() require.NoError(t, err) - assert.Equal(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") + assert.Equal(t, expectedAccessedOffsets, states.allAccessed(), "checking which pages were faulted") } func TestSerialMissing(t *testing.T) { @@ -243,8 +240,8 @@ func TestSerialMissing(t *testing.T) { expectedAccessedOffsets := getOperationsOffsets([]operation{readOp}, operationModeRead) - accessedOffsets, err := h.offsetsOnce() + states, err := h.pageStatesOnce() require.NoError(t, err) - assert.Equal(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") + assert.Equal(t, expectedAccessedOffsets, states.allAccessed(), "checking which pages were faulted") } diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_write_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_write_test.go index 52a73e5fa2..5f8d35a967 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_write_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_write_test.go @@ -119,24 +119,16 @@ func TestMissingWrite(t *testing.T) { h, err := configureCrossProcessTest(t, tt) require.NoError(t, err) - for _, operation := range tt.operations { - if operation.mode == operationModeRead { - err := h.executeRead(t.Context(), operation) - require.NoError(t, err, "for operation %+v", operation) - } - - if operation.mode == operationModeWrite { - err := h.executeWrite(t.Context(), operation) - require.NoError(t, err, "for operation %+v", operation) - } - } + h.executeAll(t, tt.operations) expectedAccessedOffsets := getOperationsOffsets(tt.operations, operationModeRead|operationModeWrite) - accessedOffsets, err := h.offsetsOnce() + states, err := h.pageStatesOnce() require.NoError(t, err) - assert.Equal(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") + assert.Equal(t, expectedAccessedOffsets, states.allAccessed(), "checking which pages were faulted") + + h.checkDirtiness(t, tt.operations) }) } } @@ -172,10 +164,10 @@ func TestParallelMissingWrite(t *testing.T) { expectedAccessedOffsets := getOperationsOffsets([]operation{writeOp}, operationModeRead|operationModeWrite) - accessedOffsets, err := h.offsetsOnce() + states, err := h.pageStatesOnce() require.NoError(t, err) - assert.Equal(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") + assert.Equal(t, expectedAccessedOffsets, states.allAccessed(), "checking which pages were faulted") } func TestParallelMissingWriteWithPrefault(t *testing.T) { @@ -212,10 +204,10 @@ func TestParallelMissingWriteWithPrefault(t *testing.T) { expectedAccessedOffsets := getOperationsOffsets([]operation{writeOp}, operationModeRead|operationModeWrite) - accessedOffsets, err := h.offsetsOnce() + states, err := h.pageStatesOnce() require.NoError(t, err) - assert.Equal(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") + assert.Equal(t, expectedAccessedOffsets, states.allAccessed(), "checking which pages were faulted") } func TestSerialMissingWrite(t *testing.T) { @@ -243,8 +235,8 @@ func TestSerialMissingWrite(t *testing.T) { expectedAccessedOffsets := getOperationsOffsets([]operation{writeOp}, operationModeRead|operationModeWrite) - accessedOffsets, err := h.offsetsOnce() + states, err := h.pageStatesOnce() require.NoError(t, err) - assert.Equal(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") + assert.Equal(t, expectedAccessedOffsets, states.allAccessed(), "checking which pages were faulted") } diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/remove_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/remove_test.go new file mode 100644 index 0000000000..07201ab78c --- /dev/null +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/remove_test.go @@ -0,0 +1,284 @@ +package userfaultfd + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/e2b-dev/infra/packages/shared/pkg/storage/header" +) + +func TestRemove(t *testing.T) { + t.Parallel() + + tests := []testConfig{ + { + name: "4k read then remove", + pagesize: header.PageSize, + numberOfPages: 2, + operations: []operation{ + {offset: 0, mode: operationModeRead}, + {offset: 0, mode: operationModeRemove}, + }, + }, + { + name: "hugepage read then remove", + pagesize: header.HugepageSize, + numberOfPages: 2, + operations: []operation{ + {offset: 0, mode: operationModeRead}, + {offset: 0, mode: operationModeRemove}, + }, + }, + { + name: "4k write then remove", + pagesize: header.PageSize, + numberOfPages: 2, + operations: []operation{ + {offset: 0, mode: operationModeWrite}, + {offset: 0, mode: operationModeRemove}, + }, + }, + { + name: "hugepage write then remove", + pagesize: header.HugepageSize, + numberOfPages: 2, + operations: []operation{ + {offset: 0, mode: operationModeWrite}, + {offset: 0, mode: operationModeRemove}, + }, + }, + { + name: "4k selective remove", + pagesize: header.PageSize, + numberOfPages: 2, + operations: []operation{ + {offset: 0, mode: operationModeRead}, + {offset: int64(header.PageSize), mode: operationModeWrite}, + {offset: 0, mode: operationModeRemove}, + }, + }, + { + name: "hugepage selective remove", + pagesize: header.HugepageSize, + numberOfPages: 2, + operations: []operation{ + {offset: 0, mode: operationModeRead}, + {offset: int64(header.HugepageSize), mode: operationModeWrite}, + {offset: 0, mode: operationModeRemove}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + h, err := configureCrossProcessTest(t, tt) + require.NoError(t, err) + + h.executeAll(t, tt.operations) + + states, err := h.pageStatesOnce() + require.NoError(t, err) + + removedOffsets := getOperationsOffsets(tt.operations, operationModeRemove) + assert.ElementsMatch(t, removedOffsets, states.removed) + + faultedOffsets := getOperationsOffsets(tt.operations, operationModeRead|operationModeWrite) + for _, r := range removedOffsets { + faultedOffsets = removeOffset(faultedOffsets, r) + } + assert.ElementsMatch(t, faultedOffsets, states.faulted) + + h.checkDirtiness(t, tt.operations) + }) + } +} + +// TestRemoveThenFault asserts that after MADV_DONTNEED + a subsequent write, +// the handler re-faults the page (state transitions: faulted → removed → faulted). +func TestRemoveThenFault(t *testing.T) { + t.Parallel() + + tests := []testConfig{ + { + name: "4k read, remove, write", + pagesize: header.PageSize, + numberOfPages: 2, + operations: []operation{ + {offset: 0, mode: operationModeRead}, + {offset: 0, mode: operationModeRemove}, + {offset: 0, mode: operationModeWrite}, + }, + }, + { + name: "hugepage read, remove, write", + pagesize: header.HugepageSize, + numberOfPages: 2, + operations: []operation{ + {offset: 0, mode: operationModeRead}, + {offset: 0, mode: operationModeRemove}, + {offset: 0, mode: operationModeWrite}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + h, err := configureCrossProcessTest(t, tt) + require.NoError(t, err) + + h.executeAll(t, tt.operations) + + states, err := h.pageStatesOnce() + require.NoError(t, err) + + assert.Empty(t, states.removed, "page should not be in removed state after re-fault") + assert.Contains(t, states.faulted, uint(0), "page should be back in faulted state") + + h.checkDirtiness(t, tt.operations) + }) + } +} + +// TestRemoveThenWriteGated verifies that when the handler is stopped, the +// kernel keeps the page mapped until REMOVE is acked. A concurrent write +// succeeds without faulting because MADV_DONTNEED blocks (waiting for ack) +// and doesn't unmap the page until the handler processes the event. +// When the handler resumes, it only sees the REMOVE — no MISSING fault. +func TestRemoveThenWriteGated(t *testing.T) { + t.Parallel() + + tests := []testConfig{ + { + name: "4k gated remove with concurrent write", + pagesize: header.PageSize, + numberOfPages: 2, + gated: true, + operations: []operation{ + {offset: 0, mode: operationModeRead}, + {mode: operationModeServePause}, + {offset: 0, mode: operationModeRemove, async: true}, + {mode: operationModeSleep}, + {offset: 0, mode: operationModeWrite, async: true}, + {mode: operationModeSleep}, + {mode: operationModeServeResume}, + }, + }, + { + name: "hugepage gated remove with concurrent write", + pagesize: header.HugepageSize, + numberOfPages: 2, + gated: true, + operations: []operation{ + {offset: 0, mode: operationModeRead}, + {mode: operationModeServePause}, + {offset: 0, mode: operationModeRemove, async: true}, + {mode: operationModeSleep}, + {offset: 0, mode: operationModeWrite, async: true}, + {mode: operationModeSleep}, + {mode: operationModeServeResume}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + h, err := configureCrossProcessTest(t, tt) + require.NoError(t, err) + + h.executeAll(t, tt.operations) + + states, err := h.pageStatesOnce() + require.NoError(t, err) + + // The page stays mapped until REMOVE is acked, so the concurrent + // write succeeds without triggering a MISSING fault. The handler + // only processes the REMOVE event. + assert.ElementsMatch(t, []uint{0}, states.removed) + assert.Empty(t, states.faulted) + }) + } +} + +// TestWriteThenRemoveGated verifies the serve loop's ordering guarantee: +// REMOVE events are processed before pagefaults even when the MISSING pagefault +// was queued first. The write to an unfaulted page triggers MISSING (queued first), +// then MADV_DONTNEED triggers REMOVE (queued second). When the handler resumes, +// it processes REMOVE first, then MISSING — the write is not skipped. +func TestWriteThenRemoveGated(t *testing.T) { + t.Parallel() + + tests := []testConfig{ + { + name: "4k write then remove in same batch", + pagesize: header.PageSize, + numberOfPages: 2, + gated: true, + operations: []operation{ + {offset: 0, mode: operationModeRead}, + {mode: operationModeServePause}, + // MISSING for page 1 queued first + {offset: int64(header.PageSize), mode: operationModeWrite, async: true}, + {mode: operationModeSleep}, + // REMOVE for page 0 queued second + {offset: 0, mode: operationModeRemove, async: true}, + {mode: operationModeSleep}, + {mode: operationModeServeResume}, + }, + }, + { + name: "hugepage write then remove in same batch", + pagesize: header.HugepageSize, + numberOfPages: 2, + gated: true, + operations: []operation{ + {offset: 0, mode: operationModeRead}, + {mode: operationModeServePause}, + {offset: int64(header.HugepageSize), mode: operationModeWrite, async: true}, + {mode: operationModeSleep}, + {offset: 0, mode: operationModeRemove, async: true}, + {mode: operationModeSleep}, + {mode: operationModeServeResume}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + h, err := configureCrossProcessTest(t, tt) + require.NoError(t, err) + + h.executeAll(t, tt.operations) + + states, err := h.pageStatesOnce() + require.NoError(t, err) + + // Page 0 was removed + assert.Contains(t, states.removed, uint(0)) + // Page 1 was faulted by the write — not skipped + pageOffset := uint(tt.pagesize) + assert.Contains(t, states.faulted, pageOffset, + "write pagefault should not be skipped even when batched with REMOVE") + }) + } +} + +func removeOffset(offsets []uint, target uint) []uint { + result := make([]uint, 0, len(offsets)) + for _, o := range offsets { + if o != target { + result = append(result, o) + } + } + + return result +} diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index 880d500be8..5c37004832 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -58,10 +58,7 @@ type Userfaultfd struct { settleRequests sync.RWMutex prefetchTracker *block.PrefetchTracker - // defaultCopyMode overrides the UFFDIO_COPY mode for all faults. - // Zero means use the default behavior (WP for reads, no WP for writes). - // Set to UFFDIO_COPY_MODE_WP to always write-protect — used in tests to - // verify WP_ASYNC handles write-first faults correctly for prefaulting. + // defaultCopyMode overrides the UFFDIO_COPY mode for all faults when non-zero. defaultCopyMode CULong wg errgroup.Group @@ -301,6 +298,7 @@ func (u *Userfaultfd) Serve( // or because we handled another page fault on the same address in the current // iteration. It can only be removed via a a UFFD_EVENT_REMOVE, which will mark the // page as `unfaulted`. + // For this to work correctly, the used pages cannot be swappable. continue case removed: // Fault the page as empty. From ad999469a17ed6067accb1ff8730308fbeaa4803 Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Thu, 26 Mar 2026 11:59:05 +0100 Subject: [PATCH 17/63] fix(uffd-test): reduce parallelism in tests Having 1M parallel operations sometimes causes intermittent issues in tests. Use 10K instead as used in other tests. Signed-off-by: Babis Chalios --- .../orchestrator/pkg/sandbox/uffd/userfaultfd/missing_test.go | 2 +- .../pkg/sandbox/uffd/userfaultfd/missing_write_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_test.go index f39a4fb892..6e99c865f9 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_test.go @@ -141,7 +141,7 @@ func TestMissing(t *testing.T) { func TestParallelMissing(t *testing.T) { t.Parallel() - parallelOperations := 1_000_000 + parallelOperations := 10_000 tt := testConfig{ pagesize: header.PageSize, diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_write_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_write_test.go index 5f8d35a967..0a20b62f59 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_write_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_write_test.go @@ -136,7 +136,7 @@ func TestMissingWrite(t *testing.T) { func TestParallelMissingWrite(t *testing.T) { t.Parallel() - parallelOperations := 1_000_000 + parallelOperations := 10_000 tt := testConfig{ pagesize: header.PageSize, From 6ee2a2aa2a0d4cabe2ac1aa80738c25bf1084c95 Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Thu, 26 Mar 2026 18:07:35 +0100 Subject: [PATCH 18/63] fix(uffd): synchronize UFFD events across iterations We are handling page fault events within go routines without much synchronization across across iterations of the serve loop (apart from when we are about to shut down the loop). This works fine when we only have page fault events, but it might be problematic with remove events in the mix. When we handle a remove event, we need to make sure that no page fault handling Go routine is still runing for the same page, as this cuases a race condition for the book keeping of the page state. Handle this, by ensuring that all outstanding Go routines have completed before handling remove events. Do that only when we do have remove events in the queue. Signed-off-by: Babis Chalios --- .../pkg/sandbox/uffd/userfaultfd/userfaultfd.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index 5c37004832..18d0e59ac0 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -263,6 +263,19 @@ func (u *Userfaultfd) Serve( noDataCounter.Log(ctx) eagainCounter.Log(ctx) + // Wait for all in-flight fault goroutines before processing REMOVE events. + // A goroutine from the previous batch could still be executing setState(faulted) + // at line 326 after its UFFDIO_COPY returned. If we process a REMOVE for the same + // page before that goroutine finishes, the goroutine's setState(faulted) would + // overwrite the removed state we just set. + if len(removes) > 0 { + if waitErr := u.wg.Wait(); waitErr != nil { + u.logger.Error(ctx, "UFFD serve error waiting for goroutines before REMOVE", zap.Error(waitErr)) + + return fmt.Errorf("failed to handle uffd: %w", waitErr) + } + } + // First handle the UFFD_EVENT_REMOVE events for _, rm := range removes { u.pageTracker.setState(uintptr(rm.start), uintptr(rm.end), removed) From d6de753d054194c571d7549e2cdef68ab0880c13 Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Tue, 31 Mar 2026 16:37:32 +0200 Subject: [PATCH 19/63] fix(uffd): eliminate remove / prefaulting race condition Previous commit added logic to wait for all Go routines handling page faults from previous iterations before handling remove events. There is one more race condition, though, between handling remove events and prefaulting operations. The previous fix did not handle this one. Re-use the settleRequests RW Mutex to instead of waiting the errorgroup. All pagefault operations are taking the read lock, so page- and pre-faulting can happen concurrently. Handling remove events takes the write lock, so no pre/page-faulting can happen while we handle those. Signed-off-by: Babis Chalios --- .../sandbox/uffd/userfaultfd/userfaultfd.go | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index 18d0e59ac0..8c21b3597f 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -263,23 +263,17 @@ func (u *Userfaultfd) Serve( noDataCounter.Log(ctx) eagainCounter.Log(ctx) - // Wait for all in-flight fault goroutines before processing REMOVE events. - // A goroutine from the previous batch could still be executing setState(faulted) - // at line 326 after its UFFDIO_COPY returned. If we process a REMOVE for the same + // First handle the UFFD_EVENT_REMOVE events. Take the settleRequests write lock to ensure that no + // other page or pre-fault operation is running concurrently. + // A goroutine from the previous batch or a prefault operation could still be executing + // setState(faulted) at line 326 after its UFFDIO_COPY returned. If we process a REMOVE for the same // page before that goroutine finishes, the goroutine's setState(faulted) would // overwrite the removed state we just set. - if len(removes) > 0 { - if waitErr := u.wg.Wait(); waitErr != nil { - u.logger.Error(ctx, "UFFD serve error waiting for goroutines before REMOVE", zap.Error(waitErr)) - - return fmt.Errorf("failed to handle uffd: %w", waitErr) - } - } - - // First handle the UFFD_EVENT_REMOVE events + u.settleRequests.Lock() for _, rm := range removes { u.pageTracker.setState(uintptr(rm.start), uintptr(rm.end), removed) } + u.settleRequests.Unlock() // Collect deferred pagefaults from previous iteration's goroutines. pagefaults = append(deferred.drain(), pagefaults...) From 74d83000408d994f9638e6d4975fcff3ec386544 Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Thu, 2 Apr 2026 10:11:43 +0200 Subject: [PATCH 20/63] fix(uffd-test): fix defer capturing stale cleanup in gated serve MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In crossProcessServe(), `cleanup` is initially set to a function that signals the original fdExit and drains exitUffd (a buffered channel of capacity 1). When a gated test issues a pause ('P'), stopServe() calls the original cleanup, which drains exitUffd. On resume ('R'), startServe() creates a new fdExit and Serve goroutine, and reassigns both `stopServe` and `cleanup` to a new stop function. However, `defer cleanup()` captures the *value* of cleanup at the time the defer statement executes — the original closure — not the variable itself. So when SIGUSR1 arrives and crossProcessServe returns, the deferred call invokes the original cleanup, which blocks forever on <-exitUffd because that channel was already drained during the pause step. The subprocess never exits, cmd.Wait() in the parent test hangs, and the parallel test slot is never released, causing unrelated tests waiting at t.Parallel() to time out. Fix by deferring through the variable so the call always dispatches to whatever cleanup currently points to at return time: defer func() { cleanup() }() Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Babis Chalios --- .../pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go index 4565496172..9ea84e9b67 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go @@ -424,7 +424,7 @@ func crossProcessServe() error { fdExit.SignalExit() <-exitUffd } - defer cleanup() + defer func() { cleanup() }() if os.Getenv("GO_GATED") == "1" { gateCmdFile := os.NewFile(uintptr(7), "gate-cmd") From 7c7b660ae87dee9be2d84a877476396d619057b9 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Fri, 10 Apr 2026 17:32:07 -0700 Subject: [PATCH 21/63] fix(uffd): copy event structs in readEvents to prevent pointer aliasing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit readEvents stored pointers into a loop-local [24]byte arg variable. Since Go may reuse the same stack slot across iterations, all pointers could alias the last event's data when multiple events arrive in one batch (e.g. gated tests). This caused the handler to serve faults at wrong addresses, leaving faulting threads blocked — producing the 11-minute test timeout in CI. Also fix HasHugePages/HasFreePageReporting semver checks to correctly handle major versions > 1. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Babis Chalios --- packages/api/internal/sandbox/sandbox_features.go | 4 ++-- .../pkg/sandbox/uffd/userfaultfd/userfaultfd.go | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/api/internal/sandbox/sandbox_features.go b/packages/api/internal/sandbox/sandbox_features.go index cf3fb933a8..3578c9d0ff 100644 --- a/packages/api/internal/sandbox/sandbox_features.go +++ b/packages/api/internal/sandbox/sandbox_features.go @@ -39,7 +39,7 @@ func (v *VersionInfo) Version() semver.Version { } func (v *VersionInfo) HasHugePages() bool { - if v.lastReleaseVersion.Major() == 1 && v.lastReleaseVersion.Minor() >= 7 { + if v.lastReleaseVersion.Major() > 1 || (v.lastReleaseVersion.Major() == 1 && v.lastReleaseVersion.Minor() >= 7) { return true } @@ -47,7 +47,7 @@ func (v *VersionInfo) HasHugePages() bool { } func (v *VersionInfo) HasFreePageReporting() bool { - if v.lastReleaseVersion.Major() == 1 && v.lastReleaseVersion.Minor() >= 14 { + if v.lastReleaseVersion.Major() > 1 || (v.lastReleaseVersion.Major() == 1 && v.lastReleaseVersion.Minor() >= 14) { return true } diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index 8c21b3597f..cb0140b50d 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -138,9 +138,11 @@ func (u *Userfaultfd) readEvents(ctx context.Context) ([]*UffdRemove, []*UffdPag switch event { case UFFD_EVENT_PAGEFAULT: - pagefaults = append(pagefaults, (*UffdPagefault)(unsafe.Pointer(&arg[0]))) + v := *(*UffdPagefault)(unsafe.Pointer(&arg[0])) + pagefaults = append(pagefaults, &v) case UFFD_EVENT_REMOVE: - removes = append(removes, (*UffdRemove)(unsafe.Pointer(&arg[0]))) + v := *(*UffdRemove)(unsafe.Pointer(&arg[0])) + removes = append(removes, &v) default: return nil, nil, ErrUnexpectedEventType } From 54f48a305f377fc857948ae2fbbe7490e44009e5 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Fri, 10 Apr 2026 18:05:44 -0700 Subject: [PATCH 22/63] fix(uffd): add wakeup pipe to prevent orphaned deferred faults MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is a race between goroutine fault handlers and the main serve loop: a goroutine can get EAGAIN from the ioctl (due to a concurrent VMA change on a different page), defer its fault, and push it to the deferred queue AFTER the main loop has already drained that queue and re-entered poll(). If no new UFFD events arrive, poll blocks forever and the deferred fault is never retried — the faulting guest thread hangs indefinitely. Fix: add a self-pipe (wakeupPipe) monitored by poll(). When a goroutine defers a fault, it writes a byte to the pipe, guaranteeing poll() returns and the deferred fault gets drained on the next iteration. Also restructured the serve loop so that uffd reads only happen when POLLIN is set on the uffd fd (not unconditionally), and deferred faults are always drained regardless of which fd triggered the wakeup. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Babis Chalios --- .../sandbox/uffd/userfaultfd/userfaultfd.go | 105 +++++++++++------- 1 file changed, 62 insertions(+), 43 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index cb0140b50d..94862f99d0 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -63,6 +63,11 @@ type Userfaultfd struct { wg errgroup.Group + // wakeupPipe is a self-pipe used to wake the poll loop when a goroutine + // defers a page fault. Without this, a deferred fault could be orphaned + // if no new UFFD events arrive to wake poll. + wakeupPipe [2]int + logger logger.Logger } @@ -76,6 +81,11 @@ func NewUserfaultfdFromFd(fd uintptr, src block.Slicer, m *memory.Mapping, logge } } + var wakeupPipe [2]int + if err := syscall.Pipe2(wakeupPipe[:], syscall.O_NONBLOCK|syscall.O_CLOEXEC); err != nil { + return nil, fmt.Errorf("failed to create wakeup pipe: %w", err) + } + u := &Userfaultfd{ fd: Fd(fd), src: src, @@ -83,6 +93,7 @@ func NewUserfaultfdFromFd(fd uintptr, src block.Slicer, m *memory.Mapping, logge pageTracker: newPageTracker(uintptr(blockSize)), prefetchTracker: block.NewPrefetchTracker(blockSize), ma: m, + wakeupPipe: wakeupPipe, logger: logger, } @@ -158,14 +169,9 @@ func (u *Userfaultfd) Serve( pollFds := []unix.PollFd{ {Fd: int32(u.fd), Events: unix.POLLIN}, {Fd: fdExit.Reader(), Events: unix.POLLIN}, + {Fd: int32(u.wakeupPipe[0]), Events: unix.POLLIN}, } - eagainCounter := newCounterReporter(u.logger, "uffd: eagain during fd read (accumulated)") - defer eagainCounter.Close(ctx) - - noDataCounter := newCounterReporter(u.logger, "uffd: no data in fd (accumulated)") - defer noDataCounter.Close(ctx) - exitFdErrorCounter := newCounterReporter(u.logger, "uffd: exit fd poll errors (accumulated)") defer exitFdErrorCounter.Close(ctx) @@ -221,6 +227,11 @@ func (u *Userfaultfd) Serve( } } + // Drain the wakeup pipe if it fired (a goroutine deferred a fault). + if hasEvent(pollFds[2].Revents, unix.POLLIN) { + u.drainWakeupPipe() + } + uffdFd := pollFds[0] // Track uffd error events @@ -230,56 +241,42 @@ func (u *Userfaultfd) Serve( } } - if !hasEvent(uffdFd.Revents, unix.POLLIN) { - // Uffd is not ready for reading as there is nothing to read on the fd. - // https://github.com/firecracker-microvm/firecracker/issues/5056 - // https://elixir.bootlin.com/linux/v6.8.12/source/fs/userfaultfd.c#L1149 - // TODO: Check for all the errors - // - https://docs.kernel.org/admin-guide/mm/userfaultfd.html - // - https://elixir.bootlin.com/linux/v6.8.12/source/fs/userfaultfd.c - // - https://man7.org/linux/man-pages/man2/userfaultfd.2.html - // It might be possible to just check for data != 0 in the syscall.Read loop - // but I don't feel confident about doing that. - noDataCounter.Increase("POLLIN") - - continue - } - - removes, pagefaults, err := u.readEvents(ctx) - if err != nil { - u.logger.Error(ctx, "uffd: read error", zap.Error(err)) - - return fmt.Errorf("failed to read: %w", err) - } + var removes []*UffdRemove + var pagefaults []*UffdPagefault - // No events were found which is weird since, if we are here, - // poll() returned with an event indicating that UFFD had something - // for us to read. Log an error and continue - if len(removes) == 0 && len(pagefaults) == 0 { - eagainCounter.Increase("EAGAIN") + if hasEvent(uffdFd.Revents, unix.POLLIN) { + var err error + removes, pagefaults, err = u.readEvents(ctx) + if err != nil { + u.logger.Error(ctx, "uffd: read error", zap.Error(err)) - continue + return fmt.Errorf("failed to read: %w", err) + } } - // We successfully read all available UFFD events. - noDataCounter.Log(ctx) - eagainCounter.Log(ctx) - // First handle the UFFD_EVENT_REMOVE events. Take the settleRequests write lock to ensure that no // other page or pre-fault operation is running concurrently. // A goroutine from the previous batch or a prefault operation could still be executing - // setState(faulted) at line 326 after its UFFDIO_COPY returned. If we process a REMOVE for the same + // setState(faulted) after its UFFDIO_COPY returned. If we process a REMOVE for the same // page before that goroutine finishes, the goroutine's setState(faulted) would // overwrite the removed state we just set. - u.settleRequests.Lock() - for _, rm := range removes { - u.pageTracker.setState(uintptr(rm.start), uintptr(rm.end), removed) + if len(removes) > 0 { + u.settleRequests.Lock() + for _, rm := range removes { + u.pageTracker.setState(uintptr(rm.start), uintptr(rm.end), removed) + } + u.settleRequests.Unlock() } - u.settleRequests.Unlock() - // Collect deferred pagefaults from previous iteration's goroutines. + // Collect deferred pagefaults from previous goroutines that got EAGAIN. + // The wakeup pipe ensures we don't sleep through these. pagefaults = append(deferred.drain(), pagefaults...) + if len(pagefaults) == 0 { + // Woke up but nothing to do (e.g., only REMOVE events, or spurious wakeup). + continue + } + for _, pf := range pagefaults { // We don't handle minor page faults. if pf.flags&UFFD_PAGEFAULT_FLAG_MINOR != 0 { @@ -349,6 +346,7 @@ func (u *Userfaultfd) Serve( u.prefetchTracker.Add(offset, accessType) } else { deferred.push(pf) + u.signalWakeup() } return nil @@ -501,6 +499,27 @@ func (u *Userfaultfd) PrefetchData() block.PrefetchData { return u.prefetchTracker.PrefetchData() } +// signalWakeup writes a byte to the wakeup pipe to unblock poll. +// Safe to call from any goroutine; spurious writes are harmless. +func (u *Userfaultfd) signalWakeup() { + syscall.Write(u.wakeupPipe[1], []byte{1}) //nolint:errcheck // best-effort; pipe is non-blocking +} + +// drainWakeupPipe consumes all bytes from the wakeup pipe so it doesn't +// keep firing on the next poll. +func (u *Userfaultfd) drainWakeupPipe() { + var buf [64]byte + for { + _, err := syscall.Read(u.wakeupPipe[0], buf[:]) + if err != nil { + break + } + } +} + func (u *Userfaultfd) Close() error { + syscall.Close(u.wakeupPipe[0]) + syscall.Close(u.wakeupPipe[1]) + return u.fd.close() } From 68f1da89f3933095366781be58a980ad5a01db31 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Fri, 10 Apr 2026 19:05:19 -0700 Subject: [PATCH 23/63] fix(uffd-test): add sleep after remove to prevent UFFD event race madvise(MADV_DONTNEED) returns immediately but the UFFD_EVENT_REMOVE is delivered asynchronously. Without a sleep, pageStatesOnce() may read state before the handler has processed the remove event. The gated tests already had sleeps; the non-gated TestRemove tests did not, causing flaky "4k selective remove" failures. Signed-off-by: Babis Chalios --- .../pkg/sandbox/uffd/userfaultfd/remove_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/remove_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/remove_test.go index 07201ab78c..eaf3c70ac1 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/remove_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/remove_test.go @@ -20,6 +20,7 @@ func TestRemove(t *testing.T) { operations: []operation{ {offset: 0, mode: operationModeRead}, {offset: 0, mode: operationModeRemove}, + {mode: operationModeSleep}, }, }, { @@ -29,6 +30,7 @@ func TestRemove(t *testing.T) { operations: []operation{ {offset: 0, mode: operationModeRead}, {offset: 0, mode: operationModeRemove}, + {mode: operationModeSleep}, }, }, { @@ -38,6 +40,7 @@ func TestRemove(t *testing.T) { operations: []operation{ {offset: 0, mode: operationModeWrite}, {offset: 0, mode: operationModeRemove}, + {mode: operationModeSleep}, }, }, { @@ -47,6 +50,7 @@ func TestRemove(t *testing.T) { operations: []operation{ {offset: 0, mode: operationModeWrite}, {offset: 0, mode: operationModeRemove}, + {mode: operationModeSleep}, }, }, { @@ -57,6 +61,7 @@ func TestRemove(t *testing.T) { {offset: 0, mode: operationModeRead}, {offset: int64(header.PageSize), mode: operationModeWrite}, {offset: 0, mode: operationModeRemove}, + {mode: operationModeSleep}, }, }, { @@ -67,6 +72,7 @@ func TestRemove(t *testing.T) { {offset: 0, mode: operationModeRead}, {offset: int64(header.HugepageSize), mode: operationModeWrite}, {offset: 0, mode: operationModeRemove}, + {mode: operationModeSleep}, }, }, } From a94652167caa962c15ffa3e355ba8100e08da6ff Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Mon, 13 Apr 2026 22:56:42 -0700 Subject: [PATCH 24/63] fix(uffd): wake thread on EEXIST to prevent DONTWAKE hang MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 4K removed-page read path uses a three-step sequence: zero(DONTWAKE) → writeProtect(WP) → wake(). If step 1 succeeds but step 2 returns EAGAIN (concurrent REMOVE), the page is mapped but the guest thread stays asleep. On retry, zero() returns EEXIST and we returned early without calling wake(), leaving the thread blocked forever. Fix by unconditionally calling wake() on EEXIST. If the thread is sleeping due to DONTWAKE this unblocks it; if already awake the wake is a harmless no-op. Signed-off-by: Babis Chalios --- .../pkg/sandbox/uffd/userfaultfd/userfaultfd.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index 94862f99d0..7b57248f99 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -460,13 +460,15 @@ func (u *Userfaultfd) faultPage( writeErr = u.fd.copy(addr, u.pageSize, b, mode) } - // Page is already mapped. - // Probably because we have already pre-faulted it. Otherwise, we should not - // try to handle a page fault for the same address twice, since we are now - // tracking the state of pages. + // Page is already mapped (e.g. pre-faulted, or a previous DONTWAKE zero succeeded + // but the subsequent writeProtect/wake step failed with EAGAIN and the fault was deferred). + // Wake the thread unconditionally: if the thread is sleeping due to DONTWAKE this + // unblocks it; if it was already woken the wake is a harmless no-op. if errors.Is(writeErr, unix.EEXIST) { span.SetAttributes(attribute.Bool("uffd.already_mapped", true)) + u.fd.wake(addr, u.pageSize) //nolint:errcheck // best-effort; thread may already be awake + return true, nil } From 5acc7ec100d549f93987d898347cb62c417bcd30 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Wed, 15 Apr 2026 15:20:59 -0700 Subject: [PATCH 25/63] fix(uffd-test): disable child process timeout and enable FPR in smoke test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cross-process UFFD test helper was spawned without -test.timeout=0, so Go's default 10m timeout killed it before the parent's 20m timeout, leaving madvise/page-fault syscalls permanently blocked. Also update Firecracker v1.14 version to v1.14.1_76f16f0 (the 458ca91 release does not exist) and enable FreePageReporting in the smoke test for FC versions >= v1.14 so the full balloon→REMOVE→zero-fill path is exercised in CI. --- .../orchestrator/cmd/smoketest/smoke_test.go | 16 +++++++++++----- .../userfaultfd/cross_process_helpers_test.go | 2 +- packages/shared/pkg/featureflags/flags.go | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/orchestrator/cmd/smoketest/smoke_test.go b/packages/orchestrator/cmd/smoketest/smoke_test.go index 5134c6379b..1fa641a090 100644 --- a/packages/orchestrator/cmd/smoketest/smoke_test.go +++ b/packages/orchestrator/cmd/smoketest/smoke_test.go @@ -37,6 +37,7 @@ import ( "github.com/e2b-dev/infra/packages/shared/pkg/dockerhub" "github.com/e2b-dev/infra/packages/shared/pkg/featureflags" "github.com/e2b-dev/infra/packages/shared/pkg/logger" + "github.com/e2b-dev/infra/packages/shared/pkg/utils" sbxlogger "github.com/e2b-dev/infra/packages/shared/pkg/logger/sandbox" "github.com/e2b-dev/infra/packages/shared/pkg/storage" "github.com/e2b-dev/infra/packages/shared/pkg/templates" @@ -71,11 +72,14 @@ func TestSmokeAllFCVersions(t *testing.T) { //nolint:paralleltest // subtests sh defer infra.close(ctx) for fcMajor, fcVersion := range featureflags.FirecrackerVersionMap { //nolint:paralleltest // sequential by design + versionOnly, _, _ := strings.Cut(fcVersion, "_") + fpr, _ := utils.IsGTEVersion(versionOnly, "v1.14.0") + t.Run("fc-"+fcMajor, func(t *testing.T) { buildID := uuid.New().String() // Phase 1: create build - t.Logf("creating build %s with FC %s", buildID, fcVersion) + t.Logf("creating build %s with FC %s (freePageReporting=%v)", buildID, fcVersion, fpr) force := true _, err := infra.builder.Build( ctx, @@ -88,6 +92,7 @@ func TestSmokeAllFCVersions(t *testing.T) { //nolint:paralleltest // subtests sh MemoryMB: 512, DiskSizeMB: 512, HugePages: true, + FreePageReporting: fpr, KernelVersion: featureflags.DefaultKernelVersion, FirecrackerVersion: fcVersion, FromImage: baseImage, @@ -111,10 +116,11 @@ func TestSmokeAllFCVersions(t *testing.T) { //nolint:paralleltest // subtests sh ctx, tmpl, sandbox.NewConfig(sandbox.Config{ - BaseTemplateID: "smoke-" + fcMajor, - Vcpu: 2, - RamMB: 512, - HugePages: true, + BaseTemplateID: "smoke-" + fcMajor, + Vcpu: 2, + RamMB: 512, + HugePages: true, + FreePageReporting: fpr, Envd: sandbox.EnvdMetadata{ Vars: map[string]string{}, AccessToken: &token, diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go index 9ea84e9b67..c0a1a14bc5 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go @@ -99,7 +99,7 @@ func configureCrossProcessTest(t *testing.T, tt testConfig) (*testHandler, error err = register(uffdFd, memoryStart, uint64(size), UFFDIO_REGISTER_MODE_MISSING|UFFDIO_REGISTER_MODE_WP) require.NoError(t, err) - cmd := exec.CommandContext(t.Context(), os.Args[0], "-test.run=TestHelperServingProcess") + cmd := exec.CommandContext(t.Context(), os.Args[0], "-test.run=TestHelperServingProcess", "-test.timeout=0") cmd.Env = append(os.Environ(), "GO_TEST_HELPER_PROCESS=1") cmd.Env = append(cmd.Env, fmt.Sprintf("GO_MMAP_START=%d", memoryStart)) cmd.Env = append(cmd.Env, fmt.Sprintf("GO_MMAP_PAGE_SIZE=%d", tt.pagesize)) diff --git a/packages/shared/pkg/featureflags/flags.go b/packages/shared/pkg/featureflags/flags.go index a557186e5d..221fb22f4a 100644 --- a/packages/shared/pkg/featureflags/flags.go +++ b/packages/shared/pkg/featureflags/flags.go @@ -242,7 +242,7 @@ const ( const ( DefaultFirecackerV1_10Version = "v1.10.1_30cbb07" DefaultFirecackerV1_12Version = "v1.12.1_210cbac" - DefaultFirecackerV1_14Version = "v1.14.1_458ca91" + DefaultFirecackerV1_14Version = "v1.14.1_76f16f0" DefaultFirecrackerVersion = DefaultFirecackerV1_14Version ) From 10d2311ae52bb713fb4e2621ebf303c519dde123 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 15 Apr 2026 22:24:04 +0000 Subject: [PATCH 26/63] chore: auto-commit generated changes --- packages/orchestrator/cmd/smoketest/smoke_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/orchestrator/cmd/smoketest/smoke_test.go b/packages/orchestrator/cmd/smoketest/smoke_test.go index 1fa641a090..7e90e7271a 100644 --- a/packages/orchestrator/cmd/smoketest/smoke_test.go +++ b/packages/orchestrator/cmd/smoketest/smoke_test.go @@ -37,10 +37,10 @@ import ( "github.com/e2b-dev/infra/packages/shared/pkg/dockerhub" "github.com/e2b-dev/infra/packages/shared/pkg/featureflags" "github.com/e2b-dev/infra/packages/shared/pkg/logger" - "github.com/e2b-dev/infra/packages/shared/pkg/utils" sbxlogger "github.com/e2b-dev/infra/packages/shared/pkg/logger/sandbox" "github.com/e2b-dev/infra/packages/shared/pkg/storage" "github.com/e2b-dev/infra/packages/shared/pkg/templates" + "github.com/e2b-dev/infra/packages/shared/pkg/utils" ) const ( From 9bc0f8286310d5e4ce0f44570ed8adf9aaf594f6 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Thu, 16 Apr 2026 14:44:29 -0700 Subject: [PATCH 27/63] chore: update FC version references to v1.14.1_76f16f0 --- .github/actions/build-sandbox-template/action.yml | 2 +- packages/orchestrator/README.md | 2 +- tests/integration/seed.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/actions/build-sandbox-template/action.yml b/.github/actions/build-sandbox-template/action.yml index f42d2b02b8..d2b3c8b538 100644 --- a/.github/actions/build-sandbox-template/action.yml +++ b/.github/actions/build-sandbox-template/action.yml @@ -8,7 +8,7 @@ runs: env: TEMPLATE_ID: "2j6ly824owf4awgai1xo" KERNEL_VERSION: "vmlinux-6.1.158" - FIRECRACKER_VERSION: "v1.14.1_458ca91" + FIRECRACKER_VERSION: "v1.14.1_76f16f0" run: | # Generate an unique build ID for the template for this run export BUILD_ID=$(uuidgen) diff --git a/packages/orchestrator/README.md b/packages/orchestrator/README.md index d51767b63a..7bdec0f2ff 100644 --- a/packages/orchestrator/README.md +++ b/packages/orchestrator/README.md @@ -37,7 +37,7 @@ Flags: - `-storage ` - Local path or `gs://bucket` (enables local mode with auto-download of kernel/FC) - `-sandbox-dir ` - Override `SANDBOX_DIR` (the rootfs path baked into the snapshot) - `-kernel ` - Kernel version (default: `vmlinux-6.1.158`) -- `-firecracker ` - Firecracker version (default: `v1.14.1_458ca91`) +- `-firecracker ` - Firecracker version (default: `v1.14.1_76f16f0`) - `-vcpu ` - vCPUs (default: `1`) - `-memory ` - Memory in MB (default: `512`) - `-disk ` - Disk in MB (default: `1000`) diff --git a/tests/integration/seed.go b/tests/integration/seed.go index aeabe4ea8a..4db805560a 100644 --- a/tests/integration/seed.go +++ b/tests/integration/seed.go @@ -208,7 +208,7 @@ INSERT INTO env_builds ( cluster_node_id, version, created_at, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, CURRENT_TIMESTAMP) `, build.id, "FROM e2bdev/base:latest", dbtypes.BuildStatusUploaded, - 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.14.1_458ca91", pkg.Version, + 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.14.1_76f16f0", pkg.Version, "integration-test-node", templates.TemplateV1Version, build.createdAt) } else { err = db.TestsRawSQL(ctx, ` @@ -218,7 +218,7 @@ INSERT INTO env_builds ( cluster_node_id, version, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, CURRENT_TIMESTAMP) `, build.id, "FROM e2bdev/base:latest", dbtypes.BuildStatusUploaded, - 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.14.1_458ca91", pkg.Version, + 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.14.1_76f16f0", pkg.Version, "integration-test-node", templates.TemplateV1Version) } if err != nil { From 5265808e5b6e8a88e6ccd4db3a49a2db12b5ca2f Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Thu, 16 Apr 2026 14:55:47 -0700 Subject: [PATCH 28/63] chore: use FC v1.14.1_458ca91 (latest deployed build) --- .github/actions/build-sandbox-template/action.yml | 2 +- packages/orchestrator/README.md | 2 +- packages/shared/pkg/featureflags/flags.go | 2 +- tests/integration/seed.go | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/actions/build-sandbox-template/action.yml b/.github/actions/build-sandbox-template/action.yml index d2b3c8b538..f42d2b02b8 100644 --- a/.github/actions/build-sandbox-template/action.yml +++ b/.github/actions/build-sandbox-template/action.yml @@ -8,7 +8,7 @@ runs: env: TEMPLATE_ID: "2j6ly824owf4awgai1xo" KERNEL_VERSION: "vmlinux-6.1.158" - FIRECRACKER_VERSION: "v1.14.1_76f16f0" + FIRECRACKER_VERSION: "v1.14.1_458ca91" run: | # Generate an unique build ID for the template for this run export BUILD_ID=$(uuidgen) diff --git a/packages/orchestrator/README.md b/packages/orchestrator/README.md index 7bdec0f2ff..d51767b63a 100644 --- a/packages/orchestrator/README.md +++ b/packages/orchestrator/README.md @@ -37,7 +37,7 @@ Flags: - `-storage ` - Local path or `gs://bucket` (enables local mode with auto-download of kernel/FC) - `-sandbox-dir ` - Override `SANDBOX_DIR` (the rootfs path baked into the snapshot) - `-kernel ` - Kernel version (default: `vmlinux-6.1.158`) -- `-firecracker ` - Firecracker version (default: `v1.14.1_76f16f0`) +- `-firecracker ` - Firecracker version (default: `v1.14.1_458ca91`) - `-vcpu ` - vCPUs (default: `1`) - `-memory ` - Memory in MB (default: `512`) - `-disk ` - Disk in MB (default: `1000`) diff --git a/packages/shared/pkg/featureflags/flags.go b/packages/shared/pkg/featureflags/flags.go index cc07f95a7f..79f9327760 100644 --- a/packages/shared/pkg/featureflags/flags.go +++ b/packages/shared/pkg/featureflags/flags.go @@ -241,7 +241,7 @@ const ( const ( DefaultFirecackerV1_10Version = "v1.10.1_30cbb07" DefaultFirecackerV1_12Version = "v1.12.1_210cbac" - DefaultFirecackerV1_14Version = "v1.14.1_76f16f0" + DefaultFirecackerV1_14Version = "v1.14.1_458ca91" DefaultFirecrackerVersion = DefaultFirecackerV1_14Version ) diff --git a/tests/integration/seed.go b/tests/integration/seed.go index 4db805560a..aeabe4ea8a 100644 --- a/tests/integration/seed.go +++ b/tests/integration/seed.go @@ -208,7 +208,7 @@ INSERT INTO env_builds ( cluster_node_id, version, created_at, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, CURRENT_TIMESTAMP) `, build.id, "FROM e2bdev/base:latest", dbtypes.BuildStatusUploaded, - 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.14.1_76f16f0", pkg.Version, + 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.14.1_458ca91", pkg.Version, "integration-test-node", templates.TemplateV1Version, build.createdAt) } else { err = db.TestsRawSQL(ctx, ` @@ -218,7 +218,7 @@ INSERT INTO env_builds ( cluster_node_id, version, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, CURRENT_TIMESTAMP) `, build.id, "FROM e2bdev/base:latest", dbtypes.BuildStatusUploaded, - 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.14.1_76f16f0", pkg.Version, + 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.14.1_458ca91", pkg.Version, "integration-test-node", templates.TemplateV1Version) } if err != nil { From 9e85de985385bd5a0bd04dde543b71b5b2e8cb97 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Thu, 16 Apr 2026 14:55:57 -0700 Subject: [PATCH 29/63] chore: use FC v1.14.1_458ca91 (latest deployed build) --- .github/actions/build-sandbox-template/action.yml | 2 +- packages/orchestrator/README.md | 2 +- packages/shared/pkg/featureflags/flags.go | 2 +- tests/integration/seed.go | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/actions/build-sandbox-template/action.yml b/.github/actions/build-sandbox-template/action.yml index d2b3c8b538..f42d2b02b8 100644 --- a/.github/actions/build-sandbox-template/action.yml +++ b/.github/actions/build-sandbox-template/action.yml @@ -8,7 +8,7 @@ runs: env: TEMPLATE_ID: "2j6ly824owf4awgai1xo" KERNEL_VERSION: "vmlinux-6.1.158" - FIRECRACKER_VERSION: "v1.14.1_76f16f0" + FIRECRACKER_VERSION: "v1.14.1_458ca91" run: | # Generate an unique build ID for the template for this run export BUILD_ID=$(uuidgen) diff --git a/packages/orchestrator/README.md b/packages/orchestrator/README.md index 7bdec0f2ff..d51767b63a 100644 --- a/packages/orchestrator/README.md +++ b/packages/orchestrator/README.md @@ -37,7 +37,7 @@ Flags: - `-storage ` - Local path or `gs://bucket` (enables local mode with auto-download of kernel/FC) - `-sandbox-dir ` - Override `SANDBOX_DIR` (the rootfs path baked into the snapshot) - `-kernel ` - Kernel version (default: `vmlinux-6.1.158`) -- `-firecracker ` - Firecracker version (default: `v1.14.1_76f16f0`) +- `-firecracker ` - Firecracker version (default: `v1.14.1_458ca91`) - `-vcpu ` - vCPUs (default: `1`) - `-memory ` - Memory in MB (default: `512`) - `-disk ` - Disk in MB (default: `1000`) diff --git a/packages/shared/pkg/featureflags/flags.go b/packages/shared/pkg/featureflags/flags.go index 221fb22f4a..a557186e5d 100644 --- a/packages/shared/pkg/featureflags/flags.go +++ b/packages/shared/pkg/featureflags/flags.go @@ -242,7 +242,7 @@ const ( const ( DefaultFirecackerV1_10Version = "v1.10.1_30cbb07" DefaultFirecackerV1_12Version = "v1.12.1_210cbac" - DefaultFirecackerV1_14Version = "v1.14.1_76f16f0" + DefaultFirecackerV1_14Version = "v1.14.1_458ca91" DefaultFirecrackerVersion = DefaultFirecackerV1_14Version ) diff --git a/tests/integration/seed.go b/tests/integration/seed.go index 4db805560a..aeabe4ea8a 100644 --- a/tests/integration/seed.go +++ b/tests/integration/seed.go @@ -208,7 +208,7 @@ INSERT INTO env_builds ( cluster_node_id, version, created_at, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, CURRENT_TIMESTAMP) `, build.id, "FROM e2bdev/base:latest", dbtypes.BuildStatusUploaded, - 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.14.1_76f16f0", pkg.Version, + 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.14.1_458ca91", pkg.Version, "integration-test-node", templates.TemplateV1Version, build.createdAt) } else { err = db.TestsRawSQL(ctx, ` @@ -218,7 +218,7 @@ INSERT INTO env_builds ( cluster_node_id, version, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, CURRENT_TIMESTAMP) `, build.id, "FROM e2bdev/base:latest", dbtypes.BuildStatusUploaded, - 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.14.1_76f16f0", pkg.Version, + 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.14.1_458ca91", pkg.Version, "integration-test-node", templates.TemplateV1Version) } if err != nil { From 8137fdeaaa0549d1b1781e5022de90566287823f Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Fri, 17 Apr 2026 17:48:09 -0700 Subject: [PATCH 30/63] fix: address PR review comments for FC v1.14 update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Proto source: add freePageReporting field (=17) to template-manager.proto (was only in generated pb.go) 2. Deprecated field: migrate EnableDiffSnapshots → TrackDirtyPages in fc/client.go (EnableDiffSnapshots deprecated in FC v1.14) 3. Generated code bug: fix return nil → continue in contextValidate loop functions in cpu_config.go (4 places) and cpuid_leaf_modifier.go (1 place). These caused the loop to exit on the first zero-valued element, skipping validation of all subsequent elements. 4. Typos: fix "Reuturn" → "Return" and "Identificator" → "Identifier" in firecracker.yml and propagated generated files. --- packages/orchestrator/pkg/sandbox/fc/client.go | 2 +- packages/orchestrator/template-manager.proto | 2 ++ .../shared/pkg/fc/client/operations/operations_client.go | 2 +- packages/shared/pkg/fc/firecracker.yml | 4 ++-- packages/shared/pkg/fc/models/cpu_config.go | 8 ++++---- packages/shared/pkg/fc/models/cpuid_leaf_modifier.go | 2 +- packages/shared/pkg/fc/models/pmem.go | 2 +- 7 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/fc/client.go b/packages/orchestrator/pkg/sandbox/fc/client.go index f5c9eb30d0..a4b72fb08a 100644 --- a/packages/orchestrator/pkg/sandbox/fc/client.go +++ b/packages/orchestrator/pkg/sandbox/fc/client.go @@ -59,7 +59,7 @@ func (c *apiClient) loadSnapshot( Context: ctx, Body: &models.SnapshotLoadParams{ ResumeVM: false, - EnableDiffSnapshots: false, + TrackDirtyPages: false, MemBackend: backend, SnapshotPath: &snapfilePath, }, diff --git a/packages/orchestrator/template-manager.proto b/packages/orchestrator/template-manager.proto index 4ef795fb59..9f0b97c782 100644 --- a/packages/orchestrator/template-manager.proto +++ b/packages/orchestrator/template-manager.proto @@ -84,6 +84,8 @@ message TemplateConfig { optional FromImageRegistry fromImageRegistry = 15; string teamID = 16; + + optional bool freePageReporting = 17; } message TemplateCreateRequest { diff --git a/packages/shared/pkg/fc/client/operations/operations_client.go b/packages/shared/pkg/fc/client/operations/operations_client.go index d29a2e37f1..50307169d3 100644 --- a/packages/shared/pkg/fc/client/operations/operations_client.go +++ b/packages/shared/pkg/fc/client/operations/operations_client.go @@ -609,7 +609,7 @@ func (a *Client) GetMemory(params *GetMemoryParams, opts ...ClientOption) (*GetM /* GetMemoryHotplug retrieves the status of the hotpluggable memory -Reuturn the status of the hotpluggable memory. This can be used to follow the progress of the guest after a PATCH API. +Return the status of the hotpluggable memory. This can be used to follow the progress of the guest after a PATCH API. */ func (a *Client) GetMemoryHotplug(params *GetMemoryHotplugParams, opts ...ClientOption) (*GetMemoryHotplugOK, error) { // NOTE: parameters are not validated before sending diff --git a/packages/shared/pkg/fc/firecracker.yml b/packages/shared/pkg/fc/firecracker.yml index af1cfb806c..1a819924b9 100644 --- a/packages/shared/pkg/fc/firecracker.yml +++ b/packages/shared/pkg/fc/firecracker.yml @@ -663,7 +663,7 @@ paths: summary: Retrieves the status of the hotpluggable memory operationId: getMemoryHotplug description: - Reuturn the status of the hotpluggable memory. This can be used to follow the progress of the guest + Return the status of the hotpluggable memory. This can be used to follow the progress of the guest after a PATCH API. responses: 200: @@ -1289,7 +1289,7 @@ definitions: id: type: string description: - Identificator for this device. + Identifier for this device. path_on_host: type: string description: diff --git a/packages/shared/pkg/fc/models/cpu_config.go b/packages/shared/pkg/fc/models/cpu_config.go index 7b5f5d03b6..7494a700fa 100644 --- a/packages/shared/pkg/fc/models/cpu_config.go +++ b/packages/shared/pkg/fc/models/cpu_config.go @@ -212,7 +212,7 @@ func (m *CPUConfig) contextValidateCpuidModifiers(ctx context.Context, formats s if m.CpuidModifiers[i] != nil { if swag.IsZero(m.CpuidModifiers[i]) { // not required - return nil + continue } if err := m.CpuidModifiers[i].ContextValidate(ctx, formats); err != nil { @@ -241,7 +241,7 @@ func (m *CPUConfig) contextValidateMsrModifiers(ctx context.Context, formats str if m.MsrModifiers[i] != nil { if swag.IsZero(m.MsrModifiers[i]) { // not required - return nil + continue } if err := m.MsrModifiers[i].ContextValidate(ctx, formats); err != nil { @@ -270,7 +270,7 @@ func (m *CPUConfig) contextValidateRegModifiers(ctx context.Context, formats str if m.RegModifiers[i] != nil { if swag.IsZero(m.RegModifiers[i]) { // not required - return nil + continue } if err := m.RegModifiers[i].ContextValidate(ctx, formats); err != nil { @@ -299,7 +299,7 @@ func (m *CPUConfig) contextValidateVcpuFeatures(ctx context.Context, formats str if m.VcpuFeatures[i] != nil { if swag.IsZero(m.VcpuFeatures[i]) { // not required - return nil + continue } if err := m.VcpuFeatures[i].ContextValidate(ctx, formats); err != nil { diff --git a/packages/shared/pkg/fc/models/cpuid_leaf_modifier.go b/packages/shared/pkg/fc/models/cpuid_leaf_modifier.go index 9ace041fef..37eff61d5a 100644 --- a/packages/shared/pkg/fc/models/cpuid_leaf_modifier.go +++ b/packages/shared/pkg/fc/models/cpuid_leaf_modifier.go @@ -140,7 +140,7 @@ func (m *CpuidLeafModifier) contextValidateModifiers(ctx context.Context, format if m.Modifiers[i] != nil { if swag.IsZero(m.Modifiers[i]) { // not required - return nil + continue } if err := m.Modifiers[i].ContextValidate(ctx, formats); err != nil { diff --git a/packages/shared/pkg/fc/models/pmem.go b/packages/shared/pkg/fc/models/pmem.go index aad5d79d21..5322acff0a 100644 --- a/packages/shared/pkg/fc/models/pmem.go +++ b/packages/shared/pkg/fc/models/pmem.go @@ -16,7 +16,7 @@ import ( // swagger:model Pmem type Pmem struct { - // Identificator for this device. + // Identifier for this device. // Required: true ID *string `json:"id"` From 8d9b57982488aefe4885c2592dacea481149e791 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Fri, 17 Apr 2026 17:49:29 -0700 Subject: [PATCH 31/63] revert: undo manual edits to generated go-swagger files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The contextValidate return nil→continue fix and typo fixes in cpu_config.go, cpuid_leaf_modifier.go, pmem.go, and operations_client.go were wrong to apply manually — these are "DO NOT EDIT" generated files that will be overwritten on regeneration. The actual fixes remain: - firecracker.yml typos fixed (source spec, not generated) - template-manager.proto field added (source, not generated) - EnableDiffSnapshots → TrackDirtyPages (our code, not generated) The go-swagger return nil bug and typo propagation should be fixed by regenerating from the corrected firecracker.yml source. --- .../shared/pkg/fc/client/operations/operations_client.go | 2 +- packages/shared/pkg/fc/models/cpu_config.go | 8 ++++---- packages/shared/pkg/fc/models/cpuid_leaf_modifier.go | 2 +- packages/shared/pkg/fc/models/pmem.go | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/shared/pkg/fc/client/operations/operations_client.go b/packages/shared/pkg/fc/client/operations/operations_client.go index 50307169d3..d29a2e37f1 100644 --- a/packages/shared/pkg/fc/client/operations/operations_client.go +++ b/packages/shared/pkg/fc/client/operations/operations_client.go @@ -609,7 +609,7 @@ func (a *Client) GetMemory(params *GetMemoryParams, opts ...ClientOption) (*GetM /* GetMemoryHotplug retrieves the status of the hotpluggable memory -Return the status of the hotpluggable memory. This can be used to follow the progress of the guest after a PATCH API. +Reuturn the status of the hotpluggable memory. This can be used to follow the progress of the guest after a PATCH API. */ func (a *Client) GetMemoryHotplug(params *GetMemoryHotplugParams, opts ...ClientOption) (*GetMemoryHotplugOK, error) { // NOTE: parameters are not validated before sending diff --git a/packages/shared/pkg/fc/models/cpu_config.go b/packages/shared/pkg/fc/models/cpu_config.go index 7494a700fa..7b5f5d03b6 100644 --- a/packages/shared/pkg/fc/models/cpu_config.go +++ b/packages/shared/pkg/fc/models/cpu_config.go @@ -212,7 +212,7 @@ func (m *CPUConfig) contextValidateCpuidModifiers(ctx context.Context, formats s if m.CpuidModifiers[i] != nil { if swag.IsZero(m.CpuidModifiers[i]) { // not required - continue + return nil } if err := m.CpuidModifiers[i].ContextValidate(ctx, formats); err != nil { @@ -241,7 +241,7 @@ func (m *CPUConfig) contextValidateMsrModifiers(ctx context.Context, formats str if m.MsrModifiers[i] != nil { if swag.IsZero(m.MsrModifiers[i]) { // not required - continue + return nil } if err := m.MsrModifiers[i].ContextValidate(ctx, formats); err != nil { @@ -270,7 +270,7 @@ func (m *CPUConfig) contextValidateRegModifiers(ctx context.Context, formats str if m.RegModifiers[i] != nil { if swag.IsZero(m.RegModifiers[i]) { // not required - continue + return nil } if err := m.RegModifiers[i].ContextValidate(ctx, formats); err != nil { @@ -299,7 +299,7 @@ func (m *CPUConfig) contextValidateVcpuFeatures(ctx context.Context, formats str if m.VcpuFeatures[i] != nil { if swag.IsZero(m.VcpuFeatures[i]) { // not required - continue + return nil } if err := m.VcpuFeatures[i].ContextValidate(ctx, formats); err != nil { diff --git a/packages/shared/pkg/fc/models/cpuid_leaf_modifier.go b/packages/shared/pkg/fc/models/cpuid_leaf_modifier.go index 37eff61d5a..9ace041fef 100644 --- a/packages/shared/pkg/fc/models/cpuid_leaf_modifier.go +++ b/packages/shared/pkg/fc/models/cpuid_leaf_modifier.go @@ -140,7 +140,7 @@ func (m *CpuidLeafModifier) contextValidateModifiers(ctx context.Context, format if m.Modifiers[i] != nil { if swag.IsZero(m.Modifiers[i]) { // not required - continue + return nil } if err := m.Modifiers[i].ContextValidate(ctx, formats); err != nil { diff --git a/packages/shared/pkg/fc/models/pmem.go b/packages/shared/pkg/fc/models/pmem.go index 5322acff0a..aad5d79d21 100644 --- a/packages/shared/pkg/fc/models/pmem.go +++ b/packages/shared/pkg/fc/models/pmem.go @@ -16,7 +16,7 @@ import ( // swagger:model Pmem type Pmem struct { - // Identifier for this device. + // Identificator for this device. // Required: true ID *string `json:"id"` From ab9b0785502045b84985baaac7e4265d4d1c5b10 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 18 Apr 2026 00:53:25 +0000 Subject: [PATCH 32/63] chore: auto-commit generated changes --- .../orchestrator/pkg/sandbox/fc/client.go | 6 +- .../fc/client/operations/operations_client.go | 2 +- packages/shared/pkg/fc/models/pmem.go | 2 +- .../template-manager/template-manager.pb.go | 255 +++++++++--------- 4 files changed, 139 insertions(+), 126 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/fc/client.go b/packages/orchestrator/pkg/sandbox/fc/client.go index a4b72fb08a..29dd687d77 100644 --- a/packages/orchestrator/pkg/sandbox/fc/client.go +++ b/packages/orchestrator/pkg/sandbox/fc/client.go @@ -58,10 +58,10 @@ func (c *apiClient) loadSnapshot( snapshotConfig := operations.LoadSnapshotParams{ Context: ctx, Body: &models.SnapshotLoadParams{ - ResumeVM: false, + ResumeVM: false, TrackDirtyPages: false, - MemBackend: backend, - SnapshotPath: &snapfilePath, + MemBackend: backend, + SnapshotPath: &snapfilePath, }, } diff --git a/packages/shared/pkg/fc/client/operations/operations_client.go b/packages/shared/pkg/fc/client/operations/operations_client.go index d29a2e37f1..50307169d3 100644 --- a/packages/shared/pkg/fc/client/operations/operations_client.go +++ b/packages/shared/pkg/fc/client/operations/operations_client.go @@ -609,7 +609,7 @@ func (a *Client) GetMemory(params *GetMemoryParams, opts ...ClientOption) (*GetM /* GetMemoryHotplug retrieves the status of the hotpluggable memory -Reuturn the status of the hotpluggable memory. This can be used to follow the progress of the guest after a PATCH API. +Return the status of the hotpluggable memory. This can be used to follow the progress of the guest after a PATCH API. */ func (a *Client) GetMemoryHotplug(params *GetMemoryHotplugParams, opts ...ClientOption) (*GetMemoryHotplugOK, error) { // NOTE: parameters are not validated before sending diff --git a/packages/shared/pkg/fc/models/pmem.go b/packages/shared/pkg/fc/models/pmem.go index aad5d79d21..5322acff0a 100644 --- a/packages/shared/pkg/fc/models/pmem.go +++ b/packages/shared/pkg/fc/models/pmem.go @@ -16,7 +16,7 @@ import ( // swagger:model Pmem type Pmem struct { - // Identificator for this device. + // Identifier for this device. // Required: true ID *string `json:"id"` diff --git a/packages/shared/pkg/grpc/template-manager/template-manager.pb.go b/packages/shared/pkg/grpc/template-manager/template-manager.pb.go index ebc032f7dc..3c3fe06023 100644 --- a/packages/shared/pkg/grpc/template-manager/template-manager.pb.go +++ b/packages/shared/pkg/grpc/template-manager/template-manager.pb.go @@ -699,6 +699,7 @@ type TemplateConfig struct { Source isTemplateConfig_Source `protobuf_oneof:"source"` FromImageRegistry *FromImageRegistry `protobuf:"bytes,15,opt,name=fromImageRegistry,proto3,oneof" json:"fromImageRegistry,omitempty"` TeamID string `protobuf:"bytes,16,opt,name=teamID,proto3" json:"teamID,omitempty"` + FreePageReporting *bool `protobuf:"varint,17,opt,name=freePageReporting,proto3,oneof" json:"freePageReporting,omitempty"` } func (x *TemplateConfig) Reset() { @@ -852,6 +853,13 @@ func (x *TemplateConfig) GetTeamID() string { return "" } +func (x *TemplateConfig) GetFreePageReporting() bool { + if x != nil && x.FreePageReporting != nil { + return *x.FreePageReporting + } + return false +} + type isTemplateConfig_Source interface { isTemplateConfig_Source() } @@ -1404,7 +1412,7 @@ var file_template_manager_proto_rawDesc = []byte{ 0x03, 0x67, 0x63, 0x70, 0x12, 0x2c, 0x0a, 0x07, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x48, 0x00, 0x52, 0x07, 0x67, 0x65, 0x6e, 0x65, 0x72, - 0x61, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x84, 0x05, 0x0a, 0x0e, 0x54, + 0x61, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xcd, 0x05, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x12, 0x18, 0x0a, @@ -1442,129 +1450,134 @@ var file_template_manager_proto_rawDesc = []byte{ 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x48, 0x02, 0x52, 0x11, 0x66, 0x72, 0x6f, 0x6d, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x88, 0x01, 0x01, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x65, 0x61, 0x6d, 0x49, 0x44, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x74, 0x65, 0x61, 0x6d, 0x49, 0x44, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x42, 0x14, 0x0a, 0x12, 0x5f, - 0x66, 0x72, 0x6f, 0x6d, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x79, 0x22, 0xa3, 0x01, 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x08, 0x74, - 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, - 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, - 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x23, 0x0a, 0x0a, 0x63, 0x61, 0x63, 0x68, - 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, - 0x63, 0x61, 0x63, 0x68, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, - 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, - 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, - 0x5f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x8b, 0x03, 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, - 0x6c, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, - 0x44, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x12, 0x1b, 0x0a, 0x06, 0x6f, - 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x06, 0x6f, - 0x66, 0x66, 0x73, 0x65, 0x74, 0x88, 0x01, 0x01, 0x12, 0x24, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, - 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, - 0x65, 0x6c, 0x48, 0x01, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x19, - 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x02, 0x52, - 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x88, 0x01, 0x01, 0x12, 0x35, 0x0a, 0x05, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x48, 0x03, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x88, 0x01, 0x01, - 0x12, 0x31, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x48, 0x04, 0x52, 0x03, 0x65, 0x6e, 0x64, - 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x4c, 0x6f, 0x67, 0x73, 0x44, 0x69, 0x72, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x05, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, - 0x74, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x42, 0x08, 0x0a, 0x06, 0x5f, - 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, - 0x06, 0x0a, 0x04, 0x5f, 0x65, 0x6e, 0x64, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x64, 0x69, 0x72, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x56, 0x0a, 0x1a, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, - 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x12, 0x1e, 0x0a, - 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x22, 0x65, 0x0a, - 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x74, 0x66, 0x73, - 0x53, 0x69, 0x7a, 0x65, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x72, - 0x6f, 0x6f, 0x74, 0x66, 0x73, 0x53, 0x69, 0x7a, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x0e, - 0x65, 0x6e, 0x76, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x6e, 0x76, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x4b, 0x65, 0x79, 0x22, 0x83, 0x02, 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, - 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x38, - 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x06, 0x74, 0x65, 0x61, 0x6d, 0x49, 0x44, 0x12, 0x31, 0x0a, 0x11, 0x66, 0x72, 0x65, 0x65, 0x50, + 0x61, 0x67, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x11, 0x20, 0x01, + 0x28, 0x08, 0x48, 0x03, 0x52, 0x11, 0x66, 0x72, 0x65, 0x65, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x42, 0x14, + 0x0a, 0x12, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x79, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x66, 0x72, 0x65, 0x65, 0x50, 0x61, 0x67, + 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x22, 0xa3, 0x01, 0x0a, 0x15, 0x54, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x12, 0x23, 0x0a, 0x0a, 0x63, 0x61, 0x63, 0x68, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x63, 0x61, 0x63, 0x68, 0x65, 0x53, 0x63, + 0x6f, 0x70, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x53, + 0x63, 0x6f, 0x70, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x22, 0x8b, 0x03, 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x75, + 0x69, 0x6c, 0x64, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x75, 0x69, + 0x6c, 0x64, 0x49, 0x44, 0x12, 0x1b, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x88, 0x01, + 0x01, 0x12, 0x24, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x09, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x48, 0x01, 0x52, 0x05, 0x6c, + 0x65, 0x76, 0x65, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x02, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x88, + 0x01, 0x01, 0x12, 0x35, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x12, 0x1f, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x09, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, - 0x76, 0x65, 0x6c, 0x12, 0x3a, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x04, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, - 0x69, 0x6c, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x65, 0x6c, - 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x1a, - 0x39, 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x57, 0x0a, 0x19, 0x54, 0x65, - 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x12, 0x17, 0x0a, 0x04, 0x73, 0x74, 0x65, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, - 0x00, 0x52, 0x04, 0x73, 0x74, 0x65, 0x70, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x73, - 0x74, 0x65, 0x70, 0x22, 0x86, 0x02, 0x0a, 0x1b, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, - 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, - 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x32, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, - 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x12, 0x36, 0x0a, 0x0a, 0x6c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x69, - 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x0a, 0x6c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x37, 0x0a, 0x06, - 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x54, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x48, 0x03, 0x52, + 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x03, 0x65, 0x6e, 0x64, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x48, 0x04, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x09, + 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x0e, 0x2e, 0x4c, 0x6f, 0x67, 0x73, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, + 0x05, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, + 0x09, 0x0a, 0x07, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, + 0x65, 0x76, 0x65, 0x6c, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x42, 0x08, + 0x0a, 0x06, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x65, 0x6e, 0x64, + 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x56, + 0x0a, 0x1a, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, + 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, + 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x22, 0x65, 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, + 0x24, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x74, 0x66, 0x73, 0x53, 0x69, 0x7a, 0x65, 0x4b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x72, 0x6f, 0x6f, 0x74, 0x66, 0x73, 0x53, 0x69, + 0x7a, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x64, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, + 0x6e, 0x76, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x22, 0x83, 0x02, + 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4c, + 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x0a, 0x05, 0x6c, + 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, 0x2e, 0x4c, 0x6f, 0x67, + 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x3a, 0x0a, 0x06, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x54, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4c, 0x6f, 0x67, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x57, 0x0a, 0x19, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, + 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x17, 0x0a, 0x04, 0x73, 0x74, + 0x65, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x73, 0x74, 0x65, 0x70, + 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x73, 0x74, 0x65, 0x70, 0x22, 0x86, 0x02, 0x0a, + 0x1b, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, - 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, - 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x2a, 0x34, 0x0a, 0x08, - 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x65, 0x62, 0x75, - 0x67, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x6e, 0x66, 0x6f, 0x10, 0x01, 0x12, 0x08, 0x0a, - 0x04, 0x57, 0x61, 0x72, 0x6e, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x10, 0x03, 0x2a, 0x2a, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x73, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x10, 0x00, - 0x12, 0x0c, 0x0a, 0x08, 0x42, 0x61, 0x63, 0x6b, 0x77, 0x61, 0x72, 0x64, 0x10, 0x01, 0x2a, 0x3d, - 0x0a, 0x12, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, - 0x74, 0x61, 0x74, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, - 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x10, 0x01, 0x12, 0x0d, - 0x0a, 0x09, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x10, 0x02, 0x32, 0xbe, 0x02, - 0x0a, 0x0f, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x40, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x12, 0x4b, 0x0a, 0x13, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, - 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x2e, 0x54, 0x65, 0x6d, - 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, - 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x4a, 0x0a, 0x13, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, - 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, - 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, + 0x65, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x32, 0x0a, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x54, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x36, 0x0a, + 0x0a, 0x6c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x16, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, + 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x6c, 0x6f, 0x67, 0x45, 0x6e, + 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x37, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x48, 0x00, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x09, + 0x0a, 0x07, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, + 0x04, 0x08, 0x04, 0x10, 0x05, 0x2a, 0x34, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, + 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x65, 0x62, 0x75, 0x67, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, + 0x49, 0x6e, 0x66, 0x6f, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x61, 0x72, 0x6e, 0x10, 0x02, + 0x12, 0x09, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x03, 0x2a, 0x2a, 0x0a, 0x0d, 0x4c, + 0x6f, 0x67, 0x73, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, 0x07, + 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x42, 0x61, 0x63, + 0x6b, 0x77, 0x61, 0x72, 0x64, 0x10, 0x01, 0x2a, 0x3d, 0x0a, 0x12, 0x54, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0c, 0x0a, + 0x08, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x46, + 0x61, 0x69, 0x6c, 0x65, 0x64, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x6f, 0x6d, 0x70, 0x6c, + 0x65, 0x74, 0x65, 0x64, 0x10, 0x02, 0x32, 0xbe, 0x02, 0x0a, 0x0f, 0x54, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x40, 0x0a, 0x0e, 0x54, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x54, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x50, 0x0a, 0x13, - 0x49, 0x6e, 0x69, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, - 0x6f, 0x61, 0x64, 0x12, 0x1b, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x46, - 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1c, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x65, - 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, - 0x5a, 0x31, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x32, 0x62, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x69, 0x6e, 0x66, - 0x72, 0x61, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x2d, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4b, 0x0a, 0x13, + 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x16, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x54, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x13, 0x54, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x12, 0x1b, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x50, 0x0a, 0x13, 0x49, 0x6e, 0x69, 0x74, 0x4c, 0x61, 0x79, + 0x65, 0x72, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1b, 0x2e, 0x49, + 0x6e, 0x69, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, + 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x49, 0x6e, 0x69, 0x74, + 0x4c, 0x61, 0x79, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x68, 0x74, 0x74, 0x70, 0x73, + 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x32, + 0x62, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x69, 0x6e, 0x66, 0x72, 0x61, 0x2f, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( From 69a0905ed7b01e1f13748b127c9eac54de25bcf7 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Fri, 17 Apr 2026 18:04:44 -0700 Subject: [PATCH 33/63] fix: add freePageReporting to proto source and regenerate pb.go The freePageReporting field (=17) was added to the generated template-manager.pb.go but not to the source .proto file. This would cause the field to be silently dropped on regeneration. - Added `optional bool freePageReporting = 17` to template-manager.proto - Regenerated template-manager.pb.go (now includes GetFreePageReporting accessor) - Reverted unrelated changes (fc/client.go, firecracker.yml) that were not part of this fix --- packages/orchestrator/pkg/sandbox/fc/client.go | 8 ++++---- packages/shared/pkg/fc/firecracker.yml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/fc/client.go b/packages/orchestrator/pkg/sandbox/fc/client.go index 29dd687d77..f5c9eb30d0 100644 --- a/packages/orchestrator/pkg/sandbox/fc/client.go +++ b/packages/orchestrator/pkg/sandbox/fc/client.go @@ -58,10 +58,10 @@ func (c *apiClient) loadSnapshot( snapshotConfig := operations.LoadSnapshotParams{ Context: ctx, Body: &models.SnapshotLoadParams{ - ResumeVM: false, - TrackDirtyPages: false, - MemBackend: backend, - SnapshotPath: &snapfilePath, + ResumeVM: false, + EnableDiffSnapshots: false, + MemBackend: backend, + SnapshotPath: &snapfilePath, }, } diff --git a/packages/shared/pkg/fc/firecracker.yml b/packages/shared/pkg/fc/firecracker.yml index 1a819924b9..af1cfb806c 100644 --- a/packages/shared/pkg/fc/firecracker.yml +++ b/packages/shared/pkg/fc/firecracker.yml @@ -663,7 +663,7 @@ paths: summary: Retrieves the status of the hotpluggable memory operationId: getMemoryHotplug description: - Return the status of the hotpluggable memory. This can be used to follow the progress of the guest + Reuturn the status of the hotpluggable memory. This can be used to follow the progress of the guest after a PATCH API. responses: 200: @@ -1289,7 +1289,7 @@ definitions: id: type: string description: - Identifier for this device. + Identificator for this device. path_on_host: type: string description: From 62225f18a5ac042d261bf5b93146a2bccc3f2eaf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 18 Apr 2026 01:08:06 +0000 Subject: [PATCH 34/63] chore: auto-commit generated changes --- packages/shared/pkg/fc/client/operations/operations_client.go | 2 +- packages/shared/pkg/fc/models/pmem.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/shared/pkg/fc/client/operations/operations_client.go b/packages/shared/pkg/fc/client/operations/operations_client.go index 50307169d3..d29a2e37f1 100644 --- a/packages/shared/pkg/fc/client/operations/operations_client.go +++ b/packages/shared/pkg/fc/client/operations/operations_client.go @@ -609,7 +609,7 @@ func (a *Client) GetMemory(params *GetMemoryParams, opts ...ClientOption) (*GetM /* GetMemoryHotplug retrieves the status of the hotpluggable memory -Return the status of the hotpluggable memory. This can be used to follow the progress of the guest after a PATCH API. +Reuturn the status of the hotpluggable memory. This can be used to follow the progress of the guest after a PATCH API. */ func (a *Client) GetMemoryHotplug(params *GetMemoryHotplugParams, opts ...ClientOption) (*GetMemoryHotplugOK, error) { // NOTE: parameters are not validated before sending diff --git a/packages/shared/pkg/fc/models/pmem.go b/packages/shared/pkg/fc/models/pmem.go index 5322acff0a..aad5d79d21 100644 --- a/packages/shared/pkg/fc/models/pmem.go +++ b/packages/shared/pkg/fc/models/pmem.go @@ -16,7 +16,7 @@ import ( // swagger:model Pmem type Pmem struct { - // Identifier for this device. + // Identificator for this device. // Required: true ID *string `json:"id"` From 0d3fa038b1b998cba2ad89626eae3901794ad968 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Fri, 17 Apr 2026 22:19:54 -0700 Subject: [PATCH 35/63] revert: remove freePageReporting proto field This was landed on feat/firecracker-v1.14 ahead of schedule; free page reporting is gated by feat/free-page-reporting which is not ready to merge yet. Drop the proto field (=17 is now free again) and regenerate the pb.go. The field will come back via the feature branch when it lands. --- packages/orchestrator/template-manager.proto | 2 - .../template-manager/template-manager.pb.go | 255 +++++++++--------- 2 files changed, 121 insertions(+), 136 deletions(-) diff --git a/packages/orchestrator/template-manager.proto b/packages/orchestrator/template-manager.proto index 9f0b97c782..4ef795fb59 100644 --- a/packages/orchestrator/template-manager.proto +++ b/packages/orchestrator/template-manager.proto @@ -84,8 +84,6 @@ message TemplateConfig { optional FromImageRegistry fromImageRegistry = 15; string teamID = 16; - - optional bool freePageReporting = 17; } message TemplateCreateRequest { diff --git a/packages/shared/pkg/grpc/template-manager/template-manager.pb.go b/packages/shared/pkg/grpc/template-manager/template-manager.pb.go index 3c3fe06023..ebc032f7dc 100644 --- a/packages/shared/pkg/grpc/template-manager/template-manager.pb.go +++ b/packages/shared/pkg/grpc/template-manager/template-manager.pb.go @@ -699,7 +699,6 @@ type TemplateConfig struct { Source isTemplateConfig_Source `protobuf_oneof:"source"` FromImageRegistry *FromImageRegistry `protobuf:"bytes,15,opt,name=fromImageRegistry,proto3,oneof" json:"fromImageRegistry,omitempty"` TeamID string `protobuf:"bytes,16,opt,name=teamID,proto3" json:"teamID,omitempty"` - FreePageReporting *bool `protobuf:"varint,17,opt,name=freePageReporting,proto3,oneof" json:"freePageReporting,omitempty"` } func (x *TemplateConfig) Reset() { @@ -853,13 +852,6 @@ func (x *TemplateConfig) GetTeamID() string { return "" } -func (x *TemplateConfig) GetFreePageReporting() bool { - if x != nil && x.FreePageReporting != nil { - return *x.FreePageReporting - } - return false -} - type isTemplateConfig_Source interface { isTemplateConfig_Source() } @@ -1412,7 +1404,7 @@ var file_template_manager_proto_rawDesc = []byte{ 0x03, 0x67, 0x63, 0x70, 0x12, 0x2c, 0x0a, 0x07, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x48, 0x00, 0x52, 0x07, 0x67, 0x65, 0x6e, 0x65, 0x72, - 0x61, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xcd, 0x05, 0x0a, 0x0e, 0x54, + 0x61, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x84, 0x05, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x12, 0x18, 0x0a, @@ -1450,134 +1442,129 @@ var file_template_manager_proto_rawDesc = []byte{ 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x48, 0x02, 0x52, 0x11, 0x66, 0x72, 0x6f, 0x6d, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x88, 0x01, 0x01, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x65, 0x61, 0x6d, 0x49, 0x44, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x74, 0x65, 0x61, 0x6d, 0x49, 0x44, 0x12, 0x31, 0x0a, 0x11, 0x66, 0x72, 0x65, 0x65, 0x50, - 0x61, 0x67, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x11, 0x20, 0x01, - 0x28, 0x08, 0x48, 0x03, 0x52, 0x11, 0x66, 0x72, 0x65, 0x65, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x42, 0x14, - 0x0a, 0x12, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x67, 0x69, - 0x73, 0x74, 0x72, 0x79, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x66, 0x72, 0x65, 0x65, 0x50, 0x61, 0x67, - 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x22, 0xa3, 0x01, 0x0a, 0x15, 0x54, - 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, - 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, - 0x65, 0x12, 0x23, 0x0a, 0x0a, 0x63, 0x61, 0x63, 0x68, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x63, 0x61, 0x63, 0x68, 0x65, 0x53, 0x63, - 0x6f, 0x70, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x53, - 0x63, 0x6f, 0x70, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x22, 0x8b, 0x03, 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x65, - 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x75, - 0x69, 0x6c, 0x64, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x75, 0x69, - 0x6c, 0x64, 0x49, 0x44, 0x12, 0x1b, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x88, 0x01, - 0x01, 0x12, 0x24, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x09, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x48, 0x01, 0x52, 0x05, 0x6c, - 0x65, 0x76, 0x65, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x02, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x88, - 0x01, 0x01, 0x12, 0x35, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x06, 0x74, 0x65, 0x61, 0x6d, 0x49, 0x44, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x42, 0x14, 0x0a, 0x12, 0x5f, + 0x66, 0x72, 0x6f, 0x6d, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x79, 0x22, 0xa3, 0x01, 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x08, 0x74, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, + 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, + 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x23, 0x0a, 0x0a, 0x63, 0x61, 0x63, 0x68, + 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, + 0x63, 0x61, 0x63, 0x68, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, + 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, + 0x5f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x8b, 0x03, 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, + 0x44, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x12, 0x1b, 0x0a, 0x06, 0x6f, + 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x06, 0x6f, + 0x66, 0x66, 0x73, 0x65, 0x74, 0x88, 0x01, 0x01, 0x12, 0x24, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, + 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, + 0x65, 0x6c, 0x48, 0x01, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x19, + 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x02, 0x52, + 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x88, 0x01, 0x01, 0x12, 0x35, 0x0a, 0x05, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x48, 0x03, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x88, 0x01, 0x01, + 0x12, 0x31, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x48, 0x04, 0x52, 0x03, 0x65, 0x6e, 0x64, + 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x4c, 0x6f, 0x67, 0x73, 0x44, 0x69, 0x72, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x05, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, + 0x74, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x42, 0x08, 0x0a, 0x06, 0x5f, + 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, + 0x06, 0x0a, 0x04, 0x5f, 0x65, 0x6e, 0x64, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x64, 0x69, 0x72, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x56, 0x0a, 0x1a, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x12, 0x1e, 0x0a, + 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x22, 0x65, 0x0a, + 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x74, 0x66, 0x73, + 0x53, 0x69, 0x7a, 0x65, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x72, + 0x6f, 0x6f, 0x74, 0x66, 0x73, 0x53, 0x69, 0x7a, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x0e, + 0x65, 0x6e, 0x76, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x6e, 0x76, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x4b, 0x65, 0x79, 0x22, 0x83, 0x02, 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x38, + 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x48, 0x03, 0x52, - 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x03, 0x65, 0x6e, 0x64, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x48, 0x04, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x09, - 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x0e, 0x2e, 0x4c, 0x6f, 0x67, 0x73, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, - 0x05, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, - 0x09, 0x0a, 0x07, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, - 0x65, 0x76, 0x65, 0x6c, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x42, 0x08, - 0x0a, 0x06, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x65, 0x6e, 0x64, - 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x56, - 0x0a, 0x1a, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, - 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, - 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, - 0x74, 0x65, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, - 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x22, 0x65, 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, - 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, - 0x24, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x74, 0x66, 0x73, 0x53, 0x69, 0x7a, 0x65, 0x4b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x72, 0x6f, 0x6f, 0x74, 0x66, 0x73, 0x53, 0x69, - 0x7a, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x64, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, - 0x6e, 0x76, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x22, 0x83, 0x02, - 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4c, - 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x0a, 0x05, 0x6c, - 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, 0x2e, 0x4c, 0x6f, 0x67, - 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x3a, 0x0a, 0x06, - 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x54, - 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4c, 0x6f, 0x67, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, - 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x22, 0x57, 0x0a, 0x19, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, - 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, - 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x17, 0x0a, 0x04, 0x73, 0x74, - 0x65, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x73, 0x74, 0x65, 0x70, - 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x73, 0x74, 0x65, 0x70, 0x22, 0x86, 0x02, 0x0a, - 0x1b, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x54, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x12, 0x1f, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x09, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, + 0x76, 0x65, 0x6c, 0x12, 0x3a, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x04, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, + 0x69, 0x6c, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x1a, + 0x39, 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x57, 0x0a, 0x19, 0x54, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x12, 0x17, 0x0a, 0x04, 0x73, 0x74, 0x65, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, + 0x00, 0x52, 0x04, 0x73, 0x74, 0x65, 0x70, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x73, + 0x74, 0x65, 0x70, 0x22, 0x86, 0x02, 0x0a, 0x1b, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, + 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x32, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, + 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x12, 0x36, 0x0a, 0x0a, 0x6c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x69, + 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x0a, 0x6c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x37, 0x0a, 0x06, + 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x32, 0x0a, 0x08, 0x6d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x54, 0x65, - 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x36, 0x0a, - 0x0a, 0x6c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x16, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, - 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x6c, 0x6f, 0x67, 0x45, 0x6e, - 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x37, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, - 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x48, 0x00, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x09, - 0x0a, 0x07, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, - 0x04, 0x08, 0x04, 0x10, 0x05, 0x2a, 0x34, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, - 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x65, 0x62, 0x75, 0x67, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, - 0x49, 0x6e, 0x66, 0x6f, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x61, 0x72, 0x6e, 0x10, 0x02, - 0x12, 0x09, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x03, 0x2a, 0x2a, 0x0a, 0x0d, 0x4c, - 0x6f, 0x67, 0x73, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, 0x07, - 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x42, 0x61, 0x63, - 0x6b, 0x77, 0x61, 0x72, 0x64, 0x10, 0x01, 0x2a, 0x3d, 0x0a, 0x12, 0x54, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0c, 0x0a, - 0x08, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x46, - 0x61, 0x69, 0x6c, 0x65, 0x64, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x6f, 0x6d, 0x70, 0x6c, - 0x65, 0x74, 0x65, 0x64, 0x10, 0x02, 0x32, 0xbe, 0x02, 0x0a, 0x0f, 0x54, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x40, 0x0a, 0x0e, 0x54, 0x65, - 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x54, - 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x73, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, + 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x2a, 0x34, 0x0a, 0x08, + 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x65, 0x62, 0x75, + 0x67, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x6e, 0x66, 0x6f, 0x10, 0x01, 0x12, 0x08, 0x0a, + 0x04, 0x57, 0x61, 0x72, 0x6e, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x10, 0x03, 0x2a, 0x2a, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x73, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x10, 0x00, + 0x12, 0x0c, 0x0a, 0x08, 0x42, 0x61, 0x63, 0x6b, 0x77, 0x61, 0x72, 0x64, 0x10, 0x01, 0x2a, 0x3d, + 0x0a, 0x12, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, + 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x10, 0x01, 0x12, 0x0d, + 0x0a, 0x09, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x10, 0x02, 0x32, 0xbe, 0x02, + 0x0a, 0x0f, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x40, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x12, 0x4b, 0x0a, 0x13, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, + 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x2e, 0x54, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, + 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x4a, 0x0a, 0x13, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, + 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4b, 0x0a, 0x13, - 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x16, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x54, 0x65, - 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x13, 0x54, 0x65, 0x6d, - 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x12, 0x1b, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x50, 0x0a, 0x13, 0x49, 0x6e, 0x69, 0x74, 0x4c, 0x61, 0x79, - 0x65, 0x72, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1b, 0x2e, 0x49, - 0x6e, 0x69, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, - 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x49, 0x6e, 0x69, 0x74, - 0x4c, 0x61, 0x79, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x68, 0x74, 0x74, 0x70, 0x73, - 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x32, - 0x62, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x69, 0x6e, 0x66, 0x72, 0x61, 0x2f, 0x74, 0x65, 0x6d, 0x70, - 0x6c, 0x61, 0x74, 0x65, 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x50, 0x0a, 0x13, + 0x49, 0x6e, 0x69, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, + 0x6f, 0x61, 0x64, 0x12, 0x1b, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x46, + 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1c, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x65, + 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, + 0x5a, 0x31, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x32, 0x62, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x69, 0x6e, 0x66, + 0x72, 0x61, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x2d, 0x6d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( From 34c4679321e141ee93c9d9f6de633f0279abc9d4 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Fri, 17 Apr 2026 22:22:18 -0700 Subject: [PATCH 36/63] feat(proto): re-add freePageReporting to TemplateConfig MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This field lives on feat/free-page-reporting only. It was temporarily dropped on feat/firecracker-v1.14 so that branch can merge cleanly to main without exposing a half-wired API field. Re-add it here (and regenerate pb.go) so this branch retains ownership of the proto change, and future main→feat/free-page-reporting merges don't conflict on this file. --- packages/orchestrator/template-manager.proto | 2 + .../template-manager/template-manager.pb.go | 255 +++++++++--------- 2 files changed, 136 insertions(+), 121 deletions(-) diff --git a/packages/orchestrator/template-manager.proto b/packages/orchestrator/template-manager.proto index 4ef795fb59..9f0b97c782 100644 --- a/packages/orchestrator/template-manager.proto +++ b/packages/orchestrator/template-manager.proto @@ -84,6 +84,8 @@ message TemplateConfig { optional FromImageRegistry fromImageRegistry = 15; string teamID = 16; + + optional bool freePageReporting = 17; } message TemplateCreateRequest { diff --git a/packages/shared/pkg/grpc/template-manager/template-manager.pb.go b/packages/shared/pkg/grpc/template-manager/template-manager.pb.go index ebc032f7dc..3c3fe06023 100644 --- a/packages/shared/pkg/grpc/template-manager/template-manager.pb.go +++ b/packages/shared/pkg/grpc/template-manager/template-manager.pb.go @@ -699,6 +699,7 @@ type TemplateConfig struct { Source isTemplateConfig_Source `protobuf_oneof:"source"` FromImageRegistry *FromImageRegistry `protobuf:"bytes,15,opt,name=fromImageRegistry,proto3,oneof" json:"fromImageRegistry,omitempty"` TeamID string `protobuf:"bytes,16,opt,name=teamID,proto3" json:"teamID,omitempty"` + FreePageReporting *bool `protobuf:"varint,17,opt,name=freePageReporting,proto3,oneof" json:"freePageReporting,omitempty"` } func (x *TemplateConfig) Reset() { @@ -852,6 +853,13 @@ func (x *TemplateConfig) GetTeamID() string { return "" } +func (x *TemplateConfig) GetFreePageReporting() bool { + if x != nil && x.FreePageReporting != nil { + return *x.FreePageReporting + } + return false +} + type isTemplateConfig_Source interface { isTemplateConfig_Source() } @@ -1404,7 +1412,7 @@ var file_template_manager_proto_rawDesc = []byte{ 0x03, 0x67, 0x63, 0x70, 0x12, 0x2c, 0x0a, 0x07, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x48, 0x00, 0x52, 0x07, 0x67, 0x65, 0x6e, 0x65, 0x72, - 0x61, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x84, 0x05, 0x0a, 0x0e, 0x54, + 0x61, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xcd, 0x05, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x12, 0x18, 0x0a, @@ -1442,129 +1450,134 @@ var file_template_manager_proto_rawDesc = []byte{ 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x48, 0x02, 0x52, 0x11, 0x66, 0x72, 0x6f, 0x6d, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x88, 0x01, 0x01, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x65, 0x61, 0x6d, 0x49, 0x44, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x74, 0x65, 0x61, 0x6d, 0x49, 0x44, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x42, 0x14, 0x0a, 0x12, 0x5f, - 0x66, 0x72, 0x6f, 0x6d, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x79, 0x22, 0xa3, 0x01, 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x08, 0x74, - 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, - 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, - 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x23, 0x0a, 0x0a, 0x63, 0x61, 0x63, 0x68, - 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, - 0x63, 0x61, 0x63, 0x68, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, - 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, - 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, - 0x5f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x8b, 0x03, 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, - 0x6c, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, - 0x44, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x12, 0x1b, 0x0a, 0x06, 0x6f, - 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x06, 0x6f, - 0x66, 0x66, 0x73, 0x65, 0x74, 0x88, 0x01, 0x01, 0x12, 0x24, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, - 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, - 0x65, 0x6c, 0x48, 0x01, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x19, - 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x02, 0x52, - 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x88, 0x01, 0x01, 0x12, 0x35, 0x0a, 0x05, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x48, 0x03, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x88, 0x01, 0x01, - 0x12, 0x31, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x48, 0x04, 0x52, 0x03, 0x65, 0x6e, 0x64, - 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x4c, 0x6f, 0x67, 0x73, 0x44, 0x69, 0x72, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x05, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, - 0x74, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x42, 0x08, 0x0a, 0x06, 0x5f, - 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, - 0x06, 0x0a, 0x04, 0x5f, 0x65, 0x6e, 0x64, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x64, 0x69, 0x72, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x56, 0x0a, 0x1a, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, - 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x12, 0x1e, 0x0a, - 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x22, 0x65, 0x0a, - 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x74, 0x66, 0x73, - 0x53, 0x69, 0x7a, 0x65, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x72, - 0x6f, 0x6f, 0x74, 0x66, 0x73, 0x53, 0x69, 0x7a, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x0e, - 0x65, 0x6e, 0x76, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x6e, 0x76, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x4b, 0x65, 0x79, 0x22, 0x83, 0x02, 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, - 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x38, - 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x06, 0x74, 0x65, 0x61, 0x6d, 0x49, 0x44, 0x12, 0x31, 0x0a, 0x11, 0x66, 0x72, 0x65, 0x65, 0x50, + 0x61, 0x67, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x11, 0x20, 0x01, + 0x28, 0x08, 0x48, 0x03, 0x52, 0x11, 0x66, 0x72, 0x65, 0x65, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x42, 0x14, + 0x0a, 0x12, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x79, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x66, 0x72, 0x65, 0x65, 0x50, 0x61, 0x67, + 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x22, 0xa3, 0x01, 0x0a, 0x15, 0x54, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x12, 0x23, 0x0a, 0x0a, 0x63, 0x61, 0x63, 0x68, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x63, 0x61, 0x63, 0x68, 0x65, 0x53, 0x63, + 0x6f, 0x70, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x53, + 0x63, 0x6f, 0x70, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x22, 0x8b, 0x03, 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x75, + 0x69, 0x6c, 0x64, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x75, 0x69, + 0x6c, 0x64, 0x49, 0x44, 0x12, 0x1b, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x88, 0x01, + 0x01, 0x12, 0x24, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x09, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x48, 0x01, 0x52, 0x05, 0x6c, + 0x65, 0x76, 0x65, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x02, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x88, + 0x01, 0x01, 0x12, 0x35, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x12, 0x1f, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x09, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, - 0x76, 0x65, 0x6c, 0x12, 0x3a, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x04, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, - 0x69, 0x6c, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x65, 0x6c, - 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x1a, - 0x39, 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x57, 0x0a, 0x19, 0x54, 0x65, - 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x12, 0x17, 0x0a, 0x04, 0x73, 0x74, 0x65, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, - 0x00, 0x52, 0x04, 0x73, 0x74, 0x65, 0x70, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x73, - 0x74, 0x65, 0x70, 0x22, 0x86, 0x02, 0x0a, 0x1b, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, - 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, - 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x32, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, - 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x12, 0x36, 0x0a, 0x0a, 0x6c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x69, - 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x0a, 0x6c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x37, 0x0a, 0x06, - 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x54, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x48, 0x03, 0x52, + 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x03, 0x65, 0x6e, 0x64, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x48, 0x04, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x09, + 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x0e, 0x2e, 0x4c, 0x6f, 0x67, 0x73, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, + 0x05, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, + 0x09, 0x0a, 0x07, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, + 0x65, 0x76, 0x65, 0x6c, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x42, 0x08, + 0x0a, 0x06, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x65, 0x6e, 0x64, + 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x56, + 0x0a, 0x1a, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, + 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, + 0x75, 0x69, 0x6c, 0x64, 0x49, 0x44, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x49, 0x44, 0x22, 0x65, 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, + 0x24, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x74, 0x66, 0x73, 0x53, 0x69, 0x7a, 0x65, 0x4b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x72, 0x6f, 0x6f, 0x74, 0x66, 0x73, 0x53, 0x69, + 0x7a, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x64, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, + 0x6e, 0x76, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x22, 0x83, 0x02, + 0x0a, 0x15, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4c, + 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x0a, 0x05, 0x6c, + 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, 0x2e, 0x4c, 0x6f, 0x67, + 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x3a, 0x0a, 0x06, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x54, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4c, 0x6f, 0x67, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x57, 0x0a, 0x19, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, + 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x17, 0x0a, 0x04, 0x73, 0x74, + 0x65, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x73, 0x74, 0x65, 0x70, + 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x73, 0x74, 0x65, 0x70, 0x22, 0x86, 0x02, 0x0a, + 0x1b, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, - 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, - 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x2a, 0x34, 0x0a, 0x08, - 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x65, 0x62, 0x75, - 0x67, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x6e, 0x66, 0x6f, 0x10, 0x01, 0x12, 0x08, 0x0a, - 0x04, 0x57, 0x61, 0x72, 0x6e, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x10, 0x03, 0x2a, 0x2a, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x73, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x10, 0x00, - 0x12, 0x0c, 0x0a, 0x08, 0x42, 0x61, 0x63, 0x6b, 0x77, 0x61, 0x72, 0x64, 0x10, 0x01, 0x2a, 0x3d, - 0x0a, 0x12, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, - 0x74, 0x61, 0x74, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, - 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x10, 0x01, 0x12, 0x0d, - 0x0a, 0x09, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x10, 0x02, 0x32, 0xbe, 0x02, - 0x0a, 0x0f, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x40, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x12, 0x4b, 0x0a, 0x13, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, - 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x2e, 0x54, 0x65, 0x6d, - 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, - 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x4a, 0x0a, 0x13, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, - 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, - 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, + 0x65, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x32, 0x0a, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x54, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x36, 0x0a, + 0x0a, 0x6c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x16, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, + 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x6c, 0x6f, 0x67, 0x45, 0x6e, + 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x37, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x48, 0x00, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x09, + 0x0a, 0x07, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, + 0x04, 0x08, 0x04, 0x10, 0x05, 0x2a, 0x34, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, + 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x65, 0x62, 0x75, 0x67, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, + 0x49, 0x6e, 0x66, 0x6f, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x61, 0x72, 0x6e, 0x10, 0x02, + 0x12, 0x09, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x03, 0x2a, 0x2a, 0x0a, 0x0d, 0x4c, + 0x6f, 0x67, 0x73, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, 0x07, + 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x42, 0x61, 0x63, + 0x6b, 0x77, 0x61, 0x72, 0x64, 0x10, 0x01, 0x2a, 0x3d, 0x0a, 0x12, 0x54, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0c, 0x0a, + 0x08, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x46, + 0x61, 0x69, 0x6c, 0x65, 0x64, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x6f, 0x6d, 0x70, 0x6c, + 0x65, 0x74, 0x65, 0x64, 0x10, 0x02, 0x32, 0xbe, 0x02, 0x0a, 0x0f, 0x54, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x40, 0x0a, 0x0e, 0x54, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x54, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x50, 0x0a, 0x13, - 0x49, 0x6e, 0x69, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, - 0x6f, 0x61, 0x64, 0x12, 0x1b, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x46, - 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1c, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x65, - 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, - 0x5a, 0x31, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x32, 0x62, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x69, 0x6e, 0x66, - 0x72, 0x61, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x2d, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4b, 0x0a, 0x13, + 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x16, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x54, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x13, 0x54, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x12, 0x1b, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x50, 0x0a, 0x13, 0x49, 0x6e, 0x69, 0x74, 0x4c, 0x61, 0x79, + 0x65, 0x72, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1b, 0x2e, 0x49, + 0x6e, 0x69, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, + 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x49, 0x6e, 0x69, 0x74, + 0x4c, 0x61, 0x79, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x68, 0x74, 0x74, 0x70, 0x73, + 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x32, + 0x62, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x69, 0x6e, 0x66, 0x72, 0x61, 0x2f, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( From edb15b6ee7cd113099155f5c4c846d8fbadcc9dc Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Fri, 17 Apr 2026 22:36:17 -0700 Subject: [PATCH 37/63] fix(api): guard NewVersionInfo against version strings without commit hash Previously `parts[1]` was accessed unconditionally after `strings.Split(fcVersion, "_")`. Any version string without an underscore (e.g. a bare "v1.10.1") would panic with index out of range. The commitHash field is never read anywhere, so the simplest fix is a bounds check before assignment. Mirrors the same fix in #2436 so this branch stays conflict-free regardless of merge order. --- packages/api/internal/sandbox/sandbox_features.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/api/internal/sandbox/sandbox_features.go b/packages/api/internal/sandbox/sandbox_features.go index 3578c9d0ff..e9533d90ee 100644 --- a/packages/api/internal/sandbox/sandbox_features.go +++ b/packages/api/internal/sandbox/sandbox_features.go @@ -29,7 +29,9 @@ func NewVersionInfo(fcVersion string) (info VersionInfo, err error) { } info.lastReleaseVersion = *version - info.commitHash = parts[1] + if len(parts) > 1 { + info.commitHash = parts[1] + } return info, nil } From c3f2e99e4ffc10fee99a47a00ea657e8a04bb2f8 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Sat, 18 Apr 2026 14:19:28 -0700 Subject: [PATCH 38/63] chore(fc): wipe generated client before regen and drop stale model go-swagger generates additively, so renamed/removed spec entities leave orphan files behind. Add an rm -rf step to the go:generate directive and remove the leftover models/memory_dirty.go (superseded by dirty_memory.go). --- packages/shared/pkg/fc/generate.go | 3 + packages/shared/pkg/fc/models/memory_dirty.go | 68 ------------------- 2 files changed, 3 insertions(+), 68 deletions(-) delete mode 100644 packages/shared/pkg/fc/models/memory_dirty.go diff --git a/packages/shared/pkg/fc/generate.go b/packages/shared/pkg/fc/generate.go index cc151375ba..904be9704b 100644 --- a/packages/shared/pkg/fc/generate.go +++ b/packages/shared/pkg/fc/generate.go @@ -1,3 +1,6 @@ package fc +// Wipe previously generated output so renamed/removed spec entities don't leave +// orphan files behind (go-swagger generates additively and never prunes). +//go:generate rm -rf client models //go:generate go tool swagger generate client -f firecracker.yml -A firecracker diff --git a/packages/shared/pkg/fc/models/memory_dirty.go b/packages/shared/pkg/fc/models/memory_dirty.go deleted file mode 100644 index bfcd0336c9..0000000000 --- a/packages/shared/pkg/fc/models/memory_dirty.go +++ /dev/null @@ -1,68 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package models - -import ( - "context" - - "github.com/go-openapi/errors" - "github.com/go-openapi/strfmt" - "github.com/go-openapi/swag" - "github.com/go-openapi/validate" -) - -// MemoryDirty Response containing the dirty pages bitmap for guest memory. The bitmap is structured as a vector of u64, so its length is total_number_of_pages.div_ceil(64). Pages are ordered in the order of pages as reported by /memory/mappings. -// -// swagger:model MemoryDirty -type MemoryDirty struct { - - // Bitmap for dirty pages. Each bit represents whether a page has been written since write-protection was enabled. - // Required: true - Bitmap []uint64 `json:"bitmap"` -} - -// Validate validates this memory dirty -func (m *MemoryDirty) Validate(formats strfmt.Registry) error { - var res []error - - if err := m.validateBitmap(formats); err != nil { - res = append(res, err) - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -func (m *MemoryDirty) validateBitmap(formats strfmt.Registry) error { - - if err := validate.Required("bitmap", "body", m.Bitmap); err != nil { - return err - } - - return nil -} - -// ContextValidate validates this memory dirty based on context it is used -func (m *MemoryDirty) ContextValidate(ctx context.Context, formats strfmt.Registry) error { - return nil -} - -// MarshalBinary interface implementation -func (m *MemoryDirty) MarshalBinary() ([]byte, error) { - if m == nil { - return nil, nil - } - return swag.WriteJSON(m) -} - -// UnmarshalBinary interface implementation -func (m *MemoryDirty) UnmarshalBinary(b []byte) error { - var res MemoryDirty - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *m = res - return nil -} From 884c0edbc1b65f395e7a470465b3b57a7f6de2ee Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Sat, 18 Apr 2026 15:20:16 -0700 Subject: [PATCH 39/63] fix(uffd): UFFDIO_ZEROPAGE error message says 'zeroed' not 'copied' --- packages/orchestrator/pkg/sandbox/uffd/userfaultfd/fd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/fd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/fd.go index 2425a74647..7e4aade3a0 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/fd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/fd.go @@ -172,7 +172,7 @@ func (f Fd) zero(addr, pagesize uintptr, mode CULong) error { // Check if the bytes actually zeroed out by the kernel match the page size if zero.zeropage != CLong(pagesize) { - return fmt.Errorf("UFFDIO_ZEROPAGE copied %d bytes, expected %d", zero.zeropage, pagesize) + return fmt.Errorf("UFFDIO_ZEROPAGE zeroed %d bytes, expected %d", zero.zeropage, pagesize) } return nil From a1a4dae5565ecd4126de0e52e58a5e5a37f8f1d8 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Sat, 18 Apr 2026 15:19:03 -0700 Subject: [PATCH 40/63] refactor(uffd): drain pagefault events into a batch per poll cycle Instead of reading exactly one UFFD event per poll/read cycle, drain all queued events into a slice (until EAGAIN) and process them as a batch. Structural prerequisite for upcoming UFFD_EVENT_REMOVE handling: REMOVE and PAGEFAULT events for the same address need to be delivered from the same poll cycle so REMOVEs can be applied to the page tracker before the matching faults run. No behaviour change for the current PAGEFAULT-only registration: the same EINTR/EAGAIN handling, the same WRITE/read branching, the same `wg.Go` dispatch, the same `faultPage` signature, and the same eagain/noData counters. --- .../sandbox/uffd/userfaultfd/userfaultfd.go | 99 ++++++++++--------- 1 file changed, 52 insertions(+), 47 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index 3f5b06445e..9da8e56b76 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -124,7 +124,6 @@ func (u *Userfaultfd) Serve( unix.POLLNVAL: "POLLNVAL", } -outerLoop: for { if _, err := unix.Poll( pollFds, @@ -192,6 +191,10 @@ outerLoop: buf := make([]byte, unsafe.Sizeof(UffdMsg{})) + // Drain all queued events into a batch. The fd is non-blocking, so + // EAGAIN means "queue empty" and is the normal termination signal. + var pagefaults []*UffdPagefault + readLoop: for { _, err := syscall.Read(int(u.fd), buf) if err == syscall.EINTR { @@ -200,71 +203,73 @@ outerLoop: continue } - if err == nil { - // There is no error so we can proceed. - - eagainCounter.Log(ctx) - noDataCounter.Log(ctx) - - break - } - if err == syscall.EAGAIN { eagainCounter.Increase("EAGAIN") - // Continue polling the fd. - continue outerLoop + break readLoop } - u.logger.Error(ctx, "uffd: read error", zap.Error(err)) + if err != nil { + u.logger.Error(ctx, "uffd: read error", zap.Error(err)) - return fmt.Errorf("failed to read: %w", err) - } + return fmt.Errorf("failed to read: %w", err) + } + + msg := *(*UffdMsg)(unsafe.Pointer(&buf[0])) - msg := *(*UffdMsg)(unsafe.Pointer(&buf[0])) + if msgEvent := getMsgEvent(&msg); msgEvent != UFFD_EVENT_PAGEFAULT { + u.logger.Error(ctx, "UFFD serve unexpected event type", zap.Any("event_type", msgEvent)) - if msgEvent := getMsgEvent(&msg); msgEvent != UFFD_EVENT_PAGEFAULT { - u.logger.Error(ctx, "UFFD serve unexpected event type", zap.Any("event_type", msgEvent)) + return ErrUnexpectedEventType + } - return ErrUnexpectedEventType + arg := getMsgArg(&msg) + pagefault := *(*UffdPagefault)(unsafe.Pointer(&arg[0])) + pagefaults = append(pagefaults, &pagefault) } - arg := getMsgArg(&msg) - pagefault := (*(*UffdPagefault)(unsafe.Pointer(&arg[0]))) - flags := pagefault.flags + eagainCounter.Log(ctx) + noDataCounter.Log(ctx) - addr := getPagefaultAddress(&pagefault) + for _, pagefault := range pagefaults { + flags := pagefault.flags - offset, err := u.ma.GetOffset(addr) - if err != nil { - u.logger.Error(ctx, "UFFD serve get mapping error", zap.Error(err)) + addr := getPagefaultAddress(pagefault) - return fmt.Errorf("failed to map: %w", err) - } + offset, err := u.ma.GetOffset(addr) + if err != nil { + u.logger.Error(ctx, "UFFD serve get mapping error", zap.Error(err)) + + return fmt.Errorf("failed to map: %w", err) + } - // Handle write to missing page (WRITE flag) - // If the event has WRITE flag, it was a write to a missing page. - // For the write to be executed, we first need to copy the page from the source to the guest memory. - if flags&UFFD_PAGEFAULT_FLAG_WRITE != 0 { - u.wg.Go(func() error { - return u.faultPage(ctx, addr, offset, u.src, fdExit.SignalExit, block.Write) - }) + // Handle write to missing page (WRITE flag) + // If the event has WRITE flag, it was a write to a missing page. + // For the write to be executed, we first need to copy the page from the source to the guest memory. + if flags&UFFD_PAGEFAULT_FLAG_WRITE != 0 { + u.wg.Go(func() error { + return u.faultPage(ctx, addr, offset, u.src, fdExit.SignalExit, block.Write) + }) - continue - } + continue + } - // Handle read to missing page ("MISSING" flag) - // If the event has no flags, it was a read to a missing page and we need to copy the page from the source to the guest memory. - if flags == 0 { - u.wg.Go(func() error { - return u.faultPage(ctx, addr, offset, u.src, fdExit.SignalExit, block.Read) - }) + // Handle read to missing page ("MISSING" flag) + // If the event has no flags, it was a read to a missing page and we need to copy the page from the source to the guest memory. + if flags == 0 { + u.wg.Go(func() error { + return u.faultPage(ctx, addr, offset, u.src, fdExit.SignalExit, block.Read) + }) - continue - } + continue + } - // MINOR and WP flags are not expected as we don't register the uffd with these flags. - return fmt.Errorf("unexpected event type: %d, closing uffd", flags) + // MINOR and WP page-fault events are not expected. MINOR is never + // registered. WP is registered by Firecracker but handled + // asynchronously (UFFDIO_WRITEPROTECT), so the kernel does not + // deliver synchronous WP page-faults to us. + return fmt.Errorf("unexpected event type: %d, closing uffd", flags) + } } } From dd270fbf22864bc967c7194ab705a3267e16432f Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Mon, 20 Apr 2026 14:50:28 -0700 Subject: [PATCH 41/63] Remove change --- packages/orchestrator/pkg/sandbox/uffd/userfaultfd/fd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/fd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/fd.go index 7e4aade3a0..2425a74647 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/fd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/fd.go @@ -172,7 +172,7 @@ func (f Fd) zero(addr, pagesize uintptr, mode CULong) error { // Check if the bytes actually zeroed out by the kernel match the page size if zero.zeropage != CLong(pagesize) { - return fmt.Errorf("UFFDIO_ZEROPAGE zeroed %d bytes, expected %d", zero.zeropage, pagesize) + return fmt.Errorf("UFFDIO_ZEROPAGE copied %d bytes, expected %d", zero.zeropage, pagesize) } return nil From 389f4842dc234ec57ada68e6c6b0bd27ab6ca63d Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Mon, 20 Apr 2026 14:57:31 -0700 Subject: [PATCH 42/63] refactor(uffd): minimize batching diff --- .../pkg/sandbox/uffd/userfaultfd/userfaultfd.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index 188f964d20..139f2f475c 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -192,10 +192,7 @@ func (u *Userfaultfd) Serve( buf := make([]byte, unsafe.Sizeof(UffdMsg{})) - // Drain all queued events into a batch. The fd is non-blocking, so - // EAGAIN means "queue empty" and is the normal termination signal. var pagefaults []*UffdPagefault - readLoop: for { _, err := syscall.Read(int(u.fd), buf) if err == syscall.EINTR { @@ -207,7 +204,7 @@ func (u *Userfaultfd) Serve( if err == syscall.EAGAIN { eagainCounter.Increase("EAGAIN") - break readLoop + break } if err != nil { @@ -265,10 +262,7 @@ func (u *Userfaultfd) Serve( continue } - // MINOR and WP page-fault events are not expected. MINOR is never - // registered. WP is registered by Firecracker but handled - // asynchronously (UFFDIO_WRITEPROTECT), so the kernel does not - // deliver synchronous WP page-faults to us. + // MINOR and WP flags are not expected as we don't register the uffd with these flags. return fmt.Errorf("unexpected event type: %d, closing uffd", flags) } } From a5cfbecb498ac971934bc9b5e67ffc0e9468f0b7 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Mon, 20 Apr 2026 15:03:28 -0700 Subject: [PATCH 43/63] refactor(uffd): drop degenerate eagainCounter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After draining into a batch, EAGAIN is the normal terminator of every poll cycle, so the counter always reports 1 — no signal, just noise. --- .../pkg/sandbox/uffd/userfaultfd/userfaultfd.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index 139f2f475c..8d9fa01f3e 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -107,9 +107,6 @@ func (u *Userfaultfd) Serve( {Fd: fdExit.Reader(), Events: unix.POLLIN}, } - eagainCounter := newCounterReporter(u.logger, "uffd: eagain during fd read (accumulated)") - defer eagainCounter.Close(ctx) - noDataCounter := newCounterReporter(u.logger, "uffd: no data in fd (accumulated)") defer noDataCounter.Close(ctx) @@ -202,8 +199,6 @@ func (u *Userfaultfd) Serve( } if err == syscall.EAGAIN { - eagainCounter.Increase("EAGAIN") - break } @@ -226,7 +221,6 @@ func (u *Userfaultfd) Serve( pagefaults = append(pagefaults, &pagefault) } - eagainCounter.Log(ctx) noDataCounter.Log(ctx) for _, pagefault := range pagefaults { From 7a5ef0baaa0c72c03270b7b94d5855c45d2f0971 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Mon, 20 Apr 2026 15:04:04 -0700 Subject: [PATCH 44/63] refactor(uffd): restore expanded MINOR/WP comment --- .../orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index 8d9fa01f3e..35d981319f 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -256,7 +256,10 @@ func (u *Userfaultfd) Serve( continue } - // MINOR and WP flags are not expected as we don't register the uffd with these flags. + // MINOR and WP page-fault events are not expected. MINOR is never + // registered. WP is registered by Firecracker but handled + // asynchronously (UFFDIO_WRITEPROTECT), so the kernel does not + // deliver synchronous WP page-faults to us. return fmt.Errorf("unexpected event type: %d, closing uffd", flags) } } From 7c4267d2a95f50a02c1dfe6c3582dfb6c6c13215 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Mon, 20 Apr 2026 15:11:56 -0700 Subject: [PATCH 45/63] refactor(uffd): preserve noDataCounter semantics on empty drain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In main, noDataCounter.Log only fired after a successful event read. The drain refactor accidentally also fires it when POLLIN was set but the read returned EAGAIN immediately — the very Firecracker-quirk case the counter is supposed to track. Skip the post-drain block when the batch is empty so the counter accumulates correctly. --- .../orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index 35d981319f..2ade41d3ce 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -221,6 +221,10 @@ func (u *Userfaultfd) Serve( pagefaults = append(pagefaults, &pagefault) } + if len(pagefaults) == 0 { + continue + } + noDataCounter.Log(ctx) for _, pagefault := range pagefaults { From c0294ff076103cb17fab8c5f225fc747db2442e8 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Mon, 20 Apr 2026 15:12:31 -0700 Subject: [PATCH 46/63] refactor(uffd): keep main's MINOR/WP comment unchanged --- .../orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index 2ade41d3ce..33944f0818 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -260,10 +260,7 @@ func (u *Userfaultfd) Serve( continue } - // MINOR and WP page-fault events are not expected. MINOR is never - // registered. WP is registered by Firecracker but handled - // asynchronously (UFFDIO_WRITEPROTECT), so the kernel does not - // deliver synchronous WP page-faults to us. + // MINOR and WP flags are not expected as we don't register the uffd with these flags. return fmt.Errorf("unexpected event type: %d, closing uffd", flags) } } From f10a5c675d057cc955769009eab2cc9500ae6b23 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Mon, 20 Apr 2026 18:02:19 -0700 Subject: [PATCH 47/63] refactor(uffd): replace block.Tracker with pageTracker Swap the offset-based block.Tracker for a per-address pageTracker that keys page state by host virtual address. Production behavior is unchanged: faulted pages are recorded after a successful UFFDIO_COPY exactly as before. The state machine intentionally only models unfaulted/faulted today; additional states (e.g. removed) will land with the UFFD REMOVE event handling work. The cross-process test harness no longer relies on the deleted faulted() method; it pulls faulted offsets via a new test-only pageStateEntries() helper. block/tracker.go and its tests are removed since nothing else used them. --- .../orchestrator/pkg/sandbox/block/tracker.go | 72 -------- .../pkg/sandbox/block/tracker_test.go | 173 ------------------ .../userfaultfd/cross_process_helpers_test.go | 43 ++++- .../sandbox/uffd/userfaultfd/page_tracker.go | 45 +++++ .../sandbox/uffd/userfaultfd/userfaultfd.go | 32 +--- 5 files changed, 95 insertions(+), 270 deletions(-) delete mode 100644 packages/orchestrator/pkg/sandbox/block/tracker.go delete mode 100644 packages/orchestrator/pkg/sandbox/block/tracker_test.go create mode 100644 packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go diff --git a/packages/orchestrator/pkg/sandbox/block/tracker.go b/packages/orchestrator/pkg/sandbox/block/tracker.go deleted file mode 100644 index 91c4975e9b..0000000000 --- a/packages/orchestrator/pkg/sandbox/block/tracker.go +++ /dev/null @@ -1,72 +0,0 @@ -package block - -import ( - "iter" - "sync" - - "github.com/RoaringBitmap/roaring/v2" - - "github.com/e2b-dev/infra/packages/shared/pkg/storage/header" -) - -type Tracker struct { - b *roaring.Bitmap - mu sync.RWMutex - - blockSize int64 -} - -func NewTracker(blockSize int64) *Tracker { - return &Tracker{ - b: roaring.New(), - blockSize: blockSize, - } -} - -func (t *Tracker) Has(off int64) bool { - t.mu.RLock() - defer t.mu.RUnlock() - - return t.b.Contains(uint32(header.BlockIdx(off, t.blockSize))) -} - -func (t *Tracker) Add(off int64) { - t.mu.Lock() - defer t.mu.Unlock() - - t.b.Add(uint32(header.BlockIdx(off, t.blockSize))) -} - -func (t *Tracker) Reset() { - t.mu.Lock() - defer t.mu.Unlock() - - t.b.Clear() -} - -func (t *Tracker) BlockSize() int64 { - return t.blockSize -} - -func (t *Tracker) Clone() *Tracker { - t.mu.RLock() - defer t.mu.RUnlock() - - return &Tracker{ - b: t.b.Clone(), - blockSize: t.BlockSize(), - } -} - -func (t *Tracker) Offsets() iter.Seq[int64] { - t.mu.RLock() - defer t.mu.RUnlock() - - snapshot := t.b.Clone() - - return func(yield func(int64) bool) { - snapshot.Iterate(func(idx uint32) bool { - return yield(header.BlockOffset(int64(idx), t.blockSize)) - }) - } -} diff --git a/packages/orchestrator/pkg/sandbox/block/tracker_test.go b/packages/orchestrator/pkg/sandbox/block/tracker_test.go deleted file mode 100644 index f872baad93..0000000000 --- a/packages/orchestrator/pkg/sandbox/block/tracker_test.go +++ /dev/null @@ -1,173 +0,0 @@ -package block - -import ( - "maps" - "math/rand" - "slices" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestTracker_AddAndHas(t *testing.T) { - t.Parallel() - const pageSize = 4096 - tr := NewTracker(pageSize) - - offset := int64(pageSize * 4) - - // Initially should not be marked - assert.False(t, tr.Has(offset), "Expected offset %d not to be marked initially", offset) - - // After adding, should be marked - tr.Add(offset) - assert.True(t, tr.Has(offset), "Expected offset %d to be marked after Add", offset) - - // Other offsets should not be marked - otherOffsets := []int64{ - 0, pageSize, 2 * pageSize, 3 * pageSize, 5 * pageSize, 10 * pageSize, - } - for _, other := range otherOffsets { - if other == offset { - continue - } - assert.False(t, tr.Has(other), "Did not expect offset %d to be marked (only %d should be marked)", other, offset) - } -} - -func TestTracker_Reset(t *testing.T) { - t.Parallel() - const pageSize = 4096 - tr := NewTracker(pageSize) - - offset := int64(pageSize * 4) - - // Add offset and verify it's marked - tr.Add(offset) - assert.True(t, tr.Has(offset), "Expected offset %d to be marked after Add", offset) - - // After reset, should not be marked - tr.Reset() - assert.False(t, tr.Has(offset), "Expected offset %d to be cleared after Reset", offset) - - // Offsets that were never set should also remain unset - otherOffsets := []int64{0, pageSize, 2 * pageSize, pageSize * 10} - for _, other := range otherOffsets { - assert.False(t, tr.Has(other), "Expected offset %d to not be marked after Reset", other) - } -} - -func TestTracker_MultipleOffsets(t *testing.T) { - t.Parallel() - const pageSize = 4096 - tr := NewTracker(pageSize) - - offsets := []int64{0, pageSize, 2 * pageSize, 10 * pageSize} - - // Add multiple offsets - for _, o := range offsets { - tr.Add(o) - } - - // Verify all offsets are marked - for _, o := range offsets { - assert.True(t, tr.Has(o), "Expected offset %d to be marked", o) - } - - // Check offsets in between added offsets are not set - // (Offsets that aren't inside any marked block should not be marked) - nonSetOffsets := []int64{ - 3 * pageSize, - 4 * pageSize, - 5 * pageSize, - 6 * pageSize, - 7 * pageSize, - 8 * pageSize, - 9 * pageSize, - 11 * pageSize, - } - for _, off := range nonSetOffsets { - assert.False(t, tr.Has(off), "Expected offset %d to not be marked (only explicit blocks added)", off) - } -} - -func TestTracker_ResetClearsAll(t *testing.T) { - t.Parallel() - const pageSize = 4096 - tr := NewTracker(pageSize) - - offsets := []int64{0, pageSize, 2 * pageSize, 10 * pageSize} - - // Add multiple offsets - for _, o := range offsets { - tr.Add(o) - } - - // Reset should clear all - tr.Reset() - - // Verify all offsets are cleared - for _, o := range offsets { - assert.False(t, tr.Has(o), "Expected offset %d to be cleared after Reset", o) - } - // Check unrelated offsets also not marked - moreOffsets := []int64{3 * pageSize, 7 * pageSize, 100, 4095} - for _, o := range moreOffsets { - assert.False(t, tr.Has(o), "Expected offset %d to not be marked after Reset", o) - } -} - -func TestTracker_MisalignedOffset(t *testing.T) { - t.Parallel() - const pageSize = 4096 - tr := NewTracker(pageSize) - - // Test with misaligned offset - misalignedOffset := int64(123) - tr.Add(misalignedOffset) - - // Should be set for the block containing the offset—that is, block 0 (0..4095) - assert.True(t, tr.Has(misalignedOffset), "Expected misaligned offset %d to be marked (should mark its containing block)", misalignedOffset) - - // Now check that any offset in the same block is also considered marked - anotherOffsetInSameBlock := int64(1000) - assert.True(t, tr.Has(anotherOffsetInSameBlock), "Expected offset %d to be marked as in same block as %d", anotherOffsetInSameBlock, misalignedOffset) - - // But not for a different block - offsetInNextBlock := int64(pageSize) - assert.False(t, tr.Has(offsetInNextBlock), "Did not expect offset %d to be marked", offsetInNextBlock) - - // And not far outside any set block - offsetFar := int64(2 * pageSize) - assert.False(t, tr.Has(offsetFar), "Did not expect offset %d to be marked", offsetFar) -} - -func TestTracker_Offsets(t *testing.T) { - t.Parallel() - const pageSize = 4096 - tr := NewTracker(pageSize) - - numOffsets := 300 - - offsetsMap := map[int64]struct{}{} - - for range numOffsets { - select { - case <-t.Context().Done(): - t.FailNow() - default: - } - - base := int64(rand.Intn(121)) // 0..120 - offset := base * pageSize - - offsetsMap[offset] = struct{}{} - tr.Add(offset) - } - - expectedOffsets := slices.Collect(maps.Keys(offsetsMap)) - actualOffsets := slices.Collect(tr.Offsets()) - - assert.Len(t, actualOffsets, len(expectedOffsets)) - assert.ElementsMatch(t, expectedOffsets, actualOffsets) -} diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go index c2617b0177..8a2f86afb6 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go @@ -314,8 +314,23 @@ func crossProcessServe() error { case <-ctx.Done(): return case <-offsetsSignal: - for offset := range uffd.faulted().Offsets() { - writeErr := binary.Write(offsetsFile, binary.LittleEndian, uint64(offset)) + entries, entriesErr := uffd.pageStateEntries() + if entriesErr != nil { + msg := fmt.Errorf("error getting page state entries: %w", entriesErr) + + fmt.Fprint(os.Stderr, msg.Error()) + + cancel(msg) + + return + } + + for _, entry := range entries { + if entry.state != faulted { + continue + } + + writeErr := binary.Write(offsetsFile, binary.LittleEndian, entry.offset) if writeErr != nil { msg := fmt.Errorf("error writing offsets to file: %w", writeErr) @@ -390,3 +405,27 @@ func crossProcessServe() error { return nil } } + +type pageStateEntry struct { + state pageState + offset uint64 +} + +func (u *Userfaultfd) pageStateEntries() ([]pageStateEntry, error) { + u.settleRequests.Lock() + u.settleRequests.Unlock() //nolint:staticcheck // SA2001: intentional — settle the read locks. + + u.pageTracker.mu.RLock() + defer u.pageTracker.mu.RUnlock() + + entries := make([]pageStateEntry, 0, len(u.pageTracker.m)) + for addr, state := range u.pageTracker.m { + offset, err := u.ma.GetOffset(addr) + if err != nil { + return nil, fmt.Errorf("address %#x not in mapping: %w", addr, err) + } + entries = append(entries, pageStateEntry{state, uint64(offset)}) + } + + return entries, nil +} diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go new file mode 100644 index 0000000000..b0f126115e --- /dev/null +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go @@ -0,0 +1,45 @@ +package userfaultfd + +import "sync" + +type pageState uint8 + +const ( + unfaulted pageState = iota + faulted +) + +type pageTracker struct { + pageSize uintptr + + m map[uintptr]pageState + mu sync.RWMutex +} + +func newPageTracker(pageSize uintptr) pageTracker { + return pageTracker{ + pageSize: pageSize, + m: make(map[uintptr]pageState), + } +} + +func (pt *pageTracker) get(addr uintptr) pageState { + pt.mu.RLock() + defer pt.mu.RUnlock() + + state, ok := pt.m[addr] + if !ok { + return unfaulted + } + + return state +} + +func (pt *pageTracker) setState(start, end uintptr, state pageState) { + pt.mu.Lock() + defer pt.mu.Unlock() + + for addr := start; addr < end; addr += pt.pageSize { + pt.m[addr] = state + } +} diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index 4f982865bb..2c71cf55d4 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -49,14 +49,12 @@ func hasEvent(revents, event int16) bool { type Userfaultfd struct { fd Fd - src block.Slicer - ma *memory.Mapping - pageSize uintptr - - // We don't skip the already mapped pages, because if the memory is swappable the page *might* under some conditions be mapped out. - // For hugepages this should not be a problem, but might theoretically happen to normal pages with swap - missingRequests *block.Tracker - // We use the settleRequests to guard the missingRequests so we can access a consistent state of the missingRequests after the requests are finished. + src block.Slicer + ma *memory.Mapping + pageSize uintptr + pageTracker pageTracker + + // We use the settleRequests to guard the pageTracker so we can access a consistent state of the pageTracker after the requests are finished. settleRequests sync.RWMutex prefetchTracker *block.PrefetchTracker @@ -83,7 +81,7 @@ func NewUserfaultfdFromFd(fd uintptr, src block.Slicer, m *memory.Mapping, logge fd: Fd(fd), src: src, pageSize: uintptr(blockSize), - missingRequests: block.NewTracker(blockSize), + pageTracker: newPageTracker(uintptr(blockSize)), prefetchTracker: block.NewPrefetchTracker(blockSize), ma: m, logger: logger, @@ -275,16 +273,6 @@ func (u *Userfaultfd) Serve( } } -func (u *Userfaultfd) faulted() *block.Tracker { - // This will be at worst cancelled when the uffd is closed. - u.settleRequests.Lock() - // The locking here would work even without using defer (just lock-then-unlock the mutex), but at this point let's make it lock to the clone, - // so it is consistent even if there is a another uffd call after. - defer u.settleRequests.Unlock() - - return u.missingRequests.Clone() -} - func (u *Userfaultfd) PrefetchData() block.PrefetchData { // This will be at worst cancelled when the uffd is closed. u.settleRequests.Lock() @@ -307,8 +295,7 @@ func (u *Userfaultfd) faultPage( // The RLock must be called inside the goroutine to ensure RUnlock runs via defer, // even if the errgroup is cancelled or the goroutine returns early. - // This check protects us against race condition between marking the request as missing and accessing the missingRequests tracker. - // The Firecracker pause should return only after the requested memory is faulted in, so we don't need to guard the pagefault from the moment it is created. + // This check protects us against race condition between marking the request for prefetching and accessing the prefetchTracker. u.settleRequests.RLock() defer u.settleRequests.RUnlock() @@ -407,8 +394,7 @@ retryLoop: return fmt.Errorf("failed uffdio copy: %w", joinedErr) } - // Add the offset to the missing requests tracker with metadata. - u.missingRequests.Add(offset) + u.pageTracker.setState(addr, addr+u.pageSize, faulted) u.prefetchTracker.Add(offset, accessType) return nil From 9dec696f378b977d2a46217749ed28f30ee4ebde Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Mon, 20 Apr 2026 18:19:19 -0700 Subject: [PATCH 48/63] fixup: address review feedback - Drop the unused pageTracker.get; PR B2 reintroduces it with REMOVE. - Return *pageTracker so the embedded mutex is never copied. - Sort pageStateEntries by offset to keep the wire format deterministic. --- .../userfaultfd/cross_process_helpers_test.go | 5 +++++ .../pkg/sandbox/uffd/userfaultfd/page_tracker.go | 16 ++-------------- .../pkg/sandbox/uffd/userfaultfd/userfaultfd.go | 2 +- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go index 8a2f86afb6..41b489eb7f 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go @@ -16,6 +16,7 @@ import ( "os" "os/exec" "os/signal" + "sort" "strconv" "strings" "syscall" @@ -427,5 +428,9 @@ func (u *Userfaultfd) pageStateEntries() ([]pageStateEntry, error) { entries = append(entries, pageStateEntry{state, uint64(offset)}) } + // Map iteration is nondeterministic; the test harness compares the emitted + // offsets via ordered assert.Equal, so sort to keep the wire format stable. + sort.Slice(entries, func(i, j int) bool { return entries[i].offset < entries[j].offset }) + return entries, nil } diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go index b0f126115e..a63f246e83 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go @@ -16,25 +16,13 @@ type pageTracker struct { mu sync.RWMutex } -func newPageTracker(pageSize uintptr) pageTracker { - return pageTracker{ +func newPageTracker(pageSize uintptr) *pageTracker { + return &pageTracker{ pageSize: pageSize, m: make(map[uintptr]pageState), } } -func (pt *pageTracker) get(addr uintptr) pageState { - pt.mu.RLock() - defer pt.mu.RUnlock() - - state, ok := pt.m[addr] - if !ok { - return unfaulted - } - - return state -} - func (pt *pageTracker) setState(start, end uintptr, state pageState) { pt.mu.Lock() defer pt.mu.Unlock() diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index 2c71cf55d4..6b53377fdb 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -52,7 +52,7 @@ type Userfaultfd struct { src block.Slicer ma *memory.Mapping pageSize uintptr - pageTracker pageTracker + pageTracker *pageTracker // We use the settleRequests to guard the pageTracker so we can access a consistent state of the pageTracker after the requests are finished. settleRequests sync.RWMutex From 417db8608ff3b8b6e9ded758639b2172527f0843 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Mon, 20 Apr 2026 18:55:17 -0700 Subject: [PATCH 49/63] test(uffd): compare faulted offsets unordered instead of sorting We don't actually care about offset order, only set equality. Use assert.ElementsMatch in the missing_test/missing_write_test callsites and drop the sort from pageStateEntries. --- .../uffd/userfaultfd/cross_process_helpers_test.go | 5 ----- .../pkg/sandbox/uffd/userfaultfd/missing_test.go | 8 ++++---- .../pkg/sandbox/uffd/userfaultfd/missing_write_test.go | 8 ++++---- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go index 41b489eb7f..8a2f86afb6 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go @@ -16,7 +16,6 @@ import ( "os" "os/exec" "os/signal" - "sort" "strconv" "strings" "syscall" @@ -428,9 +427,5 @@ func (u *Userfaultfd) pageStateEntries() ([]pageStateEntry, error) { entries = append(entries, pageStateEntry{state, uint64(offset)}) } - // Map iteration is nondeterministic; the test harness compares the emitted - // offsets via ordered assert.Equal, so sort to keep the wire format stable. - sort.Slice(entries, func(i, j int) bool { return entries[i].offset < entries[j].offset }) - return entries, nil } diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_test.go index 0fd5caf81c..affe767a91 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_test.go @@ -131,7 +131,7 @@ func TestMissing(t *testing.T) { accessedOffsets, err := h.offsetsOnce() require.NoError(t, err) - assert.Equal(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") + assert.ElementsMatch(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") h.checkDirtiness(t, tt.operations) }) @@ -172,7 +172,7 @@ func TestParallelMissing(t *testing.T) { accessedOffsets, err := h.offsetsOnce() require.NoError(t, err) - assert.Equal(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") + assert.ElementsMatch(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") } func TestParallelMissingWithPrefault(t *testing.T) { @@ -212,7 +212,7 @@ func TestParallelMissingWithPrefault(t *testing.T) { accessedOffsets, err := h.offsetsOnce() require.NoError(t, err) - assert.Equal(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") + assert.ElementsMatch(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") } func TestSerialMissing(t *testing.T) { @@ -243,5 +243,5 @@ func TestSerialMissing(t *testing.T) { accessedOffsets, err := h.offsetsOnce() require.NoError(t, err) - assert.Equal(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") + assert.ElementsMatch(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") } diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_write_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_write_test.go index 6ae073445f..18a46e2bcd 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_write_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/missing_write_test.go @@ -126,7 +126,7 @@ func TestMissingWrite(t *testing.T) { accessedOffsets, err := h.offsetsOnce() require.NoError(t, err) - assert.Equal(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") + assert.ElementsMatch(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") h.checkDirtiness(t, tt.operations) }) @@ -167,7 +167,7 @@ func TestParallelMissingWrite(t *testing.T) { accessedOffsets, err := h.offsetsOnce() require.NoError(t, err) - assert.Equal(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") + assert.ElementsMatch(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") } func TestParallelMissingWriteWithPrefault(t *testing.T) { @@ -207,7 +207,7 @@ func TestParallelMissingWriteWithPrefault(t *testing.T) { accessedOffsets, err := h.offsetsOnce() require.NoError(t, err) - assert.Equal(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") + assert.ElementsMatch(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") } func TestSerialMissingWrite(t *testing.T) { @@ -238,5 +238,5 @@ func TestSerialMissingWrite(t *testing.T) { accessedOffsets, err := h.offsetsOnce() require.NoError(t, err) - assert.Equal(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") + assert.ElementsMatch(t, expectedAccessedOffsets, accessedOffsets, "checking which pages were faulted") } From 7fcda667137a10ecb898431ac0af115baadc1d68 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Mon, 20 Apr 2026 18:58:48 -0700 Subject: [PATCH 50/63] fixup: tighten pageStateEntries locking and clarify faultPage comment - pageStateEntries now holds settleRequests.Lock for the whole snapshot so no in-flight faultPage can mutate pageTracker mid-iteration. This matches the original faulted() semantics and removes the lock-then- immediately-unlock window flagged by Bugbot. - The settleRequests RLock comment in faultPage now mentions both the pageTracker and prefetchTracker, matching the struct-level comment. --- .../sandbox/uffd/userfaultfd/cross_process_helpers_test.go | 7 +++---- .../pkg/sandbox/uffd/userfaultfd/userfaultfd.go | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go index 8a2f86afb6..8ffbe2756e 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go @@ -412,11 +412,10 @@ type pageStateEntry struct { } func (u *Userfaultfd) pageStateEntries() ([]pageStateEntry, error) { + // Hold the write lock for the whole snapshot so no in-flight faultPage can + // mutate pageTracker while we iterate. u.settleRequests.Lock() - u.settleRequests.Unlock() //nolint:staticcheck // SA2001: intentional — settle the read locks. - - u.pageTracker.mu.RLock() - defer u.pageTracker.mu.RUnlock() + defer u.settleRequests.Unlock() entries := make([]pageStateEntry, 0, len(u.pageTracker.m)) for addr, state := range u.pageTracker.m { diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index 6b53377fdb..660aa0da36 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -295,7 +295,8 @@ func (u *Userfaultfd) faultPage( // The RLock must be called inside the goroutine to ensure RUnlock runs via defer, // even if the errgroup is cancelled or the goroutine returns early. - // This check protects us against race condition between marking the request for prefetching and accessing the prefetchTracker. + // This guards against races between marking the page faulted / prefetched + // and another caller observing the pageTracker or prefetchTracker. u.settleRequests.RLock() defer u.settleRequests.RUnlock() From ecf353b644da4f509e1ee5db2eaf77fa63342297 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Mon, 20 Apr 2026 22:55:55 -0700 Subject: [PATCH 51/63] fixup: drop unused unfaulted const, remove redundant stderr print - page_tracker.go: drop the unreferenced `unfaulted` constant. Keep the zero value of pageState reserved for pages not yet present in the tracker map by starting `faulted` at iota + 1. - cross_process_helpers_test.go: drop the redundant stderr print on the pageStateEntries error path; cancel(err) already propagates the cause to the top-level return, which the helper process prints. --- .../sandbox/uffd/userfaultfd/cross_process_helpers_test.go | 6 +----- .../pkg/sandbox/uffd/userfaultfd/page_tracker.go | 5 +++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go index 8ffbe2756e..cdfd5d07bf 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go @@ -316,11 +316,7 @@ func crossProcessServe() error { case <-offsetsSignal: entries, entriesErr := uffd.pageStateEntries() if entriesErr != nil { - msg := fmt.Errorf("error getting page state entries: %w", entriesErr) - - fmt.Fprint(os.Stderr, msg.Error()) - - cancel(msg) + cancel(fmt.Errorf("error getting page state entries: %w", entriesErr)) return } diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go index a63f246e83..175d4b2790 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go @@ -5,8 +5,9 @@ import "sync" type pageState uint8 const ( - unfaulted pageState = iota - faulted + // faulted starts at 1 so the pageState zero value is reserved for pages + // not yet present in the tracker map. + faulted pageState = iota + 1 ) type pageTracker struct { From a597bf68f12deb7d4ea6048e527d5aaa2c825c31 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Mon, 20 Apr 2026 22:59:29 -0700 Subject: [PATCH 52/63] fixup: keep unfaulted as the named zero value of pageState The follow-up free-page-reporting work references unfaulted explicitly from a get() accessor that returns it for pages not yet present in the tracker map, so keep the constant named here and document its role as the pageState zero value. --- .../pkg/sandbox/uffd/userfaultfd/page_tracker.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go index 175d4b2790..4185886978 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go @@ -5,9 +5,11 @@ import "sync" type pageState uint8 const ( - // faulted starts at 1 so the pageState zero value is reserved for pages - // not yet present in the tracker map. - faulted pageState = iota + 1 + // unfaulted is the implicit state for pages not yet present in the + // tracker map; it is the zero value of pageState. The follow-up + // free-page-reporting work returns it explicitly from a get() accessor. + unfaulted pageState = iota + faulted ) type pageTracker struct { From b0b5e77c5102fbe3d74942913a0d5a6783b51f6e Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Mon, 20 Apr 2026 23:02:37 -0700 Subject: [PATCH 53/63] fixup: rename unfaulted to missing --- .../orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go index 4185886978..e848e943c8 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go @@ -5,10 +5,10 @@ import "sync" type pageState uint8 const ( - // unfaulted is the implicit state for pages not yet present in the + // missing is the implicit state for pages not yet present in the // tracker map; it is the zero value of pageState. The follow-up // free-page-reporting work returns it explicitly from a get() accessor. - unfaulted pageState = iota + missing pageState = iota faulted ) From 9126f4ff7ee5a3655319e0625657e30e9757a8e0 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Mon, 20 Apr 2026 23:04:24 -0700 Subject: [PATCH 54/63] fixup: rename pageState unfaulted to missing --- .../pkg/sandbox/uffd/userfaultfd/page_tracker.go | 6 +++--- .../pkg/sandbox/uffd/userfaultfd/remove_test.go | 2 +- .../pkg/sandbox/uffd/userfaultfd/userfaultfd.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go index 2d95c2b3bf..76f6c90ec6 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go @@ -5,9 +5,9 @@ import "sync" type pageState uint8 const ( - // unfaulted is the implicit state for pages not yet present in the + // missing is the implicit state for pages not yet present in the // tracker map; it is the zero value of pageState. - unfaulted pageState = iota + missing pageState = iota faulted removed ) @@ -32,7 +32,7 @@ func (pt *pageTracker) get(addr uintptr) pageState { state, ok := pt.m[addr] if !ok { - return unfaulted + return missing } return state diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/remove_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/remove_test.go index eaf3c70ac1..3f96d555c1 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/remove_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/remove_test.go @@ -215,7 +215,7 @@ func TestRemoveThenWriteGated(t *testing.T) { // TestWriteThenRemoveGated verifies the serve loop's ordering guarantee: // REMOVE events are processed before pagefaults even when the MISSING pagefault -// was queued first. The write to an unfaulted page triggers MISSING (queued first), +// was queued first. The write to a missing page triggers MISSING (queued first), // then MADV_DONTNEED triggers REMOVE (queued second). When the handler resumes, // it processes REMOVE first, then MISSING — the write is not skipped. func TestWriteThenRemoveGated(t *testing.T) { diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index 6490afcdb0..4b20a2ae99 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -319,12 +319,12 @@ func (u *Userfaultfd) Serve( // Skip faulting the page. This has already been faulted, either during pre-faulting // or because we handled another page fault on the same address in the current // iteration. It can only be removed via a a UFFD_EVENT_REMOVE, which will mark the - // page as `unfaulted`. + // page as `missing`. // For this to work correctly, the used pages cannot be swappable. continue case removed: // Fault the page as empty. - case unfaulted: + case missing: source = u.src default: return fmt.Errorf("unexpected pageState: %#v", state) From 1aa4a079062d681247845d8340728f74fcc25a4c Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Mon, 20 Apr 2026 23:05:51 -0700 Subject: [PATCH 55/63] fixup: drop verbose comment on missing pageState --- .../orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go index 76f6c90ec6..2f57ab9966 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/page_tracker.go @@ -5,8 +5,6 @@ import "sync" type pageState uint8 const ( - // missing is the implicit state for pages not yet present in the - // tracker map; it is the zero value of pageState. missing pageState = iota faulted removed From bb93b9fe812d4cc27b572552d0fce59d24903169 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 21 Apr 2026 19:40:55 +0000 Subject: [PATCH 56/63] chore: auto-commit generated changes --- .../orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index 67c1d0118a..79d2af19b8 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -549,5 +549,4 @@ func (u *Userfaultfd) Close() error { syscall.Close(u.wakeupPipe[1]) return u.fd.close() - } From c8b3d63f868b02652a5a53ed17bb16095fe72e31 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Mon, 27 Apr 2026 12:10:33 -0700 Subject: [PATCH 57/63] ci: cap orchestrator test parallelism and bump timeout The cross-process UFFD tests in packages/orchestrator/pkg/sandbox/uffd/userfaultfd each fork a child helper process and exercise userfaultfd page-fault servicing across processes. With ~20 t.Parallel() tests across this package plus their parallel subtests, ~29 cross-process tests can be in flight at once on a busy CI runner, starving each other for CPU and pipe I/O. This shows up as repeated 24m SIGQUIT timeouts where the parent goroutines are stuck at: - cross_process_helpers_test.go:130 (writing content into the child pipe) - cross_process_helpers_test.go:152 (waiting for the child's ready signal) - cross_process_helpers_test.go:268 (waiting on readySignal) Pass -parallel=4 to cap concurrent t.Parallel() tests within each package to 4, which serializes the heavyweight cross-process UFFD tests through 4 slots without slowing down lightweight unit-test packages much. Bump the test timeout from 20m to 30m as a safety margin so slow-but-not-stuck runs can complete. --- .github/workflows/pr-tests-arm64.yml | 7 +++++-- .github/workflows/pr-tests.yml | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pr-tests-arm64.yml b/.github/workflows/pr-tests-arm64.yml index c7aeef787f..478010827e 100644 --- a/.github/workflows/pr-tests-arm64.yml +++ b/.github/workflows/pr-tests-arm64.yml @@ -117,7 +117,10 @@ jobs: - name: Run tests that require sudo working-directory: ${{ matrix.package }} - run: sudo -E `which go` test -v -timeout 20m ${{ matrix.test_path }} + # -parallel=4 caps concurrent t.Parallel() tests to keep heavyweight + # cross-process UFFD tests in packages/orchestrator from overwhelming + # the runner and timing out (each test forks a child handler process). + run: sudo -E `which go` test -v -timeout 30m -parallel 4 ${{ matrix.test_path }} if: matrix.sudo == true - name: Compile test binaries @@ -127,5 +130,5 @@ jobs: - name: Run tests working-directory: ${{ matrix.package }} - run: go test -v -timeout 20m ${{ matrix.test_path }} + run: go test -v -timeout 30m -parallel 4 ${{ matrix.test_path }} if: matrix.sudo == false diff --git a/.github/workflows/pr-tests.yml b/.github/workflows/pr-tests.yml index eefa3cee12..d22deeff5d 100644 --- a/.github/workflows/pr-tests.yml +++ b/.github/workflows/pr-tests.yml @@ -81,12 +81,15 @@ jobs: working-directory: ${{ matrix.package }} run: | # Run tests. The '-E' flag is required to allow root to use the correct cache path. - sudo -E `which go` test -v -timeout 20m ${{ matrix.test_path }} + # -parallel=4 caps concurrent t.Parallel() tests to keep heavyweight + # cross-process UFFD tests in packages/orchestrator from overwhelming + # the runner and timing out (each test forks a child handler process). + sudo -E `which go` test -v -timeout 30m -parallel 4 ${{ matrix.test_path }} if: matrix.sudo == true - name: Run tests working-directory: ${{ matrix.package }} - run: go test -v -timeout 20m ${{ matrix.test_path }} + run: go test -v -timeout 30m -parallel 4 ${{ matrix.test_path }} if: matrix.sudo == false validate-iac: From f31027327eb107a0466f31a8c64cd687eac7e555 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Mon, 27 Apr 2026 12:33:45 -0700 Subject: [PATCH 58/63] chore(uffd): fix stale comment and minor cleanups - userfaultfd.go: correct the `case faulted` branch comment to refer to the `removed` page state (previously claimed REMOVE marks the page as `missing`) and drop the duplicated "a a" typo. - userfaultfd.go: restore the colon in the "failed uffdio copy: %w" error message that was lost when faultPage was refactored to return (bool, error). - fcversion: simplify HasFreePageReporting to a single boolean return. --- .../pkg/sandbox/uffd/userfaultfd/userfaultfd.go | 6 +++--- packages/shared/pkg/fcversion/sandbox_features.go | 6 +----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index 4cdd98d1ce..5496cd4e2b 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -317,8 +317,8 @@ func (u *Userfaultfd) Serve( case faulted: // Skip faulting the page. This has already been faulted, either during pre-faulting // or because we handled another page fault on the same address in the current - // iteration. It can only be removed via a a UFFD_EVENT_REMOVE, which will mark the - // page as `missing`. + // iteration. It can only transition out of `faulted` via a UFFD_EVENT_REMOVE, which + // will mark the page as `removed`. // For this to work correctly, the used pages cannot be swappable. continue case removed: @@ -512,7 +512,7 @@ func (u *Userfaultfd) faultPage( span.RecordError(joinedErr) u.logger.Error(ctx, "UFFD serve uffdio copy error", zap.Error(joinedErr)) - return false, fmt.Errorf("failed uffdio copy %w", joinedErr) + return false, fmt.Errorf("failed uffdio copy: %w", joinedErr) } return true, nil diff --git a/packages/shared/pkg/fcversion/sandbox_features.go b/packages/shared/pkg/fcversion/sandbox_features.go index 582f2ed7ec..b768f199d4 100644 --- a/packages/shared/pkg/fcversion/sandbox_features.go +++ b/packages/shared/pkg/fcversion/sandbox_features.go @@ -9,9 +9,5 @@ func (v *Info) HasHugePages() bool { } func (v *Info) HasFreePageReporting() bool { - if v.lastReleaseVersion.Major() > 1 || (v.lastReleaseVersion.Major() == 1 && v.lastReleaseVersion.Minor() >= 14) { - return true - } - - return false + return v.lastReleaseVersion.Major() > 1 || (v.lastReleaseVersion.Major() == 1 && v.lastReleaseVersion.Minor() >= 14) } From 24cb1e1fc15b8261750ec7ac676267df60847304 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Mon, 27 Apr 2026 16:23:46 -0700 Subject: [PATCH 59/63] fix(uffd): re-read page state inside worker goroutine under settleRequests.RLock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Serve() loop previously read pageTracker state and captured `source = u.src` in the parent loop, then dispatched a worker goroutine. A REMOVE event for the same page that arrived between the state read and the worker actually acquiring settleRequests.RLock() would silently leave the worker with a stale `source = u.src` snapshot. The worker would then UFFDIO_COPY src bytes into a page the kernel had just MADV_DONTNEED'd, leaving pageTracker == removed and the kernel page mapped with stale src data — and observably deadlocking parent madvise() in the orchestrator unit-test suite. Move the state lookup and source capture inside the goroutine, after RLock(). The read+act+commit sequence is now atomic with respect to the REMOVE batch (which takes settleRequests.Lock()). --- .../sandbox/uffd/userfaultfd/userfaultfd.go | 51 +++++++++++-------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index 5496cd4e2b..3e34589d8e 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -311,31 +311,40 @@ func (u *Userfaultfd) Serve( return fmt.Errorf("failed to map: %w", err) } - var source block.Slicer - - switch state := u.pageTracker.get(addr); state { - case faulted: - // Skip faulting the page. This has already been faulted, either during pre-faulting - // or because we handled another page fault on the same address in the current - // iteration. It can only transition out of `faulted` via a UFFD_EVENT_REMOVE, which - // will mark the page as `removed`. - // For this to work correctly, the used pages cannot be swappable. - continue - case removed: - // Fault the page as empty. - case missing: - source = u.src - default: - return fmt.Errorf("unexpected pageState: %#v", state) - } - u.wg.Go(func() error { - // The RLock must be called inside the goroutine to ensure RUnlock runs via defer, - // even if the errgroup is cancelled or the goroutine returns early. - // This check protects us against race condition between marking the request for prefetching and accessing the prefetchTracker. + // The RLock must be acquired inside the goroutine — and it must be acquired + // BEFORE we read the pageTracker / u.src state — so that the read+act+commit + // sequence (state lookup → faultPage → setState) is atomic with respect to + // any concurrent REMOVE batch (which takes settleRequests.Lock()). If the + // state read happened in the parent loop, a REMOVE could land between the + // read and the goroutine acquiring the RLock, and the goroutine would still + // commit `faulted` afterwards, overwriting `removed`. + // + // This also protects the read of u.src: in the future src could be swapped + // out under settleRequests; reading it under the RLock keeps that safe. u.settleRequests.RLock() defer u.settleRequests.RUnlock() + var source block.Slicer + + switch state := u.pageTracker.get(addr); state { + case faulted: + // Skip faulting the page. This has already been faulted, either during pre-faulting + // or because we handled another page fault on the same address in the current + // iteration. It can only transition out of `faulted` via a UFFD_EVENT_REMOVE, which + // will mark the page as `removed`. + // For this to work correctly, the used pages cannot be swappable. + return nil + case removed: + // Fault the page as empty (no source). The page was MADV_DONTNEED'd; the + // kernel still expects an UFFDIO_COPY/ZEROPAGE ack for the original + // MISSING fault, otherwise the faulting thread stays blocked. + case missing: + source = u.src + default: + return fmt.Errorf("unexpected pageState: %#v", state) + } + var accessType block.AccessType if pf.flags&UFFD_PAGEFAULT_FLAG_WRITE == 0 { From 4618cc5a20be296a9108438809a607ea39f0b537 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Mon, 27 Apr 2026 16:24:29 -0700 Subject: [PATCH 60/63] =?UTF-8?q?refactor(uffd-tests):=20replace=20parent?= =?UTF-8?q?=E2=86=94child=20signals/pipes=20with=20unix-socket=20JSON-RPC?= =?UTF-8?q?=20harness?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cross-process userfaultfd test harness used a pile of single- purpose pipes (offsets, ready, gate-cmd, gate-sync) plus a SIGUSR1 shutdown signal and a SIGUSR2 page-state-snapshot trigger. Every new piece of test-only inspection or coordination meant adding another fd, another env var, and another ad-hoc encoder/decoder. That pattern also made it hard to add deterministic fault-barrier primitives (the earlier attempt at race-test reproducibility kept reaching for soak loops because there was no clean place for "park here, fire racing op, release" handshakes). Replace all of that with one bidirectional Unix domain socket and a small length-prefixed JSON-RPC envelope (rpc_test.go). Per-call request IDs let the parent issue concurrent RPCs while a handler is parked (which is what the new fault-barrier methods need). The only fd we still hand off out-of-band is the userfaultfd itself (kernel object, has to go through ExtraFiles); the initial source data is written to a temp file under t.TempDir() because base64-stuffing megabytes through JSON would be silly. Replaced surface: - 4 pipes + 2 signals → 1 socket + 1 RPC channel - SIGUSR1 graceful exit → Shutdown RPC - SIGUSR2 + offsets pipe + binary.Write protocol → PageStates RPC - ready pipe + ReadAll-on-close handshake → WaitReady RPC - gate-cmd / gate-sync byte protocol → ServePause / ServeResume RPCs Added on top of the same channel: - InstallFaultBarrier(addr, point) → token: arms a deterministic barrier in the child's worker goroutine at one of two test-only hook points (beforeWorkerRLockHook, beforeFaultPageHook). - WaitFaultHeld(token): blocks until the worker reaches the barrier — the RPC reply IS the rendezvous. - ReleaseFault(token): lets the parked worker proceed. The two hook fields on Userfaultfd default to nil and are nil-checked on the hot path, so production sees zero behavioral change. They are only assigned in the child's crossProcessServe wiring. testConfig gains a sourcePatcher hook so race tests can plant a deterministic sentinel byte into the random source data BEFORE the content file is written, without having to depend on the happenstance value of any randomly-generated byte. --- .../userfaultfd/cross_process_helpers_test.go | 792 +++++++++++------- .../sandbox/uffd/userfaultfd/helpers_test.go | 46 +- .../pkg/sandbox/uffd/userfaultfd/rpc_test.go | 332 ++++++++ .../sandbox/uffd/userfaultfd/userfaultfd.go | 42 + 4 files changed, 886 insertions(+), 326 deletions(-) create mode 100644 packages/orchestrator/pkg/sandbox/uffd/userfaultfd/rpc_test.go diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go index ed26eb263e..a4dac03578 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go @@ -1,28 +1,36 @@ package userfaultfd -// This tests is creating uffd in the main process and handling the page faults in another process. -// It prevents problems with Go mmap during testing (https://pojntfx.github.io/networked-linux-memsync/main.html#limitations) and also more accurately simulates what we do with Firecracker. -// These problems are not affecting Firecracker, because: -// 1. It is a different process that handles the page faults -// 2. Does not use garbage collection +// This test creates the userfaultfd in the parent test process and +// drives it from a child helper process. We do this so the actual +// page-fault handling runs in a process where we can fully control +// memory layout (no Go GC scanning / touching the registered region) +// — which mirrors how Firecracker uses UFFD in production. +// +// All parent ↔ child coordination — readiness, page-state queries, +// pause/resume, fault barriers, shutdown — flows over a single Unix +// domain socket using the JSON-RPC harness in rpc_test.go. The only +// fd we still hand off out-of-band is the userfaultfd itself, which +// is a kernel object and has to be passed via ExtraFiles. The +// initial source data is written to a temp file (path passed in an +// env var) because base64-stuffing megabytes through the JSON +// envelope would be silly. import ( "context" "crypto/rand" - "encoding/binary" + "encoding/json" "errors" "fmt" - "io" + "net" "os" "os/exec" - "os/signal" + "path/filepath" "slices" "strconv" - "strings" + "sync" "syscall" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/sys/unix" @@ -33,8 +41,8 @@ import ( "github.com/e2b-dev/infra/packages/shared/pkg/logger" ) -// MemorySlicer exposes byte slice via the Slicer interface. -// This is used for testing purposes. +// MemorySlicer exposes a byte slice via the Slicer interface. +// Test-only. type MemorySlicer struct { content []byte pagesize int64 @@ -43,10 +51,7 @@ type MemorySlicer struct { var _ block.Slicer = (*MemorySlicer)(nil) func NewMemorySlicer(content []byte, pagesize int64) *MemorySlicer { - return &MemorySlicer{ - content: content, - pagesize: pagesize, - } + return &MemorySlicer{content: content, pagesize: pagesize} } func (s *MemorySlicer) Slice(_ context.Context, offset, size int64) ([]byte, error) { @@ -67,9 +72,7 @@ func (s *MemorySlicer) BlockSize() int64 { func RandomPages(pagesize, numberOfPages uint64) *MemorySlicer { size := pagesize * numberOfPages - - n := int(size) - buf := make([]byte, n) + buf := make([]byte, int(size)) if _, err := rand.Read(buf); err != nil { panic(err) } @@ -77,442 +80,423 @@ func RandomPages(pagesize, numberOfPages uint64) *MemorySlicer { return NewMemorySlicer(buf, int64(pagesize)) } +// Env vars used by the child helper process. Kept in one place so +// drift between parent (sets) and child (reads) is impossible. +const ( + envHelperFlag = "GO_TEST_HELPER_PROCESS" + envSocketPath = "GO_UFFD_SOCKET" + envContentPath = "GO_UFFD_CONTENT" + envMmapStart = "GO_UFFD_MMAP_START" + envMmapPagesize = "GO_UFFD_MMAP_PAGESIZE" + envMmapTotalSize = "GO_UFFD_MMAP_SIZE" + envAlwaysWP = "GO_UFFD_ALWAYS_WP" + envGated = "GO_UFFD_GATED" +) + // Main process, FC in our case func configureCrossProcessTest(t *testing.T, tt testConfig) (*testHandler, error) { t.Helper() data := RandomPages(tt.pagesize, tt.numberOfPages) + if tt.sourcePatcher != nil { + tt.sourcePatcher(data.Content()) + } + size, err := data.Size() require.NoError(t, err) memoryArea, memoryStart, err := testutils.NewPageMmap(t, uint64(size), tt.pagesize) require.NoError(t, err) - // We can pass mapping nil as the serve is used only in the helper process. uffdFd, err := newFd(syscall.O_CLOEXEC | syscall.O_NONBLOCK) require.NoError(t, err) - t.Cleanup(func() { uffdFd.close() }) - err = configureApi(uffdFd, tt.pagesize) - require.NoError(t, err) + require.NoError(t, configureApi(uffdFd, tt.pagesize)) + require.NoError(t, register(uffdFd, memoryStart, uint64(size), UFFDIO_REGISTER_MODE_MISSING|UFFDIO_REGISTER_MODE_WP)) + + t.Cleanup(func() { + // Tear the registration down before the late close. With + // UFFD_FEATURE_EVENT_REMOVE enabled (see configureApi), + // munmap can otherwise block on un-acked REMOVE events. + _ = unregister(uffdFd, memoryStart, uint64(size)) + }) + + tmpDir := t.TempDir() + + contentPath := filepath.Join(tmpDir, "content.bin") + require.NoError(t, os.WriteFile(contentPath, data.Content(), 0o600)) - err = register(uffdFd, memoryStart, uint64(size), UFFDIO_REGISTER_MODE_MISSING|UFFDIO_REGISTER_MODE_WP) + socketPath := filepath.Join(tmpDir, "rpc.sock") + listener, err := net.Listen("unix", socketPath) require.NoError(t, err) cmd := exec.CommandContext(t.Context(), os.Args[0], "-test.run=TestHelperServingProcess", "-test.timeout=0") - cmd.Env = append(os.Environ(), "GO_TEST_HELPER_PROCESS=1") - cmd.Env = append(cmd.Env, fmt.Sprintf("GO_MMAP_START=%d", memoryStart)) - cmd.Env = append(cmd.Env, fmt.Sprintf("GO_MMAP_PAGE_SIZE=%d", tt.pagesize)) + cmd.Env = append(os.Environ(), + envHelperFlag+"=1", + envSocketPath+"="+socketPath, + envContentPath+"="+contentPath, + fmt.Sprintf("%s=%d", envMmapStart, memoryStart), + fmt.Sprintf("%s=%d", envMmapPagesize, tt.pagesize), + fmt.Sprintf("%s=%d", envMmapTotalSize, size), + ) if tt.alwaysWP { - cmd.Env = append(cmd.Env, "GO_ALWAYS_WP=1") + cmd.Env = append(cmd.Env, envAlwaysWP+"=1") } if tt.gated { - cmd.Env = append(cmd.Env, "GO_GATED=1") + cmd.Env = append(cmd.Env, envGated+"=1") } dup, err := syscall.Dup(int(uffdFd)) require.NoError(t, err) - - // clear FD_CLOEXEC on the dup we pass across exec - _, err = unix.FcntlInt(uintptr(dup), unix.F_SETFD, 0) - require.NoError(t, err) + if _, err := unix.FcntlInt(uintptr(dup), unix.F_SETFD, 0); err != nil { + require.NoError(t, err) + } uffdFile := os.NewFile(uintptr(dup), "uffd") + cmd.ExtraFiles = []*os.File{uffdFile} + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr - contentReader, contentWriter, err := os.Pipe() - require.NoError(t, err) - - go func() { - _, writeErr := contentWriter.Write(data.Content()) - assert.NoError(t, writeErr) - - closeErr := contentWriter.Close() - assert.NoError(t, closeErr) - }() - - offsetsReader, offsetsWriter, err := os.Pipe() - require.NoError(t, err) - - t.Cleanup(func() { - offsetsReader.Close() - }) - - readyReader, readyWriter, err := os.Pipe() - require.NoError(t, err) - - t.Cleanup(func() { - readyReader.Close() - }) + require.NoError(t, cmd.Start()) + uffdFile.Close() - readySignal := make(chan struct{}, 1) + // Accept the child's connection. Tight deadline so a wedged + // child surfaces fast instead of hanging the test. + type acceptResult struct { + conn net.Conn + err error + } + acceptCh := make(chan acceptResult, 1) go func() { - _, err := io.ReadAll(readyReader) - assert.NoError(t, err) - - readySignal <- struct{}{} + c, err := listener.Accept() + acceptCh <- acceptResult{conn: c, err: err} }() - extraFiles := []*os.File{ - uffdFile, - contentReader, - offsetsWriter, - readyWriter, + var conn net.Conn + select { + case res := <-acceptCh: + require.NoError(t, res.err) + conn = res.conn + case <-t.Context().Done(): + listener.Close() + require.NoError(t, t.Context().Err()) } + listener.Close() - var gateCmdWriter *os.File - var gateSyncReader *os.File - if tt.gated { - var gateCmdReader *os.File - gateCmdReader, gateCmdWriter, err = os.Pipe() - require.NoError(t, err) - - var gateSyncWriter *os.File - gateSyncReader, gateSyncWriter, err = os.Pipe() - require.NoError(t, err) + client := newRPCClient(conn, conn) - t.Cleanup(func() { - gateCmdWriter.Close() - gateSyncReader.Close() - }) - - extraFiles = append(extraFiles, gateCmdReader) // fd 7 - extraFiles = append(extraFiles, gateSyncWriter) // fd 8 + h := &testHandler{ + memoryArea: &memoryArea, + pagesize: tt.pagesize, + data: data, + client: client, + conn: conn, + cmd: cmd, } - cmd.ExtraFiles = extraFiles - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - err = cmd.Start() - require.NoError(t, err) - - contentReader.Close() - offsetsWriter.Close() - readyWriter.Close() - uffdFile.Close() - if tt.gated { - extraFiles[4].Close() // gateCmdReader - extraFiles[5].Close() // gateSyncWriter - } + // WaitReady blocks on the child until its initial setup is done + // (uffd resumed, hooks installed). This is the RPC equivalent of + // the old "ready pipe + read until EOF" handshake. + require.NoError(t, h.client.Call(t.Context(), "WaitReady", nil, nil)) t.Cleanup(func() { - signalErr := cmd.Process.Signal(syscall.SIGUSR1) - assert.NoError(t, signalErr) - + // Best-effort graceful shutdown via RPC. If the child has + // already crashed the RPC will error and we fall back to + // killing the process via cmd.Process.Kill on the next line. + _ = h.client.Call(context.Background(), "Shutdown", nil, nil) + _ = conn.Close() + + // cmd.Wait can return ExitError, "signal: killed", or nil + // depending on whether the child exited cleanly. Any of + // those is acceptable here. waitErr := cmd.Wait() - // It can be either nil, an ExitError, a context.Canceled error, or "signal: killed" - assert.True(t, - (waitErr != nil && func(err error) bool { - var exitErr *exec.ExitError - - return errors.As(err, &exitErr) - }(waitErr)) || - errors.Is(waitErr, context.Canceled) || - (waitErr != nil && strings.Contains(waitErr.Error(), "signal: killed")) || - waitErr == nil, - "unexpected error: %v", waitErr, - ) - - // Tear down the UFFD registration before the early uffdFd.close() - // cleanup runs. This branch enables UFFD_FEATURE_EVENT_REMOVE - // (see configureApi in fd_helpers_test.go), so without the - // unregister, munmap can block on un-acked REMOVE events queued - // by the kernel against the still-registered range. Cleanups - // run LIFO, so this fires before the close registered earlier. - assert.NoError(t, unregister(uffdFd, memoryStart, uint64(size))) + if waitErr != nil { + var exitErr *exec.ExitError + if !errors.As(waitErr, &exitErr) { + t.Logf("helper process Wait: %v", waitErr) + } + } }) - // pageStatesOnce asks the serving process for a snapshot of its pageTracker - // and decodes it into a per-state view. It can only be called once. - pageStatesOnce := func() (handlerPageStates, error) { - err := cmd.Process.Signal(syscall.SIGUSR2) - if err != nil { - return handlerPageStates{}, err + if tt.gated { + h.servePause = func() error { + return h.client.Call(t.Context(), "ServePause", nil, nil) } - - var result handlerPageStates - - for { - var entry pageStateEntry - - // binary.Read uses the same field layout as binary.Write on - // the producer side (sum of fixed-size fields, no struct - // padding), so we never have to hard-code the wire size. - err := binary.Read(offsetsReader, binary.LittleEndian, &entry) - if errors.Is(err, io.EOF) { - break - } - - if err != nil { - return handlerPageStates{}, fmt.Errorf("decoding page state entry: %w", err) - } - - switch pageState(entry.State) { - case faulted: - result.faulted = append(result.faulted, uint(entry.Offset)) - case removed: - result.removed = append(result.removed, uint(entry.Offset)) - } + h.serveResume = func() error { + return h.client.Call(t.Context(), "ServeResume", nil, nil) } - - slices.Sort(result.faulted) - slices.Sort(result.removed) - - return result, nil - } - - select { - case <-t.Context().Done(): - return nil, t.Context().Err() - case <-readySignal: } - h := &testHandler{ - memoryArea: &memoryArea, - pagesize: tt.pagesize, - data: data, - pageStatesOnce: pageStatesOnce, - } + h.pageStatesOnce = func() (handlerPageStates, error) { + var entries []pageStateEntry + if err := h.client.Call(t.Context(), "PageStates", nil, &entries); err != nil { + return handlerPageStates{}, err + } - if tt.gated { - h.servePause = func() error { - if _, err := gateCmdWriter.Write([]byte{'P'}); err != nil { - return err + var states handlerPageStates + for _, e := range entries { + switch pageState(e.State) { + case faulted: + states.faulted = append(states.faulted, uint(e.Offset)) + case removed: + states.removed = append(states.removed, uint(e.Offset)) } - var buf [1]byte - _, err := gateSyncReader.Read(buf[:]) - - return err } - h.serveResume = func() error { - _, err := gateCmdWriter.Write([]byte{'R'}) + slices.Sort(states.faulted) + slices.Sort(states.removed) - return err - } + return states, nil } return h, nil } -// Secondary process, orchestrator in our case +// Secondary process, orchestrator in our case. func TestHelperServingProcess(t *testing.T) { t.Parallel() - if os.Getenv("GO_TEST_HELPER_PROCESS") != "1" { + if os.Getenv(envHelperFlag) != "1" { t.Skip("this is a helper process, skipping direct execution") } - err := crossProcessServe() - if err != nil { - fmt.Println("exit serving process", err) + if err := crossProcessServe(); err != nil { + fmt.Fprintln(os.Stderr, "exit serving process:", err) os.Exit(1) } os.Exit(0) } +// crossProcessServe wires up the child side: connects back to the +// parent socket, exposes the RPC surface, and runs uffd.Serve in a +// background goroutine that pause/resume RPCs can stop and restart. func crossProcessServe() error { - ctx, cancel := context.WithCancelCause(context.Background()) - defer cancel(nil) + socketPath := os.Getenv(envSocketPath) + if socketPath == "" { + return fmt.Errorf("missing %s", envSocketPath) + } - startRaw, err := strconv.Atoi(os.Getenv("GO_MMAP_START")) + conn, err := net.Dial("unix", socketPath) if err != nil { - return fmt.Errorf("exit parsing mmap start: %w", err) + return fmt.Errorf("dial parent socket: %w", err) } + defer conn.Close() + startRaw, err := strconv.ParseUint(os.Getenv(envMmapStart), 10, 64) + if err != nil { + return fmt.Errorf("parse %s: %w", envMmapStart, err) + } memoryStart := uintptr(startRaw) - uffdFile := os.NewFile(uintptr(3), os.Getenv("GO_UFFD_FILE")) - defer uffdFile.Close() - - uffdFd := uffdFile.Fd() - - contentFile := os.NewFile(uintptr(4), "content") - defer contentFile.Close() + pagesize, err := strconv.ParseInt(os.Getenv(envMmapPagesize), 10, 64) + if err != nil { + return fmt.Errorf("parse %s: %w", envMmapPagesize, err) + } - content, err := io.ReadAll(contentFile) + totalSize, err := strconv.ParseInt(os.Getenv(envMmapTotalSize), 10, 64) if err != nil { - return fmt.Errorf("exit reading content: %w", err) + return fmt.Errorf("parse %s: %w", envMmapTotalSize, err) } - pageSize, err := strconv.ParseInt(os.Getenv("GO_MMAP_PAGE_SIZE"), 10, 64) + content, err := os.ReadFile(os.Getenv(envContentPath)) if err != nil { - return fmt.Errorf("exit parsing page size: %w", err) + return fmt.Errorf("read content: %w", err) + } + if int64(len(content)) != totalSize { + return fmt.Errorf("content size %d != expected %d", len(content), totalSize) } - data := NewMemorySlicer(content, pageSize) + data := NewMemorySlicer(content, pagesize) + + uffdFile := os.NewFile(uintptr(3), "uffd") + defer uffdFile.Close() + uffdFd := uffdFile.Fd() - m := memory.NewMapping([]memory.Region{ + mapping := memory.NewMapping([]memory.Region{ { BaseHostVirtAddr: memoryStart, - Size: uintptr(len(content)), + Size: uintptr(totalSize), Offset: 0, - PageSize: uintptr(pageSize), + PageSize: uintptr(pagesize), }, }) l, err := logger.NewDevelopmentLogger() if err != nil { - return fmt.Errorf("exit creating logger: %w", err) + return fmt.Errorf("logger: %w", err) } - uffd, err := NewUserfaultfdFromFd(uffdFd, data, m, l) + uffd, err := NewUserfaultfdFromFd(uffdFd, data, mapping, l) if err != nil { - return fmt.Errorf("exit creating uffd: %w", err) + return fmt.Errorf("NewUserfaultfdFromFd: %w", err) } - if os.Getenv("GO_ALWAYS_WP") == "1" { + if os.Getenv(envAlwaysWP) == "1" { uffd.defaultCopyMode = UFFDIO_COPY_MODE_WP } - offsetsFile := os.NewFile(uintptr(5), "offsets") + // Wire the deterministic test barriers into the production hook + // fields. Both hooks consult the same per-addr registry below. + br := newBarrierRegistry() + uffd.beforeWorkerRLockHook = br.hookFor(barrierBeforeRLock) + uffd.beforeFaultPageHook = br.hookFor(barrierBeforeFaultPage) - offsetsSignal := make(chan os.Signal, 1) - signal.Notify(offsetsSignal, syscall.SIGUSR2) - defer signal.Stop(offsetsSignal) + server := newRPCServer(conn, conn) - go func() { - defer offsetsFile.Close() - - for { - select { - case <-ctx.Done(): - return - case <-offsetsSignal: - entries, entriesErr := uffd.pageStateEntries() - if entriesErr != nil { - cancel(fmt.Errorf("error getting page state entries: %w", entriesErr)) - - return - } - - for _, entry := range entries { - writeErr := binary.Write(offsetsFile, binary.LittleEndian, entry) - if writeErr != nil { - cancel(fmt.Errorf("error writing page state entry: %w", writeErr)) - - return - } - } - - return - } + serveCtx, serveCancel := context.WithCancel(context.Background()) + + // Lifecycle of the actual UFFD serve loop. Pause/resume RPCs + // stop and restart this goroutine; Shutdown signals the outer + // loop to exit. We use a small helper to avoid duplicating the + // "create fdexit + go uffd.Serve + wait" pattern. + var serveMu sync.Mutex + var serveStop func() + + startServe := func() { + exit, err := fdexit.New() + if err != nil { + fmt.Fprintln(os.Stderr, "fdexit.New:", err) + + return } - }() - fdExit, err := fdexit.New() - if err != nil { - return fmt.Errorf("exit creating fd exit: %w", err) + done := make(chan struct{}) + go func() { + defer close(done) + if err := uffd.Serve(serveCtx, exit); err != nil { + fmt.Fprintln(os.Stderr, "uffd.Serve:", err) + } + }() + + serveStop = func() { + _ = exit.SignalExit() + <-done + exit.Close() + } } - defer fdExit.Close() - exitUffd := make(chan struct{}, 1) + startServe() - go func() { - defer func() { exitUffd <- struct{}{} }() + gated := os.Getenv(envGated) == "1" + + // Track in-flight barrier tokens so Shutdown can release them + // (otherwise a parked worker would never return and the serve + // goroutine would never finish). + server.Register("WaitReady", func(_ context.Context, _ json.RawMessage) (any, error) { + return nil, nil + }) - serverErr := uffd.Serve(ctx, fdExit) - if serverErr != nil { - msg := fmt.Errorf("error serving: %w", serverErr) - fmt.Fprint(os.Stderr, msg.Error()) - cancel(msg) + server.Register("PageStates", func(_ context.Context, _ json.RawMessage) (any, error) { + return uffd.pageStateEntries() + }) + + server.Register("ServePause", func(_ context.Context, _ json.RawMessage) (any, error) { + if !gated { + return nil, errors.New("ServePause called on a non-gated handler") + } + serveMu.Lock() + defer serveMu.Unlock() + if serveStop != nil { + serveStop() + serveStop = nil } - }() - cleanup := func() { - fdExit.SignalExit() - <-exitUffd - } - defer func() { cleanup() }() + return nil, nil + }) - if os.Getenv("GO_GATED") == "1" { - gateCmdFile := os.NewFile(uintptr(7), "gate-cmd") - defer gateCmdFile.Close() + server.Register("ServeResume", func(_ context.Context, _ json.RawMessage) (any, error) { + if !gated { + return nil, errors.New("ServeResume called on a non-gated handler") + } + serveMu.Lock() + defer serveMu.Unlock() + startServe() - gateSyncFile := os.NewFile(uintptr(8), "gate-sync") - defer gateSyncFile.Close() + return nil, nil + }) - startServe := func() func() { - newExit, fdErr := fdexit.New() - if fdErr != nil { - cancel(fmt.Errorf("error creating fd exit: %w", fdErr)) + server.Register("InstallFaultBarrier", func(_ context.Context, raw json.RawMessage) (any, error) { + var args installFaultBarrierArgs + if err := json.Unmarshal(raw, &args); err != nil { + return nil, err + } + token := br.install(uintptr(args.Addr), barrierPoint(args.Point)) - return func() {} - } + return installFaultBarrierResp{Token: token}, nil + }) - done := make(chan struct{}) - go func() { - defer close(done) - if err := uffd.Serve(ctx, newExit); err != nil { - cancel(fmt.Errorf("error serving: %w", err)) - } - }() - - return func() { - newExit.SignalExit() - <-done - newExit.Close() - } + server.Register("WaitFaultHeld", func(ctx context.Context, raw json.RawMessage) (any, error) { + var args tokenArgs + if err := json.Unmarshal(raw, &args); err != nil { + return nil, err } - stopServe := func() { - cleanup() + return nil, br.waitArrived(ctx, args.Token) + }) + + server.Register("ReleaseFault", func(_ context.Context, raw json.RawMessage) (any, error) { + var args tokenArgs + if err := json.Unmarshal(raw, &args); err != nil { + return nil, err } + br.release(args.Token) + return nil, nil + }) + + shutdownCh := make(chan struct{}) + server.Register("Shutdown", func(_ context.Context, _ json.RawMessage) (any, error) { + // Run the actual shutdown asynchronously so the RPC reply + // goes out before we tear the channel down. go func() { - var buf [1]byte - for { - if _, err := gateCmdFile.Read(buf[:]); err != nil { - return - } - - switch buf[0] { - case 'P': - stopServe() - gateSyncFile.Write([]byte{1}) - case 'R': - newStop := startServe() - stopServe = newStop - cleanup = newStop - } - } + close(shutdownCh) }() - } - exitSignal := make(chan os.Signal, 1) - signal.Notify(exitSignal, syscall.SIGUSR1) - defer signal.Stop(exitSignal) + return nil, nil + }) - readyFile := os.NewFile(uintptr(6), "ready") + serveErrCh := make(chan error, 1) + go func() { + serveErrCh <- server.Serve(serveCtx) + }() - closeErr := readyFile.Close() - if closeErr != nil { - return fmt.Errorf("error closing ready file: %w", closeErr) + select { + case <-shutdownCh: + case err := <-serveErrCh: + if err != nil { + fmt.Fprintln(os.Stderr, "rpc server:", err) + } } - select { - case <-ctx.Done(): - return fmt.Errorf("context done: %w: %w", ctx.Err(), context.Cause(ctx)) - case <-exitSignal: - return nil + serveCancel() + + // Release any still-parked barriers so the serve goroutine can + // finish before we ask it to stop. + br.releaseAll() + + serveMu.Lock() + if serveStop != nil { + serveStop() + serveStop = nil } + serveMu.Unlock() + + return nil } -// pageStateEntry is the wire format used between the main test process -// and the serving helper process. State is emitted as a single byte so it -// can be written directly with binary.Write and decoded on the other side. +// pageStateEntry is the wire format for PageStates RPC results. type pageStateEntry struct { - State uint8 - Offset uint64 + State uint8 `json:"state"` + Offset uint64 `json:"offset"` } -// pageStateEntries returns a snapshot of every tracked page and its state. -// It holds the settleRequests write lock so no in-flight faultPage worker -// can mutate the pageTracker while we iterate. +// pageStateEntries returns a snapshot of every tracked page and its +// state. It briefly takes settleRequests.Lock so no in-flight worker +// can mutate the pageTracker while we read it. func (u *Userfaultfd) pageStateEntries() ([]pageStateEntry, error) { u.settleRequests.Lock() u.settleRequests.Unlock() //nolint:staticcheck // SA2001: intentional — settle the read locks. @@ -520,15 +504,175 @@ func (u *Userfaultfd) pageStateEntries() ([]pageStateEntry, error) { u.pageTracker.mu.RLock() defer u.pageTracker.mu.RUnlock() - var entries []pageStateEntry + entries := make([]pageStateEntry, 0, len(u.pageTracker.m)) for addr, state := range u.pageTracker.m { offset, err := u.ma.GetOffset(addr) if err != nil { return nil, fmt.Errorf("address %#x not in mapping: %w", addr, err) } - - entries = append(entries, pageStateEntry{uint8(state), uint64(offset)}) + entries = append(entries, pageStateEntry{State: uint8(state), Offset: uint64(offset)}) } return entries, nil } + +// barrierPoint identifies WHICH hook a barrier should park on. +type barrierPoint uint8 + +const ( + // barrierBeforeRLock parks the worker BEFORE settleRequests.RLock(), + // i.e. before it can read the page state. Use this for the + // stale-source race: a parallel REMOVE batch on the parent loop + // can take the write lock immediately because no worker holds + // the read lock. + barrierBeforeRLock barrierPoint = 1 + // barrierBeforeFaultPage parks the worker AFTER it has taken + // settleRequests.RLock and decided on `source`, but BEFORE the + // actual UFFDIO_COPY syscall. Use this for the in-flight COPY + // deadlock test: the parent's madvise must still return even + // though a worker holds RLock. + barrierBeforeFaultPage barrierPoint = 2 +) + +// installFaultBarrierArgs is the InstallFaultBarrier RPC payload. +type installFaultBarrierArgs struct { + Addr uint64 `json:"addr"` + Point uint8 `json:"point"` +} + +type installFaultBarrierResp struct { + Token uint64 `json:"token"` +} + +type tokenArgs struct { + Token uint64 `json:"token"` +} + +// barrierRegistry is the child-process side of the barrier. The +// hooks installed on Userfaultfd consult this registry by addr+point +// to decide whether to park, and the RPC handlers manipulate it from +// the parent over the socket. +type barrierRegistry struct { + mu sync.Mutex + next uint64 + tokens map[uint64]*barrierSlot + byKey map[barrierKey]uint64 +} + +type barrierKey struct { + addr uintptr + point barrierPoint +} + +type barrierSlot struct { + addr uintptr + point barrierPoint + arrived chan struct{} + release chan struct{} + arrivedOnce sync.Once +} + +func newBarrierRegistry() *barrierRegistry { + return &barrierRegistry{ + tokens: make(map[uint64]*barrierSlot), + byKey: make(map[barrierKey]uint64), + } +} + +func (b *barrierRegistry) install(addr uintptr, point barrierPoint) uint64 { + b.mu.Lock() + defer b.mu.Unlock() + + b.next++ + token := b.next + slot := &barrierSlot{ + addr: addr, + point: point, + arrived: make(chan struct{}), + release: make(chan struct{}), + } + b.tokens[token] = slot + b.byKey[barrierKey{addr, point}] = token + + return token +} + +func (b *barrierRegistry) lookupByAddr(addr uintptr, point barrierPoint) *barrierSlot { + b.mu.Lock() + defer b.mu.Unlock() + + token, ok := b.byKey[barrierKey{addr, point}] + if !ok { + return nil + } + + return b.tokens[token] +} + +func (b *barrierRegistry) waitArrived(ctx context.Context, token uint64) error { + b.mu.Lock() + slot, ok := b.tokens[token] + b.mu.Unlock() + if !ok { + return fmt.Errorf("unknown barrier token %d", token) + } + + select { + case <-slot.arrived: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +func (b *barrierRegistry) release(token uint64) { + b.mu.Lock() + slot, ok := b.tokens[token] + delete(b.tokens, token) + if ok { + delete(b.byKey, barrierKey{slot.addr, slot.point}) + } + b.mu.Unlock() + + if !ok { + return + } + + select { + case <-slot.release: + default: + close(slot.release) + } +} + +func (b *barrierRegistry) releaseAll() { + b.mu.Lock() + tokens := make([]uint64, 0, len(b.tokens)) + for t := range b.tokens { + tokens = append(tokens, t) + } + b.mu.Unlock() + + for _, t := range tokens { + b.release(t) + } +} + +// hookFor returns the function to assign to a Userfaultfd +// beforeXxxHook field. The returned function is a no-op for any +// (addr, point) pair that hasn't been Install'd, so non-targeted +// faults see no scheduling distortion. +func (b *barrierRegistry) hookFor(point barrierPoint) func(addr uintptr) { + return func(addr uintptr) { + slot := b.lookupByAddr(addr, point) + if slot == nil { + return + } + + slot.arrivedOnce.Do(func() { + close(slot.arrived) + }) + + <-slot.release + } +} diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/helpers_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/helpers_test.go index 003ee1523f..8c6d0bc56b 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/helpers_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/helpers_test.go @@ -4,6 +4,8 @@ import ( "bytes" "context" "fmt" + "io" + "os/exec" "sync" "testing" "time" @@ -29,6 +31,14 @@ type testConfig struct { alwaysWP bool // gated enables pause/resume control over the handler's serve loop. gated bool + // sourcePatcher, if non-nil, is invoked on the random source data + // AFTER it's generated but BEFORE it's written to the on-disk + // content file the child reads. Tests can use this to plant + // deterministic sentinel bytes in the source so the post-test + // assertion can distinguish "post-fix zero-fault" from "pre-fix + // UFFDIO_COPY of stale src bytes" without depending on the + // happenstance value of randomly-generated bytes. + sourcePatcher func([]byte) } type operationMode uint32 @@ -91,14 +101,46 @@ type testHandler struct { pagesize uint64 data *MemorySlicer // pageStatesOnce returns a per-state snapshot of the handler's pageTracker. - // It can only be called once. + // Backed by the PageStates RPC; callable any number of times. + // The "Once" suffix is kept for source-stability with the existing + // test sites. pageStatesOnce func() (handlerPageStates, error) // servePause and serveResume gate the UFFD event loop in the child process. // Tests use them to deterministically drain a batch of REMOVE events // before more faults are processed. servePause func() error serveResume func() error - mutex sync.Mutex + + // client is the RPC channel to the child helper process. nil for + // any future in-process testHandler — every method that uses it + // must guard accordingly. + client *rpcClient + conn io.Closer + cmd *exec.Cmd + + mutex sync.Mutex +} + +// installFaultBarrier asks the child to park the next worker that +// hits `point` for `addr`. Returns a token that must be passed to +// waitFaultHeld and releaseFault. +func (h *testHandler) installFaultBarrier(ctx context.Context, addr uintptr, point barrierPoint) (uint64, error) { + var resp installFaultBarrierResp + err := h.client.Call(ctx, "InstallFaultBarrier", installFaultBarrierArgs{Addr: uint64(addr), Point: uint8(point)}, &resp) + + return resp.Token, err +} + +// waitFaultHeld blocks until the child reports that a worker has +// reached the barrier identified by token. +func (h *testHandler) waitFaultHeld(ctx context.Context, token uint64) error { + return h.client.Call(ctx, "WaitFaultHeld", tokenArgs{Token: token}, nil) +} + +// releaseFault releases a parked worker so it proceeds past the +// barrier. +func (h *testHandler) releaseFault(ctx context.Context, token uint64) error { + return h.client.Call(ctx, "ReleaseFault", tokenArgs{Token: token}, nil) } func (h *testHandler) executeAll(t *testing.T, operations []operation) { diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/rpc_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/rpc_test.go new file mode 100644 index 0000000000..d009546477 --- /dev/null +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/rpc_test.go @@ -0,0 +1,332 @@ +package userfaultfd + +import ( + "context" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "io" + "sync" + "sync/atomic" +) + +// rpc_test.go is a tiny request/response RPC harness for the +// userfaultfd cross-process tests. Before this layer existed the +// parent ↔ child wire was a pile of single-purpose pipes (offsets, +// ready, gate-cmd, gate-sync) plus a SIGUSR2 trigger; every new +// piece of test-only inspection or coordination meant adding another +// fd and another env var. This file replaces all of that with a +// single bidirectional channel: +// +// - Wire format: 4-byte big-endian length prefix, JSON body. +// - Envelope: { ID, Method, Params } / { ID, Result, Error }. +// - Multiple in-flight requests are correlated by ID, so an RPC +// handler is free to block (e.g. WaitFaultHeld) while the parent +// continues issuing other RPCs. +// +// Two pipes are used: rpc-req (parent → child, fd 5 in the child) +// and rpc-resp (child → parent, fd 6 in the child). The other test- +// only fds (uffd dup, content) are unchanged. + +// rpcRequest is the on-wire request envelope. +type rpcRequest struct { + ID uint64 `json:"id"` + Method string `json:"method"` + Params json.RawMessage `json:"params,omitempty"` +} + +// rpcResponse is the on-wire response envelope. Exactly one of Result +// or Error is set per response. +type rpcResponse struct { + ID uint64 `json:"id"` + Result json.RawMessage `json:"result,omitempty"` + Error string `json:"error,omitempty"` +} + +// rpcError is the parent-side error type returned when a server +// handler reports a non-nil error. Tests can errors.Is / errors.As +// against this if they need to match a specific RPC failure. +type rpcError struct { + Method string + Msg string +} + +func (e *rpcError) Error() string { + if e.Method == "" { + return e.Msg + } + + return fmt.Sprintf("rpc %s: %s", e.Method, e.Msg) +} + +// writeFrame writes a length-prefixed JSON frame to w. Caller must +// hold any write mutex if w is shared. +func writeFrame(w io.Writer, body []byte) error { + var hdr [4]byte + binary.BigEndian.PutUint32(hdr[:], uint32(len(body))) + if _, err := w.Write(hdr[:]); err != nil { + return err + } + if _, err := w.Write(body); err != nil { + return err + } + + return nil +} + +// readFrame reads a single length-prefixed JSON frame from r. Returns +// io.EOF cleanly when the peer closes the pipe. +func readFrame(r io.Reader) ([]byte, error) { + var hdr [4]byte + if _, err := io.ReadFull(r, hdr[:]); err != nil { + return nil, err + } + + n := binary.BigEndian.Uint32(hdr[:]) + body := make([]byte, n) + if _, err := io.ReadFull(r, body); err != nil { + return nil, err + } + + return body, nil +} + +// rpcClient is the parent side of the RPC. Call is safe to invoke +// concurrently from any number of goroutines. +type rpcClient struct { + w io.Writer + + writeMu sync.Mutex + nextID atomic.Uint64 + + mu sync.Mutex + pending map[uint64]chan rpcResponse + closed bool + readerErr error +} + +// newRPCClient starts the response reader goroutine. It does not own +// the lifecycle of r/w; the caller must close them. +func newRPCClient(r io.Reader, w io.Writer) *rpcClient { + c := &rpcClient{ + w: w, + pending: make(map[uint64]chan rpcResponse), + } + + go c.readLoop(r) + + return c +} + +func (c *rpcClient) readLoop(r io.Reader) { + for { + body, err := readFrame(r) + if err != nil { + c.mu.Lock() + c.closed = true + c.readerErr = err + pending := c.pending + c.pending = nil + c.mu.Unlock() + + for _, ch := range pending { + select { + case ch <- rpcResponse{Error: fmt.Sprintf("rpc client closed: %v", err)}: + default: + } + close(ch) + } + + return + } + + var resp rpcResponse + if err := json.Unmarshal(body, &resp); err != nil { + // Malformed frame from the child — drop it but keep going; + // the test will time out on the affected Call rather than + // the whole client wedging silently. + continue + } + + c.mu.Lock() + ch, ok := c.pending[resp.ID] + if ok { + delete(c.pending, resp.ID) + } + c.mu.Unlock() + + if !ok { + continue + } + + ch <- resp + close(ch) + } +} + +// Call invokes a remote method. params is JSON-marshaled (pass nil for +// no params). result, if non-nil, is JSON-unmarshaled from the reply's +// Result field. Errors: +// +// - context cancellation returns the context error +// - a non-empty Error field on the response returns *rpcError +// - if the read loop has died, returns the underlying read error +func (c *rpcClient) Call(ctx context.Context, method string, params, result any) error { + id := c.nextID.Add(1) + + var paramsRaw json.RawMessage + if params != nil { + b, err := json.Marshal(params) + if err != nil { + return fmt.Errorf("rpc %s: marshal params: %w", method, err) + } + paramsRaw = b + } + + body, err := json.Marshal(rpcRequest{ID: id, Method: method, Params: paramsRaw}) + if err != nil { + return fmt.Errorf("rpc %s: marshal envelope: %w", method, err) + } + + ch := make(chan rpcResponse, 1) + + c.mu.Lock() + if c.closed { + c.mu.Unlock() + + return fmt.Errorf("rpc %s: client closed: %w", method, c.readerErr) + } + c.pending[id] = ch + c.mu.Unlock() + + c.writeMu.Lock() + err = writeFrame(c.w, body) + c.writeMu.Unlock() + + if err != nil { + c.mu.Lock() + delete(c.pending, id) + c.mu.Unlock() + + return fmt.Errorf("rpc %s: write: %w", method, err) + } + + select { + case <-ctx.Done(): + c.mu.Lock() + delete(c.pending, id) + c.mu.Unlock() + + return ctx.Err() + case resp, ok := <-ch: + if !ok { + return fmt.Errorf("rpc %s: client closed", method) + } + if resp.Error != "" { + return &rpcError{Method: method, Msg: resp.Error} + } + if result != nil && len(resp.Result) > 0 { + if err := json.Unmarshal(resp.Result, result); err != nil { + return fmt.Errorf("rpc %s: unmarshal result: %w", method, err) + } + } + + return nil + } +} + +// rpcHandler is the function signature a method handler must satisfy. +// params is the raw JSON params from the request; the handler returns +// any value to JSON-marshal back as the result, plus an error. +type rpcHandler func(ctx context.Context, params json.RawMessage) (any, error) + +// rpcServer is the child side. Each request is dispatched to its own +// goroutine so handlers may block (e.g. WaitFaultHeld) without +// stalling other RPCs. +type rpcServer struct { + r io.Reader + w io.Writer + + writeMu sync.Mutex + handlers map[string]rpcHandler + + wg sync.WaitGroup +} + +func newRPCServer(r io.Reader, w io.Writer) *rpcServer { + return &rpcServer{ + r: r, + w: w, + handlers: make(map[string]rpcHandler), + } +} + +// Register adds a handler for the given method. Must be called BEFORE +// Serve. +func (s *rpcServer) Register(method string, h rpcHandler) { + s.handlers[method] = h +} + +// Serve reads frames from r and dispatches them in goroutines until +// the reader returns EOF / error. Returns when the reader is done; it +// then waits for all in-flight handlers to finish. +func (s *rpcServer) Serve(ctx context.Context) error { + for { + body, err := readFrame(s.r) + if err != nil { + s.wg.Wait() + if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) { + return nil + } + + return err + } + + var req rpcRequest + if err := json.Unmarshal(body, &req); err != nil { + s.send(rpcResponse{ID: 0, Error: fmt.Sprintf("malformed request: %v", err)}) + + continue + } + + handler, ok := s.handlers[req.Method] + if !ok { + s.send(rpcResponse{ID: req.ID, Error: fmt.Sprintf("unknown method %q", req.Method)}) + + continue + } + + s.wg.Add(1) + go func(req rpcRequest, h rpcHandler) { + defer s.wg.Done() + + result, err := h(ctx, req.Params) + resp := rpcResponse{ID: req.ID} + if err != nil { + resp.Error = err.Error() + } else if result != nil { + b, err := json.Marshal(result) + if err != nil { + resp.Error = fmt.Sprintf("marshal result: %v", err) + } else { + resp.Result = b + } + } + + s.send(resp) + }(req, handler) + } +} + +func (s *rpcServer) send(resp rpcResponse) { + body, err := json.Marshal(resp) + if err != nil { + // Best-effort: respond with an error envelope using the empty body. + body, _ = json.Marshal(rpcResponse{ID: resp.ID, Error: fmt.Sprintf("marshal response: %v", err)}) + } + + s.writeMu.Lock() + defer s.writeMu.Unlock() + _ = writeFrame(s.w, body) +} diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go index 3e34589d8e..da2200163b 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go @@ -70,6 +70,29 @@ type Userfaultfd struct { // if no new UFFD events arrive to wake poll. wakeupPipe [2]int + // Test-only synchronisation hooks. Both default to nil and the nil + // branch costs a single un-predictable load + branch in the hot path, + // so they are effectively free in production. They MUST only be set + // from _test.go files. They let tests park a worker goroutine at a + // known point so a racing event (REMOVE, MISSING) can be issued + // deterministically before the worker proceeds. + // + // - beforeWorkerRLockHook: called as the very first thing in the + // worker goroutine, BEFORE settleRequests.RLock(). At this point + // the test holds the goroutine before it can claim the read lock, + // so a parallel REMOVE batch in the parent loop can take the + // write lock immediately and mutate page state. This is the + // window the production fix actually closes — the post-fix + // worker reads state under RLock, so it observes the REMOVE. + // + // - beforeFaultPageHook: called inside the worker AFTER RLock and + // AFTER the state-vs-source decision, but BEFORE the actual + // UFFDIO_COPY/UFFDIO_ZEROPAGE syscall. Lets a test simulate a + // slow data fetch / in-flight COPY so a parent madvise can race + // against an in-flight worker. + beforeWorkerRLockHook func(addr uintptr) + beforeFaultPageHook func(addr uintptr) + logger logger.Logger } @@ -312,6 +335,16 @@ func (u *Userfaultfd) Serve( } u.wg.Go(func() error { + // Test-only barrier: park the worker BEFORE it takes + // RLock. While parked, the parent loop is free to take + // settleRequests.Lock() to process REMOVE events, which + // is exactly the window the production fix had to close + // (pre-fix the worker had already captured a stale state + // snapshot in the parent loop). + if hook := u.beforeWorkerRLockHook; hook != nil { + hook(addr) + } + // The RLock must be acquired inside the goroutine — and it must be acquired // BEFORE we read the pageTracker / u.src state — so that the read+act+commit // sequence (state lookup → faultPage → setState) is atomic with respect to @@ -353,6 +386,15 @@ func (u *Userfaultfd) Serve( accessType = block.Write } + // Test-only barrier: park the worker AFTER state has been + // read under RLock but BEFORE the actual UFFDIO_* syscall. + // Lets tests simulate a slow / in-flight COPY so the + // parent's madvise (and the subsequent REMOVE batch) can + // race against a worker that already holds RLock. + if hook := u.beforeFaultPageHook; hook != nil { + hook(addr) + } + handled, err := u.faultPage( ctx, addr, From 0aac86ed9c39f031096fe5af63913689a0a46897 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Mon, 27 Apr 2026 16:24:45 -0700 Subject: [PATCH 61/63] test(uffd): cover stale-source / madvise-deadlock / serve-ordering races deterministically MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three new race tests built on the unix-socket RPC harness and the test-only fault-barrier hooks. None of them use sleeps, retries, or soak loops — each test installs explicit barriers on the child's worker goroutine, drives the racing kernel operation from the parent, and asserts on a concrete post-state. - TestStaleSourceRaceMissingAndRemove: regression test for the production fix. Plants a non-zero sentinel into the source page, parks the worker via barrierBeforeRLock, fires madvise, waits for the REMOVE batch to commit, releases the worker, then asserts the page is zero-filled. Pre-fix the worker UFFDIO_COPYs the planted sentinel because it captured `source = u.src` in the parent loop before the REMOVE landed; post-fix it re-reads state under RLock and zero-faults. Wallclock < 50ms / variant. - TestNoMadviseDeadlockWithInflightCopy: liveness regression test for the user-visible symptom that originally surfaced the race. Parks the worker via barrierBeforeFaultPage (i.e. holding RLock), fires madvise, asserts madvise returns within 2s. Catches any future change that accidentally couples readEvents() to settleRequests as a fast assertion failure rather than a 30m CI timeout. Wallclock < 50ms / variant. - TestFaultedShortCircuitOrdering: smoke test on the REMOVE-then-pagefault batch ordering using the gated harness. Pins the invariant that REMOVE batches drain before pagefault dispatch in a single Serve iteration. Wallclock ~120ms / variant. All three pass -count=20 -timeout=30s deterministically. --- .../pkg/sandbox/uffd/userfaultfd/race_test.go | 391 ++++++++++++++++++ 1 file changed, 391 insertions(+) create mode 100644 packages/orchestrator/pkg/sandbox/uffd/userfaultfd/race_test.go diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/race_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/race_test.go new file mode 100644 index 0000000000..88a03e4f3d --- /dev/null +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/race_test.go @@ -0,0 +1,391 @@ +package userfaultfd + +import ( + "context" + "fmt" + "testing" + "time" + "unsafe" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/sys/unix" + + "github.com/e2b-dev/infra/packages/orchestrator/pkg/sandbox/uffd/testutils" + "github.com/e2b-dev/infra/packages/shared/pkg/storage/header" +) + +// raceHappyPathBudget bounds every race test in this file. The whole +// point of these tests is that they detect a regression as a fast, +// targeted assertion rather than as a CI -timeout 30m hang. None of +// these tests should approach this budget on a healthy build. +const raceHappyPathBudget = 5 * time.Second + +// barrierArrivalDeadline is how long the test will wait for a worker +// to reach an installed barrier. The hook fires the first thing in +// the worker goroutine, so on a healthy build it's a sub-millisecond +// rendezvous over the unix-socket RPC. Anything approaching this +// deadline means the handler dispatch is wedged. +const barrierArrivalDeadline = 2 * time.Second + +// madviseBudget is how long we allow MADV_DONTNEED to spend in the +// kernel after we've parked a worker mid-handler. The fix guarantees +// madvise unblocks as soon as the handler drains the REMOVE event +// from the uffd fd, regardless of any worker holding RLock — +// readEvents requires no lock. +const madviseBudget = 2 * time.Second + +// withRaceContext bounds a single race test to raceHappyPathBudget, +// failing with a clear "deadlock" message if the budget is exceeded. +func withRaceContext(t *testing.T, body func(ctx context.Context)) { + t.Helper() + + ctx, cancel := context.WithTimeout(t.Context(), raceHappyPathBudget) + defer cancel() + + done := make(chan struct{}) + go func() { + defer close(done) + body(ctx) + }() + + select { + case <-done: + case <-ctx.Done(): + t.Fatalf("race test exceeded happy-path budget of %s — handler is wedged", raceHappyPathBudget) + } +} + +// TestStaleSourceRaceMissingAndRemove is the deterministic regression +// test for the production fix in Serve(): +// +// - Pre-fix the parent serve loop captured `state == missing` and +// `source = u.src` BEFORE handing the work to a worker goroutine. +// A REMOVE event for the same page that arrived between then and +// the worker actually running would silently leave the worker +// with a stale `source = u.src` snapshot, which it would then +// UFFDIO_COPY into the page that the kernel had just unmapped. +// +// - Post-fix the worker reads pageTracker state INSIDE the +// goroutine, under settleRequests.RLock, atomically with the +// decision of which `source` to use. +// +// The test installs a barrierBeforeRLock on page X (so the worker +// for X parks before it can read state), triggers a MISSING-write +// fault on X from the parent, waits for the worker to park, fires +// MADV_DONTNEED on X (which can take settleRequests.Lock immediately +// — no worker holds RLock), and then releases the worker. After +// release the worker, post-fix, observes state=removed under RLock +// and zero-faults; pre-fix it would have UFFDIO_COPY'd the planted +// sentinel byte from u.src. A direct read of the page contents +// distinguishes the two outcomes deterministically. +func TestStaleSourceRaceMissingAndRemove(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + pagesize uint64 + }{ + {name: "4k", pagesize: header.PageSize}, + {name: "hugepage", pagesize: header.HugepageSize}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + withRaceContext(t, func(ctx context.Context) { + // Plant a deterministic, non-zero sentinel as the + // first byte of the source data for the page we'll + // race on. Pre-fix, the worker would UFFDIO_COPY this + // sentinel into the page after the REMOVE has already + // unmapped it. Post-fix the worker reads + // state == removed under RLock and zero-fills. + // Planting goes through testConfig.sourcePatcher so + // it lands BOTH in the parent's MemorySlicer and in + // the on-disk content file the child reads. + const sentinel = byte(0xC3) + const pageIdx = 1 + pageOffset := int64(pageIdx) * int64(tt.pagesize) + + cfg := testConfig{ + pagesize: tt.pagesize, + numberOfPages: 4, + sourcePatcher: func(content []byte) { + content[pageOffset] = sentinel + }, + } + + h, err := configureCrossProcessTest(t, cfg) + require.NoError(t, err) + + memStart := uintptr(unsafe.Pointer(&(*h.memoryArea)[0])) + addr := memStart + uintptr(pageIdx)*uintptr(tt.pagesize) + + token, err := h.installFaultBarrier(ctx, addr, barrierBeforeRLock) + require.NoError(t, err) + + // Trigger a READ fault (NOT a write — a write would + // overwrite the very byte we want to inspect to + // distinguish the two outcomes). h.executeRead does + // the touch + content check; we run it in a goroutine + // because it blocks on the fault until we release the + // barrier. + readErrCh := make(chan error, 1) + go func() { + readErrCh <- h.executeRead(ctx, operation{offset: pageOffset, mode: operationModeRead}) + }() + + // Wait for the worker for `addr` to park at the + // pre-RLock barrier. + waitCtx, waitCancel := context.WithTimeout(ctx, barrierArrivalDeadline) + err = h.waitFaultHeld(waitCtx, token) + waitCancel() + require.NoError(t, err, "worker for page %d (addr %#x) did not park at barrier", pageIdx, addr) + + // Fire MADV_DONTNEED on the same page from the + // parent. The serve loop can take Lock immediately + // because the parked worker has not yet acquired + // RLock. + madviseCtx, madviseCancel := context.WithTimeout(ctx, madviseBudget) + err = h.executeRemove(operation{offset: pageOffset, mode: operationModeRemove}) + madviseCancel() + _ = madviseCtx + require.NoError(t, err, "MADV_DONTNEED on page %d did not return — handler dispatch wedged", pageIdx) + + // Wait for the handler to commit setState(removed). + // A tight poll loop with a hard deadline is used + // rather than a sleep — the transition is + // microseconds in the happy path. + require.NoError(t, waitForState(ctx, h, uint64(pageOffset), removed, barrierArrivalDeadline), + "handler did not transition page %d to `removed` after MADV_DONTNEED", pageIdx) + + // Release the parked worker. Post-fix it will + // observe state == removed and zero-fault; pre-fix + // it would proceed with the captured stale source. + require.NoError(t, h.releaseFault(ctx, token)) + + select { + case err := <-readErrCh: + // Pre-fix: executeRead's bytes.Equal succeeds + // (page contains src bytes), so err == nil but + // the page is observably wrong. Post-fix: + // bytes.Equal fails (page is zero-filled), so + // err != nil. We use the page-content assertion + // below instead of relying on this side-channel. + _ = err + case <-ctx.Done(): + t.Fatalf("read of page %d did not unblock after barrier release", pageIdx) + } + + // THE bug-detection assertion: post-fix the page + // MUST be zero-filled. Pre-fix the worker + // UFFDIO_COPY'd the planted sentinel. + page := (*h.memoryArea)[pageOffset : pageOffset+int64(tt.pagesize)] + assert.Equalf(t, byte(0), page[0], + "page %d first byte: want 0 (post-fix zero-fault for `removed` state), got %#x — "+ + "if this equals the sentinel %#x, the worker used a stale `source = u.src` snapshot (regression)", + pageIdx, page[0], sentinel, + ) + + // Sanity: verify with /proc/self/pagemap that the + // page is in fact present after the racing read was + // served (worker re-mapped it as zero). + pagemap, err := testutils.NewPagemapReader() + require.NoError(t, err) + defer pagemap.Close() + entry, err := pagemap.ReadEntry(addr) + require.NoError(t, err) + assert.True(t, entry.IsPresent(), "page %d should be present after the racing read", pageIdx) + }) + }) + } +} + +// TestNoMadviseDeadlockWithInflightCopy is a liveness regression test +// for the user-visible symptom that originally surfaced the stale- +// source race: the orchestrator's parent madvise(MADV_DONTNEED) +// blocking forever because the UFFD handler loop was wedged behind a +// worker. +// +// The harness parks the worker AFTER it has taken settleRequests.RLock +// AND captured `source` (i.e. as if its UFFDIO_COPY was in flight). +// From the parent we then issue MADV_DONTNEED on the same page and +// require that madvise returns within `madviseBudget`. madvise +// unblocks as soon as the handler's readEvents drains the REMOVE +// event, and readEvents requires no lock — so any future change that +// accidentally couples readEvents to settleRequests fails this test +// at the `madviseBudget` boundary instead of as a 30-minute CI +// timeout. +func TestNoMadviseDeadlockWithInflightCopy(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + pagesize uint64 + }{ + {name: "4k", pagesize: header.PageSize}, + {name: "hugepage", pagesize: header.HugepageSize}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + withRaceContext(t, func(ctx context.Context) { + cfg := testConfig{ + pagesize: tt.pagesize, + numberOfPages: 4, + } + + h, err := configureCrossProcessTest(t, cfg) + require.NoError(t, err) + + const pageIdx = 2 + pageOffset := int64(pageIdx) * int64(tt.pagesize) + + memStart := uintptr(unsafe.Pointer(&(*h.memoryArea)[0])) + addr := memStart + uintptr(pageIdx)*uintptr(tt.pagesize) + + token, err := h.installFaultBarrier(ctx, addr, barrierBeforeFaultPage) + require.NoError(t, err) + + writeErrCh := make(chan error, 1) + go func() { + writeErrCh <- h.executeWrite(ctx, operation{offset: pageOffset, mode: operationModeWrite}) + }() + + waitCtx, waitCancel := context.WithTimeout(ctx, barrierArrivalDeadline) + err = h.waitFaultHeld(waitCtx, token) + waitCancel() + require.NoError(t, err, "worker for page %d (addr %#x) did not park at pre-COPY barrier", pageIdx, addr) + + // Worker is parked AFTER RLock. Issue MADV_DONTNEED + // on the same page from the parent. The handler's + // readEvents must drain the REMOVE event (so madvise + // returns) even while the worker holds RLock. + madviseDone := make(chan error, 1) + go func() { + madviseDone <- unix.Madvise((*h.memoryArea)[pageOffset:pageOffset+int64(tt.pagesize)], unix.MADV_DONTNEED) + }() + + select { + case err := <-madviseDone: + require.NoError(t, err) + case <-time.After(madviseBudget): + _ = h.releaseFault(ctx, token) + <-writeErrCh + t.Fatalf("DEADLOCK: madvise(MADV_DONTNEED) on page %d did not return within %s "+ + "while a worker was parked holding settleRequests.RLock — readEvents must not require any lock", + pageIdx, madviseBudget) + } + + require.NoError(t, h.releaseFault(ctx, token)) + + select { + case err := <-writeErrCh: + require.NoError(t, err) + case <-ctx.Done(): + t.Fatalf("user-side write of page %d did not unblock after barrier release", pageIdx) + } + }) + }) + } +} + +// TestFaultedShortCircuitOrdering uses the gated harness to +// deterministically queue a WRITE pagefault for a fresh page AND a +// REMOVE for an already-faulted page in the SAME serve-loop +// iteration. After resume, the post-batch state is asserted: the +// REMOVE'd page is `removed` and the racing-write page is `faulted`. +// +// Both pre-fix and post-fix code reach the same end state for this +// scenario (REMOVE batch runs before the pagefault dispatch loop in +// every Serve iteration). This test guards the batch-processing +// invariant itself: any future change that, for example, dispatched +// pagefaults before draining REMOVEs would fail this test as a +// concrete state-mismatch assertion rather than a 30-minute hang. +func TestFaultedShortCircuitOrdering(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + pagesize uint64 + }{ + {name: "4k", pagesize: header.PageSize}, + {name: "hugepage", pagesize: header.HugepageSize}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + withRaceContext(t, func(_ context.Context) { + cfg := testConfig{ + pagesize: tt.pagesize, + numberOfPages: 2, + gated: true, + operations: []operation{ + {offset: 0, mode: operationModeRead}, + {mode: operationModeServePause}, + {offset: 0, mode: operationModeRemove, async: true}, + {mode: operationModeSleep}, + {offset: int64(tt.pagesize), mode: operationModeWrite, async: true}, + {mode: operationModeSleep}, + {mode: operationModeServeResume}, + }, + } + + h, err := configureCrossProcessTest(t, cfg) + require.NoError(t, err) + + h.executeAll(t, cfg.operations) + + states, err := h.pageStatesOnce() + require.NoError(t, err) + + assert.Contains(t, states.removed, uint(0), + "page 0 should be `removed` after REMOVE batch (got removed=%v faulted=%v)", + states.removed, states.faulted, + ) + assert.Contains(t, states.faulted, uint(tt.pagesize), + "page 1 (offset %d) should be `faulted` after the racing write was served (got removed=%v faulted=%v)", + tt.pagesize, states.removed, states.faulted, + ) + }) + }) + } +} + +// waitForState polls the child's PageStates RPC until the page at +// the given offset reaches `want` or `deadline` elapses. Polling is +// deliberately tight (no fixed sleep) — the state transition we care +// about is microseconds. +func waitForState(ctx context.Context, h *testHandler, offset uint64, want pageState, deadline time.Duration) error { + end := time.Now().Add(deadline) + for { + states, err := h.pageStatesOnce() + if err != nil { + return err + } + + var bucket []uint + switch want { + case removed: + bucket = states.removed + case faulted: + bucket = states.faulted + } + + for _, off := range bucket { + if uint64(off) == offset { + return nil + } + } + + if time.Now().After(end) { + return fmt.Errorf("page state at offset %d: want %d after %s — last seen removed=%v faulted=%v", + offset, want, deadline, states.removed, states.faulted) + } + + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + } +} From 35d1912fde8dd3b6594a6d6397e8d4113e67f50b Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Mon, 27 Apr 2026 16:24:55 -0700 Subject: [PATCH 62/63] ci: lower orchestrator test timeout to 10m and clarify -parallel 4 rationale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous 30m timeout was load-bearing for the userfaultfd deadlock symptom (the orchestrator suite would silently spin until the runner killed it). With the stale-source race fixed and the deterministic race tests in place, the full orchestrator package now runs in well under a minute even on a contended runner. Drop to 10m, which still leaves ~20× headroom over the steady-state runtime but caps any future regression at a manageable wait. Keep -parallel 4 but rewrite the comment: it is a runner-resource cap (cross-process UFFD tests fork child handler processes; we don't want N-CPU copies racing for the same kernel resources), not a workaround for the deadlock that is now fixed. --- .github/workflows/pr-tests-arm64.yml | 13 ++++++++----- .github/workflows/pr-tests.yml | 13 ++++++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pr-tests-arm64.yml b/.github/workflows/pr-tests-arm64.yml index 478010827e..793872ef6b 100644 --- a/.github/workflows/pr-tests-arm64.yml +++ b/.github/workflows/pr-tests-arm64.yml @@ -117,10 +117,13 @@ jobs: - name: Run tests that require sudo working-directory: ${{ matrix.package }} - # -parallel=4 caps concurrent t.Parallel() tests to keep heavyweight - # cross-process UFFD tests in packages/orchestrator from overwhelming - # the runner and timing out (each test forks a child handler process). - run: sudo -E `which go` test -v -timeout 30m -parallel 4 ${{ matrix.test_path }} + # -parallel=4 caps concurrent t.Parallel() tests as a runner-resource + # cap: heavyweight cross-process UFFD tests in packages/orchestrator + # each fork a child handler process and we don't want N-CPU copies + # racing for the same kernel resources. This is NOT a workaround for + # the stale-source race in pkg/sandbox/uffd/userfaultfd — that is + # fixed and covered by the deterministic race tests in race_test.go. + run: sudo -E `which go` test -v -timeout 10m -parallel 4 ${{ matrix.test_path }} if: matrix.sudo == true - name: Compile test binaries @@ -130,5 +133,5 @@ jobs: - name: Run tests working-directory: ${{ matrix.package }} - run: go test -v -timeout 30m -parallel 4 ${{ matrix.test_path }} + run: go test -v -timeout 10m -parallel 4 ${{ matrix.test_path }} if: matrix.sudo == false diff --git a/.github/workflows/pr-tests.yml b/.github/workflows/pr-tests.yml index d22deeff5d..bc959093a4 100644 --- a/.github/workflows/pr-tests.yml +++ b/.github/workflows/pr-tests.yml @@ -81,15 +81,18 @@ jobs: working-directory: ${{ matrix.package }} run: | # Run tests. The '-E' flag is required to allow root to use the correct cache path. - # -parallel=4 caps concurrent t.Parallel() tests to keep heavyweight - # cross-process UFFD tests in packages/orchestrator from overwhelming - # the runner and timing out (each test forks a child handler process). - sudo -E `which go` test -v -timeout 30m -parallel 4 ${{ matrix.test_path }} + # -parallel=4 caps concurrent t.Parallel() tests as a runner-resource + # cap: heavyweight cross-process UFFD tests in packages/orchestrator + # each fork a child handler process and we don't want N-CPU copies + # racing for the same kernel resources. This is NOT a workaround for + # the stale-source race in pkg/sandbox/uffd/userfaultfd — that is + # fixed and covered by the deterministic race tests in race_test.go. + sudo -E `which go` test -v -timeout 10m -parallel 4 ${{ matrix.test_path }} if: matrix.sudo == true - name: Run tests working-directory: ${{ matrix.package }} - run: go test -v -timeout 30m -parallel 4 ${{ matrix.test_path }} + run: go test -v -timeout 10m -parallel 4 ${{ matrix.test_path }} if: matrix.sudo == false validate-iac: From 4306c1691e34628a64d07e585d46141dffa243dc Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Mon, 27 Apr 2026 18:51:19 -0700 Subject: [PATCH 63/63] refactor(uffd-tests): use net/rpc/jsonrpc instead of hand-rolled envelope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the ~330-line hand-rolled length-prefixed JSON RPC layer (rpc_test.go) in favour of stdlib `net/rpc` + `net/rpc/jsonrpc` over the same unix socket. Concurrent in-flight calls and request-id correlation are handled by the standard library; the test harness only needs to register a single Service struct and dial. Also serialise the gated cross-process tests (`TestRemoveThenWriteGated`, `TestWriteThenRemoveGated`, `TestFaultedShortCircuitOrdering`) by removing `t.Parallel()`. While the handler is in the gated `paused` state, any user thread that triggers a queued pagefault on the registered region is suspended in the kernel's pagefault path. From the Go runtime's perspective that goroutine is "running" (not in syscall, since it's a plain memory store) and cannot be preempted until the fault is served. If a CONCURRENT cross-process test in the same binary triggers a stop-the-world GC pause during this window, STW will wait forever for the suspended goroutine to reach a safe point — the kernel cannot deliver the SIGURG preempt signal until the pagefault is served, and the handler is paused. Running the gated tests sequentially avoids that interleaving while leaving every other test (including the rest of the race suite) on `t.Parallel()`. --- .../userfaultfd/cross_process_helpers_test.go | 410 +++++++++++------- .../sandbox/uffd/userfaultfd/helpers_test.go | 35 +- .../pkg/sandbox/uffd/userfaultfd/race_test.go | 28 +- .../sandbox/uffd/userfaultfd/remove_test.go | 23 +- .../pkg/sandbox/uffd/userfaultfd/rpc_test.go | 332 -------------- 5 files changed, 307 insertions(+), 521 deletions(-) delete mode 100644 packages/orchestrator/pkg/sandbox/uffd/userfaultfd/rpc_test.go diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go index a4dac03578..4148c357b1 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/cross_process_helpers_test.go @@ -8,20 +8,22 @@ package userfaultfd // // All parent ↔ child coordination — readiness, page-state queries, // pause/resume, fault barriers, shutdown — flows over a single Unix -// domain socket using the JSON-RPC harness in rpc_test.go. The only -// fd we still hand off out-of-band is the userfaultfd itself, which -// is a kernel object and has to be passed via ExtraFiles. The -// initial source data is written to a temp file (path passed in an -// env var) because base64-stuffing megabytes through the JSON -// envelope would be silly. +// domain socket using the standard-library net/rpc + jsonrpc codec. +// Each in-flight RPC runs in its own server-side goroutine, so a +// blocking handler (e.g. WaitFaultHeld) does not stall other RPCs. +// The only fd we still hand off out-of-band is the userfaultfd +// itself (kernel object, has to go through ExtraFiles); the initial +// source data is written to a temp file under t.TempDir() because +// base64-stuffing megabytes through the JSON envelope would be silly. import ( "context" "crypto/rand" - "encoding/json" "errors" "fmt" "net" + "net/rpc" + "net/rpc/jsonrpc" "os" "os/exec" "path/filepath" @@ -30,6 +32,7 @@ import ( "sync" "syscall" "testing" + "time" "github.com/stretchr/testify/require" "golang.org/x/sys/unix" @@ -80,8 +83,7 @@ func RandomPages(pagesize, numberOfPages uint64) *MemorySlicer { return NewMemorySlicer(buf, int64(pagesize)) } -// Env vars used by the child helper process. Kept in one place so -// drift between parent (sets) and child (reads) is impossible. +// Env vars used by the child helper process. const ( envHelperFlag = "GO_TEST_HELPER_PROCESS" envSocketPath = "GO_UFFD_SOCKET" @@ -91,9 +93,66 @@ const ( envMmapTotalSize = "GO_UFFD_MMAP_SIZE" envAlwaysWP = "GO_UFFD_ALWAYS_WP" envGated = "GO_UFFD_GATED" + // envBarriers gates the test-only worker hooks. Only race tests + // need them; for everyone else we leave the hook fields nil so + // the hot path stays a single nil-pointer load + branch. + envBarriers = "GO_UFFD_BARRIERS" ) -// Main process, FC in our case +// ---- RPC method types --------------------------------------------------- +// +// net/rpc requires methods of the form: +// +// func (s *Service) Method(args *ArgsT, reply *ReplyT) error +// +// where both args and reply are exported pointer types. For methods +// that take or return nothing meaningful we still need a type — Empty +// fills that role. + +type Empty struct{} + +type PageStatesReply struct { + Entries []pageStateEntry +} + +type FaultBarrierArgs struct { + Addr uint64 + Point uint8 +} + +type FaultBarrierReply struct { + Token uint64 +} + +type TokenArgs struct { + Token uint64 +} + +// pageStateEntry is the wire format for PageStates RPC results. +type pageStateEntry struct { + State uint8 + Offset uint64 +} + +// ---- Parent side -------------------------------------------------------- + +// childForkMu serialises the cmd.Start() call across all parallel +// cross-process tests in this binary. Without it, the duplicated +// uffd fd we hand to one child via ExtraFiles is briefly visible in +// the parent's fd table while ANOTHER concurrent test calls fork() +// — so that other test's child inherits a uffd fd it does not own. +// The leaked fd keeps the original test's uffd kernel object alive +// after its owner closes its end, prevents madvise from completing +// once the owning child exits, and produces hard-to-diagnose +// -parallel-only deadlocks. +// +// Holding the mutex only across cmd.Start (which itself holds the +// process lock for the underlying syscall.ForkExec) is enough — by +// the time Start returns the dup'd fd is already mapped into fd 3 +// in the new child and we close it immediately in the parent below. +var childForkMu sync.Mutex + +// Main process, FC in our case. func configureCrossProcessTest(t *testing.T, tt testConfig) (*testHandler, error) { t.Helper() @@ -149,10 +208,30 @@ func configureCrossProcessTest(t *testing.T, tt testConfig) (*testHandler, error if tt.gated { cmd.Env = append(cmd.Env, envGated+"=1") } + if tt.barriers { + cmd.Env = append(cmd.Env, envBarriers+"=1") + } + + // We hand the uffd fd to the child via ExtraFiles. The child- + // side dup3 inside fork+exec clears CLOEXEC on the destination + // fd (i.e. fd 3 in the child) automatically, so the SOURCE fd + // in our parent should remain CLOEXEC — otherwise every other + // test fork()'d concurrently from this binary inherits a uffd + // it does not own, the kernel keeps the original test's uffd + // alive after its real owner exits, and madvise stops draining. + // At higher -parallel this surfaces as long, hard-to-diagnose + // hangs. + // + // syscall.Dup creates the new fd WITHOUT CLOEXEC, so we + // re-arm it explicitly. Holding childForkMu across the + // dup → cmd.Start window further guarantees no concurrent + // fork can race the F_SETFD. + childForkMu.Lock() dup, err := syscall.Dup(int(uffdFd)) require.NoError(t, err) - if _, err := unix.FcntlInt(uintptr(dup), unix.F_SETFD, 0); err != nil { + if _, err := unix.FcntlInt(uintptr(dup), unix.F_SETFD, unix.FD_CLOEXEC); err != nil { + childForkMu.Unlock() require.NoError(t, err) } @@ -161,8 +240,11 @@ func configureCrossProcessTest(t *testing.T, tt testConfig) (*testHandler, error cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - require.NoError(t, cmd.Start()) + startErr := cmd.Start() uffdFile.Close() + childForkMu.Unlock() + + require.NoError(t, startErr) // Accept the child's connection. Tight deadline so a wedged // child surfaces fast instead of hanging the test. @@ -181,13 +263,15 @@ func configureCrossProcessTest(t *testing.T, tt testConfig) (*testHandler, error case res := <-acceptCh: require.NoError(t, res.err) conn = res.conn - case <-t.Context().Done(): + case <-time.After(10 * time.Second): listener.Close() - require.NoError(t, t.Context().Err()) + _ = cmd.Process.Kill() + _, _ = cmd.Process.Wait() + t.Fatalf("child did not connect within 10s") } listener.Close() - client := newRPCClient(conn, conn) + client := jsonrpc.NewClient(conn) h := &testHandler{ memoryArea: &memoryArea, @@ -199,20 +283,18 @@ func configureCrossProcessTest(t *testing.T, tt testConfig) (*testHandler, error } // WaitReady blocks on the child until its initial setup is done - // (uffd resumed, hooks installed). This is the RPC equivalent of - // the old "ready pipe + read until EOF" handshake. - require.NoError(t, h.client.Call(t.Context(), "WaitReady", nil, nil)) + // (uffd serve goroutine running, hooks installed). The RPC reply + // IS the readiness signal — no separate ready pipe / signal + // needed. + require.NoError(t, h.client.Call("Service.WaitReady", &Empty{}, &Empty{})) t.Cleanup(func() { // Best-effort graceful shutdown via RPC. If the child has // already crashed the RPC will error and we fall back to - // killing the process via cmd.Process.Kill on the next line. - _ = h.client.Call(context.Background(), "Shutdown", nil, nil) - _ = conn.Close() + // killing the process below. + _ = h.client.Call("Service.Shutdown", &Empty{}, &Empty{}) + _ = client.Close() - // cmd.Wait can return ExitError, "signal: killed", or nil - // depending on whether the child exited cleanly. Any of - // those is acceptable here. waitErr := cmd.Wait() if waitErr != nil { var exitErr *exec.ExitError @@ -224,21 +306,21 @@ func configureCrossProcessTest(t *testing.T, tt testConfig) (*testHandler, error if tt.gated { h.servePause = func() error { - return h.client.Call(t.Context(), "ServePause", nil, nil) + return h.client.Call("Service.ServePause", &Empty{}, &Empty{}) } h.serveResume = func() error { - return h.client.Call(t.Context(), "ServeResume", nil, nil) + return h.client.Call("Service.ServeResume", &Empty{}, &Empty{}) } } h.pageStatesOnce = func() (handlerPageStates, error) { - var entries []pageStateEntry - if err := h.client.Call(t.Context(), "PageStates", nil, &entries); err != nil { + var reply PageStatesReply + if err := h.client.Call("Service.PageStates", &Empty{}, &reply); err != nil { return handlerPageStates{}, err } var states handlerPageStates - for _, e := range entries { + for _, e := range reply.Entries { switch pageState(e.State) { case faulted: states.faulted = append(states.faulted, uint(e.Offset)) @@ -255,6 +337,8 @@ func configureCrossProcessTest(t *testing.T, tt testConfig) (*testHandler, error return h, nil } +// ---- Child side --------------------------------------------------------- + // Secondary process, orchestrator in our case. func TestHelperServingProcess(t *testing.T) { t.Parallel() @@ -272,7 +356,7 @@ func TestHelperServingProcess(t *testing.T) { } // crossProcessServe wires up the child side: connects back to the -// parent socket, exposes the RPC surface, and runs uffd.Serve in a +// parent socket, registers the RPC service, and runs uffd.Serve in a // background goroutine that pause/resume RPCs can stop and restart. func crossProcessServe() error { socketPath := os.Getenv(envSocketPath) @@ -339,159 +423,171 @@ func crossProcessServe() error { uffd.defaultCopyMode = UFFDIO_COPY_MODE_WP } - // Wire the deterministic test barriers into the production hook - // fields. Both hooks consult the same per-addr registry below. br := newBarrierRegistry() - uffd.beforeWorkerRLockHook = br.hookFor(barrierBeforeRLock) - uffd.beforeFaultPageHook = br.hookFor(barrierBeforeFaultPage) - - server := newRPCServer(conn, conn) - serveCtx, serveCancel := context.WithCancel(context.Background()) + // Hooks are only wired up when the test asked for them (race + // tests). For everyone else we leave the fields nil so the hot + // path is a single nil-pointer load + branch — keeps the high- + // throughput tests (TestParallelMissingWriteWithPrefault, etc.) + // from paying for a Mutex per fault. + if os.Getenv(envBarriers) == "1" { + uffd.beforeWorkerRLockHook = br.hookFor(barrierBeforeRLock) + uffd.beforeFaultPageHook = br.hookFor(barrierBeforeFaultPage) + } - // Lifecycle of the actual UFFD serve loop. Pause/resume RPCs - // stop and restart this goroutine; Shutdown signals the outer - // loop to exit. We use a small helper to avoid duplicating the - // "create fdexit + go uffd.Serve + wait" pattern. - var serveMu sync.Mutex - var serveStop func() + gated := os.Getenv(envGated) == "1" - startServe := func() { - exit, err := fdexit.New() - if err != nil { - fmt.Fprintln(os.Stderr, "fdexit.New:", err) + svc := &Service{ + uffd: uffd, + br: br, + gated: gated, + shutdown: make(chan struct{}), + } + svc.startServe() - return - } + server := rpc.NewServer() + if err := server.Register(svc); err != nil { + return fmt.Errorf("rpc Register: %w", err) + } - done := make(chan struct{}) - go func() { - defer close(done) - if err := uffd.Serve(serveCtx, exit); err != nil { - fmt.Fprintln(os.Stderr, "uffd.Serve:", err) - } - }() + // Run the codec in a goroutine so we can react to Shutdown + // without depending on the codec returning. + codecDone := make(chan struct{}) + go func() { + defer close(codecDone) + server.ServeCodec(jsonrpc.NewServerCodec(conn)) + }() - serveStop = func() { - _ = exit.SignalExit() - <-done - exit.Close() - } + select { + case <-svc.shutdown: + fmt.Fprintln(os.Stderr, "child: shutdown received") + case <-codecDone: + fmt.Fprintln(os.Stderr, "child: codec done") } - startServe() + // Release any still-parked barriers so the serve goroutine can + // finish, then stop the serve goroutine. + br.releaseAll() + fmt.Fprintln(os.Stderr, "child: barriers released") + svc.stopServe() + fmt.Fprintln(os.Stderr, "child: serve stopped") - gated := os.Getenv(envGated) == "1" + // Closing the conn is sufficient to unblock ServeCodec if it + // hasn't already returned. + _ = conn.Close() + <-codecDone + fmt.Fprintln(os.Stderr, "child: codec exited") - // Track in-flight barrier tokens so Shutdown can release them - // (otherwise a parked worker would never return and the serve - // goroutine would never finish). - server.Register("WaitReady", func(_ context.Context, _ json.RawMessage) (any, error) { - return nil, nil - }) + return nil +} - server.Register("PageStates", func(_ context.Context, _ json.RawMessage) (any, error) { - return uffd.pageStateEntries() - }) +// Service is the RPC surface exposed to the parent. Methods follow +// net/rpc's required signature. +type Service struct { + uffd *Userfaultfd + br *barrierRegistry - server.Register("ServePause", func(_ context.Context, _ json.RawMessage) (any, error) { - if !gated { - return nil, errors.New("ServePause called on a non-gated handler") - } - serveMu.Lock() - defer serveMu.Unlock() - if serveStop != nil { - serveStop() - serveStop = nil - } + gated bool - return nil, nil - }) + mu sync.Mutex + stop func() // currently active serve-stop function, nil if paused + shutdown chan struct{} + closed bool +} - server.Register("ServeResume", func(_ context.Context, _ json.RawMessage) (any, error) { - if !gated { - return nil, errors.New("ServeResume called on a non-gated handler") - } - serveMu.Lock() - defer serveMu.Unlock() - startServe() +func (s *Service) startServe() { + exit, err := fdexit.New() + if err != nil { + fmt.Fprintln(os.Stderr, "fdexit.New:", err) - return nil, nil - }) + return + } - server.Register("InstallFaultBarrier", func(_ context.Context, raw json.RawMessage) (any, error) { - var args installFaultBarrierArgs - if err := json.Unmarshal(raw, &args); err != nil { - return nil, err + done := make(chan struct{}) + go func() { + defer close(done) + if err := s.uffd.Serve(context.Background(), exit); err != nil { + fmt.Fprintln(os.Stderr, "uffd.Serve:", err) } - token := br.install(uintptr(args.Addr), barrierPoint(args.Point)) - - return installFaultBarrierResp{Token: token}, nil - }) + }() - server.Register("WaitFaultHeld", func(ctx context.Context, raw json.RawMessage) (any, error) { - var args tokenArgs - if err := json.Unmarshal(raw, &args); err != nil { - return nil, err - } + s.stop = func() { + _ = exit.SignalExit() + <-done + exit.Close() + } +} - return nil, br.waitArrived(ctx, args.Token) - }) +func (s *Service) stopServe() { + s.mu.Lock() + defer s.mu.Unlock() + if s.stop != nil { + s.stop() + s.stop = nil + } +} - server.Register("ReleaseFault", func(_ context.Context, raw json.RawMessage) (any, error) { - var args tokenArgs - if err := json.Unmarshal(raw, &args); err != nil { - return nil, err - } - br.release(args.Token) +// WaitReady is a no-op handler whose successful reply is the +// readiness signal for the parent. +func (s *Service) WaitReady(_ *Empty, _ *Empty) error { + return nil +} - return nil, nil - }) +func (s *Service) PageStates(_ *Empty, reply *PageStatesReply) error { + entries, err := s.uffd.pageStateEntries() + if err != nil { + return err + } + reply.Entries = entries - shutdownCh := make(chan struct{}) - server.Register("Shutdown", func(_ context.Context, _ json.RawMessage) (any, error) { - // Run the actual shutdown asynchronously so the RPC reply - // goes out before we tear the channel down. - go func() { - close(shutdownCh) - }() + return nil +} - return nil, nil - }) +func (s *Service) ServePause(_ *Empty, _ *Empty) error { + if !s.gated { + return errors.New("ServePause called on a non-gated handler") + } + s.stopServe() - serveErrCh := make(chan error, 1) - go func() { - serveErrCh <- server.Serve(serveCtx) - }() + return nil +} - select { - case <-shutdownCh: - case err := <-serveErrCh: - if err != nil { - fmt.Fprintln(os.Stderr, "rpc server:", err) - } +func (s *Service) ServeResume(_ *Empty, _ *Empty) error { + if !s.gated { + return errors.New("ServeResume called on a non-gated handler") } + s.mu.Lock() + defer s.mu.Unlock() + s.startServe() - serveCancel() + return nil +} - // Release any still-parked barriers so the serve goroutine can - // finish before we ask it to stop. - br.releaseAll() +func (s *Service) InstallFaultBarrier(args *FaultBarrierArgs, reply *FaultBarrierReply) error { + reply.Token = s.br.install(uintptr(args.Addr), barrierPoint(args.Point)) - serveMu.Lock() - if serveStop != nil { - serveStop() - serveStop = nil - } - serveMu.Unlock() + return nil +} + +func (s *Service) WaitFaultHeld(args *TokenArgs, _ *Empty) error { + return s.br.waitArrived(context.Background(), args.Token) +} + +func (s *Service) ReleaseFault(args *TokenArgs, _ *Empty) error { + s.br.release(args.Token) return nil } -// pageStateEntry is the wire format for PageStates RPC results. -type pageStateEntry struct { - State uint8 `json:"state"` - Offset uint64 `json:"offset"` +func (s *Service) Shutdown(_ *Empty, _ *Empty) error { + s.mu.Lock() + defer s.mu.Unlock() + if !s.closed { + s.closed = true + close(s.shutdown) + } + + return nil } // pageStateEntries returns a snapshot of every tracked page and its @@ -516,6 +612,8 @@ func (u *Userfaultfd) pageStateEntries() ([]pageStateEntry, error) { return entries, nil } +// ---- Barrier registry --------------------------------------------------- + // barrierPoint identifies WHICH hook a barrier should park on. type barrierPoint uint8 @@ -534,20 +632,6 @@ const ( barrierBeforeFaultPage barrierPoint = 2 ) -// installFaultBarrierArgs is the InstallFaultBarrier RPC payload. -type installFaultBarrierArgs struct { - Addr uint64 `json:"addr"` - Point uint8 `json:"point"` -} - -type installFaultBarrierResp struct { - Token uint64 `json:"token"` -} - -type tokenArgs struct { - Token uint64 `json:"token"` -} - // barrierRegistry is the child-process side of the barrier. The // hooks installed on Userfaultfd consult this registry by addr+point // to decide whether to park, and the RPC handlers manipulate it from diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/helpers_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/helpers_test.go index 8c6d0bc56b..5cd7752a58 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/helpers_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/helpers_test.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "io" + "net/rpc" "os/exec" "sync" "testing" @@ -31,6 +32,10 @@ type testConfig struct { alwaysWP bool // gated enables pause/resume control over the handler's serve loop. gated bool + // barriers wires up the per-worker fault hooks in the child + // (used by race tests). Off by default so the worker hot path + // stays a single nil-pointer load + branch in non-race tests. + barriers bool // sourcePatcher, if non-nil, is invoked on the random source data // AFTER it's generated but BEFORE it's written to the on-disk // content file the child reads. Tests can use this to plant @@ -111,10 +116,8 @@ type testHandler struct { servePause func() error serveResume func() error - // client is the RPC channel to the child helper process. nil for - // any future in-process testHandler — every method that uses it - // must guard accordingly. - client *rpcClient + // client is the RPC channel to the child helper process. + client *rpc.Client conn io.Closer cmd *exec.Cmd @@ -124,23 +127,31 @@ type testHandler struct { // installFaultBarrier asks the child to park the next worker that // hits `point` for `addr`. Returns a token that must be passed to // waitFaultHeld and releaseFault. -func (h *testHandler) installFaultBarrier(ctx context.Context, addr uintptr, point barrierPoint) (uint64, error) { - var resp installFaultBarrierResp - err := h.client.Call(ctx, "InstallFaultBarrier", installFaultBarrierArgs{Addr: uint64(addr), Point: uint8(point)}, &resp) +func (h *testHandler) installFaultBarrier(_ context.Context, addr uintptr, point barrierPoint) (uint64, error) { + var reply FaultBarrierReply + err := h.client.Call("Service.InstallFaultBarrier", &FaultBarrierArgs{Addr: uint64(addr), Point: uint8(point)}, &reply) - return resp.Token, err + return reply.Token, err } // waitFaultHeld blocks until the child reports that a worker has -// reached the barrier identified by token. +// reached the barrier identified by token. The wait is bounded via +// context by issuing the call on a goroutine and racing it against +// ctx; net/rpc's Call doesn't take a context directly. func (h *testHandler) waitFaultHeld(ctx context.Context, token uint64) error { - return h.client.Call(ctx, "WaitFaultHeld", tokenArgs{Token: token}, nil) + call := h.client.Go("Service.WaitFaultHeld", &TokenArgs{Token: token}, &Empty{}, nil) + select { + case <-call.Done: + return call.Error + case <-ctx.Done(): + return ctx.Err() + } } // releaseFault releases a parked worker so it proceeds past the // barrier. -func (h *testHandler) releaseFault(ctx context.Context, token uint64) error { - return h.client.Call(ctx, "ReleaseFault", tokenArgs{Token: token}, nil) +func (h *testHandler) releaseFault(_ context.Context, token uint64) error { + return h.client.Call("Service.ReleaseFault", &TokenArgs{Token: token}, &Empty{}) } func (h *testHandler) executeAll(t *testing.T, operations []operation) { diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/race_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/race_test.go index 88a03e4f3d..b742ef25e2 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/race_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/race_test.go @@ -109,6 +109,7 @@ func TestStaleSourceRaceMissingAndRemove(t *testing.T) { cfg := testConfig{ pagesize: tt.pagesize, numberOfPages: 4, + barriers: true, sourcePatcher: func(content []byte) { content[pageOffset] = sentinel }, @@ -232,6 +233,7 @@ func TestNoMadviseDeadlockWithInflightCopy(t *testing.T) { cfg := testConfig{ pagesize: tt.pagesize, numberOfPages: 4, + barriers: true, } h, err := configureCrossProcessTest(t, cfg) @@ -301,9 +303,19 @@ func TestNoMadviseDeadlockWithInflightCopy(t *testing.T) { // invariant itself: any future change that, for example, dispatched // pagefaults before draining REMOVEs would fail this test as a // concrete state-mismatch assertion rather than a 30-minute hang. +// +// NOTE: this test deliberately does NOT call t.Parallel(). While the +// handler is in the gated `paused` state, the user thread that +// triggered the queued WRITE fault is suspended in the kernel's +// pagefault path. From the Go runtime's perspective that goroutine +// is "running" (not in syscall, since it's a plain memory store) but +// can't be preempted. If a CONCURRENT cross-process test in the same +// binary triggers a stop-the-world GC pause during this window, STW +// will wait forever for the suspended goroutine to reach a safe +// point — the kernel can't deliver the SIGURG preempt signal until +// the pagefault is served, and the handler is paused. Running this +// test sequentially avoids that interleaving. func TestFaultedShortCircuitOrdering(t *testing.T) { - t.Parallel() - tests := []struct { name string pagesize uint64 @@ -352,10 +364,14 @@ func TestFaultedShortCircuitOrdering(t *testing.T) { } // waitForState polls the child's PageStates RPC until the page at -// the given offset reaches `want` or `deadline` elapses. Polling is -// deliberately tight (no fixed sleep) — the state transition we care -// about is microseconds. +// the given offset reaches `want` or `deadline` elapses. Each RPC +// round-trip is microseconds-to-low-milliseconds; we yield with a +// small sleep between polls so the harness doesn't burn an entire +// CPU on tight-loop encoding while the rest of the suite is also +// running cross-process tests. func waitForState(ctx context.Context, h *testHandler, offset uint64, want pageState, deadline time.Duration) error { + const pollInterval = 1 * time.Millisecond + end := time.Now().Add(deadline) for { states, err := h.pageStatesOnce() @@ -385,7 +401,7 @@ func waitForState(ctx context.Context, h *testHandler, offset uint64, want pageS select { case <-ctx.Done(): return ctx.Err() - default: + case <-time.After(pollInterval): } } } diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/remove_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/remove_test.go index 3f96d555c1..6ec229f78f 100644 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/remove_test.go +++ b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/remove_test.go @@ -156,9 +156,20 @@ func TestRemoveThenFault(t *testing.T) { // succeeds without faulting because MADV_DONTNEED blocks (waiting for ack) // and doesn't unmap the page until the handler processes the event. // When the handler resumes, it only sees the REMOVE — no MISSING fault. +// +// NOTE: this test (and the other gated tests below) deliberately does +// NOT call t.Parallel(). While the handler is paused, any user thread +// that triggers a queued pagefault on the registered region is +// suspended in the kernel's pagefault path. From the Go runtime's +// perspective that goroutine is "running" (not in syscall, since it's +// a plain memory store) and cannot be preempted until the fault is +// served. If a CONCURRENT cross-process test in the same binary +// triggers a stop-the-world GC pause during this window, STW will +// wait forever for the suspended goroutine to reach a safe point — +// the kernel cannot deliver the SIGURG preempt signal until the +// pagefault is served, and the handler is paused. Running the gated +// tests sequentially avoids that interleaving. func TestRemoveThenWriteGated(t *testing.T) { - t.Parallel() - tests := []testConfig{ { name: "4k gated remove with concurrent write", @@ -194,8 +205,6 @@ func TestRemoveThenWriteGated(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - t.Parallel() - h, err := configureCrossProcessTest(t, tt) require.NoError(t, err) @@ -218,9 +227,9 @@ func TestRemoveThenWriteGated(t *testing.T) { // was queued first. The write to a missing page triggers MISSING (queued first), // then MADV_DONTNEED triggers REMOVE (queued second). When the handler resumes, // it processes REMOVE first, then MISSING — the write is not skipped. +// +// See TestRemoveThenWriteGated for why this test is not parallel. func TestWriteThenRemoveGated(t *testing.T) { - t.Parallel() - tests := []testConfig{ { name: "4k write then remove in same batch", @@ -258,8 +267,6 @@ func TestWriteThenRemoveGated(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - t.Parallel() - h, err := configureCrossProcessTest(t, tt) require.NoError(t, err) diff --git a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/rpc_test.go b/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/rpc_test.go deleted file mode 100644 index d009546477..0000000000 --- a/packages/orchestrator/pkg/sandbox/uffd/userfaultfd/rpc_test.go +++ /dev/null @@ -1,332 +0,0 @@ -package userfaultfd - -import ( - "context" - "encoding/binary" - "encoding/json" - "errors" - "fmt" - "io" - "sync" - "sync/atomic" -) - -// rpc_test.go is a tiny request/response RPC harness for the -// userfaultfd cross-process tests. Before this layer existed the -// parent ↔ child wire was a pile of single-purpose pipes (offsets, -// ready, gate-cmd, gate-sync) plus a SIGUSR2 trigger; every new -// piece of test-only inspection or coordination meant adding another -// fd and another env var. This file replaces all of that with a -// single bidirectional channel: -// -// - Wire format: 4-byte big-endian length prefix, JSON body. -// - Envelope: { ID, Method, Params } / { ID, Result, Error }. -// - Multiple in-flight requests are correlated by ID, so an RPC -// handler is free to block (e.g. WaitFaultHeld) while the parent -// continues issuing other RPCs. -// -// Two pipes are used: rpc-req (parent → child, fd 5 in the child) -// and rpc-resp (child → parent, fd 6 in the child). The other test- -// only fds (uffd dup, content) are unchanged. - -// rpcRequest is the on-wire request envelope. -type rpcRequest struct { - ID uint64 `json:"id"` - Method string `json:"method"` - Params json.RawMessage `json:"params,omitempty"` -} - -// rpcResponse is the on-wire response envelope. Exactly one of Result -// or Error is set per response. -type rpcResponse struct { - ID uint64 `json:"id"` - Result json.RawMessage `json:"result,omitempty"` - Error string `json:"error,omitempty"` -} - -// rpcError is the parent-side error type returned when a server -// handler reports a non-nil error. Tests can errors.Is / errors.As -// against this if they need to match a specific RPC failure. -type rpcError struct { - Method string - Msg string -} - -func (e *rpcError) Error() string { - if e.Method == "" { - return e.Msg - } - - return fmt.Sprintf("rpc %s: %s", e.Method, e.Msg) -} - -// writeFrame writes a length-prefixed JSON frame to w. Caller must -// hold any write mutex if w is shared. -func writeFrame(w io.Writer, body []byte) error { - var hdr [4]byte - binary.BigEndian.PutUint32(hdr[:], uint32(len(body))) - if _, err := w.Write(hdr[:]); err != nil { - return err - } - if _, err := w.Write(body); err != nil { - return err - } - - return nil -} - -// readFrame reads a single length-prefixed JSON frame from r. Returns -// io.EOF cleanly when the peer closes the pipe. -func readFrame(r io.Reader) ([]byte, error) { - var hdr [4]byte - if _, err := io.ReadFull(r, hdr[:]); err != nil { - return nil, err - } - - n := binary.BigEndian.Uint32(hdr[:]) - body := make([]byte, n) - if _, err := io.ReadFull(r, body); err != nil { - return nil, err - } - - return body, nil -} - -// rpcClient is the parent side of the RPC. Call is safe to invoke -// concurrently from any number of goroutines. -type rpcClient struct { - w io.Writer - - writeMu sync.Mutex - nextID atomic.Uint64 - - mu sync.Mutex - pending map[uint64]chan rpcResponse - closed bool - readerErr error -} - -// newRPCClient starts the response reader goroutine. It does not own -// the lifecycle of r/w; the caller must close them. -func newRPCClient(r io.Reader, w io.Writer) *rpcClient { - c := &rpcClient{ - w: w, - pending: make(map[uint64]chan rpcResponse), - } - - go c.readLoop(r) - - return c -} - -func (c *rpcClient) readLoop(r io.Reader) { - for { - body, err := readFrame(r) - if err != nil { - c.mu.Lock() - c.closed = true - c.readerErr = err - pending := c.pending - c.pending = nil - c.mu.Unlock() - - for _, ch := range pending { - select { - case ch <- rpcResponse{Error: fmt.Sprintf("rpc client closed: %v", err)}: - default: - } - close(ch) - } - - return - } - - var resp rpcResponse - if err := json.Unmarshal(body, &resp); err != nil { - // Malformed frame from the child — drop it but keep going; - // the test will time out on the affected Call rather than - // the whole client wedging silently. - continue - } - - c.mu.Lock() - ch, ok := c.pending[resp.ID] - if ok { - delete(c.pending, resp.ID) - } - c.mu.Unlock() - - if !ok { - continue - } - - ch <- resp - close(ch) - } -} - -// Call invokes a remote method. params is JSON-marshaled (pass nil for -// no params). result, if non-nil, is JSON-unmarshaled from the reply's -// Result field. Errors: -// -// - context cancellation returns the context error -// - a non-empty Error field on the response returns *rpcError -// - if the read loop has died, returns the underlying read error -func (c *rpcClient) Call(ctx context.Context, method string, params, result any) error { - id := c.nextID.Add(1) - - var paramsRaw json.RawMessage - if params != nil { - b, err := json.Marshal(params) - if err != nil { - return fmt.Errorf("rpc %s: marshal params: %w", method, err) - } - paramsRaw = b - } - - body, err := json.Marshal(rpcRequest{ID: id, Method: method, Params: paramsRaw}) - if err != nil { - return fmt.Errorf("rpc %s: marshal envelope: %w", method, err) - } - - ch := make(chan rpcResponse, 1) - - c.mu.Lock() - if c.closed { - c.mu.Unlock() - - return fmt.Errorf("rpc %s: client closed: %w", method, c.readerErr) - } - c.pending[id] = ch - c.mu.Unlock() - - c.writeMu.Lock() - err = writeFrame(c.w, body) - c.writeMu.Unlock() - - if err != nil { - c.mu.Lock() - delete(c.pending, id) - c.mu.Unlock() - - return fmt.Errorf("rpc %s: write: %w", method, err) - } - - select { - case <-ctx.Done(): - c.mu.Lock() - delete(c.pending, id) - c.mu.Unlock() - - return ctx.Err() - case resp, ok := <-ch: - if !ok { - return fmt.Errorf("rpc %s: client closed", method) - } - if resp.Error != "" { - return &rpcError{Method: method, Msg: resp.Error} - } - if result != nil && len(resp.Result) > 0 { - if err := json.Unmarshal(resp.Result, result); err != nil { - return fmt.Errorf("rpc %s: unmarshal result: %w", method, err) - } - } - - return nil - } -} - -// rpcHandler is the function signature a method handler must satisfy. -// params is the raw JSON params from the request; the handler returns -// any value to JSON-marshal back as the result, plus an error. -type rpcHandler func(ctx context.Context, params json.RawMessage) (any, error) - -// rpcServer is the child side. Each request is dispatched to its own -// goroutine so handlers may block (e.g. WaitFaultHeld) without -// stalling other RPCs. -type rpcServer struct { - r io.Reader - w io.Writer - - writeMu sync.Mutex - handlers map[string]rpcHandler - - wg sync.WaitGroup -} - -func newRPCServer(r io.Reader, w io.Writer) *rpcServer { - return &rpcServer{ - r: r, - w: w, - handlers: make(map[string]rpcHandler), - } -} - -// Register adds a handler for the given method. Must be called BEFORE -// Serve. -func (s *rpcServer) Register(method string, h rpcHandler) { - s.handlers[method] = h -} - -// Serve reads frames from r and dispatches them in goroutines until -// the reader returns EOF / error. Returns when the reader is done; it -// then waits for all in-flight handlers to finish. -func (s *rpcServer) Serve(ctx context.Context) error { - for { - body, err := readFrame(s.r) - if err != nil { - s.wg.Wait() - if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) { - return nil - } - - return err - } - - var req rpcRequest - if err := json.Unmarshal(body, &req); err != nil { - s.send(rpcResponse{ID: 0, Error: fmt.Sprintf("malformed request: %v", err)}) - - continue - } - - handler, ok := s.handlers[req.Method] - if !ok { - s.send(rpcResponse{ID: req.ID, Error: fmt.Sprintf("unknown method %q", req.Method)}) - - continue - } - - s.wg.Add(1) - go func(req rpcRequest, h rpcHandler) { - defer s.wg.Done() - - result, err := h(ctx, req.Params) - resp := rpcResponse{ID: req.ID} - if err != nil { - resp.Error = err.Error() - } else if result != nil { - b, err := json.Marshal(result) - if err != nil { - resp.Error = fmt.Sprintf("marshal result: %v", err) - } else { - resp.Result = b - } - } - - s.send(resp) - }(req, handler) - } -} - -func (s *rpcServer) send(resp rpcResponse) { - body, err := json.Marshal(resp) - if err != nil { - // Best-effort: respond with an error envelope using the empty body. - body, _ = json.Marshal(rpcResponse{ID: resp.ID, Error: fmt.Sprintf("marshal response: %v", err)}) - } - - s.writeMu.Lock() - defer s.writeMu.Unlock() - _ = writeFrame(s.w, body) -}