KOP Environment Manager - Function Workflow Handler - Rafay Product Documentation

Functions

Overview

Functions makes it easier to run functions in popular programming languages like Go, and Python directly within workflows. Users no longer need to package these functions in containers; the framework takes care of all the necessary packaging and dependencies, simplifying the process.

Users can leverage this feature for various tasks, such as executing actions on infrastructure, integrating with services like Jira and ServiceNow, or sending notifications. By allowing functions to run without needing containers, the feature reduces complexity and makes it easier to implement actions quickly.

The framework is flexible and can be expanded to support more programming languages in the future. It also includes helpful tools for error handling and logging, making it easier to troubleshoot issues. Additionally, users can choose to run functions either synchronously or asynchronously, depending on their needs. Overall, this feature streamlines workflows and improves the user experience by making function execution straightforward and efficient.

Did you know?

Functions can be managed through various methods:

Functions via UI

To create a Functions Workflow Handler, perform the below steps:

Workflow Handler General Configuration page appears.

Function Configuration

Enable Skip Build

If the images are already available, enable Skip Build to bypass the image creation process and specify the image details.

Feature Description
Add Resource(s) Specify a required dependency by entering the relevant resource name
Label Add a label to the resource. Labels are key-value pairs used to specify identifying attributes of objects (e.g., key: demo, value: value1)
Node Selector Ensure the container runs on Kubernetes nodes with specific requirements, such as certain hardware or labels
Toleration Allow a pod to be scheduled on nodes with specific taints, ensuring placement based on attributes or restrictions (e.g., GPU toleration for nodes with a gpu taint)
Required Node Affinity Define strict scheduling constraints, ensuring pods are placed only on nodes that match specific label-based conditions. If no matching nodes are found, the pod remains unscheduled
Preferred Node Affinity Specify preferred scheduling rules, allowing pods to be placed on matching nodes when possible. If no preferred matches are found, the scheduler can still place the pod on other nodes
Required Pod Affinity Enforce strict rules for scheduling pods on nodes that already have specific pods based on labels. If no matching nodes are found, the pod remains unscheduled
Preferred Pod Affinity Suggest scheduling pods on nodes with specific pods when possible. If no preferred matches are found, the scheduler can still place the pod elsewhere
Required Pod Anti Affinity Prevent pods from being scheduled on nodes that already have specific pods based on labels. If no suitable nodes are found, the pod remains unscheduled
Preferred Pod Anti Affinity Suggest avoiding scheduling pods on nodes with specific pods when possible. If no alternative nodes are available, the scheduler may still place the pod on a matching node

Disable Skip Build

If "Skip Build" is not enabled, the user will provide the source code, which should be either in Python or Go. The framework will then use the provided source code to handle the build process, including packaging the necessary dependencies and preparing the function for execution. The system will automatically create an image or package the function based on the code provided, without requiring the user to manage this process manually.

Container Options (Optional)

Optionally, provide CPU and memory limits. This practice contributes to resource isolation, fair sharing, and overall predictability in resource usage. If not provided, default values will be used.

HPA Configuration (Optional)

To add a Resource Metric, provide the following in the Add Resource Metric panel:

Note: HPA requires the latest agent version to function. Existing environment templates without HPA configured will continue to work as before. No changes are applied unless HPA is explicitly enabled.

Configuration (Optional)

Polling Config

Config Contexts

Outputs

Click Save.

The list of created functions is shown below.

These Function Workflow Handlers can be configured in Resource Templates as terraform provider or in any of the hooks.

View Logs for Failed Workflow Handlers

Detailed logs are now available for Workflow Handlers of type 'Function' that fail during the build process. Clicking the red Failed icon in the 'Build Status' column opens a panel showing the failure reason and corresponding error logs.

This enhancement enables quicker identification and resolution of build issues.


Go Function Specification

Below is an example specification that defines a function resource named simple-func-go, which executes a 'Go' function within an environment management framework. It includes configuration settings for resource limits, polling behavior, and the function's execution logic, allowing it to process requests and handle various scenarios dynamically.

apiVersion: eaas.envmgmt.io/v1
kind: WorkflowHandler
metadata:
  name: simple-func-go
  project: defaultproject
spec:
  config:
    type: function
    timeoutSeconds: 300
    pollingConfig:
      repeat: "15s"
      until: "1h"
    function:
      cpuLimitMilli: "50"
      memoryLimitMi: "128"
      language: go
      languageVersion: "1.22"
      maxConcurrency: 10
      numReplicas: 1
      source: |
        package function

