Skip to content
This repository has been archived by the owner on Dec 15, 2021. It is now read-only.

Commit

Permalink
Merge pull request #397 from phsiao/add_servicespec_to_functionspec
Browse files Browse the repository at this point in the history
Add ServiceSpec to FunctionSpec
  • Loading branch information
ngtuna authored Dec 27, 2017
2 parents 2d384fd + 443e202 commit fef344f
Show file tree
Hide file tree
Showing 11 changed files with 290 additions and 43 deletions.
16 changes: 15 additions & 1 deletion cmd/kubeless/function/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,18 @@ var deployCmd = &cobra.Command{
if err != nil {
logrus.Fatal(err)
}
headless, err := cmd.Flags().GetBool("headless")
if err != nil {
logrus.Fatal(err)
}

port, err := cmd.Flags().GetInt32("port")
if err != nil {
logrus.Fatal(err)
}
if port <= 0 || port > 65535 {
logrus.Fatalf("Invalid port number %d specified", port)
}

funcDeps := ""
if deps != "" {
Expand All @@ -140,7 +152,7 @@ var deployCmd = &cobra.Command{
defaultFunctionSpec.Metadata.Labels = map[string]string{
"created-by": "kubeless",
}
f, err := getFunctionDescription(cli, funcName, ns, handler, file, funcDeps, runtime, topic, schedule, runtimeImage, mem, timeout, triggerHTTP, envs, labels, defaultFunctionSpec)
f, err := getFunctionDescription(cli, funcName, ns, handler, file, funcDeps, runtime, topic, schedule, runtimeImage, mem, timeout, triggerHTTP, &headless, &port, envs, labels, defaultFunctionSpec)
if err != nil {
logrus.Fatal(err)
}
Expand Down Expand Up @@ -174,4 +186,6 @@ func init() {
deployCmd.Flags().Bool("trigger-http", false, "Deploy a http-based function to Kubeless")
deployCmd.Flags().StringP("runtime-image", "", "", "Custom runtime image")
deployCmd.Flags().StringP("timeout", "", "180", "Maximum timeout (in seconds) for the function to complete its execution")
deployCmd.Flags().Bool("headless", false, "Deploy http-based function without a single service IP and load balancing support from Kubernetes. See: https://kubernetes.io/docs/concepts/services-networking/service/#headless-services")
deployCmd.Flags().Int32("port", 8080, "Deploy http-based function with a custom port")
}
38 changes: 37 additions & 1 deletion cmd/kubeless/function/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/pkg/api/v1"
)
Expand Down Expand Up @@ -130,7 +131,7 @@ func getContentType(filename string, fbytes []byte) string {
return contentType
}

func getFunctionDescription(cli kubernetes.Interface, funcName, ns, handler, file, deps, runtime, topic, schedule, runtimeImage, mem, timeout string, triggerHTTP bool, envs, labels []string, defaultFunction spec.Function) (*spec.Function, error) {
func getFunctionDescription(cli kubernetes.Interface, funcName, ns, handler, file, deps, runtime, topic, schedule, runtimeImage, mem, timeout string, triggerHTTP bool, headlessFlag *bool, portFlag *int32, envs, labels []string, defaultFunction spec.Function) (*spec.Function, error) {

if handler == "" {
handler = defaultFunction.Spec.Handler
Expand Down Expand Up @@ -240,6 +241,40 @@ func getFunctionDescription(cli kubernetes.Interface, funcName, ns, handler, fil
runtimeImage = defaultFunction.Spec.Template.Spec.Containers[0].Image
}

selectorLabels := map[string]string{}
for k, v := range funcLabels {
selectorLabels[k] = v
}
selectorLabels["function"] = funcName

svcSpec := v1.ServiceSpec{
Ports: []v1.ServicePort{
{
Name: "function-port",
NodePort: 0,
Protocol: v1.ProtocolTCP,
},
},
Selector: selectorLabels,
Type: v1.ServiceTypeClusterIP,
}

if headlessFlag != nil {
if *headlessFlag == true {
svcSpec.ClusterIP = v1.ClusterIPNone
}
} else {
svcSpec.ClusterIP = defaultFunction.Spec.ServiceSpec.ClusterIP
}

if portFlag != nil {
svcSpec.Ports[0].Port = *portFlag
svcSpec.Ports[0].TargetPort = intstr.FromInt(int(*portFlag))
} else {
svcSpec.Ports[0].Port = defaultFunction.Spec.ServiceSpec.Ports[0].Port
svcSpec.Ports[0].TargetPort = defaultFunction.Spec.ServiceSpec.Ports[0].TargetPort
}

return &spec.Function{
TypeMeta: metav1.TypeMeta{
Kind: "Function",
Expand All @@ -261,6 +296,7 @@ func getFunctionDescription(cli kubernetes.Interface, funcName, ns, handler, fil
Topic: topic,
Schedule: schedule,
Timeout: timeout,
ServiceSpec: svcSpec,
Template: v1.PodTemplateSpec{
Spec: v1.PodSpec{
Containers: []v1.Container{
Expand Down
46 changes: 42 additions & 4 deletions cmd/kubeless/function/function_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/kubeless/kubeless/pkg/spec"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes/fake"
)

Expand Down Expand Up @@ -97,7 +98,9 @@ func TestGetFunctionDescription(t *testing.T) {
file.Close()
defer os.Remove(file.Name()) // clean up

result, err := getFunctionDescription(fake.NewSimpleClientset(), "test", "default", "file.handler", file.Name(), "dependencies", "runtime", "", "", "test-image", "128Mi", "10", true, []string{"TEST=1"}, []string{"test=1"}, spec.Function{})
inputHeadless := true
inputPort := int32(80)
result, err := getFunctionDescription(fake.NewSimpleClientset(), "test", "default", "file.handler", file.Name(), "dependencies", "runtime", "", "", "test-image", "128Mi", "10", true, &inputHeadless, &inputPort, []string{"TEST=1"}, []string{"test=1"}, spec.Function{})
if err != nil {
t.Error(err)
}
Expand Down Expand Up @@ -125,6 +128,23 @@ func TestGetFunctionDescription(t *testing.T) {
Topic: "",
Schedule: "",
Timeout: "10",
ServiceSpec: v1.ServiceSpec{
Ports: []v1.ServicePort{
{
Name: "function-port",
Port: int32(80),
TargetPort: intstr.FromInt(80),
NodePort: 0,
Protocol: v1.ProtocolTCP,
},
},
Selector: map[string]string{
"test": "1",
"function": "test",
},
Type: v1.ServiceTypeClusterIP,
ClusterIP: v1.ClusterIPNone,
},
Template: v1.PodTemplateSpec{
Spec: v1.PodSpec{
Containers: []v1.Container{
Expand Down Expand Up @@ -153,7 +173,7 @@ func TestGetFunctionDescription(t *testing.T) {
}

// It should take the default values
result2, err := getFunctionDescription(fake.NewSimpleClientset(), "test", "default", "", "", "", "", "", "", "", "", "", false, []string{}, []string{}, expectedFunction)
result2, err := getFunctionDescription(fake.NewSimpleClientset(), "test", "default", "", "", "", "", "", "", "", "", "", false, nil, nil, []string{}, []string{}, expectedFunction)
if err != nil {
t.Error(err)
}
Expand All @@ -172,7 +192,9 @@ func TestGetFunctionDescription(t *testing.T) {
}
file.Close()
defer os.Remove(file.Name()) // clean up
result3, err := getFunctionDescription(fake.NewSimpleClientset(), "test", "default", "file.handler2", file.Name(), "dependencies2", "runtime2", "test_topic", "", "test-image2", "256Mi", "20", false, []string{"TEST=2"}, []string{"test=2"}, expectedFunction)
input3Headless := false
input3Port := int32(8080)
result3, err := getFunctionDescription(fake.NewSimpleClientset(), "test", "default", "file.handler2", file.Name(), "dependencies2", "runtime2", "test_topic", "", "test-image2", "256Mi", "20", false, &input3Headless, &input3Port, []string{"TEST=2"}, []string{"test=2"}, expectedFunction)
if err != nil {
t.Error(err)
}
Expand Down Expand Up @@ -200,6 +222,22 @@ func TestGetFunctionDescription(t *testing.T) {
Topic: "test_topic",
Schedule: "",
Timeout: "20",
ServiceSpec: v1.ServiceSpec{
Ports: []v1.ServicePort{
{
Name: "function-port",
Port: int32(8080),
TargetPort: intstr.FromInt(8080),
NodePort: 0,
Protocol: v1.ProtocolTCP,
},
},
Selector: map[string]string{
"test": "2",
"function": "test",
},
Type: v1.ServiceTypeClusterIP,
},
Template: v1.PodTemplateSpec{
Spec: v1.PodSpec{
Containers: []v1.Container{
Expand Down Expand Up @@ -256,7 +294,7 @@ func TestGetFunctionDescription(t *testing.T) {
}
file.Close()
zipW.Close()
result4, err := getFunctionDescription(fake.NewSimpleClientset(), "test", "default", "file.handler", newfile.Name(), "dependencies", "runtime", "", "", "", "", "", false, []string{}, []string{}, expectedFunction)
result4, err := getFunctionDescription(fake.NewSimpleClientset(), "test", "default", "file.handler", newfile.Name(), "dependencies", "runtime", "", "", "", "", "", false, nil, nil, []string{}, []string{}, expectedFunction)
if err != nil {
t.Error(err)
}
Expand Down
26 changes: 25 additions & 1 deletion cmd/kubeless/function/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/robfig/cron"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)

var updateCmd = &cobra.Command{
Expand Down Expand Up @@ -121,14 +122,35 @@ var updateCmd = &cobra.Command{
}
funcDeps = string(bytes)
}
var headless *bool = nil
var port *int32 = nil
cmd.Flags().Visit(func(flag *pflag.Flag) {
switch flag.Name {
case "headless":
val, err := cmd.Flags().GetBool("headless")
headless = &val
if err != nil {
logrus.Fatal(err)
}
case "port":
val, err := cmd.Flags().GetInt32("port")
port = &val
if err != nil {
logrus.Fatal(err)
}
}
})

previousFunction, err := utils.GetFunction(funcName, ns)
if err != nil {
logrus.Fatal(err)
}

if port != nil && (*port <= 0 || *port > 65535) {
logrus.Fatalf("Invalid port number %d specified", *port)
}
cli := utils.GetClientOutOfCluster()
f, err := getFunctionDescription(cli, funcName, ns, handler, file, funcDeps, runtime, topic, schedule, runtimeImage, mem, timeout, triggerHTTP, envs, labels, previousFunction)
f, err := getFunctionDescription(cli, funcName, ns, handler, file, funcDeps, runtime, topic, schedule, runtimeImage, mem, timeout, triggerHTTP, headless, port, envs, labels, previousFunction)
if err != nil {
logrus.Fatal(err)
}
Expand Down Expand Up @@ -161,4 +183,6 @@ func init() {
updateCmd.Flags().Bool("trigger-http", false, "Deploy a http-based function to Kubeless")
updateCmd.Flags().StringP("runtime-image", "", "", "Custom runtime image")
updateCmd.Flags().StringP("timeout", "", "180", "Maximum timeout (in seconds) for the function to complete its execution")
updateCmd.Flags().Bool("headless", false, "Deploy http-based function without a single service IP and load balancing support from Kubernetes. See: https://kubernetes.io/docs/concepts/services-networking/service/#headless-services")
updateCmd.Flags().Int32("port", 8080, "Deploy http-based function with a custom port")
}
59 changes: 57 additions & 2 deletions examples/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ get-python-deps:
get-python-deps-verify:
kubeless function call get-python-deps |egrep Google

get-python-custom-port:
kubeless function deploy get-python-custom-port --trigger-http --runtime python2.7 --handler helloget.foo --from-file python/helloget.py --port 8081

get-python-custom-port-verify:
kubeless function call get-python-custom-port |egrep hello.world

get-python-deps-update:
$(eval TMPDIR := $(shell mktemp -d))
printf 'bs4\ntwitter\n' > $(TMPDIR)/requirements.txt
Expand All @@ -38,6 +44,13 @@ get-python-34:
get-python-34-verify:
kubeless function call get-python |egrep hello.world

get-python-36:
kubeless function deploy get-python-36 --trigger-http --runtime python3.6 --handler helloget.foo --from-file python/helloget.py
echo "curl localhost:8080/api/v1/proxy/namespaces/default/services/get-python-36/"

get-python-36-verify:
kubeless function call get-python-36 |egrep hello.world

scheduled-get-python:
kubeless function deploy scheduled-get-python --schedule "* * * * *" --runtime python2.7 --handler helloget.foo --from-file python/helloget.py

Expand Down Expand Up @@ -74,6 +87,13 @@ get-nodejs:
get-nodejs-verify:
kubeless function call get-nodejs |egrep hello.world

get-nodejs-custom-port:
kubeless function deploy get-nodejs-custom-port --trigger-http --runtime nodejs6 --handler helloget.foo --from-file nodejs/helloget.js --port 8083
echo "curl localhost:8083/api/v1/proxy/namespaces/default/services/get-nodejs-custom-port/"

get-nodejs-custom-port-verify:
kubeless function call get-nodejs-custom-port |egrep hello.world

timeout-nodejs:
$(eval TMPDIR := $(shell mktemp -d))
printf 'module.exports = { foo: function (req, res) { while(true) {} } }\n' > $(TMPDIR)/hello-loop.js
Expand Down Expand Up @@ -119,6 +139,13 @@ get-ruby-deps:
get-ruby-deps-verify:
kubeless function call get-ruby-deps |egrep hello.world

get-ruby-custom-port:
kubeless function deploy get-ruby-custom-port --trigger-http --runtime ruby2.4 --handler helloget.foo --from-file ruby/helloget.rb --port 8082
echo "curl localhost:8082/api/v1/proxy/namespaces/default/services/get-ruby-custom-port/"

get-ruby-custom-port-verify:
kubeless function call get-ruby-custom-port |egrep hello.world

timeout-ruby:
$(eval TMPDIR := $(shell mktemp -d))
printf 'def foo(c)\n%4swhile true do;sleep(1);end\n%4s"hello world"\nend' > $(TMPDIR)/hello-loop.rb
Expand Down Expand Up @@ -149,7 +176,7 @@ custom-get-python-update:
custom-get-python-update-verify:
kubeless function call custom-get-python |egrep hello.world.updated

get: get-python get-nodejs get-python-metadata get-ruby get-ruby-deps
get: get-python get-nodejs get-python-metadata get-ruby get-ruby-deps get-python-custom-port

post-python:
kubeless function deploy post-python --trigger-http --runtime python2.7 --handler hellowithdata.handler --from-file python/hellowithdata.py
Expand All @@ -158,6 +185,13 @@ post-python:
post-python-verify:
kubeless function call post-python --data '{"it-s": "alive"}'|egrep "it.*alive"

post-python-custom-port:
kubeless function deploy post-python-custom-port --trigger-http --runtime python2.7 --handler hellowithdata.handler --from-file python/hellowithdata.py --port 8081
echo "curl --data '{\"hello\":\"world\"}' localhost:8081/api/v1/proxy/namespaces/default/services/post-python-custom-port/ --header \"Content-Type:application/json\""

post-python-custom-port-verify:
kubeless function call post-python-custom-port --data '{"it-s": "alive"}'|egrep "it.*alive"

post-nodejs:
kubeless function deploy post-nodejs --trigger-http --runtime nodejs6 --handler hellowithdata.handler --from-file nodejs/hellowithdata.js
echo "curl --data '{\"hello\":\"world\"}' localhost:8080/api/v1/proxy/namespaces/default/services/post-nodejs/ --header \"Content-Type:application/json\""
Expand All @@ -178,7 +212,7 @@ post-dotnetcore:
post-dotnetcore-verify:
kubeless function call post-dotnetcore --data '{"it-s": "alive"}'|egrep "it.*alive"

post: post-python post-nodejs post-ruby
post: post-python post-nodejs post-ruby post-python-custom-port

pubsub-python:
kubeless topic create s3-python || true
Expand Down Expand Up @@ -225,6 +259,27 @@ pubsub-python34-verify:
done; \
$$found

pubsub-python36:
kubeless function deploy pubsub-python36 --trigger-topic s3-python36 --runtime python3.6 --handler pubsub-python.handler --from-file python/pubsub.py

pubsub-python36-verify:
$(eval DATA := $(shell mktemp -u -t XXXXXXXX))
kubeless topic publish --topic s3-python36 --data "$(DATA)"
number="1"; \
timeout="60"; \
found=false; \
while [ $$number -le $$timeout ] ; do \
pod=`kubectl get po -oname -l function=pubsub-python36`; \
logs=`kubectl logs $$pod | grep $(DATA)`; \
if [ "$$logs" != "" ]; then \
found=true; \
break; \
fi; \
sleep 1; \
number=`expr $$number + 1`; \
done; \
$$found

pubsub-nodejs:
kubeless function deploy pubsub-nodejs --trigger-topic s3-nodejs --runtime nodejs6 --handler pubsub-nodejs.handler --from-file nodejs/helloevent.js

Expand Down
Loading

0 comments on commit fef344f

Please sign in to comment.