Export new metrics for OpenFaaS Pro scaling

* Add service target metric
* Add service min replicas metric
* Add scale type metric

These combined allow new auto-scaling modes and parameters
for OpenFaaS Pro customers.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
This commit is contained in:
Alex Ellis (OpenFaaS Ltd) 2022-01-24 12:11:54 +00:00 committed by Alex Ellis
parent 34735d61d0
commit d85d5e7239
23 changed files with 235 additions and 472 deletions

View File

@ -1,6 +1,6 @@
FROM --platform=${BUILDPLATFORM:-linux/amd64} teamserverless/license-check:0.3.9 as license-check FROM --platform=${BUILDPLATFORM:-linux/amd64} teamserverless/license-check:0.3.9 as license-check
FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.15 as build FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.17 as build
ENV GO111MODULE=on ENV GO111MODULE=on
ENV CGO_ENABLED=0 ENV CGO_ENABLED=0
@ -24,7 +24,6 @@ COPY go.sum go.sum
COPY handlers handlers COPY handlers handlers
COPY metrics metrics COPY metrics metrics
COPY requests requests COPY requests requests
COPY tests tests
COPY types types COPY types types
COPY plugin plugin COPY plugin plugin

View File

@ -5,9 +5,7 @@ go 1.16
require ( require (
github.com/docker/distribution v2.7.1+incompatible github.com/docker/distribution v2.7.1+incompatible
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/nats-io/nats-server/v2 v2.3.2 // indirect github.com/openfaas/faas-provider v0.18.7
github.com/nats-io/nats-streaming-server v0.22.0 // indirect
github.com/openfaas/faas-provider v0.18.6
github.com/openfaas/nats-queue-worker v0.0.0-20210726161954-ada9a31504c9 github.com/openfaas/nats-queue-worker v0.0.0-20210726161954-ada9a31504c9
github.com/prometheus/client_golang v1.9.0 github.com/prometheus/client_golang v1.9.0
github.com/prometheus/client_model v0.2.0 github.com/prometheus/client_model v0.2.0

View File

@ -71,7 +71,6 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
@ -93,7 +92,6 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
@ -105,8 +103,8 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
@ -141,7 +139,6 @@ github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
@ -176,12 +173,9 @@ github.com/klauspost/compress v1.11.12/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdY
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
@ -245,10 +239,9 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/openfaas/faas v0.0.0-20200422113858-a7c6c3920078/go.mod h1:E0m2rLup0Vvxg53BKxGgaYAGcZa3Xl+vvL7vSi5yQ14=
github.com/openfaas/faas-provider v0.18.6 h1:wypzvPKZqta8t4rx3W6Dm14ommBCc+rQ4DKDiBdGB7M=
github.com/openfaas/faas-provider v0.18.6/go.mod h1:fq1JL0mX4rNvVVvRLaLRJ3H6o667sHuyP5p/7SZEe98= github.com/openfaas/faas-provider v0.18.6/go.mod h1:fq1JL0mX4rNvVVvRLaLRJ3H6o667sHuyP5p/7SZEe98=
github.com/openfaas/nats-queue-worker v0.0.0-20210726161233-3fa550b705fe/go.mod h1:njdij5dt/fm8EMrpUFiAW5u6tg7higDN+Xi+LXclDas= github.com/openfaas/faas-provider v0.18.7 h1:Oq3N7KrlAkAZ23N5gzZfdA9F62vpUFk3ZkQpQuHeRBU=
github.com/openfaas/faas-provider v0.18.7/go.mod h1:S217qfIaMrv+XKJxgbhBzJzCfyFvoIF+BvYdDo6XIDQ=
github.com/openfaas/nats-queue-worker v0.0.0-20210726161954-ada9a31504c9 h1:dpG1UcgTesGfLetgT3ns1cAhP8XMDZqxnxTW1MlnwSc= github.com/openfaas/nats-queue-worker v0.0.0-20210726161954-ada9a31504c9 h1:dpG1UcgTesGfLetgT3ns1cAhP8XMDZqxnxTW1MlnwSc=
github.com/openfaas/nats-queue-worker v0.0.0-20210726161954-ada9a31504c9/go.mod h1:ajlN2z+D8JPBq3kWNv4WLT6mtKPqlgeE3dYEx39d1tk= github.com/openfaas/nats-queue-worker v0.0.0-20210726161954-ada9a31504c9/go.mod h1:ajlN2z+D8JPBq3kWNv4WLT6mtKPqlgeE3dYEx39d1tk=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
@ -261,7 +254,6 @@ github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnh
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
@ -378,7 +370,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -495,7 +486,6 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
@ -503,7 +493,6 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=

View File

@ -25,6 +25,7 @@ import (
const NameExpression = "-a-zA-Z_0-9." const NameExpression = "-a-zA-Z_0-9."
func main() { func main() {
if len(version.GitCommitMessage) == 0 { if len(version.GitCommitMessage) == 0 {
version.GitCommitMessage = "See GitHub for latest changes" version.GitCommitMessage = "See GitHub for latest changes"
} }
@ -125,7 +126,7 @@ func main() {
faasHandlers.DeployFunction = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, nilURLTransformer, serviceAuthInjector) faasHandlers.DeployFunction = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, nilURLTransformer, serviceAuthInjector)
faasHandlers.DeleteFunction = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, nilURLTransformer, serviceAuthInjector) faasHandlers.DeleteFunction = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, nilURLTransformer, serviceAuthInjector)
faasHandlers.UpdateFunction = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, nilURLTransformer, serviceAuthInjector) faasHandlers.UpdateFunction = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, nilURLTransformer, serviceAuthInjector)
faasHandlers.QueryFunction = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, nilURLTransformer, serviceAuthInjector) faasHandlers.FunctionStatus = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, nilURLTransformer, serviceAuthInjector)
faasHandlers.InfoHandler = handlers.MakeInfoHandler(handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, nilURLTransformer, serviceAuthInjector)) faasHandlers.InfoHandler = handlers.MakeInfoHandler(handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, nilURLTransformer, serviceAuthInjector))
faasHandlers.SecretHandler = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, nilURLTransformer, serviceAuthInjector) faasHandlers.SecretHandler = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, nilURLTransformer, serviceAuthInjector)
@ -198,8 +199,8 @@ func main() {
decorateExternalAuth(faasHandlers.ListFunctions, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) decorateExternalAuth(faasHandlers.ListFunctions, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody)
faasHandlers.ScaleFunction = faasHandlers.ScaleFunction =
decorateExternalAuth(faasHandlers.ScaleFunction, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) decorateExternalAuth(faasHandlers.ScaleFunction, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody)
faasHandlers.QueryFunction = faasHandlers.FunctionStatus =
decorateExternalAuth(faasHandlers.QueryFunction, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) decorateExternalAuth(faasHandlers.FunctionStatus, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody)
faasHandlers.InfoHandler = faasHandlers.InfoHandler =
decorateExternalAuth(faasHandlers.InfoHandler, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody) decorateExternalAuth(faasHandlers.InfoHandler, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody)
faasHandlers.AsyncReport = faasHandlers.AsyncReport =
@ -222,7 +223,7 @@ func main() {
r.HandleFunc("/system/info", faasHandlers.InfoHandler).Methods(http.MethodGet) r.HandleFunc("/system/info", faasHandlers.InfoHandler).Methods(http.MethodGet)
r.HandleFunc("/system/alert", faasHandlers.Alert).Methods(http.MethodPost) r.HandleFunc("/system/alert", faasHandlers.Alert).Methods(http.MethodPost)
r.HandleFunc("/system/function/{name:["+NameExpression+"]+}", faasHandlers.QueryFunction).Methods(http.MethodGet) r.HandleFunc("/system/function/{name:["+NameExpression+"]+}", faasHandlers.FunctionStatus).Methods(http.MethodGet)
r.HandleFunc("/system/functions", faasHandlers.ListFunctions).Methods(http.MethodGet) r.HandleFunc("/system/functions", faasHandlers.ListFunctions).Methods(http.MethodGet)
r.HandleFunc("/system/functions", faasHandlers.DeployFunction).Methods(http.MethodPost) r.HandleFunc("/system/functions", faasHandlers.DeployFunction).Methods(http.MethodPost)
r.HandleFunc("/system/functions", faasHandlers.DeleteFunction).Methods(http.MethodDelete) r.HandleFunc("/system/functions", faasHandlers.DeleteFunction).Methods(http.MethodDelete)

View File

@ -35,9 +35,7 @@ func AddMetricsHandler(handler http.HandlerFunc, prometheusQuery PrometheusQuery
recorder.Code, recorder.Code,
string(upstreamBody)) string(upstreamBody))
w.Header().Set("Content-Type", "text/plain") http.Error(w, "Unexpected status code retriving functions from backend", http.StatusInternalServerError)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("List functions responded with code %d", recorder.Code)))
return return
} }
@ -48,28 +46,33 @@ func AddMetricsHandler(handler http.HandlerFunc, prometheusQuery PrometheusQuery
if err != nil { if err != nil {
log.Printf("Metrics upstream error: %s", err) log.Printf("Metrics upstream error: %s", err)
w.Header().Set("Content-Type", "text/plain") http.Error(w, "Error parsing metrics from upstream provider/backend", http.StatusInternalServerError)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Error parsing metrics from upstream provider/backend."))
return return
} }
expr := url.QueryEscape(`sum(gateway_function_invocation_total{function_name=~".*", code=~".*"}) by (function_name, code)`) // Ensure values are empty first.
// expr := "sum(gateway_function_invocation_total%7Bfunction_name%3D~%22.*%22%2C+code%3D~%22.*%22%7D)+by+(function_name%2C+code)" for i := range functions {
results, fetchErr := prometheusQuery.Fetch(expr) functions[i].InvocationCount = 0
if fetchErr != nil {
log.Printf("Error querying Prometheus API: %s\n", fetchErr.Error())
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(upstreamBody)
return
} }
if len(functions) > 0 {
ns := functions[0].Namespace
q := fmt.Sprintf(`sum(gateway_function_invocation_total{function_name=~".*.%s"}) by (function_name)`, ns)
// Restrict query results to only function names matching namespace suffix.
results, err := prometheusQuery.Fetch(url.QueryEscape(q))
if err != nil {
log.Printf("Error querying Prometheus: %s\n", err.Error())
return
}
mixIn(&functions, results) mixIn(&functions, results)
}
bytesOut, marshalErr := json.Marshal(functions) bytesOut, err := json.Marshal(functions)
if marshalErr != nil { if err != nil {
log.Println(marshalErr) log.Printf("Error serializing functions: %s", err)
http.Error(w, "error writing response after adding metrics", http.StatusInternalServerError)
return return
} }
@ -85,25 +88,19 @@ func mixIn(functions *[]types.FunctionStatus, metrics *VectorQueryResponse) {
return return
} }
// Ensure values are empty first.
for i := range *functions {
(*functions)[i].InvocationCount = 0
}
for i, function := range *functions { for i, function := range *functions {
for _, v := range metrics.Data.Result { for _, v := range metrics.Data.Result {
if v.Metric.FunctionName == fmt.Sprintf("%s.%s", function.Name, function.Namespace) { if v.Metric.FunctionName == fmt.Sprintf("%s.%s", function.Name, function.Namespace) {
metricValue := v.Value[1] metricValue := v.Value[1]
switch metricValue.(type) { switch value := metricValue.(type) {
case string: case string:
f, strconvErr := strconv.ParseFloat(metricValue.(string), 64) f, err := strconv.ParseFloat(value, 64)
if strconvErr != nil { if err != nil {
log.Printf("Unable to convert value for metric: %s\n", strconvErr) log.Printf("add_metrics: unable to convert value %q for metric: %s", value, err)
continue continue
} }
(*functions)[i].InvocationCount += f (*functions)[i].InvocationCount += f
break
} }
} }
} }

View File

@ -83,7 +83,7 @@ func Test_FunctionsHandler_ReturnsJSONAndOneFunction(t *testing.T) {
func makeFunctionsHandler() http.HandlerFunc { func makeFunctionsHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
functions := []types.FunctionStatus{ functions := []types.FunctionStatus{
types.FunctionStatus{ {
Name: "func_echoit", Name: "func_echoit",
Replicas: 0, Replicas: 0,
Namespace: "openfaas-fn", Namespace: "openfaas-fn",

View File

@ -12,12 +12,14 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"strconv"
"time" "time"
"log" "log"
"github.com/openfaas/faas-provider/auth" "github.com/openfaas/faas-provider/auth"
types "github.com/openfaas/faas-provider/types" types "github.com/openfaas/faas-provider/types"
"github.com/openfaas/faas/gateway/scaling"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
) )
@ -46,6 +48,7 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
e.metricOptions.GatewayFunctionsHistogram.Describe(ch) e.metricOptions.GatewayFunctionsHistogram.Describe(ch)
e.metricOptions.ServiceReplicasGauge.Describe(ch) e.metricOptions.ServiceReplicasGauge.Describe(ch)
e.metricOptions.GatewayFunctionInvocationStarted.Describe(ch) e.metricOptions.GatewayFunctionInvocationStarted.Describe(ch)
e.metricOptions.ServiceTargetLoadGauge.Describe(ch)
e.metricOptions.ServiceMetrics.Counter.Describe(ch) e.metricOptions.ServiceMetrics.Counter.Describe(ch)
e.metricOptions.ServiceMetrics.Histogram.Describe(ch) e.metricOptions.ServiceMetrics.Histogram.Describe(ch)
@ -59,6 +62,8 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
e.metricOptions.GatewayFunctionInvocationStarted.Collect(ch) e.metricOptions.GatewayFunctionInvocationStarted.Collect(ch)
e.metricOptions.ServiceReplicasGauge.Reset() e.metricOptions.ServiceReplicasGauge.Reset()
e.metricOptions.ServiceTargetLoadGauge.Reset()
for _, service := range e.services { for _, service := range e.services {
var serviceName string var serviceName string
if len(service.Namespace) > 0 { if len(service.Namespace) > 0 {
@ -66,12 +71,54 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
} else { } else {
serviceName = service.Name serviceName = service.Name
} }
// Set current replica count
e.metricOptions.ServiceReplicasGauge. e.metricOptions.ServiceReplicasGauge.
WithLabelValues(serviceName). WithLabelValues(serviceName).
Set(float64(service.Replicas)) Set(float64(service.Replicas))
// Set minimum replicas
minReplicas := scaling.DefaultMinReplicas
if service.Labels != nil {
a := *service.Labels
if v, ok := a[scaling.MinScaleLabel]; ok && len(v) > 0 {
val, _ := strconv.Atoi(v)
minReplicas = val
}
}
e.metricOptions.ServiceMinReplicasGauge.
WithLabelValues(serviceName).
Set(float64(minReplicas))
// Set scale type
scaleType := scaling.DefaultTypeScale
if service.Labels != nil {
a := *service.Labels
if v, ok := a[scaling.ScaleTypeLabel]; ok && len(v) > 0 {
scaleType = v
}
}
// Set target load
targetScale := scaling.DefaultTargetLoad
if service.Labels != nil {
a := *service.Labels
if v, ok := a[scaling.TargetLoadLabel]; ok && len(v) > 0 {
val, _ := strconv.Atoi(v)
targetScale = val
}
}
e.metricOptions.ServiceTargetLoadGauge.
WithLabelValues(serviceName, scaleType).
Set(float64(targetScale))
} }
e.metricOptions.ServiceReplicasGauge.Collect(ch) e.metricOptions.ServiceReplicasGauge.Collect(ch)
e.metricOptions.ServiceMinReplicasGauge.Collect(ch)
e.metricOptions.ServiceTargetLoadGauge.Collect(ch)
e.metricOptions.ServiceMetrics.Counter.Collect(ch) e.metricOptions.ServiceMetrics.Counter.Collect(ch)
e.metricOptions.ServiceMetrics.Histogram.Collect(ch) e.metricOptions.ServiceMetrics.Histogram.Collect(ch)

View File

@ -44,21 +44,21 @@ func Test_Describe_DescribesThePrometheusMetrics(t *testing.T) {
expectedGatewayFunctionInvocationDesc := `Desc{fqName: "gateway_function_invocation_total", help: "Function metrics", constLabels: {}, variableLabels: [function_name code]}` expectedGatewayFunctionInvocationDesc := `Desc{fqName: "gateway_function_invocation_total", help: "Function metrics", constLabels: {}, variableLabels: [function_name code]}`
actualGatewayFunctionInvocationDesc := d.String() actualGatewayFunctionInvocationDesc := d.String()
if expectedGatewayFunctionInvocationDesc != actualGatewayFunctionInvocationDesc { if expectedGatewayFunctionInvocationDesc != actualGatewayFunctionInvocationDesc {
t.Errorf("Want %s, got: %s", expectedGatewayFunctionInvocationDesc, actualGatewayFunctionInvocationDesc) t.Errorf("Want\n%s\ngot\n%s", expectedGatewayFunctionInvocationDesc, actualGatewayFunctionInvocationDesc)
} }
d = <-ch d = <-ch
expectedGatewayFunctionsHistogramDesc := `Desc{fqName: "gateway_functions_seconds", help: "Function time taken", constLabels: {}, variableLabels: [function_name]}` expectedGatewayFunctionsHistogramDesc := `Desc{fqName: "gateway_functions_seconds", help: "Function time taken", constLabels: {}, variableLabels: [function_name]}`
actualGatewayFunctionsHistogramDesc := d.String() actualGatewayFunctionsHistogramDesc := d.String()
if expectedGatewayFunctionsHistogramDesc != actualGatewayFunctionsHistogramDesc { if expectedGatewayFunctionsHistogramDesc != actualGatewayFunctionsHistogramDesc {
t.Errorf("Want %s, got: %s", expectedGatewayFunctionsHistogramDesc, actualGatewayFunctionsHistogramDesc) t.Errorf("Want\n%s\ngot\n%s", expectedGatewayFunctionsHistogramDesc, actualGatewayFunctionsHistogramDesc)
} }
d = <-ch d = <-ch
expectedServiceReplicasGaugeDesc := `Desc{fqName: "gateway_service_count", help: "Service replicas", constLabels: {}, variableLabels: [function_name]}` expectedServiceReplicasGaugeDesc := `Desc{fqName: "gateway_service_count", help: "Current count of replicas for function", constLabels: {}, variableLabels: [function_name]}`
actualServiceReplicasGaugeDesc := d.String() actualServiceReplicasGaugeDesc := d.String()
if expectedServiceReplicasGaugeDesc != actualServiceReplicasGaugeDesc { if expectedServiceReplicasGaugeDesc != actualServiceReplicasGaugeDesc {
t.Errorf("Want %s, got: %s", expectedServiceReplicasGaugeDesc, actualServiceReplicasGaugeDesc) t.Errorf("Want\n%s\ngot\n%s", expectedServiceReplicasGaugeDesc, actualServiceReplicasGaugeDesc)
} }
} }

View File

@ -16,7 +16,11 @@ type MetricOptions struct {
GatewayFunctionInvocation *prometheus.CounterVec GatewayFunctionInvocation *prometheus.CounterVec
GatewayFunctionsHistogram *prometheus.HistogramVec GatewayFunctionsHistogram *prometheus.HistogramVec
GatewayFunctionInvocationStarted *prometheus.CounterVec GatewayFunctionInvocationStarted *prometheus.CounterVec
ServiceReplicasGauge *prometheus.GaugeVec ServiceReplicasGauge *prometheus.GaugeVec
ServiceMinReplicasGauge *prometheus.GaugeVec
ServiceTargetLoadGauge *prometheus.GaugeVec
ServiceMetrics *ServiceMetricOptions ServiceMetrics *ServiceMetricOptions
} }
@ -62,11 +66,29 @@ func BuildMetricsOptions() MetricOptions {
prometheus.GaugeOpts{ prometheus.GaugeOpts{
Namespace: "gateway", Namespace: "gateway",
Name: "service_count", Name: "service_count",
Help: "Service replicas", Help: "Current count of replicas for function",
}, },
[]string{"function_name"}, []string{"function_name"},
) )
serviceMinReplicas := prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: "gateway",
Name: "service_min",
Help: "Minium replicas for function",
},
[]string{"function_name"},
)
serviceTargetLoad := prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: "gateway",
Name: "service_target_load",
Help: "Target load for function",
},
[]string{"function_name", "scaling_type"},
)
// For automatic monitoring and alerting (RED method) // For automatic monitoring and alerting (RED method)
histogram := prometheus.NewHistogramVec(prometheus.HistogramOpts{ histogram := prometheus.NewHistogramVec(prometheus.HistogramOpts{
Subsystem: "http", Subsystem: "http",
@ -104,6 +126,8 @@ func BuildMetricsOptions() MetricOptions {
GatewayFunctionsHistogram: gatewayFunctionsHistogram, GatewayFunctionsHistogram: gatewayFunctionsHistogram,
GatewayFunctionInvocation: gatewayFunctionInvocation, GatewayFunctionInvocation: gatewayFunctionInvocation,
ServiceReplicasGauge: serviceReplicas, ServiceReplicasGauge: serviceReplicas,
ServiceMinReplicasGauge: serviceMinReplicas,
ServiceTargetLoadGauge: serviceTargetLoad,
ServiceMetrics: serviceMetricOptions, ServiceMetrics: serviceMetricOptions,
GatewayFunctionInvocationStarted: gatewayFunctionInvocationStarted, GatewayFunctionInvocationStarted: gatewayFunctionInvocationStarted,
} }

View File

@ -100,12 +100,15 @@ func (s ExternalServiceQuery) GetReplicas(serviceName, serviceNamespace string)
scalingFactor := uint64(scaling.DefaultScalingFactor) scalingFactor := uint64(scaling.DefaultScalingFactor)
availableReplicas := function.AvailableReplicas availableReplicas := function.AvailableReplicas
targetLoad := uint64(scaling.DefaultTargetLoad)
if function.Labels != nil { if function.Labels != nil {
labels := *function.Labels labels := *function.Labels
minReplicas = extractLabelValue(labels[scaling.MinScaleLabel], minReplicas) minReplicas = extractLabelValue(labels[scaling.MinScaleLabel], minReplicas)
maxReplicas = extractLabelValue(labels[scaling.MaxScaleLabel], maxReplicas) maxReplicas = extractLabelValue(labels[scaling.MaxScaleLabel], maxReplicas)
extractedScalingFactor := extractLabelValue(labels[scaling.ScalingFactorLabel], scalingFactor) extractedScalingFactor := extractLabelValue(labels[scaling.ScalingFactorLabel], scalingFactor)
targetLoad = extractLabelValue(labels[scaling.TargetLoadLabel], targetLoad)
if extractedScalingFactor >= 0 && extractedScalingFactor <= 100 { if extractedScalingFactor >= 0 && extractedScalingFactor <= 100 {
scalingFactor = extractedScalingFactor scalingFactor = extractedScalingFactor
@ -113,6 +116,7 @@ func (s ExternalServiceQuery) GetReplicas(serviceName, serviceNamespace string)
log.Printf("Bad Scaling Factor: %d, is not in range of [0 - 100]. Will fallback to %d", extractedScalingFactor, scalingFactor) log.Printf("Bad Scaling Factor: %d, is not in range of [0 - 100]. Will fallback to %d", extractedScalingFactor, scalingFactor)
} }
} }
log.Printf("GetReplicas [%s.%s] took: %fs", serviceName, serviceNamespace, time.Since(start).Seconds()) log.Printf("GetReplicas [%s.%s] took: %fs", serviceName, serviceNamespace, time.Since(start).Seconds())
return scaling.ServiceQueryResponse{ return scaling.ServiceQueryResponse{
@ -122,6 +126,7 @@ func (s ExternalServiceQuery) GetReplicas(serviceName, serviceNamespace string)
ScalingFactor: scalingFactor, ScalingFactor: scalingFactor,
AvailableReplicas: availableReplicas, AvailableReplicas: availableReplicas,
Annotations: function.Annotations, Annotations: function.Annotations,
TargetLoad: targetLoad,
}, err }, err
} }

View File

@ -75,6 +75,7 @@ func TestGetReplicasExistentFn(t *testing.T) {
MinReplicas: uint64(scaling.DefaultMinReplicas), MinReplicas: uint64(scaling.DefaultMinReplicas),
ScalingFactor: uint64(scaling.DefaultScalingFactor), ScalingFactor: uint64(scaling.DefaultScalingFactor),
AvailableReplicas: 0, AvailableReplicas: 0,
TargetLoad: 10,
} }
var injector middleware.AuthInjector var injector middleware.AuthInjector
@ -89,7 +90,7 @@ func TestGetReplicasExistentFn(t *testing.T) {
t.Fail() t.Fail()
} }
if svcQryResp != expectedSvcQryResp { if svcQryResp != expectedSvcQryResp {
t.Logf("Unexpected return values - wanted %+v, got: %+v ", expectedSvcQryResp, svcQryResp) t.Logf("Unexpected return values - wanted\n%+v\ngot\n%+v ", expectedSvcQryResp, svcQryResp)
t.Fail() t.Fail()
} }
} }

