Make NewDeployment specialization timeout configurable (#1260)

* Add specializationtimeout flag to function create/update
* Set specialization timeout of 120 seconds if not present
* Add default newdeploy timeout for rest of the test cases
* Comment out specialization timeout in validations for compatibility
* Add warning if specializationtimeout is lower than default value
This commit is contained in:
Suraj Banakar
2019-08-16 23:15:17 +08:00
committed by Ta-Ching Chen
parent 88b5343775
commit 80910562b3
7 changed files with 177 additions and 68 deletions
+5 -4
View File
@@ -167,10 +167,11 @@ type (
and resources allocated to the function pod.
*/
ExecutionStrategy struct {
ExecutorType ExecutorType
MinScale int
MaxScale int
TargetCPUPercent int
ExecutorType ExecutorType
MinScale int
MaxScale int
TargetCPUPercent int
SpecializationTimeout int
}
FunctionReferenceType string
+5
View File
@@ -346,6 +346,11 @@ func (es ExecutionStrategy) Validate() error {
if es.TargetCPUPercent <= 0 || es.TargetCPUPercent > 100 {
result = multierror.Append(result, MakeValidationErr(ErrorInvalidValue, "ExecutionStrategy.TargetCPUPercent", es.TargetCPUPercent, "TargetCPUPercent must be a value between 1 - 100"))
}
// TODO Add validation warning
//if es.SpecializationTimeout < 120 {
// result = multierror.Append(result, MakeValidationErr(ErrorInvalidValue, "ExecutionStrategy.SpecializationTimeout", es.SpecializationTimeout, "SpecializationTimeout must be a value equal to or greater than 120"))
//}
}
return result.ErrorOrNil()
+17 -7
View File
@@ -38,14 +38,16 @@ import (
)
const (
DeploymentKind = "Deployment"
DeploymentVersion = "extensions/v1beta1"
DeploymentKind = "Deployment"
DeploymentVersion = "extensions/v1beta1"
DEFAULT_SPECIALIZATION_TIMEOUT = 120
)
func (deploy *NewDeploy) createOrGetDeployment(fn *fv1.Function, env *fv1.Environment,
deployName string, deployLabels map[string]string, deployNamespace string, firstcreate bool) (*v1beta1.Deployment, error) {
minScale := int32(fn.Spec.InvokeStrategy.ExecutionStrategy.MinScale)
specializationTimeout := int(fn.Spec.InvokeStrategy.ExecutionStrategy.SpecializationTimeout)
// If it's not the first time creation and minscale is 0 means that all pods for function were recycled,
// in such cases we need set minscale to 1 for router to serve requests.
@@ -65,7 +67,7 @@ func (deploy *NewDeploy) createOrGetDeployment(fn *fv1.Function, env *fv1.Enviro
}
if existingDepl.Status.AvailableReplicas < minScale {
existingDepl, err = deploy.waitForDeploy(existingDepl, minScale)
existingDepl, err = deploy.waitForDeploy(existingDepl, minScale, specializationTimeout)
}
}
return existingDepl, err
@@ -93,7 +95,7 @@ func (deploy *NewDeploy) createOrGetDeployment(fn *fv1.Function, env *fv1.Enviro
}
if waitForDeploy {
depl, err = deploy.waitForDeploy(depl, minScale)
depl, err = deploy.waitForDeploy(depl, minScale, specializationTimeout)
}
return depl, err
@@ -414,8 +416,13 @@ func (deploy *NewDeploy) deleteSvc(ns string, name string) error {
return nil
}
func (deploy *NewDeploy) waitForDeploy(depl *v1beta1.Deployment, replicas int32) (*v1beta1.Deployment, error) {
for i := 0; i < 120; i++ {
func (deploy *NewDeploy) waitForDeploy(depl *v1beta1.Deployment, replicas int32, specializationTimeout int) (*v1beta1.Deployment, error) {
// if no specializationTimeout is set, use default value
if specializationTimeout < DEFAULT_SPECIALIZATION_TIMEOUT {
specializationTimeout = DEFAULT_SPECIALIZATION_TIMEOUT
}
for i := 0; i < specializationTimeout; i++ {
latestDepl, err := deploy.kubernetesClient.ExtensionsV1beta1().Deployments(depl.ObjectMeta.Namespace).Get(depl.Name, metav1.GetOptions{})
if err != nil {
return nil, err
@@ -428,7 +435,10 @@ func (deploy *NewDeploy) waitForDeploy(depl *v1beta1.Deployment, replicas int32)
}
time.Sleep(time.Second)
}
return nil, errors.New("failed to create deployment within timeout window")
// this error appears in the executor pod logs
timeoutError := fmt.Errorf("failed to create deployment within the timeout window of %d seconds", specializationTimeout)
return nil, timeoutError
}
// cleanupNewdeploy cleans all kubernetes objects related to function
+36 -7
View File
@@ -29,7 +29,7 @@ import (
"time"
"github.com/fission/fission/pkg/types"
"github.com/satori/go.uuid"
uuid "github.com/satori/go.uuid"
"github.com/urfave/cli"
apiv1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
@@ -43,8 +43,9 @@ import (
)
const (
DEFAULT_MIN_SCALE = 1
DEFAULT_TARGET_CPU_PERCENTAGE = 80
DEFAULT_MIN_SCALE = 1
DEFAULT_TARGET_CPU_PERCENTAGE = 80
DEFAULT_SPECIALIZATION_TIMEOUT = 120
)
func printPodLogs(c *cli.Context) error {
@@ -101,6 +102,10 @@ func getInvokeStrategy(c *cli.Context, existingInvokeStrategy *fv1.InvokeStrateg
fnExecutor = newFnExecutor
}
if c.IsSet("specializationtimeout") && fnExecutor != types.ExecutorTypeNewdeploy {
return nil, errors.New("specializationtimeout flag is only applicable for newdeploy type of executor")
}
if fnExecutor == types.ExecutorTypePoolmgr {
if c.IsSet("targetcpu") || c.IsSet("minscale") || c.IsSet("maxscale") {
log.Fatal("To set target CPU or min/max scale for function, please specify \"--executortype newdeploy\"")
@@ -120,11 +125,13 @@ func getInvokeStrategy(c *cli.Context, existingInvokeStrategy *fv1.InvokeStrateg
targetCPU := DEFAULT_TARGET_CPU_PERCENTAGE
minScale := DEFAULT_MIN_SCALE
maxScale := minScale
specializationTimeout := DEFAULT_SPECIALIZATION_TIMEOUT
if existingInvokeStrategy != nil && existingInvokeStrategy.ExecutionStrategy.ExecutorType == types.ExecutorTypeNewdeploy {
minScale = existingInvokeStrategy.ExecutionStrategy.MinScale
maxScale = existingInvokeStrategy.ExecutionStrategy.MaxScale
targetCPU = existingInvokeStrategy.ExecutionStrategy.TargetCPUPercent
specializationTimeout = existingInvokeStrategy.ExecutionStrategy.SpecializationTimeout
}
if c.IsSet("targetcpu") {
@@ -142,6 +149,13 @@ func getInvokeStrategy(c *cli.Context, existingInvokeStrategy *fv1.InvokeStrateg
}
}
if c.IsSet("specializationtimeout") {
specializationTimeout = c.Int("specializationtimeout")
if specializationTimeout < DEFAULT_SPECIALIZATION_TIMEOUT {
return nil, errors.New("specializationtimeout must be greater than or equal to 120 seconds")
}
}
if minScale > maxScale {
return nil, fmt.Errorf("minscale provided: %v can not be greater than maxscale value %v", minScale, maxScale)
}
@@ -151,10 +165,11 @@ func getInvokeStrategy(c *cli.Context, existingInvokeStrategy *fv1.InvokeStrateg
strategy = &fv1.InvokeStrategy{
StrategyType: fv1.StrategyTypeExecution,
ExecutionStrategy: fv1.ExecutionStrategy{
ExecutorType: fnExecutor,
MinScale: minScale,
MaxScale: maxScale,
TargetCPUPercent: targetCPU,
ExecutorType: fnExecutor,
MinScale: minScale,
MaxScale: maxScale,
TargetCPUPercent: targetCPU,
SpecializationTimeout: specializationTimeout,
},
}
}
@@ -489,6 +504,7 @@ func fnUpdate(c *cli.Context) error {
secretName := c.String("secret")
cfgMapName := c.String("configmap")
specializationTimeout := c.Int("specializationtimeout")
if len(srcArchiveFiles) > 0 && len(deployArchiveFiles) > 0 {
log.Fatal("Need either of --src or --deploy and not both arguments.")
@@ -556,6 +572,19 @@ func fnUpdate(c *cli.Context) error {
log.Fatal(err)
}
function.Spec.InvokeStrategy = *strategy
if c.IsSet("specializationtimeout") {
if c.String("executortype") != types.ExecutorTypeNewdeploy {
log.Fatal("specializationtimeout flag is only applicable for newdeploy type of executor")
}
if specializationTimeout < DEFAULT_SPECIALIZATION_TIMEOUT {
log.Fatal("specializationtimeout must be greater than or equal to 120 seconds")
} else {
function.Spec.InvokeStrategy.ExecutionStrategy.SpecializationTimeout = specializationTimeout
}
}
function.Spec.Resources = getResourceReq(c, function.Spec.Resources)
pkg, err := client.PackageGet(&metav1.ObjectMeta{
+107 -48
View File
@@ -49,10 +49,11 @@ func TestGetInvokeStrategy(t *testing.T) {
expectedResult: &fv1.InvokeStrategy{
StrategyType: fv1.StrategyTypeExecution,
ExecutionStrategy: fv1.ExecutionStrategy{
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: DEFAULT_MIN_SCALE,
MaxScale: DEFAULT_MIN_SCALE,
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: DEFAULT_MIN_SCALE,
MaxScale: DEFAULT_MIN_SCALE,
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
},
},
expectError: false,
@@ -69,10 +70,11 @@ func TestGetInvokeStrategy(t *testing.T) {
expectedResult: &fv1.InvokeStrategy{
StrategyType: fv1.StrategyTypeExecution,
ExecutionStrategy: fv1.ExecutionStrategy{
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: DEFAULT_MIN_SCALE,
MaxScale: DEFAULT_MIN_SCALE,
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: DEFAULT_MIN_SCALE,
MaxScale: DEFAULT_MIN_SCALE,
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
},
},
expectError: false,
@@ -83,10 +85,11 @@ func TestGetInvokeStrategy(t *testing.T) {
existingInvokeStrategy: &fv1.InvokeStrategy{
StrategyType: fv1.StrategyTypeExecution,
ExecutionStrategy: fv1.ExecutionStrategy{
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: DEFAULT_MIN_SCALE,
MaxScale: DEFAULT_MIN_SCALE,
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: DEFAULT_MIN_SCALE,
MaxScale: DEFAULT_MIN_SCALE,
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
},
},
expectedResult: &fv1.InvokeStrategy{
@@ -108,10 +111,11 @@ func TestGetInvokeStrategy(t *testing.T) {
expectedResult: &fv1.InvokeStrategy{
StrategyType: fv1.StrategyTypeExecution,
ExecutionStrategy: fv1.ExecutionStrategy{
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: 2,
MaxScale: 3,
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: 2,
MaxScale: 3,
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
},
},
expectError: false,
@@ -147,10 +151,11 @@ func TestGetInvokeStrategy(t *testing.T) {
expectedResult: &fv1.InvokeStrategy{
StrategyType: fv1.StrategyTypeExecution,
ExecutionStrategy: fv1.ExecutionStrategy{
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: DEFAULT_MIN_SCALE,
MaxScale: 3,
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: DEFAULT_MIN_SCALE,
MaxScale: 3,
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
},
},
expectError: false,
@@ -174,19 +179,21 @@ func TestGetInvokeStrategy(t *testing.T) {
existingInvokeStrategy: &fv1.InvokeStrategy{
StrategyType: fv1.StrategyTypeExecution,
ExecutionStrategy: fv1.ExecutionStrategy{
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: 2,
MaxScale: 5,
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: 2,
MaxScale: 5,
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
},
},
expectedResult: &fv1.InvokeStrategy{
StrategyType: fv1.StrategyTypeExecution,
ExecutionStrategy: fv1.ExecutionStrategy{
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: 2,
MaxScale: 9,
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: 2,
MaxScale: 9,
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
},
},
expectError: false,
@@ -199,19 +206,21 @@ func TestGetInvokeStrategy(t *testing.T) {
existingInvokeStrategy: &fv1.InvokeStrategy{
StrategyType: fv1.StrategyTypeExecution,
ExecutionStrategy: fv1.ExecutionStrategy{
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: 2,
MaxScale: 5,
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: 2,
MaxScale: 5,
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
},
},
expectedResult: &fv1.InvokeStrategy{
StrategyType: fv1.StrategyTypeExecution,
ExecutionStrategy: fv1.ExecutionStrategy{
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: 2,
MaxScale: 5,
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: 2,
MaxScale: 5,
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
},
},
expectError: false,
@@ -226,10 +235,11 @@ func TestGetInvokeStrategy(t *testing.T) {
expectedResult: &fv1.InvokeStrategy{
StrategyType: fv1.StrategyTypeExecution,
ExecutionStrategy: fv1.ExecutionStrategy{
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: DEFAULT_MIN_SCALE,
MaxScale: DEFAULT_MIN_SCALE,
TargetCPUPercent: 50,
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: DEFAULT_MIN_SCALE,
MaxScale: DEFAULT_MIN_SCALE,
TargetCPUPercent: 50,
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
},
},
expectError: false,
@@ -243,23 +253,72 @@ func TestGetInvokeStrategy(t *testing.T) {
existingInvokeStrategy: &fv1.InvokeStrategy{
StrategyType: fv1.StrategyTypeExecution,
ExecutionStrategy: fv1.ExecutionStrategy{
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: 2,
MaxScale: 5,
TargetCPUPercent: 88,
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: 2,
MaxScale: 5,
TargetCPUPercent: 88,
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
},
},
expectedResult: &fv1.InvokeStrategy{
StrategyType: fv1.StrategyTypeExecution,
ExecutionStrategy: fv1.ExecutionStrategy{
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: 2,
MaxScale: 5,
TargetCPUPercent: 20,
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: 2,
MaxScale: 5,
TargetCPUPercent: 20,
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
},
},
expectError: false,
},
{
// case: change specializationtimeout
testArgs: map[string]string{
"executortype": fv1.ExecutorTypeNewdeploy,
"specializationtimeout": "200",
},
existingInvokeStrategy: &fv1.InvokeStrategy{
StrategyType: fv1.StrategyTypeExecution,
ExecutionStrategy: fv1.ExecutionStrategy{
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: 2,
MaxScale: 5,
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
},
},
expectedResult: &fv1.InvokeStrategy{
StrategyType: fv1.StrategyTypeExecution,
ExecutionStrategy: fv1.ExecutionStrategy{
ExecutorType: fv1.ExecutorTypeNewdeploy,
MinScale: 2,
MaxScale: 5,
SpecializationTimeout: 200,
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
},
},
expectError: false,
},
{
// case: specializationtimeout should not work for poolmgr
testArgs: map[string]string{
"executortype": fv1.ExecutorTypePoolmgr,
"specializationtimeout": "10",
},
existingInvokeStrategy: nil,
expectedResult: nil,
expectError: true,
},
{
// case: specializationtimeout should not be less than 120
testArgs: map[string]string{
"executortype": fv1.ExecutorTypeNewdeploy,
"specializationtimeout": "90",
},
existingInvokeStrategy: nil,
expectedResult: nil,
expectError: true,
},
}
for i, c := range cases {
+3 -2
View File
@@ -94,6 +94,7 @@ func NewCliApp() *cli.App {
minScale := cli.IntFlag{Name: "minscale", Usage: "Minimum number of pods (Uses resource inputs to configure HPA)"}
maxScale := cli.IntFlag{Name: "maxscale", Usage: "Maximum number of pods (Uses resource inputs to configure HPA)"}
targetcpu := cli.IntFlag{Name: "targetcpu", Usage: "Target average CPU usage percentage across pods for scaling"}
specializationTimeoutFlag := cli.IntFlag{Name: "specializationtimeout, st", Usage: "Timeout for newdeploy to wait for function pod creation"}
// functions
fnNameFlag := cli.StringFlag{Name: "name", Usage: "function name"}
@@ -119,10 +120,10 @@ func NewCliApp() *cli.App {
fnTimeoutFlag := cli.DurationFlag{Name: "timeout, t", Value: 30 * time.Second, Usage: "The length of time to wait for the response. If set to zero or negative number, no timeout is set."}
fnSubcommands := []cli.Command{
{Name: "create", Usage: "Create new function (and optionally, an HTTP route to it)", Flags: []cli.Flag{fnNameFlag, fnNamespaceFlag, fnEnvNameFlag, envNamespaceFlag, specSaveFlag, fnCodeFlag, fnSrcArchiveFlag, fnDeployArchiveFlag, fnEntryPointFlag, fnBuildCmdFlag, fnPkgNameFlag, htUrlFlag, htMethodFlag, minCpu, maxCpu, minMem, maxMem, minScale, maxScale, fnExecutorTypeFlag, targetcpu, fnCfgMapFlag, fnSecretFlag}, Action: fnCreate},
{Name: "create", Usage: "Create new function (and optionally, an HTTP route to it)", Flags: []cli.Flag{fnNameFlag, fnNamespaceFlag, fnEnvNameFlag, envNamespaceFlag, specSaveFlag, fnCodeFlag, fnSrcArchiveFlag, fnDeployArchiveFlag, fnEntryPointFlag, fnBuildCmdFlag, fnPkgNameFlag, htUrlFlag, htMethodFlag, minCpu, maxCpu, minMem, maxMem, minScale, maxScale, fnExecutorTypeFlag, targetcpu, fnCfgMapFlag, fnSecretFlag, specializationTimeoutFlag}, Action: fnCreate},
{Name: "get", Usage: "Get function source code", Flags: []cli.Flag{fnNameFlag, fnNamespaceFlag}, Action: fnGet},
{Name: "getmeta", Usage: "Get function metadata", Flags: []cli.Flag{fnNameFlag, fnNamespaceFlag}, Action: fnGetMeta},
{Name: "update", Usage: "Update function source code", Flags: []cli.Flag{fnNameFlag, fnNamespaceFlag, fnEnvNameFlag, envNamespaceFlag, fnCodeFlag, fnSrcArchiveFlag, fnDeployArchiveFlag, fnEntryPointFlag, fnPkgNameFlag, pkgNamespaceFlag, fnBuildCmdFlag, fnForceFlag, minCpu, maxCpu, minMem, maxMem, minScale, maxScale, fnExecutorTypeFlag, targetcpu}, Action: fnUpdate},
{Name: "update", Usage: "Update function source code", Flags: []cli.Flag{fnNameFlag, fnNamespaceFlag, fnEnvNameFlag, envNamespaceFlag, fnCodeFlag, fnSrcArchiveFlag, fnDeployArchiveFlag, fnEntryPointFlag, fnPkgNameFlag, pkgNamespaceFlag, fnBuildCmdFlag, fnForceFlag, minCpu, maxCpu, minMem, maxMem, minScale, maxScale, fnExecutorTypeFlag, targetcpu, specializationTimeoutFlag}, Action: fnUpdate},
{Name: "delete", Usage: "Delete function", Flags: []cli.Flag{fnNameFlag, fnNamespaceFlag}, Action: fnDelete},
// TODO : for fnList, i feel like it's nice to allow --fns all, to list functions across all namespaces for cluster admins, although, this is against ns isolation.
// so, in the future, if we end up using kubeconfig in fission cli and enforcing rolebindings to be created for users by admins etc, we can add this option at the time.
+4
View File
@@ -437,6 +437,10 @@ func (fr *FissionResources) validate(c *cli.Context) error {
if _, ok := environments[fmt.Sprintf("%s:%s", f.Spec.Environment.Name, f.Spec.Environment.Namespace)]; !ok {
log.Warn(fmt.Sprintf("Environment %s is referenced in function %s but not declared in specs", f.Spec.Environment.Name, f.Metadata.Name))
}
strategy := f.Spec.InvokeStrategy.ExecutionStrategy
if strategy.ExecutorType == fv1.ExecutorTypeNewdeploy && strategy.SpecializationTimeout < DEFAULT_SPECIALIZATION_TIMEOUT {
log.Warn(fmt.Sprintf("SpecializationTimeout in function spec.InvokeStrategy.ExecutionStrategy should be a value equal to or greater than %v", DEFAULT_SPECIALIZATION_TIMEOUT))
}
}
// (ErrorOrNil returns nil if there were no errors appended.)