Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4702fe066 | ||
|
|
fea8dbef1a | ||
|
|
6a527d36a8 | ||
|
|
2292f472c9 | ||
|
|
2aaaeee5a7 | ||
|
|
ca161690da | ||
|
|
8bd1a71065 | ||
|
|
eb72fdc717 | ||
|
|
a7f819a78e | ||
|
|
d4d58e166b | ||
|
|
1d41a198cb | ||
|
|
6ced9d03d3 | ||
|
|
19c7616a4b | ||
|
|
a82281ad1c | ||
|
|
f3e1f9df90 | ||
|
|
c85b9e8cbc | ||
|
|
ece0475808 | ||
|
|
2e0bb9304a | ||
|
|
74c0142968 | ||
|
|
7c71d90d17 | ||
|
|
f86fde81e6 | ||
|
|
43814450e5 | ||
|
|
d5077ee816 | ||
|
|
2cf2c6979e | ||
|
|
154fe0d447 | ||
|
|
a493f0117d | ||
|
|
cc0400bdd4 | ||
|
|
8b57a035ee | ||
|
|
dc57f83d19 | ||
|
|
611f556cf9 |
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 -}}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# Git hooks
|
||||
|
||||
* pre-push: Check, build and test the changes.
|
||||
|
||||
```bash
|
||||
$ cp githooks/* .git/hooks/
|
||||
```
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
ROOT=`realpath $(dirname $0)/../..`
|
||||
pushd $ROOT
|
||||
make
|
||||
popd
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
kubectl get crds -o custom-columns=:metadata.name | grep 'fission.io' | xargs kubectl delete crds
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
API_GROUP_VERSIONS="
|
||||
core/v1
|
||||
"
|
||||
API_PACKAGES="
|
||||
"
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
Executable
+19
@@ -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"
|
||||
Executable
+56
@@ -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
|
||||
@@ -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
|
||||
@@ -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")
|
||||
@@ -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 @
|
||||
@@ -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
@@ -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/
|
||||
|
||||
@@ -55,6 +55,7 @@ const (
|
||||
const (
|
||||
ExecutorTypePoolmgr ExecutorType = "poolmgr"
|
||||
ExecutorTypeNewdeploy ExecutorType = "newdeploy"
|
||||
ExecutorTypeContainer ExecutorType = "container"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -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.",
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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")
|
||||
|
||||
}()
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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{})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/life1347/color"
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -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'"}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
Reference in New Issue
Block a user