View File

@ -10,6 +10,11 @@ const (
// DefaultScalingFactor is the defining proportion for the scaling increments. // DefaultScalingFactor is the defining proportion for the scaling increments.
DefaultScalingFactor = 20 DefaultScalingFactor = 20
// DefaultTargetLoad
DefaultTargetLoad = 10
DefaultTypeScale = "rps"
// MinScaleLabel label indicating min scale for a function // MinScaleLabel label indicating min scale for a function
MinScaleLabel = "com.openfaas.scale.min" MinScaleLabel = "com.openfaas.scale.min"
@ -18,4 +23,10 @@ const (
// ScalingFactorLabel label indicates the scaling factor for a function // ScalingFactorLabel label indicates the scaling factor for a function
ScalingFactorLabel = "com.openfaas.scale.factor" ScalingFactorLabel = "com.openfaas.scale.factor"
// TargetLoadLabel see also DefaultTargetScale
TargetLoadLabel = "com.openfaas.scale.target"
// ScaleTypeLabel see also DefaultScaleType
ScaleTypeLabel = "com.openfaas.scale.type"
) )

View File

@ -17,4 +17,5 @@ type ServiceQueryResponse struct {
ScalingFactor uint64 ScalingFactor uint64
AvailableReplicas uint64 AvailableReplicas uint64
Annotations *map[string]string Annotations *map[string]string
TargetLoad uint64
} }

