From 0433025d74e582cdb24eca8210a14904eed1dde3 Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Thu, 19 Feb 2026 15:07:57 +0100 Subject: [PATCH 01/24] 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 4b028c3917cb141b4202c8df65ad63e91042486f Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Fri, 13 Feb 2026 11:00:04 -0800 Subject: [PATCH 02/24] 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/feature-flags/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 c4aee77de9..48c3f96958 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_a41d3fb" + FIRECRACKER_VERSION: "v1.14.1_aa14c57" 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 498d21be3d..a0e2d9ab81 100644 --- a/packages/orchestrator/README.md +++ b/packages/orchestrator/README.md @@ -36,7 +36,7 @@ Flags: - `-template ` - Template ID (default: `local-template`) - `-storage ` - Local path or `gs://bucket` (enables local mode with auto-download of kernel/FC) - `-kernel ` - Kernel version (default: `vmlinux-6.1.102`) -- `-firecracker ` - Firecracker version (default: `v1.12.1_a41d3fb`) +- `-firecracker ` - Firecracker version (default: `v1.14.1_aa14c57`) - `-vcpu ` - vCPUs (default: `1`) - `-memory ` - Memory in MB (default: `512`) - `-disk ` - Disk in MB (default: `1000`) diff --git a/packages/shared/pkg/feature-flags/flags.go b/packages/shared/pkg/feature-flags/flags.go index 7e65aa8a0a..646f8e3d9c 100644 --- a/packages/shared/pkg/feature-flags/flags.go +++ b/packages/shared/pkg/feature-flags/flags.go @@ -208,12 +208,14 @@ const ( const ( DefaultFirecackerV1_10Version = "v1.10.1_30cbb07" DefaultFirecackerV1_12Version = "v1.12.1_a41d3fb" - DefaultFirecrackerVersion = DefaultFirecackerV1_12Version + DefaultFirecackerV1_14Version = "v1.14.1_aa14c57" + 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 d3fa713909..48d287c13f 100644 --- a/tests/integration/seed.go +++ b/tests/integration/seed.go @@ -207,7 +207,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_a41d3fb", "0.2.4", + 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.14.1_aa14c57", "0.2.4", "integration-test-node", templates.TemplateV1Version, build.createdAt) } else { err = db.TestsRawSQL(ctx, ` @@ -217,7 +217,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_a41d3fb", "0.2.4", + 2, 512, 512, 1982, "vmlinux-6.1.102", "v1.14.1_aa14c57", "0.2.4", "integration-test-node", templates.TemplateV1Version) } if err != nil { From 7b71471d7d892b6ce5862017766ef720cd6b396b Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Fri, 20 Feb 2026 12:35:01 +0100 Subject: [PATCH 03/24] 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. Signed-off-by: Babis Chalios --- .../internal/sandbox/uffd/memory/mapping.go | 16 +++---- .../uffd/memory/mapping_offset_test.go | 36 ++++++---------- .../sandbox/uffd/userfaultfd/page_tracker.go | 43 +++++++++++++++++++ .../sandbox/uffd/userfaultfd/userfaultfd.go | 24 +++++++---- 4 files changed, 80 insertions(+), 39 deletions(-) create mode 100644 packages/orchestrator/internal/sandbox/uffd/userfaultfd/page_tracker.go diff --git a/packages/orchestrator/internal/sandbox/uffd/memory/mapping.go b/packages/orchestrator/internal/sandbox/uffd/memory/mapping.go index a9fdc85f36..2872d86276 100644 --- a/packages/orchestrator/internal/sandbox/uffd/memory/mapping.go +++ b/packages/orchestrator/internal/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/internal/sandbox/uffd/memory/mapping_offset_test.go b/packages/orchestrator/internal/sandbox/uffd/memory/mapping_offset_test.go index 4be1c1e6e0..c2841885d8 100644 --- a/packages/orchestrator/internal/sandbox/uffd/memory/mapping_offset_test.go +++ b/packages/orchestrator/internal/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/internal/sandbox/uffd/userfaultfd/page_tracker.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/page_tracker.go new file mode 100644 index 0000000000..5fa38979a8 --- /dev/null +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/page_tracker.go @@ -0,0 +1,43 @@ +package userfaultfd + +import ( + "sync" +) + +type pageState int + +const ( + unfaulted pageState = iota + faulted + removed +) + +// pageTracker is a concurrent map tracking the state of guest memory pages, +// keyed by host virtual address. Pages not yet recorded are implicitly +// unfaulted. +// +// Uses sync.Map because the access pattern is predominantly read-heavy +// with disjoint key access across goroutines. +type pageTracker struct { + pageSize uintptr + m sync.Map // map[uintptr]pageState +} + +func newPageTracker(pageSize uintptr) pageTracker { + return pageTracker{pageSize: pageSize} +} + +func (pt *pageTracker) get(addr uintptr) pageState { + v, ok := pt.m.Load(addr) + if !ok { + return unfaulted + } + + return v.(pageState) +} + +func (pt *pageTracker) setState(state pageState, start, end uintptr) { + for addr := start; addr < end; addr += pt.pageSize { + pt.m.Store(addr, state) + } +} diff --git a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go index 4db0fe5127..f698fdb97a 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go @@ -35,8 +35,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 @@ -64,6 +66,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, @@ -220,7 +224,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)) @@ -232,7 +236,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 @@ -242,7 +246,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 @@ -281,16 +285,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. @@ -359,6 +363,7 @@ func (u *Userfaultfd) faultPage( 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 } @@ -380,6 +385,7 @@ func (u *Userfaultfd) faultPage( // 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 0c1cb6d41c984db779f0c5982a5e116f49b9a992 Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Wed, 11 Feb 2026 17:32:25 -0800 Subject: [PATCH 04/24] 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 --- .../internal/sandbox/uffd/userfaultfd/fd.go | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/fd.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/fd.go index 60c773c540..bd3ddbd188 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/fd.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/fd.go @@ -21,6 +21,11 @@ struct uffd_pagefault { #define UFFD_FEATURE_WP_ASYNC (1 << 15) #endif +struct uffd_remove { + __u64 start; + __u64 end; +}; + */ import "C" @@ -35,18 +40,21 @@ const ( 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_API = C.UFFDIO_API + UFFDIO_REGISTER = C.UFFDIO_REGISTER + UFFDIO_COPY = C.UFFDIO_COPY + UFFDIO_ZEROPAGE = C.UFFDIO_ZEROPAGE 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_WP_ASYNC = C.UFFD_FEATURE_WP_ASYNC @@ -59,11 +67,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 +108,14 @@ 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 getMsgEvent(msg *UffdMsg) CUChar { return msg.event } @@ -130,6 +148,21 @@ 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) close() error { return syscall.Close(int(f)) } From f0bcd530400cb822128a569d8bd0a1103324a09f Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Fri, 20 Feb 2026 18:38:27 +0100 Subject: [PATCH 05/24] 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. Signed-off-by: Babis Chalios --- .../sandbox/block/prefetch_tracker.go | 7 + .../sandbox/uffd/userfaultfd/queue.go | 72 +++++ .../sandbox/uffd/userfaultfd/userfaultfd.go | 278 ++++++++++++------ 3 files changed, 267 insertions(+), 90 deletions(-) create mode 100644 packages/orchestrator/internal/sandbox/uffd/userfaultfd/queue.go diff --git a/packages/orchestrator/internal/sandbox/block/prefetch_tracker.go b/packages/orchestrator/internal/sandbox/block/prefetch_tracker.go index af586fc02b..428875dac7 100644 --- a/packages/orchestrator/internal/sandbox/block/prefetch_tracker.go +++ b/packages/orchestrator/internal/sandbox/block/prefetch_tracker.go @@ -26,6 +26,9 @@ const ( Write AccessType = "write" // Prefetch indicates a proactively prefetched block, not a real fault. Prefetch AccessType = "prefetch" + // Remove indicates a block that was removed from the memory. + // As there must have been a read or write before remove, the prefetch will correctly not overwrite these with "remove" by default. + Remove AccessType = "remove" ) // BlockEntry holds metadata about a tracked block. @@ -72,6 +75,10 @@ func (t *PrefetchTracker) Add(off int64, accessType AccessType) { // Only add if not already tracked if _, ok := t.blockEntries[idx]; !ok { + if accessType == Remove { + // TODO: Check if having remove here would break things down the line + return + } t.blockEntries[idx] = PrefetchBlockEntry{ Index: idx, Order: t.orderCounter, diff --git a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/queue.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/queue.go new file mode 100644 index 0000000000..22acfe4357 --- /dev/null +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/queue.go @@ -0,0 +1,72 @@ +package userfaultfd + +import ( + "container/list" + "iter" + "sync" +) + +// queue is a concurrency-safe heterogeneous FIFO queue. +// Any event type can be pushed into it, and drain extracts +// events of a specific type while leaving others in place. +type queue struct { + mu sync.Mutex + l *list.List +} + +func newQueue() *queue { + return &queue{ + l: list.New(), + } +} + +// push adds an event to the back of the queue. +func (q *queue) push(item any) { + q.mu.Lock() + defer q.mu.Unlock() + + q.l.PushBack(item) +} + +func (q *queue) prepend(q2 *queue) { + q.mu.Lock() + defer q.mu.Unlock() + q2.mu.Lock() + defer q2.mu.Unlock() + + q.l.PushFrontList(q2.l) +} + +func (q *queue) size() int { + q.mu.Lock() + defer q.mu.Unlock() + + return q.l.Len() +} + +func (q *queue) reset() { + q.mu.Lock() + defer q.mu.Unlock() + + q.l.Init() +} + +// each returns an iterator over all items of type T in the queue. +// Items of other types are skipped. +func each[T any](q *queue) iter.Seq[T] { + return func(yield func(T) bool) { + q.mu.Lock() + defer q.mu.Unlock() + + for e := q.l.Front(); e != nil; e = e.Next() { + v, ok := e.Value.(T) + if !ok { + continue + } + + if !yield(v) { + break + } + } + } +} diff --git a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go index f698fdb97a..6d47ff900b 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go @@ -19,6 +19,7 @@ import ( "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/uffd/fdexit" "github.com/e2b-dev/infra/packages/orchestrator/internal/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/internal/sandbox/uffd/userfaultfd") @@ -86,6 +87,58 @@ func (u *Userfaultfd) Close() error { return u.fd.close() } +// readEvents reads all available UFFD events from the file descriptor. +// Returns a queue with the events read, or an error. +func (u *Userfaultfd) readEvents(ctx context.Context) (*queue, error) { + buf := make([]byte, unsafe.Sizeof(UffdMsg{})) + + queue := newQueue() + 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 descriptro. + // We are done. + break + } + + if err != nil { + return 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: + pagefault := (*UffdPagefault)(unsafe.Pointer(&arg[0])) + queue.push(pagefault) + case UFFD_EVENT_REMOVE: + remove := (*UffdRemove)(unsafe.Pointer(&arg[0])) + queue.push(remove) + default: + return nil, ErrUnexpectedEventType + } + } + + return queue, nil +} + func (u *Userfaultfd) Serve( ctx context.Context, fdExit *fdexit.FdExit, @@ -113,7 +166,8 @@ func (u *Userfaultfd) Serve( unix.POLLNVAL: "POLLNVAL", } -outerLoop: + deferredEvents := newQueue() + for { if _, err := unix.Poll( pollFds, @@ -179,81 +233,95 @@ 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 - } - + events, 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])) + events.prepend(deferredEvents) + // This is not racy because there shouldn't be any goroutines running at the moment. + deferredEvents.reset() - if msgEvent := getMsgEvent(&msg); msgEvent != UFFD_EVENT_PAGEFAULT { - u.logger.Error(ctx, "UFFD serve unexpected event type", zap.Any("event_type", msgEvent)) + // 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 events.size() == 0 { + eagainCounter.Increase("EAGAIN") - return ErrUnexpectedEventType + continue } - arg := getMsgArg(&msg) - pagefault := (*(*UffdPagefault)(unsafe.Pointer(&arg[0]))) - flags := pagefault.flags + // We successfully read all available UFFD events. + noDataCounter.Log(ctx) + eagainCounter.Log(ctx) - 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) + // First handle the UFFD_EVENT_REMOVE events + for rm := range each[*UffdRemove](events) { + u.pageTracker.setState(removed, uintptr(rm.start), uintptr(rm.end)) } - // 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) - }) + for pf := range each[*UffdPagefault](events) { + // We don't handle minor page faults. + if pf.flags&UFFD_PAGEFAULT_FLAG_MINOR != 0 { + return fmt.Errorf("unexpected MINOR pagefault event, closing UFFD") + } - continue - } + // 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("unexecpted WP pagefault event, closuing UFFD") + } - // 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) - }) + addr := getPagefaultAddress(pf) + offset, err := u.ma.GetOffset(addr) + if err != nil { + u.logger.Error(ctx, "UFFD serve got mapping error", zap.Error(err)) - continue - } + return fmt.Errorf("failed to map: %w", err) + } - // 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) + state := u.pageTracker.get(addr) + switch state { + case faulted: + continue + case removed: + u.wg.Go(func() error { + handled, err := u.faultPage(ctx, addr, offset, nil, fdExit.SignalExit, block.Remove) + if err != nil { + return err + } + + if !handled { + deferredEvents.push(pf) + } + + return nil + }) + case unfaulted: + var accessType block.AccessType + if pf.flags&UFFD_PAGEFAULT_FLAG_WRITE == 0 { + accessType = block.Read + } else { + accessType = block.Write + } + + u.wg.Go(func() error { + handled, err := u.faultPage(ctx, addr, offset, u.src, fdExit.SignalExit, accessType) + if err != nil { + return err + } + + if !handled { + deferredEvents.push(pf) + } + + return nil + }) + default: + return fmt.Errorf("unexpected pageState: %#v", state) + } + } } } @@ -294,7 +362,16 @@ func (u *Userfaultfd) Prefault(ctx context.Context, offset int64, data []byte) e return fmt.Errorf("data length (%d) is less than pagesize (%d)", len(data), u.pageSize) } - return u.faultPage(ctx, addr, offset, u.pageSize, directDataSource{data, int64(u.pageSize)}, nil, block.Prefetch) + handled, err := u.faultPage(ctx, addr, offset, directDataSource{data, int64(u.pageSize)}, nil, block.Prefetch) + if err != nil { + return fmt.Errorf("failted to fault page: %w", err) + } + + if !handled { + span.RecordError(fmt.Errorf("page already faulted")) + } + + return nil } // directDataSource wraps a byte slice to implement block.Slicer for prefaulting. @@ -315,11 +392,10 @@ func (u *Userfaultfd) faultPage( ctx context.Context, addr uintptr, offset int64, - pagesize uintptr, source block.Slicer, onFailure func() error, accessType block.AccessType, -) error { +) (bool, error) { span := trace.SpanFromContext(ctx) // The RLock must be called inside the goroutine to ensure RUnlock runs via defer, @@ -331,61 +407,83 @@ func (u *Userfaultfd) faultPage( 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)) } }() - b, dataErr := source.Slice(ctx, offset, int64(pagesize)) - if dataErr != nil { - var signalErr error - if onFailure != nil { - signalErr = onFailure() - } - - joinedErr := errors.Join(dataErr, signalErr) - - span.RecordError(joinedErr) - u.logger.Error(ctx, "UFFD serve data fetch error", zap.Error(joinedErr)) - - return fmt.Errorf("failed to read from source: %w", joinedErr) - } - + var writeErr error var copyMode 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 cause clear the write-protection bit. if accessType != block.Write { - copyMode |= UFFDIO_COPY_MODE_WP + copyMode = UFFDIO_COPY_MODE_WP } - copyErr := u.fd.copy(addr, pagesize, b, copyMode) - if errors.Is(copyErr, unix.EEXIST) { - // Page is already mapped + // Write to guest memory. nil data means zero-fill + switch { + case source == nil && u.pageSize == header.PageSize: + writeErr = u.fd.zero(addr, u.pageSize, copyMode) + case source == nil && u.pageSize == header.HugepageSize: + writeErr = u.fd.copy(addr, u.pageSize, header.EmptyHugePage, 0) + default: + b, dataErr := source.Slice(ctx, offset, int64(u.pageSize)) + if dataErr != nil { + var signalErr error + if onFailure != nil { + signalErr = onFailure() + } + + joinedErr := errors.Join(dataErr, signalErr) + + span.RecordError(joinedErr) + u.logger.Error(ctx, "UFFD serve data fetch error", zap.Error(joinedErr)) + + return false, fmt.Errorf("failed to read from source: %w", joinedErr) + } + + writeErr = u.fd.copy(addr, u.pageSize, b, copyMode) + } + + // 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 twich, since we are now + // tracking the state of pages. + if errors.Is(writeErr, unix.EEXIST) { + u.pageTracker.setState(faulted, addr, addr+u.pageSize) span.SetAttributes(attribute.Bool("uffd.already_mapped", true)) - u.pageTracker.setState(faulted, addr, addr+pagesize) - return nil + return true, nil } - if copyErr != nil { + if errors.Is(writeErr, unix.EAGAIN) { + // This happens when a remove event arrives in the UFFD file descriptor while + // we are trying to copy()/zero() a page. We need to read all the events from + // file descriptor and try again. + u.logger.Debug(ctx, "UFFD page write EAGAIN, deferring", zap.Uintptr("addr", addr)) + + return false, nil + } + + if writeErr != nil { var signalErr error if onFailure != nil { signalErr = onFailure() } - joinedErr := errors.Join(copyErr, signalErr) + joinedErr := errors.Join(writeErr, signalErr) span.RecordError(joinedErr) u.logger.Error(ctx, "UFFD serve uffdio copy error", zap.Error(joinedErr)) - return fmt.Errorf("failed uffdio copy: %w", joinedErr) + return false, fmt.Errorf("failed uffdio copy %w", joinedErr) } // 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) + u.pageTracker.setState(faulted, addr, addr+u.pageSize) - return nil + return true, nil } From b7711980a5d79030364f8c3e3e265d7ba1fd586e Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Thu, 12 Feb 2026 00:33:05 -0800 Subject: [PATCH 06/24] 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. Signed-off-by: Babis Chalios --- .../orchestrator/cmd/create-build/main.go | 25 ++++++++++++++++--- .../internal/sandbox/fc/client.go | 24 ++++++++++++++++++ .../internal/sandbox/fc/config.go | 17 +++++++++++++ .../internal/sandbox/fc/process.go | 11 ++++++++ .../orchestrator/internal/sandbox/sandbox.go | 4 +++ .../internal/template/build/config/config.go | 3 +++ .../template/build/phases/base/builder.go | 15 ++++++++--- .../template/build/phases/finalize/builder.go | 12 ++++++--- .../template/build/phases/steps/builder.go | 12 ++++++--- .../template/server/create_template.go | 25 +++++++++++++++++++ packages/orchestrator/template-manager.proto | 1 + .../template-manager/template-manager.pb.go | 12 +++++++-- 12 files changed, 144 insertions(+), 17 deletions(-) diff --git a/packages/orchestrator/cmd/create-build/main.go b/packages/orchestrator/cmd/create-build/main.go index 9b95714c54..1974e4c74e 100644 --- a/packages/orchestrator/cmd/create-build/main.go +++ b/packages/orchestrator/cmd/create-build/main.go @@ -24,6 +24,7 @@ import ( "github.com/e2b-dev/infra/packages/orchestrator/internal/proxy" "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox" blockmetrics "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/block/metrics" + sandboxfc "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/fc" "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/nbd" "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/network" sbxtemplate "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/template" @@ -59,6 +60,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") @@ -96,7 +98,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) } @@ -174,9 +185,10 @@ func setupEnv(ctx context.Context, storagePath, kernel, fc string, localMode boo 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, @@ -316,6 +328,12 @@ func doBuild( }) } + // Default FPR to enabled when the FC version supports it; explicit flag overrides. + fprEnabled := sandboxfc.Config{FirecrackerVersion: fcVersion}.SupportsFreePageReporting() + if freePageReporting != nil { + fprEnabled = *freePageReporting + } + tmpl := config.TemplateConfig{ Version: templates.TemplateV2LatestVersion, TemplateID: templateID, @@ -324,10 +342,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/internal/sandbox/fc/client.go b/packages/orchestrator/internal/sandbox/fc/client.go index 9aec93d236..4ebd3e3212 100644 --- a/packages/orchestrator/internal/sandbox/fc/client.go +++ b/packages/orchestrator/internal/sandbox/fc/client.go @@ -389,6 +389,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/internal/sandbox/fc/config.go b/packages/orchestrator/internal/sandbox/fc/config.go index 22d50583e2..48c81014cc 100644 --- a/packages/orchestrator/internal/sandbox/fc/config.go +++ b/packages/orchestrator/internal/sandbox/fc/config.go @@ -2,8 +2,10 @@ package fc import ( "path/filepath" + "strings" "github.com/e2b-dev/infra/packages/orchestrator/internal/cfg" + "github.com/e2b-dev/infra/packages/shared/pkg/utils" ) const ( @@ -38,6 +40,21 @@ func (t Config) FirecrackerPath(config cfg.BuilderConfig) string { return filepath.Join(config.FirecrackerVersionsDir, t.FirecrackerVersion, FirecrackerBinaryName) } +// SupportsFreePageReporting reports whether the Firecracker version supports +// free page reporting via the balloon device. Free page reporting was introduced +// in Firecracker v1.14.0. +func (t Config) SupportsFreePageReporting() bool { + // Version format is vX.Y.Z_commithash — strip the commit hash before comparing. + versionOnly, _, _ := strings.Cut(t.FirecrackerVersion, "_") + + supported, err := utils.IsGTEVersion(versionOnly, "v1.14.0") + if err != nil { + return false + } + + return supported +} + type RootfsPaths struct { TemplateVersion uint64 TemplateID string diff --git a/packages/orchestrator/internal/sandbox/fc/process.go b/packages/orchestrator/internal/sandbox/fc/process.go index 11a04b6495..bd1d6c24f8 100644 --- a/packages/orchestrator/internal/sandbox/fc/process.go +++ b/packages/orchestrator/internal/sandbox/fc/process.go @@ -299,6 +299,7 @@ func (p *Process) Create( vCPUCount int64, memoryMB int64, hugePages bool, + freePageReporting bool, options ProcessOptions, txRateLimit TxRateLimiterConfig, cgroupFD int, @@ -437,6 +438,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/internal/sandbox/sandbox.go b/packages/orchestrator/internal/sandbox/sandbox.go index cad07ac37c..8bddd4845b 100644 --- a/packages/orchestrator/internal/sandbox/sandbox.go +++ b/packages/orchestrator/internal/sandbox/sandbox.go @@ -71,6 +71,9 @@ type Config struct { TotalDiskSizeMB int64 HugePages bool + // Enable free page reporting + FreePageReporting bool + Network *orchestrator.SandboxNetworkConfig Envd EnvdMetadata @@ -363,6 +366,7 @@ func (f *Factory) CreateSandbox( config.Vcpu, config.RamMB, config.HugePages, + config.FreePageReporting, processOptions, fc.TxRateLimiterConfig{ Ops: fc.TokenBucketConfig(throttleConfig.Ops), diff --git a/packages/orchestrator/internal/template/build/config/config.go b/packages/orchestrator/internal/template/build/config/config.go index fc73a097bf..8a31beb7e8 100644 --- a/packages/orchestrator/internal/template/build/config/config.go +++ b/packages/orchestrator/internal/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/internal/template/build/phases/base/builder.go b/packages/orchestrator/internal/template/build/phases/base/builder.go index d2442126a5..f43b1b4057 100644 --- a/packages/orchestrator/internal/template/build/phases/base/builder.go +++ b/packages/orchestrator/internal/template/build/phases/base/builder.go @@ -199,11 +199,21 @@ func (bb *BaseBuilder) buildLayerFromOCI( // Provision sandbox with systemd and other vital parts userLogger.Info(ctx, "Provisioning sandbox template") + fcConfig := fc.Config{ + KernelVersion: bb.Config.KernelVersion, + FirecrackerVersion: bb.Config.FirecrackerVersion, + } + baseSbxConfig := sandbox.Config{ Vcpu: bb.Config.VCpuCount, RamMB: bb.Config.MemoryMB, HugePages: bb.Config.HugePages, + // Enable free page reporting if requested and supported by the Firecracker version. + // The balloon device state is saved in the snapshot, so resumed sandboxes + // will automatically have free page reporting active. + FreePageReporting: bb.Config.FreePageReporting && fcConfig.SupportsFreePageReporting(), + // Allow sandbox internet access during provisioning Network: &orchestrator.SandboxNetworkConfig{}, @@ -211,10 +221,7 @@ func (bb *BaseBuilder) buildLayerFromOCI( Version: bb.EnvdVersion, }, - FirecrackerConfig: fc.Config{ - KernelVersion: bb.Config.KernelVersion, - FirecrackerVersion: bb.Config.FirecrackerVersion, - }, + FirecrackerConfig: fcConfig, } err = bb.provisionSandbox( ctx, diff --git a/packages/orchestrator/internal/template/build/phases/finalize/builder.go b/packages/orchestrator/internal/template/build/phases/finalize/builder.go index 1b84dd2a8f..4e8c397e62 100644 --- a/packages/orchestrator/internal/template/build/phases/finalize/builder.go +++ b/packages/orchestrator/internal/template/build/phases/finalize/builder.go @@ -147,22 +147,26 @@ func (ppb *PostProcessingBuilder) Build( defaultWorkdir = nil } + fcConfig := fc.Config{ + KernelVersion: ppb.Config.KernelVersion, + FirecrackerVersion: ppb.Config.FirecrackerVersion, + } + // Configure sandbox for final layer sbxConfig := sandbox.Config{ Vcpu: ppb.Config.VCpuCount, RamMB: ppb.Config.MemoryMB, HugePages: ppb.Config.HugePages, + FreePageReporting: ppb.Config.FreePageReporting && fcConfig.SupportsFreePageReporting(), + Envd: sandbox.EnvdMetadata{ Version: ppb.EnvdVersion, DefaultUser: defaultUser, DefaultWorkdir: defaultWorkdir, }, - FirecrackerConfig: fc.Config{ - KernelVersion: ppb.Config.KernelVersion, - FirecrackerVersion: ppb.Config.FirecrackerVersion, - }, + FirecrackerConfig: fcConfig, } // Select the IO Engine to use for the rootfs drive diff --git a/packages/orchestrator/internal/template/build/phases/steps/builder.go b/packages/orchestrator/internal/template/build/phases/steps/builder.go index f11a4e6369..e67a61a074 100644 --- a/packages/orchestrator/internal/template/build/phases/steps/builder.go +++ b/packages/orchestrator/internal/template/build/phases/steps/builder.go @@ -158,19 +158,23 @@ func (sb *StepBuilder) Build( step := sb.step + fcConfig := fc.Config{ + KernelVersion: sb.Config.KernelVersion, + FirecrackerVersion: sb.Config.FirecrackerVersion, + } + sbxConfig := sandbox.Config{ Vcpu: sb.Config.VCpuCount, RamMB: sb.Config.MemoryMB, HugePages: sb.Config.HugePages, + FreePageReporting: sb.Config.FreePageReporting && fcConfig.SupportsFreePageReporting(), + Envd: sandbox.EnvdMetadata{ Version: sb.EnvdVersion, }, - FirecrackerConfig: fc.Config{ - KernelVersion: sb.Config.KernelVersion, - FirecrackerVersion: sb.Config.FirecrackerVersion, - }, + FirecrackerConfig: fcConfig, } // First not cached layer is create (to change CPU, Memory, etc), subsequent are layers are resumes. diff --git a/packages/orchestrator/internal/template/server/create_template.go b/packages/orchestrator/internal/template/server/create_template.go index f5ee87aafb..a504769067 100644 --- a/packages/orchestrator/internal/template/server/create_template.go +++ b/packages/orchestrator/internal/template/server/create_template.go @@ -10,6 +10,7 @@ import ( "go.uber.org/zap/zapcore" "google.golang.org/protobuf/types/known/emptypb" + "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/fc" "github.com/e2b-dev/infra/packages/orchestrator/internal/template/build/builderrors" "github.com/e2b-dev/infra/packages/orchestrator/internal/template/build/buildlogger" "github.com/e2b-dev/infra/packages/orchestrator/internal/template/build/config" @@ -59,6 +60,11 @@ func (s *ServerStore) TemplateCreate(ctx context.Context, templateRequest *templ } } + freePageReporting, err := resolveFreePageReporting(cfg.FreePageReporting, cfg.GetFirecrackerVersion()) + if err != nil { + return nil, fmt.Errorf("invalid template config: %w", err) + } + template := config.TemplateConfig{ Version: version, TeamID: cfg.GetTeamID(), @@ -70,6 +76,7 @@ func (s *ServerStore) TemplateCreate(ctx context.Context, templateRequest *templ ReadyCmd: cfg.GetReadyCommand(), DiskSizeMB: int64(cfg.GetDiskSizeMB()), HugePages: cfg.GetHugePages(), + FreePageReporting: freePageReporting, FromImage: cfg.GetFromImage(), FromTemplate: cfg.GetFromTemplate(), RegistryAuthProvider: authProvider, @@ -160,3 +167,21 @@ func (s *ServerStore) TemplateCreate(ctx context.Context, templateRequest *templ return nil, nil } + +// resolveFreePageReporting determines the effective FPR setting. +// When override is nil (caller did not set the field), it defaults to true +// if the Firecracker version supports FPR. An explicit true on an unsupported +// version is an error; an explicit false always disables FPR. +func resolveFreePageReporting(override *bool, fcVersion string) (bool, error) { + supported := fc.Config{FirecrackerVersion: fcVersion}.SupportsFreePageReporting() + + if override != nil { + if *override && !supported { + return false, fmt.Errorf("free page reporting is not supported by Firecracker %s (requires >= v1.14.0)", fcVersion) + } + + return *override, nil + } + + return supported, nil +} 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 ebc032f7dc..8b0e2fca47 100644 --- a/packages/shared/pkg/grpc/template-manager/template-manager.pb.go +++ b/packages/shared/pkg/grpc/template-manager/template-manager.pb.go @@ -697,8 +697,9 @@ type TemplateConfig struct { // *TemplateConfig_FromImage // *TemplateConfig_FromTemplate 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"` + 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() } From 89bf52feb49ee4917a6fc0523565db8bc9e38e1b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 23 Feb 2026 14:48:12 +0000 Subject: [PATCH 07/24] chore: auto-commit generated changes --- .../template-manager/template-manager.pb.go | 253 +++++++++--------- 1 file changed, 129 insertions(+), 124 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 8b0e2fca47..3c3fe06023 100644 --- a/packages/shared/pkg/grpc/template-manager/template-manager.pb.go +++ b/packages/shared/pkg/grpc/template-manager/template-manager.pb.go @@ -697,9 +697,9 @@ type TemplateConfig struct { // *TemplateConfig_FromImage // *TemplateConfig_FromTemplate 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"` + 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() { @@ -1412,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, @@ -1450,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 8c8c3deed0752073981759d4db1040c2b618795a Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Fri, 27 Feb 2026 17:38:24 -0800 Subject: [PATCH 08/24] Cleanup and move free page reporting field to API --- .../api/internal/sandbox/sandbox_features.go | 8 +++++++ .../template-manager/create_template.go | 3 +++ .../internal/sandbox/fc/config.go | 17 -------------- .../template/build/phases/base/builder.go | 22 +++++++------------ .../template/build/phases/finalize/builder.go | 19 +++++++--------- .../template/build/phases/steps/builder.go | 19 +++++++--------- .../template/server/create_template.go | 7 +----- 7 files changed, 36 insertions(+), 59 deletions(-) diff --git a/packages/api/internal/sandbox/sandbox_features.go b/packages/api/internal/sandbox/sandbox_features.go index e8eb0e7d0d..82d517d537 100644 --- a/packages/api/internal/sandbox/sandbox_features.go +++ b/packages/api/internal/sandbox/sandbox_features.go @@ -45,3 +45,11 @@ func (v *VersionInfo) HasHugePages() bool { return false } + +func (v *VersionInfo) HasFreePageReporting() bool { + if v.lastReleaseVersion.Major() >= 1 && v.lastReleaseVersion.Minor() >= 14 { + return true + } + + return false +} 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/internal/sandbox/fc/config.go b/packages/orchestrator/internal/sandbox/fc/config.go index 48c81014cc..22d50583e2 100644 --- a/packages/orchestrator/internal/sandbox/fc/config.go +++ b/packages/orchestrator/internal/sandbox/fc/config.go @@ -2,10 +2,8 @@ package fc import ( "path/filepath" - "strings" "github.com/e2b-dev/infra/packages/orchestrator/internal/cfg" - "github.com/e2b-dev/infra/packages/shared/pkg/utils" ) const ( @@ -40,21 +38,6 @@ func (t Config) FirecrackerPath(config cfg.BuilderConfig) string { return filepath.Join(config.FirecrackerVersionsDir, t.FirecrackerVersion, FirecrackerBinaryName) } -// SupportsFreePageReporting reports whether the Firecracker version supports -// free page reporting via the balloon device. Free page reporting was introduced -// in Firecracker v1.14.0. -func (t Config) SupportsFreePageReporting() bool { - // Version format is vX.Y.Z_commithash — strip the commit hash before comparing. - versionOnly, _, _ := strings.Cut(t.FirecrackerVersion, "_") - - supported, err := utils.IsGTEVersion(versionOnly, "v1.14.0") - if err != nil { - return false - } - - return supported -} - type RootfsPaths struct { TemplateVersion uint64 TemplateID string diff --git a/packages/orchestrator/internal/template/build/phases/base/builder.go b/packages/orchestrator/internal/template/build/phases/base/builder.go index f43b1b4057..a2fb5fc7c7 100644 --- a/packages/orchestrator/internal/template/build/phases/base/builder.go +++ b/packages/orchestrator/internal/template/build/phases/base/builder.go @@ -199,20 +199,11 @@ func (bb *BaseBuilder) buildLayerFromOCI( // Provision sandbox with systemd and other vital parts userLogger.Info(ctx, "Provisioning sandbox template") - fcConfig := fc.Config{ - KernelVersion: bb.Config.KernelVersion, - FirecrackerVersion: bb.Config.FirecrackerVersion, - } - baseSbxConfig := sandbox.Config{ - Vcpu: bb.Config.VCpuCount, - RamMB: bb.Config.MemoryMB, - HugePages: bb.Config.HugePages, - - // Enable free page reporting if requested and supported by the Firecracker version. - // The balloon device state is saved in the snapshot, so resumed sandboxes - // will automatically have free page reporting active. - FreePageReporting: bb.Config.FreePageReporting && fcConfig.SupportsFreePageReporting(), + Vcpu: bb.Config.VCpuCount, + RamMB: bb.Config.MemoryMB, + HugePages: bb.Config.HugePages, + FreePageReporting: bb.Config.FreePageReporting, // Allow sandbox internet access during provisioning Network: &orchestrator.SandboxNetworkConfig{}, @@ -221,7 +212,10 @@ func (bb *BaseBuilder) buildLayerFromOCI( Version: bb.EnvdVersion, }, - FirecrackerConfig: fcConfig, + FirecrackerConfig: fc.Config{ + KernelVersion: bb.Config.KernelVersion, + FirecrackerVersion: bb.Config.FirecrackerVersion, + }, } err = bb.provisionSandbox( ctx, diff --git a/packages/orchestrator/internal/template/build/phases/finalize/builder.go b/packages/orchestrator/internal/template/build/phases/finalize/builder.go index 4e8c397e62..abbda2db76 100644 --- a/packages/orchestrator/internal/template/build/phases/finalize/builder.go +++ b/packages/orchestrator/internal/template/build/phases/finalize/builder.go @@ -147,18 +147,12 @@ func (ppb *PostProcessingBuilder) Build( defaultWorkdir = nil } - fcConfig := fc.Config{ - KernelVersion: ppb.Config.KernelVersion, - FirecrackerVersion: ppb.Config.FirecrackerVersion, - } - // Configure sandbox for final layer sbxConfig := sandbox.Config{ - Vcpu: ppb.Config.VCpuCount, - RamMB: ppb.Config.MemoryMB, - HugePages: ppb.Config.HugePages, - - FreePageReporting: ppb.Config.FreePageReporting && fcConfig.SupportsFreePageReporting(), + Vcpu: ppb.Config.VCpuCount, + RamMB: ppb.Config.MemoryMB, + HugePages: ppb.Config.HugePages, + FreePageReporting: ppb.Config.FreePageReporting, Envd: sandbox.EnvdMetadata{ Version: ppb.EnvdVersion, @@ -166,7 +160,10 @@ func (ppb *PostProcessingBuilder) Build( DefaultWorkdir: defaultWorkdir, }, - FirecrackerConfig: fcConfig, + FirecrackerConfig: fc.Config{ + KernelVersion: ppb.Config.KernelVersion, + FirecrackerVersion: ppb.Config.FirecrackerVersion, + }, } // Select the IO Engine to use for the rootfs drive diff --git a/packages/orchestrator/internal/template/build/phases/steps/builder.go b/packages/orchestrator/internal/template/build/phases/steps/builder.go index e67a61a074..c77c1b52cf 100644 --- a/packages/orchestrator/internal/template/build/phases/steps/builder.go +++ b/packages/orchestrator/internal/template/build/phases/steps/builder.go @@ -158,23 +158,20 @@ func (sb *StepBuilder) Build( step := sb.step - fcConfig := fc.Config{ - KernelVersion: sb.Config.KernelVersion, - FirecrackerVersion: sb.Config.FirecrackerVersion, - } - sbxConfig := sandbox.Config{ - Vcpu: sb.Config.VCpuCount, - RamMB: sb.Config.MemoryMB, - HugePages: sb.Config.HugePages, - - FreePageReporting: sb.Config.FreePageReporting && fcConfig.SupportsFreePageReporting(), + Vcpu: sb.Config.VCpuCount, + RamMB: sb.Config.MemoryMB, + HugePages: sb.Config.HugePages, + FreePageReporting: sb.Config.FreePageReporting, Envd: sandbox.EnvdMetadata{ Version: sb.EnvdVersion, }, - FirecrackerConfig: fcConfig, + FirecrackerConfig: fc.Config{ + KernelVersion: sb.Config.KernelVersion, + FirecrackerVersion: sb.Config.FirecrackerVersion, + }, } // First not cached layer is create (to change CPU, Memory, etc), subsequent are layers are resumes. diff --git a/packages/orchestrator/internal/template/server/create_template.go b/packages/orchestrator/internal/template/server/create_template.go index a504769067..041fb14474 100644 --- a/packages/orchestrator/internal/template/server/create_template.go +++ b/packages/orchestrator/internal/template/server/create_template.go @@ -60,11 +60,6 @@ func (s *ServerStore) TemplateCreate(ctx context.Context, templateRequest *templ } } - freePageReporting, err := resolveFreePageReporting(cfg.FreePageReporting, cfg.GetFirecrackerVersion()) - if err != nil { - return nil, fmt.Errorf("invalid template config: %w", err) - } - template := config.TemplateConfig{ Version: version, TeamID: cfg.GetTeamID(), @@ -76,7 +71,7 @@ func (s *ServerStore) TemplateCreate(ctx context.Context, templateRequest *templ ReadyCmd: cfg.GetReadyCommand(), DiskSizeMB: int64(cfg.GetDiskSizeMB()), HugePages: cfg.GetHugePages(), - FreePageReporting: freePageReporting, + FreePageReporting: cfg.GetFreePageReporting(), FromImage: cfg.GetFromImage(), FromTemplate: cfg.GetFromTemplate(), RegistryAuthProvider: authProvider, From e0cb013a4bb183d2fa04bb68a696715a779d640b Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Fri, 27 Feb 2026 18:40:04 -0800 Subject: [PATCH 09/24] Cleanup --- .../userfaultfd/cross_process_helpers_test.go | 10 +++ .../internal/sandbox/uffd/userfaultfd/fd.go | 4 +- .../sandbox/uffd/userfaultfd/page_tracker.go | 30 +++---- .../sandbox/uffd/userfaultfd/prefault.go | 57 ++++++++++++ .../sandbox/uffd/userfaultfd/userfaultfd.go | 87 ++++--------------- .../template/server/create_template.go | 19 ---- 6 files changed, 101 insertions(+), 106 deletions(-) create mode 100644 packages/orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go diff --git a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/cross_process_helpers_test.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/cross_process_helpers_test.go index 382a20fdb2..40717a63ae 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/cross_process_helpers_test.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/cross_process_helpers_test.go @@ -383,3 +383,13 @@ func crossProcessServe() error { return nil } } + +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() +} diff --git a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/fd.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/fd.go index bd3ddbd188..8280d190ce 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/fd.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/fd.go @@ -25,7 +25,6 @@ struct uffd_remove { __u64 start; __u64 end; }; - */ import "C" @@ -38,7 +37,8 @@ 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 diff --git a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/page_tracker.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/page_tracker.go index 5fa38979a8..e961ebb305 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/page_tracker.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/page_tracker.go @@ -1,10 +1,8 @@ package userfaultfd -import ( - "sync" -) +import "sync" -type pageState int +type pageState uint8 const ( unfaulted pageState = iota @@ -12,15 +10,11 @@ const ( removed ) -// pageTracker is a concurrent map tracking the state of guest memory pages, -// keyed by host virtual address. Pages not yet recorded are implicitly -// unfaulted. -// -// Uses sync.Map because the access pattern is predominantly read-heavy -// with disjoint key access across goroutines. type pageTracker struct { pageSize uintptr - m sync.Map // map[uintptr]pageState + + m map[uintptr]pageState + mu sync.RWMutex } func newPageTracker(pageSize uintptr) pageTracker { @@ -28,16 +22,22 @@ func newPageTracker(pageSize uintptr) pageTracker { } func (pt *pageTracker) get(addr uintptr) pageState { - v, ok := pt.m.Load(addr) + pt.mu.RLock() + defer pt.mu.RUnlock() + + state, ok := pt.m[addr] if !ok { return unfaulted } - return v.(pageState) + return state } -func (pt *pageTracker) setState(state pageState, start, end uintptr) { +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.Store(addr, state) + pt.m[addr] = state } } diff --git a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go new file mode 100644 index 0000000000..1f1b7d5563 --- /dev/null +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go @@ -0,0 +1,57 @@ +package userfaultfd + +import ( + "context" + "fmt" + + "github.com/e2b-dev/infra/packages/orchestrator/internal/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 { + 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) + } + + handled, err := u.faultPage( + ctx, + addr, + offset, + directDataSource{data, int64(u.pageSize)}, + nil, + block.Prefetch, + ) + if err != nil { + return fmt.Errorf("failed to fault page: %w", err) + } + + if !handled { + span.RecordError(fmt.Errorf("page already faulted")) + } + + 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/internal/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go index 6d47ff900b..904362d132 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go @@ -83,10 +83,6 @@ 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. // Returns a queue with the events read, or an error. func (u *Userfaultfd) readEvents(ctx context.Context) (*queue, error) { @@ -259,7 +255,7 @@ func (u *Userfaultfd) Serve( // First handle the UFFD_EVENT_REMOVE events for rm := range each[*UffdRemove](events) { - u.pageTracker.setState(removed, uintptr(rm.start), uintptr(rm.end)) + u.pageTracker.setState(uintptr(rm.start), uintptr(rm.end), removed) } for pf := range each[*UffdPagefault](events) { @@ -325,69 +321,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() - // 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.prefetchTracker.PrefetchData() -} - -// 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) - } - - if len(data) != int(u.pageSize) { - return fmt.Errorf("data length (%d) is less than pagesize (%d)", len(data), u.pageSize) - } - - handled, err := u.faultPage(ctx, addr, offset, directDataSource{data, int64(u.pageSize)}, nil, block.Prefetch) - if err != nil { - return fmt.Errorf("failted to fault page: %w", err) - } - - if !handled { - span.RecordError(fmt.Errorf("page already faulted")) - } - - 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 -} - func (u *Userfaultfd) faultPage( ctx context.Context, addr uintptr, @@ -451,7 +384,7 @@ func (u *Userfaultfd) faultPage( // try to handle a page fault for the same address twich, since we are now // tracking the state of pages. if errors.Is(writeErr, unix.EEXIST) { - u.pageTracker.setState(faulted, addr, addr+u.pageSize) + u.pageTracker.setState(addr, addr+u.pageSize, faulted) span.SetAttributes(attribute.Bool("uffd.already_mapped", true)) return true, nil @@ -483,7 +416,21 @@ func (u *Userfaultfd) faultPage( // 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+u.pageSize) + u.pageTracker.setState(addr, addr+u.pageSize, faulted) return true, nil } + +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() + + return u.prefetchTracker.PrefetchData() +} + +func (u *Userfaultfd) Close() error { + return u.fd.close() +} diff --git a/packages/orchestrator/internal/template/server/create_template.go b/packages/orchestrator/internal/template/server/create_template.go index 041fb14474..118ed67dfd 100644 --- a/packages/orchestrator/internal/template/server/create_template.go +++ b/packages/orchestrator/internal/template/server/create_template.go @@ -10,7 +10,6 @@ import ( "go.uber.org/zap/zapcore" "google.golang.org/protobuf/types/known/emptypb" - "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/fc" "github.com/e2b-dev/infra/packages/orchestrator/internal/template/build/builderrors" "github.com/e2b-dev/infra/packages/orchestrator/internal/template/build/buildlogger" "github.com/e2b-dev/infra/packages/orchestrator/internal/template/build/config" @@ -162,21 +161,3 @@ func (s *ServerStore) TemplateCreate(ctx context.Context, templateRequest *templ return nil, nil } - -// resolveFreePageReporting determines the effective FPR setting. -// When override is nil (caller did not set the field), it defaults to true -// if the Firecracker version supports FPR. An explicit true on an unsupported -// version is an error; an explicit false always disables FPR. -func resolveFreePageReporting(override *bool, fcVersion string) (bool, error) { - supported := fc.Config{FirecrackerVersion: fcVersion}.SupportsFreePageReporting() - - if override != nil { - if *override && !supported { - return false, fmt.Errorf("free page reporting is not supported by Firecracker %s (requires >= v1.14.0)", fcVersion) - } - - return *override, nil - } - - return supported, nil -} From c596541345f77f67a9b4e680fe60e4a022fd41d1 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Fri, 27 Feb 2026 18:44:34 -0800 Subject: [PATCH 10/24] Wrap onFailure --- .../internal/sandbox/uffd/userfaultfd/invoke.go | 10 ++++++++++ .../sandbox/uffd/userfaultfd/userfaultfd.go | 14 ++------------ 2 files changed, 12 insertions(+), 12 deletions(-) create mode 100644 packages/orchestrator/internal/sandbox/uffd/userfaultfd/invoke.go diff --git a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/invoke.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/invoke.go new file mode 100644 index 0000000000..71205ca263 --- /dev/null +++ b/packages/orchestrator/internal/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/internal/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go index 904362d132..c0f62d4821 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go @@ -363,12 +363,7 @@ func (u *Userfaultfd) faultPage( default: b, dataErr := source.Slice(ctx, offset, int64(u.pageSize)) if dataErr != nil { - var signalErr error - if onFailure != nil { - signalErr = onFailure() - } - - joinedErr := errors.Join(dataErr, signalErr) + joinedErr := errors.Join(dataErr, safeInvoke(onFailure)) span.RecordError(joinedErr) u.logger.Error(ctx, "UFFD serve data fetch error", zap.Error(joinedErr)) @@ -400,12 +395,7 @@ func (u *Userfaultfd) faultPage( } if writeErr != nil { - var signalErr error - if onFailure != nil { - signalErr = onFailure() - } - - joinedErr := errors.Join(writeErr, signalErr) + joinedErr := errors.Join(writeErr, safeInvoke(onFailure)) span.RecordError(joinedErr) u.logger.Error(ctx, "UFFD serve uffdio copy error", zap.Error(joinedErr)) From 08bac562fc5899bc2eefe582eaf5d015bf2c325c Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Tue, 3 Mar 2026 12:11:34 -0800 Subject: [PATCH 11/24] Clarify prefetch remove handling --- .../orchestrator/internal/sandbox/block/prefetch_tracker.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/orchestrator/internal/sandbox/block/prefetch_tracker.go b/packages/orchestrator/internal/sandbox/block/prefetch_tracker.go index 428875dac7..726a13ccab 100644 --- a/packages/orchestrator/internal/sandbox/block/prefetch_tracker.go +++ b/packages/orchestrator/internal/sandbox/block/prefetch_tracker.go @@ -75,10 +75,12 @@ func (t *PrefetchTracker) Add(off int64, accessType AccessType) { // Only add if not already tracked if _, ok := t.blockEntries[idx]; !ok { + // To prevent accidentally adding remove before a read or write, we don't allow it to be added. + // This should happen only as a race condition in the UFFD. if accessType == Remove { - // TODO: Check if having remove here would break things down the line return } + t.blockEntries[idx] = PrefetchBlockEntry{ Index: idx, Order: t.orderCounter, From 843b282c4bf626ae5d18926ffa9fda28c4f37bf9 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Tue, 3 Mar 2026 23:29:14 -0800 Subject: [PATCH 12/24] Cleanup --- .../orchestrator/cmd/create-build/main.go | 11 +- .../sandbox/block/prefetch_tracker.go | 9 - .../internal/sandbox/block/tracker.go | 80 -------- .../internal/sandbox/block/tracker_test.go | 173 ------------------ .../userfaultfd/cross_process_helpers_test.go | 44 ++++- .../sandbox/uffd/userfaultfd/deferred.go | 26 +++ .../sandbox/uffd/userfaultfd/page_tracker.go | 5 +- .../sandbox/uffd/userfaultfd/prefault.go | 5 +- .../sandbox/uffd/userfaultfd/queue.go | 72 -------- .../sandbox/uffd/userfaultfd/userfaultfd.go | 155 +++++++--------- 10 files changed, 148 insertions(+), 432 deletions(-) delete mode 100644 packages/orchestrator/internal/sandbox/block/tracker.go delete mode 100644 packages/orchestrator/internal/sandbox/block/tracker_test.go create mode 100644 packages/orchestrator/internal/sandbox/uffd/userfaultfd/deferred.go delete mode 100644 packages/orchestrator/internal/sandbox/uffd/userfaultfd/queue.go diff --git a/packages/orchestrator/cmd/create-build/main.go b/packages/orchestrator/cmd/create-build/main.go index 1974e4c74e..b6d2c30929 100644 --- a/packages/orchestrator/cmd/create-build/main.go +++ b/packages/orchestrator/cmd/create-build/main.go @@ -24,7 +24,6 @@ import ( "github.com/e2b-dev/infra/packages/orchestrator/internal/proxy" "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox" blockmetrics "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/block/metrics" - sandboxfc "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/fc" "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/nbd" "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/network" sbxtemplate "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/template" @@ -328,10 +327,16 @@ func doBuild( }) } - // Default FPR to enabled when the FC version supports it; explicit flag overrides. - fprEnabled := sandboxfc.Config{FirecrackerVersion: fcVersion}.SupportsFreePageReporting() + // 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{ diff --git a/packages/orchestrator/internal/sandbox/block/prefetch_tracker.go b/packages/orchestrator/internal/sandbox/block/prefetch_tracker.go index 726a13ccab..af586fc02b 100644 --- a/packages/orchestrator/internal/sandbox/block/prefetch_tracker.go +++ b/packages/orchestrator/internal/sandbox/block/prefetch_tracker.go @@ -26,9 +26,6 @@ const ( Write AccessType = "write" // Prefetch indicates a proactively prefetched block, not a real fault. Prefetch AccessType = "prefetch" - // Remove indicates a block that was removed from the memory. - // As there must have been a read or write before remove, the prefetch will correctly not overwrite these with "remove" by default. - Remove AccessType = "remove" ) // BlockEntry holds metadata about a tracked block. @@ -75,12 +72,6 @@ func (t *PrefetchTracker) Add(off int64, accessType AccessType) { // Only add if not already tracked if _, ok := t.blockEntries[idx]; !ok { - // To prevent accidentally adding remove before a read or write, we don't allow it to be added. - // This should happen only as a race condition in the UFFD. - if accessType == Remove { - return - } - t.blockEntries[idx] = PrefetchBlockEntry{ Index: idx, Order: t.orderCounter, diff --git a/packages/orchestrator/internal/sandbox/block/tracker.go b/packages/orchestrator/internal/sandbox/block/tracker.go deleted file mode 100644 index 7cd21a562b..0000000000 --- a/packages/orchestrator/internal/sandbox/block/tracker.go +++ /dev/null @@ -1,80 +0,0 @@ -package block - -import ( - "iter" - "sync" - - "github.com/bits-and-blooms/bitset" - - "github.com/e2b-dev/infra/packages/shared/pkg/storage/header" - "github.com/e2b-dev/infra/packages/shared/pkg/utils" -) - -type Tracker struct { - b *bitset.BitSet - mu sync.RWMutex - - blockSize int64 -} - -func NewTracker(blockSize int64) *Tracker { - return &Tracker{ - // The bitset resizes automatically based on the maximum set bit. - b: bitset.New(0), - blockSize: blockSize, - } -} - -func (t *Tracker) Has(off int64) bool { - t.mu.RLock() - defer t.mu.RUnlock() - - return t.b.Test(uint(header.BlockIdx(off, t.blockSize))) -} - -func (t *Tracker) Add(off int64) { - t.mu.Lock() - defer t.mu.Unlock() - - t.b.Set(uint(header.BlockIdx(off, t.blockSize))) -} - -func (t *Tracker) Reset() { - t.mu.Lock() - defer t.mu.Unlock() - - t.b.ClearAll() -} - -// BitSet returns the bitset. -// This is not safe to use concurrently. -func (t *Tracker) BitSet() *bitset.BitSet { - return t.b -} - -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() - - return bitsetOffsets(t.b.Clone(), t.BlockSize()) -} - -func bitsetOffsets(b *bitset.BitSet, blockSize int64) iter.Seq[int64] { - return utils.TransformTo(b.EachSet(), func(idx uint) int64 { - return header.BlockOffset(int64(idx), blockSize) - }) -} diff --git a/packages/orchestrator/internal/sandbox/block/tracker_test.go b/packages/orchestrator/internal/sandbox/block/tracker_test.go deleted file mode 100644 index f872baad93..0000000000 --- a/packages/orchestrator/internal/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/internal/sandbox/uffd/userfaultfd/cross_process_helpers_test.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/cross_process_helpers_test.go index 40717a63ae..1941e9b290 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/cross_process_helpers_test.go +++ b/packages/orchestrator/internal/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) @@ -384,12 +396,30 @@ func crossProcessServe() error { } } -func (u *Userfaultfd) faulted() *block.Tracker { +func (u *Userfaultfd) faulted() ([]uint64, error) { // 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() + 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 u.missingRequests.Clone() + return offsets, nil } diff --git a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/deferred.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/deferred.go new file mode 100644 index 0000000000..6089ad7660 --- /dev/null +++ b/packages/orchestrator/internal/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/internal/sandbox/uffd/userfaultfd/page_tracker.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/page_tracker.go index e961ebb305..b3ba62176f 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/page_tracker.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/page_tracker.go @@ -18,7 +18,10 @@ type pageTracker struct { } func newPageTracker(pageSize uintptr) pageTracker { - return pageTracker{pageSize: pageSize} + return pageTracker{ + pageSize: pageSize, + m: make(map[uintptr]pageState), + } } func (pt *pageTracker) get(addr uintptr) pageState { diff --git a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go index 1f1b7d5563..36bef8d3f1 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go @@ -3,8 +3,6 @@ package userfaultfd import ( "context" "fmt" - - "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/block" ) // Prefault proactively copies a page to guest memory at the given offset. @@ -29,7 +27,8 @@ func (u *Userfaultfd) Prefault(ctx context.Context, offset int64, data []byte) e offset, directDataSource{data, int64(u.pageSize)}, nil, - block.Prefetch, + // TODO: What mode should we pass here? + 0, ) if err != nil { return fmt.Errorf("failed to fault page: %w", err) diff --git a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/queue.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/queue.go deleted file mode 100644 index 22acfe4357..0000000000 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/queue.go +++ /dev/null @@ -1,72 +0,0 @@ -package userfaultfd - -import ( - "container/list" - "iter" - "sync" -) - -// queue is a concurrency-safe heterogeneous FIFO queue. -// Any event type can be pushed into it, and drain extracts -// events of a specific type while leaving others in place. -type queue struct { - mu sync.Mutex - l *list.List -} - -func newQueue() *queue { - return &queue{ - l: list.New(), - } -} - -// push adds an event to the back of the queue. -func (q *queue) push(item any) { - q.mu.Lock() - defer q.mu.Unlock() - - q.l.PushBack(item) -} - -func (q *queue) prepend(q2 *queue) { - q.mu.Lock() - defer q.mu.Unlock() - q2.mu.Lock() - defer q2.mu.Unlock() - - q.l.PushFrontList(q2.l) -} - -func (q *queue) size() int { - q.mu.Lock() - defer q.mu.Unlock() - - return q.l.Len() -} - -func (q *queue) reset() { - q.mu.Lock() - defer q.mu.Unlock() - - q.l.Init() -} - -// each returns an iterator over all items of type T in the queue. -// Items of other types are skipped. -func each[T any](q *queue) iter.Seq[T] { - return func(yield func(T) bool) { - q.mu.Lock() - defer q.mu.Unlock() - - for e := q.l.Front(); e != nil; e = e.Next() { - v, ok := e.Value.(T) - if !ok { - continue - } - - if !yield(v) { - break - } - } - } -} diff --git a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go index c0f62d4821..8c8304bbd7 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go @@ -41,12 +41,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 @@ -69,7 +65,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, @@ -83,12 +78,14 @@ func NewUserfaultfdFromFd(fd uintptr, src block.Slicer, m *memory.Mapping, logge return u, nil } -// readEvents reads all available UFFD events from the file descriptor. -// Returns a queue with the events read, or an error. -func (u *Userfaultfd) readEvents(ctx context.Context) (*queue, error) { +// 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) { buf := make([]byte, unsafe.Sizeof(UffdMsg{})) - queue := newQueue() + var removes []*UffdRemove + var pagefaults []*UffdPagefault + for { n, err := syscall.Read(int(u.fd), buf) if errors.Is(err, syscall.EINTR) { @@ -98,13 +95,13 @@ func (u *Userfaultfd) readEvents(ctx context.Context) (*queue, error) { } if errors.Is(err, syscall.EAGAIN) { - // EAGAIN means that we have drained all the available events for the file descriptro. + // EAGAIN means that we have drained all the available events for the file descriptor. // We are done. break } if err != nil { - return nil, fmt.Errorf("failed reading uffd: %w", err) + return nil, nil, fmt.Errorf("failed reading uffd: %w", err) } // `Read` returned with 0 bytes actually read. No more events to read @@ -122,17 +119,15 @@ func (u *Userfaultfd) readEvents(ctx context.Context) (*queue, error) { switch event { case UFFD_EVENT_PAGEFAULT: - pagefault := (*UffdPagefault)(unsafe.Pointer(&arg[0])) - queue.push(pagefault) + pagefaults = append(pagefaults, (*UffdPagefault)(unsafe.Pointer(&arg[0]))) case UFFD_EVENT_REMOVE: - remove := (*UffdRemove)(unsafe.Pointer(&arg[0])) - queue.push(remove) + removes = append(removes, (*UffdRemove)(unsafe.Pointer(&arg[0]))) default: - return nil, ErrUnexpectedEventType + return nil, nil, ErrUnexpectedEventType } } - return queue, nil + return removes, pagefaults, nil } func (u *Userfaultfd) Serve( @@ -162,7 +157,7 @@ func (u *Userfaultfd) Serve( unix.POLLNVAL: "POLLNVAL", } - deferredEvents := newQueue() + var deferred deferredFaults for { if _, err := unix.Poll( @@ -229,21 +224,20 @@ func (u *Userfaultfd) Serve( continue } - events, err := u.readEvents(ctx) + 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) } - events.prepend(deferredEvents) - // This is not racy because there shouldn't be any goroutines running at the moment. - deferredEvents.reset() + // Collect deferred pagefaults from previous iteration's goroutines. + pagefaults = append(deferred.drain(), pagefaults...) // 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 events.size() == 0 { + if len(removes) == 0 && len(pagefaults) == 0 { eagainCounter.Increase("EAGAIN") continue @@ -254,11 +248,11 @@ func (u *Userfaultfd) Serve( eagainCounter.Log(ctx) // First handle the UFFD_EVENT_REMOVE events - for rm := range each[*UffdRemove](events) { + for _, rm := range removes { u.pageTracker.setState(uintptr(rm.start), uintptr(rm.end), removed) } - for pf := range each[*UffdPagefault](events) { + 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") @@ -266,7 +260,7 @@ func (u *Userfaultfd) Serve( // 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("unexecpted WP pagefault event, closuing UFFD") + return fmt.Errorf("unexpected WP pagefault event, closing UFFD") } addr := getPagefaultAddress(pf) @@ -277,46 +271,62 @@ func (u *Userfaultfd) Serve( return fmt.Errorf("failed to map: %w", err) } - state := u.pageTracker.get(addr) - switch state { + var source block.Slicer + + switch state := u.pageTracker.get(addr); state { case faulted: + // TODO: Can we skip faulting pages that are already faulted? How does that play with prefaulting? continue case removed: - u.wg.Go(func() error { - handled, err := u.faultPage(ctx, addr, offset, nil, fdExit.SignalExit, block.Remove) - if err != nil { - return err - } - - if !handled { - deferredEvents.push(pf) - } - - return nil - }) + continue case unfaulted: + 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. + 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 } - u.wg.Go(func() error { - handled, err := u.faultPage(ctx, addr, offset, u.src, fdExit.SignalExit, accessType) - if err != nil { - return err - } + handled, err := u.faultPage( + ctx, + addr, + offset, + source, + fdExit.SignalExit, + copyMode, + ) + if err != nil { + return err + } - if !handled { - deferredEvents.push(pf) - } + if handled { + u.pageTracker.setState(addr, addr+u.pageSize, faulted) + } else { + deferred.push(pf) + } - return nil - }) - default: - return fmt.Errorf("unexpected pageState: %#v", state) - } + u.prefetchTracker.Add(offset, accessType) + + return nil + }) } } } @@ -327,17 +337,10 @@ func (u *Userfaultfd) faultPage( offset int64, source block.Slicer, onFailure func() error, - accessType block.AccessType, + 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", u.pageSize), zap.Any("panic", r)) @@ -345,21 +348,13 @@ func (u *Userfaultfd) faultPage( }() var writeErr error - var copyMode 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 cause clear the write-protection bit. - if accessType != block.Write { - copyMode = 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, copyMode) + writeErr = u.fd.zero(addr, u.pageSize, mode) case source == nil && u.pageSize == header.HugepageSize: - writeErr = u.fd.copy(addr, u.pageSize, header.EmptyHugePage, 0) + writeErr = u.fd.copy(addr, u.pageSize, header.EmptyHugePage, mode) default: b, dataErr := source.Slice(ctx, offset, int64(u.pageSize)) if dataErr != nil { @@ -371,15 +366,14 @@ func (u *Userfaultfd) faultPage( return false, fmt.Errorf("failed to read from source: %w", joinedErr) } - writeErr = u.fd.copy(addr, u.pageSize, b, copyMode) + 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 twich, since we are now + // try to handle a page fault for the same address twice, since we are now // tracking the state of pages. if errors.Is(writeErr, unix.EEXIST) { - u.pageTracker.setState(addr, addr+u.pageSize, faulted) span.SetAttributes(attribute.Bool("uffd.already_mapped", true)) return true, nil @@ -403,20 +397,13 @@ func (u *Userfaultfd) faultPage( return false, fmt.Errorf("failed uffdio copy %w", joinedErr) } - // Add the offset to the missing requests tracker with metadata. - u.missingRequests.Add(offset) - u.prefetchTracker.Add(offset, accessType) - u.pageTracker.setState(addr, addr+u.pageSize, faulted) - return true, nil } 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() + u.settleRequests.Unlock() //nolint:staticcheck // SA2001: intentional — we just need to settle the read locks. return u.prefetchTracker.PrefetchData() } From 9a6f394e03cd15c7cdb180318e49c7a57189d8fe Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Tue, 3 Mar 2026 23:38:34 -0800 Subject: [PATCH 13/24] Fix lint --- packages/orchestrator/internal/portmap/recover.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/orchestrator/internal/portmap/recover.go b/packages/orchestrator/internal/portmap/recover.go index dcdadc0e45..a5e7058b68 100644 --- a/packages/orchestrator/internal/portmap/recover.go +++ b/packages/orchestrator/internal/portmap/recover.go @@ -57,8 +57,8 @@ func (h *recovery) PMAPPROC_CALLIT(args rfc1057.Call_args) rfc1057.Call_result { return h.inner.PMAPPROC_CALLIT(args) } -func (h *recovery) tryRecovery(name string) { - if r := recover(); r != nil { //nolint:revive // recover is called from a deferred named function, which is valid Go +func (h *recovery) tryRecovery(name string) { //nolint:revive // recover works fine — always called via defer + if r := recover(); r != nil { logger.L().Error(h.ctx, fmt.Sprintf("panic in %q portmap handler", name), zap.Any("panic", r)) } } From 01183cee7edc7a8ecfa114150f98c7362fbdb4ad Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Tue, 3 Mar 2026 23:47:52 -0800 Subject: [PATCH 14/24] Fix lint supress --- packages/orchestrator/internal/portmap/recover.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/orchestrator/internal/portmap/recover.go b/packages/orchestrator/internal/portmap/recover.go index a5e7058b68..a688f1bd00 100644 --- a/packages/orchestrator/internal/portmap/recover.go +++ b/packages/orchestrator/internal/portmap/recover.go @@ -57,8 +57,8 @@ func (h *recovery) PMAPPROC_CALLIT(args rfc1057.Call_args) rfc1057.Call_result { return h.inner.PMAPPROC_CALLIT(args) } -func (h *recovery) tryRecovery(name string) { //nolint:revive // recover works fine — always called via defer - if r := recover(); r != nil { +func (h *recovery) tryRecovery(name string) { + if r := recover(); r != nil { //nolint:revive // recover works fine — always called via defer logger.L().Error(h.ctx, fmt.Sprintf("panic in %q portmap handler", name), zap.Any("panic", r)) } } From 30c581cb64b5f55d30b407286d137298185552e9 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Tue, 3 Mar 2026 23:56:20 -0800 Subject: [PATCH 15/24] Clarify behavior; Fix bug --- .../internal/sandbox/uffd/userfaultfd/userfaultfd.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go index 8c8304bbd7..880ecd3d87 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go @@ -276,9 +276,11 @@ func (u *Userfaultfd) Serve( switch state := u.pageTracker.get(addr); state { case faulted: // TODO: Can we skip faulting pages that are already faulted? How does that play with prefaulting? + // Skip faulting the page. continue case removed: - continue + // Fault the page as empty. + break case unfaulted: source = u.src default: From f23b1fb2f8bbf1d8057df0894cfaba7a2ca0689b Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Wed, 4 Mar 2026 00:16:17 -0800 Subject: [PATCH 16/24] Cleanup --- .../internal/sandbox/uffd/userfaultfd/userfaultfd.go | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go index 880ecd3d87..85e5389121 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go @@ -280,7 +280,6 @@ func (u *Userfaultfd) Serve( continue case removed: // Fault the page as empty. - break case unfaulted: source = u.src default: From 430f224eb60f04c20538501102181e1f05a71049 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Wed, 4 Mar 2026 00:29:33 -0800 Subject: [PATCH 17/24] Fix version check for major > 2 --- packages/api/internal/sandbox/sandbox_features.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/api/internal/sandbox/sandbox_features.go b/packages/api/internal/sandbox/sandbox_features.go index 82d517d537..cf3fb933a8 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.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.Minor() >= 14 { return true } From 550f4324be8916df52c0ee22d778c3f035f2be2e Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Fri, 6 Mar 2026 20:09:16 +0100 Subject: [PATCH 18/24] fix(uffd): clarifications and minor fixes Signed-off-by: Babis Chalios --- .../internal/sandbox/uffd/userfaultfd/userfaultfd.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go index 85e5389121..86a27db1e3 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go @@ -81,6 +81,9 @@ func NewUserfaultfdFromFd(fd uintptr, src block.Slicer, m *memory.Mapping, logge // 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 @@ -275,8 +278,10 @@ func (u *Userfaultfd) Serve( switch state := u.pageTracker.get(addr); state { case faulted: - // TODO: Can we skip faulting pages that are already faulted? How does that play with prefaulting? - // Skip faulting the page. + // 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. @@ -320,12 +325,11 @@ func (u *Userfaultfd) Serve( if handled { u.pageTracker.setState(addr, addr+u.pageSize, faulted) + u.prefetchTracker.Add(offset, accessType) } else { deferred.push(pf) } - u.prefetchTracker.Add(offset, accessType) - return nil }) } From 891deb2bd77f916b33dfad2ad645ac0e16eca3cf Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Mon, 9 Mar 2026 15:20:52 +0100 Subject: [PATCH 19/24] fix: 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 --- .../orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go index 36bef8d3f1..8c46a0e192 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go @@ -27,8 +27,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 { return fmt.Errorf("failed to fault page: %w", err) From 2184a6725823355e5880e03c108781e2fdef1065 Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Mon, 9 Mar 2026 17:23:22 +0100 Subject: [PATCH 20/24] 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 --- .../sandbox/uffd/userfaultfd/prefault.go | 14 ++++++++++++- .../sandbox/uffd/userfaultfd/userfaultfd.go | 21 ++++++++++++------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go index 8c46a0e192..d4452484fd 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go @@ -3,6 +3,8 @@ package userfaultfd import ( "context" "fmt" + + "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/block" ) // Prefault proactively copies a page to guest memory at the given offset. @@ -21,13 +23,23 @@ func (u *Userfaultfd) Prefault(ctx context.Context, offset int64, data []byte) e return fmt.Errorf("data length (%d) does not match pagesize (%d)", len(data), u.pageSize) } + // 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 + // 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, + block.Read, directDataSource{data, int64(u.pageSize)}, nil, - UFFDIO_COPY_MODE_WP, ) if err != nil { return fmt.Errorf("failed to fault page: %w", err) diff --git a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go index 86a27db1e3..ca0dc15405 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go @@ -298,14 +298,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 @@ -315,9 +310,9 @@ func (u *Userfaultfd) Serve( ctx, addr, offset, + accessType, source, fdExit.SignalExit, - copyMode, ) if err != nil { return err @@ -340,9 +335,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) @@ -353,11 +348,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 ede59468a53e7eab31658fd99bd5913f63acbb22 Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Mon, 9 Mar 2026 17:28:17 +0100 Subject: [PATCH 21/24] fix(uffd): sanitize error recording while prefaulting During pre-faulting we always recorded an error to the span when `faultPage` returned `!handled` for a block, but not all such cases are actually errors. EEXIST errors means that the page has been faulted already, which can potentially happen since we are pre-faulting and handling on-demand page faults at the same time. EAGAIN happens when a UFFD_EVENT_REMOVE event landed in the UFFD while were trying to handle a page fault. Both those cases are fine from the prefaulter's point of view. In the former, there's nothing to do, on-demand handler won the race. In the latter, we need to let the on-demand page fault handler handle the remove event before we proceed. Only record an error when err != nil. Otherwise, add an event. Signed-off-by: Babis Chalios --- .../internal/sandbox/uffd/userfaultfd/prefault.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go index d4452484fd..ee3096b7ee 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go @@ -42,11 +42,13 @@ func (u *Userfaultfd) Prefault(ctx context.Context, offset int64, data []byte) e nil, ) if err != nil { + span.RecordError(fmt.Errorf("could not prefault page")) + return fmt.Errorf("failed to fault page: %w", err) } if !handled { - span.RecordError(fmt.Errorf("page already faulted")) + span.AddEvent("prefault: page already faulted or write returned EAGAIN") } return nil From 82cd6f89b2a9345a8a5a3b356c6696b96ddd009b Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Tue, 10 Mar 2026 14:26:54 +0100 Subject: [PATCH 22/24] 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 --- .../internal/sandbox/uffd/userfaultfd/fd.go | 41 +++++++++++++++++-- .../sandbox/uffd/userfaultfd/userfaultfd.go | 19 +++++++-- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/fd.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/fd.go index 8280d190ce..958a199f47 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/fd.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/fd.go @@ -47,10 +47,16 @@ const ( UFFDIO_COPY_MODE_WP = C.UFFDIO_COPY_MODE_WP - UFFDIO_API = C.UFFDIO_API - UFFDIO_REGISTER = C.UFFDIO_REGISTER - UFFDIO_COPY = C.UFFDIO_COPY - UFFDIO_ZEROPAGE = C.UFFDIO_ZEROPAGE + 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_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 @@ -116,6 +122,13 @@ func newUffdioZero(address, pagesize, mode CULong) UffdioZero { } } +func newUffdioWriteProtect(address, pagesize, mode CULong) UffdioWriteProtect { + return UffdioWriteProtect{ + _range: newUffdioRange(address, pagesize), + mode: mode, + } +} + func getMsgEvent(msg *UffdMsg) CUChar { return msg.event } @@ -163,6 +176,26 @@ func (f Fd) zero(addr, pagesize uintptr, mode CULong) error { return nil } +func (f Fd) writeProtect(addr, pagesize uintptr, mode CULong) error { + writeProtect := newUffdioWriteProtect(CULong(addr), 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)) + + 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)) } diff --git a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go index ca0dc15405..dcb159d4ca 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go @@ -360,9 +360,22 @@ 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. - writeErr = u.fd.zero(addr, u.pageSize, 0) + // 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.HugepageSize: writeErr = u.fd.copy(addr, u.pageSize, header.EmptyHugePage, mode) default: From 823e8444ac22edda1e699fcafdb913089e05b20b Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Tue, 10 Mar 2026 13:50:54 -0700 Subject: [PATCH 23/24] Add feature flag for free page reporting --- packages/api/internal/template-manager/create_template.go | 3 ++- packages/shared/pkg/feature-flags/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..09c4c6c270 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" + featureflags "github.com/e2b-dev/infra/packages/shared/pkg/feature-flags" 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/feature-flags/flags.go b/packages/shared/pkg/feature-flags/flags.go index 646f8e3d9c..86a12c7ee8 100644 --- a/packages/shared/pkg/feature-flags/flags.go +++ b/packages/shared/pkg/feature-flags/flags.go @@ -104,6 +104,7 @@ var ( PersistentVolumesFlag = newBoolFlag("can-use-persistent-volumes", env.IsDevelopment()) ExecutionMetricsOnWebhooksFlag = newBoolFlag("execution-metrics-on-webhooks", false) // TODO: Remove NLT 20250315 SandboxLabelBasedSchedulingFlag = newBoolFlag("sandbox-label-based-scheduling", false) + FreePageReportingFlag = newBoolFlag("free-page-reporting", false) ) type IntFlag struct { From 49350ad22aaa6d17df329416e24a4a30f20fa871 Mon Sep 17 00:00:00 2001 From: ValentaTomas Date: Tue, 10 Mar 2026 21:46:44 -0700 Subject: [PATCH 24/24] Add WP mode test for prefetch --- .../sandbox/uffd/userfaultfd/async_wp_test.go | 55 +++++++++++++++++++ .../userfaultfd/cross_process_helpers_test.go | 7 +++ .../sandbox/uffd/userfaultfd/helpers_test.go | 2 + .../sandbox/uffd/userfaultfd/prefault.go | 2 +- .../sandbox/uffd/userfaultfd/userfaultfd.go | 11 ++-- 5 files changed, 72 insertions(+), 5 deletions(-) diff --git a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/async_wp_test.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/async_wp_test.go index 301a112578..e2e24f4210 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/async_wp_test.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/async_wp_test.go @@ -36,6 +36,7 @@ func TestAsyncWriteProtection(t *testing.T) { operations []operation expectedDirty []int expectedClean []int + alwaysWP bool }{ { name: "4k read then write same page", @@ -224,6 +225,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 { @@ -233,6 +287,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/internal/sandbox/uffd/userfaultfd/cross_process_helpers_test.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/cross_process_helpers_test.go index 1941e9b290..be9aa044d2 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/cross_process_helpers_test.go +++ b/packages/orchestrator/internal/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/internal/sandbox/uffd/userfaultfd/helpers_test.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/helpers_test.go index 467ab423b4..9495d88366 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/helpers_test.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/helpers_test.go @@ -20,6 +20,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/internal/sandbox/uffd/userfaultfd/prefault.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go index ee3096b7ee..a7f3253c6e 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/prefault.go @@ -24,7 +24,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/internal/sandbox/uffd/userfaultfd/userfaultfd.go b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go index dcb159d4ca..f3718841b7 100644 --- a/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go +++ b/packages/orchestrator/internal/sandbox/uffd/userfaultfd/userfaultfd.go @@ -45,6 +45,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 @@ -348,11 +354,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 }