import (
          "context"

sdk "github.com/RafaySystems/function-templates/sdk/go"
        )

func Handle(ctx context.Context, logger sdk.Logger, req sdk.Request) (sdk.Response, error) {
          logger.Info("received request", "req", req)

counter := 0.0
          if prev, ok := req["previous"]; ok {
            logger.Info("previous request", "prev", prev)
            counter = prev.(map[string]any)["counter"].(float64)
          }

resp := make(sdk.Response)
          resp["output"] = "Hello World"
          resp["request"] = req

if err, ok := req["error"]; ok {
            errString, _ := err.(string)
            switch errString {
            case "execute_again":
              if counter > 1 {
                break
              }
              return nil, sdk.NewErrExecuteAgain(errString, map[string]any{
                "rkey":    "rvalue",
                "counter": counter + 1,
              })
            case "transient":
              return nil, sdk.NewErrTransient(errString)
            default:
              return nil, sdk.NewErrFailed(errString)
            }
          }

return sdk.Response(resp), nil
        }

Environment Template Integration

The below specification illustrates how the simple-func-go function is integrated into an environment template named demo-envtemp as hooks. These hooks are triggered upon environment initialization, allowing for dynamic execution of functions and enhancing automation capabilities within resource management.

apiVersion: eaas.envmgmt.io/v1
kind: EnvironmentTemplate
metadata:
  name: demo-envtemp
  project: defaultproject
spec:
  version: v1
  resources:
    - type: dynamic
      kind: resourcetemplate
      name: resource1
      resourceOptions:
        version: v1
  hooks:
    onInit:
      - name: func-go
        type: workflowHandler
        onFailure: unspecified
        workflowHandler:
          name: simple-func-go
      - name: func-py
        type: workflowHandler
        options: {}
        onFailure: unspecified
        workflowHandler:
          name: simple-func-py

Note: Functions can be attached as hooks in environment templates, resource templates, or as tasks in the Resource Template - Custom Provider.


Return Errors/Exceptions

Functions executed can return three (3) types of errors/exceptions:

Important The function can have only two terminal states: Success and Failed

Example 1 Below is an example showcasing how different return errors/exceptions can be handled within a Go function:

package function

import (
    "context"
    sdk "github.com/RafaySystems/function-templates/sdk/go"
)

func Handle(ctx context.Context, logger sdk.Logger, req sdk.Request) (sdk.Response, error) {
    logger.Info("Request received", "request", req)
    // Access inputs and metadata
    i1 := req["input1"]
    meta := req["metadata"]
    prev := req["previous"]

// Example logic for triggering errors
    if executeAgain {
        // If the function needs to be executed again before proceeding
        return nil, sdk.NewErrExecuteAgain("execute again", map[string]interface{}{"key": "value"})
    }

if transientError {
        // For retrying the function in case of a transient error (e.g., network issues)
        return nil, sdk.NewErrTransient("transient error")
    }

if failedError {
        // Mark the function as failed and transition to a terminal state
        return nil, sdk.NewErrFailed("failed")
    }

// On success, return the desired output
    return sdk.Response{"output1": "value1"}, nil
}

Example 2 Below is an example showcasing how different return errors/exceptions can be handled within a Python function:

from typing import *
from logging import Logger

def handle(logger: Logger,request: Dict[str, Any]) -> Dict[str, Any]:
    logger.info(f"inside function handler, request: {request}", extra={"request": request})
    # inputs can be accessed from (request)
    i1 = req["input1"]
    # metadata of the function can be accessed using "metadata" key
    meta = req["metadata"]
    # data passed using NewErrExecuteAgain can be accessed using "previous" key
    prev = req["previous"]

# logic
    # ...
    # logic

if executeAgain:
        # If the function has to be executed again
        # before proceeding to next activity.
        # For eg: to check the status of a third party integration.
        # Any data passed using this error
        # is available for the next execution of the function.
        raise sdk.ExecuteAgainException("Please wait for the ticket to be approved or declined", ticket_id=id, counter=counter+1)

if transientError:
        # If the function has to be retried.
        # For eg: due to intermittent errors like network errors.
        # Will be only retried for the num of times configured
        raise sdk.TransientException(f"ConnectionError: {str(e)}")

if failedError:
        # If the function has to marked as failure
        raise sdk.FailedException(f"FailedException: {str(e)}")

return {
       "output1": "value1"
    }