View File

@ -1,16 +0,0 @@
# Integration testing
These tests should be run against the sample stack included in the repository root.
## Deploy the stack
```
./deploy_stack.sh
faas-cli deploy -f ./stack.yml
```
## Remove the stack
1. Delete all OpenFaaS deployed functions
```
faas-cli remove
docker stack rm func
```

View File

@ -1,88 +0,0 @@
// Copyright (c) Alex Ellis 2017. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package inttests
import (
"encoding/json"
"net/http"
"strings"
"testing"
types "github.com/openfaas/faas-provider/types"
requests "github.com/openfaas/faas/gateway/requests"
)
func createFunction(request types.FunctionDeployment) (string, int, error) {
marshalled, _ := json.Marshal(request)
return fireRequest("http://localhost:8080/system/functions", http.MethodPost, string(marshalled))
}
func deleteFunction(name string) (string, int, error) {
marshalled, _ := json.Marshal(requests.DeleteFunctionRequest{FunctionName: name})
return fireRequest("http://localhost:8080/system/functions", http.MethodDelete, string(marshalled))
}
func TestCreate_ValidRequest(t *testing.T) {
request := types.FunctionDeployment{
Service: "test_resizer",
Image: "functions/resizer",
EnvProcess: "",
}
_, code, err := createFunction(request)
if err != nil {
t.Log(err)
t.Fail()
}
expectedErrorCode := http.StatusAccepted
if code != expectedErrorCode {
t.Errorf("Got HTTP code: %d, want %d\n", code, expectedErrorCode)
return
}
deleteFunction("test_resizer")
}
func TestCreate_InvalidImage(t *testing.T) {
request := types.FunctionDeployment{
Service: "test_resizer",
Image: "a b c",
EnvProcess: "",
}
body, code, err := createFunction(request)
if err != nil {
t.Log(err)
t.Fail()
}
expectedErrorCode := http.StatusBadRequest
if code != expectedErrorCode {
t.Errorf("Got HTTP code: %d, want %d\n", code, expectedErrorCode)
return
}
expectedErrorSlice := "is not a valid repository/tag"
if !strings.Contains(body, expectedErrorSlice) {
t.Errorf("Error message %s does not contain: %s\n", body, expectedErrorSlice)
return
}
}
func TestCreate_InvalidJson(t *testing.T) {
reqBody := `not json`
_, code, err := fireRequest("http://localhost:8080/system/functions", http.MethodPost, reqBody)
if err != nil {
t.Log(err)
t.Fail()
}
if code != http.StatusBadRequest {
t.Errorf("Got HTTP code: %d, want %d\n", code, http.StatusBadRequest)
}
}

