Skip to content

Commit

Permalink
feat: Idempotent run V1 - services can now be live-updated inside an …
Browse files Browse the repository at this point in the history
…enclave (#954)

## Description:
Kurtosis is now able to re-run the same Starlark script with changes to
the `ServiceConfig` objects used in the `add_service` and `add_services`
instructions. Kurtosis will now re-run those specific instructions to
update the underlying service. Dependent instructions referencing this
service (like `exec`, `wait`) will also be re-run when the service is
updated.

## Is this change user facing?
YES
<!-- If yes, please add the "user facing" label to the PR -->
<!-- If yes, don't forget to include docs changes where relevant -->

## References (if applicable):
<!-- Add relevant Github Issues, Discord threads, or other helpful
information. -->
  • Loading branch information
Guillaume Bouvignies authored Jul 24, 2023
1 parent f1b52ca commit a6a118d
Show file tree
Hide file tree
Showing 45 changed files with 1,002 additions and 222 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,10 @@ func (network *DefaultServiceNetwork) AddServices(
startedServices := map[service.ServiceName]*service.Service{}
failedServices := map[service.ServiceName]error{}

if len(serviceConfigs) == 0 {
return startedServices, failedServices, nil
}

// Save the services currently running in enclave for later
currentlyRunningServicesInEnclave := map[service.ServiceName]bool{}
for serviceName := range network.registeredServiceInfo {
Expand Down Expand Up @@ -460,10 +464,9 @@ func (network *DefaultServiceNetwork) AddServices(
return startedServices, map[service.ServiceName]error{}, nil
}

// UpdateService updates a service currently running inside an enclave. See UpdateServices for more details
func (network *DefaultServiceNetwork) UpdateService(ctx context.Context, serviceName service.ServiceName, serviceConfig *service.ServiceConfig) (*service.Service, error) {
func (network *DefaultServiceNetwork) UpdateService(ctx context.Context, serviceName service.ServiceName, updateServiceConfig *service.ServiceConfig) (*service.Service, error) {
serviceConfigMap := map[service.ServiceName]*service.ServiceConfig{
serviceName: serviceConfig,
serviceName: updateServiceConfig,
}

startedServices, serviceFailed, err := network.UpdateServices(ctx, serviceConfigMap, singleServiceStartupBatch)
Expand All @@ -489,6 +492,10 @@ func (network *DefaultServiceNetwork) UpdateServices(ctx context.Context, update
failedServicesPool := map[service.ServiceName]error{}
successfullyUpdatedService := map[service.ServiceName]*service.Service{}

if len(updateServiceConfigs) == 0 {
return successfullyUpdatedService, failedServicesPool, nil
}

// First, remove the service
serviceUuidToNameMap := map[service.ServiceUUID]service.ServiceName{}
serviceUuidsToRemove := map[service.ServiceUUID]bool{}
Expand All @@ -511,7 +518,7 @@ func (network *DefaultServiceNetwork) UpdateServices(ctx context.Context, update
if serviceName, found := serviceUuidToNameMap[serviceUuid]; found {
failedServicesPool[serviceName] = serviceErr
} else {
return nil, nil, stacktrace.NewError("Error mapping service UUID to service name. This is a bug in Kurtosis")
return nil, nil, stacktrace.NewError("Error mapping service UUID to service name. This is a bug in Kurtosis.\nserviceUuidsToRemove=%v\nfailedRemovedServices=%v\nsuccessfullyRemovedServices=%v\nserviceUuidToNameMap=%v", serviceUuidsToRemove, failedRemovedServices, successfullyRemovedServices, serviceUuidToNameMap)
}
}

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ type ServiceNetwork interface {
UpdateService(
ctx context.Context,
serviceName service.ServiceName,
serviceConfig *service.ServiceConfig,
updateServiceConfig *service.ServiceConfig,
) (
*service.Service,
error,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package enclave_structure

//go:generate go run github.com/dmarkham/enumer -trimprefix "EnclaveComponentStatus" -type=EnclaveComponentStatus
type EnclaveComponentStatus uint8

const (
// ComponentIsNew means the component was first created during this run
ComponentIsNew EnclaveComponentStatus = iota

// ComponentWasLeftIntact means the component was present prior to the beginning of this run and has been left
// intact
ComponentWasLeftIntact

// ComponentIsUpdated means the component was present prior to the beginning of this run but has been updated
// during this run
ComponentIsUpdated
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package enclave_structure

import (
"github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service"
)

type EnclaveComponents struct {
enclaveServices map[service.ServiceName]EnclaveComponentStatus
}

func NewEnclaveComponents() *EnclaveComponents {
return &EnclaveComponents{
enclaveServices: map[service.ServiceName]EnclaveComponentStatus{},
}
}

func (components *EnclaveComponents) AddService(serviceName service.ServiceName, componentStatus EnclaveComponentStatus) {
components.enclaveServices[serviceName] = componentStatus
}

func (components *EnclaveComponents) HasServiceBeenUpdated(serviceName service.ServiceName) bool {
if serviceStatus, found := components.enclaveServices[serviceName]; found {
return serviceStatus == ComponentIsUpdated
}
return false
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package enclave_structure

//go:generate go run github.com/dmarkham/enumer -trimprefix "InstructionResolutionStatus" -type=InstructionResolutionStatus
type InstructionResolutionStatus uint8

const (
// InstructionIsEqual means this instruction is strictly equal to the one it's being compared to.
InstructionIsEqual InstructionResolutionStatus = iota

// InstructionIsUpdate means this instruction is the same as the one it's being compared to, but with some
// changes to its parameters. It should be re-run so that the enclave can be updated.
InstructionIsUpdate

// InstructionIsUnknown means the two instructions are completely different.
InstructionIsUnknown

// InstructionIsNotResolvableAbort means one of the compared instructions is fundamentally incompatible with the
// concept of idempotency. Kurtosis should stop trying to run it in an idempotent way and fall back to the default
// behaviour
InstructionIsNotResolvableAbort
)

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit a6a118d

Please sign in to comment.