lambda/supervisor/model/model.go (259 lines of code) (raw):
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package model
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"syscall"
"time"
)
// Start, Stop and Configure methods are not used in Core anymore.
// Client interface splitted into Launcher and Executer parts for backward compatibility of dependent packages.
type ContainerSupervisor interface {
Start(context.Context, *StartRequest) error
Configure(context.Context, *ConfigureRequest) error
Stop(context.Context, *StopRequest) (*StopResponse, error)
Freeze(context.Context, *FreezeRequest) (*FreezeResponse, error)
Thaw(context.Context, *ThawRequest) error
Exit(context.Context)
}
type ProcessSupervisor interface {
Exec(context.Context, *ExecRequest) error
Terminate(context.Context, *TerminateRequest) error
Kill(context.Context, *KillRequest) error
Events(context.Context, *EventsRequest) (<-chan Event, error)
}
type SupervisorClient interface {
ContainerSupervisor
ProcessSupervisor
Ping(ctx context.Context) error
}
type StartRequest struct {
Domain string `json:"domain"`
}
type Mount struct {
DriveMount DriveMount
BindMount BindMount
MountType MountType
}
type MountType int
const (
_ MountType = iota
MountTypeDrive
MountTypeBind
)
type CgroupProfileName string
const (
Throttled CgroupProfileName = "throttled"
Unthrottled CgroupProfileName = "unthrottled"
)
func (m *Mount) MarshalJSON() ([]byte, error) {
switch m.MountType {
case MountTypeDrive:
return m.DriveMount.MarshalJSON()
case MountTypeBind:
return m.BindMount.MarshalJSON()
default:
return nil, fmt.Errorf("invalid mount type: %v", m.MountType)
}
}
// Mount in lockhard::mnt is a Rust enum, an algebraic type, where each case has different set of fields.
// This models only the Mount::Drive case, the only one we need for now.
type DriveMount struct {
Source string `json:"source,omitempty"`
Destination string `json:"destination,omitempty"`
FsType string `json:"fs_type,omitempty"`
Options []string `json:"options,omitempty"`
Chowner []uint32 `json:"chowner,omitempty"` // array of two integers representing a tuple
Chmode uint32 `json:"chmode,omitempty"`
// Lockhard also expects a "type" field here, which in our case is constant, so we provide it upon serialization below
}
// Adds the "type": "drive" to json
func (m *DriveMount) MarshalJSON() ([]byte, error) {
type driveMountAlias DriveMount
return json.Marshal(&struct {
Type string `json:"type,omitempty"`
*driveMountAlias
}{
Type: "drive",
driveMountAlias: (*driveMountAlias)(m),
})
}
type BindMount struct {
Source string `json:"source,omitempty"`
Destination string `json:"destination,omitempty"`
Options []string `json:"options,omitempty"`
}
func (m *BindMount) MarshalJSON() ([]byte, error) {
type bindMountAlias BindMount
return json.Marshal(&struct {
Type string `json:"type,omitempty"`
*bindMountAlias
}{
Type: "bind",
bindMountAlias: (*bindMountAlias)(m),
})
}
type Capabilities struct {
Ambient []string `json:"ambient,omitempty"`
Bounding []string `json:"bounding,omitempty"`
Effective []string `json:"effective,omitempty"`
Inheritable []string `json:"inheritable,omitempty"`
Permitted []string `json:"permitted,omitempty"`
}
type CgroupProfiles struct {
Throttled CgroupProfileConfig `json:"throttled"`
Unthrottled CgroupProfileConfig `json:"unthrottled"`
}
type CgroupProfileConfig struct {
CPULimit float64 `json:"cpu_limit"`
MemoryLimitBytes uint64 `json:"memory_limit_bytes"`
}
type ExecUser struct {
UID *uint32 `json:"uid"`
GID *uint32 `json:"gid"`
}
type ConfigureRequest struct {
// domain to configure
Domain string `json:"domain"`
Mounts []Mount `json:"mounts,omitempty"`
Capabilities *Capabilities `json:"capabilities,omitempty"`
SeccompFilters []string `json:"seccomp_filters,omitempty"`
// list of cgroup profiles available for the domain
// cgroup profiles are set on start and thaw request. Start profile
// if configured (as it can vary), thaw profile is always the same (throttled)
CgroupProfiles *CgroupProfiles `json:"cgroup_profiles,omitempty"`
// name of the cgroup profile to enforce at domain start
StartProfile CgroupProfileName `json:"start_profile,omitempty"`
// uid and gid of the user the spawned process runs as (w.r.t. the domain user namespace).
// If nil, Supervisor will use the ExecUser specified in the domain configuration file
ExecUser *ExecUser `json:"exec_user,omitempty"`
// additional hooks to execute on domain start
AdditionalStartHooks []Hook `json:"additional_start_hooks,omitempty"`
}
type EventsRequest struct {
Domain string `json:"domain"`
}
type Event struct {
Time uint64 `json:"timestamp_millis"`
Event EventData `json:"event"`
}
// EventData is a union type tagged by the "EventType"
// and "Cause" strings.
// you can use ProcessTermination() or EventLoss() to access
// the correct type of Event.
type EventData struct {
EvType string `json:"type"`
Domain *string `json:"domain"`
Name *string `json:"name"`
Cause *string `json:"cause"`
Signo *int32 `json:"signo"`
ExitStatus *int32 `json:"exit_status"`
Size *uint64 `json:"size"`
}
// returns nil if the event is not a EventLoss event
// otherwise returns how many events were lost due to
// backpressure (slow reader)
func (d EventData) EventLoss() *uint64 {
return d.Size
}
// Returns a ProcessTermination struct that describe the process
// which terminated. Use Signaled() or Exited() to check whether
// the process terminated because of a signal or exited on its own
func (d EventData) ProcessTerminated() *ProcessTermination {
if d.Signo != nil || d.ExitStatus != nil {
return &ProcessTermination{
Domain: d.Domain,
Name: d.Name,
Signo: d.Signo,
ExitStatus: d.ExitStatus,
}
}
return nil
}
// Event signalling that a process exited
type ProcessTermination struct {
Domain *string
Name *string
Signo *int32
ExitStatus *int32
}
// If not nil, the process was terminated by an unhandled signal.
// The returned value is the number of the signal that terminated the process
func (t ProcessTermination) Signaled() *int32 {
return t.Signo
}
// It not nil, the process exited (as opposed to killed by a signal).
// The returned value is the exit_status returned by the process
func (t ProcessTermination) Exited() *int32 {
return t.ExitStatus
}
func (t ProcessTermination) Success() bool {
return t.ExitStatus != nil && *t.ExitStatus == 0
}
// Transform the process termination status in a string that
// is equal to what would be returned by golang exec.ExitError.Error()
// We used to rely on this format to report errors to customer (sigh)
// so we keep this for backwards compatibility
func (t ProcessTermination) String() string {
if t.ExitStatus != nil {
return fmt.Sprintf("exit status %d", *t.ExitStatus)
}
sig := syscall.Signal(*t.Signo)
return fmt.Sprintf("signal: %s", sig.String())
}
type Hook struct {
// Unique name identifying the hook
Name string `json:"name"`
// Path in the parent domain mount namespace that locates
// the executable to run as the hook
Path string `json:"path"`
// Args for the hook
Args []string `json:"args,omitempty"`
// Map of ENV variables to set when running the hook
Env *map[string]string `json:"envs,omitempty"`
}
type ExecRequest struct {
// Identifier that Supervisor will assign to the spawned process.
// The tuple (Domain,Name) must be unique. It is the caller's responsibility
// to generate the unique name
Name string `json:"name"`
Domain string `json:"domain"`
// Path pointing to the exectuable file within the domain's root filesystem
Path string `json:"path"`
Args []string `json:"args,omitempty"`
// If nil, root of the domain
Cwd *string `json:"cwd,omitempty"`
Env *map[string]string `json:"env,omitempty"`
Logging Logging `json:"log_config"`
StdoutWriter io.Writer `json:"-"`
StderrWriter io.Writer `json:"-"`
ExtraFiles *[]*os.File `json:"-"`
}
// Logging specifies where Supervisor should send Command's logs to
type Logging struct {
Managed ManagedLogging `json:"managed"`
}
type ManagedLogging struct {
Topic ManagedLoggingTopic `json:"topic"`
Formats []ManagedLoggingFormat `json:"formats"`
}
type ManagedLoggingTopic string
const (
RuntimeManagedLoggingTopic ManagedLoggingTopic = "runtime"
RtExtensionManagedLoggingTopic ManagedLoggingTopic = "runtime_extension"
)
type ManagedLoggingFormat string
const (
LineBasedManagedLogging ManagedLoggingFormat = "line"
MessageBasedManagedLogging ManagedLoggingFormat = "message"
)
type ErrorKind string
const (
// operation on an unkown entity (e.g., domain process)
NoSuchEntity ErrorKind = "no_such_entity"
// operation not allowed in the current state (e.g., tried to exec a proces in a domain which is not booted)
InvalidState ErrorKind = "invalid_state"
// Serialization or derserialization issue in the communication
Serde ErrorKind = "serde"
// Unhandled Supervisor server error
Failure ErrorKind = "failure"
)
type SupervisorError struct {
Kind ErrorKind `json:"error_kind"`
Message *string `json:"message"`
}
func (e *SupervisorError) Error() string {
return string(e.Kind)
}
// Send SIGETERM asynchrnously to a process
type TerminateRequest struct {
Name string `json:"name"`
Domain string `json:"domain"`
}
// Force terminate a process (SIGKILL)
// Block until process is exited or timeout
// Deadline needs to be in the future
type KillRequest struct {
Name string `json:"name"`
Domain string `json:"domain"`
Deadline time.Time `json:"deadline"`
}
// Stop the domain.
type StopRequest struct {
Domain string `json:"domain"`
Deadline time.Time `json:"deadline"`
}
type StopResponse struct {
CycleDeltaMetrics CycleDeltaMetrics `json:"cycle_delta_metrics"`
}
type FreezeRequest struct {
Domain string `json:"domain"`
}
type FreezeResponse struct {
CycleDeltaMetrics CycleDeltaMetrics `json:"cycle_delta_metrics"`
}
type MicrovmNetworkInterfaceMetrics struct {
ReceivedBytes uint64 `json:"received_bytes"`
TransmittedBytes uint64 `json:"transmitted_bytes"`
}
type CycleDeltaMetrics struct {
// CPU time (in nanoseconds) obtained by domain cgroup from cpuacct.usage
// https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt
DomainCPURunNs uint64 `json:"domain_cpu_run_ns"`
// time (in nanoseconds) for domain cycle
DomainRunNs uint64 `json:"domain_run_ns"`
// CPU delta time for service cgroup
ServiceCPURunNs uint64 `json:"service_cpu_run_ns"`
// Maximum memory used (in bytes) for domain
DomainMaxMemoryUsageBytes uint64 `json:"domain_max_memory_usage_bytes"`
// CPU delta time (in nanoseconds) obtained from /sys/fs/cgroup/cpu,cpuacct/cpuacct.usage
MicrovmCPURunNs uint64 `json:"microvm_cpu_run_ns"`
// Map with network interface name as key and network metrics as a value
MicrovmNetworksBytes map[string]MicrovmNetworkInterfaceMetrics `json:"microvm_network_interfaces"`
// time ( in nanoseconds ) for idle cpu time
InvokeIdleCPURunNs uint64 `json:"idle_cpu_run_ns"`
}
type ThawRequest struct {
Domain string `json:"domain"`
}