View File

@ -1,37 +0,0 @@
// Copyright (c) Alex Ellis 2017. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package inttests
import (
"net/http"
"testing"
)
func TestDelete_EmptyFunctionGivenFails(t *testing.T) {
reqBody := `{"functionName":""}`
_, code, err := fireRequest("http://localhost:8080/system/functions", http.MethodDelete, reqBody)
if err != nil {
t.Log(err)
t.Fail()
}
if code != http.StatusBadRequest {
t.Errorf("Got HTTP code: %d, want %d\n", code, http.StatusBadRequest)
}
}
func TestDelete_NonExistingFunctionGives404(t *testing.T) {
reqBody := `{"functionName":"does_not_exist"}`
_, code, err := fireRequest("http://localhost:8080/system/functions", http.MethodDelete, reqBody)
if err != nil {
t.Log(err)
t.Fail()
}
if code != http.StatusNotFound {
t.Errorf("Got HTTP code: %d, want %d\n", code, http.StatusNotFound)
}
}

View File

@ -1,68 +0,0 @@
package inttests
import (
"encoding/json"
"net/http"
"testing"
"github.com/openfaas/faas/gateway/types"
)
func Test_InfoEndpoint_Returns_200(t *testing.T) {
_, code, err := fireRequest("http://localhost:8080/system/info", http.MethodGet, "")
if err != nil {
t.Log(err)
t.Fail()
}
wantCode := http.StatusOK
if code != wantCode {
t.Errorf("status code, want: %d, got: %d", wantCode, code)
t.Fail()
}
}
func Test_InfoEndpoint_Returns_Gateway_Version_SHA_And_Message(t *testing.T) {
body, _, err := fireRequest("http://localhost:8080/system/info", http.MethodGet, "")
if err != nil {
t.Log(err)
t.Fail()
}
gatewayInfo := &types.GatewayInfo{}
err = json.Unmarshal([]byte(body), gatewayInfo)
if err != nil {
t.Errorf("Could not unmarshal gateway info, response body:%s, error:%s", body, err.Error())
t.Fail()
}
if len(gatewayInfo.Version.SHA) != 40 {
t.Errorf("length of SHA incorrect, want: %d, got: %d. Json body was %s", 40, len(gatewayInfo.Version.SHA), body)
}
if len(gatewayInfo.Version.CommitMessage) == 0 {
t.Errorf("length of commit message should be greater than 0. Json body was %s", body)
}
}
func Test_InfoEndpoint_Returns_Arch(t *testing.T) {
body, _, err := fireRequest("http://localhost:8080/system/info", http.MethodGet, "")
if err != nil {
t.Log(err)
t.Fail()
}
gatewayInfo := &types.GatewayInfo{}
err = json.Unmarshal([]byte(body), gatewayInfo)
if err != nil {
t.Errorf("Could not unmarshal gateway info, response body:%s, error:%s", body, err.Error())
t.Fail()
}
if len(gatewayInfo.Arch) == 0 {
t.Errorf("value of arch should be non-empty")
}
}

