Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Added
ReadyConditions
type which is used in ServiceConfig
f…
…or defining how to check services readiness (#151)
- Loading branch information
Showing
10 changed files
with
415 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
120 changes: 120 additions & 0 deletions
120
...artosis_engine/kurtosis_starlark_framework/test_engine/ready_conditions_framework_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package test_engine | ||
|
||
import ( | ||
"fmt" | ||
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/kurtosis_type_constructor" | ||
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_types/service_config" | ||
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/recipe" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"go.starlark.net/starlark" | ||
"testing" | ||
"time" | ||
) | ||
|
||
type readyConditionsTestCase struct { | ||
*testing.T | ||
} | ||
|
||
func newReadyConditionsTestCase(t *testing.T) *readyConditionsTestCase { | ||
return &readyConditionsTestCase{ | ||
T: t, | ||
} | ||
} | ||
|
||
func (t *readyConditionsTestCase) GetId() string { | ||
return service_config.ReadyConditionsTypeName | ||
} | ||
|
||
func (t *readyConditionsTestCase) GetTypeConstructor() *kurtosis_type_constructor.KurtosisTypeConstructor { | ||
return service_config.NewReadyConditionsType() | ||
} | ||
|
||
func (t *readyConditionsTestCase) GetStarlarkCode() string { | ||
return fmt.Sprintf("%s(%s=%s(%s=%q, %s=%q, %s=%s), %s=%q, %s=%q, %s=%s, %s=%q, %s=%q)", | ||
service_config.ReadyConditionsTypeName, | ||
service_config.RecipeAttr, | ||
recipe.GetHttpRecipeTypeName, | ||
recipe.PortIdAttr, | ||
TestReadyConditionsRecipePortId, | ||
recipe.EndpointAttr, | ||
TestReadyConditionsRecipeEndpoint, | ||
recipe.ExtractKeyPrefix, | ||
TestReadyConditionsRecipeExtract, | ||
service_config.FieldAttr, | ||
TestReadyConditionsField, | ||
service_config.AssertionAttr, | ||
TestReadyConditionsAssertion, | ||
service_config.TargetAttr, | ||
TestReadyConditionsTarget, | ||
service_config.IntervalAttr, | ||
TestReadyConditionsInterval, | ||
service_config.TimeoutAttr, | ||
TestReadyConditionsTimeout, | ||
) | ||
} | ||
|
||
func (t *readyConditionsTestCase) Assert(typeValue starlark.Value) { | ||
receivedReadyConditions, ok := typeValue.(*service_config.ReadyConditions) | ||
require.True(t, ok) | ||
|
||
uncastedRecipe, err := receivedReadyConditions.GetRecipe() | ||
if assert.Nil(t, err) { | ||
castedRecipe, ok := uncastedRecipe.(*recipe.HttpRequestRecipe) | ||
require.True(t, ok) | ||
|
||
portIdAttrValue, err := castedRecipe.Attr(recipe.PortIdAttr) | ||
if assert.Nil(t, err) { | ||
portId, ok := portIdAttrValue.(starlark.String) | ||
require.True(t, ok) | ||
require.Equal(t, TestReadyConditionsRecipePortId, portId.GoString()) | ||
} | ||
|
||
endpointAttrValue, err := castedRecipe.Attr(recipe.EndpointAttr) | ||
if assert.Nil(t, err) { | ||
endpoint, ok := endpointAttrValue.(starlark.String) | ||
require.True(t, ok) | ||
require.Equal(t, TestReadyConditionsRecipeEndpoint, endpoint.GoString()) | ||
} | ||
|
||
extractAttrValue, err := castedRecipe.Attr(recipe.ExtractKeyPrefix) | ||
if assert.Nil(t, err) { | ||
extract, ok := extractAttrValue.(*starlark.Dict) | ||
require.True(t, ok) | ||
expectedExtractLen := 0 | ||
require.Equal(t, expectedExtractLen, extract.Len()) | ||
} | ||
} | ||
|
||
field, err := receivedReadyConditions.GetField() | ||
if assert.Nil(t, err) { | ||
require.Equal(t, TestReadyConditionsField, field) | ||
} | ||
|
||
assertion, err := receivedReadyConditions.GetAssertion() | ||
if assert.Nil(t, err) { | ||
require.Equal(t, TestReadyConditionsAssertion, assertion) | ||
} | ||
|
||
target, err := receivedReadyConditions.GetTarget() | ||
if assert.Nil(t, err) { | ||
require.Equal(t, TestReadyConditionsTarget, target.String()) | ||
} | ||
|
||
interval, err := receivedReadyConditions.GetInterval() | ||
if assert.Nil(t, err) { | ||
expectedInterval, err := time.ParseDuration(TestReadyConditionsInterval) | ||
if assert.Nil(t, err) { | ||
require.Equal(t, expectedInterval, interval) | ||
} | ||
} | ||
|
||
timeout, err := receivedReadyConditions.GetTimeout() | ||
if assert.Nil(t, err) { | ||
expectedTimeout, err := time.ParseDuration(TestReadyConditionsTimeout) | ||
if assert.Nil(t, err) { | ||
require.Equal(t, expectedTimeout, timeout) | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
207 changes: 207 additions & 0 deletions
207
...r/api_container/server/startosis_engine/kurtosis_types/service_config/ready_conditions.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
package service_config | ||
|
||
import ( | ||
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_instruction/assert" | ||
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework" | ||
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/builtin_argument" | ||
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/kurtosis_type_constructor" | ||
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/recipe" | ||
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_errors" | ||
"go.starlark.net/starlark" | ||
"reflect" | ||
"time" | ||
) | ||
|
||
const ( | ||
ReadyConditionsTypeName = "ReadyConditions" | ||
|
||
RecipeAttr = "recipe" | ||
FieldAttr = "field" | ||
AssertionAttr = "assertion" | ||
TargetAttr = "target_value" | ||
IntervalAttr = "interval" | ||
TimeoutAttr = "timeout" | ||
|
||
defaultInterval = 1 * time.Second | ||
defaultTimeout = 15 * time.Minute //TODO we could move these two to the service helpers method | ||
) | ||
|
||
func NewReadyConditionsType() *kurtosis_type_constructor.KurtosisTypeConstructor { | ||
return &kurtosis_type_constructor.KurtosisTypeConstructor{ | ||
KurtosisBaseBuiltin: &kurtosis_starlark_framework.KurtosisBaseBuiltin{ | ||
Name: ReadyConditionsTypeName, | ||
Arguments: []*builtin_argument.BuiltinArgument{ | ||
{ | ||
Name: RecipeAttr, | ||
IsOptional: false, | ||
ZeroValueProvider: builtin_argument.ZeroValueProvider[starlark.Value], | ||
Validator: validateRecipe, | ||
}, | ||
{ | ||
Name: FieldAttr, | ||
IsOptional: false, | ||
ZeroValueProvider: builtin_argument.ZeroValueProvider[starlark.String], | ||
Validator: func(value starlark.Value) *startosis_errors.InterpretationError { | ||
return builtin_argument.NonEmptyString(value, FieldAttr) | ||
}, | ||
}, | ||
{ | ||
Name: AssertionAttr, | ||
IsOptional: false, | ||
ZeroValueProvider: builtin_argument.ZeroValueProvider[starlark.String], | ||
Validator: assert.ValidateAssertionToken, | ||
}, | ||
{ | ||
Name: TargetAttr, | ||
IsOptional: false, | ||
ZeroValueProvider: builtin_argument.ZeroValueProvider[starlark.Comparable], | ||
Validator: func(value starlark.Value) *startosis_errors.InterpretationError { | ||
return builtin_argument.NonEmptyString(value, FieldAttr) | ||
}, | ||
}, | ||
{ | ||
Name: IntervalAttr, | ||
IsOptional: true, | ||
ZeroValueProvider: builtin_argument.ZeroValueProvider[starlark.String], | ||
Validator: func(value starlark.Value) *startosis_errors.InterpretationError { | ||
return builtin_argument.Duration(value, IntervalAttr) | ||
}, | ||
}, | ||
{ | ||
Name: TimeoutAttr, | ||
IsOptional: true, | ||
ZeroValueProvider: builtin_argument.ZeroValueProvider[starlark.String], | ||
Validator: func(value starlark.Value) *startosis_errors.InterpretationError { | ||
return builtin_argument.Duration(value, TimeoutAttr) | ||
}, | ||
}, | ||
}, | ||
}, | ||
Instantiate: instantiateReadyConditions, | ||
} | ||
} | ||
|
||
func instantiateReadyConditions(arguments *builtin_argument.ArgumentValuesSet) (kurtosis_type_constructor.KurtosisValueType, *startosis_errors.InterpretationError) { | ||
kurtosisValueType, err := kurtosis_type_constructor.CreateKurtosisStarlarkTypeDefault(ReadyConditionsTypeName, arguments) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &ReadyConditions{ | ||
KurtosisValueTypeDefault: kurtosisValueType, | ||
}, nil | ||
} | ||
|
||
// ReadyConditions is a starlark.Value that holds all the information needed for ensuring service readiness | ||
type ReadyConditions struct { | ||
*kurtosis_type_constructor.KurtosisValueTypeDefault | ||
} | ||
|
||
func (readyConditions *ReadyConditions) GetRecipe() (recipe.Recipe, *startosis_errors.InterpretationError) { | ||
var genericRecipe recipe.Recipe | ||
|
||
httpRecipe, found, interpretationErr := kurtosis_type_constructor.ExtractAttrValue[*recipe.HttpRequestRecipe](readyConditions.KurtosisValueTypeDefault, RecipeAttr) | ||
genericRecipe = httpRecipe | ||
if !found { | ||
return nil, startosis_errors.NewInterpretationError("Required attribute '%s' could not be found on type '%s'", | ||
RecipeAttr, ReadyConditionsTypeName) | ||
} | ||
//TODO we should rework the recipe types to inherit a single common type, this will avoid the double parsing here. | ||
if interpretationErr != nil { | ||
execRecipe, _, interpretationErr := kurtosis_type_constructor.ExtractAttrValue[*recipe.ExecRecipe](readyConditions.KurtosisValueTypeDefault, RecipeAttr) | ||
if interpretationErr != nil { | ||
return nil, interpretationErr | ||
} | ||
genericRecipe = execRecipe | ||
} | ||
|
||
return genericRecipe, nil | ||
} | ||
|
||
func (readyConditions *ReadyConditions) GetField() (string, *startosis_errors.InterpretationError) { | ||
field, found, interpretationErr := kurtosis_type_constructor.ExtractAttrValue[starlark.String](readyConditions.KurtosisValueTypeDefault, FieldAttr) | ||
if interpretationErr != nil { | ||
return "", interpretationErr | ||
} | ||
if !found { | ||
return "", startosis_errors.NewInterpretationError("Required attribute '%s' could not be found on type '%s'", | ||
FieldAttr, ReadyConditionsTypeName) | ||
} | ||
fieldStr := field.GoString() | ||
|
||
return fieldStr, nil | ||
} | ||
|
||
func (readyConditions *ReadyConditions) GetAssertion() (string, *startosis_errors.InterpretationError) { | ||
assertion, found, interpretationErr := kurtosis_type_constructor.ExtractAttrValue[starlark.String](readyConditions.KurtosisValueTypeDefault, AssertionAttr) | ||
if interpretationErr != nil { | ||
return "", interpretationErr | ||
} | ||
if !found { | ||
return "", startosis_errors.NewInterpretationError("Required attribute '%s' could not be found on type '%s'", | ||
AssertionAttr, ReadyConditionsTypeName) | ||
} | ||
assertionStr := assertion.GoString() | ||
|
||
return assertionStr, nil | ||
} | ||
|
||
func (readyConditions *ReadyConditions) GetTarget() (starlark.Comparable, *startosis_errors.InterpretationError) { | ||
target, found, interpretationErr := kurtosis_type_constructor.ExtractAttrValue[starlark.Comparable](readyConditions.KurtosisValueTypeDefault, TargetAttr) | ||
if interpretationErr != nil { | ||
return nil, interpretationErr | ||
} | ||
if !found { | ||
return nil, startosis_errors.NewInterpretationError("Required attribute '%s' could not be found on type '%s'", | ||
TargetAttr, ReadyConditionsTypeName) | ||
} | ||
|
||
return target, nil | ||
} | ||
|
||
func (readyConditions *ReadyConditions) GetInterval() (time.Duration, *startosis_errors.InterpretationError) { | ||
interval := defaultInterval | ||
|
||
intervalStr, found, interpretationErr := kurtosis_type_constructor.ExtractAttrValue[starlark.String](readyConditions.KurtosisValueTypeDefault, IntervalAttr) | ||
if interpretationErr != nil { | ||
return interval, interpretationErr | ||
} | ||
if found { | ||
parsedInterval, parseErr := time.ParseDuration(intervalStr.GoString()) | ||
if parseErr != nil { | ||
return interval, startosis_errors.WrapWithInterpretationError(parseErr, "An error occurred when parsing interval '%v'", intervalStr.GoString()) | ||
} | ||
interval = parsedInterval | ||
} | ||
|
||
return interval, nil | ||
} | ||
|
||
func (readyConditions *ReadyConditions) GetTimeout() (time.Duration, *startosis_errors.InterpretationError) { | ||
timeout := defaultTimeout | ||
|
||
timeoutStr, found, interpretationErr := kurtosis_type_constructor.ExtractAttrValue[starlark.String](readyConditions.KurtosisValueTypeDefault, TimeoutAttr) | ||
if interpretationErr != nil { | ||
return timeout, interpretationErr | ||
} | ||
if found { | ||
parsedTimeout, parseErr := time.ParseDuration(timeoutStr.GoString()) | ||
if parseErr != nil { | ||
return timeout, startosis_errors.WrapWithInterpretationError(parseErr, "An error occurred when parsing timeout '%v'", timeoutStr.GoString()) | ||
} | ||
timeout = parsedTimeout | ||
} | ||
|
||
return timeout, nil | ||
} | ||
|
||
func validateRecipe(value starlark.Value) *startosis_errors.InterpretationError { | ||
_, ok := value.(*recipe.HttpRequestRecipe) | ||
if !ok { | ||
//TODO we should rework the recipe types to inherit a single common type, this will avoid the double parsing here. | ||
_, ok := value.(*recipe.ExecRecipe) | ||
if !ok { | ||
return startosis_errors.NewInterpretationError("The '%s' attribute is not a Recipe (was '%s').", RecipeAttr, reflect.TypeOf(value)) | ||
} | ||
} | ||
return nil | ||
} |
Oops, something went wrong.