Compare commits

...
30 Commits
Author SHA1 Message Date
Sanket SudakeandGitHub f4702fe066 Update chart version to 1.14.0 (#2127)
Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-08-02 12:18:11 +05:30
Sanket SudakeandGitHub fea8dbef1a Remove unused code from Fission (#2136)
Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-07-29 19:15:50 +05:30
Gaurav GahlotandGitHub 6a527d36a8 reading right flag key for rounds (#2135)
Signed-off-by: Gaurav Gahlot <gauravgahlot0107@gmail.com>
2021-07-29 19:13:06 +05:30
Sanket SudakeandGitHub 2292f472c9 Option to keep or remove prefix when router triggers prefix based function (#2133)
Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-07-29 18:50:26 +05:30
Sanket SudakeandGitHub 2aaaeee5a7 Update runc to v1.0.1 (#2134)
Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-07-29 15:57:06 +05:30
Sanket SudakeandGitHub ca161690da Disable analytics in upgrade tests and use latest node env in integration tests (#2128)
Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-07-27 11:24:01 +05:30
Sanket SudakeandGitHub 8bd1a71065 Change Ingress version from v1beta1 to v1 for K8s 1.22+ compatibility (#2124)
* Change Ingress version from v1beta1 to v1 for K8s 1.22+ compatibility

Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>

* Minor fix

Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-07-26 09:50:42 +05:30
Sanket SudakeandGitHub eb72fdc717 Enable race detector for unit tests (#2085)
Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-07-23 13:24:10 +05:30
Sanket SudakeandGitHub a7f819a78e Optimize swagger doc generator for Fission CR types (#2123)
Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-07-22 19:05:37 +05:30
Sanket SudakeandGitHub d4d58e166b Avoid dumping request headers in router (#2122)
Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-07-22 16:19:39 +05:30
Sanket SudakeandGitHub 1d41a198cb Add CRDs link for easy references (#2121)
Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-07-22 12:41:29 +05:30
Sanket SudakeandGitHub 6ced9d03d3 Support to dump request/response in router when debug mode enabled (#2119)
Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-07-21 20:45:08 +05:30
Sanket SudakeandGitHub 19c7616a4b Newdeploy/container function service names should fit in 63 characters (#2117)
* Add error message on deployment provision failure

Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>

* Add 63 characters limit for objects created via newdeploy/container

Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-07-19 15:43:10 +05:30
a82281ad1c containers as functions (#1681)
PR adds a new executortype which supports running containers as functions. New CLI under functions is added to create container as functions.

Co-authored-by: Harsh Thakur <harshthakur9030@gmail.com>
Co-authored-by: Sanket Sudake <sanketsudake@gmail.com>
2021-07-16 18:08:41 +05:30
Sanket SudakeandGitHub f3e1f9df90 Add go pprof support across fission components (#2112)
Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-07-16 13:34:03 +05:30
Sanket SudakeandGitHub c85b9e8cbc Update keda connectors version to latest (#2115)
Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-07-16 09:40:49 +05:30
Sanket SudakeandGitHub ece0475808 Labels and annotation support for functions and environments (#2113)
1. Support for adding labels and annotations to functions & environment via fission CLI through create & update command.
2. Change ensures labels and annotations assigned to the environment would reflect on pods created via executor type pool manager and new deploy.

Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-07-15 17:22:01 +05:30
Sanket SudakeandGitHub 2e0bb9304a Covert maps to sync maps in executor fscache to avoid dirty reads (#2105)
Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-07-08 10:40:17 +05:30
Sanket SudakeandGitHub 74c0142968 Replace controller with generated SharedIndexerInformers (#2103)
Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-07-06 13:56:10 +05:30
Sanket SudakeandGitHub 7c71d90d17 Fix race condition in fetcher readyness check (#2101)
Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-07-06 10:56:06 +05:30
Sanket SudakeandGitHub f86fde81e6 Shared informers (#2092)
* Use shared informers across executor for fission CRs

Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>

* Add informer for configmap and secrets

Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>

* Use shared informers in router

Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>

* Add shared informer for canary config

Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>

* Add sharedinformer for ready pod check

Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>

* Refer informer instead of store in resolver

Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>

* Run informers before executors

Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>

* Add missing canary config handler calls

Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>

* Enable websocket test

Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>

* Run dump collection always

Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-07-05 20:28:26 +05:30
Sanket SudakeandGitHub 43814450e5 Increase timeout for kubectl test (#2100)
Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-07-05 14:57:11 +05:30
Sanket SudakeandGitHub d5077ee816 Updated Go dependencies and added cache in workflows (#2099)
Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-07-04 12:38:27 +05:30
Sanket SudakeandGitHub 2cf2c6979e Use latest release for upgrade tests (#2097)
Fix upgrade CI flow and added checks at places required
2021-07-02 15:48:02 +05:30
Sanket SudakeandGitHub 154fe0d447 Retrieve pod metrics only if metrics server is running and Go lint fixes (#2094)
* Retrieve pod metrics only if metrics server is running

Currently we query pod metrics every 30 sec which floods executor logs,
added check which confirms if metrics server is running then only we start
querying pod metrics for identifying CPU utilization.

Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>

* Fixed couple of typos and misspells with Go CI

Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>

* Remove unnecessary conversions with Go CI

Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-06-28 10:06:32 +05:30
Sanket SudakeandGitHub a493f0117d Move informers,clientset and listers to generated package (#2093)
Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-06-26 11:13:46 +05:30
Sanket SudakeandGitHub cc0400bdd4 Move clientset,listers,informers to pkg/client from pkg/apis (#2090)
* Move clientset,listers,informers to pkg/client from pkg/apis

Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>

* Update golangci-lint version and timeout

Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-06-24 12:07:14 +05:30
Sanket SudakeandGitHub 8b57a035ee Add a separate lint and unit test flow (#2089)
Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-06-22 15:00:58 +05:30
dc57f83d19 Add multi k8s version and os support in ci tests (#2082)
* add multi env and multi k8s support on integraation tests

* test workflow

* test workflow

* test workflow

* remove test branch name

* correct kind action with image and version

* use same kind version as per kind images

* use OS as ubuntu-latest

* fetch upstream

* add .github/workflows to paths

* resolve conflicts

* make kindimage appear first on workflow

Co-authored-by: Sanket Sudake <sanketsudake@gmail.com>
2021-06-22 13:25:55 +05:30
Sanket SudakeandGitHub 611f556cf9 Update changelog (#2088)
Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
2021-06-21 19:22:29 +05:30
175 changed files with 8385 additions and 1829 deletions
+11
View File
@@ -6,11 +6,15 @@ on:
- master
paths:
- '**.go'
- go.mod
- go.sum
pull_request:
branches:
- master
paths:
- '**.go'
- go.mod
- go.sum
schedule:
- cron: "0 0 * * 0"
workflow_dispatch:
@@ -23,6 +27,13 @@ jobs:
- name: Check out code
uses: actions/checkout@v2
- uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
+71
View File
@@ -0,0 +1,71 @@
name: Lint and Unit tests
on:
push:
branches:
- master
paths:
- '**.go'
- go.mod
- go.sum
pull_request:
branches:
- master
paths:
- '**.go'
- go.mod
- go.sum
workflow_dispatch:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.15
uses: actions/setup-go@v2
with:
go-version: 1.15
- name: Check out code
uses: actions/checkout@v2
- uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Verify dependencies
run: |
go mod verify
go mod download
LINT_VERSION=1.41.1
curl -fsSL https://github.com/golangci/golangci-lint/releases/download/v${LINT_VERSION}/golangci-lint-${LINT_VERSION}-linux-amd64.tar.gz | \
tar xz --strip-components 1 --wildcards \*/golangci-lint
mkdir -p bin && mv golangci-lint bin/
- name: Run checks
run: |
STATUS=0
assert-nothing-changed() {
local diff
"$@" >/dev/null || return 1
if ! diff="$(git diff -U1 --color --exit-code)"; then
printf '\e[31mError: running `\e[1m%s\e[22m` results in modifications that you must check into version control:\e[0m\n%s\n\n' "$*" "$diff" >&2
git checkout -- .
STATUS=1
fi
}
assert-nothing-changed go fmt ./...
assert-nothing-changed go mod tidy
bin/golangci-lint run --out-format=github-actions --timeout=5m || STATUS=$?
exit $STATUS
- name: Run unit tests
run: ./hack/runtests.sh
+33 -18
View File
@@ -8,6 +8,8 @@ on:
- '**.go'
- 'charts/**'
- 'test/**'
- go.mod
- go.sum
pull_request:
branches:
- master
@@ -15,13 +17,25 @@ on:
- '**.go'
- 'charts/**'
- 'test/**'
- go.mod
- go.sum
workflow_dispatch:
jobs:
# Job to run change detection
integration-test:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
kindimage : [ 'kindest/node:v1.19.11','kindest/node:v1.20.7', 'kindest/node:v1.21.2' ]
os: [ ubuntu-latest ]
steps:
- name: setup go
uses: actions/setup-go@v2.1.3
with:
go-version: "1.15.12"
- name: Checkout sources
uses: actions/checkout@v2.3.4
@@ -31,10 +45,12 @@ jobs:
repository: fission/examples
path: examples
- name: setup go
uses: actions/setup-go@v2.1.3
- uses: actions/cache@v2
with:
go-version: "1.15.12"
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Helm installation
uses: Azure/setup-helm@v1
@@ -44,24 +60,20 @@ jobs:
- name: Kind Clutser
uses: engineerd/setup-kind@v0.5.0
with:
image: ${{ matrix.kindimage }}
version: v0.11.1
config: kind.yaml
- name: Configuring and testing the Installation
run: |
kubectl cluster-info --context kind-kind
kind get kubeconfig --internal >$HOME/.kube/config
kubectl get nodes
sudo apt-get install -y apache2-utils
- name: Static code analysis
- name: Helm chart lint
run: |
./hack/verify-gofmt.sh
./hack/verify-govet.sh
helm lint charts/fission-all/ charts/fission-core/
- name: Run unit tests
run: ./hack/runtests.sh
- name: Helm update
run: helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
@@ -79,9 +91,8 @@ jobs:
- name: Build and Install Fission CLI
run: |
go build -o fission cmd/fission-cli/main.go
sudo mv fission /usr/local/bin
fission version
sudo make install-fission-cli
sudo chmod +x /usr/local/bin/fission
- name: Port-forward fission components
run: |
@@ -89,18 +100,22 @@ jobs:
kubectl port-forward svc/controller 8889:80 -nfission &
kubectl port-forward svc/nats-streaming 8890:4222 -nfission &
- name: Get fission version
run: |
fission version
- name: Integration tests
run: ./test/kind_CI.sh
- name: Collect Fission Dump
if: ${{ failure() }}
if: ${{ always() }}
run: |
fission support dump
command -v fission && fission support dump
- name: Archive fission dump
if: ${{ failure() }}
uses: actions/upload-artifact@v2
with:
name: fission-support-dump
name: fission-dump
path: fission-dump/*.zip
retention-days: 5
retention-days: 5
+37 -7
View File
@@ -8,6 +8,8 @@ on:
- '**.go'
- 'charts/**'
- 'test/**'
- go.mod
- go.sum
pull_request:
branches:
- master
@@ -15,20 +17,34 @@ on:
- '**.go'
- 'charts/**'
- 'test/**'
- go.mod
- go.sum
workflow_dispatch:
jobs:
upgrade-test:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
kindimage : [ 'kindest/node:v1.19.11' ]
os: [ ubuntu-latest ]
steps:
- name: Checkout action sources
uses: actions/checkout@v2.3.4
- name: Setup go
uses: actions/setup-go@v2.1.3
with:
go-version: '1.15.12'
- name: Checkout action sources
uses: actions/checkout@v2.3.4
- uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Setup Helm
uses: Azure/setup-helm@v1
with:
@@ -37,13 +53,14 @@ jobs:
- name: Setup Kind Clutser
uses: engineerd/setup-kind@v0.5.0
with:
config: kind.yaml
image: ${{ matrix.kindimage }}
version: v0.11.1
- name: Setup kubectl & fetch node information
run: |
kubectl cluster-info --context kind-kind
kind get kubeconfig --internal >$HOME/.kube/config
kubectl get nodes
kubectl get storageclasses.storage.k8s.io
- name: Dump system info
run: |
@@ -64,4 +81,17 @@ jobs:
- name: Test previously created fission objects with new release
run: |
source ./test/upgrade_test/fission_objects.sh test_fission_objects
source ./test/upgrade_test/fission_objects.sh test_fission_objects
- name: Collect Fission Dump
if: ${{ always() }}
run: |
command -v fission && fission support dump
- name: Archive fission dump
if: ${{ failure() }}
uses: actions/upload-artifact@v2
with:
name: fission-dump
path: fission-dump/*.zip
retention-days: 5
+32 -1
View File
@@ -1,7 +1,38 @@
linters:
enable:
# Default linter
- deadcode
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- structcheck
- typecheck
- unused
- varcheck
# Additional linters
- gofmt
- goimports
- misspell
- nakedret
- unconvert
# Enable in future
# - bodyclose
# - dogsled
# - dupl
# - gosec
# - ifshort
# - nilerr
# - prealloc
# - revive
# - unparam
# - wrapcheck
# - gocritic
linters-settings:
errcheck:
ignore: go.uber.org/zap:Sync
goimports:
# put imports beginning with prefix after 3rd-party packages;
# it's a comma-separated list of prefixes
local-prefixes: github.com/trussworks/my-cli-tool
local-prefixes: github.com/fission/fission
+16
View File
@@ -1,5 +1,21 @@
# Changelog
## [v1.13.1](https://github.com/fission/fission/tree/v1.13.1) (2021-06-21)
[Full Changelog](https://github.com/fission/fission/compare/1.13.1...v1.13.1)
## [1.13.1](https://github.com/fission/fission/tree/1.13.1) (2021-06-21)
[Full Changelog](https://github.com/fission/fission/compare/v1.13.0...1.13.1)
**Merged pull requests:**
- Changes for 1.13.1 release [\#2087](https://github.com/fission/fission/pull/2087) ([sanketsudake](https://github.com/sanketsudake))
- Ignore errors from zap logger Sync [\#2086](https://github.com/fission/fission/pull/2086) ([sanketsudake](https://github.com/sanketsudake))
- Correct release name in release script for non helm install [\#2084](https://github.com/fission/fission/pull/2084) ([sanketsudake](https://github.com/sanketsudake))
- Use codeql for code scanning [\#2078](https://github.com/fission/fission/pull/2078) ([sanketsudake](https://github.com/sanketsudake))
- Lint fix [\#2074](https://github.com/fission/fission/pull/2074) ([RealHarshThakur](https://github.com/RealHarshThakur))
## [v1.13.0](https://github.com/fission/fission/tree/v1.13.0) (2021-06-16)
[Full Changelog](https://github.com/fission/fission/compare/v1.12.0...v1.13.0)
+33
View File
@@ -0,0 +1,33 @@
# Profiling Fission with Pprof
Fission uses [net/pprof](https://pkg.go.dev/net/http/pprof) for profiling the code across Fission components.
It would be helpful in identifying performance bottlenecks.
To enable profiling, just set `pprof.enabled` to `true` while installing Fission helm chart.
## Pprof data of component pod
Do port forwarding to port 6060 of the pod,
```sh
kubectl port-forward pod/executor-668dfd7c89-2b2ff 6060:6060
```
Run different commands to get or analyze pprof data,
```sh
go tool pprof http://localhost:6060/debug/pprof/flamegraph
go tool pprof http://localhost:6060/debug/pprof/profile\?seconds\=60
```
You can also analyze with binary to get correct references of source,
```sh
# Download binary from pod
kubectl cp fission/executor-668dfd7c89-2b2ff:/fission-bundle fission-bundle
go tool pprof -http ":49816" fission-bundle http://localhost:49513/debug/pprof
```
You can also download pprof data and visualize/analyze with different compatible tools.
+21 -6
View File
@@ -56,13 +56,22 @@ test-run: code-checks
@rm -f coverage.txt
### Binaries
fission-cli:
%-cli:
@mkdir -p $(BINDIR)
GOOS=$(GOOS) GOARCH=$(GOARCH) $(GO) build \
-gcflags '$(GCFLAGS)' \
-asmflags '$(ASMFLAGS)' \
-ldflags "$(GO_LDFLAGS)" \
-o $(BINDIR)/fission-$(VERSION)-$(GOOS)-$(GOARCH)$(FISSION-CLI-SUFFIX) ./cmd/fission-cli
-o $(BINDIR)/$(subst -cli,,$@)-$(VERSION)-$(GOOS)-$(GOARCH)$(FISSION-CLI-SUFFIX) ./$<
fission-cli: cmd/fission-cli
fission-bundle-cli: cmd/fission-bundle
fetcher-cli: cmd/fetcher
builder-cli: cmd/builder
preupgradechecks-cli: cmd/preupgradechecks
reporter-cli: cmd/reporter
local-bins: fission-cli fission-bundle-cli fetcher-cli builder-cli preupgradechecks-cli reporter-cli
all-fission-cli:
$(MAKE) fission-cli GOOS=windows GOARCH=amd64
@@ -72,7 +81,7 @@ all-fission-cli:
$(MAKE) fission-cli GOOS=darwin GOARCH=amd64
install-fission-cli: fission-cli
mv $(BINDIR)/fission-$(VERSION)-$(GOOS)-$(GOARCH)$(FISSION-CLI-SUFFIX) /usr/local/bin/
mv $(BINDIR)/fission-$(VERSION)-$(GOOS)-$(GOARCH)$(FISSION-CLI-SUFFIX) /usr/local/bin/fission
### Container images
FISSION_IMGS := fission-bundle-multiarch-img \
@@ -103,6 +112,10 @@ reporter-multiarch-img: cmd/reporter/Dockerfile.reporter
--build-arg BUILDVERSION=$(VERSION) \
$(DOCKER_FLAGS) -f $< .
### Codegen
codegen:
@./hack/update-codegen.sh
### CRDs
generate-crds:
controller-gen crd:trivialVersions=false,preserveUnknownFields=false \
@@ -129,8 +142,10 @@ clean:
### Misc
generate-swagger-doc:
@cd pkg/apis/core/v1/tool && ./update-generated-swagger-docs.sh
@./hack/update-swagger-docs.sh
make release:
all-generators: codegen generate-crds generate-swagger-doc
release:
@./hack/release.sh $(VERSION)
@./hack/releas-tag.sh $(VERSION)
@./hack/release-tag.sh $(VERSION)
+2 -2
View File
@@ -1,6 +1,6 @@
apiVersion: v2
name: fission-all
version: 1.13.1
version: 1.14.0
description: Fission is a fast serverless framework for Kubernetes.
keywords:
- fission
@@ -12,7 +12,7 @@ maintainers:
- name: Sanket Sudake
email: sanket@infracloud.io
engine: gotpl
appVersion: 1.13.1
appVersion: 1.14.0
type: application
dependencies:
- name: prometheus
+35 -5
View File
@@ -340,6 +340,8 @@ spec:
value: {{ .Values.traceSamplingRate | default "0.5" | quote }}
- name: DEBUG_ENV
value: {{ .Values.debugEnv | quote }}
- name: PPROF_ENABLED
value: {{ .Values.pprof.enabled | quote }}
- name: POD_NAMESPACE
valueFrom:
fieldRef:
@@ -364,6 +366,10 @@ spec:
ports:
- containerPort: 8888
name: http
{{- if .Values.pprof.enabled }}
- containerPort: 6060
name: pprof
{{- end }}
serviceAccountName: fission-svc
volumes:
- name: config-volume
@@ -432,6 +438,8 @@ spec:
value: {{ .Values.fetcher.resource.mem.limits | quote }}
- name: DEBUG_ENV
value: {{ .Values.debugEnv | quote }}
- name: PPROF_ENABLED
value: {{ .Values.pprof.enabled | quote }}
readinessProbe:
httpGet:
path: "/healthz"
@@ -450,6 +458,10 @@ spec:
name: metrics
- containerPort: 8888
name: http
{{- if .Values.pprof.enabled }}
- containerPort: 6060
name: pprof
{{- end }}
serviceAccountName: fission-svc
{{- if .Values.extraCoreComponentPodConfig }}
{{ toYaml .Values.extraCoreComponentPodConfig | indent 6 -}}
@@ -506,6 +518,8 @@ spec:
value: {{ .Values.fetcher.resource.mem.limits | quote }}
- name: DEBUG_ENV
value: {{ .Values.debugEnv | quote }}
- name: PPROF_ENABLED
value: {{ .Values.pprof.enabled | quote }}
serviceAccountName: fission-svc
{{- if .Values.extraCoreComponentPodConfig }}
{{ toYaml .Values.extraCoreComponentPodConfig | indent 6 -}}
@@ -542,6 +556,8 @@ spec:
value: {{ .Values.traceSamplingRate | default "0.5" | quote }}
- name: DEBUG_ENV
value: {{ .Values.debugEnv | quote }}
- name: PPROF_ENABLED
value: {{ .Values.pprof.enabled | quote }}
serviceAccountName: fission-svc
{{- if .Values.extraCoreComponentPodConfig }}
{{ toYaml .Values.extraCoreComponentPodConfig | indent 6 -}}
@@ -615,7 +631,7 @@ metadata:
kubernetes.io/cluster-service: 'true'
kubernetes.io/name: heapster
spec:
type: ClusterIP
type: ClusterIP
ports:
- port: 80
targetPort: 8082
@@ -644,7 +660,7 @@ spec:
- name: heapster
image: gcr.io/google_containers/heapster-amd64:v1.5.0
imagePullPolicy: {{ .Values.pullPolicy }}
command:
command:
- /heapster
- --source=kubernetes:https://kubernetes.default
serviceAccountName: {{ .Release.Namespace }}/fission-svc
@@ -676,6 +692,8 @@ spec:
env:
- name: DEBUG_ENV
value: {{ .Values.debugEnv | quote }}
- name: PPROF_ENABLED
value: {{ .Values.pprof.enabled | quote }}
serviceAccountName: fission-svc
{{- if .Values.extraCoreComponentPodConfig }}
{{ toYaml .Values.extraCoreComponentPodConfig | indent 6 -}}
@@ -815,6 +833,8 @@ spec:
value: {{ .Values.traceSamplingRate | default "0.5" | quote }}
- name: DEBUG_ENV
value: {{ .Values.debugEnv | quote }}
- name: PPROF_ENABLED
value: {{ .Values.pprof.enabled | quote }}
serviceAccountName: fission-svc
{{- if .Values.extraCoreComponentPodConfig }}
{{ toYaml .Values.extraCoreComponentPodConfig | indent 6 -}}
@@ -866,6 +886,8 @@ spec:
value: {{ .Values.traceSamplingRate | default "0.5" | quote }}
- name: DEBUG_ENV
value: {{ .Values.debugEnv | quote }}
- name: PPROF_ENABLED
value: {{ .Values.pprof.enabled | quote }}
# TLS authentication is TLS with authentication (2 way)
# More info: https://docs.confluent.io/current/kafka/authentication_ssl.html#ssl-overview
{{- if .Values.kafka.authentication.tls.enabled }}
@@ -878,7 +900,7 @@ spec:
volumeMounts:
- name: kafka-secrets
mountPath: /etc/fission/secrets
{{- end }}
{{- end }}
serviceAccountName: fission-svc
{{- if .Values.kafka.authentication.tls.enabled }}
volumes:
@@ -891,7 +913,7 @@ spec:
{{- if .Values.kafka.authentication.tls.enabled }}
apiVersion: v1
kind: Secret
metadata:
metadata:
name: mqtrigger-kafka-secrets
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
@@ -953,7 +975,7 @@ spec:
- name: TRACE_JAEGER_COLLECTOR_ENDPOINT
value: "{{ .Values.traceCollectorEndpoint }}"
- name: TRACING_SAMPLING_RATE
value: {{ .Values.traceSamplingRate | default "0.5" | quote }}
value: {{ .Values.traceSamplingRate | default "0.5" | quote }}
- name: MESSAGE_QUEUE_TYPE
value: azure-storage-queue
- name: AZURE_STORAGE_ACCOUNT_NAME
@@ -965,6 +987,8 @@ spec:
key: key
- name: DEBUG_ENV
value: {{ .Values.debugEnv | quote }}
- name: PPROF_ENABLED
value: {{ .Values.pprof.enabled | quote }}
serviceAccountName: fission-svc
{{- if .Values.extraCoreComponentPodConfig }}
{{ toYaml .Values.extraCoreComponentPodConfig | indent 6 -}}
@@ -1010,6 +1034,8 @@ spec:
value: "{{.Values.pruneInterval}}"
- name: DEBUG_ENV
value: {{ .Values.debugEnv | quote }}
- name: PPROF_ENABLED
value: {{ .Values.pprof.enabled | quote }}
{{- if and (.Values.persistence.enabled) (eq (.Values.persistence.storageType | default "local") "s3") }}
- name: STORAGE_S3_ENDPOINT
value: {{ .Values.persistence.s3.endPoint }}
@@ -1045,6 +1071,10 @@ spec:
ports:
- containerPort: 8000
name: http
{{- if .Values.pprof.enabled }}
- containerPort: 6060
name: pprof
{{- end }}
serviceAccountName: fission-svc
{{- if and (.Values.persistence.enabled) (ne (.Values.persistence.storageType | default "local") "s3") }}
volumes:
+6
View File
@@ -63,6 +63,8 @@ spec:
value: {{ .Values.router.useEncodedPath | default false | quote }}
- name: DEBUG_ENV
value: {{ .Values.debugEnv | quote }}
- name: PPROF_ENABLED
value: {{ .Values.pprof.enabled | quote }}
- name: DISPLAY_ACCESS_LOG
value: {{ .Values.router.displayAccessLog | default false | quote }}
resources:
@@ -85,6 +87,10 @@ spec:
name: metrics
- containerPort: 8888
name: http
{{- if .Values.pprof.enabled }}
- containerPort: 6060
name: pprof
{{- end }}
serviceAccountName: fission-svc
{{- if .Values.router.extraCoreComponentPodConfig }}
{{ toYaml .Values.router.extraCoreComponentPodConfig | indent 6 -}}
+12 -8
View File
@@ -20,7 +20,7 @@ image: fission/fission-bundle
pullPolicy: IfNotPresent
## Fission image version
imageTag: 1.13.1
imageTag: 1.14.0
## Port at which Fission controller service should be exposed
controllerPort: 31313
@@ -50,7 +50,7 @@ fetcher:
## Fetcher repository
image: fission/fetcher
## Fetcher image version
imageTag: 1.13.1
imageTag: 1.14.0
## Fetcher is only for to downloading or uploading archive.
## Normally, you don't need to change the value here, unless necessary.
@@ -350,19 +350,23 @@ mqt_keda:
connector_images:
kafka:
image: fission/keda-kafka-http-connector
tag: v0.6
tag: v0.7
rabbitmq:
image: fission/keda-rabbitmq-http-connector
tag: v0.6
tag: v0.7
awskinesis:
image: fission/keda-aws-kinesis-http-connector
tag: v0.6
tag: v0.7
aws_sqs:
image: fission/keda-aws-sqs-http-connector
tag: v0.6
tag: v0.7
nats_steaming:
image: fission/keda-nats-streaming-http-connector
tag: v0.6
tag: v0.7
gcp_pub_sub:
image: fission/keda-gcp-pubsub-http-connector
tag: v0.1
tag: v0.2
## Enable Pprof based profiling
pprof:
enabled: false
+2 -2
View File
@@ -1,6 +1,6 @@
apiVersion: v2
name: fission-core
version: 1.13.1
version: 1.14.0
description: Fission is a fast serverless framework for Kubernetes.
keywords:
- fission
@@ -12,7 +12,7 @@ maintainers:
- name: Sanket Sudake
email: sanket@infracloud.io
engine: gotpl
appVersion: 1.13.1
appVersion: 1.14.0
type: application
dependencies:
- name: prometheus
+8 -8
View File
@@ -17,7 +17,7 @@ repository: index.docker.io
image: fission/fission-bundle
## Fission image version
imageTag: 1.13.1
imageTag: 1.14.0
## Image pull policy
pullPolicy: IfNotPresent
@@ -43,7 +43,7 @@ fetcher:
## Fetcher repository
image: fission/fetcher
## Fetcher image version
imageTag: 1.13.1
imageTag: 1.14.0
## Fetcher is only for to downloading or uploading archive.
## Normally, you don't need to change the value here, unless necessary.
@@ -239,19 +239,19 @@ mqt_keda:
connector_images:
kafka:
image: fission/keda-kafka-http-connector
tag: v0.6
tag: v0.7
rabbitmq:
image: fission/keda-rabbitmq-http-connector
tag: v0.6
tag: v0.7
awskinesis:
image: fission/keda-aws-kinesis-http-connector
tag: v0.6
tag: v0.7
aws_sqs:
image: fission/keda-aws-sqs-http-connector
tag: v0.6
tag: v0.7
nats_steaming:
image: fission/keda-nats-streaming-http-connector
tag: v0.6
tag: v0.7
gcp_pub_sub:
image: fission/keda-gcp-pubsub-http-connector
tag: v0.1
tag: v0.2
+3
View File
@@ -24,10 +24,13 @@ import (
"go.uber.org/zap/zapcore"
"github.com/fission/fission/cmd/builder/app"
"github.com/fission/fission/pkg/utils/profile"
)
// Usage: builder <shared volume path>
func main() {
profile.ProfileIfEnabled()
config := zap.NewProductionConfig()
config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
logger, err := config.Build()
+12 -12
View File
@@ -24,6 +24,7 @@ import (
"log"
"net/http"
"os"
"sync/atomic"
"contrib.go.opencensus.io/exporter/jaeger"
"go.opencensus.io/plugin/ochttp"
@@ -33,6 +34,10 @@ import (
"github.com/fission/fission/pkg/fetcher"
)
var (
readyToServe uint32
)
func registerTraceExporter(collectorEndpoint string) error {
if collectorEndpoint == "" {
return nil
@@ -80,17 +85,17 @@ func Run(logger *zap.Logger) {
}
}
if err := registerTraceExporter(*collectorEndpoint); err != nil {
logger.Fatal("could not register trace exporter", zap.Error(err), zap.String("collector_endpoint", *collectorEndpoint))
}
go func() {
if err := registerTraceExporter(*collectorEndpoint); err != nil {
logger.Fatal("could not register trace exporter", zap.Error(err), zap.String("collector_endpoint", *collectorEndpoint))
}
}()
f, err := fetcher.MakeFetcher(logger, dir, *secretDir, *configDir)
if err != nil {
logger.Fatal("error making fetcher", zap.Error(err))
}
readyToServe := false
// do specialization in other goroutine to prevent blocking in newdeploy
go func() {
if *specializeOnStart {
@@ -106,9 +111,8 @@ func Run(logger *zap.Logger) {
if err != nil {
logger.Fatal("error specializing function pod", zap.Error(err))
}
readyToServe = true
}
atomic.StoreUint32(&readyToServe, 1)
}()
mux := http.NewServeMux()
@@ -120,7 +124,7 @@ func Run(logger *zap.Logger) {
mux.HandleFunc("/wsevent/end", f.WsEndHandler)
readinessHandler := func(w http.ResponseWriter, r *http.Request) {
if !*specializeOnStart || readyToServe {
if atomic.LoadUint32(&readyToServe) == 1 {
w.WriteHeader(http.StatusOK)
} else {
w.WriteHeader(http.StatusServiceUnavailable)
@@ -132,10 +136,6 @@ func Run(logger *zap.Logger) {
w.WriteHeader(http.StatusOK)
})
// For backward compatibility
// TODO: remove this path in future
mux.HandleFunc("/readniess-healthz", readinessHandler)
logger.Info("fetcher ready to receive requests")
err = http.ListenAndServe(":8000", &ochttp.Handler{
Handler: mux,
+3
View File
@@ -23,10 +23,13 @@ import (
"go.uber.org/zap/zapcore"
"github.com/fission/fission/cmd/fetcher/app"
"github.com/fission/fission/pkg/utils/profile"
)
// Usage: fetcher <shared volume path>
func main() {
profile.ProfileIfEnabled()
config := zap.NewProductionConfig()
config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
logger, err := config.Build()
+2
View File
@@ -40,6 +40,7 @@ import (
"github.com/fission/fission/pkg/router"
"github.com/fission/fission/pkg/storagesvc"
"github.com/fission/fission/pkg/timer"
"github.com/fission/fission/pkg/utils/profile"
)
func runController(logger *zap.Logger, port int) {
@@ -237,6 +238,7 @@ Options:
--builderMgr Start builder manager.
--version Print version information
`
profile.ProfileIfEnabled()
var logger *zap.Logger
var config zap.Config
+2 -1
View File
@@ -99,7 +99,7 @@ func (client *PreUpgradeTaskClient) LatestSchemaApplied() error {
return errors.New("Could not get the Function CRD")
}
// Any new field added in Function spec can be checked here provided the substring matches the description in CRD Validation of the field
if !strings.Contains(funcCRD.Spec.String(), "RequestsPerPod") || !strings.Contains(funcCRD.Spec.String(), "OnceOnly") {
if !strings.Contains(funcCRD.Spec.String(), "RequestsPerPod") || !strings.Contains(funcCRD.Spec.String(), "OnceOnly") || !strings.Contains(funcCRD.Spec.String(), "PodSpec") {
return errors.New("Apply the newer CRDs before upgrading")
}
@@ -112,6 +112,7 @@ func (client *PreUpgradeTaskClient) LatestSchemaApplied() error {
if !strings.Contains(mqtCRD.Spec.String(), "PodSpec") {
return errors.New("Apply the newer CRDs before upgrading")
}
return nil
}
+2 -1
View File
@@ -18,8 +18,9 @@ package app
import (
"log"
"github.com/fission/fission/pkg/tracker"
"github.com/spf13/cobra"
"github.com/fission/fission/pkg/tracker"
)
func eventCommandHandler(cmd *cobra.Command, args []string) error {
+11
View File
@@ -0,0 +1,11 @@
# Custom Resource Definitions Reference
- [CanaryConfig](https://doc.crds.dev/github.com/fission/fission/fission.io/CanaryConfig/v1)
- [Environment](https://doc.crds.dev/github.com/fission/fission/fission.io/Environment/v1)
- [Function](https://doc.crds.dev/github.com/fission/fission/fission.io/Function/v1)
- [HTTPTrigger](https://doc.crds.dev/github.com/fission/fission/fission.io/HTTPTrigger/v1)
- [KubernetesWatchTrigger](https://doc.crds.dev/github.com/fission/fission/fission.io/KubernetesWatchTrigger/v1)
- [MessageQueueTrigger](https://doc.crds.dev/github.com/fission/fission/fission.io/MessageQueueTrigger/v1)
- [Package](https://doc.crds.dev/github.com/fission/fission/fission.io/Package/v1)
- [TimeTrigger](https://doc.crds.dev/github.com/fission/fission/fission.io/TimeTrigger/v1)
File diff suppressed because it is too large Load Diff
+3
View File
@@ -76,6 +76,9 @@ spec:
description: TLS is for user to specify a Secret that contains TLS key and certificate. The domain name in the key and crt must match the value of Host field.
type: string
type: object
keepPrefix:
description: When function is exposed with Prefix based path, keepPrefix decides whether to keep or trim prefix in URL while invoking function.
type: boolean
method:
description: Use Methods instead of Method. This field is going to be deprecated in a future release HTTP method to access a function.
type: string
-7
View File
@@ -1,7 +0,0 @@
# Git hooks
* pre-push: Check, build and test the changes.
```bash
$ cp githooks/* .git/hooks/
```
-8
View File
@@ -1,8 +0,0 @@
#!/bin/bash
set -e
ROOT=`realpath $(dirname $0)/../..`
pushd $ROOT
make
popd
+20 -21
View File
@@ -3,12 +3,12 @@ module github.com/fission/fission
go 1.15
require (
contrib.go.opencensus.io/exporter/jaeger v0.1.0
contrib.go.opencensus.io/exporter/jaeger v0.2.1
github.com/Azure/azure-sdk-for-go v12.4.0-beta+incompatible
github.com/Azure/go-autorest/autorest v0.11.18 // indirect
github.com/Microsoft/go-winio v0.4.16 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/Shopify/sarama v1.23.1
github.com/Shopify/sarama v1.29.1
github.com/aws/aws-sdk-go v1.36.33 // indirect
github.com/blend/go-sdk v1.20210116.5 // indirect
github.com/bsm/sarama-cluster v2.1.15+incompatible
@@ -23,48 +23,47 @@ require (
github.com/dustin/go-humanize v1.0.0
github.com/emicklei/go-restful v2.9.6+incompatible
github.com/emicklei/go-restful-openapi v1.2.0
github.com/fatih/color v1.12.0
github.com/fsnotify/fsnotify v1.4.9
github.com/ghodss/yaml v1.0.0
github.com/go-git/go-git/v5 v5.2.0
github.com/go-ini/ini v1.62.0 // indirect
github.com/go-openapi/spec v0.19.3
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/gorilla/mux v1.7.0
github.com/gorilla/mux v1.8.0
github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect
github.com/graymeta/stow v0.0.0-20180719215413-7b5498c561bb
github.com/hashicorp/go-multierror v1.0.0
github.com/imdario/mergo v0.3.9
github.com/hashicorp/go-multierror v1.1.1
github.com/imdario/mergo v0.3.12
github.com/influxdata/influxdb v1.2.0
github.com/life1347/color v1.7.0
github.com/marstr/guid v1.1.0 // indirect
github.com/mholt/archiver v0.0.0-20180417220235-e4ef56d48eb0
github.com/minio/minio-go v6.0.14+incompatible
github.com/nats-io/nats-streaming-server v0.17.0
github.com/nats-io/nats.go v1.9.1
github.com/nats-io/stan.go v0.6.0
github.com/nats-io/nats-streaming-server v0.22.0
github.com/nats-io/nats.go v1.11.0
github.com/nats-io/stan.go v0.9.0
github.com/nwaples/rardecode v1.1.0 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/opencontainers/runc v0.1.1 // indirect
github.com/opencontainers/runc v1.0.1 // indirect
github.com/ory/dockertest v3.3.5+incompatible
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.7.1
github.com/prometheus/common v0.10.0
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967
github.com/satori/go.uuid v1.2.0
github.com/spf13/cobra v1.1.1
github.com/spf13/cobra v1.2.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.6.1
github.com/stretchr/testify v1.7.0
github.com/ulikunitz/xz v0.5.9 // indirect
github.com/wcharczuk/go-chart v2.0.1+incompatible
go.opencensus.io v0.22.4
go.uber.org/zap v1.10.0
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb
go.opencensus.io v0.23.0
go.uber.org/zap v1.18.1
golang.org/x/net v0.0.0-20210614182718-04defd469f4e
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect
k8s.io/api v0.19.2
k8s.io/apiextensions-apiserver v0.19.2
k8s.io/apimachinery v0.19.2
k8s.io/client-go v0.19.2
k8s.io/api v0.19.12
k8s.io/apiextensions-apiserver v0.19.12
k8s.io/apimachinery v0.19.12
k8s.io/client-go v0.19.12
k8s.io/klog v1.0.0
k8s.io/metrics v0.19.2
k8s.io/metrics v0.19.12
)
+305 -104
View File
File diff suppressed because it is too large Load Diff
-5
View File
@@ -1,5 +0,0 @@
FROM golang:1.16.3
ENV GO111MODULE on
RUN mkdir -p /go/src/github.com/fission
RUN wget -qO- https://download.docker.com/linux/static/stable/x86_64/docker-17.03.0-ce.tgz | tar xvz -C /usr/local/bin/ --strip 1
RUN wget -qO- https://get.helm.sh/helm-v3.3.0-linux-amd64.tar.gz | tar xvz -C /usr/local/bin/ --strip 1
-1
View File
@@ -1 +0,0 @@
kubectl get crds -o custom-columns=:metadata.name | grep 'fission.io' | xargs kubectl delete crds
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
API_GROUP_VERSIONS="
core/v1
"
API_PACKAGES="
"
+1 -1
View File
@@ -31,7 +31,7 @@ echo "" > coverage.txt
# The executor unit test only works with NodePort-type services for
# now. So disable it for our travis ci tests except some partial tests.
for d in $(go list ./... | grep -v '/vendor/' | grep -v 'examples/go' | grep -v executor | grep -v 'benchmark') github.com/fission/fission/pkg/executor/util; do
go test -v -coverprofile=profile.out -covermode=atomic $d
go test -race -v -coverprofile=profile.out -covermode=atomic $d
if [ -f profile.out ]; then
cat profile.out >> coverage.txt
rm profile.out
-93
View File
@@ -1,93 +0,0 @@
#!/bin/bash
#
# Download kubectl, save kubeconfig, and ensure we can access the test cluster
#
set -e
TOOL_DIR=$HOME/tool
if [ ! -d $TOOL_DIR ]
then
mkdir -p $TOOL_DIR
fi
# Get staticcheck
STATICCHECK_VERSION=2019.2.3
if [ ! -f $TOOL_DIR/staticcheck ] || (staticcheck -version | grep -v $STATICCHECK_VERSION)
then
curl -LO https://github.com/dominikh/go-tools/releases/download/${STATICCHECK_VERSION}/staticcheck_linux_amd64.tar.gz
tar xzvf staticcheck_linux_amd64.tar.gz
mv staticcheck/staticcheck $TOOL_DIR/staticcheck
fi
K8SCLI_DIR=$HOME/k8scli
if [ ! -d $K8SCLI_DIR ]
then
mkdir -p $K8SCLI_DIR
fi
# Get helm
HELM_VERSION=3.3.0
if [ ! -f $K8SCLI_DIR/helm ] || (helm version --client | grep -v $HELM_VERSION)
then
curl -LO https://get.helm.sh/helm-v3.3.0-linux-amd64.tar.gz
tar xzvf helm-*.tar.gz
mv linux-amd64/helm $K8SCLI_DIR/helm
fi
# If we don't have gcloud credentials, bail out of these tests.
if [ -z "$FISSION_CI_SERVICE_ACCOUNT" ]
then
echo "Skipping tests, no cluster credentials"
exit 0
fi
# Get kubectl
if [ ! -f $K8SCLI_DIR/kubectl ]
then
curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
chmod +x ./kubectl
mv kubectl $K8SCLI_DIR/kubectl
fi
mkdir ${HOME}/.kube
# echo $KUBECONFIG_CONTENTS | base64 -D - > ${HOME}/.kube/config
# kubectl version
# gcloud stuff
# https://stackoverflow.com/questions/38762590/how-to-install-google-cloud-sdk-on-travis
if [ ! -d "${HOME}/google-cloud-sdk/bin" ]
then
rm -rf $HOME/google-cloud-sdk
export CLOUDSDK_CORE_DISABLE_PROMPTS=1
curl https://sdk.cloud.google.com | bash
fi
# ensure we have the gcloud binary
gcloud version
# get gcloud credentials
echo $FISSION_CI_SERVICE_ACCOUNT | base64 -d - > ${HOME}/gcloud-service-key.json
gcloud auth activate-service-account --key-file ${HOME}/gcloud-service-key.json
# get kube config
gcloud container clusters get-credentials fission-ci --zone us-central1-a --project $GKE_PROJECT_NAME
# remove gcloud creds
unset FISSION_CI_SERVICE_ACCOUNT
rm ${HOME}/gcloud-service-key.json
# does it work?
if [ ! -f ${HOME}/.kube/config ]
then
echo "Missing kubeconfig"
exit 1
fi
kubectl get node
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
if [ ! -d "../code-generator" ]; then
echo "Please get code-generator from fission org"
exit 1
fi
SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ../code-generator)}
bash "${CODEGEN_PKG}"/generate-groups.sh "deepcopy,client,informer,lister" \
github.com/fission/fission/pkg/generated \
github.com/fission/fission/pkg/apis \
"core:v1" \
--output-base "$(dirname "${BASH_SOURCE[0]}")/../../../.." \
--go-header-file "$(dirname "${BASH_SOURCE[0]}")/boilerplate.txt"
+56
View File
@@ -0,0 +1,56 @@
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
source "$(dirname "${BASH_SOURCE}")/lib/init.sh"
SCRIPT_ROOT=$(dirname ${BASH_SOURCE})/..
# Generates types_swagger_doc_generated file for the given group version.
# $1: Name of the group version
# $2: Path to the directory where types.go for that group version exists. This
# is the directory where the file will be generated.
kube::swagger::gen_types_swagger_doc() {
local group_version=$1
local gv_dir=$2
local TMPFILE="${TMPDIR:-/tmp}/zz_generated.swagger_doc_generated.$(date +%s).go"
echo "Generating swagger type docs for ${group_version} at ${gv_dir}"
sed 's/YEAR/2017/' hack/boilerplate.txt > "$TMPFILE"
echo "package ${group_version##*/}" >> "$TMPFILE"
cat >> "$TMPFILE" <<EOF
// This file contains a collection of methods that can be used from go-restful to
// generate Swagger API documentation for its models. Please read this PR for more
// information on the implementation: https://github.com/emicklei/go-restful/pull/215
//
// TODOs are ignored from the parser (e.g. TODO(andronat):... || TODO:...) if and only if
// they are on one line! For multiple line or blocks that you want to ignore use ---.
// Any context after a --- is ignored.
//
// Those methods can be generated by using hack/update-swagger-docs.sh
// AUTO-GENERATED FUNCTIONS START HERE
EOF
go run tools/genswaggertypedocs/swagger_type_docs.go -s \
${gv_dir}/types*.go \
-f - \
>> "$TMPFILE"
echo "// AUTO-GENERATED FUNCTIONS END HERE" >> "$TMPFILE"
gofmt -w -s "$TMPFILE"
mv "$TMPFILE" ""${gv_dir}"/zz_generated.swagger_doc_generated.go"
}
util::group-version-to-pkg-path() {
local group_version="$1"
echo "pkg/apis/${group_version}"
}
for gv in ${API_GROUP_VERSIONS}; do
rm -f "${SCRIPT_ROOT}/${gv}/zz_generated.swagger_doc_generated.go"
util::group-version-to-pkg-path "${gv}"
kube::swagger::gen_types_swagger_doc "${gv}" "$(util::group-version-to-pkg-path "${gv}")"
done
-21
View File
@@ -1,21 +0,0 @@
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
find_files() {
find . -not \( \
\( \
-wholename '*/vendor/*' \
\) -prune \
\) -name '*.go'
}
GOFMT="gofmt -s"
bad_files=$(find_files | xargs $GOFMT -l)
if [[ -n "${bad_files}" ]]; then
echo "!!! '$GOFMT' needs to be run on the following files: "
echo "${bad_files}"
exit 1
fi
-7
View File
@@ -1,7 +0,0 @@
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
go vet -v $(go list ./...| grep -v "vendor" | grep -v "examples" | grep -v "genclient" | grep -v "demos" | grep -v "test")
-7
View File
@@ -1,7 +0,0 @@
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
go list ./...| grep -v vendor | grep -v "examples" | grep -v "demos" | grep -v "genclient" | grep -v "test" | xargs -I@ staticcheck @
+1 -2
View File
@@ -8,7 +8,6 @@ kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
image: kindest/node:v1.19.11
kubeadmConfigPatches:
- |
kind: InitConfiguration
@@ -22,4 +21,4 @@ nodes:
protocol: TCP
- containerPort: 443
hostPort: 443
protocol: TCP
protocol: TCP
+3 -9
View File
@@ -1,18 +1,12 @@
# Fission CRD generation
* Clone https://github.com/fission/code-generator to generate fission CRD object deepcopy and client methods.
* Clone [code-generator](https://github.com/fission/code-generator) to generate fission CRD object deepcopy and client methods.
* MUST run code-generator in the fission root directory.
``` bash
$ cd $GOPATH/src/github.com/fission/fission/
$ bash $GOPATH/src/k8s.io/code-generator/generate-groups.sh \
all \
github.com/fission/fission/pkg/apis/genclient \
github.com/fission/fission/pkg/apis \
"core:v1" \
--go-header-file $GOPATH/src/github.com/fission/fission/pkg/apis/boilerplate.txt
$ make codgen
```
# Reference
## Reference
* https://blog.openshift.com/kubernetes-deep-dive-code-generation-customresources/
+1
View File
@@ -55,6 +55,7 @@ const (
const (
ExecutorTypePoolmgr ExecutorType = "poolmgr"
ExecutorTypeNewdeploy ExecutorType = "newdeploy"
ExecutorTypeContainer ExecutorType = "container"
)
const (
-7
View File
@@ -1,7 +0,0 @@
# How to update swagger (OpenAPI) struct description
Run `update-generated-swagger-docs.sh` and it will parse all comments in `types.go`.
```bash
./update-generated-swagger-docs.sh
```
-67
View File
@@ -1,67 +0,0 @@
#!/usr/bin/env bash
# Copyright 2016 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Please refer https://github.com/kubernetes/kubernetes/tree/master/hack for original file
#
# Contains swagger related util functions.
#
set -o errexit
set -o nounset
set -o pipefail
# Generates types_swagger_doc_generated file for the given group version.
# $1: Name of the group version
# $2: Path to the directory where types.go for that group version exists. This
# is the directory where the file will be generated.
kube::swagger::gen_types_swagger_doc() {
local group_version=$1
local gv_dir=$2
local TMPFILE
TMPFILE="${TMPDIR:-/tmp}/types_swagger_doc_generated.$(date +%s).go"
echo "Generating swagger type docs for ${group_version} at ${gv_dir}"
{
echo -e "$(cat boilerplate.generatego.txt)\n"
echo "package ${group_version##*/}"
cat <<EOF
// This file contains a collection of methods that can be used from go-restful to
// generate Swagger API documentation for its models. Please read this PR for more
// information on the implementation: https://github.com/emicklei/go-restful/pull/215
//
// TODOs are ignored from the parser (e.g. TODO(andronat):... || TODO:...) if and only if
// they are on one line! For multiple line or blocks that you want to ignore use ---.
// Any context after a --- is ignored.
//
// Those methods can be generated by using hack/update-generated-swagger-docs.sh
// AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT.
EOF
} > "${TMPFILE}"
go run ./swagger_type_docs.go -s \
"${gv_dir}/types.go" \
-f - \
>> "${TMPFILE}"
echo "// AUTO-GENERATED FUNCTIONS END HERE" >> "${TMPFILE}"
gofmt -w -s "${TMPFILE}"
mv "${TMPFILE}" "${gv_dir}/types_swagger_doc_generated.go"
}
@@ -1,39 +0,0 @@
#!/usr/bin/env bash
# Copyright 2015 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Generates `types_swagger_doc_generated.go` files for API group
# versions. That file contains functions on API structs that return
# the comments that should be surfaced for the corresponding API type
# in our API docs.
#
# Please refer https://github.com/kubernetes/kubernetes/tree/master/hack for original file
#
set -o errexit
set -o nounset
set -o pipefail
FISSION_CRD_VERSION=v1
source "swagger.sh"
# To avoid compile errors, remove the currently existing files.
for group_version in "${FISSION_CRD_VERSION}"; do
kube::swagger::gen_types_swagger_doc "${group_version}" ../../${FISSION_CRD_VERSION}
done
+11
View File
@@ -409,6 +409,11 @@ type (
// This is optional. If not specified default value will be taken as false
// +optional
OnceOnly bool `json:"onceOnly,omitempty"`
// Podspec specifies podspec to use for executor type container based functions
// Different arguments mentioned for container based function are populated inside a pod.
// +optional
PodSpec *apiv1.PodSpec `json:"podspec,omitempty"`
}
// InvokeStrategy is a set of controls over how the function executes.
@@ -450,6 +455,7 @@ type (
// Available value:
// - poolmgr
// - newdeploy
// - container
// +optional
ExecutorType ExecutorType `json:"ExecutorType"`
@@ -658,6 +664,11 @@ type (
// +optional
Prefix *string `json:"prefix,omitempty"`
// When function is exposed with Prefix based path,
// keepPrefix decides whether to keep or trim prefix in URL while invoking function.
// +optional
KeepPrefix bool `json:"keepPrefix,omitempty"`
// Use Methods instead of Method. This field is going to be deprecated in a future release
// HTTP method to access a function.
// +optional
+5 -1
View File
@@ -274,6 +274,10 @@ func (spec FunctionSpec) Validate() error {
result = multierror.Append(result, spec.InvokeStrategy.Validate())
}
if spec.InvokeStrategy.ExecutionStrategy.ExecutorType == ExecutorTypeContainer && spec.PodSpec == nil {
result = multierror.Append(result, MakeValidationErr(ErrorInvalidObject, "FunctionSpec.PodSpec", "", "executor type container requires a pod spec"))
}
// TODO Add below validation warning
/*if spec.FunctionTimeout <= 0 {
result = multierror.Append(result, MakeValidationErr(ErrorInvalidValue, "FunctionTimeout value", spec.FunctionTimeout, "not a valid value. Should always be more than 0"))
@@ -300,7 +304,7 @@ func (es ExecutionStrategy) Validate() error {
result := &multierror.Error{}
switch es.ExecutorType {
case ExecutorTypeNewdeploy, ExecutorTypePoolmgr: // no op
case ExecutorTypeNewdeploy, ExecutorTypePoolmgr, ExecutorTypeContainer: // no op
default:
result = multierror.Append(result, MakeValidationErr(ErrorUnsupportedType, "ExecutionStrategy.ExecutorType", es.ExecutorType, "not a valid executor type"))
}
@@ -431,6 +431,11 @@ func (in *FunctionSpec) DeepCopyInto(out *FunctionSpec) {
*out = new(int)
**out = **in
}
if in.PodSpec != nil {
in, out := &in.PodSpec, &out.PodSpec
*out = new(corev1.PodSpec)
(*in).DeepCopyInto(*out)
}
return
}
@@ -1,5 +1,5 @@
/*
Copyright 2019 The Fission Authors.
Copyright The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,7 +13,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
// This file contains a collection of methods that can be used from go-restful to
@@ -24,9 +23,8 @@ package v1
// they are on one line! For multiple line or blocks that you want to ignore use ---.
// Any context after a --- is ignored.
//
// Those methods can be generated by using hack/update-generated-swagger-docs.sh
// AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT.
// Those methods can be generated by using hack/update-swagger-docs.sh
// AUTO-GENERATED FUNCTIONS START HERE
var map_Archive = map[string]string{
"": "Archive contains or references a collection of source or binary files.",
"type": "Type defines how the package is specified: literal or URL. Available value:\n - literal\n - url",
@@ -149,7 +147,7 @@ func (EnvironmentSpec) SwaggerDoc() map[string]string {
var map_ExecutionStrategy = map[string]string{
"": "ExecutionStrategy specifies low-level parameters for function execution, such as the number of instances.\n\nMinScale affects the cold start behavior for a function. If MinScale is 0 then the deployment is created on first invocation of function and is good for requests of asynchronous nature. If MinScale is greater than 0 then MinScale number of pods are created at the time of creation of function. This ensures faster response during first invocation at the cost of consuming resources.\n\nMaxScale is the maximum number of pods that function will scale to based on TargetCPUPercent and resources allocated to the function pod.",
"ExecutorType": "ExecutorType is the executor type of a function used. Defaults to \"poolmgr\".\n\nAvailable value:\n - poolmgr\n - newdeploy",
"ExecutorType": "ExecutorType is the executor type of a function used. Defaults to \"poolmgr\".\n\nAvailable value:\n - poolmgr\n - newdeploy\n - container",
"MinScale": "This is only for newdeploy to set up minimum replicas of deployment.",
"MaxScale": "This is only for newdeploy to set up maximum replicas of deployment.",
"TargetCPUPercent": "This is only for newdeploy to set up target CPU utilization of HPA.",
@@ -210,6 +208,7 @@ var map_FunctionSpec = map[string]string{
"concurrency": "Maximum number of pods to be specialized which will serve requests This is optional. If not specified default value will be taken as 500",
"requestsPerPod": "RequestsPerPod indicates the maximum number of concurrent requests that can be served by a specialized pod This is optional. If not specified default value will be taken as 1",
"onceOnly": "OnceOnly specifies if specialized pod will serve exactly one request in its lifetime and would be garbage collected after serving that one request This is optional. If not specified default value will be taken as false",
"podspec": "Podspec specifies podspec to use for executor type container based functions Different arguments mentioned for container based function are populated inside a pod.",
}
func (FunctionSpec) SwaggerDoc() map[string]string {
@@ -237,6 +236,7 @@ var map_HTTPTriggerSpec = map[string]string{
"host": "Deprecated: the original idea of this field is not for setting Ingress. Since we have IngressConfig now, remove Host after couple releases.",
"relativeurl": "RelativeURL is the exposed URL for external client to access a function with.",
"prefix": "Prefix with which functions are exposed. NOTE: Prefix takes precedence over URL/RelativeURL. Note that it does not treat slashes specially (\"/foobar/\" will be matched by the prefix \"/foobar\").",
"keepPrefix": "When function is exposed with Prefix based path, keepPrefix decides whether to keep or trim prefix in URL while invoking function.",
"method": "Use Methods instead of Method. This field is going to be deprecated in a future release HTTP method to access a function.",
"methods": "HTTP methods to access a function",
"functionref": "FunctionReference is a reference to the target function.",
+1 -1
View File
@@ -82,7 +82,7 @@ func (c *Client) Build(req *builder.PackageBuildRequest) (*builder.PackageBuildR
}
pkgBuildResp := builder.PackageBuildResponse{}
err = json.Unmarshal([]byte(rBody), &pkgBuildResp)
err = json.Unmarshal(rBody, &pkgBuildResp)
if err != nil {
c.logger.Error("error parsing resp body", zap.Error(err))
return nil, err
+11 -4
View File
@@ -17,11 +17,15 @@ limitations under the License.
package buildermgr
import (
"time"
"github.com/pkg/errors"
"go.uber.org/zap"
k8sInformers "k8s.io/client-go/informers"
"github.com/fission/fission/pkg/crd"
fetcherConfig "github.com/fission/fission/pkg/fetcher/config"
genInformer "github.com/fission/fission/pkg/generated/informers/externalversions"
)
// Start the buildermgr service.
@@ -46,9 +50,12 @@ func Start(logger *zap.Logger, storageSvcUrl string, envBuilderNamespace string)
envWatcher := makeEnvironmentWatcher(bmLogger, fissionClient, kubernetesClient, fetcherConfig, envBuilderNamespace)
go envWatcher.watchEnvironments()
k8sInformerFactory := k8sInformers.NewSharedInformerFactory(kubernetesClient, time.Second*30)
informerFactory := genInformer.NewSharedInformerFactory(fissionClient, 60*time.Minute)
podInformer := k8sInformerFactory.Core().V1().Pods().Informer()
pkgInformer := informerFactory.Core().V1().Packages().Informer()
pkgWatcher := makePackageWatcher(bmLogger, fissionClient,
kubernetesClient, envBuilderNamespace, storageSvcUrl)
go pkgWatcher.watchPackages()
select {}
kubernetesClient, envBuilderNamespace, storageSvcUrl, &podInformer, &pkgInformer)
pkgWatcher.Run()
return nil
}
+22 -24
View File
@@ -25,7 +25,6 @@ import (
apiv1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/client-go/kubernetes"
k8sCache "k8s.io/client-go/tools/cache"
@@ -40,26 +39,26 @@ type (
logger *zap.Logger
fissionClient *crd.FissionClient
k8sClient *kubernetes.Clientset
podStore k8sCache.Store
pkgStore k8sCache.Store
podInformer *k8sCache.SharedIndexInformer
pkgInformer *k8sCache.SharedIndexInformer
builderNamespace string
storageSvcUrl string
buildCache *cache.Cache
}
)
func makePackageWatcher(logger *zap.Logger, fissionClient *crd.FissionClient, k8sClientSet *kubernetes.Clientset,
builderNamespace string, storageSvcUrl string) *packageWatcher {
lw := k8sCache.NewListWatchFromClient(k8sClientSet.CoreV1().RESTClient(), "pods", metav1.NamespaceAll, fields.Everything())
store, controller := k8sCache.NewInformer(lw, &apiv1.Pod{}, 30*time.Second, k8sCache.ResourceEventHandlerFuncs{})
go controller.Run(make(chan struct{}))
builderNamespace string, storageSvcUrl string, podInformer *k8sCache.SharedIndexInformer,
pkgInformer *k8sCache.SharedIndexInformer) *packageWatcher {
pkgw := &packageWatcher{
logger: logger.Named("package_watcher"),
fissionClient: fissionClient,
k8sClient: k8sClientSet,
podStore: store,
podInformer: podInformer,
pkgInformer: pkgInformer,
builderNamespace: builderNamespace,
storageSvcUrl: storageSvcUrl,
buildCache: cache.MakeCache(0, 0),
}
return pkgw
}
@@ -74,15 +73,15 @@ func makePackageWatcher(logger *zap.Logger, fissionClient *crd.FissionClient, k8
// 5. Update package resource in package ref of functions that share the same package
// 6. Update package status to succeed state
// *. Update package status to failed state,if any one of steps above failed/time out
func (pkgw *packageWatcher) build(buildCache *cache.Cache, srcpkg *fv1.Package) {
func (pkgw *packageWatcher) build(srcpkg *fv1.Package) {
// Ignore duplicate build requests
key := fmt.Sprintf("%v-%v", srcpkg.ObjectMeta.Name, srcpkg.ObjectMeta.ResourceVersion)
_, err := buildCache.Set(key, srcpkg)
_, err := pkgw.buildCache.Set(key, srcpkg)
if err != nil {
return
}
defer func() {
err := buildCache.Delete(key)
err := pkgw.buildCache.Delete(key)
if err != nil {
pkgw.logger.Error("error deleting key from cache", zap.String("key", key), zap.Error(err))
}
@@ -122,7 +121,7 @@ func (pkgw *packageWatcher) build(buildCache *cache.Cache, srcpkg *fv1.Package)
for healthCheckBackOff.NextExists() {
// Informer store is not able to use label to find the pod,
// iterate all available environment builders.
items := pkgw.podStore.List()
items := (*pkgw.podInformer).GetStore().List()
if err != nil {
pkgw.logger.Error("error retrieving pod information for environment", zap.Error(err), zap.String("environment", env.ObjectMeta.Name))
return
@@ -281,10 +280,7 @@ func (pkgw *packageWatcher) build(buildCache *cache.Cache, srcpkg *fv1.Package)
zap.String("package", fmt.Sprintf("%s.%s", pkg.ObjectMeta.Name, pkg.ObjectMeta.Namespace)))
}
func (pkgw *packageWatcher) watchPackages() {
buildCache := cache.MakeCache(0, 0)
lw := k8sCache.NewListWatchFromClient(pkgw.fissionClient.CoreV1().RESTClient(), "packages", apiv1.NamespaceAll, fields.Everything())
func (pkgw *packageWatcher) packageInformerHandler() k8sCache.ResourceEventHandlerFuncs {
processPkg := func(pkg *fv1.Package) {
var err error
@@ -298,14 +294,12 @@ func (pkgw *packageWatcher) watchPackages() {
// don't need to build the package at this moment.
return
}
// Only build pending state packages.
if pkg.Status.BuildStatus == fv1.BuildStatusPending {
go pkgw.build(buildCache, pkg)
go pkgw.build(pkg)
}
}
pkgStore, controller := k8sCache.NewInformer(lw, &fv1.Package{}, 60*time.Minute, k8sCache.ResourceEventHandlerFuncs{
return k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
pkg := obj.(*fv1.Package)
processPkg(pkg)
@@ -325,10 +319,14 @@ func (pkgw *packageWatcher) watchPackages() {
}
processPkg(pkg)
},
})
}
}
pkgw.pkgStore = pkgStore
controller.Run(make(chan struct{}))
func (pkgw *packageWatcher) Run() {
context := context.Background()
go (*pkgw.podInformer).Run(context.Done())
(*pkgw.pkgInformer).AddEventHandler(pkgw.packageInformerHandler())
(*pkgw.pkgInformer).Run(context.Done())
}
// setInitialBuildStatus sets initial build status to a package if it is empty.
+35 -44
View File
@@ -28,13 +28,12 @@ import (
"go.uber.org/zap"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
k8sCache "k8s.io/client-go/tools/cache"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
"github.com/fission/fission/pkg/crd"
genInformer "github.com/fission/fission/pkg/generated/informers/externalversions"
)
const (
@@ -45,14 +44,12 @@ type canaryConfigMgr struct {
logger *zap.Logger
fissionClient *crd.FissionClient
kubeClient *kubernetes.Clientset
canaryConfigStore k8sCache.Store
canaryConfigController k8sCache.Controller
canaryConfigInformer *k8sCache.SharedIndexInformer
promClient *PrometheusApiClient
crdClient rest.Interface
canaryCfgCancelFuncMap *canaryConfigCancelFuncMap
}
func MakeCanaryConfigMgr(logger *zap.Logger, fissionClient *crd.FissionClient, kubeClient *kubernetes.Clientset, crdClient rest.Interface, prometheusSvc string) (*canaryConfigMgr, error) {
func MakeCanaryConfigMgr(logger *zap.Logger, fissionClient *crd.FissionClient, kubeClient *kubernetes.Clientset, prometheusSvc string) (*canaryConfigMgr, error) {
if prometheusSvc == "" {
logger.Info("try to retrieve prometheus server information from environment variables")
@@ -92,54 +89,48 @@ func MakeCanaryConfigMgr(logger *zap.Logger, fissionClient *crd.FissionClient, k
logger: logger.Named("canary_config_manager"),
fissionClient: fissionClient,
kubeClient: kubeClient,
crdClient: crdClient,
promClient: promClient,
canaryCfgCancelFuncMap: makecanaryConfigCancelFuncMap(),
}
store, controller := configMgr.initCanaryConfigController()
configMgr.canaryConfigStore = store
configMgr.canaryConfigController = controller
informerFactory := genInformer.NewSharedInformerFactory(fissionClient, time.Second*30)
informer := informerFactory.Core().V1().CanaryConfigs().Informer()
configMgr.canaryConfigInformer = &informer
configMgr.CanaryConfigEventHandlers()
return configMgr, nil
}
func (canaryCfgMgr *canaryConfigMgr) initCanaryConfigController() (k8sCache.Store, k8sCache.Controller) {
resyncPeriod := 30 * time.Second
listWatch := k8sCache.NewListWatchFromClient(canaryCfgMgr.crdClient, "canaryconfigs", metav1.NamespaceAll, fields.Everything())
store, controller := k8sCache.NewInformer(listWatch, &fv1.CanaryConfig{}, resyncPeriod,
k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
canaryConfig := obj.(*fv1.CanaryConfig)
if canaryConfig.Status.Status == fv1.CanaryConfigStatusPending {
go canaryCfgMgr.addCanaryConfig(canaryConfig)
}
},
DeleteFunc: func(obj interface{}) {
canaryConfig := obj.(*fv1.CanaryConfig)
go canaryCfgMgr.deleteCanaryConfig(canaryConfig)
},
UpdateFunc: func(oldObj interface{}, newObj interface{}) {
oldConfig := oldObj.(*fv1.CanaryConfig)
newConfig := newObj.(*fv1.CanaryConfig)
if oldConfig.ObjectMeta.ResourceVersion != newConfig.ObjectMeta.ResourceVersion &&
newConfig.Status.Status == fv1.CanaryConfigStatusPending {
canaryCfgMgr.logger.Info("update canary config invoked",
zap.String("name", newConfig.ObjectMeta.Name),
zap.String("namespace", newConfig.ObjectMeta.Namespace),
zap.String("version", newConfig.ObjectMeta.ResourceVersion))
go canaryCfgMgr.updateCanaryConfig(oldConfig, newConfig)
}
go canaryCfgMgr.reSyncCanaryConfigs()
func (canaryCfgMgr *canaryConfigMgr) CanaryConfigEventHandlers() {
(*canaryCfgMgr.canaryConfigInformer).AddEventHandler(k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
canaryConfig := obj.(*fv1.CanaryConfig)
if canaryConfig.Status.Status == fv1.CanaryConfigStatusPending {
go canaryCfgMgr.addCanaryConfig(canaryConfig)
}
},
DeleteFunc: func(obj interface{}) {
canaryConfig := obj.(*fv1.CanaryConfig)
go canaryCfgMgr.deleteCanaryConfig(canaryConfig)
},
UpdateFunc: func(oldObj interface{}, newObj interface{}) {
oldConfig := oldObj.(*fv1.CanaryConfig)
newConfig := newObj.(*fv1.CanaryConfig)
if oldConfig.ObjectMeta.ResourceVersion != newConfig.ObjectMeta.ResourceVersion &&
newConfig.Status.Status == fv1.CanaryConfigStatusPending {
canaryCfgMgr.logger.Info("update canary config invoked",
zap.String("name", newConfig.ObjectMeta.Name),
zap.String("namespace", newConfig.ObjectMeta.Namespace),
zap.String("version", newConfig.ObjectMeta.ResourceVersion))
go canaryCfgMgr.updateCanaryConfig(oldConfig, newConfig)
}
go canaryCfgMgr.reSyncCanaryConfigs()
},
})
return store, controller
},
})
}
func (canaryCfgMgr *canaryConfigMgr) Run(ctx context.Context) {
go canaryCfgMgr.canaryConfigController.Run(ctx.Done())
go (*canaryCfgMgr.canaryConfigInformer).Run(ctx.Done())
canaryCfgMgr.logger.Info("started canary configmgr controller")
}
@@ -501,7 +492,7 @@ func (canaryCfgMgr *canaryConfigMgr) rollForward(canaryConfig *fv1.CanaryConfig,
}
func (canaryCfgMgr *canaryConfigMgr) reSyncCanaryConfigs() {
for _, obj := range canaryCfgMgr.canaryConfigStore.List() {
for _, obj := range (*canaryCfgMgr.canaryConfigInformer).GetStore().List() {
canaryConfig := obj.(*fv1.CanaryConfig)
_, err := canaryCfgMgr.canaryCfgCancelFuncMap.lookup(&canaryConfig.ObjectMeta)
if err != nil && canaryConfig.Status.Status == fv1.CanaryConfigStatusPending {
+1 -2
View File
@@ -31,8 +31,7 @@ import (
func ConfigCanaryFeature(context context.Context, logger *zap.Logger, fissionClient *crd.FissionClient, kubeClient *kubernetes.Clientset, featureConfig *config.FeatureConfig, featureStatus map[string]string) error {
// start the appropriate controller
if featureConfig.CanaryConfig.IsEnabled {
canaryCfgMgr, err := canaryconfigmgr.MakeCanaryConfigMgr(logger, fissionClient, kubeClient, fissionClient.CoreV1().RESTClient(),
featureConfig.CanaryConfig.PrometheusSvc)
canaryCfgMgr, err := canaryconfigmgr.MakeCanaryConfigMgr(logger, fissionClient, kubeClient, featureConfig.CanaryConfig.PrometheusSvc)
if err != nil {
featureStatus[config.CanaryFeature] = err.Error()
return errors.Wrap(err, "failed to start canary config manager")
+1 -1
View File
@@ -189,7 +189,7 @@ func (a *API) PackageApiGet(w http.ResponseWriter, r *http.Request) {
var resp []byte
if raw != "" {
resp = []byte(f.Spec.Deployment.Literal)
resp = f.Spec.Deployment.Literal
} else {
resp, err = json.Marshal(f)
if err != nil {
+1 -1
View File
@@ -31,7 +31,7 @@ import (
"k8s.io/client-go/tools/clientcmd"
metricsclient "k8s.io/metrics/pkg/client/clientset/versioned"
genClientset "github.com/fission/fission/pkg/apis/genclient/clientset/versioned"
genClientset "github.com/fission/fission/pkg/generated/clientset/versioned"
)
type (
+4 -3
View File
@@ -19,6 +19,7 @@ package executor
import (
"encoding/json"
"fmt"
"html"
"io/ioutil"
"net/http"
"strings"
@@ -87,10 +88,10 @@ func (executor *Executor) getServiceForFunctionAPI(w http.ResponseWriter, r *htt
if active >= concurrency {
errMsg := fmt.Sprintf("max concurrency reached for %v. All %v instance are active", fn.ObjectMeta.Name, concurrency)
executor.logger.Error("error occurred", zap.String("error", errMsg))
http.Error(w, errMsg, http.StatusTooManyRequests)
http.Error(w, html.EscapeString(errMsg), http.StatusTooManyRequests)
return
}
} else if t == fv1.ExecutorTypeNewdeploy {
} else if t == fv1.ExecutorTypeNewdeploy || t == fv1.ExecutorTypeContainer {
fsvc, err := et.GetFuncSvcFromCache(fn)
if err == nil {
if et.IsValid(fsvc) {
@@ -227,7 +228,7 @@ func (executor *Executor) unTapService(w http.ResponseWriter, r *http.Request) {
t := tapSvcReq.FnExecutorType
if t != fv1.ExecutorTypePoolmgr {
msg := fmt.Sprintf("Unknown executor type '%v'", t)
http.Error(w, msg, http.StatusBadRequest)
http.Error(w, html.EscapeString(msg), http.StatusBadRequest)
return
}
+73
View File
@@ -0,0 +1,73 @@
/*
Copyright 2021 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cms
import (
"context"
"go.uber.org/zap"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
k8sCache "k8s.io/client-go/tools/cache"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
"github.com/fission/fission/pkg/crd"
"github.com/fission/fission/pkg/executor/executortype"
)
func getConfigmapRelatedFuncs(logger *zap.Logger, m *metav1.ObjectMeta, fissionClient *crd.FissionClient) ([]fv1.Function, error) {
funcList, err := fissionClient.CoreV1().Functions(metav1.NamespaceAll).List(context.TODO(), metav1.ListOptions{})
if err != nil {
return nil, err
}
// In future a cache that populates at start and is updated on changes might be better solution
relatedFunctions := make([]fv1.Function, 0)
for _, f := range funcList.Items {
for _, cm := range f.Spec.ConfigMaps {
if (cm.Name == m.Name) && (cm.Namespace == m.Namespace) {
relatedFunctions = append(relatedFunctions, f)
break
}
}
}
return relatedFunctions, nil
}
func ConfigMapEventHandlers(logger *zap.Logger, fissionClient *crd.FissionClient,
kubernetesClient *kubernetes.Clientset, types map[fv1.ExecutorType]executortype.ExecutorType) k8sCache.ResourceEventHandlerFuncs {
return k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {},
DeleteFunc: func(obj interface{}) {},
UpdateFunc: func(oldObj interface{}, newObj interface{}) {
oldCm := oldObj.(*apiv1.ConfigMap)
newCm := newObj.(*apiv1.ConfigMap)
if oldCm.ObjectMeta.ResourceVersion != newCm.ObjectMeta.ResourceVersion {
if newCm.ObjectMeta.Namespace != "kube-system" {
logger.Debug("Configmap changed",
zap.String("configmap_name", newCm.ObjectMeta.Name),
zap.String("configmap_namespace", newCm.ObjectMeta.Namespace))
}
funcs, err := getConfigmapRelatedFuncs(logger, &newCm.ObjectMeta, fissionClient)
if err != nil {
logger.Error("Failed to get functions related to configmap", zap.String("configmap_name", newCm.ObjectMeta.Name), zap.String("configmap_namespace", newCm.ObjectMeta.Namespace))
}
refreshPods(logger, funcs, types)
}
},
}
}
+14 -115
View File
@@ -17,16 +17,10 @@ limitations under the License.
package cms
import (
"context"
"time"
"github.com/pkg/errors"
"go.uber.org/zap"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
k8sCache "k8s.io/client-go/tools/cache"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
"github.com/fission/fission/pkg/crd"
@@ -38,126 +32,31 @@ type (
ConfigSecretController struct {
logger *zap.Logger
configmapController cache.Controller
secretController cache.Controller
configmapInformer *k8sCache.SharedIndexInformer
secretInformer *k8sCache.SharedIndexInformer
fissionClient *crd.FissionClient
}
)
//MakeConfigSecretController makes a controller for configmaps and secrets which changes related functions
// MakeConfigSecretController makes a controller for configmaps and secrets which changes related functions
func MakeConfigSecretController(logger *zap.Logger, fissionClient *crd.FissionClient,
kubernetesClient *kubernetes.Clientset, types map[fv1.ExecutorType]executortype.ExecutorType) *ConfigSecretController {
kubernetesClient *kubernetes.Clientset, types map[fv1.ExecutorType]executortype.ExecutorType,
configmapInformer *k8sCache.SharedIndexInformer,
secretInformer *k8sCache.SharedIndexInformer) *ConfigSecretController {
logger.Debug("Creating ConfigMap & Secret Controller")
_, cmcontroller := initConfigmapController(logger, fissionClient, kubernetesClient, types)
_, scontroller := initSecretController(logger, fissionClient, kubernetesClient, types)
cmsController := &ConfigSecretController{
logger: logger,
configmapController: cmcontroller,
secretController: scontroller,
fissionClient: fissionClient,
logger: logger,
configmapInformer: configmapInformer,
secretInformer: secretInformer,
fissionClient: fissionClient,
}
(*configmapInformer).AddEventHandler(ConfigMapEventHandlers(logger, fissionClient, kubernetesClient, types))
(*secretInformer).AddEventHandler(SecretEventHandlers(logger, fissionClient, kubernetesClient, types))
return cmsController
}
//Run runs the controllers for configmaps and secrets
func (csController *ConfigSecretController) Run(ctx context.Context) {
go csController.configmapController.Run(ctx.Done())
go csController.secretController.Run(ctx.Done())
}
func initConfigmapController(logger *zap.Logger, fissionClient *crd.FissionClient,
kubernetesClient *kubernetes.Clientset, types map[fv1.ExecutorType]executortype.ExecutorType) (cache.Store, cache.Controller) {
resyncPeriod := 30 * time.Second
listWatch := cache.NewListWatchFromClient(kubernetesClient.CoreV1().RESTClient(), "configmaps", metav1.NamespaceAll, fields.Everything())
store, controller := cache.NewInformer(listWatch, &apiv1.ConfigMap{}, resyncPeriod, cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {},
DeleteFunc: func(obj interface{}) {},
UpdateFunc: func(oldObj interface{}, newObj interface{}) {
oldCm := oldObj.(*apiv1.ConfigMap)
newCm := newObj.(*apiv1.ConfigMap)
if oldCm.ObjectMeta.ResourceVersion != newCm.ObjectMeta.ResourceVersion {
if newCm.ObjectMeta.Namespace != "kube-system" {
logger.Debug("Configmap changed",
zap.String("configmap_name", newCm.ObjectMeta.Name),
zap.String("configmap_namespace", newCm.ObjectMeta.Namespace))
}
funcs, err := getConfigmapRelatedFuncs(logger, &newCm.ObjectMeta, fissionClient)
if err != nil {
logger.Error("Failed to get functions related to configmap", zap.String("configmap_name", newCm.ObjectMeta.Name), zap.String("configmap_namespace", newCm.ObjectMeta.Namespace))
}
refreshPods(logger, funcs, types)
}
},
})
return store, controller
}
func getConfigmapRelatedFuncs(logger *zap.Logger, m *metav1.ObjectMeta, fissionClient *crd.FissionClient) ([]fv1.Function, error) {
funcList, err := fissionClient.CoreV1().Functions(metav1.NamespaceAll).List(context.TODO(), metav1.ListOptions{})
if err != nil {
return nil, err
}
// In future a cache that populates at start and is updated on changes might be better solution
relatedFunctions := make([]fv1.Function, 0)
for _, f := range funcList.Items {
for _, cm := range f.Spec.ConfigMaps {
if (cm.Name == m.Name) && (cm.Namespace == m.Namespace) {
relatedFunctions = append(relatedFunctions, f)
break
}
}
}
return relatedFunctions, nil
}
func initSecretController(logger *zap.Logger, fissionClient *crd.FissionClient,
kubernetesClient *kubernetes.Clientset, types map[fv1.ExecutorType]executortype.ExecutorType) (cache.Store, cache.Controller) {
resyncPeriod := 30 * time.Second
listWatch := cache.NewListWatchFromClient(kubernetesClient.CoreV1().RESTClient(), "secrets", metav1.NamespaceAll, fields.Everything())
store, controller := cache.NewInformer(listWatch, &apiv1.Secret{}, resyncPeriod, cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {},
DeleteFunc: func(obj interface{}) {},
UpdateFunc: func(oldObj interface{}, newObj interface{}) {
oldS := oldObj.(*apiv1.Secret)
newS := newObj.(*apiv1.Secret)
if oldS.ObjectMeta.ResourceVersion != newS.ObjectMeta.ResourceVersion {
if newS.ObjectMeta.Namespace != "kube-system" {
logger.Debug("Secret changed",
zap.String("configmap_name", newS.ObjectMeta.Name),
zap.String("configmap_namespace", newS.ObjectMeta.Namespace))
}
funcs, err := getSecretRelatedFuncs(logger, &newS.ObjectMeta, fissionClient)
if err != nil {
logger.Error("Failed to get functions related to secret", zap.String("secret_name", newS.ObjectMeta.Name), zap.String("secret_namespace", newS.ObjectMeta.Namespace))
}
refreshPods(logger, funcs, types)
}
},
})
return store, controller
}
func getSecretRelatedFuncs(logger *zap.Logger, m *metav1.ObjectMeta, fissionClient *crd.FissionClient) ([]fv1.Function, error) {
funcList, err := fissionClient.CoreV1().Functions(metav1.NamespaceAll).List(context.TODO(), metav1.ListOptions{})
if err != nil {
return nil, err
}
// In future a cache that populates at start and is updated on changes might be better solution
relatedFunctions := make([]fv1.Function, 0)
for _, f := range funcList.Items {
for _, secret := range f.Spec.Secrets {
if (secret.Name == m.Name) && (secret.Namespace == m.Namespace) {
relatedFunctions = append(relatedFunctions, f)
break
}
}
}
return relatedFunctions, nil
}
func refreshPods(logger *zap.Logger, funcs []fv1.Function, types map[fv1.ExecutorType]executortype.ExecutorType) {
for _, f := range funcs {
var err error
+72
View File
@@ -0,0 +1,72 @@
/*
Copyright 2021 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cms
import (
"context"
"go.uber.org/zap"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
k8sCache "k8s.io/client-go/tools/cache"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
"github.com/fission/fission/pkg/crd"
"github.com/fission/fission/pkg/executor/executortype"
)
func getSecretRelatedFuncs(logger *zap.Logger, m *metav1.ObjectMeta, fissionClient *crd.FissionClient) ([]fv1.Function, error) {
funcList, err := fissionClient.CoreV1().Functions(metav1.NamespaceAll).List(context.TODO(), metav1.ListOptions{})
if err != nil {
return nil, err
}
// In future a cache that populates at start and is updated on changes might be better solution
relatedFunctions := make([]fv1.Function, 0)
for _, f := range funcList.Items {
for _, secret := range f.Spec.Secrets {
if (secret.Name == m.Name) && (secret.Namespace == m.Namespace) {
relatedFunctions = append(relatedFunctions, f)
break
}
}
}
return relatedFunctions, nil
}
func SecretEventHandlers(logger *zap.Logger, fissionClient *crd.FissionClient,
kubernetesClient *kubernetes.Clientset, types map[fv1.ExecutorType]executortype.ExecutorType) k8sCache.ResourceEventHandlerFuncs {
return k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {},
DeleteFunc: func(obj interface{}) {},
UpdateFunc: func(oldObj interface{}, newObj interface{}) {
oldS := oldObj.(*apiv1.Secret)
newS := newObj.(*apiv1.Secret)
if oldS.ObjectMeta.ResourceVersion != newS.ObjectMeta.ResourceVersion {
if newS.ObjectMeta.Namespace != "kube-system" {
logger.Debug("Secret changed",
zap.String("configmap_name", newS.ObjectMeta.Name),
zap.String("configmap_namespace", newS.ObjectMeta.Namespace))
}
funcs, err := getSecretRelatedFuncs(logger, &newS.ObjectMeta, fissionClient)
if err != nil {
logger.Error("Failed to get functions related to secret", zap.String("secret_name", newS.ObjectMeta.Name), zap.String("secret_namespace", newS.ObjectMeta.Namespace))
}
refreshPods(logger, funcs, types)
}
},
}
}
+57 -14
View File
@@ -30,17 +30,21 @@ import (
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.uber.org/zap"
k8sInformers "k8s.io/client-go/informers"
k8sCache "k8s.io/client-go/tools/cache"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
"github.com/fission/fission/pkg/crd"
"github.com/fission/fission/pkg/executor/cms"
"github.com/fission/fission/pkg/executor/executortype"
"github.com/fission/fission/pkg/executor/executortype/container"
"github.com/fission/fission/pkg/executor/executortype/newdeploy"
"github.com/fission/fission/pkg/executor/executortype/poolmgr"
"github.com/fission/fission/pkg/executor/fscache"
"github.com/fission/fission/pkg/executor/reaper"
"github.com/fission/fission/pkg/executor/util"
fetcherConfig "github.com/fission/fission/pkg/fetcher/config"
genInformer "github.com/fission/fission/pkg/generated/informers/externalversions"
)
type (
@@ -54,7 +58,7 @@ type (
fissionClient *crd.FissionClient
requestChan chan *createFuncServiceRequest
fsCreateWg map[string]*sync.WaitGroup
fsCreateWg sync.Map
}
createFuncServiceRequest struct {
@@ -69,8 +73,9 @@ type (
)
// MakeExecutor returns an Executor for given ExecutorType(s).
func MakeExecutor(logger *zap.Logger, cms *cms.ConfigSecretController,
fissionClient *crd.FissionClient, types map[fv1.ExecutorType]executortype.ExecutorType) (*Executor, error) {
func MakeExecutor(ctx context.Context, logger *zap.Logger, cms *cms.ConfigSecretController,
fissionClient *crd.FissionClient, types map[fv1.ExecutorType]executortype.ExecutorType,
informers []k8sCache.SharedIndexInformer) (*Executor, error) {
executor := &Executor{
logger: logger.Named("executor"),
cms: cms,
@@ -78,14 +83,19 @@ func MakeExecutor(logger *zap.Logger, cms *cms.ConfigSecretController,
executorTypes: types,
requestChan: make(chan *createFuncServiceRequest),
fsCreateWg: make(map[string]*sync.WaitGroup),
}
// Run all informers
for _, informer := range informers {
go informer.Run(ctx.Done())
}
for _, et := range types {
go func(et executortype.ExecutorType) {
et.Run(context.Background())
et.Run(ctx)
}(et)
}
go cms.Run(context.Background())
go executor.serveCreateFuncServices()
return executor, nil
@@ -128,13 +138,13 @@ func (executor *Executor) serveCreateFuncServices() {
}
// Cache miss -- is this first one to request the func?
wg, found := executor.fsCreateWg[crd.CacheKey(fnMetadata)]
wg, found := executor.fsCreateWg.Load(crd.CacheKey(fnMetadata))
if !found {
// create a waitgroup for other requests for
// the same function to wait on
wg := &sync.WaitGroup{}
wg.Add(1)
executor.fsCreateWg[crd.CacheKey(fnMetadata)] = wg
executor.fsCreateWg.Store(crd.CacheKey(fnMetadata), wg)
// launch a goroutine for each request, to parallelize
// the specialization of different functions
@@ -166,7 +176,7 @@ func (executor *Executor) serveCreateFuncServices() {
funcSvc: fsvc,
err: err,
}
delete(executor.fsCreateWg, crd.CacheKey(fnMetadata))
executor.fsCreateWg.Delete(crd.CacheKey(fnMetadata))
wg.Done()
}()
} else {
@@ -174,6 +184,14 @@ func (executor *Executor) serveCreateFuncServices() {
go func() {
executor.logger.Debug("waiting for concurrent request for the same function",
zap.Any("function", fnMetadata))
wg, ok := wg.(*sync.WaitGroup)
if !ok {
err := fmt.Errorf("could not convert value to workgroup for function %v in namespace %v", fnMetadata.Name, fnMetadata.Namespace)
req.respChan <- &createFuncServiceResponse{
funcSvc: nil,
err: err,
}
}
wg.Wait()
// get the function service from the cache
@@ -257,25 +275,43 @@ func StartExecutor(logger *zap.Logger, functionNamespace string, envBuilderNames
logger.Info("Starting executor", zap.String("instanceID", executorInstanceID))
informerFactory := genInformer.NewSharedInformerFactory(fissionClient, time.Second*30)
funcInformer := informerFactory.Core().V1().Functions().Informer()
pkgInformer := informerFactory.Core().V1().Packages().Informer()
envInformer := informerFactory.Core().V1().Environments().Informer()
gpm, err := poolmgr.MakeGenericPoolManager(
logger,
fissionClient, kubernetesClient, metricsClient,
functionNamespace, fetcherConfig, executorInstanceID)
functionNamespace, fetcherConfig, executorInstanceID,
&funcInformer, &pkgInformer,
)
if err != nil {
return errors.Wrap(err, "pool manager creation faied")
}
ndm, err := newdeploy.MakeNewDeploy(
logger,
fissionClient, kubernetesClient, fissionClient.CoreV1().RESTClient(),
functionNamespace, fetcherConfig, executorInstanceID)
fissionClient, kubernetesClient,
functionNamespace, fetcherConfig, executorInstanceID,
&funcInformer, &envInformer,
)
if err != nil {
return errors.Wrap(err, "new deploy manager creation faied")
}
cnm, err := container.MakeContainer(
logger,
fissionClient, kubernetesClient,
functionNamespace, executorInstanceID, &funcInformer)
if err != nil {
return errors.Wrap(err, "container manager creation faied")
}
executorTypes := make(map[fv1.ExecutorType]executortype.ExecutorType)
executorTypes[gpm.GetTypeName()] = gpm
executorTypes[ndm.GetTypeName()] = ndm
executorTypes[cnm.GetTypeName()] = cnm
adoptExistingResources, _ := strconv.ParseBool(os.Getenv("ADOPT_EXISTING_RESOURCES"))
@@ -294,9 +330,16 @@ func StartExecutor(logger *zap.Logger, functionNamespace string, envBuilderNames
// TODO: use context to control the waiting time once kubernetes client supports it.
util.WaitTimeout(wg, 30*time.Second)
cms := cms.MakeConfigSecretController(logger, fissionClient, kubernetesClient, executorTypes)
k8sInformerFactory := k8sInformers.NewSharedInformerFactory(kubernetesClient, time.Second*30)
configmapInformer := k8sInformerFactory.Core().V1().ConfigMaps().Informer()
secretInformer := k8sInformerFactory.Core().V1().Secrets().Informer()
api, err := MakeExecutor(logger, cms, fissionClient, executorTypes)
cms := cms.MakeConfigSecretController(logger, fissionClient, kubernetesClient, executorTypes, &configmapInformer, &secretInformer)
ctx := context.Background()
api, err := MakeExecutor(ctx, logger, cms, fissionClient, executorTypes, []k8sCache.SharedIndexInformer{
funcInformer, pkgInformer, envInformer, configmapInformer, secretInformer,
})
if err != nil {
return err
}
@@ -0,0 +1,153 @@
/*
Copyright 2020 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package container
import (
"context"
"strconv"
multierror "github.com/hashicorp/go-multierror"
"go.uber.org/zap"
apiv1 "k8s.io/api/core/v1"
k8s_err "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
)
// getResources gets the resources(CPU, memory) set for the function
func (cn *Container) getResources(fn *fv1.Function) apiv1.ResourceRequirements {
resources := fn.Spec.Resources
if resources.Requests == nil {
resources.Requests = make(map[apiv1.ResourceName]resource.Quantity)
}
if resources.Limits == nil {
resources.Limits = make(map[apiv1.ResourceName]resource.Quantity)
}
val, ok := fn.Spec.Resources.Requests[apiv1.ResourceCPU]
if ok && !val.IsZero() {
resources.Requests[apiv1.ResourceCPU] = fn.Spec.Resources.Requests[apiv1.ResourceCPU]
}
val, ok = fn.Spec.Resources.Requests[apiv1.ResourceMemory]
if ok && !val.IsZero() {
resources.Requests[apiv1.ResourceMemory] = fn.Spec.Resources.Requests[apiv1.ResourceMemory]
}
val, ok = fn.Spec.Resources.Limits[apiv1.ResourceCPU]
if ok && !val.IsZero() {
resources.Limits[apiv1.ResourceCPU] = fn.Spec.Resources.Limits[apiv1.ResourceCPU]
}
val, ok = fn.Spec.Resources.Limits[apiv1.ResourceMemory]
if ok && !val.IsZero() {
resources.Limits[apiv1.ResourceMemory] = fn.Spec.Resources.Limits[apiv1.ResourceMemory]
}
return resources
}
// cleanupContainer cleans all kubernetes objects related to function
func (cn *Container) cleanupContainer(ns string, name string) error {
result := &multierror.Error{}
err := cn.deleteSvc(ns, name)
if err != nil && !k8s_err.IsNotFound(err) {
cn.logger.Error("error deleting service for Container function",
zap.Error(err),
zap.String("function_name", name),
zap.String("function_namespace", ns))
result = multierror.Append(result, err)
}
err = cn.deleteHpa(ns, name)
if err != nil && !k8s_err.IsNotFound(err) {
cn.logger.Error("error deleting HPA for Container function",
zap.Error(err),
zap.String("function_name", name),
zap.String("function_namespace", ns))
result = multierror.Append(result, err)
}
err = cn.deleteDeployment(ns, name)
if err != nil && !k8s_err.IsNotFound(err) {
cn.logger.Error("error deleting deployment for Container function",
zap.Error(err),
zap.String("function_name", name),
zap.String("function_namespace", ns))
result = multierror.Append(result, err)
}
return result.ErrorOrNil()
}
// referencedResourcesRVSum returns the sum of resource version of all resources the function references to.
// We used to update timestamp in the deployment environment field in order to trigger a rolling update when
// the function referenced resources get updated. However, use timestamp means we are not able to avoid tri-
// ggering a rolling update when executor tries to adopt orphaned deployment due to timestamp changed which
// is unwanted. In order to let executor adopt deployment without triggering a rolling update, we need an
// identical way to get a value that can reflect resources changed without affecting by the time.
// To achieve this goal, the sum of the resource version of all referenced resources is a good fit for our
// scenario since the sum of the resource version is always the same as long as no resources changed.
func referencedResourcesRVSum(client *kubernetes.Clientset, namespace string, secrets []fv1.SecretReference, cfgmaps []fv1.ConfigMapReference) (int, error) {
rvCount := 0
if len(secrets) > 0 {
list, err := client.CoreV1().Secrets(namespace).List(context.TODO(), metav1.ListOptions{})
if err != nil {
return 0, err
}
objmap := make(map[string]apiv1.Secret)
for _, secret := range list.Items {
objmap[secret.Namespace+"/"+secret.Name] = secret
}
for _, ref := range secrets {
s, ok := objmap[ref.Namespace+"/"+ref.Name]
if ok {
rv, _ := strconv.ParseInt(s.ResourceVersion, 10, 32)
rvCount += int(rv)
}
}
}
if len(cfgmaps) > 0 {
list, err := client.CoreV1().ConfigMaps(namespace).List(context.TODO(), metav1.ListOptions{})
if err != nil {
return 0, err
}
objmap := make(map[string]apiv1.ConfigMap)
for _, cfg := range list.Items {
objmap[cfg.Namespace+"/"+cfg.Name] = cfg
}
for _, ref := range cfgmaps {
s, ok := objmap[ref.Namespace+"/"+ref.Name]
if ok {
rv, _ := strconv.ParseInt(s.ResourceVersion, 10, 32)
rvCount += int(rv)
}
}
}
return rvCount, nil
}
@@ -0,0 +1,787 @@
/*
Copyright 2020 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package container
import (
"context"
"fmt"
"os"
"reflect"
"strconv"
"strings"
"sync"
"time"
multierror "github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
"go.uber.org/zap"
appsv1 "k8s.io/api/apps/v1"
apiv1 "k8s.io/api/core/v1"
k8sErrs "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
k8sTypes "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
k8sCache "k8s.io/client-go/tools/cache"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
"github.com/fission/fission/pkg/crd"
"github.com/fission/fission/pkg/executor/executortype"
"github.com/fission/fission/pkg/executor/fscache"
"github.com/fission/fission/pkg/executor/reaper"
"github.com/fission/fission/pkg/throttler"
"github.com/fission/fission/pkg/utils"
"github.com/fission/fission/pkg/utils/maps"
)
var _ executortype.ExecutorType = &Container{}
type (
// Container represents an executor type
Container struct {
logger *zap.Logger
kubernetesClient *kubernetes.Clientset
fissionClient *crd.FissionClient
instanceID string
// fetcherConfig *fetcherConfig.Config
runtimeImagePullPolicy apiv1.PullPolicy
namespace string
useIstio bool
fsCache *fscache.FunctionServiceCache // cache funcSvc's by function, address and pod name
throttler *throttler.Throttler
funcInformer *k8sCache.SharedIndexInformer
serviceInformer k8sCache.SharedIndexInformer
deploymentInformer k8sCache.SharedIndexInformer
defaultIdlePodReapTime time.Duration
}
)
// MakeContainer initializes and returns an instance of CaaF
func MakeContainer(
logger *zap.Logger,
fissionClient *crd.FissionClient,
kubernetesClient *kubernetes.Clientset,
namespace string,
instanceID string,
funcInformer *k8sCache.SharedIndexInformer) (executortype.ExecutorType, error) {
enableIstio := false
if len(os.Getenv("ENABLE_ISTIO")) > 0 {
istio, err := strconv.ParseBool(os.Getenv("ENABLE_ISTIO"))
if err != nil {
logger.Error("failed to parse 'ENABLE_ISTIO', set to false", zap.Error(err))
}
enableIstio = istio
}
caaf := &Container{
logger: logger.Named("CaaF"),
fissionClient: fissionClient,
kubernetesClient: kubernetesClient,
instanceID: instanceID,
namespace: namespace,
fsCache: fscache.MakeFunctionServiceCache(logger),
throttler: throttler.MakeThrottler(1 * time.Minute),
funcInformer: funcInformer,
runtimeImagePullPolicy: utils.GetImagePullPolicy(os.Getenv("RUNTIME_IMAGE_PULL_POLICY")),
useIstio: enableIstio,
// Time is set slightly higher than NewDeploy as cold starts are longer for CaaF
defaultIdlePodReapTime: 1 * time.Minute,
}
(*caaf.funcInformer).AddEventHandler(caaf.FuncInformerHandler())
informerFactory, err := utils.GetInformerFactoryByExecutor(caaf.kubernetesClient, fv1.ExecutorTypeContainer)
if err != nil {
return nil, err
}
caaf.serviceInformer = informerFactory.Core().V1().Services().Informer()
caaf.deploymentInformer = informerFactory.Apps().V1().Deployments().Informer()
return caaf, nil
}
// Run start the function along with an object reaper.
func (caaf *Container) Run(ctx context.Context) {
go caaf.idleObjectReaper()
}
// GetTypeName returns the executor type name.
func (caaf *Container) GetTypeName() fv1.ExecutorType {
return fv1.ExecutorTypeContainer
}
// GetTotalAvailable has not been implemented for CaaF.
func (caaf *Container) GetTotalAvailable(fn *fv1.Function) int {
// Not Implemented for CaaF.
return 0
}
// UnTapService has not been implemented for CaaF.
func (caaf *Container) UnTapService(key string, svcHost string) {
// Not Implemented for CaaF.
}
// GetFuncSvc returns a function service; error otherwise.
func (caaf *Container) GetFuncSvc(ctx context.Context, fn *fv1.Function) (*fscache.FuncSvc, error) {
return caaf.createFunction(fn)
}
// GetFuncSvcFromCache returns a function service from cache; error otherwise.
func (caaf *Container) GetFuncSvcFromCache(fn *fv1.Function) (*fscache.FuncSvc, error) {
return caaf.fsCache.GetByFunction(&fn.ObjectMeta)
}
// DeleteFuncSvcFromCache deletes a function service from cache.
func (caaf *Container) DeleteFuncSvcFromCache(fsvc *fscache.FuncSvc) {
caaf.fsCache.DeleteEntry(fsvc)
}
// GetFuncSvcFromPoolCache has not been implemented for Container Functions
func (caaf *Container) GetFuncSvcFromPoolCache(fn *fv1.Function, requestsPerPod int) (*fscache.FuncSvc, int, error) {
// Not Implemented for NewDeployment. Will be used when support of concurrent specialization of same function is added.
return nil, 0, nil
}
// TapService makes a TouchByAddress request to the cache.
func (caaf *Container) TapService(svcHost string) error {
err := caaf.fsCache.TouchByAddress(svcHost)
if err != nil {
return err
}
return nil
}
func (caaf *Container) getServiceInfo(obj apiv1.ObjectReference) (*apiv1.Service, error) {
item, exists, err := utils.GetCachedItem(obj, caaf.serviceInformer)
if err != nil || !exists {
caaf.logger.Debug(
"Falling back to getting service info from k8s API -- this may cause performance issues for your function.",
zap.Bool("exists", exists),
zap.Error(err),
)
service, err := caaf.kubernetesClient.CoreV1().Services(obj.Namespace).Get(context.TODO(), obj.Name, metav1.GetOptions{})
return service, err
}
service := item.(*apiv1.Service)
return service, nil
}
func (caaf *Container) getDeploymentInfo(obj apiv1.ObjectReference) (*appsv1.Deployment, error) {
item, exists, err := utils.GetCachedItem(obj, caaf.deploymentInformer)
if err != nil || !exists {
caaf.logger.Debug(
"Falling back to getting deployment info from k8s API -- this may cause performance issues for your function.",
zap.Bool("exists", exists),
zap.Error(err),
)
deployment, err := caaf.kubernetesClient.AppsV1().Deployments(obj.Namespace).Get(context.TODO(), obj.Name, metav1.GetOptions{})
return deployment, err
}
deployment := item.(*appsv1.Deployment)
return deployment, nil
}
// IsValid does a get on the service address to ensure it's a valid service, then
// scale deployment to 1 replica if there are no available replicas for function.
// Return true if no error occurs, return false otherwise.
func (caaf *Container) IsValid(fsvc *fscache.FuncSvc) bool {
if len(strings.Split(fsvc.Address, ".")) == 0 {
caaf.logger.Error("address not found in function service")
return false
}
if len(fsvc.KubernetesObjects) == 0 {
caaf.logger.Error("no kubernetes object related to function", zap.String("function", fsvc.Function.Name))
return false
}
for _, obj := range fsvc.KubernetesObjects {
if strings.ToLower(obj.Kind) == "service" {
_, err := caaf.getServiceInfo(obj)
if err != nil {
if !k8sErrs.IsNotFound(err) {
caaf.logger.Error("error validating function service", zap.String("function", fsvc.Function.Name), zap.Error(err))
}
return false
}
} else if strings.ToLower(obj.Kind) == "deployment" {
currentDeploy, err := caaf.getDeploymentInfo(obj)
if err != nil {
if !k8sErrs.IsNotFound(err) {
caaf.logger.Error("error validating function deployment", zap.String("function", fsvc.Function.Name), zap.Error(err))
}
return false
}
if currentDeploy.Status.AvailableReplicas < 1 {
return false
}
}
}
return true
}
// RefreshFuncPods deletes pods related to the function so that new pods are replenished
func (caaf *Container) RefreshFuncPods(logger *zap.Logger, f fv1.Function) error {
funcLabels := caaf.getDeployLabels(f.ObjectMeta)
dep, err := caaf.kubernetesClient.AppsV1().Deployments(metav1.NamespaceAll).List(context.TODO(), metav1.ListOptions{
LabelSelector: labels.Set(funcLabels).AsSelector().String(),
})
if err != nil {
return err
}
// Ideally there should be only one deployment but for now we rely on label/selector to ensure that condition
for _, deployment := range dep.Items {
rvCount, err := referencedResourcesRVSum(caaf.kubernetesClient, deployment.Namespace, f.Spec.Secrets, f.Spec.ConfigMaps)
if err != nil {
return err
}
patch := fmt.Sprintf(`{"spec" : {"template": {"spec":{"containers":[{"name": "%s", "env":[{"name": "%s", "value": "%v"}]}]}}}}`,
f.ObjectMeta.Name, fv1.ResourceVersionCount, rvCount)
_, err = caaf.kubernetesClient.AppsV1().Deployments(deployment.ObjectMeta.Namespace).Patch(context.TODO(), deployment.ObjectMeta.Name,
k8sTypes.StrategicMergePatchType,
[]byte(patch), metav1.PatchOptions{})
if err != nil {
return err
}
}
return nil
}
// AdoptExistingResources attempts to adopt resources for functions in all namespaces.
func (caaf *Container) AdoptExistingResources() {
fnList, err := caaf.fissionClient.CoreV1().Functions(metav1.NamespaceAll).List(context.TODO(), metav1.ListOptions{})
if err != nil {
caaf.logger.Error("error getting function list", zap.Error(err))
return
}
wg := &sync.WaitGroup{}
for i := range fnList.Items {
fn := &fnList.Items[i]
if fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType == fv1.ExecutorTypeContainer {
wg.Add(1)
go func() {
defer wg.Done()
_, err = caaf.fnCreate(fn)
if err != nil {
caaf.logger.Warn("failed to adopt resources for function", zap.Error(err))
return
}
caaf.logger.Info("adopt resources for function", zap.String("function", fn.ObjectMeta.Name))
}()
}
}
wg.Wait()
}
// CleanupOldExecutorObjects cleans orphaned resources.
func (caaf *Container) CleanupOldExecutorObjects() {
caaf.logger.Info("CaaF starts to clean orphaned resources", zap.String("instanceID", caaf.instanceID))
errs := &multierror.Error{}
listOpts := metav1.ListOptions{
LabelSelector: labels.Set(map[string]string{fv1.EXECUTOR_TYPE: string(fv1.ExecutorTypeContainer)}).AsSelector().String(),
}
err := reaper.CleanupHpa(caaf.logger, caaf.kubernetesClient, caaf.instanceID, listOpts)
if err != nil {
errs = multierror.Append(errs, err)
}
err = reaper.CleanupDeployments(caaf.logger, caaf.kubernetesClient, caaf.instanceID, listOpts)
if err != nil {
errs = multierror.Append(errs, err)
}
err = reaper.CleanupServices(caaf.logger, caaf.kubernetesClient, caaf.instanceID, listOpts)
if err != nil {
errs = multierror.Append(errs, err)
}
if errs.ErrorOrNil() != nil {
// TODO retry reaper; logged and ignored for now
caaf.logger.Error("Failed to cleanup old executor objects", zap.Error(err))
}
}
func (caaf *Container) createFunction(fn *fv1.Function) (*fscache.FuncSvc, error) {
if fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType != fv1.ExecutorTypeContainer {
return nil, nil
}
fsvcObj, err := caaf.throttler.RunOnce(string(fn.ObjectMeta.UID), func(ableToCreate bool) (interface{}, error) {
if ableToCreate {
return caaf.fnCreate(fn)
}
return caaf.fsCache.GetByFunctionUID(fn.ObjectMeta.UID)
})
if err != nil {
e := "error creating k8s resources for function"
caaf.logger.Error(e,
zap.Error(err),
zap.String("function_name", fn.ObjectMeta.Name),
zap.String("function_namespace", fn.ObjectMeta.Namespace))
return nil, errors.Wrapf(err, "%s %s_%s", e, fn.ObjectMeta.Name, fn.ObjectMeta.Namespace)
}
fsvc, ok := fsvcObj.(*fscache.FuncSvc)
if !ok {
caaf.logger.Panic("receive unknown object while creating function - expected pointer of function service object")
}
return fsvc, err
}
func (caaf *Container) deleteFunction(fn *fv1.Function) error {
if fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType != fv1.ExecutorTypeContainer {
return nil
}
err := caaf.fnDelete(fn)
if err != nil {
err = errors.Wrapf(err, "error deleting kubernetes objects of function %v", fn.ObjectMeta)
}
return err
}
func (caaf *Container) fnCreate(fn *fv1.Function) (*fscache.FuncSvc, error) {
cleanupFunc := func(ns string, name string) {
err := caaf.cleanupContainer(ns, name)
if err != nil {
caaf.logger.Error("received error while cleaning function resources",
zap.String("namespace", ns), zap.String("name", name))
}
}
objName := caaf.getObjName(fn)
deployLabels := caaf.getDeployLabels(fn.ObjectMeta)
deployAnnotations := caaf.getDeployAnnotations(fn.ObjectMeta)
// to support backward compatibility, if the function was created in default ns, we fall back to creating the
// deployment of the function in fission-function ns
ns := caaf.namespace
if fn.ObjectMeta.Namespace != metav1.NamespaceDefault {
ns = fn.ObjectMeta.Namespace
}
// Envoy(istio-proxy) returns 404 directly before istio pilot
// propagates latest Envoy-specific configuration.
// Since Container waits for pods of deployment to be ready,
// change the order of kubeObject creation (create service first,
// then deployment) to take advantage of waiting time.
svc, err := caaf.createOrGetSvc(fn, deployLabels, deployAnnotations, objName, ns)
if err != nil {
caaf.logger.Error("error creating service", zap.Error(err), zap.String("service", objName))
go cleanupFunc(ns, objName)
return nil, errors.Wrapf(err, "error creating service %v", objName)
}
svcAddress := fmt.Sprintf("%v.%v", svc.Name, svc.Namespace)
depl, err := caaf.createOrGetDeployment(fn, objName, deployLabels, deployAnnotations, ns)
if err != nil {
caaf.logger.Error("error creating deployment", zap.Error(err), zap.String("deployment", objName))
go cleanupFunc(ns, objName)
return nil, errors.Wrapf(err, "error creating deployment %v", objName)
}
hpa, err := caaf.createOrGetHpa(objName, &fn.Spec.InvokeStrategy.ExecutionStrategy, depl, deployLabels, deployAnnotations)
if err != nil {
caaf.logger.Error("error creating HPA", zap.Error(err), zap.String("hpa", objName))
go cleanupFunc(ns, objName)
return nil, errors.Wrapf(err, "error creating the HPA %v", objName)
}
kubeObjRefs := []apiv1.ObjectReference{
{
//obj.TypeMeta.Kind does not work hence this, needs investigation and a fix
Kind: "deployment",
Name: depl.ObjectMeta.Name,
APIVersion: depl.TypeMeta.APIVersion,
Namespace: depl.ObjectMeta.Namespace,
ResourceVersion: depl.ObjectMeta.ResourceVersion,
UID: depl.ObjectMeta.UID,
},
{
Kind: "service",
Name: svc.ObjectMeta.Name,
APIVersion: svc.TypeMeta.APIVersion,
Namespace: svc.ObjectMeta.Namespace,
ResourceVersion: svc.ObjectMeta.ResourceVersion,
UID: svc.ObjectMeta.UID,
},
{
Kind: "horizontalpodautoscaler",
Name: hpa.ObjectMeta.Name,
APIVersion: hpa.TypeMeta.APIVersion,
Namespace: hpa.ObjectMeta.Namespace,
ResourceVersion: hpa.ObjectMeta.ResourceVersion,
UID: hpa.ObjectMeta.UID,
},
}
fsvc := &fscache.FuncSvc{
Name: objName,
Function: &fn.ObjectMeta,
Address: svcAddress,
KubernetesObjects: kubeObjRefs,
Executor: fv1.ExecutorTypeContainer,
}
_, err = caaf.fsCache.Add(*fsvc)
if err != nil {
caaf.logger.Error("error adding function to cache", zap.Error(err), zap.Any("function", fsvc.Function))
return fsvc, err
}
caaf.fsCache.IncreaseColdStarts(fn.ObjectMeta.Name, string(fn.ObjectMeta.UID))
return fsvc, nil
}
func (caaf *Container) updateFunction(oldFn *fv1.Function, newFn *fv1.Function) error {
if oldFn.ObjectMeta.ResourceVersion == newFn.ObjectMeta.ResourceVersion {
return nil
}
// Ignoring updates to functions which are not of Container type
if newFn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType != fv1.ExecutorTypeContainer &&
oldFn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType != fv1.ExecutorTypeContainer {
return nil
}
// Executor type is no longer Container
if newFn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType != fv1.ExecutorTypeContainer &&
oldFn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType == fv1.ExecutorTypeContainer {
caaf.logger.Info("function does not use new deployment executor anymore, deleting resources",
zap.Any("function", newFn))
// IMP - pass the oldFn, as the new/modified function is not in cache
return caaf.deleteFunction(oldFn)
}
// Executor type changed to Container from something else
if oldFn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType != fv1.ExecutorTypeContainer &&
newFn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType == fv1.ExecutorTypeContainer {
caaf.logger.Info("function type changed to Container, creating resources",
zap.Any("old_function", oldFn.ObjectMeta),
zap.Any("new_function", newFn.ObjectMeta))
_, err := caaf.createFunction(newFn)
if err != nil {
caaf.updateStatus(oldFn, err, "error changing the function's type to Container")
}
return err
}
if oldFn.Spec.InvokeStrategy != newFn.Spec.InvokeStrategy {
// to support backward compatibility, if the function was created in default ns, we fall back to creating the
// deployment of the function in fission-function ns, so cleaning up resources there
ns := caaf.namespace
if newFn.ObjectMeta.Namespace != metav1.NamespaceDefault {
ns = newFn.ObjectMeta.Namespace
}
fsvc, err := caaf.fsCache.GetByFunctionUID(newFn.ObjectMeta.UID)
if err != nil {
err = errors.Wrapf(err, "error updating function due to unable to find function service cache: %v", oldFn)
return err
}
hpa, err := caaf.getHpa(ns, fsvc.Name)
if err != nil {
caaf.updateStatus(oldFn, err, "error getting HPA while updating function")
return err
}
hpaChanged := false
if newFn.Spec.InvokeStrategy.ExecutionStrategy.MinScale != oldFn.Spec.InvokeStrategy.ExecutionStrategy.MinScale {
replicas := int32(newFn.Spec.InvokeStrategy.ExecutionStrategy.MinScale)
hpa.Spec.MinReplicas = &replicas
hpaChanged = true
}
if newFn.Spec.InvokeStrategy.ExecutionStrategy.MaxScale != oldFn.Spec.InvokeStrategy.ExecutionStrategy.MaxScale {
hpa.Spec.MaxReplicas = int32(newFn.Spec.InvokeStrategy.ExecutionStrategy.MaxScale)
hpaChanged = true
}
if newFn.Spec.InvokeStrategy.ExecutionStrategy.TargetCPUPercent != oldFn.Spec.InvokeStrategy.ExecutionStrategy.TargetCPUPercent {
targetCpupercent := int32(newFn.Spec.InvokeStrategy.ExecutionStrategy.TargetCPUPercent)
hpa.Spec.TargetCPUUtilizationPercentage = &targetCpupercent
hpaChanged = true
}
if hpaChanged {
err := caaf.updateHpa(hpa)
if err != nil {
caaf.updateStatus(oldFn, err, "error updating HPA while updating function")
return err
}
}
}
deployChanged := false
// If length of slice has changed then no need to check individual elements
if len(oldFn.Spec.Secrets) != len(newFn.Spec.Secrets) {
deployChanged = true
} else {
for i, newSecret := range newFn.Spec.Secrets {
if newSecret != oldFn.Spec.Secrets[i] {
deployChanged = true
break
}
}
}
if len(oldFn.Spec.ConfigMaps) != len(newFn.Spec.ConfigMaps) {
deployChanged = true
} else {
for i, newConfig := range newFn.Spec.ConfigMaps {
if newConfig != oldFn.Spec.ConfigMaps[i] {
deployChanged = true
break
}
}
}
if !reflect.DeepEqual(oldFn.Spec.PodSpec, newFn.Spec.PodSpec) {
deployChanged = true
}
if deployChanged {
return caaf.updateFuncDeployment(newFn)
}
return nil
}
func (caaf *Container) updateFuncDeployment(fn *fv1.Function) error {
fsvc, err := caaf.fsCache.GetByFunctionUID(fn.ObjectMeta.UID)
if err != nil {
err = errors.Wrapf(err, "error updating function due to unable to find function service cache: %v", fn)
return err
}
fnObjName := fsvc.Name
deployLabels := caaf.getDeployLabels(fn.ObjectMeta)
caaf.logger.Info("updating deployment due to function update",
zap.String("deployment", fnObjName), zap.Any("function", fn.ObjectMeta.Name))
// to support backward compatibility, if the function was created in default ns, we fall back to creating the
// deployment of the function in fission-function ns
ns := caaf.namespace
if fn.ObjectMeta.Namespace != metav1.NamespaceDefault {
ns = fn.ObjectMeta.Namespace
}
existingDepl, err := caaf.kubernetesClient.AppsV1().Deployments(ns).Get(context.TODO(), fnObjName, metav1.GetOptions{})
if err != nil {
return err
}
// the resource version inside function packageRef is changed,
// so the content of fetchRequest in deployment cmd is different.
// Therefore, the deployment update will trigger a rolling update.
newDeployment, err := caaf.getDeploymentSpec(fn, existingDepl.Spec.Replicas, // use current replicas instead of minscale in the ExecutionStrategy.
fnObjName, ns, deployLabels, caaf.getDeployAnnotations(fn.ObjectMeta))
if err != nil {
caaf.updateStatus(fn, err, "failed to get new deployment spec while updating function")
return err
}
err = caaf.updateDeployment(newDeployment, ns)
if err != nil {
caaf.updateStatus(fn, err, "failed to update deployment while updating function")
return err
}
return nil
}
func (caaf *Container) fnDelete(fn *fv1.Function) error {
multierr := &multierror.Error{}
// GetByFunction uses resource version as part of cache key, however,
// the resource version in function metadata will be changed when a function
// is deleted and cause Container backend fails to delete the entry.
// Use GetByFunctionUID instead of GetByFunction here to find correct
// fsvc entry.
fsvc, err := caaf.fsCache.GetByFunctionUID(fn.ObjectMeta.UID)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("fsvc not found in cache: %v", fn.ObjectMeta))
return err
}
objName := fsvc.Name
_, err = caaf.fsCache.DeleteOld(fsvc, time.Second*0)
if err != nil {
multierr = multierror.Append(multierr,
errors.Wrapf(err, "error deleting the function from cache"))
}
// to support backward compatibility, if the function was created in default ns, we fall back to creating the
// deployment of the function in fission-function ns, so cleaning up resources there
ns := caaf.namespace
if fn.ObjectMeta.Namespace != metav1.NamespaceDefault {
ns = fn.ObjectMeta.Namespace
}
err = caaf.cleanupContainer(ns, objName)
multierr = multierror.Append(multierr, err)
return multierr.ErrorOrNil()
}
// getObjName returns a unique name for kubernetes objects of function
func (caaf *Container) getObjName(fn *fv1.Function) string {
// use meta uuid of function, this ensure we always get the same name for the same function.
uid := fn.ObjectMeta.UID[len(fn.ObjectMeta.UID)-17:]
var functionMetadata string
if len(fn.ObjectMeta.Name)+len(fn.ObjectMeta.Namespace) < 35 {
functionMetadata = fn.ObjectMeta.Name + "-" + fn.ObjectMeta.Namespace
} else {
functionMetadata = fn.ObjectMeta.Name[:17] + "-" + fn.ObjectMeta.Namespace[:17]
}
// contructed name should be 63 characters long, as it is a valid k8s name
// functionMetadata should be 35 characters long, as we take 17 characters from functionUid
// with newdeploy 10 character prefix
return strings.ToLower(fmt.Sprintf("container-%s-%s", functionMetadata, uid))
}
func (caaf *Container) getDeployLabels(fnMeta metav1.ObjectMeta) map[string]string {
deployLabels := maps.CopyStringMap(fnMeta.Labels)
deployLabels[fv1.EXECUTOR_TYPE] = string(fv1.ExecutorTypeContainer)
deployLabels[fv1.FUNCTION_NAME] = fnMeta.Name
deployLabels[fv1.FUNCTION_NAMESPACE] = fnMeta.Namespace
deployLabels[fv1.FUNCTION_UID] = string(fnMeta.UID)
return deployLabels
}
func (caaf *Container) getDeployAnnotations(fnMeta metav1.ObjectMeta) map[string]string {
deployAnnotations := maps.CopyStringMap(fnMeta.Annotations)
deployAnnotations[fv1.EXECUTOR_INSTANCEID_LABEL] = caaf.instanceID
deployAnnotations[fv1.FUNCTION_RESOURCE_VERSION] = fnMeta.ResourceVersion
return deployAnnotations
}
// updateStatus is a function which updates status of update.
// Current implementation only logs messages, in future it will update function status
func (caaf *Container) updateStatus(fn *fv1.Function, err error, message string) {
caaf.logger.Error("function status update", zap.Error(err), zap.Any("function", fn), zap.String("message", message))
}
// idleObjectReaper reaps objects after certain idle time
func (caaf *Container) idleObjectReaper() {
pollSleep := 5 * time.Second
for {
time.Sleep(pollSleep)
funcSvcs, err := caaf.fsCache.ListOld(pollSleep)
if err != nil {
caaf.logger.Error("error reaping idle pods", zap.Error(err))
continue
}
for i := range funcSvcs {
fsvc := funcSvcs[i]
if fsvc.Executor != fv1.ExecutorTypeContainer {
continue
}
fn, err := caaf.fissionClient.CoreV1().Functions(fsvc.Function.Namespace).Get(context.TODO(), fsvc.Function.Name, metav1.GetOptions{})
if err != nil {
// CaaF manager handles the function delete event and clean cache/kubeobjs itself,
// so we ignore the not found error for functions with CaaF executor type here.
if k8sErrs.IsNotFound(err) && fsvc.Executor == fv1.ExecutorTypeContainer {
continue
}
caaf.logger.Error("error getting function", zap.Error(err), zap.String("function", fsvc.Function.Name))
continue
}
idlePodReapTime := caaf.defaultIdlePodReapTime
if fn.Spec.IdleTimeout != nil {
idlePodReapTime = time.Duration(*fn.Spec.IdleTimeout) * time.Second
}
if time.Since(fsvc.Atime) < idlePodReapTime {
continue
}
go func() {
deployObj := getDeploymentObj(fsvc.KubernetesObjects)
if deployObj == nil {
caaf.logger.Error("error finding function deployment", zap.Error(err), zap.String("function", fsvc.Function.Name))
return
}
currentDeploy, err := caaf.kubernetesClient.AppsV1().
Deployments(deployObj.Namespace).Get(context.TODO(), deployObj.Name, metav1.GetOptions{})
if err != nil {
caaf.logger.Error("error getting function deployment", zap.Error(err), zap.String("function", fsvc.Function.Name))
return
}
minScale := int32(fn.Spec.InvokeStrategy.ExecutionStrategy.MinScale)
// do nothing if the current replicas is already lower than minScale
if *currentDeploy.Spec.Replicas <= minScale {
return
}
err = caaf.scaleDeployment(deployObj.Namespace, deployObj.Name, minScale)
if err != nil {
caaf.logger.Error("error scaling down function deployment", zap.Error(err), zap.String("function", fsvc.Function.Name))
}
}()
}
}
}
func getDeploymentObj(kubeobjs []apiv1.ObjectReference) *apiv1.ObjectReference {
for _, kubeobj := range kubeobjs {
switch strings.ToLower(kubeobj.Kind) {
case "deployment":
return &kubeobj
}
}
return nil
}
@@ -0,0 +1,295 @@
/*
Copyright 2020 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package container
import (
"context"
"fmt"
"time"
"go.uber.org/zap"
appsv1 "k8s.io/api/apps/v1"
autoscalingv1 "k8s.io/api/autoscaling/v1"
apiv1 "k8s.io/api/core/v1"
k8s_err "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
"github.com/fission/fission/pkg/executor/util"
)
func (cn *Container) createOrGetDeployment(fn *fv1.Function, deployName string, deployLabels map[string]string, deployAnnotations map[string]string, deployNamespace string) (*appsv1.Deployment, error) {
// The specializationTimeout here refers to the creation of the pod and not the loading of function
// as in other executors.
specializationTimeout := fn.Spec.InvokeStrategy.ExecutionStrategy.SpecializationTimeout
minScale := int32(fn.Spec.InvokeStrategy.ExecutionStrategy.MinScale)
// Always scale to at least one pod when createOrGetDeployment
// is called. The idleObjectReaper will scale-in the deployment
// later if no requests to the function.
if minScale <= 0 {
minScale = 1
}
deployment, err := cn.getDeploymentSpec(fn, &minScale, deployName, deployNamespace, deployLabels, deployAnnotations)
if err != nil {
return nil, err
}
existingDepl, err := cn.kubernetesClient.AppsV1().Deployments(deployNamespace).Get(context.TODO(), deployName, metav1.GetOptions{})
if err == nil {
// Try to adopt orphan deployment created by the old executor.
if existingDepl.Annotations[fv1.EXECUTOR_INSTANCEID_LABEL] != cn.instanceID {
existingDepl.Annotations = deployment.Annotations
existingDepl.Labels = deployment.Labels
existingDepl.Spec.Template.Spec.Containers = deployment.Spec.Template.Spec.Containers
existingDepl.Spec.Template.Spec.ServiceAccountName = deployment.Spec.Template.Spec.ServiceAccountName
existingDepl.Spec.Template.Spec.TerminationGracePeriodSeconds = deployment.Spec.Template.Spec.TerminationGracePeriodSeconds
// Update with the latest deployment spec. Kubernetes will trigger
// rolling update if spec is different from the one in the cluster.
existingDepl, err = cn.kubernetesClient.AppsV1().Deployments(deployNamespace).Update(context.TODO(), existingDepl, metav1.UpdateOptions{})
if err != nil {
cn.logger.Warn("error adopting cn", zap.Error(err),
zap.String("cn", deployName), zap.String("ns", deployNamespace))
return nil, err
}
// In this case, we just return without waiting for it for fast bootstraping.
return existingDepl, nil
}
if *existingDepl.Spec.Replicas < minScale {
err = cn.scaleDeployment(existingDepl.Namespace, existingDepl.Name, minScale)
if err != nil {
cn.logger.Error("error scaling up function deployment", zap.Error(err), zap.String("function", fn.ObjectMeta.Name))
return nil, err
}
}
if existingDepl.Status.AvailableReplicas < minScale {
existingDepl, err = cn.waitForDeploy(existingDepl, minScale, specializationTimeout)
}
return existingDepl, err
} else if k8s_err.IsNotFound(err) {
depl, err := cn.kubernetesClient.AppsV1().Deployments(deployNamespace).Create(context.TODO(), deployment, metav1.CreateOptions{})
if err != nil {
if k8s_err.IsAlreadyExists(err) {
depl, err = cn.kubernetesClient.AppsV1().Deployments(deployNamespace).Get(context.TODO(), deployName, metav1.GetOptions{})
}
if err != nil {
cn.logger.Error("error while creating function deployment",
zap.Error(err),
zap.String("function", fn.ObjectMeta.Name),
zap.String("deployment_name", deployName),
zap.String("deployment_namespace", deployNamespace))
return nil, err
}
}
if minScale > 0 {
depl, err = cn.waitForDeploy(depl, minScale, specializationTimeout)
}
return depl, err
}
return nil, err
}
func (cn *Container) updateDeployment(deployment *appsv1.Deployment, ns string) error {
_, err := cn.kubernetesClient.AppsV1().Deployments(ns).Update(context.TODO(), deployment, metav1.UpdateOptions{})
return err
}
func (cn *Container) deleteDeployment(ns string, name string) error {
// DeletePropagationBackground deletes the object immediately and dependent are deleted later
// DeletePropagationForeground not advisable; it marks for deleteion and API can still serve those objects
deletePropagation := metav1.DeletePropagationBackground
return cn.kubernetesClient.AppsV1().Deployments(ns).Delete(context.TODO(), name, metav1.DeleteOptions{
PropagationPolicy: &deletePropagation,
})
}
func (cn *Container) waitForDeploy(depl *appsv1.Deployment, replicas int32, specializationTimeout int) (latestDepl *appsv1.Deployment, err error) {
oldStatus := depl.Status
// if no specializationTimeout is set, use default value
if specializationTimeout < fv1.DefaultSpecializationTimeOut {
specializationTimeout = fv1.DefaultSpecializationTimeOut
}
for i := 0; i < specializationTimeout; i++ {
latestDepl, err := cn.kubernetesClient.AppsV1().Deployments(depl.ObjectMeta.Namespace).Get(context.TODO(), depl.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
// TODO check for imagePullerror
// use AvailableReplicas here is better than ReadyReplicas
// since the pods may not be able to serve network traffic yet.
if latestDepl.Status.AvailableReplicas >= replicas {
return latestDepl, err
}
time.Sleep(time.Second)
}
cn.logger.Error("Deployment provision failed within timeout window",
zap.String("name", latestDepl.ObjectMeta.Name), zap.Any("old_status", oldStatus),
zap.Any("current_status", latestDepl.Status), zap.Int("timeout", specializationTimeout))
// 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
}
func (cn *Container) getDeploymentSpec(fn *fv1.Function, targetReplicas *int32,
deployName string, deployNamespace string, deployLabels map[string]string, deployAnnotations map[string]string) (*appsv1.Deployment, error) {
replicas := int32(fn.Spec.InvokeStrategy.ExecutionStrategy.MinScale)
if targetReplicas != nil {
replicas = *targetReplicas
}
gracePeriodSeconds := int64(6 * 60)
podAnnotations := make(map[string]string)
if cn.useIstio {
podAnnotations["sidecar.istio.io/inject"] = "false"
}
podLabels := make(map[string]string)
for k, v := range deployLabels {
podLabels[k] = v
}
// Set maxUnavailable and maxSurge to 20% is because we want
// fission to rollout newer function version gradually without
// affecting any online service. For example, if you set maxSurge
// to 100%, the new ReplicaSet scales up immediately and may
// consume all remaining compute resources which might be an
// issue if a cluster's resource is on a budget.
// TODO: add to ExecutionStrategy so that the user
// can do more fine control over different functions.
maxUnavailable := intstr.FromString("20%")
maxSurge := intstr.FromString("20%")
// Container updates the environment variable "LastUpdateTimestamp" of deployment
// whenever a configmap/secret gets an update, but it also leaves multiple ReplicaSets for
// rollback purpose. Since fission always update a deployment instead of performing a
// rollback, set RevisionHistoryLimit to 0 to disable this feature.
revisionHistoryLimit := int32(0)
resources := cn.getResources(fn)
// Other executor types rely on Environments to add configmaps and secrets
envFromSources, err := util.ConvertConfigSecrets(fn, cn.kubernetesClient)
if err != nil {
return nil, err
}
rvCount, err := referencedResourcesRVSum(cn.kubernetesClient, fn.ObjectMeta.Namespace, fn.Spec.Secrets, fn.Spec.ConfigMaps)
if err != nil {
return nil, err
}
if fn.Spec.PodSpec == nil {
return nil, fmt.Errorf("podSpec is not set for function %s", fn.ObjectMeta.Name)
}
container := &apiv1.Container{
Name: fn.ObjectMeta.Name,
ImagePullPolicy: cn.runtimeImagePullPolicy,
TerminationMessagePath: "/dev/termination-log",
Lifecycle: &apiv1.Lifecycle{
PreStop: &apiv1.Handler{
Exec: &apiv1.ExecAction{
Command: []string{
"/bin/sleep",
fmt.Sprintf("%v", gracePeriodSeconds),
},
},
},
},
Env: []apiv1.EnvVar{
{
Name: fv1.ResourceVersionCount,
Value: fmt.Sprintf("%v", rvCount),
},
},
EnvFrom: envFromSources,
// https://istio.io/docs/setup/kubernetes/additional-setup/requirements/
Resources: resources,
}
podSpec, err := util.MergePodSpec(&apiv1.PodSpec{
Containers: []apiv1.Container{*container},
TerminationGracePeriodSeconds: &gracePeriodSeconds,
}, fn.Spec.PodSpec)
if err != nil {
return nil, err
}
pod := apiv1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: podLabels,
Annotations: podAnnotations,
},
Spec: *podSpec,
}
pod.Spec = *(util.ApplyImagePullSecret("", pod.Spec))
deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: deployName,
Labels: deployLabels,
Annotations: deployAnnotations,
},
Spec: appsv1.DeploymentSpec{
Replicas: &replicas,
Selector: &metav1.LabelSelector{
MatchLabels: deployLabels,
},
Template: pod,
Strategy: appsv1.DeploymentStrategy{
Type: appsv1.RollingUpdateDeploymentStrategyType,
RollingUpdate: &appsv1.RollingUpdateDeployment{
MaxUnavailable: &maxUnavailable,
MaxSurge: &maxSurge,
},
},
RevisionHistoryLimit: &revisionHistoryLimit,
},
}
return deployment, nil
}
func (caaf *Container) scaleDeployment(deplNS string, deplName string, replicas int32) error {
caaf.logger.Info("scaling deployment",
zap.String("deployment", deplName),
zap.String("namespace", deplNS),
zap.Int32("replicas", replicas))
_, err := caaf.kubernetesClient.AppsV1().Deployments(deplNS).UpdateScale(context.TODO(), deplName, &autoscalingv1.Scale{
ObjectMeta: metav1.ObjectMeta{
Name: deplName,
Namespace: deplNS,
},
Spec: autoscalingv1.ScaleSpec{
Replicas: replicas,
},
}, metav1.UpdateOptions{})
return err
}
@@ -0,0 +1,85 @@
/*
Copyright 2020 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package container
import (
"go.uber.org/zap"
k8sCache "k8s.io/client-go/tools/cache"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
)
func (caaf *Container) FuncInformerHandler() k8sCache.ResourceEventHandlerFuncs {
return k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
fn := obj.(*fv1.Function)
fnExecutorType := fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType
if fnExecutorType != "" && fnExecutorType != fv1.ExecutorTypeContainer {
return
}
// TODO: A workaround to process items in parallel. We should use workqueue ("k8s.io/client-go/util/workqueue")
// and worker pattern to process items instead of moving process to another goroutine.
// example: https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/job/job_controller.go
go func() {
log := caaf.logger.With(zap.String("function_name", fn.ObjectMeta.Name), zap.String("function_namespace", fn.ObjectMeta.Namespace))
log.Debug("start function create handler")
_, err := caaf.createFunction(fn)
if err != nil {
log.Error("error eager creating function", zap.Error(err))
}
log.Debug("end function create handler")
}()
},
DeleteFunc: func(obj interface{}) {
fn := obj.(*fv1.Function)
fnExecutorType := fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType
if fnExecutorType != "" && fnExecutorType != fv1.ExecutorTypeContainer {
return
}
go func() {
log := caaf.logger.With(zap.String("function_name", fn.ObjectMeta.Name), zap.String("function_namespace", fn.ObjectMeta.Namespace))
log.Debug("start function delete handler")
err := caaf.deleteFunction(fn)
if err != nil {
log.Error("error deleting function", zap.Error(err))
}
log.Debug("end function delete handler")
}()
},
UpdateFunc: func(oldObj interface{}, newObj interface{}) {
oldFn := oldObj.(*fv1.Function)
newFn := newObj.(*fv1.Function)
fnExecutorType := oldFn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType
if fnExecutorType != "" && fnExecutorType != fv1.ExecutorTypeContainer {
return
}
go func() {
log := caaf.logger.With(zap.String("function_name", newFn.ObjectMeta.Name),
zap.String("function_namespace", newFn.ObjectMeta.Namespace),
zap.String("old_function_name", oldFn.ObjectMeta.Name))
log.Debug("start function update handler")
err := caaf.updateFunction(oldFn, newFn)
if err != nil {
log.Error("error updating function",
zap.Error(err))
}
log.Debug("end function update handler")
}()
},
}
}
+113
View File
@@ -0,0 +1,113 @@
/*
Copyright 2020 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package container
import (
"context"
"github.com/pkg/errors"
"go.uber.org/zap"
appsv1 "k8s.io/api/apps/v1"
asv1 "k8s.io/api/autoscaling/v1"
k8s_err "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
)
const (
DeploymentKind = "Deployment"
DeploymentVersion = "apps/v1"
)
func (cn *Container) createOrGetHpa(hpaName string, execStrategy *fv1.ExecutionStrategy,
depl *appsv1.Deployment, deployLabels map[string]string, deployAnnotations map[string]string) (*asv1.HorizontalPodAutoscaler, error) {
if depl == nil {
return nil, errors.New("failed to create HPA, found empty deployment")
}
minRepl := int32(execStrategy.MinScale)
if minRepl == 0 {
minRepl = 1
}
maxRepl := int32(execStrategy.MaxScale)
if maxRepl == 0 {
maxRepl = minRepl
}
targetCPU := int32(execStrategy.TargetCPUPercent)
hpa := &asv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: hpaName,
Labels: deployLabels,
Annotations: deployAnnotations,
},
Spec: asv1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: asv1.CrossVersionObjectReference{
Kind: DeploymentKind,
Name: depl.ObjectMeta.Name,
APIVersion: DeploymentVersion,
},
MinReplicas: &minRepl,
MaxReplicas: maxRepl,
TargetCPUUtilizationPercentage: &targetCPU,
},
}
existingHpa, err := cn.getHpa(depl.ObjectMeta.Namespace, hpaName)
if err == nil {
// to adopt orphan service
if existingHpa.Annotations[fv1.EXECUTOR_INSTANCEID_LABEL] != cn.instanceID {
existingHpa.Annotations = hpa.Annotations
existingHpa.Labels = hpa.Labels
existingHpa.Spec = hpa.Spec
existingHpa, err = cn.kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(depl.ObjectMeta.Namespace).Update(context.TODO(), existingHpa, metav1.UpdateOptions{})
if err != nil {
cn.logger.Warn("error adopting HPA", zap.Error(err),
zap.String("HPA", hpaName), zap.String("ns", depl.ObjectMeta.Namespace))
return nil, err
}
}
return existingHpa, err
} else if k8s_err.IsNotFound(err) {
cHpa, err := cn.kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(depl.ObjectMeta.Namespace).Create(context.TODO(), hpa, metav1.CreateOptions{})
if err != nil {
if k8s_err.IsAlreadyExists(err) {
cHpa, err = cn.getHpa(depl.ObjectMeta.Namespace, hpaName)
}
if err != nil {
return nil, err
}
}
return cHpa, nil
}
return nil, err
}
func (cn *Container) getHpa(ns, name string) (*asv1.HorizontalPodAutoscaler, error) {
return cn.kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(ns).Get(context.TODO(), name, metav1.GetOptions{})
}
func (cn *Container) updateHpa(hpa *asv1.HorizontalPodAutoscaler) error {
_, err := cn.kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(hpa.ObjectMeta.Namespace).Update(context.TODO(), hpa, metav1.UpdateOptions{})
return err
}
func (cn *Container) deleteHpa(ns string, name string) error {
return cn.kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(ns).Delete(context.TODO(), name, metav1.DeleteOptions{})
}
+103
View File
@@ -0,0 +1,103 @@
/*
Copyright 2020 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package container
import (
"context"
"fmt"
"go.uber.org/zap"
apiv1 "k8s.io/api/core/v1"
k8s_err "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
)
func (cn *Container) getSvPort(fn *fv1.Function) (port int32, err error) {
if fn.Spec.PodSpec == nil {
return port, fmt.Errorf("podspec is empty for function %s", fn.ObjectMeta.Name)
}
if len(fn.Spec.PodSpec.Containers) != 1 {
return port, fmt.Errorf("podspec should have exactly one container %s", fn.ObjectMeta.Name)
}
if len(fn.Spec.PodSpec.Containers[0].Ports) != 1 {
return port, fmt.Errorf("container should have exactly one port %s", fn.ObjectMeta.Name)
}
return fn.Spec.PodSpec.Containers[0].Ports[0].ContainerPort, nil
}
func (cn *Container) createOrGetSvc(fn *fv1.Function, deployLabels map[string]string, deployAnnotations map[string]string, svcName string, svcNamespace string) (*apiv1.Service, error) {
targetPort, err := cn.getSvPort(fn)
if err != nil {
return nil, err
}
service := &apiv1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: svcName,
Labels: deployLabels,
Annotations: deployAnnotations,
},
Spec: apiv1.ServiceSpec{
Ports: []apiv1.ServicePort{
{
Name: "http-env",
Port: int32(80),
TargetPort: intstr.FromInt(int(targetPort)),
},
},
Selector: deployLabels,
Type: apiv1.ServiceTypeClusterIP,
},
}
existingSvc, err := cn.kubernetesClient.CoreV1().Services(svcNamespace).Get(context.TODO(), svcName, metav1.GetOptions{})
if err == nil {
// to adopt orphan service
if existingSvc.Annotations[fv1.EXECUTOR_INSTANCEID_LABEL] != cn.instanceID {
existingSvc.Annotations = service.Annotations
existingSvc.Labels = service.Labels
existingSvc.Spec.Ports = service.Spec.Ports
existingSvc.Spec.Selector = service.Spec.Selector
existingSvc.Spec.Type = service.Spec.Type
existingSvc, err = cn.kubernetesClient.CoreV1().Services(svcNamespace).Update(context.TODO(), existingSvc, metav1.UpdateOptions{})
if err != nil {
cn.logger.Warn("error adopting service", zap.Error(err),
zap.String("service", svcName), zap.String("ns", svcNamespace))
return nil, err
}
}
return existingSvc, err
} else if k8s_err.IsNotFound(err) {
svc, err := cn.kubernetesClient.CoreV1().Services(svcNamespace).Create(context.TODO(), service, metav1.CreateOptions{})
if err != nil {
if k8s_err.IsAlreadyExists(err) {
svc, err = cn.kubernetesClient.CoreV1().Services(svcNamespace).Get(context.TODO(), svcName, metav1.GetOptions{})
}
if err != nil {
return nil, err
}
}
return svc, nil
}
return nil, err
}
func (cn *Container) deleteSvc(ns string, name string) error {
return cn.kubernetesClient.CoreV1().Services(ns).Delete(context.TODO(), name, metav1.DeleteOptions{})
}
@@ -0,0 +1,54 @@
/*
Copyright 2021 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package newdeploy
import (
"context"
"go.uber.org/zap"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8sCache "k8s.io/client-go/tools/cache"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
)
func (deploy *NewDeploy) EnvEventHandlers() k8sCache.ResourceEventHandlerFuncs {
return k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {},
DeleteFunc: func(obj interface{}) {},
UpdateFunc: func(oldObj interface{}, newObj interface{}) {
newEnv := newObj.(*fv1.Environment)
oldEnv := oldObj.(*fv1.Environment)
// Currently only an image update in environment calls for function's deployment recreation. In future there might be more attributes which would want to do it
if oldEnv.Spec.Runtime.Image != newEnv.Spec.Runtime.Image {
deploy.logger.Debug("Updating all function of the environment that changed, old env:", zap.Any("environment", oldEnv))
funcs := deploy.getEnvFunctions(&newEnv.ObjectMeta)
for _, f := range funcs {
function, err := deploy.fissionClient.CoreV1().Functions(f.ObjectMeta.Namespace).Get(context.TODO(), f.ObjectMeta.Name, metav1.GetOptions{})
if err != nil {
deploy.logger.Error("Error getting function", zap.Error(err), zap.Any("function", function))
continue
}
err = deploy.updateFuncDeployment(function, newEnv)
if err != nil {
deploy.logger.Error("Error updating function", zap.Error(err), zap.Any("function", function))
continue
}
}
}
},
}
}
@@ -0,0 +1,68 @@
/*
Copyright 2021 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package newdeploy
import (
"go.uber.org/zap"
k8sCache "k8s.io/client-go/tools/cache"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
)
func (deploy *NewDeploy) FunctionEventHandlers() k8sCache.ResourceEventHandlerFuncs {
return k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
// TODO: A workaround to process items in parallel. We should use workqueue ("k8s.io/client-go/util/workqueue")
// and worker pattern to process items instead of moving process to another goroutine.
// example: https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/job/job_controller.go
go func() {
fn := obj.(*fv1.Function)
deploy.logger.Debug("create deployment for function", zap.Any("fn", fn.ObjectMeta), zap.Any("fnspec", fn.Spec))
_, err := deploy.createFunction(fn)
if err != nil {
deploy.logger.Error("error eager creating function",
zap.Error(err),
zap.Any("function", fn))
}
deploy.logger.Debug("end create deployment for function", zap.Any("fn", fn.ObjectMeta), zap.Any("fnspec", fn.Spec))
}()
},
DeleteFunc: func(obj interface{}) {
fn := obj.(*fv1.Function)
go func() {
err := deploy.deleteFunction(fn)
if err != nil {
deploy.logger.Error("error deleting function",
zap.Error(err),
zap.Any("function", fn))
}
}()
},
UpdateFunc: func(oldObj interface{}, newObj interface{}) {
oldFn := oldObj.(*fv1.Function)
newFn := newObj.(*fv1.Function)
go func() {
err := deploy.updateFunction(oldFn, newFn)
if err != nil {
deploy.logger.Error("error updating function",
zap.Error(err),
zap.Any("old_function", oldFn),
zap.Any("new_function", newFn))
}
}()
},
}
}
@@ -48,7 +48,7 @@ const (
func (deploy *NewDeploy) createOrGetDeployment(fn *fv1.Function, env *fv1.Environment,
deployName string, deployLabels map[string]string, deployAnnotations map[string]string, deployNamespace string) (*appsv1.Deployment, error) {
specializationTimeout := int(fn.Spec.InvokeStrategy.ExecutionStrategy.SpecializationTimeout)
specializationTimeout := fn.Spec.InvokeStrategy.ExecutionStrategy.SpecializationTimeout
minScale := int32(fn.Spec.InvokeStrategy.ExecutionStrategy.MinScale)
// Always scale to at least one pod when createOrGetDeployment
@@ -262,7 +262,8 @@ func (deploy *NewDeploy) getDeploymentSpec(fn *fv1.Function, env *fv1.Environmen
// https://istio.io/docs/setup/kubernetes/additional-setup/requirements/
Ports: []apiv1.ContainerPort{
{
Name: "http-env",
Name: "http-env",
// Now that we have added Port field in spec, should we make this configurable too?
ContainerPort: int32(8888),
},
},
@@ -453,8 +454,9 @@ func (deploy *NewDeploy) createOrGetSvc(deployLabels map[string]string, deployAn
Spec: apiv1.ServiceSpec{
Ports: []apiv1.ServicePort{
{
Name: "http-env",
Port: int32(80),
Name: "http-env",
Port: int32(80),
// Since Function spec now supports Port , should we make this configurable too?
TargetPort: intstr.FromInt(8888),
},
},
@@ -499,14 +501,16 @@ func (deploy *NewDeploy) deleteSvc(ns string, name string) error {
return deploy.kubernetesClient.CoreV1().Services(ns).Delete(context.TODO(), name, metav1.DeleteOptions{})
}
func (deploy *NewDeploy) waitForDeploy(depl *appsv1.Deployment, replicas int32, specializationTimeout int) (*appsv1.Deployment, error) {
func (deploy *NewDeploy) waitForDeploy(depl *appsv1.Deployment, replicas int32, specializationTimeout int) (latestDepl *appsv1.Deployment, err error) {
oldStatus := depl.Status
// if no specializationTimeout is set, use default value
if specializationTimeout < fv1.DefaultSpecializationTimeOut {
specializationTimeout = fv1.DefaultSpecializationTimeOut
}
for i := 0; i < specializationTimeout; i++ {
latestDepl, err := deploy.kubernetesClient.AppsV1().Deployments(depl.ObjectMeta.Namespace).Get(context.TODO(), depl.Name, metav1.GetOptions{})
latestDepl, err = deploy.kubernetesClient.AppsV1().Deployments(depl.ObjectMeta.Namespace).Get(context.TODO(), depl.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
@@ -519,6 +523,10 @@ func (deploy *NewDeploy) waitForDeploy(depl *appsv1.Deployment, replicas int32,
time.Sleep(time.Second)
}
deploy.logger.Error("Deployment provision failed within timeout window",
zap.String("name", latestDepl.ObjectMeta.Name), zap.Any("old_status", oldStatus),
zap.Any("current_status", latestDepl.Status), zap.Int("timeout", specializationTimeout))
// 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
@@ -33,11 +33,9 @@ import (
apiv1 "k8s.io/api/core/v1"
k8sErrs "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
k8sTypes "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
k8sCache "k8s.io/client-go/tools/cache"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
@@ -48,6 +46,7 @@ import (
fetcherConfig "github.com/fission/fission/pkg/fetcher/config"
"github.com/fission/fission/pkg/throttler"
"github.com/fission/fission/pkg/utils"
"github.com/fission/fission/pkg/utils/maps"
)
var _ executortype.ExecutorType = &NewDeploy{}
@@ -59,7 +58,6 @@ type (
kubernetesClient *kubernetes.Clientset
fissionClient *crd.FissionClient
crdClient rest.Interface
instanceID string
fetcherConfig *fetcherConfig.Config
@@ -69,12 +67,11 @@ type (
fsCache *fscache.FunctionServiceCache // cache funcSvc's by function, address and pod name
throttler *throttler.Throttler
funcStore k8sCache.Store
funcController k8sCache.Controller
throttler *throttler.Throttler
funcInformer *k8sCache.SharedIndexInformer
envInformer *k8sCache.SharedIndexInformer
envStore k8sCache.Store
envController k8sCache.Controller
serviceInformer k8sCache.SharedIndexInformer
deploymentInformer k8sCache.SharedIndexInformer
@@ -87,10 +84,11 @@ func MakeNewDeploy(
logger *zap.Logger,
fissionClient *crd.FissionClient,
kubernetesClient *kubernetes.Clientset,
crdClient rest.Interface,
namespace string,
fetcherConfig *fetcherConfig.Config,
instanceID string,
funcInformer *k8sCache.SharedIndexInformer,
envInformer *k8sCache.SharedIndexInformer,
) (executortype.ExecutorType, error) {
enableIstio := false
if len(os.Getenv("ENABLE_ISTIO")) > 0 {
@@ -106,7 +104,6 @@ func MakeNewDeploy(
fissionClient: fissionClient,
kubernetesClient: kubernetesClient,
crdClient: crdClient,
instanceID: instanceID,
namespace: namespace,
@@ -118,19 +115,14 @@ func MakeNewDeploy(
useIstio: enableIstio,
defaultIdlePodReapTime: 2 * time.Minute,
funcInformer: funcInformer,
envInformer: envInformer,
}
if nd.crdClient != nil {
fnStore, fnController := nd.initFuncController()
nd.funcStore = fnStore
nd.funcController = fnController
(*nd.funcInformer).AddEventHandler(nd.FunctionEventHandlers())
(*nd.envInformer).AddEventHandler(nd.EnvEventHandlers())
envStore, envController := nd.initEnvController()
nd.envStore = envStore
nd.envController = envController
}
informerFactory, err := utils.GetInformerFacoryByExecutor(nd.kubernetesClient, fv1.ExecutorTypePoolmgr)
informerFactory, err := utils.GetInformerFactoryByExecutor(nd.kubernetesClient, fv1.ExecutorTypePoolmgr)
if err != nil {
return nil, err
}
@@ -141,8 +133,6 @@ func MakeNewDeploy(
// Run start the function and environment controller along with an object reaper.
func (deploy *NewDeploy) Run(ctx context.Context) {
go deploy.funcController.Run(ctx.Done())
go deploy.envController.Run(ctx.Done())
go deploy.serviceInformer.Run(ctx.Done())
go deploy.deploymentInformer.Run(ctx.Done())
go deploy.idleObjectReaper()
@@ -191,23 +181,12 @@ func (deploy *NewDeploy) TapService(svcHost string) error {
return nil
}
func getCachedItem(obj apiv1.ObjectReference, informer k8sCache.SharedIndexInformer) (item interface{}, exists bool, err error) {
store := informer.GetStore()
item, exists, err = store.Get(obj)
if err != nil || !exists {
item, exists, err = store.GetByKey(fmt.Sprintf("%s/%s", obj.Namespace, obj.Name))
}
return item, exists, err
}
func (deploy *NewDeploy) getServiceInfo(obj apiv1.ObjectReference) (*apiv1.Service, error) {
item, exists, err := getCachedItem(obj, deploy.serviceInformer)
item, exists, err := utils.GetCachedItem(obj, deploy.serviceInformer)
if err != nil || !exists {
deploy.logger.Debug(
"Falling back to getting service info from k8s API -- this may cause performace issues for your function.",
"Falling back to getting service info from k8s API -- this may cause performance issues for your function.",
zap.Bool("exists", exists),
zap.Error(err),
)
@@ -220,11 +199,11 @@ func (deploy *NewDeploy) getServiceInfo(obj apiv1.ObjectReference) (*apiv1.Servi
}
func (deploy *NewDeploy) getDeploymentInfo(obj apiv1.ObjectReference) (*appsv1.Deployment, error) {
item, exists, err := getCachedItem(obj, deploy.deploymentInformer)
item, exists, err := utils.GetCachedItem(obj, deploy.deploymentInformer)
if err != nil || !exists {
deploy.logger.Debug(
"Falling back to getting deployment info from k8s API -- this may cause performace issues for your function.",
"Falling back to getting deployment info from k8s API -- this may cause performance issues for your function.",
zap.Bool("exists", exists),
zap.Error(err),
)
@@ -241,9 +220,13 @@ func (deploy *NewDeploy) getDeploymentInfo(obj apiv1.ObjectReference) (*appsv1.D
// Return true if no error occurs, return false otherwise.
func (deploy *NewDeploy) IsValid(fsvc *fscache.FuncSvc) bool {
if len(strings.Split(fsvc.Address, ".")) == 0 {
deploy.logger.Error("address not found in function service")
return false
}
if len(fsvc.KubernetesObjects) == 0 {
deploy.logger.Error("no kubernetes object related to function", zap.String("function", fsvc.Function.Name))
return false
}
for _, obj := range fsvc.KubernetesObjects {
if strings.ToLower(obj.Kind) == "service" {
_, err := deploy.getServiceInfo(obj)
@@ -254,11 +237,7 @@ func (deploy *NewDeploy) IsValid(fsvc *fscache.FuncSvc) bool {
return false
}
}
}
for _, obj := range fsvc.KubernetesObjects {
if strings.ToLower(obj.Kind) == "deployment" {
} else if strings.ToLower(obj.Kind) == "deployment" {
currentDeploy, err := deploy.getDeploymentInfo(obj)
if err != nil {
if !k8sErrs.IsNotFound(err) {
@@ -266,17 +245,15 @@ func (deploy *NewDeploy) IsValid(fsvc *fscache.FuncSvc) bool {
}
return false
}
// return directly when available replicas > 0
if currentDeploy.Status.AvailableReplicas > 0 {
return true
if currentDeploy.Status.AvailableReplicas < 1 {
return false
}
}
}
return false
return true
}
// RefreshFuncPods deleted pods related to the function so that new pods are replenished
// RefreshFuncPods deletes pods related to the function so that new pods are replenished
func (deploy *NewDeploy) RefreshFuncPods(logger *zap.Logger, f fv1.Function) error {
env, err := deploy.fissionClient.CoreV1().Environments(f.Spec.Environment.Namespace).Get(context.TODO(), f.Spec.Environment.Name, metav1.GetOptions{})
@@ -378,85 +355,6 @@ func (deploy *NewDeploy) CleanupOldExecutorObjects() {
}
}
func (deploy *NewDeploy) initFuncController() (k8sCache.Store, k8sCache.Controller) {
resyncPeriod := 30 * time.Second
listWatch := k8sCache.NewListWatchFromClient(deploy.crdClient, "functions", metav1.NamespaceAll, fields.Everything())
store, controller := k8sCache.NewInformer(listWatch, &fv1.Function{}, resyncPeriod, k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
// TODO: A workaround to process items in parallel. We should use workqueue ("k8s.io/client-go/util/workqueue")
// and worker pattern to process items instead of moving process to another goroutine.
// example: https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/job/job_controller.go
go func() {
fn := obj.(*fv1.Function)
deploy.logger.Debug("create deployment for function", zap.Any("fn", fn.ObjectMeta), zap.Any("fnspec", fn.Spec))
_, err := deploy.createFunction(fn)
if err != nil {
deploy.logger.Error("error eager creating function",
zap.Error(err),
zap.Any("function", fn))
}
deploy.logger.Debug("end create deployment for function", zap.Any("fn", fn.ObjectMeta), zap.Any("fnspec", fn.Spec))
}()
},
DeleteFunc: func(obj interface{}) {
fn := obj.(*fv1.Function)
go func() {
err := deploy.deleteFunction(fn)
if err != nil {
deploy.logger.Error("error deleting function",
zap.Error(err),
zap.Any("function", fn))
}
}()
},
UpdateFunc: func(oldObj interface{}, newObj interface{}) {
oldFn := oldObj.(*fv1.Function)
newFn := newObj.(*fv1.Function)
go func() {
err := deploy.updateFunction(oldFn, newFn)
if err != nil {
deploy.logger.Error("error updating function",
zap.Error(err),
zap.Any("old_function", oldFn),
zap.Any("new_function", newFn))
}
}()
},
})
return store, controller
}
func (deploy *NewDeploy) initEnvController() (k8sCache.Store, k8sCache.Controller) {
resyncPeriod := 30 * time.Second
listWatch := k8sCache.NewListWatchFromClient(deploy.crdClient, "environments", metav1.NamespaceAll, fields.Everything())
store, controller := k8sCache.NewInformer(listWatch, &fv1.Environment{}, resyncPeriod, k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {},
DeleteFunc: func(obj interface{}) {},
UpdateFunc: func(oldObj interface{}, newObj interface{}) {
newEnv := newObj.(*fv1.Environment)
oldEnv := oldObj.(*fv1.Environment)
// Currently only an image update in environment calls for function's deployment recreation. In future there might be more attributes which would want to do it
if oldEnv.Spec.Runtime.Image != newEnv.Spec.Runtime.Image {
deploy.logger.Debug("Updating all function of the environment that changed, old env:", zap.Any("environment", oldEnv))
funcs := deploy.getEnvFunctions(&newEnv.ObjectMeta)
for _, f := range funcs {
function, err := deploy.fissionClient.CoreV1().Functions(f.ObjectMeta.Namespace).Get(context.TODO(), f.ObjectMeta.Name, metav1.GetOptions{})
if err != nil {
deploy.logger.Error("Error getting function", zap.Error(err), zap.Any("function", function))
continue
}
err = deploy.updateFuncDeployment(function, newEnv)
if err != nil {
deploy.logger.Error("Error updating function", zap.Error(err), zap.Any("function", function))
continue
}
}
}
},
})
return store, controller
}
func (deploy *NewDeploy) getEnvFunctions(m *metav1.ObjectMeta) []fv1.Function {
funcList, err := deploy.fissionClient.CoreV1().Functions(metav1.NamespaceAll).List(context.TODO(), metav1.ListOptions{})
if err != nil {
@@ -512,6 +410,13 @@ func (deploy *NewDeploy) deleteFunction(fn *fv1.Function) error {
}
func (deploy *NewDeploy) fnCreate(fn *fv1.Function) (*fscache.FuncSvc, error) {
cleanupFunc := func(ns string, name string) {
err := deploy.cleanupNewdeploy(ns, name)
if err != nil {
deploy.logger.Error("received error while cleaning function resources",
zap.String("namespace", ns), zap.String("name", name))
}
}
env, err := deploy.fissionClient.CoreV1().
Environments(fn.Spec.Environment.Namespace).
Get(context.TODO(), fn.Spec.Environment.Name, metav1.GetOptions{})
@@ -521,7 +426,7 @@ func (deploy *NewDeploy) fnCreate(fn *fv1.Function) (*fscache.FuncSvc, error) {
objName := deploy.getObjName(fn)
deployLabels := deploy.getDeployLabels(fn.ObjectMeta, env.ObjectMeta)
deployAnnotations := deploy.getDeployAnnotations(fn.ObjectMeta)
deployAnnotations := deploy.getDeployAnnotations(fn.ObjectMeta, env.ObjectMeta)
// to support backward compatibility, if the function was created in default ns, we fall back to creating the
// deployment of the function in fission-function ns
@@ -538,7 +443,7 @@ func (deploy *NewDeploy) fnCreate(fn *fv1.Function) (*fscache.FuncSvc, error) {
svc, err := deploy.createOrGetSvc(deployLabels, deployAnnotations, objName, ns)
if err != nil {
deploy.logger.Error("error creating service", zap.Error(err), zap.String("service", objName))
go deploy.cleanupNewdeploy(ns, objName) //nolint: errcheck
go cleanupFunc(ns, objName)
return nil, errors.Wrapf(err, "error creating service %v", objName)
}
svcAddress := fmt.Sprintf("%v.%v", svc.Name, svc.Namespace)
@@ -546,14 +451,14 @@ func (deploy *NewDeploy) fnCreate(fn *fv1.Function) (*fscache.FuncSvc, error) {
depl, err := deploy.createOrGetDeployment(fn, env, objName, deployLabels, deployAnnotations, ns)
if err != nil {
deploy.logger.Error("error creating deployment", zap.Error(err), zap.String("deployment", objName))
go deploy.cleanupNewdeploy(ns, objName) //nolint: errcheck
go cleanupFunc(ns, objName)
return nil, errors.Wrapf(err, "error creating deployment %v", objName)
}
hpa, err := deploy.createOrGetHpa(objName, &fn.Spec.InvokeStrategy.ExecutionStrategy, depl, deployLabels, deployAnnotations)
if err != nil {
deploy.logger.Error("error creating HPA", zap.Error(err), zap.String("hpa", objName))
go deploy.cleanupNewdeploy(ns, objName) //nolint: errcheck
go cleanupFunc(ns, objName)
return nil, errors.Wrapf(err, "error creating the HPA %v", objName)
}
@@ -761,7 +666,7 @@ func (deploy *NewDeploy) updateFuncDeployment(fn *fv1.Function, env *fv1.Environ
// Therefore, the deployment update will trigger a rolling update.
newDeployment, err := deploy.getDeploymentSpec(fn, env,
existingDepl.Spec.Replicas, // use current replicas instead of minscale in the ExecutionStrategy.
fnObjName, ns, deployLabels, deploy.getDeployAnnotations(fn.ObjectMeta))
fnObjName, ns, deployLabels, deploy.getDeployAnnotations(fn.ObjectMeta, env.ObjectMeta))
if err != nil {
deploy.updateStatus(fn, err, "failed to get new deployment spec while updating function")
return err
@@ -815,11 +720,20 @@ func (deploy *NewDeploy) fnDelete(fn *fv1.Function) error {
func (deploy *NewDeploy) getObjName(fn *fv1.Function) string {
// use meta uuid of function, this ensure we always get the same name for the same function.
uid := fn.ObjectMeta.UID[len(fn.ObjectMeta.UID)-17:]
return strings.ToLower(fmt.Sprintf("newdeploy-%v-%v-%v", fn.ObjectMeta.Name, fn.ObjectMeta.Namespace, uid))
var functionMetadata string
if len(fn.ObjectMeta.Name)+len(fn.ObjectMeta.Namespace) < 35 {
functionMetadata = fn.ObjectMeta.Name + "-" + fn.ObjectMeta.Namespace
} else {
functionMetadata = fn.ObjectMeta.Name[:17] + "-" + fn.ObjectMeta.Namespace[:17]
}
// contructed name should be 63 characters long, as it is a valid k8s name
// functionMetadata should be 35 characters long, as we take 17 characters from functionUid
// with newdeploy 10 character prefix
return strings.ToLower(fmt.Sprintf("newdeploy-%s-%s", functionMetadata, uid))
}
func (deploy *NewDeploy) getDeployLabels(fnMeta metav1.ObjectMeta, envMeta metav1.ObjectMeta) map[string]string {
return map[string]string{
deployLabels := map[string]string{
fv1.EXECUTOR_TYPE: string(fv1.ExecutorTypeNewdeploy),
fv1.ENVIRONMENT_NAME: envMeta.Name,
fv1.ENVIRONMENT_NAMESPACE: envMeta.Namespace,
@@ -828,13 +742,17 @@ func (deploy *NewDeploy) getDeployLabels(fnMeta metav1.ObjectMeta, envMeta metav
fv1.FUNCTION_NAMESPACE: fnMeta.Namespace,
fv1.FUNCTION_UID: string(fnMeta.UID),
}
for k, v := range envMeta.Labels {
deployLabels[k] = v
}
return deployLabels
}
func (deploy *NewDeploy) getDeployAnnotations(fnMeta metav1.ObjectMeta) map[string]string {
return map[string]string{
fv1.EXECUTOR_INSTANCEID_LABEL: deploy.instanceID,
fv1.FUNCTION_RESOURCE_VERSION: fnMeta.ResourceVersion,
}
func (deploy *NewDeploy) getDeployAnnotations(fnMeta metav1.ObjectMeta, envMeta metav1.ObjectMeta) map[string]string {
deployAnnotations := maps.CopyStringMap(envMeta.Annotations)
deployAnnotations[fv1.EXECUTOR_INSTANCEID_LABEL] = deploy.instanceID
deployAnnotations[fv1.FUNCTION_RESOURCE_VERSION] = fnMeta.ResourceVersion
return deployAnnotations
}
// updateStatus is a function which updates status of update.
@@ -876,7 +794,7 @@ func (deploy *NewDeploy) idleObjectReaper() {
// For function with the environment that no longer exists, executor
// scales down the deployment as usual and prints log to notify user.
if _, ok := envList[fsvc.Environment.ObjectMeta.UID]; !ok {
deploy.logger.Error("function environment no longer exists",
deploy.logger.Warn("function environment no longer exists",
zap.String("environment", fsvc.Environment.ObjectMeta.Name),
zap.String("function", fsvc.Name))
}
@@ -0,0 +1,198 @@
/*
Copyright 2018 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package poolmgr
import (
"context"
"go.uber.org/zap"
apiv1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes"
k8sCache "k8s.io/client-go/tools/cache"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
"github.com/fission/fission/pkg/utils"
)
func getIstioServiceLabels(fnName string) map[string]string {
return map[string]string{
"functionName": fnName,
}
}
func (gpm *GenericPoolManager) FunctionEventHandlers(kubernetesClient *kubernetes.Clientset, fissionfnNamespace string, istioEnabled bool) k8sCache.ResourceEventHandlerFuncs {
return k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
fn := obj.(*fv1.Function)
// Since istio only allows accessing pod through k8s service,
// for the functions with executor type "poolmgr" we need to
// create a service for sending requests to pod in pool.
// Functions with executor type "Newdeploy" is specialized at
// pod starts. In this case, just ignore such functions.
fnExecutorType := fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType
// In some cases, user may not enter the executorType explicitly, for example in his spec.yaml.
// we assume it to be of type poolmgr
if fnExecutorType != "" && fnExecutorType != fv1.ExecutorTypePoolmgr {
return
}
// create or update role-binding
envNs := fissionfnNamespace
if fn.Spec.Environment.Namespace != metav1.NamespaceDefault {
envNs = fn.Spec.Environment.Namespace
}
// TODO : Just bring to your attention during review :
// setup rolebinding is tried, if it fails, we don't return. we just log an error and move on, because :
// 1. not all functions have secrets and/or configmaps, so things will work without this rolebinding in that case.
// 2. on the contrary, when the route is tried, the env fetcher logs will show a 403 forbidden message and same will be relayed to executor.
err := utils.SetupRoleBinding(gpm.logger, kubernetesClient, fv1.SecretConfigMapGetterRB, fn.ObjectMeta.Namespace, fv1.SecretConfigMapGetterCR, fv1.ClusterRole, fv1.FissionFetcherSA, envNs)
if err != nil {
gpm.logger.Error("error creating rolebinding", zap.Error(err), zap.String("role_binding", fv1.SecretConfigMapGetterRB))
} else {
gpm.logger.Debug("successfully set up rolebinding for fetcher service account for function",
zap.String("service_account", fv1.FissionFetcherSA),
zap.String("service_account_namepsace", envNs),
zap.String("function_name", fn.ObjectMeta.Name),
zap.String("function_namespace", fn.ObjectMeta.Namespace))
}
if istioEnabled {
// create a same name service for function
// since istio only allows the traffic to service
sel := map[string]string{
"functionName": fn.ObjectMeta.Name,
"functionUid": string(fn.ObjectMeta.UID),
}
svcName := utils.GetFunctionIstioServiceName(fn.ObjectMeta.Name, fn.ObjectMeta.Namespace)
// service for accepting user traffic
svc := apiv1.Service{
ObjectMeta: metav1.ObjectMeta{
Namespace: envNs,
Name: svcName,
Labels: getIstioServiceLabels(fn.ObjectMeta.Name),
},
Spec: apiv1.ServiceSpec{
Type: apiv1.ServiceTypeClusterIP,
Ports: []apiv1.ServicePort{
// Service port name should begin with a recognized prefix, or the traffic will be
// treated as TCP traffic. (https://istio.io/docs/setup/kubernetes/additional-setup/requirements/)
{
Name: "http-fetcher",
Protocol: apiv1.ProtocolTCP,
Port: 8000,
TargetPort: intstr.FromInt(8000),
},
{
Name: "http-env",
Protocol: apiv1.ProtocolTCP,
Port: 8888,
TargetPort: intstr.FromInt(8888),
},
},
Selector: sel,
},
}
// create function istio service if it does not exist
_, err = kubernetesClient.CoreV1().Services(envNs).Create(context.TODO(), &svc, metav1.CreateOptions{})
if err != nil && !kerrors.IsAlreadyExists(err) {
gpm.logger.Error("error creating istio service for function",
zap.Error(err),
zap.String("service_name", svcName),
zap.String("function_name", fn.ObjectMeta.Name),
zap.Any("selectors", sel))
}
}
},
DeleteFunc: func(obj interface{}) {
fn := obj.(*fv1.Function)
fnExecutorType := fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType
if fnExecutorType != "" && fnExecutorType != fv1.ExecutorTypePoolmgr {
return
}
envNs := fissionfnNamespace
if fn.Spec.Environment.Namespace != metav1.NamespaceDefault {
envNs = fn.Spec.Environment.Namespace
}
if istioEnabled {
svcName := utils.GetFunctionIstioServiceName(fn.ObjectMeta.Name, fn.ObjectMeta.Namespace)
// delete function istio service
err := kubernetesClient.CoreV1().Services(envNs).Delete(context.TODO(), svcName, metav1.DeleteOptions{})
if err != nil && !kerrors.IsNotFound(err) {
gpm.logger.Error("error deleting istio service for function",
zap.Error(err),
zap.String("service_name", svcName),
zap.String("function_name", fn.ObjectMeta.Name))
}
}
},
UpdateFunc: func(oldObj, newObj interface{}) {
oldFunc := oldObj.(*fv1.Function)
newFunc := newObj.(*fv1.Function)
if oldFunc.ObjectMeta.ResourceVersion == newFunc.ObjectMeta.ResourceVersion {
return
}
envChanged := (oldFunc.Spec.Environment.Namespace != newFunc.Spec.Environment.Namespace)
executorTypeChangedToPM := (oldFunc.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType != fv1.ExecutorTypePoolmgr &&
newFunc.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType == fv1.ExecutorTypePoolmgr)
// if a func's env reference gets updated and the newly referenced env is in a different ns,
// we need to create a rolebinding in func's ns so that the fetcher-sa in env ns has access
// to fetch secrets and config maps from the func's ns.
// similarly if executorType changed to Pool Manager, we now need a rolebinding in the func ns for fetcher sa
// present in env ns because for newdeploy, the fetcher sa is in function namespace
if envChanged || executorTypeChangedToPM {
envNs := fissionfnNamespace
if newFunc.Spec.Environment.Namespace != metav1.NamespaceDefault {
envNs = newFunc.Spec.Environment.Namespace
}
err := utils.SetupRoleBinding(gpm.logger, kubernetesClient, fv1.SecretConfigMapGetterRB,
newFunc.ObjectMeta.Namespace, fv1.SecretConfigMapGetterCR, fv1.ClusterRole,
fv1.FissionFetcherSA, envNs)
if err != nil {
gpm.logger.Error("error creating rolebinding", zap.Error(err), zap.String("role_binding", fv1.SecretConfigMapGetterRB))
} else {
gpm.logger.Debug("successfully set up rolebinding for fetcher service account for function",
zap.String("service_account", fv1.FissionFetcherSA),
zap.String("service_account_namepsace", envNs),
zap.String("function_name", newFunc.ObjectMeta.Name),
zap.String("function_namespace", newFunc.ObjectMeta.Namespace))
}
}
},
}
}
@@ -1,208 +0,0 @@
/*
Copyright 2018 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package poolmgr
import (
"context"
"time"
"go.uber.org/zap"
apiv1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes"
k8sCache "k8s.io/client-go/tools/cache"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
"github.com/fission/fission/pkg/crd"
"github.com/fission/fission/pkg/utils"
)
func getIstioServiceLabels(fnName string) map[string]string {
return map[string]string{
"functionName": fnName,
}
}
func (gpm *GenericPoolManager) makeFuncController(fissionClient *crd.FissionClient,
kubernetesClient *kubernetes.Clientset, fissionfnNamespace string, istioEnabled bool) (k8sCache.Store, k8sCache.Controller) {
resyncPeriod := 30 * time.Second
lw := k8sCache.NewListWatchFromClient(fissionClient.CoreV1().RESTClient(), "functions", metav1.NamespaceAll, fields.Everything())
funcStore, controller := k8sCache.NewInformer(lw, &fv1.Function{}, resyncPeriod,
k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
fn := obj.(*fv1.Function)
// Since istio only allows accessing pod through k8s service,
// for the functions with executor type "poolmgr" we need to
// create a service for sending requests to pod in pool.
// Functions with executor type "Newdeploy" is specialized at
// pod starts. In this case, just ignore such functions.
fnExecutorType := fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType
// In some cases, user may not enter the executorType explicitly, for example in his spec.yaml.
// we assume it to be of type poolmgr
if fnExecutorType != "" && fnExecutorType != fv1.ExecutorTypePoolmgr {
return
}
// create or update role-binding
envNs := fissionfnNamespace
if fn.Spec.Environment.Namespace != metav1.NamespaceDefault {
envNs = fn.Spec.Environment.Namespace
}
// TODO : Just bring to your attention during review :
// setup rolebinding is tried, if it fails, we don't return. we just log an error and move on, because :
// 1. not all functions have secrets and/or configmaps, so things will work without this rolebinding in that case.
// 2. on the contrary, when the route is tried, the env fetcher logs will show a 403 forbidden message and same will be relayed to executor.
err := utils.SetupRoleBinding(gpm.logger, kubernetesClient, fv1.SecretConfigMapGetterRB, fn.ObjectMeta.Namespace, fv1.SecretConfigMapGetterCR, fv1.ClusterRole, fv1.FissionFetcherSA, envNs)
if err != nil {
gpm.logger.Error("error creating rolebinding", zap.Error(err), zap.String("role_binding", fv1.SecretConfigMapGetterRB))
} else {
gpm.logger.Debug("successfully set up rolebinding for fetcher service account for function",
zap.String("service_account", fv1.FissionFetcherSA),
zap.String("service_account_namepsace", envNs),
zap.String("function_name", fn.ObjectMeta.Name),
zap.String("function_namespace", fn.ObjectMeta.Namespace))
}
if istioEnabled {
// create a same name service for function
// since istio only allows the traffic to service
sel := map[string]string{
"functionName": fn.ObjectMeta.Name,
"functionUid": string(fn.ObjectMeta.UID),
}
svcName := utils.GetFunctionIstioServiceName(fn.ObjectMeta.Name, fn.ObjectMeta.Namespace)
// service for accepting user traffic
svc := apiv1.Service{
ObjectMeta: metav1.ObjectMeta{
Namespace: envNs,
Name: svcName,
Labels: getIstioServiceLabels(fn.ObjectMeta.Name),
},
Spec: apiv1.ServiceSpec{
Type: apiv1.ServiceTypeClusterIP,
Ports: []apiv1.ServicePort{
// Service port name should begin with a recognized prefix, or the traffic will be
// treated as TCP traffic. (https://istio.io/docs/setup/kubernetes/additional-setup/requirements/)
{
Name: "http-fetcher",
Protocol: apiv1.ProtocolTCP,
Port: 8000,
TargetPort: intstr.FromInt(8000),
},
{
Name: "http-env",
Protocol: apiv1.ProtocolTCP,
Port: 8888,
TargetPort: intstr.FromInt(8888),
},
},
Selector: sel,
},
}
// create function istio service if it does not exist
_, err = kubernetesClient.CoreV1().Services(envNs).Create(context.TODO(), &svc, metav1.CreateOptions{})
if err != nil && !kerrors.IsAlreadyExists(err) {
gpm.logger.Error("error creating istio service for function",
zap.Error(err),
zap.String("service_name", svcName),
zap.String("function_name", fn.ObjectMeta.Name),
zap.Any("selectors", sel))
}
}
},
DeleteFunc: func(obj interface{}) {
fn := obj.(*fv1.Function)
fnExecutorType := fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType
if fnExecutorType != "" && fnExecutorType != fv1.ExecutorTypePoolmgr {
return
}
envNs := fissionfnNamespace
if fn.Spec.Environment.Namespace != metav1.NamespaceDefault {
envNs = fn.Spec.Environment.Namespace
}
if istioEnabled {
svcName := utils.GetFunctionIstioServiceName(fn.ObjectMeta.Name, fn.ObjectMeta.Namespace)
// delete function istio service
err := kubernetesClient.CoreV1().Services(envNs).Delete(context.TODO(), svcName, metav1.DeleteOptions{})
if err != nil && !kerrors.IsNotFound(err) {
gpm.logger.Error("error deleting istio service for function",
zap.Error(err),
zap.String("service_name", svcName),
zap.String("function_name", fn.ObjectMeta.Name))
}
}
},
UpdateFunc: func(oldObj, newObj interface{}) {
oldFunc := oldObj.(*fv1.Function)
newFunc := newObj.(*fv1.Function)
if oldFunc.ObjectMeta.ResourceVersion == newFunc.ObjectMeta.ResourceVersion {
return
}
envChanged := (oldFunc.Spec.Environment.Namespace != newFunc.Spec.Environment.Namespace)
executorTypeChangedToPM := (oldFunc.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType != fv1.ExecutorTypePoolmgr &&
newFunc.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType == fv1.ExecutorTypePoolmgr)
// if a func's env reference gets updated and the newly referenced env is in a different ns,
// we need to create a rolebinding in func's ns so that the fetcher-sa in env ns has access
// to fetch secrets and config maps from the func's ns.
// similarly if executorType changed to Pool Manager, we now need a rolebinding in the func ns for fetcher sa
// present in env ns because for newdeploy, the fetcher sa is in function namespace
if envChanged || executorTypeChangedToPM {
envNs := fissionfnNamespace
if newFunc.Spec.Environment.Namespace != metav1.NamespaceDefault {
envNs = newFunc.Spec.Environment.Namespace
}
err := utils.SetupRoleBinding(gpm.logger, kubernetesClient, fv1.SecretConfigMapGetterRB,
newFunc.ObjectMeta.Namespace, fv1.SecretConfigMapGetterCR, fv1.ClusterRole,
fv1.FissionFetcherSA, envNs)
if err != nil {
gpm.logger.Error("error creating rolebinding", zap.Error(err), zap.String("role_binding", fv1.SecretConfigMapGetterRB))
} else {
gpm.logger.Debug("successfully set up rolebinding for fetcher service account for function",
zap.String("service_account", fv1.FissionFetcherSA),
zap.String("service_account_namepsace", envNs),
zap.String("function_name", newFunc.ObjectMeta.Name),
zap.String("function_namespace", newFunc.ObjectMeta.Namespace))
}
}
},
})
return funcStore, controller
}
+56 -34
View File
@@ -28,7 +28,6 @@ import (
"time"
"github.com/dchest/uniuri"
"github.com/fission/fission/pkg/utils"
"github.com/pkg/errors"
"go.uber.org/zap"
appsv1 "k8s.io/api/apps/v1"
@@ -50,6 +49,8 @@ import (
"github.com/fission/fission/pkg/executor/util"
fetcherClient "github.com/fission/fission/pkg/fetcher/client"
fetcherConfig "github.com/fission/fission/pkg/fetcher/config"
"github.com/fission/fission/pkg/utils"
"github.com/fission/fission/pkg/utils/maps"
)
type (
@@ -71,8 +72,7 @@ type (
fissionClient *crd.FissionClient
fetcherConfig *fetcherConfig.Config
stopReadyPodControllerCh chan struct{}
readyPodController cache.Controller
readyPodIndexer cache.Indexer
readyPodInformer cache.SharedIndexInformer
readyPodQueue workqueue.DelayingInterface
poolInstanceID string // small random string to uniquify pod names
instanceID string // poolmgr instance id
@@ -156,47 +156,73 @@ func MakeGenericPool(
}
func (gp *GenericPool) getEnvironmentPoolLabels() map[string]string {
return map[string]string{
fv1.EXECUTOR_TYPE: string(fv1.ExecutorTypePoolmgr),
fv1.ENVIRONMENT_NAME: gp.env.ObjectMeta.Name,
fv1.ENVIRONMENT_NAMESPACE: gp.env.ObjectMeta.Namespace,
fv1.ENVIRONMENT_UID: string(gp.env.ObjectMeta.UID),
"managed": "true", // this allows us to easily find pods managed by the deployment
}
envLabels := maps.CopyStringMap(gp.env.ObjectMeta.Labels)
envLabels[fv1.EXECUTOR_TYPE] = string(fv1.ExecutorTypePoolmgr)
envLabels[fv1.ENVIRONMENT_NAME] = gp.env.ObjectMeta.Name
envLabels[fv1.ENVIRONMENT_NAMESPACE] = gp.env.ObjectMeta.Namespace
envLabels[fv1.ENVIRONMENT_UID] = string(gp.env.ObjectMeta.UID)
envLabels["managed"] = "true" // this allows us to easily find pods managed by the deployment
return envLabels
}
func (gp *GenericPool) getDeployAnnotations() map[string]string {
return map[string]string{
fv1.EXECUTOR_INSTANCEID_LABEL: gp.instanceID,
deployAnnotations := maps.CopyStringMap(gp.env.Annotations)
deployAnnotations[fv1.EXECUTOR_INSTANCEID_LABEL] = gp.instanceID
return deployAnnotations
}
func (gp *GenericPool) checkMetricsApi() bool {
apiGroups, err := gp.metricsClient.DiscoveryClient.ServerGroups()
if err != nil {
gp.logger.Error("faied to discover API groups", zap.Error(err))
return false
}
return utils.SupportedMetricsAPIVersionAvailable(apiGroups)
}
func (gp *GenericPool) updateCPUUtilizationSvc() {
for {
var metricsApiAvailabe bool
checkDuration := 30
if !gp.checkMetricsApi() {
checkDuration = 180
gp.logger.Warn("Metrics API not available")
}
serviceFunc := func() {
podMetricsList, err := gp.metricsClient.MetricsV1beta1().PodMetricses(gp.namespace).List(context.TODO(), metav1.ListOptions{
LabelSelector: "managed=false",
})
if err != nil {
gp.logger.Error("failed to fetch pod metrics list", zap.Error(err))
} else {
gp.logger.Debug("pods found", zap.Any("length", len(podMetricsList.Items)))
for _, val := range podMetricsList.Items {
p, _ := resource.ParseQuantity("0m")
for _, container := range val.Containers {
p.Add(container.Usage["cpu"])
}
if value, ok := gp.podFSVCMap.Load(val.ObjectMeta.Name); ok {
if valArray, ok1 := value.([]interface{}); ok1 {
function, address := valArray[0], valArray[1]
gp.fsCache.SetCPUUtilizaton(function.(string), address.(string), p)
gp.logger.Info(fmt.Sprintf("updated function %s, address %s, cpuUsage %+v", function.(string), address.(string), p))
}
return
}
gp.logger.Debug("pods found", zap.Any("length", len(podMetricsList.Items)))
for _, val := range podMetricsList.Items {
p, _ := resource.ParseQuantity("0m")
for _, container := range val.Containers {
p.Add(container.Usage["cpu"])
}
if value, ok := gp.podFSVCMap.Load(val.ObjectMeta.Name); ok {
if valArray, ok1 := value.([]interface{}); ok1 {
function, address := valArray[0], valArray[1]
gp.fsCache.SetCPUUtilizaton(function.(string), address.(string), p)
gp.logger.Info(fmt.Sprintf("updated function %s, address %s, cpuUsage %+v", function.(string), address.(string), p))
}
}
}
}
time.Sleep(30 * time.Second)
for {
if metricsApiAvailabe {
serviceFunc()
} else {
if gp.checkMetricsApi() {
metricsApiAvailabe = true
checkDuration = 30
}
}
time.Sleep(time.Duration(checkDuration) * time.Second)
}
}
@@ -223,7 +249,7 @@ func (gp *GenericPool) choosePod(newLabels map[string]string) (string, *apiv1.Po
key = item.(string)
gp.logger.Debug("got key from the queue", zap.String("key", key))
obj, exists, err := gp.readyPodIndexer.GetByKey(key)
obj, exists, err := gp.readyPodInformer.GetIndexer().GetByKey(key)
if err != nil {
gp.logger.Error("fetching object from store failed", zap.String("key", key), zap.Error(err))
return "", nil, err
@@ -688,11 +714,7 @@ func (gp *GenericPool) getFuncSvc(ctx context.Context, fn *fv1.Function) (*fscac
Atime: time.Now(),
}
if gp.fsCache.PodToFsvc == nil {
gp.fsCache.PodToFsvc = make(map[string]*fscache.FuncSvc)
}
gp.fsCache.PodToFsvc[pod.GetObjectMeta().GetName()] = fsvc
gp.fsCache.PodToFsvc.Store(pod.GetObjectMeta().GetName(), fsvc)
gp.podFSVCMap.Store(pod.ObjectMeta.Name, []interface{}{crd.CacheKey(fsvc.Function), fsvc.Address})
gp.fsCache.AddFunc(*fsvc)
+29 -20
View File
@@ -76,11 +76,10 @@ type (
enableIstio bool
fetcherConfig *fetcherConfig.Config
funcStore k8sCache.Store
funcController k8sCache.Controller
pkgStore k8sCache.Store
pkgController k8sCache.Controller
podInformer k8sCache.SharedIndexInformer
funcInformer *k8sCache.SharedIndexInformer
pkgInformer *k8sCache.SharedIndexInformer
podInformer k8sCache.SharedIndexInformer
defaultIdlePodReapTime time.Duration
}
@@ -103,7 +102,10 @@ func MakeGenericPoolManager(
metricsClient *metricsclient.Clientset,
functionNamespace string,
fetcherConfig *fetcherConfig.Config,
instanceID string) (executortype.ExecutorType, error) {
instanceID string,
funcInformer *k8sCache.SharedIndexInformer,
pkgInformer *k8sCache.SharedIndexInformer,
) (executortype.ExecutorType, error) {
gpmLogger := logger.Named("generic_pool_manager")
@@ -120,6 +122,8 @@ func MakeGenericPoolManager(
requestChannel: make(chan *request),
defaultIdlePodReapTime: 2 * time.Minute,
fetcherConfig: fetcherConfig,
funcInformer: funcInformer,
pkgInformer: pkgInformer,
}
go gpm.service()
@@ -132,16 +136,14 @@ func MakeGenericPoolManager(
gpm.enableIstio = istio
}
gpm.funcStore, gpm.funcController = gpm.makeFuncController(
gpm.fissionClient, gpm.kubernetesClient, gpm.namespace, gpm.enableIstio)
(*gpm.funcInformer).AddEventHandler(gpm.FunctionEventHandlers(gpm.kubernetesClient, gpm.namespace, gpm.enableIstio))
(*gpm.pkgInformer).AddEventHandler(gpm.PackageEventHandlers(gpm.kubernetesClient, gpm.namespace))
gpm.pkgStore, gpm.pkgController = gpm.makePkgController(gpm.fissionClient, gpm.kubernetesClient, gpm.namespace)
informerFactory, err := utils.GetInformerFacoryByExecutor(gpm.kubernetesClient, fv1.ExecutorTypePoolmgr)
kubeInformerFactory, err := utils.GetInformerFactoryByExecutor(gpm.kubernetesClient, fv1.ExecutorTypePoolmgr)
if err != nil {
return nil, err
}
gpm.podInformer = informerFactory.Core().V1().Pods().Informer()
gpm.podInformer = kubeInformerFactory.Core().V1().Pods().Informer()
return gpm, nil
}
@@ -149,8 +151,6 @@ func (gpm *GenericPoolManager) Run(ctx context.Context) {
// eagerPoolCreator must run after CleanupOldExecutorObjects.
// Otherwise, the poolmanager may wrongly delete the deployment.
go gpm.eagerPoolCreator()
go gpm.funcController.Run(ctx.Done())
go gpm.pkgController.Run(ctx.Done())
go gpm.podInformer.Run(ctx.Done())
go gpm.idleObjectReaper()
}
@@ -211,7 +211,7 @@ func (gpm *GenericPoolManager) getPodInfo(obj apiv1.ObjectReference) (*apiv1.Pod
}
if err != nil || !exists {
gpm.logger.Debug("Falling back to getting pod info from k8s API -- this may cause performace issues for your function.")
gpm.logger.Debug("Falling back to getting pod info from k8s API -- this may cause performance issues for your function.")
pod, err := gpm.kubernetesClient.CoreV1().Pods(obj.Namespace).Get(context.TODO(), obj.Name, metav1.GetOptions{})
return pod, err
}
@@ -644,7 +644,7 @@ func (gpm *GenericPoolManager) idleObjectReaper() {
continue
}
if _, ok := gpm.fsCache.WebsocketFsvc[fsvc.Name]; ok {
if _, ok := gpm.fsCache.WebsocketFsvc.Load(fsvc.Name); ok {
continue
}
// For function with the environment that no longer exists, executor
@@ -725,8 +725,13 @@ func (gpm *GenericPoolManager) WebsocketStartEventChecker(kubeClient *kubernetes
zap.String("Pod name", mObj.GetName()))
podName := strings.SplitAfter(mObj.GetName(), ".")
if fsvc, ok := gpm.fsCache.PodToFsvc[strings.TrimSuffix(podName[0], ".")]; ok {
gpm.fsCache.WebsocketFsvc[fsvc.Name] = true
if fsvc, ok := gpm.fsCache.PodToFsvc.Load(strings.TrimSuffix(podName[0], ".")); ok {
fsvc, ok := fsvc.(*fscache.FuncSvc)
if !ok {
gpm.logger.Error("could not covert item from PodToFsvc")
return
}
gpm.fsCache.WebsocketFsvc.Store(fsvc.Name, true)
}
},
})
@@ -761,8 +766,12 @@ func (gpm *GenericPoolManager) NoActiveConnectionEventChecker(kubeClient *kubern
zap.String("Pod name", mObj.GetName()))
podName := strings.SplitAfter(mObj.GetName(), ".")
if fsvc, ok := gpm.fsCache.PodToFsvc[strings.TrimSuffix(podName[0], ".")]; ok {
if fsvc, ok := gpm.fsCache.PodToFsvc.Load(strings.TrimSuffix(podName[0], ".")); ok {
fsvc, ok := fsvc.(*fscache.FuncSvc)
if !ok {
gpm.logger.Error("could not covert value from PodToFsvc")
return
}
gpm.fsCache.DeleteFunctionSvc(fsvc)
for i := range fsvc.KubernetesObjects {
gpm.logger.Info("release idle function resources due to inactivity",
@@ -0,0 +1,99 @@
/*
Copyright 2018 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package poolmgr
import (
"go.uber.org/zap"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
k8sCache "k8s.io/client-go/tools/cache"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
"github.com/fission/fission/pkg/utils"
)
func (gpm *GenericPoolManager) PackageEventHandlers(kubernetesClient *kubernetes.Clientset, fissionfnNamespace string) k8sCache.ResourceEventHandlerFuncs {
return k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
pkg := obj.(*fv1.Package)
gpm.logger.Debug("list watch for package reported a new package addition",
zap.String("package_name", pkg.ObjectMeta.Name),
zap.String("package_namespace", pkg.ObjectMeta.Namespace))
// create or update role-binding for fetcher sa in env ns to be able to get the pkg contents from pkg namespace
envNs := fissionfnNamespace
if pkg.Spec.Environment.Namespace != metav1.NamespaceDefault {
envNs = pkg.Spec.Environment.Namespace
}
// here, we return if we hit an error during rolebinding setup. this is because this rolebinding is mandatory for
// every function's package to be loaded into its env. without that, there's no point to move forward.
err := utils.SetupRoleBinding(gpm.logger, kubernetesClient, fv1.PackageGetterRB, pkg.ObjectMeta.Namespace, fv1.PackageGetterCR, fv1.ClusterRole, fv1.FissionFetcherSA, envNs)
if err != nil {
gpm.logger.Error("error creating rolebinding for package",
zap.Error(err),
zap.String("role_binding", fv1.PackageGetterRB),
zap.String("package_name", pkg.ObjectMeta.Name),
zap.String("package_namespace", pkg.ObjectMeta.Namespace))
return
}
gpm.logger.Debug("successfully set up rolebinding for fetcher service account",
zap.String("service_account", fv1.FissionFetcherSA),
zap.String("service_account_namespace", envNs),
zap.String("package_name", pkg.ObjectMeta.Name),
zap.String("package_namespace", pkg.ObjectMeta.Namespace))
},
UpdateFunc: func(oldObj, newObj interface{}) {
oldPkg := oldObj.(*fv1.Package)
newPkg := newObj.(*fv1.Package)
if oldPkg.ObjectMeta.ResourceVersion == newPkg.ObjectMeta.ResourceVersion {
return
}
// if a pkg's env reference gets updated and the newly referenced env is in a different ns,
// we need to update the role-binding in pkg ns to grant permissions to the fetcher-sa in env ns
// to do a get on pkg
if oldPkg.Spec.Environment.Namespace != newPkg.Spec.Environment.Namespace {
envNs := fissionfnNamespace
if newPkg.Spec.Environment.Namespace != metav1.NamespaceDefault {
envNs = newPkg.Spec.Environment.Namespace
}
err := utils.SetupRoleBinding(gpm.logger, kubernetesClient, fv1.PackageGetterRB,
newPkg.ObjectMeta.Namespace, fv1.PackageGetterCR, fv1.ClusterRole,
fv1.FissionFetcherSA, envNs)
if err != nil {
gpm.logger.Error("error updating rolebinding for package",
zap.Error(err),
zap.String("role_binding", fv1.PackageGetterRB),
zap.String("package_name", newPkg.ObjectMeta.Name),
zap.String("package_namespace", newPkg.ObjectMeta.Namespace))
return
}
gpm.logger.Debug("successfully updated rolebinding for fetcher service account",
zap.String("service_account", fv1.FissionFetcherSA),
zap.String("service_account_namespace", envNs),
zap.String("package_name", newPkg.ObjectMeta.Name),
zap.String("package_namespace", newPkg.ObjectMeta.Namespace))
}
},
}
}
@@ -1,111 +0,0 @@
/*
Copyright 2018 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package poolmgr
import (
"time"
"github.com/fission/fission/pkg/utils"
"go.uber.org/zap"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/client-go/kubernetes"
k8sCache "k8s.io/client-go/tools/cache"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
"github.com/fission/fission/pkg/crd"
)
// TODO : It may make sense to make each of add, update, delete funcs run as separate go routines.
func (gpm *GenericPoolManager) makePkgController(fissionClient *crd.FissionClient,
kubernetesClient *kubernetes.Clientset, fissionfnNamespace string) (k8sCache.Store, k8sCache.Controller) {
resyncPeriod := 30 * time.Second
lw := k8sCache.NewListWatchFromClient(fissionClient.CoreV1().RESTClient(), "packages", metav1.NamespaceAll, fields.Everything())
pkgStore, controller := k8sCache.NewInformer(lw, &fv1.Package{}, resyncPeriod,
k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
pkg := obj.(*fv1.Package)
gpm.logger.Debug("list watch for package reported a new package addition",
zap.String("package_name", pkg.ObjectMeta.Name),
zap.String("package_namespace", pkg.ObjectMeta.Namespace))
// create or update role-binding for fetcher sa in env ns to be able to get the pkg contents from pkg namespace
envNs := fissionfnNamespace
if pkg.Spec.Environment.Namespace != metav1.NamespaceDefault {
envNs = pkg.Spec.Environment.Namespace
}
// here, we return if we hit an error during rolebinding setup. this is because this rolebinding is mandatory for
// every function's package to be loaded into its env. without that, there's no point to move forward.
err := utils.SetupRoleBinding(gpm.logger, kubernetesClient, fv1.PackageGetterRB, pkg.ObjectMeta.Namespace, fv1.PackageGetterCR, fv1.ClusterRole, fv1.FissionFetcherSA, envNs)
if err != nil {
gpm.logger.Error("error creating rolebinding for package",
zap.Error(err),
zap.String("role_binding", fv1.PackageGetterRB),
zap.String("package_name", pkg.ObjectMeta.Name),
zap.String("package_namespace", pkg.ObjectMeta.Namespace))
return
}
gpm.logger.Debug("successfully set up rolebinding for fetcher service account",
zap.String("service_account", fv1.FissionFetcherSA),
zap.String("service_account_namespace", envNs),
zap.String("package_name", pkg.ObjectMeta.Name),
zap.String("package_namespace", pkg.ObjectMeta.Namespace))
},
UpdateFunc: func(oldObj, newObj interface{}) {
oldPkg := oldObj.(*fv1.Package)
newPkg := newObj.(*fv1.Package)
if oldPkg.ObjectMeta.ResourceVersion == newPkg.ObjectMeta.ResourceVersion {
return
}
// if a pkg's env reference gets updated and the newly referenced env is in a different ns,
// we need to update the role-binding in pkg ns to grant permissions to the fetcher-sa in env ns
// to do a get on pkg
if oldPkg.Spec.Environment.Namespace != newPkg.Spec.Environment.Namespace {
envNs := fissionfnNamespace
if newPkg.Spec.Environment.Namespace != metav1.NamespaceDefault {
envNs = newPkg.Spec.Environment.Namespace
}
err := utils.SetupRoleBinding(gpm.logger, kubernetesClient, fv1.PackageGetterRB,
newPkg.ObjectMeta.Namespace, fv1.PackageGetterCR, fv1.ClusterRole,
fv1.FissionFetcherSA, envNs)
if err != nil {
gpm.logger.Error("error updating rolebinding for package",
zap.Error(err),
zap.String("role_binding", fv1.PackageGetterRB),
zap.String("package_name", newPkg.ObjectMeta.Name),
zap.String("package_namespace", newPkg.ObjectMeta.Namespace))
return
}
gpm.logger.Debug("successfully updated rolebinding for fetcher service account",
zap.String("service_account", fv1.FissionFetcherSA),
zap.String("service_account_namespace", envNs),
zap.String("package_name", newPkg.ObjectMeta.Name),
zap.String("package_namespace", newPkg.ObjectMeta.Namespace))
}
},
})
return pkgStore, controller
}
@@ -4,27 +4,31 @@ import (
"time"
"go.uber.org/zap"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
informers "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
)
func (gp *GenericPool) newPodInformer() cache.SharedIndexInformer {
optionsModifier := func(options *metav1.ListOptions) {
options.LabelSelector = labels.Set(
gp.deployment.Spec.Selector.MatchLabels).AsSelector().String()
options.FieldSelector = "status.phase=Running"
}
return informers.NewFilteredPodInformer(gp.kubernetesClient, gp.namespace, 0, nil, optionsModifier)
}
func (gp *GenericPool) startReadyPodController() {
// create the pod watcher to filter by labels
// Filtering pod by phase=Running. In some cases the pod can be in
// different state than Running, for example Kubernetes sets a
// pod to Termination while k8s waits for the grace period of
// the pod, even if all the containers are in Ready state.
optionsModifier := func(options *metav1.ListOptions) {
options.LabelSelector = labels.Set(
gp.deployment.Spec.Selector.MatchLabels).AsSelector().String()
options.FieldSelector = "status.phase=Running"
}
readyPodWatcher := cache.NewFilteredListWatchFromClient(gp.kubernetesClient.CoreV1().RESTClient(), "pods", gp.namespace, optionsModifier)
gp.readyPodQueue = workqueue.NewDelayingQueue()
gp.readyPodIndexer, gp.readyPodController = cache.NewIndexerInformer(readyPodWatcher, &apiv1.Pod{}, 0, cache.ResourceEventHandlerFuncs{
gp.readyPodInformer = gp.newPodInformer()
gp.readyPodInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
key, err := cache.MetaNamespaceKeyFunc(obj)
if err == nil {
@@ -39,7 +43,7 @@ func (gp *GenericPool) startReadyPodController() {
gp.logger.Debug("delete func called", zap.String("key", key))
}
},
}, cache.Indexers{})
go gp.readyPodController.Run(gp.stopReadyPodControllerCh)
})
go gp.readyPodInformer.Run(gp.stopReadyPodControllerCh)
gp.logger.Info("readyPod controller started", zap.String("env", gp.env.ObjectMeta.Name), zap.String("envID", string(gp.env.ObjectMeta.UID)))
}
+3 -4
View File
@@ -18,6 +18,7 @@ package fscache
import (
"fmt"
"sync"
"time"
"github.com/pkg/errors"
@@ -68,8 +69,8 @@ type (
byAddress *cache.Cache // address -> function : map[string]metav1.ObjectMeta
byFunctionUID *cache.Cache // function uid -> function : map[string]metav1.ObjectMeta
connFunctionCache *poolcache.Cache // function-key -> funcSvc : map[string]*funcSvc
PodToFsvc map[string]*FuncSvc
WebsocketFsvc map[string]bool
PodToFsvc sync.Map // pod-name -> funcSvc: map[string]*FuncSvc
WebsocketFsvc sync.Map // funcSvc-name -> bool: map[string]bool
requestChannel chan *fscRequest
}
@@ -111,8 +112,6 @@ func MakeFunctionServiceCache(logger *zap.Logger) *FunctionServiceCache {
byFunctionUID: cache.MakeCache(0, 0),
connFunctionCache: poolcache.NewPoolCache(),
requestChannel: make(chan *fscRequest),
PodToFsvc: make(map[string]*FuncSvc),
WebsocketFsvc: make(map[string]bool),
}
go fsc.service()
return fsc
+1 -1
View File
@@ -88,7 +88,7 @@ func (fsc *FunctionServiceCache) setFuncAlive(funcname, funcuid string, isAlive
// ReapTime is the amount of time taken to reap a pod
func (fsc *FunctionServiceCache) ReapTime(funcName, funcAddress string, time float64) {
funcReapTime.WithLabelValues(funcName, funcAddress).Observe(float64(time))
funcReapTime.WithLabelValues(funcName, funcAddress).Observe(time)
}
// IdleTime is the amount of time it took Reaper to find out the pod was idle
+64
View File
@@ -17,10 +17,16 @@ limitations under the License.
package util
import (
"context"
"errors"
"sync"
"time"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
)
// ApplyImagePullSecret applies image pull secret to the give pod spec.
@@ -49,3 +55,61 @@ func WaitTimeout(wg *sync.WaitGroup, timeout time.Duration) {
case <-time.After(timeout):
}
}
// ConvertConfigSecrets returns envFromSource which can be passed directly into the pod spec
func ConvertConfigSecrets(fn *fv1.Function, kc *kubernetes.Clientset) ([]apiv1.EnvFromSource, error) {
cmList := fn.Spec.ConfigMaps
secList := fn.Spec.Secrets
cmEnvSources := make([]*apiv1.ConfigMapEnvSource, 0)
secEnvSources := make([]*apiv1.SecretEnvSource, 0)
for _, cm := range cmList {
if cm.Namespace != fn.Namespace {
return nil, errors.New("Function should not reference config map of different namespace")
}
_, err := kc.CoreV1().ConfigMaps(cm.Namespace).Get(context.TODO(), cm.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
cmEnvSource := &apiv1.ConfigMapEnvSource{
LocalObjectReference: apiv1.LocalObjectReference{Name: cm.Name},
}
cmEnvSources = append(cmEnvSources, cmEnvSource)
}
for _, sec := range secList {
if sec.Namespace != fn.Namespace {
return nil, errors.New("Function should not reference secret of different namespace")
}
_, err := kc.CoreV1().Secrets(sec.Namespace).Get(context.TODO(), sec.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
secEnvSource := &apiv1.SecretEnvSource{
LocalObjectReference: apiv1.LocalObjectReference{Name: sec.Name},
}
secEnvSources = append(secEnvSources, secEnvSource)
}
envFromSources := make([]apiv1.EnvFromSource, 0)
for _, cmEnvSource := range cmEnvSources {
envFromSource := apiv1.EnvFromSource{
ConfigMapRef: cmEnvSource,
}
envFromSources = append(envFromSources, envFromSource)
}
for _, secEnvSource := range secEnvSources {
envFromSource := apiv1.EnvFromSource{
SecretRef: secEnvSource,
}
envFromSources = append(envFromSources, envFromSource)
}
return envFromSources, nil
}
+9 -4
View File
@@ -31,10 +31,13 @@ func Commands() *cobra.Command {
}
wrapper.SetFlags(createCmd, flag.FlagSet{
Required: []flag.Flag{flag.EnvName, flag.EnvImage},
Optional: []flag.Flag{flag.EnvPoolsize, flag.EnvBuilderImage, flag.EnvBuildCmd,
Optional: []flag.Flag{
flag.EnvPoolsize, flag.EnvBuilderImage, flag.EnvBuildCmd,
flag.RunTimeMinCPU, flag.RunTimeMaxCPU, flag.RunTimeMinMemory, flag.RunTimeMaxMemory,
flag.EnvTerminationGracePeriod, flag.EnvVersion, flag.EnvImagePullSecret,
flag.EnvExternalNetwork, flag.EnvKeepArchive, flag.NamespaceEnvironment, flag.SpecSave, flag.SpecDry},
flag.EnvTerminationGracePeriod, flag.EnvVersion, flag.EnvImagePullSecret, flag.EnvKeepArchive,
flag.NamespaceEnvironment, flag.EnvExternalNetwork,
flag.Labels, flag.Annotation,
flag.SpecSave, flag.SpecDry},
})
getCmd := &cobra.Command{
@@ -57,7 +60,9 @@ func Commands() *cobra.Command {
Optional: []flag.Flag{flag.EnvImage, flag.EnvPoolsize,
flag.EnvBuilderImage, flag.EnvBuildCmd, flag.EnvImagePullSecret,
flag.RunTimeMinCPU, flag.RunTimeMaxCPU, flag.RunTimeMinMemory, flag.RunTimeMaxMemory,
flag.EnvTerminationGracePeriod, flag.EnvKeepArchive, flag.NamespaceEnvironment, flag.EnvExternalNetwork},
flag.EnvTerminationGracePeriod, flag.EnvKeepArchive,
flag.NamespaceEnvironment, flag.EnvExternalNetwork,
flag.Labels, flag.Annotation},
})
deleteCmd := &cobra.Command{
+5 -1
View File
@@ -129,7 +129,7 @@ func createEnvironmentFromCmd(input cli.Input) (*fv1.Environment, error) {
poolsize := input.Int(flagkey.EnvPoolsize)
if poolsize < 1 {
console.Warn("poolsize is not positive, if you are using pool manager please set postive value")
console.Warn("poolsize is not positive, if you are using pool manager please set positive value")
}
envBuilderImg := input.String(flagkey.EnvBuilderImage)
@@ -179,6 +179,10 @@ func createEnvironmentFromCmd(input cli.Input) (*fv1.Environment, error) {
},
}
err = util.ApplyLabelsAndAnnotations(input, &env.ObjectMeta)
if err != nil {
return nil, err
}
err = env.Validate()
if err != nil {
return nil, fv1.AggregateValidationErrors("Environment", err)
+7 -1
View File
@@ -31,6 +31,7 @@ import (
"github.com/fission/fission/pkg/fission-cli/cmd"
"github.com/fission/fission/pkg/fission-cli/console"
flagkey "github.com/fission/fission/pkg/fission-cli/flag/key"
"github.com/fission/fission/pkg/fission-cli/util"
"github.com/fission/fission/pkg/utils"
)
@@ -66,6 +67,11 @@ func (opts *UpdateSubCommand) complete(input cli.Input) error {
}
opts.env = env
err = util.ApplyLabelsAndAnnotations(input, &opts.env.ObjectMeta)
if err != nil {
return err
}
return nil
}
@@ -105,7 +111,7 @@ func updateExistingEnvironmentWithCmd(env *fv1.Environment, input cli.Input) (*f
if input.IsSet(flagkey.EnvPoolsize) {
env.Spec.Poolsize = input.Int(flagkey.EnvPoolsize)
if env.Spec.Poolsize < 1 {
console.Warn("poolsize is not positive, if you are using pool manager please set postive value")
console.Warn("poolsize is not positive, if you are using pool manager please set positive value")
}
}
+51 -4
View File
@@ -36,7 +36,7 @@ func Commands() *cobra.Command {
flag.FnExecutorType, flag.FnCfgMap, flag.FnSecret,
flag.FnSpecializationTimeout, flag.FnExecutionTimeout,
flag.FnIdleTimeout, flag.FnConcurrency, flag.FnRequestsPerPod,
flag.FnOnceOnly,
flag.FnOnceOnly, flag.Labels, flag.Annotation,
// TODO retired pkg & trigger related flags from function cmd
flag.PkgCode, flag.PkgSrcArchive, flag.PkgDeployArchive,
@@ -88,7 +88,7 @@ func Commands() *cobra.Command {
flag.FnExecutorType, flag.FnSecret, flag.FnCfgMap,
flag.FnSpecializationTimeout, flag.FnExecutionTimeout,
flag.FnIdleTimeout, flag.FnConcurrency, flag.FnRequestsPerPod,
flag.FnOnceOnly,
flag.FnOnceOnly, flag.Labels, flag.Annotation,
flag.PkgCode, flag.PkgSrcArchive, flag.PkgDeployArchive,
flag.PkgSrcChecksum, flag.PkgDeployChecksum, flag.PkgInsecure,
@@ -154,13 +154,60 @@ func Commands() *cobra.Command {
},
})
runContainerCmd := &cobra.Command{
Use: "run-container",
Aliases: []string{"runc"},
Short: "Alpha: Run a container image as a function",
RunE: wrapper.Wrapper(RunContainer),
}
wrapper.SetFlags(runContainerCmd, flag.FlagSet{
Required: []flag.Flag{flag.FnName, flag.FnImageName},
Optional: []flag.Flag{
flag.FnPort, flag.FnCommand, flag.FnArgs,
flag.FnCfgMap, flag.FnSecret,
flag.FnExecutionTimeout,
flag.FnIdleTimeout,
flag.Labels, flag.Annotation,
// flag for newdeploy to use.
flag.RunTimeMinCPU, flag.RunTimeMaxCPU, flag.RunTimeMinMemory,
flag.RunTimeMaxMemory, flag.ReplicasMin,
flag.ReplicasMax, flag.RunTimeTargetCPU,
flag.NamespaceFunction, flag.SpecSave, flag.SpecDry,
},
})
updateContainerCmd := &cobra.Command{
Use: "update-container",
Aliases: []string{"updatec"},
Short: "Alpha: Update a function running a container",
RunE: wrapper.Wrapper(UpdateContainer),
}
wrapper.SetFlags(updateContainerCmd, flag.FlagSet{
Required: []flag.Flag{flag.FnName},
Optional: []flag.Flag{
flag.FnImageName, flag.FnPort,
flag.FnCommand, flag.FnArgs,
flag.FnSecret, flag.FnCfgMap,
flag.FnExecutionTimeout, flag.FnIdleTimeout,
flag.Labels, flag.Annotation,
flag.RunTimeMinCPU, flag.RunTimeMaxCPU, flag.RunTimeMinMemory,
flag.RunTimeMaxMemory, flag.ReplicasMin, flag.ReplicasMax,
flag.RunTimeTargetCPU,
flag.NamespaceFunction, flag.SpecSave,
},
})
command := &cobra.Command{
Use: "function",
Aliases: []string{"fn"},
Short: "Create, update and manage functions",
}
command.AddCommand(createCmd, getCmd, getmetaCmd, updateCmd, deleteCmd, listCmd, logsCmd, testCmd)
command.AddCommand(createCmd, getCmd, getmetaCmd, updateCmd, deleteCmd, listCmd, logsCmd, testCmd,
runContainerCmd, updateContainerCmd)
return command
}
+40 -24
View File
@@ -285,18 +285,6 @@ func (opts *CreateSubCommand) complete(input cli.Input) error {
Namespace: fnNamespace,
},
Spec: fv1.FunctionSpec{
Environment: fv1.EnvironmentReference{
Name: envName,
Namespace: envNamespace,
},
Package: fv1.FunctionPackageRef{
FunctionName: entrypoint,
PackageRef: fv1.PackageRef{
Namespace: pkgMetadata.Namespace,
Name: pkgMetadata.Name,
ResourceVersion: pkgMetadata.ResourceVersion,
},
},
Secrets: secrets,
ConfigMaps: cfgmaps,
Resources: *resourceReq,
@@ -309,6 +297,23 @@ func (opts *CreateSubCommand) complete(input cli.Input) error {
},
}
err = util.ApplyLabelsAndAnnotations(input, &opts.function.ObjectMeta)
if err != nil {
return err
}
opts.function.Spec.Environment = fv1.EnvironmentReference{
Name: envName,
Namespace: envNamespace,
}
opts.function.Spec.Package = fv1.FunctionPackageRef{
FunctionName: entrypoint,
PackageRef: fv1.PackageRef{
Namespace: pkgMetadata.Namespace,
Name: pkgMetadata.Name,
ResourceVersion: pkgMetadata.ResourceVersion,
},
}
return nil
}
@@ -387,13 +392,19 @@ func getInvokeStrategy(input cli.Input, existingInvokeStrategy *fv1.InvokeStrate
var es *fv1.ExecutionStrategy
if existingInvokeStrategy == nil {
es, err = getExecutionStrategy(input)
executorType, err := getExecutorType(input)
if err != nil {
return nil, err
}
es, err = getExecutionStrategy(executorType, input)
if err != nil {
return nil, err
}
} else {
es, err = updateExecutionStrategy(input, &existingInvokeStrategy.ExecutionStrategy)
}
if err != nil {
return nil, err
if err != nil {
return nil, err
}
}
return &fv1.InvokeStrategy{
@@ -402,20 +413,23 @@ func getInvokeStrategy(input cli.Input, existingInvokeStrategy *fv1.InvokeStrate
}, nil
}
func getExecutionStrategy(input cli.Input) (strategy *fv1.ExecutionStrategy, err error) {
var fnExecutor fv1.ExecutorType
func getExecutorType(input cli.Input) (executorType fv1.ExecutorType, err error) {
switch input.String(flagkey.FnExecutorType) {
case "":
fallthrough
case string(fv1.ExecutorTypePoolmgr):
fnExecutor = fv1.ExecutorTypePoolmgr
executorType = fv1.ExecutorTypePoolmgr
case string(fv1.ExecutorTypeNewdeploy):
fnExecutor = fv1.ExecutorTypeNewdeploy
executorType = fv1.ExecutorTypeNewdeploy
case string(fv1.ExecutorTypeContainer):
executorType = fv1.ExecutorTypeContainer
default:
return nil, errors.Errorf("executor type must be one of '%v' or '%v'", fv1.ExecutorTypePoolmgr, fv1.ExecutorTypeNewdeploy)
err = errors.Errorf("executor type must be one of '%v', '%v' or '%v'", fv1.ExecutorTypePoolmgr, fv1.ExecutorTypeNewdeploy, fv1.ExecutorTypeContainer)
}
return executorType, err
}
func getExecutionStrategy(fnExecutor fv1.ExecutorType, input cli.Input) (strategy *fv1.ExecutionStrategy, err error) {
specializationTimeout := fv1.DefaultSpecializationTimeOut
if input.IsSet(flagkey.FnSpecializationTimeout) {
@@ -490,8 +504,10 @@ func updateExecutionStrategy(input cli.Input, existingExecutionStrategy *fv1.Exe
fnExecutor = fv1.ExecutorTypePoolmgr
case string(fv1.ExecutorTypeNewdeploy):
fnExecutor = fv1.ExecutorTypeNewdeploy
case string(fv1.ExecutorTypeContainer):
fnExecutor = fv1.ExecutorTypeContainer
default:
return nil, errors.Errorf("executor type must be one of '%v' or '%v'", fv1.ExecutorTypePoolmgr, fv1.ExecutorTypeNewdeploy)
return nil, errors.Errorf("executor type must be one of '%v', %v or '%v'", fv1.ExecutorTypePoolmgr, fv1.ExecutorTypeNewdeploy, fv1.ExecutorTypeContainer)
}
}
@@ -0,0 +1,238 @@
/*
Copyright 2019 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package function
import (
"fmt"
"strings"
"github.com/pkg/errors"
apiv1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
ferror "github.com/fission/fission/pkg/error"
"github.com/fission/fission/pkg/fission-cli/cliwrapper/cli"
"github.com/fission/fission/pkg/fission-cli/cmd"
"github.com/fission/fission/pkg/fission-cli/cmd/spec"
"github.com/fission/fission/pkg/fission-cli/console"
flagkey "github.com/fission/fission/pkg/fission-cli/flag/key"
"github.com/fission/fission/pkg/fission-cli/util"
)
type RunContainerSubCommand struct {
cmd.CommandActioner
function *fv1.Function
specFile string
}
func RunContainer(input cli.Input) error {
return (&RunContainerSubCommand{}).do(input)
}
func (opts *RunContainerSubCommand) do(input cli.Input) error {
err := opts.complete(input)
if err != nil {
return err
}
return opts.run(input)
}
func (opts *RunContainerSubCommand) complete(input cli.Input) error {
fnName := input.String(flagkey.FnName)
fnNamespace := input.String(flagkey.NamespaceFunction)
// user wants a spec, create a yaml file with package and function
toSpec := false
if input.Bool(flagkey.SpecSave) {
toSpec = true
opts.specFile = fmt.Sprintf("function-%v.yaml", fnName)
}
if !toSpec {
// check for unique function names within a namespace
fn, err := opts.Client().V1().Function().Get(&metav1.ObjectMeta{
Name: input.String(flagkey.FnName),
Namespace: input.String(flagkey.NamespaceFunction),
})
if err != nil && !ferror.IsNotFound(err) {
return err
} else if fn != nil {
return errors.New("a function with the same name already exists")
}
}
fnTimeout := input.Int(flagkey.FnExecutionTimeout)
if fnTimeout <= 0 {
return errors.Errorf("--%v must be greater than 0", flagkey.FnExecutionTimeout)
}
fnIdleTimeout := input.Int(flagkey.FnIdleTimeout)
secretNames := input.StringSlice(flagkey.FnSecret)
cfgMapNames := input.StringSlice(flagkey.FnCfgMap)
es, err := getExecutionStrategy(fv1.ExecutorTypeContainer, input)
if err != nil {
return err
}
invokeStrategy := &fv1.InvokeStrategy{
ExecutionStrategy: *es,
StrategyType: fv1.StrategyTypeExecution,
}
resourceReq, err := util.GetResourceReqs(input, &apiv1.ResourceRequirements{})
if err != nil {
return err
}
var imageName string
var port int
var command, args string
imageName = input.String(flagkey.FnImageName)
if imageName == "" {
return errors.New("need --image argument")
}
port = input.Int(flagkey.FnPort)
command = input.String(flagkey.FnCommand)
args = input.String(flagkey.FnArgs)
var secrets []fv1.SecretReference
var cfgmaps []fv1.ConfigMapReference
if len(secretNames) > 0 {
// check the referenced secret is in the same ns as the function, if not give a warning.
if !toSpec { // TODO: workaround in order not to block users from creating function spec, remove it.
for _, secretName := range secretNames {
err := opts.Client().V1().Misc().SecretExists(&metav1.ObjectMeta{
Namespace: fnNamespace,
Name: secretName,
})
if err != nil {
if k8serrors.IsNotFound(err) {
console.Warn(fmt.Sprintf("Secret %s not found in Namespace: %s. Secret needs to be present in the same namespace as function", secretName, fnNamespace))
} else {
return errors.Wrapf(err, "error checking secret %s", secretName)
}
}
}
}
for _, secretName := range secretNames {
newSecret := fv1.SecretReference{
Name: secretName,
Namespace: fnNamespace,
}
secrets = append(secrets, newSecret)
}
}
if len(cfgMapNames) > 0 {
// check the referenced cfgmap is in the same ns as the function, if not give a warning.
if !toSpec {
for _, cfgMapName := range cfgMapNames {
err := opts.Client().V1().Misc().ConfigMapExists(&metav1.ObjectMeta{
Namespace: fnNamespace,
Name: cfgMapName,
})
if err != nil {
if k8serrors.IsNotFound(err) {
console.Warn(fmt.Sprintf("ConfigMap %s not found in Namespace: %s. ConfigMap needs to be present in the same namespace as function", cfgMapName, fnNamespace))
} else {
return errors.Wrapf(err, "error checking configmap %s", cfgMapName)
}
}
}
}
for _, cfgMapName := range cfgMapNames {
newCfgMap := fv1.ConfigMapReference{
Name: cfgMapName,
Namespace: fnNamespace,
}
cfgmaps = append(cfgmaps, newCfgMap)
}
}
opts.function = &fv1.Function{
ObjectMeta: metav1.ObjectMeta{
Name: fnName,
Namespace: fnNamespace,
},
Spec: fv1.FunctionSpec{
Secrets: secrets,
ConfigMaps: cfgmaps,
Resources: *resourceReq,
InvokeStrategy: *invokeStrategy,
FunctionTimeout: fnTimeout,
IdleTimeout: &fnIdleTimeout,
},
}
err = util.ApplyLabelsAndAnnotations(input, &opts.function.ObjectMeta)
if err != nil {
return err
}
container := &apiv1.Container{
Name: fnName,
Image: imageName,
Ports: []apiv1.ContainerPort{
{
Name: "http-env",
ContainerPort: int32(port),
},
},
}
if command != "" {
container.Command = strings.Split(command, " ")
}
if args != "" {
container.Args = strings.Split(args, " ")
}
opts.function.Spec.PodSpec = &apiv1.PodSpec{
Containers: []apiv1.Container{*container},
}
return nil
}
// run write the resource to a spec file or create a fission CRD with remote fission server.
// It also prints warning/error if necessary.
func (opts *RunContainerSubCommand) run(input cli.Input) error {
// if we're writing a spec, don't create the function
// save to spec file or display the spec to console
if input.Bool(flagkey.SpecDry) {
return spec.SpecDry(*opts.function)
}
if input.Bool(flagkey.SpecSave) {
err := spec.SpecSave(*opts.function, opts.specFile)
if err != nil {
return errors.Wrap(err, "error saving function spec")
}
return nil
}
_, err := opts.Client().V1().Function().Create(opts.function)
if err != nil {
return errors.Wrap(err, "error creating function")
}
fmt.Printf("function '%v' created\n", opts.function.ObjectMeta.Name)
return nil
}
+5
View File
@@ -243,6 +243,11 @@ func (opts *UpdateSubCommand) complete(input cli.Input) error {
opts.function = function
err = util.ApplyLabelsAndAnnotations(input, &opts.function.ObjectMeta)
if err != nil {
return err
}
return nil
}
@@ -0,0 +1,197 @@
/*
Copyright 2019 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package function
import (
"fmt"
"strings"
"github.com/pkg/errors"
apiv1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
"github.com/fission/fission/pkg/fission-cli/cliwrapper/cli"
"github.com/fission/fission/pkg/fission-cli/cmd"
"github.com/fission/fission/pkg/fission-cli/console"
flagkey "github.com/fission/fission/pkg/fission-cli/flag/key"
"github.com/fission/fission/pkg/fission-cli/util"
)
type UpdateContainerSubCommand struct {
cmd.CommandActioner
function *fv1.Function
}
func UpdateContainer(input cli.Input) error {
return (&UpdateContainerSubCommand{}).do(input)
}
func (opts *UpdateContainerSubCommand) do(input cli.Input) error {
err := opts.complete(input)
if err != nil {
return err
}
return opts.run(input)
}
func (opts *UpdateContainerSubCommand) complete(input cli.Input) error {
fnName := input.String(flagkey.FnName)
fnNamespace := input.String(flagkey.NamespaceFunction)
function, err := opts.Client().V1().Function().Get(&metav1.ObjectMeta{
Name: input.String(flagkey.FnName),
Namespace: input.String(flagkey.NamespaceFunction),
})
if err != nil {
return errors.Wrap(err, fmt.Sprintf("read function '%v'", fnName))
}
if fv1.ExecutorTypeContainer != function.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType {
return fmt.Errorf("executor type for function is not %s", fv1.ExecutorTypeContainer)
}
imageName := input.String(flagkey.FnImageName)
port := input.Int(flagkey.FnPort)
command := input.String(flagkey.FnCommand)
args := input.String(flagkey.FnArgs)
secretNames := input.StringSlice(flagkey.FnSecret)
cfgMapNames := input.StringSlice(flagkey.FnCfgMap)
var secrets []fv1.SecretReference
var configMaps []fv1.ConfigMapReference
if len(secretNames) > 0 {
// check that the referenced secret is in the same ns as the function, if not give a warning.
for _, secretName := range secretNames {
err := opts.Client().V1().Misc().SecretExists(&metav1.ObjectMeta{
Namespace: fnNamespace,
Name: secretName,
})
if k8serrors.IsNotFound(err) {
console.Warn(fmt.Sprintf("secret %s not found in Namespace: %s. Secret needs to be present in the same namespace as function", secretName, fnNamespace))
}
}
for _, secretName := range secretNames {
newSecret := fv1.SecretReference{
Name: secretName,
Namespace: fnNamespace,
}
secrets = append(secrets, newSecret)
}
function.Spec.Secrets = secrets
}
if len(cfgMapNames) > 0 {
// check that the referenced cfgmap is in the same ns as the function, if not give a warning.
for _, cfgMapName := range cfgMapNames {
err := opts.Client().V1().Misc().ConfigMapExists(&metav1.ObjectMeta{
Namespace: fnNamespace,
Name: cfgMapName,
})
if k8serrors.IsNotFound(err) {
console.Warn(fmt.Sprintf("ConfigMap %s not found in Namespace: %s. ConfigMap needs to be present in the same namespace as the function", cfgMapName, fnNamespace))
}
}
for _, cfgMapName := range cfgMapNames {
newCfgMap := fv1.ConfigMapReference{
Name: cfgMapName,
Namespace: fnNamespace,
}
configMaps = append(configMaps, newCfgMap)
}
function.Spec.ConfigMaps = configMaps
}
if input.IsSet(flagkey.FnExecutionTimeout) {
fnTimeout := input.Int(flagkey.FnExecutionTimeout)
if fnTimeout <= 0 {
return errors.Errorf("--%v must be greater than 0", flagkey.FnExecutionTimeout)
}
function.Spec.FunctionTimeout = fnTimeout
}
if input.IsSet(flagkey.FnIdleTimeout) {
fnTimeout := input.Int(flagkey.FnIdleTimeout)
function.Spec.IdleTimeout = &fnTimeout
}
strategy, err := getInvokeStrategy(input, &function.Spec.InvokeStrategy)
if err != nil {
return err
}
function.Spec.InvokeStrategy = *strategy
resReqs, err := util.GetResourceReqs(input, &function.Spec.Resources)
if err != nil {
return err
}
function.Spec.Resources = *resReqs
if len(function.Spec.PodSpec.Containers) > 1 {
return errors.Errorf("function %s has more than one container, only one container is supported", fnName)
}
container := &function.Spec.PodSpec.Containers[0]
if imageName != "" {
container.Image = imageName
}
if port != 0 {
if len(container.Ports) > 1 {
return errors.Errorf("function %s has more than one port, only one port is supported", fnName)
}
container.Ports = []apiv1.ContainerPort{
{
Name: "http-env",
ContainerPort: int32(port),
},
}
}
if command != "" {
container.Command = strings.Split(command, " ")
}
if args != "" {
container.Args = strings.Split(args, " ")
}
function.Spec.Environment = fv1.EnvironmentReference{}
function.Spec.Package = fv1.FunctionPackageRef{}
opts.function = function
err = util.ApplyLabelsAndAnnotations(input, &opts.function.ObjectMeta)
if err != nil {
return err
}
return nil
}
func (opts *UpdateContainerSubCommand) run(input cli.Input) error {
_, err := opts.Client().V1().Function().Update(opts.function)
if err != nil {
return errors.Wrap(err, "error updating function")
}
return nil
}
+4 -2
View File
@@ -33,7 +33,8 @@ func Commands() *cobra.Command {
Required: []flag.Flag{flag.HtFnName},
Optional: []flag.Flag{flag.HtUrl, flag.HtName, flag.HtMethod, flag.HtIngress,
flag.HtIngressRule, flag.HtIngressAnnotation, flag.HtIngressTLS,
flag.HtFnWeight, flag.HtHost, flag.NamespaceFunction, flag.SpecSave, flag.SpecDry, flag.HtPrefix},
flag.HtFnWeight, flag.HtHost, flag.NamespaceFunction, flag.SpecSave, flag.SpecDry,
flag.HtPrefix, flag.HtKeepPrefix},
})
getCmd := &cobra.Command{
@@ -56,7 +57,8 @@ func Commands() *cobra.Command {
Required: []flag.Flag{flag.HtName},
Optional: []flag.Flag{flag.HtUrl, flag.HtFnName,
flag.HtMethod, flag.HtIngress, flag.HtIngressRule, flag.HtIngressAnnotation,
flag.HtIngressTLS, flag.HtFnWeight, flag.HtHost, flag.NamespaceTrigger, flag.HtPrefix},
flag.HtIngressTLS, flag.HtFnWeight, flag.HtHost, flag.NamespaceTrigger,
flag.HtPrefix, flag.HtKeepPrefix},
})
deleteCmd := &cobra.Command{
@@ -179,9 +179,14 @@ func (opts *CreateSubCommand) complete(input cli.Input) error {
CreateIngress: createIngress,
IngressConfig: *ingressConfig,
Prefix: &prefix,
KeepPrefix: input.Bool(flagkey.HtKeepPrefix),
},
}
if input.IsSet(flagkey.HtKeepPrefix) {
opts.trigger.Spec.KeepPrefix = input.Bool(flagkey.HtKeepPrefix)
}
return nil
}
@@ -80,6 +80,10 @@ func (opts *UpdateSubCommand) complete(input cli.Input) error {
ht.Spec.RelativeURL = triggerUrl
ht.Spec.Prefix = &prefix
if input.IsSet(flagkey.HtKeepPrefix) {
ht.Spec.KeepPrefix = input.Bool(flagkey.HtKeepPrefix)
}
methods := input.StringSlice(flagkey.HtMethod)
if len(methods) > 0 {
for _, method := range methods {
+1 -1
View File
@@ -28,6 +28,7 @@ import (
"github.com/hashicorp/go-multierror"
"github.com/mholt/archiver"
"github.com/pkg/errors"
uuid "github.com/satori/go.uuid"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
"github.com/fission/fission/pkg/controller/client"
@@ -39,7 +40,6 @@ import (
flagkey "github.com/fission/fission/pkg/fission-cli/flag/key"
"github.com/fission/fission/pkg/fission-cli/util"
"github.com/fission/fission/pkg/utils"
uuid "github.com/satori/go.uuid"
)
// CreateArchive returns a fv1.Archive made from an archive . If specFile, then
+3
View File
@@ -383,6 +383,9 @@ func applyResources(fclient client.Interface, specDir string, fr *FissionResourc
// of the package. This ensures that various caches can invalidate themselves
// when the package changes.
for i, f := range fr.Functions {
if f.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType == fv1.ExecutorTypeContainer {
continue
}
k := mapKey(&metav1.ObjectMeta{
Namespace: f.Spec.Package.PackageRef.Namespace,
Name: f.Spec.Package.PackageRef.Name,
+31 -29
View File
@@ -361,38 +361,40 @@ func (fr *FissionResources) Validate(input cli.Input) ([]string, error) {
for _, f := range fr.Functions {
functions[MapKey(&f.ObjectMeta)] = false
pkgMeta := &metav1.ObjectMeta{
Name: f.Spec.Package.PackageRef.Name,
Namespace: f.Spec.Package.PackageRef.Namespace,
}
if f.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType != fv1.ExecutorTypeContainer {
pkgMeta := &metav1.ObjectMeta{
Name: f.Spec.Package.PackageRef.Name,
Namespace: f.Spec.Package.PackageRef.Namespace,
}
// check package ref from function
packageRefExists := func() bool {
_, ok := packages[MapKey(pkgMeta)]
return ok
}
// check package ref from function
packageRefExists := func() bool {
_, ok := packages[MapKey(pkgMeta)]
return ok
}
// check that the package referenced by each function is in the same ns as the function
packageRefInFuncNs := func(f *fv1.Function) bool {
return f.Spec.Package.PackageRef.Namespace == f.ObjectMeta.Namespace
}
// check that the package referenced by each function is in the same ns as the function
packageRefInFuncNs := func(f *fv1.Function) bool {
return f.Spec.Package.PackageRef.Namespace == f.ObjectMeta.Namespace
}
if !packageRefInFuncNs(&f) {
result = multierror.Append(result, fmt.Errorf(
"%v: function '%v' references a package outside of its namespace %v/%v",
fr.SourceMap.Locations["Function"][f.ObjectMeta.Namespace][f.ObjectMeta.Name],
f.ObjectMeta.Name,
f.Spec.Package.PackageRef.Namespace,
f.Spec.Package.PackageRef.Name))
} else if !packageRefExists() {
result = multierror.Append(result, fmt.Errorf(
"%v: function '%v' references unknown package %v/%v",
fr.SourceMap.Locations["Function"][f.ObjectMeta.Namespace][f.ObjectMeta.Name],
f.ObjectMeta.Name,
pkgMeta.Namespace,
pkgMeta.Name))
} else {
packages[MapKey(pkgMeta)] = true
if !packageRefInFuncNs(&f) {
result = multierror.Append(result, fmt.Errorf(
"%v: function '%v' references a package outside of its namespace %v/%v",
fr.SourceMap.Locations["Function"][f.ObjectMeta.Namespace][f.ObjectMeta.Name],
f.ObjectMeta.Name,
f.Spec.Package.PackageRef.Namespace,
f.Spec.Package.PackageRef.Name))
} else if !packageRefExists() {
result = multierror.Append(result, fmt.Errorf(
"%v: function '%v' references unknown package %v/%v",
fr.SourceMap.Locations["Function"][f.ObjectMeta.Namespace][f.ObjectMeta.Name],
f.ObjectMeta.Name,
pkgMeta.Namespace,
pkgMeta.Name))
} else {
packages[MapKey(pkgMeta)] = true
}
}
client, err := util.GetServer(input)
+2 -1
View File
@@ -37,8 +37,9 @@ func (opts *ShowSubCommand) do(input cli.Input) error {
}
func (opts *ShowSubCommand) run(flaginput cli.Input) error {
round := flaginput.Int(flagkey.TtName)
round := flaginput.Int(flagkey.TtRound)
cronSpec := flaginput.String(flagkey.TtCron)
if len(cronSpec) == 0 {
return errors.New("need a cron spec like '0 30 * * * *', '@every 1h30m', or '@hourly'; use --cron")
}
+1 -1
View File
@@ -21,7 +21,7 @@ import (
"os"
"strings"
"github.com/life1347/color"
"github.com/fatih/color"
)
var (
+8
View File
@@ -74,6 +74,9 @@ var (
KubeContext = Flag{Type: String, Name: flagkey.KubeContext, Usage: "Kubernetes context to be used for the execution of Fission commands", DefaultValue: ""}
Labels = Flag{Type: String, Name: flagkey.Labels, Usage: "Comma separated labels to apply to the function. Eg. --labels=\"environment=dev,application=analytics\""}
Annotation = Flag{Type: StringSlice, Name: flagkey.Annotation, Usage: "Annotation to apply to the function. To mention multiple annotations --annotation=\"abc.com/team=dev\" --annotation=\"foo=bar\""}
NamespaceFunction = Flag{Type: String, Name: flagkey.NamespaceFunction, Aliases: []string{"fns"}, Usage: "Namespace for function object", DefaultValue: metav1.NamespaceDefault}
NamespaceEnvironment = Flag{Type: String, Name: flagkey.NamespaceEnvironment, Aliases: []string{"envns"}, Usage: "Namespace for environment object", DefaultValue: metav1.NamespaceDefault}
NamespacePackage = Flag{Type: String, Name: flagkey.NamespacePackage, Aliases: []string{"pkgns"}, Usage: "Namespace for package object", DefaultValue: metav1.NamespaceDefault}
@@ -93,6 +96,10 @@ var (
FnSpecializationTimeout = Flag{Type: Int, Name: flagkey.FnSpecializationTimeout, Aliases: []string{"st"}, Usage: "Timeout for executor to wait for function pod creation", DefaultValue: fv1.DefaultSpecializationTimeOut}
FnEnvName = Flag{Type: String, Name: flagkey.FnEnvironmentName, Usage: "Environment name for function"}
FnPkgName = Flag{Type: String, Name: flagkey.FnPackageName, Aliases: []string{"pkg"}, Usage: "Name of the existing package (--deploy and --src and --env will be ignored), should be in the same namespace as the function"}
FnImageName = Flag{Type: String, Name: flagkey.FnImageName, Usage: "Name of the Docker image to be deployed as a function. Valid only when executorType is set to 'container'"}
FnPort = Flag{Type: Int, Name: flagkey.FnPort, Usage: "Port where the application is running", DefaultValue: 8888}
FnCommand = Flag{Type: String, Name: flagkey.FnCommand, Usage: "Command to be passed to the container. If not specified , the ones defined in the image are used"}
FnArgs = Flag{Type: String, Name: flagkey.FnArgs, Usage: "Args to be passed to the command on the container. If not specified , the ones defined in the image are used"}
FnEntryPoint = Flag{Type: String, Name: flagkey.FnEntrypoint, Aliases: []string{"entry"}, Usage: "Entry point for environment v2 to load with"}
FnBuildCmd = Flag{Type: String, Name: flagkey.FnBuildCmd, Usage: "Package build command for builder to run with"}
FnSecret = Flag{Type: StringSlice, Name: flagkey.FnSecret, Usage: "Function access to secret, should be present in the same namespace as the function. You can provide multiple secrets using multiple --secrets flags. In the case of fn update the the secrets will be replaced by the provided list of secrets."}
@@ -127,6 +134,7 @@ var (
HtFnWeight = Flag{Type: IntSlice, Name: flagkey.HtFnWeight, Usage: "Weight for each function supplied with --function flag, in the same order. Used for canary deployment"}
HtFnFilter = Flag{Type: String, Name: flagkey.HtFilter, Usage: "Name of the function for trigger(s)"}
HtPrefix = Flag{Type: String, Name: flagkey.HtPrefix, Usage: "Prefix with which functions are exposed. NOTE: Prefix takes precedence over URL/RelativeURL"}
HtKeepPrefix = Flag{Type: Bool, Name: flagkey.HtKeepPrefix, Usage: "Keep the prefix in the URL while forwarding request to the function"}
TtName = Flag{Type: String, Name: flagkey.TtName, Usage: "Time Trigger name"}
TtCron = Flag{Type: String, Name: flagkey.TtCron, Usage: "Time trigger cron spec with each asterisk representing respectively second, minute, hour, the day of the month, month and day of the week. Also supports readable formats like '@every 5m', '@hourly'"}
+8
View File
@@ -26,6 +26,9 @@ const (
force = "force"
Output = "output"
Labels = "labels"
Annotation = "annotation"
NamespaceFunction = "fnNamespace"
NamespaceEnvironment = "envNamespace"
NamespacePackage = "pkgNamespace"
@@ -45,6 +48,10 @@ const (
FnSpecializationTimeout = "specializationtimeout"
FnEnvironmentName = "env"
FnPackageName = "pkgname"
FnImageName = "image"
FnPort = "port"
FnCommand = "command"
FnArgs = "args"
FnEntrypoint = "entrypoint"
FnBuildCmd = "buildcmd"
FnSecret = "secret"
@@ -80,6 +87,7 @@ const (
HtFnWeight = "weight"
HtFilter = HtFnName
HtPrefix = "prefix"
HtKeepPrefix = "keepprefix"
TtName = resourceName
TtCron = "cron"
+50
View File
@@ -32,6 +32,7 @@ import (
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
@@ -350,3 +351,52 @@ func UrlForFunction(name, namespace string) string {
}
return fmt.Sprintf("%v/%v", prefix, name)
}
func ParseAnnotations(annotations []string) (map[string]string, error) {
var invalidAnnotations string
annotationMap := make(map[string]string)
for _, arg := range annotations {
if strings.Contains(arg, "=") && arg[0] != '=' {
parts := strings.SplitN(arg, "=", 2)
if len(parts) == 2 {
annotationMap[parts[0]] = parts[1]
} else {
if invalidAnnotations != "" {
invalidAnnotations = fmt.Sprintf("%s,%s", invalidAnnotations, arg)
} else {
invalidAnnotations = arg
}
}
} else {
if invalidAnnotations != "" {
invalidAnnotations = fmt.Sprintf("%s,%s", invalidAnnotations, arg)
} else {
invalidAnnotations = arg
}
}
}
if invalidAnnotations != "" {
return nil, errors.Errorf("invalid annotations: %s", invalidAnnotations)
}
return annotationMap, nil
}
func ApplyLabelsAndAnnotations(input cli.Input, objectMeta *metav1.ObjectMeta) error {
labelStr := input.String(flagkey.Labels)
if labelStr != "" {
set, err := labels.ConvertSelectorToLabelsMap(labelStr)
if err != nil {
return err
}
objectMeta.Labels = set
}
annotationStr := input.StringSlice(flagkey.Annotation)
if len(annotationStr) > 0 {
set, err := ParseAnnotations(annotationStr)
if err != nil {
return err
}
objectMeta.Annotations = set
}
return nil
}
@@ -21,7 +21,7 @@ package versioned
import (
"fmt"
corev1 "github.com/fission/fission/pkg/apis/genclient/clientset/versioned/typed/core/v1"
corev1 "github.com/fission/fission/pkg/generated/clientset/versioned/typed/core/v1"
discovery "k8s.io/client-go/discovery"
rest "k8s.io/client-go/rest"
flowcontrol "k8s.io/client-go/util/flowcontrol"

Some files were not shown because too many files have changed in this diff Show More