mirror of
https://github.com/openfaas/faasd.git
synced 2025-06-09 08:26:47 +00:00
Annotation support
Provide support for annotations in faasd with namespaced container labels. Unit tested and confirmed with end to end test via faasd deployed to multipass VM Signed-off-by: Alex Tomic <atomic777@gmail.com>
This commit is contained in:
parent
716ef6f51c
commit
ac1cc16f0c
@ -26,6 +26,8 @@ import (
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
const annotationLabelPrefix = "com.openfaas.annotations."
|
||||
|
||||
func MakeDeployHandler(client *containerd.Client, cni gocni.CNI, secretMountPath string, alwaysPull bool) func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
@ -101,9 +103,9 @@ func deploy(ctx context.Context, req types.FunctionDeployment, client *container
|
||||
|
||||
name := req.Service
|
||||
|
||||
labels := map[string]string{}
|
||||
if req.Labels != nil {
|
||||
labels = *req.Labels
|
||||
labels, err := buildLabels(&req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to apply labels to conatiner: %s, error: %s", name, err)
|
||||
}
|
||||
|
||||
var memory *specs.LinuxMemory
|
||||
@ -140,6 +142,30 @@ func deploy(ctx context.Context, req types.FunctionDeployment, client *container
|
||||
|
||||
}
|
||||
|
||||
func buildLabels(request *types.FunctionDeployment) (map[string]string, error) {
|
||||
// Adapted from faas-swarm/handlers/deploy.go:buildLabels
|
||||
labels := map[string]string{}
|
||||
|
||||
if request.Labels != nil {
|
||||
for k, v := range *request.Labels {
|
||||
labels[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if request.Annotations != nil {
|
||||
for k, v := range *request.Annotations {
|
||||
key := fmt.Sprintf("%s%s", annotationLabelPrefix, k)
|
||||
if _, ok := labels[key]; !ok {
|
||||
labels[key] = v
|
||||
} else {
|
||||
return nil, errors.New(fmt.Sprintf("Key %s cannot be used as a label due to a conflict with annotation prefix %s", k, annotationLabelPrefix))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
func createTask(ctx context.Context, client *containerd.Client, container containerd.Container, cni gocni.CNI) error {
|
||||
|
||||
name := container.ID()
|
||||
|
63
pkg/provider/handlers/deploy_test.go
Normal file
63
pkg/provider/handlers/deploy_test.go
Normal file
@ -0,0 +1,63 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/openfaas/faas-provider/types"
|
||||
)
|
||||
|
||||
func Test_BuildLabels_WithAnnotations(t *testing.T) {
|
||||
// Test each combination of nil/non-nil annotation + label
|
||||
tables := []struct {
|
||||
label map[string]string
|
||||
annotation map[string]string
|
||||
result map[string]string
|
||||
}{
|
||||
{nil, nil, map[string]string{}},
|
||||
{map[string]string{"L1": "V1"}, nil, map[string]string{"L1": "V1"}},
|
||||
{nil, map[string]string{"A1": "V2"}, map[string]string{fmt.Sprintf("%sA1", annotationLabelPrefix): "V2"}},
|
||||
{
|
||||
map[string]string{"L1": "V1"}, map[string]string{"A1": "V2"},
|
||||
map[string]string{"L1": "V1", fmt.Sprintf("%sA1", annotationLabelPrefix): "V2"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, pair := range tables {
|
||||
|
||||
request := &types.FunctionDeployment{
|
||||
Labels: &pair.label,
|
||||
Annotations: &pair.annotation,
|
||||
}
|
||||
|
||||
val, err := buildLabels(request)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("want: no error got: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(val, pair.result) {
|
||||
t.Errorf("Got: %s, expected %s", val, pair.result)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func Test_BuildLabels_WithAnnotationCollision(t *testing.T) {
|
||||
request := &types.FunctionDeployment{
|
||||
Labels: &map[string]string{
|
||||
"function_name": "echo",
|
||||
fmt.Sprintf("%scurrent-time", annotationLabelPrefix): "Wed 25 Jul 06:41:43 BST 2018",
|
||||
},
|
||||
Annotations: &map[string]string{"current-time": "Wed 25 Jul 06:41:43 BST 2018"},
|
||||
}
|
||||
|
||||
val, err := buildLabels(request)
|
||||
|
||||
fmt.Printf("%s, %s\n", val, err)
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error, got %d values", len(val))
|
||||
}
|
||||
|
||||
}
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
@ -20,6 +21,7 @@ type Function struct {
|
||||
replicas int
|
||||
IP string
|
||||
labels map[string]string
|
||||
annotations map[string]string
|
||||
}
|
||||
|
||||
// ListFunctions returns a map of all functions with running tasks on namespace
|
||||
@ -48,16 +50,20 @@ func GetFunction(client *containerd.Client, name string) (Function, error) {
|
||||
image, _ := c.Image(ctx)
|
||||
|
||||
containerName := c.ID()
|
||||
labels, labelErr := c.Labels(ctx)
|
||||
allLabels, labelErr := c.Labels(ctx)
|
||||
|
||||
if labelErr != nil {
|
||||
log.Printf("cannot list container %s labels: %s", containerName, labelErr.Error())
|
||||
}
|
||||
|
||||
labels, annotations := buildLabelsAndAnnotations(allLabels)
|
||||
|
||||
f := Function{
|
||||
name: containerName,
|
||||
namespace: faasd.FunctionNamespace,
|
||||
image: image.Name(),
|
||||
labels: labels,
|
||||
annotations: annotations,
|
||||
}
|
||||
|
||||
replicas := 0
|
||||
@ -90,3 +96,23 @@ func GetFunction(client *containerd.Client, name string) (Function, error) {
|
||||
|
||||
return Function{}, fmt.Errorf("unable to find function: %s, error %s", name, err)
|
||||
}
|
||||
|
||||
func buildLabelsAndAnnotations(ctrLabels map[string]string) (labels map[string]string, annotations map[string]string) {
|
||||
for k, v := range ctrLabels {
|
||||
if strings.HasPrefix(k, annotationLabelPrefix) {
|
||||
if annotations == nil {
|
||||
annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
annotations[strings.TrimPrefix(k, annotationLabelPrefix)] = v
|
||||
} else {
|
||||
if labels == nil {
|
||||
labels = make(map[string]string)
|
||||
}
|
||||
|
||||
labels[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return labels, annotations
|
||||
}
|
||||
|
29
pkg/provider/handlers/functions_test.go
Normal file
29
pkg/provider/handlers/functions_test.go
Normal file
@ -0,0 +1,29 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_BuildLabelsAndAnnotationsFromServiceSpec_Annotations(t *testing.T) {
|
||||
container := map[string]string{
|
||||
"qwer": "ty",
|
||||
"dvor": "ak",
|
||||
fmt.Sprintf("%scurrent-time", annotationLabelPrefix): "5 Nov 20:10:20 PST 1955",
|
||||
fmt.Sprintf("%sfuture-time", annotationLabelPrefix): "21 Oct 20:10:20 PST 2015",
|
||||
}
|
||||
|
||||
labels, annotation := buildLabelsAndAnnotations(container)
|
||||
|
||||
if len(labels) != 2 {
|
||||
t.Errorf("want: %d labels got: %d", 2, len(labels))
|
||||
}
|
||||
|
||||
if len(annotation) != 2 {
|
||||
t.Errorf("want: %d annotation got: %d", 1, len(annotation))
|
||||
}
|
||||
|
||||
if _, ok := annotation["current-time"]; !ok {
|
||||
t.Errorf("want: '%s' entry in annotation map got: key not found", "current-time")
|
||||
}
|
||||
}
|
@ -21,12 +21,14 @@ func MakeReadHandler(client *containerd.Client) func(w http.ResponseWriter, r *h
|
||||
return
|
||||
}
|
||||
for _, function := range funcs {
|
||||
|
||||
res = append(res, types.FunctionStatus{
|
||||
Name: function.name,
|
||||
Image: function.image,
|
||||
Replicas: uint64(function.replicas),
|
||||
Namespace: function.namespace,
|
||||
Labels: &function.labels,
|
||||
Annotations: &function.annotations,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ func MakeReplicaReaderHandler(client *containerd.Client) func(w http.ResponseWri
|
||||
Replicas: uint64(f.replicas),
|
||||
Namespace: f.namespace,
|
||||
Labels: &f.labels,
|
||||
Annotations: &f.annotations,
|
||||
}
|
||||
|
||||
functionBytes, _ := json.Marshal(found)
|
||||
|
Loading…
x
Reference in New Issue
Block a user