View File

@ -1,125 +0,0 @@
// Copyright (c) Alex Ellis 2017. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package inttests
import (
"bytes"
"io/ioutil"
"log"
"net/http"
"testing"
"time"
)
// Before running these tests do a Docker stack deploy.
func fireRequest(url string, method string, reqBody string) (string, int, error) {
headers := make(map[string]string)
return fireRequestWithHeaders(url, method, reqBody, headers)
}
func fireRequestWithHeaders(url string, method string, reqBody string, headers map[string]string) (string, int, error) {
httpClient := http.Client{
Timeout: time.Second * 2, // Maximum of 2 secs
}
req, err := http.NewRequest(method, url, bytes.NewBufferString(reqBody))
if err != nil {
log.Fatal(err)
}
req.Header.Set("User-Agent", "go-integration")
for kk, vv := range headers {
req.Header.Set(kk, vv)
}
res, getErr := httpClient.Do(req)
if getErr != nil {
log.Fatal(getErr)
}
body, readErr := ioutil.ReadAll(res.Body)
defer req.Body.Close()
if readErr != nil {
log.Fatal(readErr)
}
return string(body), res.StatusCode, readErr
}
func TestGet_Rejected(t *testing.T) {
var reqBody string
unsupportedMethod := http.MethodHead
_, code, err := fireRequest("http://localhost:8080/function/echoit", unsupportedMethod, reqBody)
want := http.StatusMethodNotAllowed
if code != want {
t.Logf("Failed got: %d, wanted: %d", code, want)
t.Fail()
}
if err != nil {
t.Log(err)
t.Fail()
}
}
func TestEchoIt_Post_Route_Handler_ForwardsClientHeaders(t *testing.T) {
reqBody := "test message"
headers := make(map[string]string, 0)
headers["X-Api-Key"] = "123"
body, code, err := fireRequestWithHeaders("http://localhost:8080/function/echoit", http.MethodPost, reqBody, headers)
if err != nil {
t.Log(err)
t.Fail()
}
if code != http.StatusOK {
t.Logf("Failed, code: %d, body:%s", code, body)
t.Fail()
}
if body != reqBody {
t.Log("Expected body returned")
t.Fail()
}
}
func TestEchoIt_Post_Route_Handler(t *testing.T) {
reqBody := "test message"
body, code, err := fireRequest("http://localhost:8080/function/echoit", http.MethodPost, reqBody)
if err != nil {
t.Log(err)
t.Fail()
}
if code != http.StatusOK {
t.Log("Failed")
}
if body != reqBody {
t.Log("Expected body returned")
t.Fail()
}
}
// Test suppressed due to X-Header deprecation.
// func TestEchoIt_Post_X_Header_Routing_Handler(t *testing.T) {
// reqBody := "test message"
// headers := make(map[string]string, 0)
// headers["X-Function"] = "func_echoit"
// body, code, err := fireRequestWithHeaders("http://localhost:8080/", http.MethodPost, reqBody, headers)
// if err != nil {
// t.Log(err)
// t.Fail()
// }
// if code != http.StatusOK {
// t.Logf("statusCode - want: %d, got: %d", http.StatusOK, code)
// }
// if body != reqBody {
// t.Logf("Expected body from echo function to be equal to input, but was: %s", body)
// t.Fail()
// }
// }

View File

@ -4,18 +4,24 @@ import "net/http"
// HandlerSet can be initialized with handlers for binding to mux // HandlerSet can be initialized with handlers for binding to mux
type HandlerSet struct { type HandlerSet struct {
// Proxy invokes functions upstream // Proxy invokes a function
Proxy http.HandlerFunc Proxy http.HandlerFunc
// DeployFunction deploys a new function that isn't already deployed
DeployFunction http.HandlerFunc DeployFunction http.HandlerFunc
// DeleteFunction deletes a function that is already deployed
DeleteFunction http.HandlerFunc DeleteFunction http.HandlerFunc
// ListFunctions lists all deployed functions in a namespace
ListFunctions http.HandlerFunc ListFunctions http.HandlerFunc
Alert http.HandlerFunc Alert http.HandlerFunc
// UpdateFunction updates an existing function
UpdateFunction http.HandlerFunc UpdateFunction http.HandlerFunc
// QueryFunction queries the metdata for a function // FunctionStatus returns the status of an already deployed function
QueryFunction http.HandlerFunc FunctionStatus http.HandlerFunc
// QueuedProxy queue work and return synchronous response // QueuedProxy queue work and return synchronous response
QueuedProxy http.HandlerFunc QueuedProxy http.HandlerFunc

View File

@ -0,0 +1,51 @@
package types
// FunctionDeployment represents a request to create or update a Function.
type FunctionDeployment struct {
// Service is the name of the function deployment
Service string `json:"service"`
// Image is a fully-qualified container image
Image string `json:"image"`
// Namespace for the function, if supported by the faas-provider
Namespace string `json:"namespace,omitempty"`
// EnvProcess overrides the fprocess environment variable and can be used
// with the watchdog
EnvProcess string `json:"envProcess,omitempty"`
// EnvVars can be provided to set environment variables for the function runtime.
EnvVars map[string]string `json:"envVars,omitempty"`
// Constraints are specific to the faas-provider.
Constraints []string `json:"constraints,omitempty"`
// Secrets list of secrets to be made available to function
Secrets []string `json:"secrets,omitempty"`
// Labels are metadata for functions which may be used by the
// faas-provider or the gateway
Labels *map[string]string `json:"labels,omitempty"`
// Annotations are metadata for functions which may be used by the
// faas-provider or the gateway
Annotations *map[string]string `json:"annotations,omitempty"`
// Limits for function
Limits *FunctionResources `json:"limits,omitempty"`
// Requests of resources requested by function
Requests *FunctionResources `json:"requests,omitempty"`
// ReadOnlyRootFilesystem removes write-access from the root filesystem
// mount-point.
ReadOnlyRootFilesystem bool `json:"readOnlyRootFilesystem,omitempty"`
}
// FunctionResources Memory and CPU
type FunctionResources struct {
Memory string `json:"memory,omitempty"`
CPU string `json:"cpu,omitempty"`
}

View File

@ -2,72 +2,6 @@ package types
import "time" import "time"
// Secret for underlying orchestrator
type Secret struct {
// Name of the secret
Name string `json:"name"`
// Namespace if applicable for the secret
Namespace string `json:"namespace,omitempty"`
// Value is a string representing the string's value
Value string `json:"value,omitempty"`
// RawValue can be used to provide binary data when
// Value is not set
RawValue []byte `json:"rawValue,omitempty"`
}
// FunctionDeployment represents a request to create or update a Function.
type FunctionDeployment struct {
// Service is the name of the function deployment
Service string `json:"service"`
// Image is a fully-qualified container image
Image string `json:"image"`
// Namespace for the function, if supported by the faas-provider
Namespace string `json:"namespace,omitempty"`
// EnvProcess overrides the fprocess environment variable and can be used
// with the watchdog
EnvProcess string `json:"envProcess,omitempty"`
// EnvVars can be provided to set environment variables for the function runtime.
EnvVars map[string]string `json:"envVars,omitempty"`
// Constraints are specific to the faas-provider.
Constraints []string `json:"constraints,omitempty"`
// Secrets list of secrets to be made available to function
Secrets []string `json:"secrets,omitempty"`
// Labels are metadata for functions which may be used by the
// faas-provider or the gateway
Labels *map[string]string `json:"labels,omitempty"`
// Annotations are metadata for functions which may be used by the
// faas-provider or the gateway
Annotations *map[string]string `json:"annotations,omitempty"`
// Limits for function
Limits *FunctionResources `json:"limits,omitempty"`
// Requests of resources requested by function
Requests *FunctionResources `json:"requests,omitempty"`
// ReadOnlyRootFilesystem removes write-access from the root filesystem
// mount-point.
ReadOnlyRootFilesystem bool `json:"readOnlyRootFilesystem,omitempty"`
}
// FunctionResources Memory and CPU
type FunctionResources struct {
Memory string `json:"memory,omitempty"`
CPU string `json:"cpu,omitempty"`
}
// FunctionStatus exported for system/functions endpoint // FunctionStatus exported for system/functions endpoint
type FunctionStatus struct { type FunctionStatus struct {
@ -128,4 +62,24 @@ type FunctionStatus struct {
// CreatedAt is the time read back from the faas backend's // CreatedAt is the time read back from the faas backend's
// data store for when the function or its container was created. // data store for when the function or its container was created.
CreatedAt time.Time `json:"createdAt,omitempty"` CreatedAt time.Time `json:"createdAt,omitempty"`
// Utilisation represents CPU and RAM used by all of the
// functions' replicas. Divide by AvailableReplicas for an
// average value per replica.
Utilisation FunctionUtilisation `json:"usage,omitempty"`
}
// FunctionUtilisation represents CPU and RAM used by all of the
// functions' replicas.
//
// CPU is measured in seconds consumed since the last measurement
// RAM is measured in total bytes consumed
//
type FunctionUtilisation struct {
// CPU is the increase in CPU usage since the last measurement
// equivalent to Kubernetes' concept of millicores.
CPU float64 `json:"cpu,omitempty"`
//TotalMemoryBytes is the total memory usage in bytes.
TotalMemoryBytes float64 `json:"totalMemoryBytes,omitempty"`
} }

View File

@ -0,0 +1,17 @@
package types
// Secret for underlying orchestrator
type Secret struct {
// Name of the secret
Name string `json:"name"`
// Namespace if applicable for the secret
Namespace string `json:"namespace,omitempty"`
// Value is a string representing the string's value
Value string `json:"value,omitempty"`
// RawValue can be used to provide binary data when
// Value is not set
RawValue []byte `json:"rawValue,omitempty"`
}

View File

@ -20,10 +20,6 @@ github.com/golang/protobuf/ptypes/timestamp
github.com/gorilla/mux github.com/gorilla/mux
# github.com/matttproud/golang_protobuf_extensions v1.0.1 # github.com/matttproud/golang_protobuf_extensions v1.0.1
github.com/matttproud/golang_protobuf_extensions/pbutil github.com/matttproud/golang_protobuf_extensions/pbutil
# github.com/nats-io/nats-server/v2 v2.3.2
## explicit
# github.com/nats-io/nats-streaming-server v0.22.0
## explicit
# github.com/nats-io/nats.go v1.11.1-0.20210623165838-4b75fc59ae30 # github.com/nats-io/nats.go v1.11.1-0.20210623165838-4b75fc59ae30
github.com/nats-io/nats.go github.com/nats-io/nats.go
github.com/nats-io/nats.go/encoders/builtin github.com/nats-io/nats.go/encoders/builtin
@ -35,7 +31,7 @@ github.com/nats-io/nuid
# github.com/nats-io/stan.go v0.9.0 # github.com/nats-io/stan.go v0.9.0
github.com/nats-io/stan.go github.com/nats-io/stan.go
github.com/nats-io/stan.go/pb github.com/nats-io/stan.go/pb
# github.com/openfaas/faas-provider v0.18.6 # github.com/openfaas/faas-provider v0.18.7
## explicit ## explicit
github.com/openfaas/faas-provider/auth github.com/openfaas/faas-provider/auth
github.com/openfaas/faas-provider/types github.com/openfaas/faas-provider/types