├── .asf.yaml ├── .github └── workflows │ ├── build.yml │ ├── codeql-analysis.yml │ ├── go.fmt.yml │ ├── go.mod.yml │ ├── go.vet.yml │ ├── golangci-lint.yml │ ├── helm.yml │ ├── test.yml │ └── trivy.yml ├── .gitignore ├── .gitmodules ├── .golangci.yaml ├── .vscode ├── extensions.json ├── settings.json └── tasks.json ├── CODEOWNERS ├── Dockerfile ├── GO_VERSION ├── LICENSE ├── README.md ├── bin ├── entry.sh ├── records-config.sh ├── tls-config.sh └── tls-reload.sh ├── charts └── ats-ingress │ ├── .helmignore │ ├── Chart.yaml │ ├── README.md │ ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── clusterrole.yaml │ ├── clusterrolebinding.yaml │ ├── deployment.yaml │ ├── pullsecret.yaml │ ├── service.yaml │ └── serviceaccount.yaml │ └── values.yaml ├── config ├── healthchecks.config ├── logging.yaml ├── plugin.config ├── records.config └── redis.conf ├── docs ├── ARCHITECTURE.md ├── DEVELOPMENT.md ├── TUTORIAL.md ├── ats-ingress-0.1.0.tgz ├── images │ ├── abstract.png │ └── how-it-works.png ├── index.html └── index.yaml ├── endpoint └── endpoint.go ├── go.mod ├── go.sum ├── k8s └── images │ ├── node-app-1 │ ├── .dockerignore │ ├── Dockerfile │ ├── hello-updated.html │ ├── hello.html │ ├── package-lock.json │ ├── package.json │ └── server.js │ └── node-app-2 │ ├── .dockerignore │ ├── Dockerfile │ ├── hello.html │ ├── package-lock.json │ ├── package.json │ └── server.js ├── main └── main.go ├── namespace └── namespace.go ├── pluginats ├── connect_redis.lua └── connect_redis_test.lua ├── proxy ├── ats.go └── fakeATS.go ├── redis ├── redis.go └── redis_test.go ├── tests ├── conftest.py ├── data │ ├── ats-ingress-add.yaml │ ├── ats-ingress-delete.yaml │ ├── ats-ingress-snippet.yaml │ ├── ats-ingress-update.yaml │ └── setup │ │ ├── apps │ │ ├── app-deployment.yaml │ │ ├── app-service.yaml │ │ └── apps-rbac.yaml │ │ ├── configmaps │ │ ├── ats-configmap.yaml │ │ └── fluentd-configmap.yaml │ │ ├── ingresses │ │ ├── ats-ingress-2.yaml │ │ ├── ats-ingress-2s.yaml │ │ └── ats-ingress.yaml │ │ └── traffic-server │ │ ├── ats-deployment.yaml │ │ └── ats-rbac.yaml ├── requirements.txt └── suite │ └── test_ingress.py ├── util └── util.go └── watcher ├── handlerConfigmap.go ├── handlerConfigmap_test.go ├── handlerEndpoint.go ├── handlerEndpoint_test.go ├── handlerIngress.go ├── handlerIngress_test.go ├── watcher.go └── watcher_test.go /.asf.yaml: -------------------------------------------------------------------------------- 1 | # Documentation https://cwiki.apache.org/confluence/display/INFRA/git+-+.asf.yaml+features 2 | notifications: 3 | commits: commits@trafficserver.apache.org 4 | issues: issues@trafficserver.apache.org 5 | pullrequests: github@trafficserver.apache.org 6 | github: 7 | description: Apache Traffic Server Ingress Controller for Kubernetes 8 | homepage: https://github.com/apache/trafficserver-ingress-controller 9 | ghp_branch: master 10 | ghp_path: /docs 11 | labels: 12 | - ingress-controller 13 | - kubernetes 14 | - kubernetes-ingress 15 | - ingress 16 | - kubernetes-ingress-controller 17 | - k8s 18 | - apache 19 | features: 20 | issues: true 21 | enabled_merge_buttons: 22 | # enable squash button: 23 | squash: true 24 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and Integrate 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | paths-ignore: 8 | - '.vscode/**' 9 | - 'charts/**' 10 | - 'docs/**' 11 | 12 | env: 13 | REGISTRY: ghcr.io 14 | IMAGE_NAME: apache/ats-ingress 15 | 16 | jobs: 17 | build-and-integrate: 18 | runs-on: ubuntu-22.04 19 | permissions: 20 | contents: read 21 | packages: write 22 | security-events: write # for github/codeql-action/upload-sarif to upload SARIF results 23 | 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v4 27 | with: 28 | submodules: 'true' 29 | 30 | - name: Setup Minikube 31 | uses: manusa/actions-setup-minikube@v2.13.0 32 | with: 33 | minikube version: 'v1.35.0' 34 | kubernetes version: 'v1.32.5' 35 | driver: 'docker' 36 | container runtime: 'docker' 37 | github token: ${{ secrets.GITHUB_TOKEN }} 38 | 39 | - name: Set up Python 3.8 40 | uses: actions/setup-python@v5 41 | with: 42 | python-version: '3.8.18' 43 | 44 | - name: Build ATS Alpine 45 | run: | 46 | eval $(minikube -p minikube docker-env) 47 | docker build -t ats-ingress . --network=host 48 | 49 | # - name: Build Exporter 50 | # run: docker build -t ats-ingress-exporter k8s/images/trafficserver_exporter/ 51 | 52 | - name: Build App 1 53 | run: | 54 | eval $(minikube -p minikube docker-env) 55 | docker build -t node-app-1 k8s/images/node-app-1/ 56 | 57 | - name: Build App 2 58 | run: | 59 | eval $(minikube -p minikube docker-env) 60 | docker build -t node-app-2 k8s/images/node-app-2/ 61 | 62 | - name: Install dependencies 63 | run: | 64 | cd tests 65 | python -m pip install --upgrade pip 66 | pip install -r requirements.txt 67 | 68 | - name: Test 69 | run: | 70 | cd tests 71 | pytest -q --minikubeip="$(minikube ip)" suite/test_ingress.py 72 | 73 | - name: Log in to the Container registry 74 | if: github.repository == 'apache/trafficserver-ingress-controller' 75 | uses: docker/login-action@v3 76 | with: 77 | registry: ${{ env.REGISTRY }} 78 | username: ${{ github.actor }} 79 | password: ${{ secrets.GITHUB_TOKEN }} 80 | 81 | - name: Extract metadata (tags, labels) for Docker 82 | if: github.repository == 'apache/trafficserver-ingress-controller' 83 | id: meta 84 | uses: docker/metadata-action@v5 85 | with: 86 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 87 | tags: | 88 | type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'master') }} 89 | 90 | - name: Build and push Docker image 91 | if: github.repository == 'apache/trafficserver-ingress-controller' 92 | uses: docker/build-push-action@v5 93 | with: 94 | context: . 95 | push: true 96 | no-cache: true 97 | tags: ${{ steps.meta.outputs.tags }} 98 | labels: ${{ steps.meta.outputs.labels }} 99 | 100 | - name: Run Trivy vulnerability scanner 101 | if: github.repository == 'apache/trafficserver-ingress-controller' 102 | uses: aquasecurity/trivy-action@0.24.0 103 | with: 104 | image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest 105 | format: 'sarif' 106 | output: 'trivy-results.sarif' 107 | 108 | - name: Upload Trivy scan results to GitHub Security tab 109 | if: github.repository == 'apache/trafficserver-ingress-controller' 110 | uses: github/codeql-action/upload-sarif@v3 111 | with: 112 | sarif_file: 'trivy-results.sarif' 113 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | name: "CodeQL" 19 | 20 | on: 21 | push: 22 | branches: [ master ] 23 | paths-ignore: 24 | - '.vscode/**' 25 | - 'charts/**' 26 | - 'docs/**' 27 | pull_request: 28 | # The branches below must be a subset of the branches above 29 | branches: [ master ] 30 | paths-ignore: 31 | - '.vscode/**' 32 | - 'charts/**' 33 | - 'docs/**' 34 | schedule: 35 | - cron: '30 1 * * 1' 36 | 37 | jobs: 38 | analyze: 39 | name: Analyze 40 | runs-on: ubuntu-latest 41 | permissions: 42 | actions: read 43 | contents: read 44 | security-events: write 45 | 46 | strategy: 47 | fail-fast: false 48 | matrix: 49 | language: [ 'go' ] 50 | 51 | steps: 52 | - name: Checkout repository 53 | uses: actions/checkout@v4 54 | 55 | # Initializes the CodeQL tools for scanning. 56 | - name: Initialize CodeQL 57 | uses: github/codeql-action/init@v3 58 | with: 59 | languages: ${{ matrix.language }} 60 | # If you wish to specify custom queries, you can do so here or in a config file. 61 | # By default, queries listed here will override any specified in a config file. 62 | # Prefix the list here with "+" to use these queries and those in the config file. 63 | 64 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 65 | # queries: security-extended,security-and-quality 66 | 67 | 68 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 69 | # If this step fails, then you should remove it and run the build manually (see below) 70 | - name: Autobuild 71 | uses: github/codeql-action/autobuild@v3 72 | 73 | # ℹ️ Command-line programs to run using the OS shell. 74 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 75 | 76 | # If the Autobuild fails above, remove it and uncomment the following three lines. 77 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 78 | 79 | # - run: | 80 | # echo "Run, Build Application using script" 81 | # ./location_of_script_within_repo/buildscript.sh 82 | 83 | - name: Perform CodeQL Analysis 84 | uses: github/codeql-action/analyze@v3 85 | -------------------------------------------------------------------------------- /.github/workflows/go.fmt.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | name: Go Fmt 19 | 20 | on: 21 | push: 22 | branches: 23 | - master 24 | paths-ignore: 25 | - '.vscode/**' 26 | - 'charts/**' 27 | - 'docs/**' 28 | pull_request: 29 | branches: 30 | - master 31 | paths-ignore: 32 | - '.vscode/**' 33 | - 'charts/**' 34 | - 'docs/**' 35 | 36 | jobs: 37 | go-fmt: 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Checkout 41 | uses: actions/checkout@master 42 | - name: go-version 43 | run: echo "value=$(cat GO_VERSION)" >>$GITHUB_OUTPUT 44 | id: go-version 45 | - uses: actions/setup-go@v5 46 | with: 47 | go-version: ${{ steps.go-version.outputs.value }} # The Go version to download (if necessary) and use. 48 | - name: go fmt 49 | run: | 50 | go fmt ./... > /tmp/go.fmt.temp 51 | if [ -s /tmp/go.fmt.temp ]; then 52 | printf "Files need to go through go fmt.\n"; 53 | cat /tmp/go.fmt.temp; 54 | rm /tmp/go.fmt.temp; 55 | exit 1; 56 | fi 57 | 58 | -------------------------------------------------------------------------------- /.github/workflows/go.mod.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | name: Go Mod 19 | 20 | on: 21 | push: 22 | branches: 23 | - master 24 | paths-ignore: 25 | - '.vscode/**' 26 | - 'charts/**' 27 | - 'docs/**' 28 | pull_request: 29 | branches: 30 | - master 31 | paths-ignore: 32 | - '.vscode/**' 33 | - 'charts/**' 34 | - 'docs/**' 35 | 36 | jobs: 37 | go-mod: 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Checkout 41 | uses: actions/checkout@master 42 | - name: go-version 43 | run: echo "value=$(cat GO_VERSION)" >>$GITHUB_OUTPUT 44 | id: go-version 45 | - uses: actions/setup-go@v5 46 | with: 47 | go-version: ${{ steps.go-version.outputs.value }} # The Go version to download (if necessary) and use. 48 | - name: go mod 49 | run: | 50 | go mod tidy 51 | - name: check go.sum 52 | run: | 53 | if git diff --exit-code -- go.sum; then 54 | printf "go.sum is up-to-date.\n" 55 | exit 0 56 | fi 57 | printf "Changes found in go.sum. Please run go mod tidy\n" 58 | exit 1 59 | - name: check go.mod 60 | run: | 61 | if git diff --exit-code -- go.mod; then 62 | printf "go.mod is up-to-date.\n" 63 | exit 0 64 | fi 65 | printf "Changes found in go.mod. Please run go mod tidy\n" 66 | exit 1 67 | -------------------------------------------------------------------------------- /.github/workflows/go.vet.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | name: Go Vet 19 | 20 | on: 21 | push: 22 | branches: 23 | - master 24 | paths-ignore: 25 | - '.vscode/**' 26 | - 'charts/**' 27 | - 'docs/**' 28 | pull_request: 29 | branches: 30 | - master 31 | paths-ignore: 32 | - '.vscode/**' 33 | - 'charts/**' 34 | - 'docs/**' 35 | 36 | jobs: 37 | go-vet: 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Checkout 41 | uses: actions/checkout@master 42 | - name: go-version 43 | run: echo "value=$(cat GO_VERSION)" >>$GITHUB_OUTPUT 44 | id: go-version 45 | - uses: actions/setup-go@v5 46 | with: 47 | go-version: ${{ steps.go-version.outputs.value }} # The Go version to download (if necessary) and use. 48 | - name: go vet 49 | run: go vet ./... 50 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | name: golangci-lint 18 | on: 19 | push: 20 | branches: 21 | - master 22 | paths-ignore: 23 | - '.vscode/**' 24 | - 'charts/**' 25 | - 'docs/**' 26 | pull_request: 27 | branches: 28 | - master 29 | paths-ignore: 30 | - '.vscode/**' 31 | - 'charts/**' 32 | - 'docs/**' 33 | 34 | permissions: 35 | contents: read 36 | 37 | jobs: 38 | golangci: 39 | permissions: 40 | contents: read # for actions/checkout to fetch code 41 | pull-requests: read # for golangci/golangci-lint-action to fetch pull requests 42 | name: lint 43 | runs-on: ubuntu-latest 44 | 45 | steps: 46 | - name: Check out code into the Go module directory 47 | uses: actions/checkout@v4 48 | - name: go-version 49 | run: echo "value=$(cat GO_VERSION)" >>$GITHUB_OUTPUT 50 | id: go-version 51 | - uses: actions/setup-go@v5 52 | with: 53 | go-version: ${{ steps.go-version.outputs.value }} 54 | - name: golangci-lint 55 | uses: golangci/golangci-lint-action@v5 56 | with: 57 | version: latest 58 | args: --timeout=10m 59 | 60 | -------------------------------------------------------------------------------- /.github/workflows/helm.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release Helm Chart 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | paths: 8 | - 'charts/**' 9 | 10 | jobs: 11 | build-and-release-helm: 12 | if: github.repository == 'apache/trafficserver-ingress-controller' 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Install Helm 17 | uses: azure/setup-helm@v1 18 | with: 19 | version: v3.5.4 20 | 21 | - uses: actions/checkout@v3 22 | - run: | 23 | cd charts 24 | helm package ats-ingress 25 | helm repo index . --url https://apache.github.io/trafficserver-ingress-controller 26 | cp index.yaml ../docs/ 27 | cp ats-ingress-*.tgz ../docs/ 28 | cd .. 29 | git config user.name github-actions 30 | git config user.email github-actions@github.com 31 | git add docs/index.yaml 32 | git add docs/ats-ingress-*.tgz 33 | git commit -m 'Release new version of helm chart' 34 | git push 35 | 36 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths-ignore: 8 | - '.vscode/**' 9 | - 'charts/**' 10 | - 'docs/**' 11 | pull_request: 12 | branches: 13 | - master 14 | paths-ignore: 15 | - '.vscode/**' 16 | - 'charts/**' 17 | - 'docs/**' 18 | 19 | jobs: 20 | 21 | build: 22 | name: Test Redis and Lua 23 | runs-on: ubuntu-latest 24 | steps: 25 | 26 | - name: Check out code into the Go module directory 27 | uses: actions/checkout@v4 28 | 29 | - name: go-version 30 | run: echo "value=$(cat GO_VERSION)" >>$GITHUB_OUTPUT 31 | id: go-version 32 | 33 | - name: Set up Go 1.x 34 | uses: actions/setup-go@v5 35 | with: 36 | go-version: ${{ steps.go-version.outputs.value }} 37 | id: go 38 | 39 | # commit for v8.0.0 40 | - uses: leafo/gh-actions-lua@v10 41 | with: 42 | luaVersion: "5.1.5" 43 | 44 | # commit for v4.3.0 45 | - uses: leafo/gh-actions-luarocks@e65774a6386cb4f24e293dca7fc4ff89165b64c5 46 | 47 | - name: Install Busted 48 | run: | 49 | luarocks install busted 50 | 51 | - name: Get dependencies 52 | run: | 53 | go get -v -t -d ./... 54 | if [ -f Gopkg.toml ]; then 55 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 56 | dep ensure 57 | fi 58 | 59 | - name: Test Watcher 60 | run: go test -v ./watcher/ 61 | 62 | - name: Test Redis 63 | run: go test -v ./redis/ 64 | 65 | - name: Test RedisPluginATS 66 | run: | 67 | cd pluginats 68 | ../.luarocks/bin/busted connect_redis_test.lua 69 | -------------------------------------------------------------------------------- /.github/workflows/trivy.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | name: Periodic Trivy Scan 18 | 19 | on: 20 | schedule: 21 | - cron: '30 1 * * 2' 22 | 23 | permissions: 24 | contents: read 25 | 26 | jobs: 27 | trivy-scan: 28 | permissions: 29 | contents: read # for actions/checkout to fetch code 30 | security-events: write # for github/codeql-action/upload-sarif to upload SARIF results 31 | name: Trivy Scan 32 | runs-on: "ubuntu-22.04" 33 | steps: 34 | - name: Checkout 35 | uses: actions/checkout@v4 36 | 37 | - name: Run Trivy vulnerability scanner 38 | uses: aquasecurity/trivy-action@0.24.0 39 | with: 40 | image-ref: 'ghcr.io/apache/ats-ingress:latest' 41 | format: 'sarif' 42 | output: 'trivy-results.sarif' 43 | 44 | - name: Upload Trivy scan results to GitHub Security tab 45 | uses: github/codeql-action/upload-sarif@v3 46 | with: 47 | sarif_file: 'trivy-results.sarif' 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # go dependency files 2 | vendor/ 3 | 4 | # go mod 5 | !go.sum 6 | !go.mod 7 | 8 | # builds 9 | ingress*ats 10 | 11 | # untracking 12 | trash/ 13 | other/ 14 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/trafficserver-ingress-controller/ce183aa48edd668cc0d246338b80980386ab0c4a/.gitmodules -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | # https://golangci-lint.run/usage/linters/ 18 | linters: 19 | disable-all: true 20 | enable: 21 | - errcheck 22 | - gosimple 23 | - govet 24 | - ineffassign 25 | - staticcheck 26 | - unused 27 | 28 | # Options for analysis running. 29 | run: 30 | concurrency: 4 31 | timeout: 5m 32 | tests: false 33 | 34 | output: 35 | format: colored-line-number 36 | print-issued-lines: true 37 | print-linter-name: true 38 | 39 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | "redhat.vscode-yaml", // docker + k8s code hints and code snippets 8 | "keyring.lua", // lua language support 9 | "ms-kubernetes-tools.vscode-kubernetes-tools", // managing k8s 10 | "ms-azuretools.vscode-docker", // managing docker 11 | "wayou.vscode-todo-highlight" // highlighting TODOs and FIXMEs 12 | ], 13 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 14 | "unwantedRecommendations": [ 15 | 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Editor 3 | "editor.tabSize": 4, 4 | "editor.detectIndentation": true, 5 | "editor.autoIndent": true, 6 | "editor.insertSpaces": true 7 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at 4 | // http://www.apache.org/licenses/LICENSE-2.0 5 | // Unless required by applicable law or agreed to in writing, software 6 | // distributed under the License is distributed on an "AS IS" BASIS, 7 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and 9 | // limitations under the License. 10 | { 11 | "version": "2.0.0", 12 | "tasks": [ 13 | { 14 | "label": "golangci-lint", 15 | "type": "shell", 16 | "command": "golangci-lint run" 17 | }, 18 | { 19 | "label": "go mod tidy", 20 | "type": "shell", 21 | "command": "go mod tidy" 22 | }, 23 | { 24 | "label": "go vet", 25 | "type": "shell", 26 | "command": "go vet ./..." 27 | }, 28 | { 29 | "label": "go fmt", 30 | "type": "shell", 31 | "command": "go fmt ./..." 32 | }, 33 | { 34 | "label": "Build", 35 | "type": "shell", 36 | "command": "go build -o ingress-ats main/main.go" 37 | }, 38 | { 39 | "label": "Test", 40 | "type": "shell", 41 | "command": "go test ./watcher/ && go test ./redis/" 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @shukitchan 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | FROM alpine:3.20.6 as builder 19 | 20 | RUN apk add --no-cache --virtual .tools \ 21 | bzip2 curl nghttp2-libs git automake libtool autoconf make sed file perl openrc openssl 22 | 23 | # ATS dependencies 24 | RUN apk add --no-cache --virtual .ats-build-deps \ 25 | build-base openssl-dev tcl-dev pcre-dev zlib-dev \ 26 | linux-headers libunwind-dev curl-dev \ 27 | brotli-dev jansson-dev readline-dev geoip-dev libxml2-dev 28 | 29 | RUN apk add --no-cache --virtual .ats-extra-build-deps --repository https://dl-cdn.alpinelinux.org/alpine/edge/community hwloc-dev 30 | 31 | RUN apk add --no-cache --repository=https://dl-cdn.alpinelinux.org/alpine/v3.16/main libexecinfo-dev 32 | 33 | RUN apk add --no-cache --repository=https://dl-cdn.alpinelinux.org/alpine/v3.19/main luajit-dev=2.1_p20230410-r3 34 | 35 | RUN addgroup -Sg 1000 ats 36 | 37 | RUN adduser -S -D -H -u 1000 -h /tmp -s /sbin/nologin -G ats -g ats ats 38 | 39 | # download and build ATS 40 | # patch 2 files due to pthread in musl vs glibc - see https://github.com/apache/trafficserver/pull/7611/files 41 | RUN curl -L https://downloads.apache.org/trafficserver/trafficserver-9.2.10.tar.bz2 | bzip2 -dc | tar xf - \ 42 | && cd trafficserver-9.2.10/ \ 43 | && sed -i "s/PTHREAD_RWLOCK_WRITER_NONRECURSIVE_INITIALIZER_NP/PTHREAD_RWLOCK_INITIALIZER/" include/tscore/ink_rwlock.h \ 44 | && sed -i "s/PTHREAD_RWLOCK_WRITER_NONRECURSIVE_INITIALIZER_NP/PTHREAD_RWLOCK_INITIALIZER/" include/tscpp/util/TsSharedMutex.h \ 45 | && autoreconf -if \ 46 | && ./configure --enable-debug=yes --prefix=/opt/ats --with-user=ats \ 47 | && make \ 48 | && make install 49 | 50 | COPY ["./config/plugin.config", "/opt/ats/etc/trafficserver/plugin.config"] 51 | COPY ["./config/healthchecks.config", "/opt/ats/etc/trafficserver/healthchecks.config"] 52 | COPY ["./config/records.config", "/opt/ats/etc/trafficserver/records.config"] 53 | COPY ["./config/logging.yaml", "/opt/ats/etc/trafficserver/logging.yaml"] 54 | 55 | # enable traffic.out for alpine/gentoo 56 | RUN sed -i "s/TM_DAEMON_ARGS=\"\"/TM_DAEMON_ARGS=\" --bind_stdout \/opt\/ats\/var\/log\/trafficserver\/traffic.out --bind_stderr \/opt\/ats\/var\/log\/trafficserver\/traffic.out \"/" /opt/ats/bin/trafficserver 57 | RUN sed -i "s/TS_DAEMON_ARGS=\"\"/TS_DAEMON_ARGS=\" --bind_stdout \/opt\/ats\/var\/log\/trafficserver\/traffic.out --bind_stderr \/opt\/ats\/var\/log\/trafficserver\/traffic.out \"/" /opt/ats/bin/trafficserver 58 | 59 | # luasocket 60 | RUN wget https://github.com/lunarmodules/luasocket/archive/refs/tags/v3.0.0.tar.gz \ 61 | && tar zxf v3.0.0.tar.gz \ 62 | && cd luasocket-3.0.0 \ 63 | && sed -i "s/LDFLAGS_linux=-O -shared -fpic -o/LDFLAGS_linux=-O -shared -fpic -L\/usr\/lib -lluajit-5.1 -o/" src/makefile \ 64 | && ln -sf /usr/lib/libluajit-5.1.so.2.1.0 /usr/lib/libluajit-5.1.so \ 65 | && mkdir -p /usr/include/lua \ 66 | && ln -sf /usr/include/luajit-2.1 /usr/include/lua/5.1 \ 67 | && make \ 68 | && make install-unix prefix=/opt/ats 69 | 70 | # redis.lua 71 | RUN wget https://github.com/nrk/redis-lua/archive/v2.0.4.tar.gz \ 72 | && tar zxf v2.0.4.tar.gz \ 73 | && cp redis-lua-2.0.4/src/redis.lua /opt/ats/share/lua/5.1/redis.lua 74 | 75 | # ingress-ats 76 | RUN apk add --no-cache --virtual .ingress-build-deps \ 77 | bash gcc musl-dev openssl go 78 | 79 | # Installing Golang https://github.com/CentOS/CentOS-Dockerfiles/blob/master/golang/centos7/Dockerfile 80 | COPY GO_VERSION / 81 | RUN go_version=$(cat /GO_VERSION) \ 82 | && wget https://dl.google.com/go/go${go_version}.linux-amd64.tar.gz \ 83 | && rm -rf /opt/ats/go && tar -C /opt/ats -xzf go${go_version}.linux-amd64.tar.gz 84 | ENV PATH=${PATH}:/opt/ats/go/bin 85 | ENV GOPATH="/opt/ats/go/bin" 86 | 87 | # ----------------------- Copy over Project Code to Go path ------------------------ 88 | RUN mkdir -p /opt/ats/go/bin/src/github.com/apache/trafficserver-ingress-controller 89 | 90 | COPY ["./main/", "$GOPATH/src/github.com/apache/trafficserver-ingress-controller/main"] 91 | COPY ["./proxy/", "$GOPATH/src/github.com/apache/trafficserver-ingress-controller/proxy"] 92 | COPY ["./namespace/", "$GOPATH/src/github.com/apache/trafficserver-ingress-controller/namespace"] 93 | COPY ["./endpoint/", "$GOPATH/src/github.com/apache/trafficserver-ingress-controller/endpoint"] 94 | COPY ["./util/", "$GOPATH/src/github.com/apache/trafficserver-ingress-controller/util"] 95 | COPY ["./watcher/", "$GOPATH/src/github.com/apache/trafficserver-ingress-controller/watcher"] 96 | COPY ["./pluginats/", "/opt/ats/var/pluginats"] 97 | COPY ["./redis/", "$GOPATH/src/github.com/apache/trafficserver-ingress-controller/redis"] 98 | COPY ["./go.mod", "$GOPATH/src/github.com/apache/trafficserver-ingress-controller/go.mod"] 99 | COPY ["./go.sum", "$GOPATH/src/github.com/apache/trafficserver-ingress-controller/go.sum"] 100 | 101 | # Building Project Main 102 | WORKDIR /opt/ats/go/bin/src/github.com/apache/trafficserver-ingress-controller 103 | ENV GO111MODULE=on 104 | RUN /opt/ats/go/bin/go build -o /opt/ats/bin/ingress_ats main/main.go 105 | RUN /opt/ats/go/bin/go clean -modcache 106 | RUN rm -rf /opt/ats/go 107 | 108 | # redis conf 109 | COPY ["./config/redis.conf", "/opt/ats/etc/redis.conf"] 110 | 111 | # entry.sh + other scripts 112 | COPY ["./bin/tls-config.sh", "/opt/ats/bin/tls-config.sh"] 113 | COPY ["./bin/tls-reload.sh", "/opt/ats/bin/tls-reload.sh"] 114 | COPY ["./bin/records-config.sh", "/opt/ats/bin/records-config.sh"] 115 | COPY ["./bin/entry.sh", "/opt/ats/bin/entry.sh"] 116 | WORKDIR /opt/ats/bin/ 117 | RUN chmod 755 tls-config.sh 118 | RUN chmod 755 tls-reload.sh 119 | RUN chmod 755 records-config.sh 120 | RUN chmod 755 entry.sh 121 | 122 | # redis 123 | RUN mkdir -p /opt/ats/var/run/redis/ \ 124 | && touch /opt/ats/var/run/redis/redis.sock \ 125 | && mkdir -p /opt/ats/var/log/redis 126 | 127 | # set up ingress log location 128 | RUN mkdir -p /opt/ats/var/log/ingress/ 129 | 130 | FROM alpine:3.20.6 131 | 132 | # essential library 133 | RUN apk add --no-cache -U \ 134 | bash \ 135 | build-base \ 136 | curl \ 137 | nghttp2-libs \ 138 | ca-certificates \ 139 | pcre \ 140 | zlib \ 141 | openssl \ 142 | brotli \ 143 | jansson \ 144 | libunwind \ 145 | readline \ 146 | geoip \ 147 | redis \ 148 | tcl \ 149 | openrc \ 150 | inotify-tools \ 151 | cpulimit \ 152 | libxml2 153 | 154 | RUN apk add --no-cache -U --repository https://dl-cdn.alpinelinux.org/alpine/edge/community hwloc 155 | 156 | RUN apk add --no-cache --repository=https://dl-cdn.alpinelinux.org/alpine/v3.16/main libexecinfo 157 | 158 | RUN apk add --no-cache --repository=https://dl-cdn.alpinelinux.org/alpine/v3.19/main luajit=2.1_p20230410-r3 159 | 160 | # symlink for luajit 161 | RUN ln -sf /usr/lib/libluajit-5.1.so.2.1.0 /usr/lib/libluajit-5.1.so 162 | 163 | # create ats user/group 164 | RUN addgroup -Sg 1000 ats 165 | 166 | RUN adduser -S -D -H -u 1000 -h /tmp -s /sbin/nologin -G ats -g ats ats 167 | 168 | COPY --from=builder --chown=ats:ats /opt/ats /opt/ats 169 | 170 | USER ats 171 | 172 | ENTRYPOINT ["/opt/ats/bin/entry.sh"] 173 | -------------------------------------------------------------------------------- /GO_VERSION: -------------------------------------------------------------------------------- 1 | 1.23.10 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 19 | 20 | ATS Kubernetes Ingress Controller 21 | ================================= 22 | [![Project Status: Concept – Minimal or no implementation has been done yet, or the repository is only intended to be a limited example, demo, or proof-of-concept.](https://www.repostatus.org/badges/latest/concept.svg)](https://www.repostatus.org/#concept) 23 | ![Test](https://github.com/apache/trafficserver-ingress-controller/actions/workflows/test.yml/badge.svg) 24 | ![Build and Integrate](https://github.com/apache/trafficserver-ingress-controller/actions/workflows/build.yml/badge.svg) 25 | [![Go Report 26 | Card](https://goreportcard.com/badge/github.com/apache/trafficserver-ingress-controller)](https://goreportcard.com/report/github.com/apache/trafficserver-ingress-controller) 27 | 28 | ## Introduction 29 | [Apache Traffic Server (ATS)](https://trafficserver.apache.org/) is a high performance, open-source, caching proxy server that is scalable and configurable. This project uses ATS as a [Kubernetes(K8s)](https://kubernetes.io/) [ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) 30 | 31 | - [Architecture](https://github.com/apache/trafficserver-ingress-controller/blob/master/docs/ARCHITECTURE.md) 32 | - [Tutorial](https://github.com/apache/trafficserver-ingress-controller/blob/master/docs/TUTORIAL.md) 33 | - [Development](https://github.com/apache/trafficserver-ingress-controller/blob/master/docs/DEVELOPMENT.md) 34 | 35 | ## Dependencies 36 | - Alpine Linux 3.20.6 37 | - Apache Traffic Server 9.2.10 38 | - OpenResty LuaJIT2 v2.1-20230410 39 | - Go (Version can be found in `GO_VERSION` file found at the base of this repository) 40 | - Other Packages 41 | - luasocket 3.0.0 42 | - redis-lua 2.0.4 43 | - Tested on Minikube 1.35.0 / Kubernetes 1.32.5 44 | 45 | -------------------------------------------------------------------------------- /bin/entry.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one or more 4 | # contributor license agreements. See the NOTICE file distributed with 5 | # this work for additional information regarding copyright ownership. 6 | # The ASF licenses this file to You under the Apache License, Version 2.0 7 | # (the "License"); you may not use this file except in compliance with 8 | # the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | set +x 20 | 21 | # TLS auto reload script 22 | #/opt/ats/bin/tls-reload.sh >> /opt/ats/var/log/ingress/ingress_ats.err & 23 | 24 | # generate TLS cert config file for ats 25 | /opt/ats/bin/tls-config.sh 26 | 27 | # append specific environment variables to records.config 28 | /opt/ats/bin/records-config.sh 29 | 30 | # append extra plugins to plugin.config 31 | if [ ! -f "${EXTRA_PLUGIN_FNAME}" ]; then 32 | cat $EXTRA_PLUGIN_FNAME >> /opt/ats/etc/trafficserver/plugin.config 33 | fi 34 | 35 | # replace lua plugin parameters to plugin.config if snippet is allowed 36 | if [ ! -z "${SNIPPET}" ]; then 37 | sed -i 's/tslua.so \/opt\/ats\/var\/pluginats\/connect_redis.lua/tslua.so \/opt\/ats\/var\/pluginats\/connect_redis.lua snippet/' /opt/ats/etc/trafficserver/plugin.config 38 | fi 39 | 40 | # start redis 41 | redis-server /opt/ats/etc/redis.conf 42 | 43 | # create health check file and start ats 44 | touch /opt/ats/var/run/ts-alive 45 | # chown -R nobody:nobody /opt/ats/etc/trafficserver 46 | DISTRIB_ID=gentoo /opt/ats/bin/trafficserver start 47 | 48 | if [ -z "${INGRESS_NS}" ]; then 49 | INGRESS_NS="all" 50 | fi 51 | 52 | if [ -z "${RESYNC_PERIOD}" ]; then 53 | RESYNC_PERIOD="0" 54 | fi 55 | 56 | if [ -z "${INGRESS_DEBUG}" ]; then 57 | /opt/ats/bin/ingress_ats -atsIngressClass="$INGRESS_CLASS" -atsNamespace="$POD_NAMESPACE" -namespaces="$INGRESS_NS" -ignoreNamespaces="$INGRESS_IGNORE_NS" -useInClusterConfig=T -resyncPeriod="$RESYNC_PERIOD" 58 | else 59 | /opt/ats/bin/ingress_ats -atsIngressClass="$INGRESS_CLASS" -atsNamespace="$POD_NAMESPACE" -namespaces="$INGRESS_NS" -ignoreNamespaces="$INGRESS_IGNORE_NS" -useInClusterConfig=T -resyncPeriod="$RESYNC_PERIOD" 2>>/opt/ats/var/log/ingress/ingress_ats.err 60 | fi 61 | -------------------------------------------------------------------------------- /bin/records-config.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one or more 4 | # contributor license agreements. See the NOTICE file distributed with 5 | # this work for additional information regarding copyright ownership. 6 | # The ASF licenses this file to You under the Apache License, Version 2.0 7 | # (the "License"); you may not use this file except in compliance with 8 | # the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | set +x 20 | 21 | if [ ! -z "${LOG_CONFIG_FNAME}" ]; then 22 | echo "CONFIG proxy.config.log.config.filename STRING ${LOG_CONFIG_FNAME}" >> /opt/ats/etc/trafficserver/records.config 23 | fi 24 | 25 | if [ ! -z "${SSL_SERVERNAME_FNAME}" ]; then 26 | echo "CONFIG proxy.config.ssl.servername.filename STRING ${SSL_SERVERNAME_FNAME}" >> /opt/ats/etc/trafficserver/records.config 27 | fi 28 | -------------------------------------------------------------------------------- /bin/tls-config.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one or more 4 | # contributor license agreements. See the NOTICE file distributed with 5 | # this work for additional information regarding copyright ownership. 6 | # The ASF licenses this file to You under the Apache License, Version 2.0 7 | # (the "License"); you may not use this file except in compliance with 8 | # the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | set +x 20 | 21 | if [ -z "${POD_TLS_PATH}" ]; then 22 | echo "POD_TLS_PATH not defined" 23 | exit 1 24 | fi 25 | 26 | tlspath="$POD_TLS_PATH/" 27 | tlskey="$POD_TLS_PATH/tls.key" 28 | tlscrt="$POD_TLS_PATH/tls.crt" 29 | 30 | if [ ! -f "${tlscrt}" ]; then 31 | echo "${tlscrt} not found" 32 | exit 1 33 | fi 34 | 35 | if [ ! -f "${tlskey}" ]; then 36 | echo "${tlskey} not found" 37 | exit 1 38 | fi 39 | 40 | echo "dest_ip=* ssl_cert_name=${tlscrt} ssl_key_name=${tlskey}" > /opt/ats/etc/trafficserver/ssl_multicert.config 41 | -------------------------------------------------------------------------------- /bin/tls-reload.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one or more 4 | # contributor license agreements. See the NOTICE file distributed with 5 | # this work for additional information regarding copyright ownership. 6 | # The ASF licenses this file to You under the Apache License, Version 2.0 7 | # (the "License"); you may not use this file except in compliance with 8 | # the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | set +x 20 | 21 | if [ -z "${POD_TLS_PATH}" ]; then 22 | echo "POD_TLS_PATH not defined" 23 | exit 1 24 | fi 25 | 26 | tlspath="$POD_TLS_PATH/" 27 | tlskey="$POD_TLS_PATH/tls.key" 28 | tlscrt="$POD_TLS_PATH/tls.crt" 29 | 30 | if [ ! -f "${tlscrt}" ]; then 31 | echo "${tlscrt} not found" 32 | exit 1 33 | fi 34 | 35 | oldcksum=`cksum ${tlscrt}` 36 | 37 | inotifywait -e modify,move,create,delete -mr --timefmt '%d/%m/%y %H:%M' --format '%T' \ 38 | ${tlspath} | while read date time; do 39 | 40 | newcksum=`cksum ${tlscrt}` 41 | if [ "$newcksum" != "$oldcksum" ]; then 42 | echo "At ${time} on ${date}, tls cert/key files update detected." 43 | oldcksum=$newcksum 44 | touch /opt/ats/etc/trafficserver/ssl_multicert.config 45 | traffic_ctl config reload 46 | fi 47 | done 48 | 49 | -------------------------------------------------------------------------------- /charts/ats-ingress/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /charts/ats-ingress/Chart.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | apiVersion: v2 18 | name: ats-ingress 19 | description: A Helm chart for Kubernetes 20 | 21 | # A chart can be either an 'application' or a 'library' chart. 22 | # 23 | # Application charts are a collection of templates that can be packaged into versioned archives 24 | # to be deployed. 25 | # 26 | # Library charts provide useful utilities or functions for the chart developer. They're included as 27 | # a dependency of application charts to inject those utilities and functions into the rendering 28 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 29 | type: application 30 | 31 | # This is the chart version. This version number should be incremented each time you make changes 32 | # to the chart and its templates, including the app version. 33 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 34 | version: 0.1.0 35 | 36 | # This is the version number of the application being deployed. This version number should be 37 | # incremented each time you make changes to the application. Versions are not expected to 38 | # follow Semantic Versioning. They should reflect the version the application is using. 39 | appVersion: 0.1.0 40 | -------------------------------------------------------------------------------- /charts/ats-ingress/README.md: -------------------------------------------------------------------------------- 1 | 19 | 20 | # Helm support 21 | This is the ats-ingress chart repository for Helm V3. 22 | 23 | ## To install from git source 24 | 1. git clone the project 25 | 2. `$ kubectl create namespace ats-helm` 26 | 3. `$ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=atssvc/O=atssvc"` 27 | 4. `$ kubectl create secret tls tls-secret --key tls.key --cert tls.crt -n ats-helm --dry-run=client -o yaml | kubectl apply -f -` 28 | 5. `$ helm install charts/ats-ingress --generate-name -n ats-helm` 29 | 30 | ## To install from helm repo 31 | 1. `$ kubectl create namespace ats-helm` 32 | 2. `$ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=atssvc/O=atssvc"` 33 | 3. `$ kubectl create secret tls tls-secret --key tls.key --cert tls.crt -n ats-helm --dry-run=client -o yaml | kubectl apply -f -` 34 | 4. `$ helm repo add ats-ingress https://apache.github.io/trafficserver-ingress-controller` 35 | 5. `$ helm repo update` 36 | 6. `$ helm install ats-ingress/ats-ingress --generate-name -n ats-helm` 37 | 38 | -------------------------------------------------------------------------------- /charts/ats-ingress/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | ATS Ingress Controller has been successfully installed. 2 | 3 | Controller image deployed is: "{{ .Values.controller.image.repository }}:{{ tpl .Values.controller.image.tag . }}". 4 | 5 | For more examples and up to date documentation, please visit: 6 | https://github.com/apache/trafficserver-ingress-controller/ 7 | 8 | -------------------------------------------------------------------------------- /charts/ats-ingress/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | {{/* vim: set filetype=mustache: */}} 17 | {{/* 18 | Expand the name of the chart. 19 | */}} 20 | {{- define "ats-ingress.name" -}} 21 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | 24 | {{/* 25 | Create a default fully qualified app name. 26 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 27 | If release name contains chart name it will be used as a full name. 28 | */}} 29 | {{- define "ats-ingress.fullname" -}} 30 | {{- if .Values.fullnameOverride }} 31 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 32 | {{- else }} 33 | {{- $name := default .Chart.Name .Values.nameOverride }} 34 | {{- if contains $name .Release.Name }} 35 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 36 | {{- else }} 37 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 38 | {{- end }} 39 | {{- end }} 40 | {{- end }} 41 | 42 | {{/* 43 | Create chart name and version as used by the chart label. 44 | */}} 45 | {{- define "ats-ingress.chart" -}} 46 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 47 | {{- end }} 48 | 49 | {{/* 50 | Encode an imagePullSecret string. 51 | */}} 52 | {{- define "ats-ingress.imagePullSecret" }} 53 | {{- printf "{\"auths\": {\"%s\": {\"auth\": \"%s\"}}}" .Values.controller.imageCredentials.registry (printf "%s:%s" .Values.controller.imageCredentials.username .Values.controller.imageCredentials.password | b64enc) | b64enc }} 54 | {{- end }} 55 | 56 | {{/* 57 | Create the name of the controller service account to use. 58 | */}} 59 | {{- define "ats-ingress.serviceAccountName" -}} 60 | {{- if .Values.serviceAccount.create -}} 61 | {{ default (include "ats-ingress.fullname" .) .Values.serviceAccount.name }} 62 | {{- else -}} 63 | {{ default "default" .Values.serviceAccount.name }} 64 | {{- end -}} 65 | {{- end -}} 66 | -------------------------------------------------------------------------------- /charts/ats-ingress/templates/clusterrole.yaml: -------------------------------------------------------------------------------- 1 | {{/* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */}} 18 | 19 | {{- if .Values.rbac.create -}} 20 | apiVersion: rbac.authorization.k8s.io/v1 21 | kind: ClusterRole 22 | metadata: 23 | name: {{ include "ats-ingress.fullname" . }} 24 | labels: 25 | app.kubernetes.io/name: {{ include "ats-ingress.name" . }} 26 | helm.sh/chart: {{ include "ats-ingress.chart" . }} 27 | app.kubernetes.io/managed-by: {{ .Release.Service }} 28 | app.kubernetes.io/instance: {{ .Release.Name }} 29 | app.kubernetes.io/version: {{ .Chart.AppVersion }} 30 | rules: 31 | - apiGroups: 32 | - "" 33 | resources: 34 | - configmaps 35 | - endpoints 36 | - services 37 | - namespaces 38 | - events 39 | - secrets 40 | verbs: 41 | - get 42 | - list 43 | - watch 44 | - apiGroups: 45 | - "extensions" 46 | - "networking.k8s.io" 47 | resources: 48 | - ingresses 49 | - ingresses/status 50 | - ingressclasses 51 | verbs: 52 | - get 53 | - list 54 | - watch 55 | - apiGroups: 56 | - "extensions" 57 | - "networking.k8s.io" 58 | resources: 59 | - ingresses/status 60 | verbs: 61 | - update 62 | {{- end -}} 63 | 64 | -------------------------------------------------------------------------------- /charts/ats-ingress/templates/clusterrolebinding.yaml: -------------------------------------------------------------------------------- 1 | {{/* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */}} 18 | 19 | {{- if .Values.rbac.create -}} 20 | apiVersion: rbac.authorization.k8s.io/v1 21 | kind: ClusterRoleBinding 22 | metadata: 23 | name: {{ include "ats-ingress.fullname" . }} 24 | labels: 25 | app.kubernetes.io/name: {{ include "ats-ingress.name" . }} 26 | helm.sh/chart: {{ include "ats-ingress.chart" . }} 27 | app.kubernetes.io/managed-by: {{ .Release.Service }} 28 | app.kubernetes.io/instance: {{ .Release.Name }} 29 | app.kubernetes.io/version: {{ .Chart.AppVersion }} 30 | roleRef: 31 | apiGroup: rbac.authorization.k8s.io 32 | kind: ClusterRole 33 | name: {{ include "ats-ingress.fullname" . }} 34 | subjects: 35 | - kind: ServiceAccount 36 | name: {{ include "ats-ingress.serviceAccountName" . }} 37 | namespace: {{ .Release.Namespace }} 38 | {{- end -}} 39 | 40 | -------------------------------------------------------------------------------- /charts/ats-ingress/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | apiVersion: apps/v1 17 | kind: Deployment 18 | metadata: 19 | name: {{ include "ats-ingress.fullname" . }} 20 | namespace: {{ .Release.Namespace }} 21 | labels: 22 | app.kubernetes.io/name: {{ include "ats-ingress.name" . }} 23 | helm.sh/chart: {{ include "ats-ingress.chart" . }} 24 | app.kubernetes.io/managed-by: {{ .Release.Service }} 25 | app.kubernetes.io/instance: {{ .Release.Name }} 26 | app.kubernetes.io/version: {{ .Chart.AppVersion }} 27 | {{- if .Values.controller.extraLabels }} 28 | {{ toYaml .Values.controller.extraLabels | indent 4 }} 29 | {{- end }} 30 | spec: 31 | minReadySeconds: {{ .Values.controller.minReadySeconds }} 32 | 33 | {{- if not .Values.controller.autoscaling.enabled }} 34 | replicas: {{ .Values.controller.replicaCount }} 35 | {{- end }} 36 | 37 | selector: 38 | matchLabels: 39 | app.kubernetes.io/name: {{ include "ats-ingress.name" . }} 40 | app.kubernetes.io/instance: {{ .Release.Name }} 41 | {{- with .Values.controller.strategy }} 42 | strategy: 43 | {{- toYaml . | nindent 4 }} 44 | {{- end }} 45 | template: 46 | metadata: 47 | labels: 48 | app.kubernetes.io/name: {{ include "ats-ingress.name" . }} 49 | app.kubernetes.io/instance: {{ .Release.Name }} 50 | {{- if .Values.controller.podLabels }} 51 | {{ toYaml .Values.controller.podLabels | indent 8 }} 52 | {{- end }} 53 | {{- if .Values.controller.podAnnotations }} 54 | annotations: 55 | {{ toYaml .Values.controller.podAnnotations | indent 8 }} 56 | {{- end }} 57 | spec: 58 | serviceAccountName: {{ include "ats-ingress.serviceAccountName" . }} 59 | terminationGracePeriodSeconds: {{ .Values.controller.terminationGracePeriodSeconds }} 60 | {{- with .Values.controller.topologySpreadConstraints }} 61 | topologySpreadConstraints: 62 | {{- toYaml . | nindent 8 }} 63 | {{- end }} 64 | {{- if .Values.controller.podSecurityContext }} 65 | securityContext: 66 | {{ toYaml .Values.controller.podSecurityContext | indent 8 }} 67 | {{- end }} 68 | {{- if .Values.controller.dnsConfig }} 69 | dnsConfig: 70 | {{ toYaml .Values.controller.dnsConfig | indent 8 }} 71 | {{- end }} 72 | dnsPolicy: {{ .Values.controller.dnsPolicy }} 73 | {{- if .Values.controller.imageCredentials.registry }} 74 | imagePullSecrets: 75 | - name: {{ include "ats-ingress.fullname" . }} 76 | {{- else if .Values.controller.existingImagePullSecret }} 77 | imagePullSecrets: 78 | - name: {{ .Values.controller.existingImagePullSecret }} 79 | {{- end }} 80 | {{- if .Values.controller.priorityClassName }} 81 | priorityClassName: {{ .Values.controller.priorityClassName }} 82 | {{- end }} 83 | containers: 84 | - name: {{ .Chart.Name }} 85 | image: {{ .Values.controller.image.repository }}:{{ .Values.controller.image.tag | default .Chart.AppVersion }} 86 | imagePullPolicy: {{ .Values.controller.image.pullPolicy }} 87 | {{- if .Values.controller.command }} 88 | command: {{ .Values.controller.command }} 89 | {{- end }} 90 | args: 91 | {{- range .Values.controller.extraArgs }} 92 | - {{ . }} 93 | {{- end }} 94 | volumeMounts: 95 | - mountPath: {{ .Values.controller.ssl.path | quote }} 96 | name: {{ .Values.controller.ssl.name }} 97 | readOnly: true 98 | - name: log-trafficserver 99 | mountPath: {{ .Values.controller.log.trafficserver.dir }} 100 | - name: log-ingress 101 | mountPath: {{ .Values.controller.log.ingress.dir }} 102 | {{- if .Values.controller.extraVolumeMounts -}} 103 | {{- toYaml .Values.controller.extraVolumeMounts | nindent 10 }} 104 | {{- end }} 105 | env: 106 | - name: POD_NAME 107 | valueFrom: 108 | fieldRef: 109 | fieldPath: metadata.name 110 | - name: POD_NAMESPACE 111 | valueFrom: 112 | fieldRef: 113 | fieldPath: metadata.namespace 114 | - name: POD_TLS_PATH 115 | value: {{ .Values.controller.ssl.path | quote }} 116 | {{- if .Values.controller.extraEnvs -}} 117 | {{- toYaml .Values.controller.extraEnvs | nindent 10 }} 118 | {{- end }} 119 | ports: 120 | - containerPort: 8080 121 | name: http 122 | protocol: TCP 123 | - containerPort: 8443 124 | name: https 125 | protocol: TCP 126 | resources: 127 | {{- toYaml .Values.controller.resources | nindent 12 }} 128 | {{- if .Values.controller.livenessProbe }} 129 | livenessProbe: 130 | {{ toYaml .Values.controller.livenessProbe | indent 12 }} 131 | {{- end }} 132 | {{- if .Values.controller.readinessProbe }} 133 | readinessProbe: 134 | {{ toYaml .Values.controller.readinessProbe | indent 12 }} 135 | {{- end }} 136 | {{- if .Values.controller.startupProbe }} 137 | startupProbe: 138 | {{ toYaml .Values.controller.startupProbe | indent 12 }} 139 | {{- end }} 140 | {{- if .Values.controller.securityContext }} 141 | securityContext: 142 | {{ toYaml .Values.controller.securityContext | indent 12 }} 143 | {{- end }} 144 | {{- if .Values.controller.lifecycle }} 145 | lifecycle: 146 | {{- if eq "string" (printf "%T" .Values.controller.lifecycle) }} 147 | {{ tpl .Values.controller.lifecycle . | indent 12 }} 148 | {{- else }} 149 | {{ toYaml .Values.controller.lifecycle | indent 12 }} 150 | {{- end }} 151 | {{- end }} 152 | {{- if .Values.controller.extraContainers }} 153 | {{- if eq "string" (printf "%T" .Values.controller.extraContainers) }} 154 | {{ tpl .Values.controller.extraContainers . | indent 8 }} 155 | {{- else }} 156 | {{ toYaml .Values.controller.extraContainers | indent 8 }} 157 | {{- end }} 158 | {{- end }} 159 | volumes: 160 | - name: {{ .Values.controller.ssl.name }} 161 | secret: 162 | secretName: {{ .Values.controller.ssl.secret }} 163 | - name: log-trafficserver 164 | emptyDir: {} 165 | - name: log-ingress 166 | emptyDir: {} 167 | {{- if .Values.controller.extraVolumes }} 168 | {{- if eq "string" (printf "%T" .Values.controller.extraVolumes) }} 169 | {{ tpl .Values.controller.extraVolumes . | indent 8 }} 170 | {{- else }} 171 | {{ toYaml .Values.controller.extraVolumes | indent 8 }} 172 | {{- end }} 173 | {{- end }} 174 | {{- with.Values.controller.initContainers }} 175 | initContainers: 176 | {{- toYaml . | nindent 8 }} 177 | {{- end }} 178 | {{- with .Values.controller.nodeSelector }} 179 | nodeSelector: 180 | {{- toYaml . | nindent 8 }} 181 | {{- end }} 182 | {{- with .Values.controller.affinity }} 183 | affinity: 184 | {{- toYaml . | nindent 8 }} 185 | {{- end }} 186 | {{- with .Values.controller.tolerations }} 187 | tolerations: 188 | {{- toYaml . | nindent 8 }} 189 | {{- end }} 190 | 191 | -------------------------------------------------------------------------------- /charts/ats-ingress/templates/pullsecret.yaml: -------------------------------------------------------------------------------- 1 | {{/* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */}} 18 | 19 | {{- if .Values.controller.imageCredentials.registry -}} 20 | apiVersion: v1 21 | kind: Secret 22 | metadata: 23 | name: {{ include "ats-ingress.fullname" . }} 24 | namespace: {{ .Release.Namespace }} 25 | labels: 26 | app.kubernetes.io/name: {{ include "ats-ingress.name" . }} 27 | helm.sh/chart: {{ include "ats-ingress.chart" . }} 28 | app.kubernetes.io/managed-by: {{ .Release.Service }} 29 | app.kubernetes.io/instance: {{ .Release.Name }} 30 | app.kubernetes.io/version: {{ .Chart.AppVersion }} 31 | type: kubernetes.io/dockerconfigjson 32 | data: 33 | .dockerconfigjson: {{ include "ats-ingress.imagePullSecret" . }} 34 | {{- end -}} 35 | -------------------------------------------------------------------------------- /charts/ats-ingress/templates/service.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | apiVersion: v1 18 | kind: Service 19 | metadata: 20 | name: {{ include "ats-ingress.fullname" . }} 21 | namespace: {{ .Release.Namespace }} 22 | labels: 23 | app.kubernetes.io/name: {{ include "ats-ingress.name" . }} 24 | helm.sh/chart: {{ include "ats-ingress.chart" . }} 25 | app.kubernetes.io/managed-by: {{ .Release.Service }} 26 | app.kubernetes.io/instance: {{ .Release.Name }} 27 | app.kubernetes.io/version: {{ .Chart.AppVersion }} 28 | {{- if .Values.controller.service.labels }} 29 | {{ toYaml .Values.controller.service.labels | indent 4 }} 30 | {{- end }} 31 | annotations: 32 | {{- range $key, $value := .Values.controller.service.annotations }} 33 | {{ $key }}: {{ $value | quote }} 34 | {{- end }} 35 | spec: 36 | {{ with .Values.controller.service.clusterIP }}clusterIP: {{ . }}{{ end }} 37 | type: {{ .Values.controller.service.type }} 38 | {{- if .Values.controller.service.externalTrafficPolicy }} 39 | externalTrafficPolicy: {{ .Values.controller.service.externalTrafficPolicy }} 40 | {{- end }} 41 | {{- if .Values.controller.service.healthCheckNodePort }} 42 | healthCheckNodePort: {{ .Values.controller.service.healthCheckNodePort }} 43 | {{- end }} 44 | ports: 45 | - name: http 46 | port: {{ .Values.controller.service.http.port }} 47 | protocol: TCP 48 | targetPort: {{ .Values.controller.service.http.targetPort }} 49 | nodePort: {{ .Values.controller.service.http.nodePort }} 50 | - name: https 51 | port: {{ .Values.controller.service.https.port }} 52 | protocol: TCP 53 | targetPort: {{ .Values.controller.service.https.targetPort }} 54 | nodePort: {{ .Values.controller.service.https.nodePort }} 55 | selector: 56 | app.kubernetes.io/name: {{ template "ats-ingress.name" . }} 57 | app.kubernetes.io/instance: {{ .Release.Name }} 58 | {{- if .Values.controller.service.sessionAffinity }} 59 | sessionAffinity: {{ .Values.controller.service.sessionAffinity }} 60 | {{- end }} 61 | externalIPs: 62 | {{- if .Values.controller.service.externalIPs }} 63 | {{ toYaml .Values.controller.service.externalIPs | indent 4 }} 64 | {{- end -}} 65 | {{- if (eq .Values.controller.service.type "LoadBalancer") }} 66 | {{- if .Values.controller.service.loadBalancerIP }} 67 | loadBalancerIP: "{{ .Values.controller.service.loadBalancerIP }}" 68 | {{- end }} 69 | {{- if .Values.controller.service.loadBalancerSourceRanges }} 70 | loadBalancerSourceRanges: 71 | {{ toYaml .Values.controller.service.loadBalancerSourceRanges | indent 4 }} 72 | {{- end }} 73 | {{- end }} 74 | -------------------------------------------------------------------------------- /charts/ats-ingress/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{/* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */}} 18 | 19 | {{- if .Values.serviceAccount.create -}} 20 | apiVersion: v1 21 | kind: ServiceAccount 22 | metadata: 23 | name: {{ include "ats-ingress.serviceAccountName" . }} 24 | namespace: {{ .Release.Namespace }} 25 | labels: 26 | app.kubernetes.io/name: {{ include "ats-ingress.name" . }} 27 | helm.sh/chart: {{ include "ats-ingress.chart" . }} 28 | app.kubernetes.io/managed-by: {{ .Release.Service }} 29 | app.kubernetes.io/instance: {{ .Release.Name }} 30 | app.kubernetes.io/version: {{ .Chart.AppVersion }} 31 | {{- end -}} 32 | 33 | -------------------------------------------------------------------------------- /charts/ats-ingress/values.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Default values for ats-ingress. 18 | # This is a YAML-formatted file. 19 | # Declare variables to be passed into your templates. 20 | 21 | ## Enable RBAC Authorization 22 | ## ref: https://kubernetes.io/docs/reference/access-authn-authz/rbac/ 23 | rbac: 24 | create: true 25 | 26 | ## Configure Service Account 27 | ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ 28 | serviceAccount: 29 | create: true 30 | name: 31 | 32 | ## Override name / fullname for the controller 33 | nameOverride: "" 34 | fullnameOverride: "" 35 | 36 | ## Controller default values 37 | controller: 38 | name: controller 39 | 40 | ## Image Information 41 | image: 42 | repository: ghcr.io/apache/ats-ingress 43 | pullPolicy: IfNotPresent 44 | tag: latest 45 | 46 | ## Private Registry configuration 47 | ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ 48 | imageCredentials: 49 | registry: null 50 | username: null 51 | password: null 52 | existingImagePullSecret: null 53 | 54 | ## optionally override entrypoint 55 | command: "" 56 | 57 | ## Additional command line arguments to pass to Controller 58 | extraArgs: [] 59 | 60 | ## Security Context for pod 61 | ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ 62 | podSecurityContext: {} 63 | 64 | ## Security Context for container 65 | ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ 66 | securityContext: {} 67 | 68 | ## Controller Container liveness/readiness probe configuration 69 | ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ 70 | livenessProbe: {} 71 | readinessProbe: {} 72 | startupProbe: {} 73 | 74 | ## Controller Service configuration 75 | ## ref: https://kubernetes.io/docs/concepts/services-networking/service/ 76 | service: 77 | type: NodePort 78 | 79 | ## Service annotations 80 | ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ 81 | annotations: {} 82 | 83 | ## Service labels 84 | ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ 85 | labels: {} 86 | 87 | http: 88 | port: 8080 89 | targetPort: 8080 90 | nodePort: 30080 91 | https: 92 | port: 8443 93 | targetPort: 8443 94 | nodePort: 30443 95 | 96 | # clusterIP: "" 97 | 98 | # sessionAffinity: "" 99 | 100 | # externalTrafficPolicy: "Local" 101 | 102 | healthCheckNodePort: 0 103 | 104 | externalIPs: [] 105 | 106 | loadBalancerIP: "" 107 | 108 | loadBalancerSourceRanges: [] 109 | 110 | replicaCount: 1 111 | 112 | autoscaling: 113 | enabled: false 114 | 115 | ## SSL certificate information 116 | ssl: 117 | path: "/etc/ats/ssl" 118 | name: ats-ssl 119 | secret: tls-secret 120 | 121 | ## log location for ATS and controller program 122 | log: 123 | trafficserver: 124 | dir: /opt/ats/var/log/trafficserver 125 | ingress: 126 | dir: /opt/ats/var/log/ingress 127 | 128 | ## Additional labels to add to the deployment or daemonset metadata 129 | ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ 130 | extraLabels: {} 131 | 132 | ## Additional labels to add to the pod container metadata 133 | ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ 134 | podLabels: {} 135 | 136 | ## Additional annotations to add to the pod container metadata 137 | ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ 138 | podAnnotations: {} 139 | 140 | ## Set additional environment variables 141 | extraEnvs: [] 142 | 143 | ## Add additional containers 144 | extraContainers: [] 145 | 146 | ## Volumes required by additional containers 147 | extraVolumes: [] 148 | 149 | ## Volume Mounts 150 | extraVolumeMounts: [] 151 | 152 | ## Init Containers 153 | ## ref: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ 154 | initContainers: [] 155 | 156 | ## Pod Node assignment 157 | ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ 158 | nodeSelector: {} 159 | 160 | ## Node Taints and Tolerations for pod-node scheduling through attraction/repelling 161 | ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ 162 | tolerations: [] 163 | 164 | ## Node Affinity for pod-node scheduling constraints 165 | ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity 166 | affinity: {} 167 | 168 | ## Topology spread constraints 169 | ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ 170 | topologySpreadConstraints: [] 171 | 172 | ## Pod DNS Config 173 | ## ref: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/ 174 | dnsConfig: {} 175 | 176 | ## Pod DNS Policy 177 | ## ref: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy 178 | dnsPolicy: Default 179 | 180 | ## Controller Pod PriorityClass 181 | ## ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass 182 | priorityClassName: "" 183 | 184 | ## Compute Resources for controller container 185 | ## ref: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/ 186 | resources: {} 187 | 188 | ## Controller deployment strategy definition 189 | ## ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy 190 | strategy: {} 191 | 192 | ## Controller container lifecycle handlers 193 | ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/attach-handler-lifecycle-event/ 194 | lifecycle: {} 195 | 196 | ## Pod termination grace period 197 | ## ref: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/ 198 | terminationGracePeriodSeconds: 60 199 | 200 | ## minimum number of seconds for which a newly created Pod should be ready 201 | ## ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#min-ready-seconds 202 | minReadySeconds: 30 203 | -------------------------------------------------------------------------------- /config/healthchecks.config: -------------------------------------------------------------------------------- 1 | /status.html /opt/ats/var/run/ts-alive text/plain 200 404 2 | -------------------------------------------------------------------------------- /config/logging.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Custom log configuration 18 | # 19 | # Documentation on logging: 20 | # https://docs.trafficserver.apache.org/en/8.0.x/admin-guide/logging/index.en.html 21 | # 22 | # Documentaion on logging.yaml file format: 23 | # https://docs.trafficserver.apache.org/en/8.0.x/admin-guide/files/logging.yaml.en.html 24 | # 25 | # Example log configurations: 26 | # https://docs.trafficserver.apache.org/en/8.0.x/admin-guide/logging/examples.en.html 27 | 28 | logging: 29 | formats: 30 | # WebTrends Enhanced Log Format. 31 | # 32 | # The following is compatible with the WebTrends Enhanced Log Format. 33 | # If you want to generate a log that can be parsed by WebTrends 34 | # reporting tools, simply create a log that uses this format. 35 | - name: welf 36 | format: |- 37 | id=firewall time="% %" fw=% pri=6 proto=% duration=% sent=% rcvd=% src=% dst=% dstname=% user=% op=% arg="%" result=% ref="%<{Referer}cqh>" agent="%<{user-agent}cqh>" cache=% 38 | # Squid Log Format with seconds resolution timestamp. 39 | # The following is the squid format but with a seconds-only timestamp 40 | # (cqts) instead of a seconds and milliseconds timestamp (cqtq). 41 | - name: squid_seconds_only_timestamp 42 | format: '% % % %/% % % % % %/% %' 43 | 44 | # Squid Log Format. 45 | - name: squid 46 | format: '% % % %/% % % % % %/% %' 47 | 48 | # Common Log Format. 49 | - name: common 50 | format: '% - % [%] "%" % %' 51 | 52 | # Extended Log Format. 53 | - name: 'extended' 54 | format: '% - % [%] "%" % % % % % % % % % % %' 55 | 56 | # Extended2 Log Formats 57 | - name: "extended2" 58 | format: '% - % [%] "%" % % % % % % % % % % % % % % %' 59 | 60 | logs: 61 | - filename: squid 62 | format: squid 63 | mode: ascii 64 | 65 | 66 | # vim: set ft=yaml : 67 | -------------------------------------------------------------------------------- /config/plugin.config: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | healthchecks.so /opt/ats/etc/trafficserver/healthchecks.config 18 | tslua.so /opt/ats/var/pluginats/connect_redis.lua 19 | stats_over_http.so 20 | -------------------------------------------------------------------------------- /config/redis.conf: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | port 0 18 | unixsocket /opt/ats/var/run/redis/redis.sock 19 | unixsocketperm 777 20 | daemonize yes 21 | 22 | # no need for snapshot 23 | save "" 24 | stop-writes-on-bgsave-error no 25 | -------------------------------------------------------------------------------- /docs/ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | 19 | 20 | ## Architecture 21 | [Apache Traffic Server (ATS)](https://trafficserver.apache.org/) is a high performance, open-source, caching proxy server that is scalable and configurable. This project uses ATS as a [Kubernetes(K8s)](https://kubernetes.io/) [ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) 22 | 23 | ![Abstract](images/abstract.png) 24 | 25 | From high-level, the ingress controller talks to K8s' API and sets up `watchers` on specific resources that are interesting to ATS. Then, the controller _controls_ ATS by either(1) relay the information from K8s API to ATS, or (2) configure ATS directly. 26 | 27 | ![How](images/how-it-works.png) 28 | 29 | -------------------------------------------------------------------------------- /docs/DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | 19 | 20 | ## Development 21 | 22 | ### Develop with Go-Lang in Linux 23 | 1. Get Go-lang from [official site](https://golang.org/dl/). Check out the version used in `GO_VERSION` file at the base of this repository. 24 | 2. Add `go` command to your PATH: `export PATH=$PATH:/usr/local/go/bin` 25 | 3. Define GOPATH: `export GOPATH=$(go env GOPATH)` 26 | 4. Add Go workspace to your PATH: `export PATH=$PATH:$(go env GOPATH)/bin` 27 | 5. Define the base path: `mkdir -p $GOPATH/src/github.com/apache` 28 | 6. Clone the project: 29 | - `cd $GOPATH/src/github.com/apache` 30 | - `git clone ` 31 | - `cd $GOPATH/src/github.com/apache/` 32 | 7. Define GO111MODULE: `export GO111MODULE=on` to be able to compile locally. 33 | 34 | ### GO code guidelines 35 | 1. Run `golangci-lint run` 36 | 2. Run `go mod tidy` 37 | 3. Run `go fmt ./...` 38 | 4. Run `go vet ./...` 39 | 40 | ### Compilation 41 | To compile, type: `go build -o ingress-ats main/main.go` 42 | 43 | ### Unit Tests 44 | The project includes unit tests for the controller written in Golang and the ATS plugin written in Lua. 45 | 46 | To run the Golang unit tests: `go test ./watcher/ && go test ./redis/` 47 | 48 | The Lua unit tests use `busted` for testing. `busted` can be installed using `luarocks`:`luarocks install busted`. More information on how to install busted is available [here](https://olivinelabs.com/busted/). 49 | > :warning: **Note that the project uses Lua 5.1 version** 50 | 51 | To run the Lua unit tests: 52 | - `cd pluginats` 53 | - `busted connect_redis_test.lua` 54 | 55 | ### Text-Editor 56 | The repository comes with basic support for both [vscode](https://code.visualstudio.com/). 57 | 58 | If you're using `vscode`: 59 | - `.vscode/settings.json` contains some basic settings for whitespaces and tabs 60 | - `.vscode/extensions.json` contains a few recommended extensions for this project. 61 | - It is highly recommended to install the Go extension since it contains the code lint this project used during development. 62 | 63 | -------------------------------------------------------------------------------- /docs/TUTORIAL.md: -------------------------------------------------------------------------------- 1 | 19 | 20 | ## Tutorial 21 | - [Usage](#usage) 22 | - [ConfigMap](#configmap) 23 | - [Namespaces for Ingresses](#namespaces-for-ingresses) 24 | - [Snippet](#snippet) 25 | - [Ingress Class](#ingress-class) 26 | - [Customizing Logging and TLS](#customizing-logging-and-tls) 27 | - [Customizing plugins](#customizing-plugins) 28 | - [Enabling Controller Debug Log](#enabling-controller-debug-log) 29 | - [Resync Period of Controller](#resync-period-of-controller) 30 | - [Integrating with Fluentd and Prometheus](#integrating-with-fluentd-and-prometheus) 31 | - [Helm Chart](#helm-chart) 32 | 33 | ### Usage 34 | 35 | Check out the project's github action "Build and Integrate". It has an example of building the docker image for the ingress controller. It also demonstrates the usage of the ingress controller throught integration tests. A Kubernetes cluster is setup with applications and ingress controller for them. 36 | 37 | #### ConfigMap 38 | 39 | The above also shows example of configuring Apache Traffic Server [_reloadable_ configurations](https://docs.trafficserver.apache.org/en/9.2.x/admin-guide/files/records.config.en.html#reloadable) using [kubernetes configmap](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/) resource. 40 | 41 | #### Namespaces for Ingresses 42 | 43 | You can specifiy the list of namespaces to look for ingress object by providing an environment variable called `INGRESS_NS`. The default is `all`, which tells the controller to look for ingress objects in all namespaces. Alternatively you can provide a comma-separated list of namespaces for the controller to look for ingresses. Similarly you can specifiy a comma-separated list of namespaces to ignore while the controller is looking for ingresses by providing `INGRESS_IGNORE_NS`. 44 | 45 | #### Snippet 46 | 47 | You can attach [ATS lua script](https://docs.trafficserver.apache.org/en/9.2.x/admin-guide/plugins/lua.en.html) to an ingress object and ATS will execute it for requests matching the routing rules defined in the ingress object. This can be enabled by providing an environment variable called `SNIPPET` in the deployment. 48 | 49 | #### Ingress Class 50 | 51 | You can provide an environment variable called `INGRESS_CLASS` in the deployment to specify the ingress class. The above contains an example commented out in the deployment yaml file. Only ingress object with parameter `ingressClassName` in `spec` section with value equal to the environment variable value will be used by ATS for routing. 52 | 53 | #### Customizing Logging and TLS 54 | 55 | You can specify a different 56 | [logging.yaml](https://docs.trafficserver.apache.org/en/9.2.x/admin-guide/files/logging.yaml.en.html) and [sni.yaml](https://docs.trafficserver.apache.org/en/9.2.x/admin-guide/files/sni.yaml.en.html) by providing environment variable `LOG_CONFIG_FNAME` and `SSL_SERVERNAME_FNAME` respsectively. The new contents of them can be provided through a ConfigMap and loaded to a volume mounted for the ATS container (Example [here](https://kubernetes.io/docs/concepts/storage/volumes/#configmap) ). Similarly certificates needed for the connection between ATS and origin can be provided through a Secret that loaded to a volume mounted for the ATS container as well (Example [here](https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets-as-files-from-a-pod) ). To refresh these certificates we may need to override the entrypoint with our own command and add extra script to watch for changes in those secret in order to reload ATS (Example [here](../bin/tls-reload.sh) ). 57 | 58 | #### Customizing Plugins 59 | 60 | You can specify extra plugins for [plugin.config](https://docs.trafficserver.apache.org/en/9.2.x/admin-guide/files/plugin.config.en.html) by providing environment variable `EXTRA_PLUGIN_FNAME`. Its contents can be provided through a ConfigMap and loaded to a volume mounted for the ATS container (Example [here](https://kubernetes.io/docs/concepts/storage/volumes/#configmap) ). 61 | 62 | #### Enabling Controller Debug Log 63 | 64 | You can enable debug for the controller by providing environment variable `INGRESS_DEBUG`. 65 | 66 | #### Resync Period of Controller 67 | 68 | You can adjust the resync period for the controller by providing environment variable `RESYNC_PRIOD`. 69 | 70 | ### Integrating with Fluentd and Prometheus 71 | 72 | [Fluentd](https://docs.fluentd.org/) can be used to capture the traffic server access logs. [Prometheus](https://prometheus.io/) can be used to capture metrics. Please checkout the below projects for examples. 73 | 74 | * https://github.com/gdvalle/trafficserver_exporter 75 | * https://github.com/buraksarp/apache-traffic-server-exporter 76 | 77 | ### Helm Chart 78 | 79 | An example of Helm Chart is provided [here](../charts/ats-ingress/README.md) and is unsupported for now 80 | -------------------------------------------------------------------------------- /docs/ats-ingress-0.1.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/trafficserver-ingress-controller/ce183aa48edd668cc0d246338b80980386ab0c4a/docs/ats-ingress-0.1.0.tgz -------------------------------------------------------------------------------- /docs/images/abstract.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/trafficserver-ingress-controller/ce183aa48edd668cc0d246338b80980386ab0c4a/docs/images/abstract.png -------------------------------------------------------------------------------- /docs/images/how-it-works.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/trafficserver-ingress-controller/ce183aa48edd668cc0d246338b80980386ab0c4a/docs/images/how-it-works.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | Chart repo 17 | 18 | 19 |

Apache Traffic Server Ingress Controller Charts Repo (Unsupported)

20 |

Point Helm at this repo to see charts.

21 | 22 | 23 | -------------------------------------------------------------------------------- /docs/index.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | entries: 3 | ats-ingress: 4 | - apiVersion: v2 5 | appVersion: 0.1.0 6 | created: "2022-06-08T18:28:17.849370736Z" 7 | description: A Helm chart for Kubernetes 8 | digest: a45f33d184f769d74f5347cacba33ad18f58a9f2504b881122a509ae5a407859 9 | name: ats-ingress 10 | type: application 11 | urls: 12 | - https://apache.github.io/trafficserver-ingress-controller/ats-ingress-0.1.0.tgz 13 | version: 0.1.0 14 | generated: "2022-06-08T18:28:17.848257154Z" 15 | -------------------------------------------------------------------------------- /endpoint/endpoint.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package endpoint 17 | 18 | import ( 19 | "github.com/apache/trafficserver-ingress-controller/namespace" 20 | "github.com/apache/trafficserver-ingress-controller/proxy" 21 | "github.com/apache/trafficserver-ingress-controller/redis" 22 | ) 23 | 24 | const ( 25 | // UpdateRedis for readability 26 | UpdateRedis bool = true 27 | // UpdateATS for readability 28 | UpdateATS bool = true 29 | ) 30 | 31 | // Endpoint stores all essential information to act on HostGroups 32 | type Endpoint struct { 33 | RedisClient *redis.Client 34 | ATSManager proxy.ATSManagerInterface 35 | NsManager *namespace.NsManager 36 | } 37 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/apache/trafficserver-ingress-controller 2 | 3 | go 1.23.10 4 | 5 | require ( 6 | github.com/alicebob/miniredis/v2 v2.31.1 7 | github.com/go-redis/redis v6.15.9+incompatible 8 | k8s.io/api v0.27.9 9 | k8s.io/apimachinery v0.27.9 10 | k8s.io/client-go v0.27.9 11 | ) 12 | 13 | require ( 14 | github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/emicklei/go-restful/v3 v3.9.0 // indirect 17 | github.com/evanphx/json-patch v4.12.0+incompatible // indirect 18 | github.com/go-logr/logr v1.2.3 // indirect 19 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 20 | github.com/go-openapi/jsonreference v0.20.1 // indirect 21 | github.com/go-openapi/swag v0.22.3 // indirect 22 | github.com/gogo/protobuf v1.3.2 // indirect 23 | github.com/golang/protobuf v1.5.3 // indirect 24 | github.com/google/gnostic v0.6.9 // indirect 25 | github.com/google/go-cmp v0.5.9 // indirect 26 | github.com/google/gofuzz v1.1.0 // indirect 27 | github.com/google/uuid v1.3.0 // indirect 28 | github.com/imdario/mergo v0.3.6 // indirect 29 | github.com/josharian/intern v1.0.0 // indirect 30 | github.com/json-iterator/go v1.1.12 // indirect 31 | github.com/mailru/easyjson v0.7.7 // indirect 32 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 33 | github.com/modern-go/reflect2 v1.0.2 // indirect 34 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 35 | github.com/onsi/ginkgo v1.16.5 // indirect 36 | github.com/pkg/errors v0.9.1 // indirect 37 | github.com/spf13/pflag v1.0.5 // indirect 38 | github.com/yuin/gopher-lua v1.1.0 // indirect 39 | golang.org/x/net v0.38.0 // indirect 40 | golang.org/x/oauth2 v0.7.0 // indirect 41 | golang.org/x/sys v0.31.0 // indirect 42 | golang.org/x/term v0.30.0 // indirect 43 | golang.org/x/text v0.23.0 // indirect 44 | golang.org/x/time v0.3.0 // indirect 45 | google.golang.org/appengine v1.6.7 // indirect 46 | google.golang.org/protobuf v1.33.0 // indirect 47 | gopkg.in/inf.v0 v0.9.1 // indirect 48 | gopkg.in/yaml.v2 v2.4.0 // indirect 49 | gopkg.in/yaml.v3 v3.0.1 // indirect 50 | k8s.io/klog/v2 v2.90.1 // indirect 51 | k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect 52 | k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 // indirect 53 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 54 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 55 | sigs.k8s.io/yaml v1.3.0 // indirect 56 | ) 57 | -------------------------------------------------------------------------------- /k8s/images/node-app-1/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | 4 | -------------------------------------------------------------------------------- /k8s/images/node-app-1/Dockerfile: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | FROM node:8 18 | 19 | WORKDIR /usr/src/app 20 | 21 | COPY package*.json ./ 22 | 23 | RUN npm install 24 | 25 | COPY . . 26 | 27 | EXPOSE 8080 28 | 29 | CMD ["npm", "start"] 30 | 31 | -------------------------------------------------------------------------------- /k8s/images/node-app-1/hello-updated.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Hello from app1 - Request to path /app2 7 | 8 | 9 | 10 | 11 |

Hi

12 |

This is very minimal "hello world" HTML document.

13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /k8s/images/node-app-1/hello.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Hello from app1 7 | 8 | 9 | 10 | 11 |

Hi

12 |

This is very minimal "hello world" HTML document.

13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /k8s/images/node-app-1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dockernode", 3 | "version": "1.0.0", 4 | "description": "docker-ize a very simple express.js app", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Apache Software Foundation", 10 | "license": "Apache-2.0", 11 | "dependencies": { 12 | "express": "^4.21.2" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /k8s/images/node-app-1/server.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | 'use strict'; 14 | 15 | const express = require('express'); 16 | 17 | // Constants 18 | const PORT = 8080; 19 | const HOST = '0.0.0.0'; 20 | 21 | // App 22 | const app = express(); 23 | app.get('/', (req, res) => { 24 | res.send('Hello world\n'); 25 | }); 26 | 27 | app.get('/test', (req, res) => { // lgtm[js/missing-rate-limiting] 28 | res.sendFile('hello.html', {root: __dirname }); 29 | }) 30 | 31 | app.get('/app1', (req, res) => { // lgtm[js/missing-rate-limiting] 32 | res.sendFile('hello.html', {root: __dirname }); 33 | }) 34 | 35 | app.get('/app2', (req, res) => { // lgtm[js/missing-rate-limiting] 36 | res.sendFile('hello-updated.html', {root: __dirname }); 37 | }) 38 | 39 | 40 | app.listen(PORT, HOST); 41 | console.log(`Running on http://${HOST}:${PORT}`); 42 | -------------------------------------------------------------------------------- /k8s/images/node-app-2/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | 4 | -------------------------------------------------------------------------------- /k8s/images/node-app-2/Dockerfile: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | FROM node:8 18 | 19 | WORKDIR /usr/src/app 20 | 21 | COPY package*.json ./ 22 | 23 | RUN npm install 24 | 25 | COPY . . 26 | 27 | EXPOSE 8080 28 | 29 | CMD ["npm", "start"] 30 | 31 | -------------------------------------------------------------------------------- /k8s/images/node-app-2/hello.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | A Small Hello 7 | 8 | 9 | 10 | 11 |

Hi

12 |

This is very minimal "hello world" HTML document.

13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /k8s/images/node-app-2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dockernode", 3 | "version": "1.0.0", 4 | "description": "docker-ize a very simple express.js app", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Apache Software Foundation", 10 | "license": "Apache-2.0", 11 | "dependencies": { 12 | "express": "^4.21.2" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /k8s/images/node-app-2/server.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | 'use strict'; 14 | 15 | const express = require('express'); 16 | 17 | // Constants 18 | const PORT = 8080; 19 | const HOST = '0.0.0.0'; 20 | 21 | // App 22 | const app = express(); 23 | app.get('/', (req, res) => { 24 | res.send('Hello world\n'); 25 | }); 26 | 27 | app.get('/test', (req, res) => { // lgtm[js/missing-rate-limiting] 28 | res.sendFile('hello.html', {root: __dirname }); 29 | }) 30 | 31 | app.get('/app2', (req, res) => { // lgtm[js/missing-rate-limiting] 32 | res.sendFile('hello.html', {root: __dirname }); 33 | }) 34 | 35 | app.listen(PORT, HOST); 36 | console.log(`Running on http://${HOST}:${PORT}`); 37 | -------------------------------------------------------------------------------- /main/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package main 17 | 18 | import ( 19 | "flag" 20 | "log" 21 | "os" 22 | "os/signal" 23 | "strings" 24 | "syscall" 25 | "time" 26 | 27 | "k8s.io/client-go/kubernetes" 28 | "k8s.io/client-go/rest" 29 | 30 | "k8s.io/client-go/tools/clientcmd" 31 | 32 | _ "k8s.io/client-go/util/workqueue" 33 | 34 | _ "k8s.io/api/networking/v1" 35 | 36 | ep "github.com/apache/trafficserver-ingress-controller/endpoint" 37 | "github.com/apache/trafficserver-ingress-controller/namespace" 38 | "github.com/apache/trafficserver-ingress-controller/proxy" 39 | "github.com/apache/trafficserver-ingress-controller/redis" 40 | w "github.com/apache/trafficserver-ingress-controller/watcher" 41 | ) 42 | 43 | var ( 44 | apiServer = flag.String("apiServer", "http://notfound.com/", "The kubernetes api server. It should be set if useKubeConfig is set to false. Setting to a dummy value to prevent accidental usage.") 45 | useKubeConfig = flag.Bool("useKubeConfig", false, "Set to true to use kube config passed in kubeconfig arg.") 46 | useInClusterConfig = flag.Bool("useInClusterConfig", true, "Set to false to opt out incluster config passed in kubeconfig arg.") 47 | kubeconfig = flag.String("kubeconfig", "/usr/local/etc/k8s/k8s.config", "Absolute path to the kubeconfig file. Only works if useKubeConfig is set to true.") 48 | 49 | certFilePath = flag.String("certFilePath", "/etc/pki/tls/certs/kube-router.pem", "Absolute path to kuberouter user cert file.") 50 | keyFilePath = flag.String("keyFilePath", "/etc/pki/tls/private/kube-router-key.pem", "Absolute path to kuberouter user key file.") 51 | caFilePath = flag.String("caFilePath", "/etc/pki/tls/certs/ca.pem", "Absolute path to k8s cluster ca file.") 52 | 53 | namespaces = flag.String("namespaces", namespace.ALL, "Comma separated list of namespaces to watch for ingress and endpoints.") 54 | ignoreNamespaces = flag.String("ignoreNamespaces", "", "Comma separated list of namespaces to ignore for ingress and endpoints.") 55 | 56 | atsNamespace = flag.String("atsNamespace", "default", "Name of Namespace the ATS pod resides.") 57 | atsIngressClass = flag.String("atsIngressClass", "", "Ingress Class of Ingress object that ATS will retrieve routing info from") 58 | 59 | resyncPeriod = flag.Duration("resyncPeriod", 0*time.Second, "Resync period for the cache of informer") 60 | ) 61 | 62 | func init() { 63 | flag.Parse() 64 | } 65 | 66 | func main() { 67 | var ( 68 | config *rest.Config 69 | err error 70 | namespaceMap, ignoreNamespaceMap map[string]bool 71 | ) 72 | 73 | if *atsNamespace == "" { 74 | log.Panicln("Not all required args given.") 75 | } 76 | 77 | namespaceMap = make(map[string]bool) 78 | 79 | // default namespace to "all" 80 | if *namespaces != namespace.ALL { 81 | namespaceList := strings.Split(strings.Replace(strings.ToLower(*namespaces), " ", "", -1), ",") 82 | for _, namespace := range namespaceList { 83 | if namespace != "" { 84 | namespaceMap[namespace] = true 85 | } 86 | } 87 | } 88 | 89 | ignoreNamespaceMap = make(map[string]bool) 90 | 91 | if *ignoreNamespaces != "" { 92 | ignoreNamespaceList := strings.Split(strings.Replace(strings.ToLower(*ignoreNamespaces), " ", "", -1), ",") 93 | for _, namespace := range ignoreNamespaceList { 94 | if namespace != "" { 95 | ignoreNamespaceMap[namespace] = true 96 | } 97 | } 98 | } 99 | 100 | if *useKubeConfig { 101 | log.Println("Read config from ", *kubeconfig) 102 | /* For running outside of the cluster 103 | uses the current context in kubeconfig */ 104 | config, err = clientcmd.BuildConfigFromFlags("", *kubeconfig) 105 | if err != nil { 106 | log.Panicln(err.Error()) 107 | } 108 | /* for running inside the cluster */ 109 | } else if *useInClusterConfig { 110 | config, err = rest.InClusterConfig() 111 | if err != nil { 112 | log.Panicln("failed to create InClusterConfig: " + err.Error()) 113 | } 114 | } else { 115 | /* create config and set necessary parameters */ 116 | config = &rest.Config{} 117 | config.Host = *apiServer 118 | config.CertFile = *certFilePath 119 | config.KeyFile = *keyFilePath 120 | config.CAFile = *caFilePath 121 | } 122 | 123 | /* creates the clientset */ 124 | clientset, err := kubernetes.NewForConfig(config) 125 | if err != nil { 126 | log.Panicln(err.Error()) 127 | } 128 | 129 | stopChan := make(chan struct{}) 130 | 131 | // ------------ Resolving Namespaces -------------------------------------- 132 | nsManager := namespace.NsManager{ 133 | NamespaceMap: namespaceMap, 134 | IgnoreNamespaceMap: ignoreNamespaceMap, 135 | } 136 | 137 | nsManager.Init() 138 | 139 | //------------ Setting up Redis in memory Datastructure ------------------- 140 | rClient, err := redis.Init() 141 | if err != nil { 142 | log.Panicln("Redis Error: ", err) 143 | } 144 | 145 | // ALL services must be using CORE V1 API 146 | endpoint := ep.Endpoint{ 147 | RedisClient: rClient, 148 | ATSManager: &proxy.ATSManager{Namespace: *atsNamespace, IngressClass: *atsIngressClass}, 149 | NsManager: &nsManager, 150 | } 151 | 152 | watcher := w.Watcher{ 153 | Cs: clientset, 154 | ATSNamespace: *atsNamespace, 155 | ResyncPeriod: *resyncPeriod, 156 | Ep: &endpoint, 157 | StopChan: stopChan, 158 | } 159 | 160 | err = watcher.Watch() 161 | if err != nil { 162 | log.Panicln("Error received from watcher.Watch() :", err) 163 | } 164 | 165 | /* Program termination */ 166 | signalChan := make(chan os.Signal, 1) 167 | signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) 168 | for range signalChan { 169 | log.Println("Shutdown signal received, exiting...") 170 | close(stopChan) 171 | os.Exit(0) 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /namespace/namespace.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package namespace 17 | 18 | // ALL Namespaces constant for unified text 19 | const ALL string = "all" 20 | 21 | // NsManager is a function wrapper that is used to determine 22 | // if a namespace n should be included 23 | type NsManager struct { 24 | NamespaceMap map[string]bool 25 | IgnoreNamespaceMap map[string]bool 26 | allNamespaces bool 27 | } 28 | 29 | // IncludeNamespace is exported method to determine if a 30 | // namespace should be included 31 | func (m *NsManager) IncludeNamespace(n string) bool { 32 | if m.allNamespaces { 33 | _, prs := m.IgnoreNamespaceMap[n] 34 | return !prs 35 | } 36 | _, ns_prs := m.NamespaceMap[n] 37 | _, ins_prs := m.IgnoreNamespaceMap[n] 38 | if ns_prs && !ins_prs { 39 | return ns_prs 40 | } 41 | return false 42 | } 43 | 44 | // Init initializes NsManager's Namespaces 45 | func (m *NsManager) Init() { 46 | if len(m.NamespaceMap) == 0 { 47 | m.allNamespaces = true 48 | } else { 49 | m.allNamespaces = false 50 | } 51 | } 52 | 53 | func (m *NsManager) DisableAllNamespaces() { 54 | m.allNamespaces = false 55 | } 56 | -------------------------------------------------------------------------------- /pluginats/connect_redis.lua: -------------------------------------------------------------------------------- 1 | -- Licensed to the Apache Software Foundation (ASF) under one 2 | -- or more contributor license agreements. See the NOTICE file 3 | -- distributed with this work for additional information 4 | -- regarding copyright ownership. The ASF licenses this file 5 | -- to you under the Apache License, Version 2.0 (the 6 | -- "License"); you may not use this file except in compliance 7 | -- with the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an "AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | 17 | ts.add_package_cpath('/opt/ats/lib/lua/5.1/?.so;/opt/ats/lib/lua/5.1/socket/?.so;/opt/ats/lib/lua/5.1/mime/?.so') 18 | ts.add_package_path('/opt/ats/share/lua/5.1/?.lua;/opt/ats/share/lua/5.1/socket/?.lua') 19 | 20 | local redis = require 'redis' 21 | 22 | -- connecting to unix domain socket 23 | local client = redis.connect('unix:///opt/ats/var/run/redis/redis.sock') 24 | 25 | local snippet_enabled = false 26 | 27 | function __init__(argtb) 28 | if (#argtb) > 0 then 29 | ts.debug("Parameter is given. Snippet is enabled.") 30 | snippet_enabled = true 31 | end 32 | end 33 | 34 | -- helper function to split a string 35 | function ipport_split(s, delimiter) 36 | result = {} 37 | if (s ~= nil and s ~= '') then 38 | for match in (s..delimiter):gmatch("(.-)"..delimiter) do 39 | table.insert(result, match) 40 | end 41 | end 42 | return result 43 | end 44 | 45 | function check_path_exact_match(req_scheme, req_host, req_path) 46 | local host_path = "E+"..req_scheme .. "://" .. req_host .. req_path 47 | ts.debug('checking host_path: '..host_path) 48 | client:select(1) -- go with hostpath table first 49 | return client:smembers(host_path) -- redis blocking call 50 | end 51 | 52 | function check_path_prefix_match(req_scheme, req_host, req_path) 53 | local host_path = "P+"..req_scheme .. "://" .. req_host .. req_path 54 | ts.debug('checking host_path: '..host_path) 55 | client:select(1) 56 | local svcs = client:smembers(host_path) -- redis blocking call 57 | 58 | if (svcs ~= nil and #svcs > 0) then 59 | return svcs 60 | end 61 | 62 | -- finding location of / in request path 63 | local t = {} -- table to store the indices 64 | local i = 0 65 | while true do 66 | i = string.find(req_path, "%/", i+1) -- find 'next' dir 67 | if i == nil then break end 68 | table.insert(t, i) 69 | end 70 | 71 | for index = #t, 1, -1 do 72 | local pathindex = t[index] 73 | local subpath = string.sub (req_path, 1, pathindex) 74 | 75 | host_path = "P+"..req_scheme .. "://" .. req_host .. subpath 76 | ts.debug('checking host_path: '..host_path) 77 | client:select(1) 78 | svcs =client:smembers(host_path) -- redis blocking call 79 | if (svcs ~= nil and #svcs > 0) then 80 | return svcs 81 | end 82 | 83 | if pathindex > 1 then 84 | subpath = string.sub (req_path, 1, pathindex - 1) 85 | 86 | host_path = "P+"..req_scheme .. "://" .. req_host .. subpath 87 | ts.debug('checking host_path: '..host_path) 88 | client:select(1) 89 | svcs = client:smembers(host_path) -- redis blocking call 90 | if (svcs ~= nil and #svcs > 0) then 91 | return svcs 92 | end 93 | end 94 | end 95 | 96 | return nil 97 | end 98 | 99 | function get_wildcard_domain(req_host) 100 | local pos = string.find( req_host, '%.' ) 101 | if pos == nil then 102 | return nil 103 | end 104 | return "*" .. string.sub (req_host, pos) 105 | end 106 | 107 | --------------------------------------------- 108 | ----------------- DO_REMAP ------------------ 109 | --------------------------------------------- 110 | function do_global_read_request() 111 | ts.debug("In do_global_read_request()==========") 112 | local response = client:ping() 113 | 114 | -- if cannot connect to redis client, terminate early 115 | if not response then 116 | ts.debug("In 'not response: '", response) 117 | return 0 118 | end 119 | 120 | -- We only care about host, path, and port# 121 | local req_scheme = ts.client_request.get_url_scheme() or 'http' 122 | local req_host = ts.client_request.get_url_host() or '' 123 | local req_path = ts.client_request.get_uri() or '' 124 | local req_port = ts.client_request.get_url_port() or '' 125 | 126 | local wildcard_req_host = get_wildcard_domain(req_host) 127 | ts.debug("-----Request-----") 128 | ts.debug("req_scheme: "..req_scheme) 129 | ts.debug("req_host: " .. req_host) 130 | ts.debug("req_port: " .. req_port) 131 | ts.debug("req_path: " .. req_path) 132 | ts.debug("wildcard_req_host: " .. (wildcard_req_host or 'invalid domain name')) 133 | ts.debug("-----------------") 134 | 135 | -- check for path exact match 136 | local svcs = check_path_exact_match(req_scheme, req_host, req_path) 137 | 138 | if (svcs == nil or #svcs == 0) then 139 | -- check for path prefix match 140 | svcs = check_path_prefix_match(req_scheme, req_host, req_path) 141 | end 142 | 143 | if (svcs == nil or #svcs == 0) and wildcard_req_host ~= nil then 144 | -- check for path exact match with wildcard domain name in prefix 145 | svcs = check_path_exact_match(req_scheme, wildcard_req_host, req_path) 146 | end 147 | 148 | if (svcs == nil or #svcs == 0) and wildcard_req_host ~= nil then 149 | -- check for path prefix match with wildcard domain name in prefix 150 | svcs = check_path_prefix_match(req_scheme, wildcard_req_host, req_path) 151 | end 152 | 153 | if (svcs == nil or #svcs == 0) then 154 | -- check for path exact match with wildcard domain name 155 | svcs = check_path_exact_match(req_scheme, '*', req_path) 156 | end 157 | 158 | if (svcs == nil or #svcs == 0) then 159 | -- check for path prefix match with wildcard domain name 160 | svcs = check_path_prefix_match(req_scheme, '*', req_path) 161 | end 162 | 163 | if (svcs == nil or #svcs == 0) then 164 | ts.error("Redis Lookup Failure: svcs == nil for hostpath") 165 | return 0 166 | end 167 | 168 | for _, svc in ipairs(svcs) do 169 | if svc == nil then 170 | ts.error("Redis Lookup Failure: svc == nil for hostpath") 171 | return 0 172 | end 173 | if string.sub(svc, 1, 1) ~= "$" then 174 | ts.debug("routing") 175 | client:select(0) -- go with svc table second 176 | local ipport = client:srandmember(svc) -- redis blocking call 177 | -- svc not in redis DB 178 | if ipport == nil then 179 | ts.error("Redis Lookup Failure: ipport == nil for svc") 180 | return 0 181 | end 182 | 183 | -- find protocol, ip , port info 184 | local values = ipport_split(ipport, '#'); 185 | if #values ~= 3 then 186 | ts.error("Redis Lookup Failure: wrong format - "..ipport) 187 | return 0 188 | end 189 | 190 | ts.http.skip_remapping_set(1) 191 | ts.client_request.set_url_scheme(values[3]) 192 | ts.client_request.set_uri(req_path) 193 | ts.client_request.set_url_host(values[1]) 194 | ts.client_request.set_url_port(values[2]) 195 | end 196 | end 197 | 198 | if snippet_enabled == true then 199 | for _, svc in ipairs(svcs) do 200 | if svc == nil then 201 | ts.error("Redis Lookup Failure: svc == nil for hostpath") 202 | return 0 203 | end 204 | if string.sub(svc, 1, 1) == "$" then 205 | ts.debug("snippet") 206 | client:select(1) 207 | local snippets = client:smembers(svc) 208 | 209 | if snippets == nil then 210 | ts.error("Redis Lookup Failure: snippets == nil for hostpath") 211 | return 0 212 | end 213 | 214 | local snippet = snippets[1] 215 | if snippet == nil then 216 | ts.error("Redis Lookup Failure: snippet == nil for hostpath") 217 | return 0 218 | end 219 | 220 | local f = loadstring(snippet) 221 | f() 222 | end 223 | end 224 | end 225 | end 226 | 227 | -------------------------------------------------------------------------------- /pluginats/connect_redis_test.lua: -------------------------------------------------------------------------------- 1 | -- Licensed to the Apache Software Foundation (ASF) under one 2 | -- or more contributor license agreements. See the NOTICE file 3 | -- distributed with this work for additional information 4 | -- regarding copyright ownership. The ASF licenses this file 5 | -- to you under the Apache License, Version 2.0 (the 6 | -- "License"); you may not use this file except in compliance 7 | -- with the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an "AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | 17 | _G.ts = { client_request = {}, http = {} } 18 | _G.client = {dbone = {}, dbdefault = {}, selecteddb = 0} 19 | _G.TS_LUA_REMAP_DID_REMAP = 1 20 | 21 | function ts.client_request.get_url_scheme() 22 | return 'http' 23 | end 24 | 25 | function ts.client_request.get_url_host() 26 | return 'test.edge.com' 27 | end 28 | 29 | function ts.client_request.get_uri() 30 | return '/app1' 31 | end 32 | function ts.client_request.get_url_port() 33 | return '80' 34 | end 35 | 36 | function connect(path) 37 | return client 38 | end 39 | 40 | function client.select(self, number) 41 | if number == 1 then 42 | self.selecteddb = 1 43 | elseif number == 0 then 44 | self.selecteddb = 0 45 | end 46 | end 47 | 48 | function client.sadd(self, key, ...) 49 | db = nil 50 | if self.selecteddb == 1 then 51 | db = self.dbone 52 | elseif self.selecteddb == 0 then 53 | db = self.dbdefault 54 | end 55 | 56 | if type(db[key]) ~= "table" then 57 | db[key] = {} 58 | end 59 | 60 | for i=1,select('#',...) do 61 | local tmp = select(i,...) 62 | table.insert(db[key],tmp) 63 | end 64 | end 65 | 66 | function client.ping() 67 | return "PONG" 68 | end 69 | 70 | function client.smembers(self, key) 71 | db = nil 72 | if self.selecteddb == 1 then 73 | db = self.dbone 74 | elseif self.selecteddb == 0 then 75 | db = self.dbdefault 76 | end 77 | 78 | return db[key] 79 | end 80 | 81 | function client.srandmember(self, key) 82 | idx = math.random(1,2) 83 | db = nil 84 | if self.selecteddb == 1 then 85 | db = self.dbone 86 | elseif self.selecteddb == 0 then 87 | db = self.dbdefault 88 | end 89 | 90 | return db[key][idx] 91 | end 92 | 93 | 94 | describe("Unit tests - Lua", function() 95 | describe("Ingress Controller", function() 96 | 97 | setup(function() 98 | local match = require("luassert.match") 99 | 100 | package.loaded.redis = nil 101 | local redis = {} 102 | redis.connect = {} 103 | redis.connect = connect 104 | package.preload['redis'] = function () 105 | return redis 106 | end 107 | 108 | client = redis.connect() 109 | 110 | client:select(1) 111 | client:sadd("E+http://test.edge.com/app1","trafficserver-test-2:appsvc1:8080") 112 | client:select(0) 113 | client:sadd("trafficserver-test-2:appsvc1:8080","172.17.0.3#8080#http","172.17.0.5#8080#http") 114 | --require 'pl.pretty'.dump(client) 115 | 116 | stub(ts, "add_package_cpath") 117 | stub(ts, "add_package_path") 118 | stub(ts, "debug") 119 | stub(ts, "error") 120 | stub(ts.client_request, "set_url_host") 121 | stub(ts.client_request, "set_url_port") 122 | stub(ts.client_request, "set_url_scheme") 123 | stub(ts.client_request, "set_uri") 124 | stub(ts.http, "skip_remapping_set") 125 | stub(ts.http, "set_resp") 126 | end) 127 | 128 | it("Test - Redirect to correct IP", function() 129 | require("connect_redis") 130 | local result = do_global_read_request() 131 | 132 | assert.stub(ts.client_request.set_url_host).was.called_with(match.is_any_of(match.is_same("172.17.0.3"),match.is_same("172.17.0.5"))) 133 | assert.stub(ts.client_request.set_url_port).was.called_with("8080") 134 | assert.stub(ts.client_request.set_url_scheme).was.called_with("http") 135 | end) 136 | 137 | it("Test - Snippet", function() 138 | client:select(1) 139 | client:sadd("E+http://test.edge.com/app1","$trafficserver-test-3/app-ingress/411990") 140 | snippet = "ts.debug('Debug msg example')\nts.error('Error msg example')\n-- ts.hook(TS_LUA_HOOK_SEND_RESPONSE_HDR, function()\n-- ts.client_response.header['Location'] = 'https://test.edge.com/app2'\n-- end)\nts.http.skip_remapping_set(0)\nts.http.set_resp(301, 'Redirect')\nts.debug('Uncomment the above lines to redirect http request to https')\nts.debug('Modification for testing')\n" 141 | client:sadd("$trafficserver-test-3/app-ingress/411990",snippet) 142 | 143 | --require 'pl.pretty'.dump(client) 144 | require "connect_redis" 145 | local input_args = {"snippet"} 146 | local result = __init__(input_args) 147 | result = do_global_read_request() 148 | 149 | assert.stub(ts.error).was.called_with("Error msg example") 150 | assert.stub(ts.http.skip_remapping_set).was.called_with(0) 151 | assert.stub(ts.http.set_resp).was.called_with(301,"Redirect") 152 | end) 153 | 154 | end) 155 | end) 156 | -------------------------------------------------------------------------------- /proxy/ats.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package proxy 17 | 18 | import ( 19 | "fmt" 20 | "os/exec" 21 | "strings" 22 | ) 23 | 24 | // ATSManager talks to ATS 25 | // In the future, this is the struct that should manage 26 | // everything related to ATS 27 | type ATSManagerInterface interface { 28 | ConfigSet(k, v string) (string, error) 29 | ConfigGet(k string) (string, error) 30 | IncludeIngressClass(c string) bool 31 | } 32 | 33 | type ATSManager struct { 34 | Namespace string 35 | IngressClass string 36 | } 37 | 38 | func (m *ATSManager) IncludeIngressClass(c string) bool { 39 | if m.IngressClass == "" { 40 | return true 41 | } 42 | 43 | if m.IngressClass == c { 44 | return true 45 | } 46 | 47 | return false 48 | } 49 | 50 | // ConfigSet configures reloadable ATS config. When there is no error, 51 | // a message string is returned 52 | func (m *ATSManager) ConfigSet(k, v string) (msg string, err error) { 53 | cmd := exec.Command("traffic_ctl", "config", "set", k, v) 54 | stdoutStderr, err := cmd.CombinedOutput() 55 | if err != nil { 56 | return "", fmt.Errorf("failed to execute: traffic_ctl config set %s %s Error: %s", k, v, err.Error()) 57 | } 58 | return fmt.Sprintf("Ran p.Key: %s p.Val: %s --> stdoutStderr: %q", k, v, stdoutStderr), nil 59 | } 60 | 61 | func (m *ATSManager) ConfigGet(k string) (msg string, err error) { 62 | cmd := exec.Command("traffic_ctl", "config", "get", k) 63 | stdoutStderr, err := cmd.CombinedOutput() 64 | if err != nil { 65 | return "", fmt.Errorf("failed to execute: traffic_ctl config get %s Error: %s", k, err.Error()) 66 | } 67 | stdoutString := fmt.Sprintf("%q", stdoutStderr) 68 | configValue := strings.Split(strings.Trim(strings.Trim(stdoutString, "\""), "\\n"), ": ")[1] 69 | return configValue, err 70 | } 71 | -------------------------------------------------------------------------------- /proxy/fakeATS.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package proxy 17 | 18 | import ( 19 | "errors" 20 | "fmt" 21 | ) 22 | 23 | type FakeATSManager struct { 24 | Namespace string 25 | IngressClass string 26 | Config map[string]string 27 | } 28 | 29 | func (m *FakeATSManager) IncludeIngressClass(c string) bool { 30 | if m.IngressClass == "" { 31 | return true 32 | } 33 | 34 | if m.IngressClass == c { 35 | return true 36 | } 37 | 38 | return false 39 | } 40 | 41 | func (m *FakeATSManager) ConfigSet(k, v string) (msg string, err error) { 42 | m.Config[k] = v 43 | return fmt.Sprintf("Ran p.Key: %s p.Val: %s", k, v), nil 44 | } 45 | 46 | func (m *FakeATSManager) ConfigGet(k string) (msg string, err error) { 47 | if val, ok := m.Config[k]; ok { 48 | return val, nil 49 | } 50 | return "", errors.New("key does not exist") 51 | } 52 | -------------------------------------------------------------------------------- /redis/redis.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package redis 17 | 18 | import ( 19 | "fmt" 20 | "log" 21 | 22 | "github.com/alicebob/miniredis/v2" 23 | "github.com/go-redis/redis" 24 | ) 25 | 26 | // Client stores essential client info 27 | type Client struct { 28 | DefaultDB *redis.Client 29 | DBOne *redis.Client 30 | } 31 | 32 | const ( 33 | redisSocketAddr string = "/opt/ats/var/run/redis/redis.sock" 34 | // RSUCCESS is the success code returned by a Redis op 35 | RSUCCESS int64 = 1 36 | // RFAIL is the failure code returned by a Redis op 37 | RFAIL int64 = 0 38 | ) 39 | 40 | // TODO: Currently we have host/path --> ip --> port 41 | // if ports are the same for host/path, then it might make more sense, 42 | // in short terms, to have host/path --> ip, host/path --> port instead 43 | 44 | // Init initializes the redis clients 45 | func Init() (*Client, error) { 46 | rClient, err := CreateRedisClient() // connecting to redis 47 | if err != nil { 48 | return nil, fmt.Errorf("Failed connecting to Redis: %s", err.Error()) 49 | } 50 | err = rClient.Flush() // when the program starts, flush all stale memory 51 | if err != nil { 52 | return nil, fmt.Errorf("Failed to FlushAll: %s", err.Error()) 53 | } 54 | return rClient, nil 55 | } 56 | 57 | func InitForTesting() (*Client, error) { 58 | mr, err := miniredis.Run() 59 | 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | defaultDB := redis.NewClient(&redis.Options{ 65 | Addr: mr.Addr(), // connect to domain socket 66 | DB: 0, // use default DB 67 | }) 68 | 69 | _, err = defaultDB.Ping().Result() 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | dbOne := redis.NewClient(&redis.Options{ 75 | Addr: mr.Addr(), // connect to domain socket 76 | DB: 1, // use DB number 1 77 | }) 78 | 79 | _, err = dbOne.Ping().Result() 80 | if err != nil { 81 | return nil, err 82 | } 83 | 84 | return &Client{defaultDB, dbOne}, nil 85 | 86 | } 87 | 88 | // CreateRedisClient establishes connection to redis DB 89 | func CreateRedisClient() (*Client, error) { 90 | defaultDB := redis.NewClient(&redis.Options{ 91 | Network: "unix", // use default Addr 92 | Addr: redisSocketAddr, // connect to domain socket 93 | Password: "", // no password set 94 | DB: 0, // use default DB 95 | }) 96 | 97 | _, err := defaultDB.Ping().Result() 98 | if err != nil { 99 | return nil, err 100 | } 101 | 102 | dbOne := redis.NewClient(&redis.Options{ 103 | Network: "unix", // use default Addr 104 | Addr: redisSocketAddr, // connect to domain socket 105 | Password: "", // no password set 106 | DB: 1, // use DB number 1 107 | }) 108 | 109 | _, err = dbOne.Ping().Result() 110 | if err != nil { 111 | return nil, err 112 | } 113 | 114 | return &Client{defaultDB, dbOne}, nil 115 | } 116 | 117 | //--------------------- Default DB: svc port --> []IPport ------------------------ 118 | 119 | // DefaulDBSAdd does SAdd on Default DB and logs results 120 | func (c *Client) DefaultDBSAdd(svcport, ipport string) { 121 | _, err := c.DefaultDB.SAdd(svcport, ipport).Result() 122 | if err != nil { 123 | log.Printf("DefaultDB.SAdd(%s, %s).Result() Error: %s\n", svcport, ipport, err.Error()) 124 | } 125 | } 126 | 127 | // DefaultDBDel does Del on Default DB and logs results 128 | func (c *Client) DefaultDBDel(svcport string) { 129 | // then delete host from Default DB 130 | _, err := c.DefaultDB.Del(svcport).Result() 131 | if err != nil { 132 | log.Printf("DefaultDB.Del(%s).Result() Error: %s\n", svcport, err.Error()) 133 | } 134 | } 135 | 136 | // DefaultDBSUnionStore does sunionstore on default db 137 | func (c *Client) DefaultDBSUnionStore(dest, src string) { 138 | _, err := c.DefaultDB.SUnionStore(dest, src).Result() 139 | if err != nil { 140 | log.Printf("DefaultDB.SUnionStore(%s, %s).Result() Error: %s\n", dest, src, err.Error()) 141 | } 142 | } 143 | 144 | //----------------------- DB One: hostport --> []svc port ------------------------------- 145 | 146 | // DBOneSAdd does SAdd on DB One and logs results 147 | func (c *Client) DBOneSAdd(hostport, svcport string) { 148 | _, err := c.DBOne.SAdd(hostport, svcport).Result() 149 | 150 | if err != nil { 151 | log.Printf("DBOne.SAdd(%s, %s).Result() Error: %s\n", hostport, svcport, err.Error()) 152 | } 153 | } 154 | 155 | // DBOneSRem does SRem on DB One and logs results 156 | func (c *Client) DBOneSRem(hostport, svcport string) { 157 | _, err := c.DBOne.SRem(hostport, svcport).Result() 158 | if err != nil { 159 | log.Printf("DBOne.SRem(%s, %s).Result() Error: %s\n", hostport, svcport, err.Error()) 160 | } 161 | } 162 | 163 | // DBOneDel does Del on Default DB and logs results 164 | func (c *Client) DBOneDel(hostport string) { 165 | // then delete host from DB One 166 | _, err := c.DBOne.Del(hostport).Result() 167 | if err != nil { 168 | log.Printf("DBOne.Del(%s).Result() Error: %s\n", hostport, err.Error()) 169 | } 170 | } 171 | 172 | // DefaultDBSUnionStore does sunionstore on default db 173 | func (c *Client) DBOneSUnionStore(dest, src string) { 174 | _, err := c.DBOne.SUnionStore(dest, src).Result() 175 | if err != nil { 176 | log.Printf("DBOne.SUnionStore(%s, %s).Result() Error: %s\n", dest, src, err.Error()) 177 | } 178 | } 179 | 180 | //------------------------- Other --------------------------------------------- 181 | 182 | // Flush flushes all of redis database 183 | func (c *Client) Flush() error { 184 | if _, err := c.DefaultDB.FlushAll().Result(); err != nil { 185 | return err 186 | } 187 | return nil 188 | } 189 | 190 | // Close tries to close the 2 clients 191 | func (c *Client) Close() { 192 | c.DefaultDB.Close() 193 | c.DBOne.Close() 194 | // for garbage collector 195 | c.DefaultDB = nil 196 | c.DBOne = nil 197 | } 198 | 199 | // Terminate tries to flush the entire redis and close clients 200 | func (c *Client) Terminate() { 201 | c.Flush() // should go first 202 | c.Close() 203 | } 204 | 205 | // PrintAllKeys prints all the keys in the redis client. For debugging purposes etc 206 | func (c *Client) PrintAllKeys() { 207 | var ( 208 | res interface{} 209 | err error 210 | ) 211 | if res, err = c.DefaultDB.Do("KEYS", "*").Result(); err != nil { 212 | log.Println("Error Printing Default DB (0): ", err) 213 | } else { 214 | log.Println("DefaultDB.Do(\"KEYS\", \"*\").Result(): ", res) 215 | } 216 | 217 | if res, err = c.DBOne.Do("KEYS", "*").Result(); err != nil { 218 | log.Println("Error Printing DB One (1): ", err) 219 | } else { 220 | log.Println("DBOne.Do(\"KEYS\", \"*\").Result(): ", res) 221 | } 222 | } 223 | 224 | func (c *Client) GetDefaultDBKeyValues() map[string][]string { 225 | var ( 226 | res interface{} 227 | err error 228 | keyValueMap map[string][]string 229 | ) 230 | 231 | if res, err = c.DefaultDB.Do("KEYS", "*").Result(); err != nil { 232 | log.Println("Error Printing DB One (1): ", err) 233 | } 234 | 235 | keyValueMap = make(map[string][]string) 236 | 237 | switch keys := res.(type) { 238 | case []interface{}: 239 | for _, key := range keys { 240 | if smembers, err := c.DefaultDB.Do("SMEMBERS", key).Result(); err != nil { 241 | log.Println("Error Printing DB One (1): ", err) 242 | } else { 243 | keyValueMap[key.(string)] = []string{} 244 | switch values := smembers.(type) { 245 | case []interface{}: 246 | for _, value := range values { 247 | keyValueMap[key.(string)] = append(keyValueMap[key.(string)], value.(string)) 248 | } 249 | default: 250 | fmt.Printf("Cannot iterate over %T\n", smembers) 251 | } 252 | } 253 | } 254 | default: 255 | fmt.Printf("Cannot iterate over %T\n", res) 256 | } 257 | 258 | return keyValueMap 259 | } 260 | 261 | func (c *Client) GetDBOneKeyValues() map[string][]string { 262 | var ( 263 | res interface{} 264 | err error 265 | keyValueMap map[string][]string 266 | ) 267 | 268 | if res, err = c.DBOne.Do("KEYS", "*").Result(); err != nil { 269 | log.Println("Error Printing DB One (1): ", err) 270 | } 271 | 272 | keyValueMap = make(map[string][]string) 273 | 274 | switch keys := res.(type) { 275 | case []interface{}: 276 | for _, key := range keys { 277 | if smembers, err := c.DBOne.Do("SMEMBERS", key).Result(); err != nil { 278 | log.Println("Error Printing DB One (1): ", err) 279 | } else { 280 | keyValueMap[key.(string)] = []string{} 281 | switch values := smembers.(type) { 282 | case []interface{}: 283 | for _, value := range values { 284 | keyValueMap[key.(string)] = append(keyValueMap[key.(string)], value.(string)) 285 | } 286 | default: 287 | fmt.Printf("Cannot iterate over %T\n", smembers) 288 | } 289 | } 290 | } 291 | default: 292 | fmt.Printf("Cannot iterate over %T\n", res) 293 | } 294 | 295 | return keyValueMap 296 | } 297 | -------------------------------------------------------------------------------- /redis/redis_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | package redis 15 | 16 | import ( 17 | "github.com/apache/trafficserver-ingress-controller/util" 18 | "testing" 19 | ) 20 | 21 | func TestInit(t *testing.T) { 22 | _, err := InitForTesting() 23 | if err != nil { 24 | t.Error(err) 25 | } 26 | } 27 | 28 | func TestFlush(t *testing.T) { 29 | rClient, _ := InitForTesting() 30 | rClient.DefaultDB.SAdd("test-key", "test-val") 31 | rClient.DefaultDB.SAdd("test-key", "test-val-2") 32 | 33 | err := rClient.Flush() 34 | if err != nil { 35 | t.Error(err) 36 | } 37 | 38 | returnedKeys := rClient.GetDefaultDBKeyValues() 39 | expectedKeys := make(map[string][]string) 40 | if !util.IsSameMap(returnedKeys, expectedKeys) { 41 | t.Errorf("returned \n%v, but expected \n%v", returnedKeys, expectedKeys) 42 | } 43 | } 44 | 45 | func TestGetDefaultDBKeyValues(t *testing.T) { 46 | rClient, _ := InitForTesting() 47 | 48 | rClient.DefaultDB.SAdd("test-key", "test-val") 49 | rClient.DefaultDB.SAdd("test-key", "test-val-2") 50 | rClient.DefaultDB.SAdd("test-key-2", "test-val") 51 | 52 | returnedKeys := rClient.GetDefaultDBKeyValues() 53 | expectedKeys := getExpectedKeysForAdd() 54 | expectedKeys["test-key"] = append([]string{"test-val-2"}, expectedKeys["test-key"]...) 55 | expectedKeys["test-key-2"] = make([]string, 1) 56 | expectedKeys["test-key-2"][0] = "test-val" 57 | 58 | if !util.IsSameMap(returnedKeys, expectedKeys) { 59 | t.Errorf("returned \n%v, but expected \n%v", returnedKeys, expectedKeys) 60 | } 61 | } 62 | 63 | func TestGetDBOneKeyValues(t *testing.T) { 64 | rClient, _ := InitForTesting() 65 | 66 | rClient.DBOne.SAdd("test-key", "test-val") 67 | rClient.DBOne.SAdd("test-key", "test-val-2") 68 | rClient.DBOne.SAdd("test-key-2", "test-val") 69 | 70 | returnedKeys := rClient.GetDBOneKeyValues() 71 | expectedKeys := getExpectedKeysForAdd() 72 | expectedKeys["test-key"] = append([]string{"test-val-2"}, expectedKeys["test-key"]...) 73 | expectedKeys["test-key-2"] = make([]string, 1) 74 | expectedKeys["test-key-2"][0] = "test-val" 75 | 76 | if !util.IsSameMap(returnedKeys, expectedKeys) { 77 | t.Errorf("returned \n%v, but expected \n%v", returnedKeys, expectedKeys) 78 | } 79 | } 80 | 81 | func TestDefaultDBSAdd(t *testing.T) { 82 | rClient, _ := InitForTesting() 83 | 84 | rClient.DefaultDBSAdd("test-key", "test-val") 85 | returnedKeys := rClient.GetDefaultDBKeyValues() 86 | expectedKeys := getExpectedKeysForAdd() 87 | 88 | if !util.IsSameMap(returnedKeys, expectedKeys) { 89 | t.Errorf("returned \n%v, but expected \n%v", returnedKeys, expectedKeys) 90 | } 91 | } 92 | 93 | func TestDefaultDBDel(t *testing.T) { 94 | rClient, _ := InitForTesting() 95 | 96 | rClient.DefaultDBSAdd("test-key", "test-val") 97 | rClient.DefaultDBSAdd("test-key-2", "test-val-2") 98 | rClient.DefaultDBDel("test-key") 99 | 100 | returnedKeys := rClient.GetDefaultDBKeyValues() 101 | expectedKeys := getExpectedKeysForAdd() 102 | delete(expectedKeys, "test-key") 103 | expectedKeys["test-key-2"] = make([]string, 1) 104 | expectedKeys["test-key-2"][0] = "test-val-2" 105 | 106 | if !util.IsSameMap(returnedKeys, expectedKeys) { 107 | t.Errorf("returned \n%v, but expected \n%v", returnedKeys, expectedKeys) 108 | } 109 | } 110 | 111 | func TestDefaultDBSUnionStore(t *testing.T) { 112 | rClient, _ := InitForTesting() 113 | 114 | rClient.DefaultDBSAdd("test-key", "test-val") 115 | rClient.DefaultDBSAdd("test-key-2", "test-val-2") 116 | rClient.DefaultDBSUnionStore("test-key", "test-key-2") 117 | 118 | returnedKeys := rClient.GetDefaultDBKeyValues() 119 | expectedKeys := getExpectedKeysForAdd() 120 | expectedKeys["test-key"][0] = "test-val-2" 121 | expectedKeys["test-key-2"] = make([]string, 1) 122 | expectedKeys["test-key-2"][0] = "test-val-2" 123 | 124 | if !util.IsSameMap(returnedKeys, expectedKeys) { 125 | t.Errorf("returned \n%v, but expected \n%v", returnedKeys, expectedKeys) 126 | } 127 | } 128 | 129 | func TestDBOneSAdd(t *testing.T) { 130 | rClient, _ := InitForTesting() 131 | 132 | rClient.DBOneSAdd("test-key", "test-val") 133 | returnedKeys := rClient.GetDBOneKeyValues() 134 | expectedKeys := getExpectedKeysForAdd() 135 | 136 | if !util.IsSameMap(returnedKeys, expectedKeys) { 137 | t.Errorf("returned \n%v, but expected \n%v", returnedKeys, expectedKeys) 138 | } 139 | } 140 | 141 | func TestDBOneSRem(t *testing.T) { 142 | rClient, _ := InitForTesting() 143 | 144 | rClient.DBOneSAdd("test-key", "test-val") 145 | rClient.DBOneSAdd("test-key", "test-val-2") 146 | rClient.DBOneSAdd("test-key", "test-val-3") 147 | rClient.DBOneSRem("test-key", "test-val-2") 148 | returnedKeys := rClient.GetDBOneKeyValues() 149 | expectedKeys := getExpectedKeysForAdd() 150 | expectedKeys["test-key"] = append([]string{"test-val-3"}, expectedKeys["test-key"]...) 151 | 152 | if !util.IsSameMap(returnedKeys, expectedKeys) { 153 | t.Errorf("returned \n%v, but expected \n%v", returnedKeys, expectedKeys) 154 | } 155 | } 156 | 157 | func TestDBOneDel(t *testing.T) { 158 | rClient, _ := InitForTesting() 159 | 160 | rClient.DBOneSAdd("test-key", "test-val") 161 | rClient.DBOneSAdd("test-key-2", "test-val-2") 162 | rClient.DBOneDel("test-key") 163 | 164 | returnedKeys := rClient.GetDBOneKeyValues() 165 | expectedKeys := getExpectedKeysForAdd() 166 | delete(expectedKeys, "test-key") 167 | expectedKeys["test-key-2"] = make([]string, 1) 168 | expectedKeys["test-key-2"][0] = "test-val-2" 169 | 170 | if !util.IsSameMap(returnedKeys, expectedKeys) { 171 | t.Errorf("returned \n%v, but expected \n%v", returnedKeys, expectedKeys) 172 | } 173 | } 174 | 175 | func TestDBOneSUnionStore(t *testing.T) { 176 | rClient, _ := InitForTesting() 177 | 178 | rClient.DBOneSAdd("test-key", "test-val") 179 | rClient.DBOneSAdd("test-key-2", "test-val-2") 180 | rClient.DBOneSUnionStore("test-key", "test-key-2") 181 | 182 | returnedKeys := rClient.GetDBOneKeyValues() 183 | expectedKeys := getExpectedKeysForAdd() 184 | expectedKeys["test-key"][0] = "test-val-2" 185 | expectedKeys["test-key-2"] = make([]string, 1) 186 | expectedKeys["test-key-2"][0] = "test-val-2" 187 | 188 | if !util.IsSameMap(returnedKeys, expectedKeys) { 189 | t.Errorf("returned \n%v, but expected \n%v", returnedKeys, expectedKeys) 190 | } 191 | } 192 | 193 | func getExpectedKeysForAdd() map[string][]string { 194 | expectedKeys := make(map[string][]string) 195 | expectedKeys["test-key"] = make([]string, 1) 196 | expectedKeys["test-key"][0] = "test-val" 197 | return expectedKeys 198 | } 199 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import pytest 18 | 19 | def pytest_addoption(parser): 20 | parser.addoption("--minikubeip", action="store") 21 | 22 | 23 | def pytest_generate_tests(metafunc): 24 | # This is called for every test. Only get/set command line arguments 25 | # if the argument is specified in the list of test "fixturenames". 26 | option_value = metafunc.config.option.minikubeip 27 | if 'minikubeip' in metafunc.fixturenames and option_value is not None: 28 | metafunc.parametrize("minikubeip", [option_value]) -------------------------------------------------------------------------------- /tests/data/ats-ingress-add.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | apiVersion: networking.k8s.io/v1 17 | kind: Ingress 18 | metadata: 19 | name: app-ingress 20 | namespace: trafficserver-test-2 21 | spec: 22 | rules: 23 | - host: test.media.com 24 | http: 25 | paths: 26 | - path: /app1 27 | pathType: Exact 28 | backend: 29 | service: 30 | name: appsvc1 31 | port: 32 | number: 8080 33 | - path: /test 34 | pathType: Exact 35 | backend: 36 | service: 37 | name: appsvc1 38 | port: 39 | number: 8080 40 | 41 | - host: test.edge.com 42 | http: 43 | paths: 44 | - path: /app1 45 | pathType: Exact 46 | backend: 47 | service: 48 | name: appsvc1 49 | port: 50 | number: 8080 51 | 52 | -------------------------------------------------------------------------------- /tests/data/ats-ingress-delete.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | apiVersion: networking.k8s.io/v1 17 | kind: Ingress 18 | metadata: 19 | name: app-ingress 20 | namespace: trafficserver-test-2 21 | spec: 22 | rules: 23 | - host: test.media.com 24 | http: 25 | paths: 26 | - path: /app1 27 | pathType: Exact 28 | backend: 29 | service: 30 | name: appsvc1 31 | port: 32 | number: 8080 33 | 34 | - host: test.edge.com 35 | http: 36 | paths: 37 | - path: /app1 38 | pathType: Exact 39 | backend: 40 | service: 41 | name: appsvc1 42 | port: 43 | number: 8080 44 | 45 | -------------------------------------------------------------------------------- /tests/data/ats-ingress-snippet.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | apiVersion: networking.k8s.io/v1 17 | kind: Ingress 18 | metadata: 19 | name: app-ingress 20 | namespace: trafficserver-test-3 21 | annotations: 22 | ats.ingress.kubernetes.io/server-snippet: | 23 | ts.hook(TS_LUA_HOOK_SEND_RESPONSE_HDR, function() 24 | ts.client_response.header['Location'] = 'https://test.edge.com/app2' 25 | end) 26 | ts.http.skip_remapping_set(0) 27 | ts.http.set_resp(301, 'Redirect') 28 | spec: 29 | rules: 30 | - host: test.edge.com 31 | http: 32 | paths: 33 | - path: /app2 34 | pathType: Exact 35 | backend: 36 | service: 37 | name: appsvc2 38 | port: 39 | number: 8080 40 | -------------------------------------------------------------------------------- /tests/data/ats-ingress-update.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | apiVersion: networking.k8s.io/v1 17 | kind: Ingress 18 | metadata: 19 | name: app-ingress 20 | namespace: trafficserver-test-2 21 | spec: 22 | rules: 23 | - host: test.media.com 24 | http: 25 | paths: 26 | - path: /app1 27 | pathType: Exact 28 | backend: 29 | service: 30 | name: appsvc1 31 | port: 32 | number: 8080 33 | - path: /app2 34 | pathType: Exact 35 | backend: 36 | service: 37 | name: appsvc1 38 | port: 39 | number: 8080 40 | 41 | - host: test.edge.com 42 | http: 43 | paths: 44 | - path: /app1 45 | pathType: Exact 46 | backend: 47 | service: 48 | name: appsvc1 49 | port: 50 | number: 8080 51 | 52 | -------------------------------------------------------------------------------- /tests/data/setup/apps/app-deployment.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | apiVersion: v1 18 | kind: Namespace 19 | metadata: 20 | name: trafficserver-test-2 21 | 22 | --- 23 | 24 | apiVersion: v1 25 | kind: Namespace 26 | metadata: 27 | name: trafficserver-test-3 28 | 29 | --- 30 | 31 | apiVersion: apps/v1 32 | kind: Deployment 33 | metadata: 34 | name: app1 35 | namespace: trafficserver-test-2 36 | spec: 37 | replicas: 2 38 | selector: 39 | matchLabels: 40 | app: app1 41 | template: 42 | metadata: 43 | labels: 44 | app: app1 45 | spec: 46 | containers: 47 | - name: app1 48 | image: node-app-1:latest 49 | imagePullPolicy: Never 50 | env: 51 | - name: AUTHOR 52 | value: app1 53 | ports: 54 | - containerPort: 8080 55 | name: http 56 | protocol: TCP 57 | --- 58 | apiVersion: apps/v1 59 | kind: Deployment 60 | metadata: 61 | name: app2 62 | namespace: trafficserver-test-2 63 | spec: 64 | replicas: 2 65 | selector: 66 | matchLabels: 67 | app: app2 68 | template: 69 | metadata: 70 | labels: 71 | app: app2 72 | spec: 73 | containers: 74 | - name: app2 75 | image: node-app-2:latest 76 | imagePullPolicy: Never 77 | env: 78 | - name: AUTHOR 79 | value: app2 80 | ports: 81 | - containerPort: 8080 82 | name: http 83 | protocol: TCP 84 | --- 85 | 86 | apiVersion: apps/v1 87 | kind: Deployment 88 | metadata: 89 | name: app1 90 | namespace: trafficserver-test-3 91 | spec: 92 | replicas: 2 93 | selector: 94 | matchLabels: 95 | app: app1 96 | template: 97 | metadata: 98 | labels: 99 | app: app1 100 | spec: 101 | containers: 102 | - name: app1 103 | image: node-app-1:latest 104 | imagePullPolicy: Never 105 | env: 106 | - name: AUTHOR 107 | value: app1 108 | ports: 109 | - containerPort: 8080 110 | name: http 111 | protocol: TCP 112 | --- 113 | apiVersion: apps/v1 114 | kind: Deployment 115 | metadata: 116 | name: app2 117 | namespace: trafficserver-test-3 118 | spec: 119 | replicas: 2 120 | selector: 121 | matchLabels: 122 | app: app2 123 | template: 124 | metadata: 125 | labels: 126 | app: app2 127 | spec: 128 | containers: 129 | - name: app2 130 | image: node-app-2:latest 131 | imagePullPolicy: Never 132 | env: 133 | - name: AUTHOR 134 | value: app2 135 | ports: 136 | - containerPort: 8080 137 | name: http 138 | protocol: TCP 139 | -------------------------------------------------------------------------------- /tests/data/setup/apps/app-service.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | apiVersion: v1 18 | kind: Namespace 19 | metadata: 20 | name: trafficserver-test-2 21 | 22 | --- 23 | 24 | apiVersion: v1 25 | kind: Namespace 26 | metadata: 27 | name: trafficserver-test-3 28 | 29 | --- 30 | 31 | apiVersion: v1 32 | kind: Service 33 | metadata: 34 | name: appsvc1 35 | namespace: trafficserver-test-2 36 | spec: 37 | ports: 38 | - port: 8080 39 | name: "appsvc1http" 40 | protocol: TCP 41 | targetPort: 8080 42 | selector: 43 | app: app1 44 | --- 45 | apiVersion: v1 46 | kind: Service 47 | metadata: 48 | name: appsvc2 49 | namespace: trafficserver-test-2 50 | spec: 51 | ports: 52 | - port: 8080 53 | name: "appsvc2http" 54 | protocol: TCP 55 | targetPort: 8080 56 | 57 | selector: 58 | app: app2 59 | 60 | --- 61 | 62 | apiVersion: v1 63 | kind: Service 64 | metadata: 65 | name: appsvc1 66 | namespace: trafficserver-test-3 67 | spec: 68 | ports: 69 | - port: 8080 70 | name: "appsvc1http" 71 | protocol: TCP 72 | targetPort: 8080 73 | selector: 74 | app: app1 75 | --- 76 | apiVersion: v1 77 | kind: Service 78 | metadata: 79 | name: appsvc2 80 | namespace: trafficserver-test-3 81 | spec: 82 | ports: 83 | - port: 8080 84 | name: "appsvc2http" 85 | protocol: TCP 86 | targetPort: 8080 87 | 88 | selector: 89 | app: app2 90 | 91 | 92 | -------------------------------------------------------------------------------- /tests/data/setup/apps/apps-rbac.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | apiVersion: v1 19 | kind: Namespace 20 | metadata: 21 | name: trafficserver-test-2 22 | 23 | --- 24 | 25 | apiVersion: rbac.authorization.k8s.io/v1 26 | kind: ClusterRole 27 | metadata: 28 | name: trafficserver-test-2 29 | rules: 30 | - apiGroups: 31 | - '*' 32 | resources: 33 | - ingresses 34 | - secrets 35 | - services 36 | - pods 37 | - namespaces 38 | - replicationcontrollers 39 | - endpoints 40 | - configmaps 41 | verbs: 42 | - get 43 | - list 44 | - watch 45 | 46 | --- 47 | 48 | apiVersion: rbac.authorization.k8s.io/v1 49 | kind: ClusterRoleBinding 50 | metadata: 51 | name: trafficserver-test-2 52 | subjects: 53 | - kind: ServiceAccount 54 | name: default 55 | namespace: trafficserver-test-2 56 | roleRef: 57 | apiGroup: rbac.authorization.k8s.io 58 | kind: ClusterRole 59 | name: trafficserver-test-2 60 | 61 | --- 62 | 63 | apiVersion: v1 64 | kind: Namespace 65 | metadata: 66 | name: trafficserver-test-3 67 | 68 | --- 69 | 70 | apiVersion: rbac.authorization.k8s.io/v1 71 | kind: ClusterRole 72 | metadata: 73 | name: trafficserver-test-3 74 | rules: 75 | - apiGroups: 76 | - '*' 77 | resources: 78 | - ingresses 79 | - secrets 80 | - services 81 | - pods 82 | - namespaces 83 | - replicationcontrollers 84 | - endpoints 85 | - configmaps 86 | verbs: 87 | - get 88 | - list 89 | - watch 90 | 91 | --- 92 | 93 | apiVersion: rbac.authorization.k8s.io/v1 94 | kind: ClusterRoleBinding 95 | metadata: 96 | name: trafficserver-test-3 97 | subjects: 98 | - kind: ServiceAccount 99 | name: default 100 | namespace: trafficserver-test-3 101 | roleRef: 102 | apiGroup: rbac.authorization.k8s.io 103 | kind: ClusterRole 104 | name: trafficserver-test-3 105 | -------------------------------------------------------------------------------- /tests/data/setup/configmaps/ats-configmap.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | apiVersion: v1 18 | kind: Namespace 19 | metadata: 20 | name: trafficserver-test 21 | 22 | --- 23 | 24 | apiVersion: v1 25 | kind: ConfigMap 26 | metadata: 27 | namespace: trafficserver-test 28 | name: ats 29 | data: 30 | # reloadable data only 31 | proxy.config.output.logfile.rolling_enabled: "1" 32 | proxy.config.output.logfile.rolling_interval_sec: "3000" 33 | proxy.config.restart.active_client_threshold: "0" 34 | -------------------------------------------------------------------------------- /tests/data/setup/configmaps/fluentd-configmap.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | apiVersion: v1 18 | kind: ConfigMap 19 | metadata: 20 | name: fluentd-config 21 | namespace: trafficserver-test 22 | data: 23 | fluent.conf: | 24 | 25 | @type tail 26 | path /var/log/squid.log 27 | pos_file /var/log/squid.log.pos 28 | 29 | @type regexp 30 | expression ^(?[^ ]*)\s(?[^ ]*)\s(?[^ ]*)\s(?[^ ]*)\s(?[^ ]*)\s(?[^ ]*)\s(?[^ ]*)\s(?[^ ]*)\s(?[^ ]*)\s(?[^ ]*)$ 31 | 32 | tag trafficserver.access 33 | 34 | 35 | 36 | @type stdout 37 | 38 | 39 | -------------------------------------------------------------------------------- /tests/data/setup/ingresses/ats-ingress-2.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | apiVersion: v1 18 | kind: Namespace 19 | metadata: 20 | name: trafficserver-test-3 21 | 22 | --- 23 | 24 | apiVersion: networking.k8s.io/v1 25 | kind: Ingress 26 | metadata: 27 | name: app-ingress 28 | namespace: trafficserver-test-3 29 | annotations: 30 | ats.ingress.kubernetes.io/server-snippet: | 31 | ts.debug('Debug msg example') 32 | ts.error('Error msg example') 33 | -- ts.hook(TS_LUA_HOOK_SEND_RESPONSE_HDR, function() 34 | -- ts.client_response.header['Location'] = 'https://test.edge.com/app2' 35 | -- end) 36 | -- ts.http.skip_remapping_set(0) 37 | -- ts.http.set_resp(301, 'Redirect') 38 | ts.debug('Uncomment the above lines to redirect http request to https') 39 | spec: 40 | rules: 41 | - host: test.edge.com 42 | http: 43 | paths: 44 | - path: /app2 45 | pathType: Exact 46 | backend: 47 | service: 48 | name: appsvc2 49 | port: 50 | number: 8080 51 | -------------------------------------------------------------------------------- /tests/data/setup/ingresses/ats-ingress-2s.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | apiVersion: v1 18 | kind: Namespace 19 | metadata: 20 | name: trafficserver-test-3 21 | 22 | --- 23 | 24 | apiVersion: networking.k8s.io/v1 25 | kind: Ingress 26 | metadata: 27 | name: app-ingress-https 28 | namespace: trafficserver-test-3 29 | spec: 30 | tls: 31 | - hosts: 32 | - test.edge.com 33 | rules: 34 | - host: test.edge.com 35 | http: 36 | paths: 37 | - path: /app2 38 | pathType: Exact 39 | backend: 40 | service: 41 | name: appsvc2 42 | port: 43 | number: 8080 44 | -------------------------------------------------------------------------------- /tests/data/setup/ingresses/ats-ingress.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | apiVersion: v1 18 | kind: Namespace 19 | metadata: 20 | name: trafficserver-test-2 21 | 22 | --- 23 | 24 | apiVersion: networking.k8s.io/v1 25 | kind: Ingress 26 | metadata: 27 | name: app-ingress 28 | namespace: trafficserver-test-2 29 | spec: 30 | rules: 31 | - host: test.media.com 32 | http: 33 | paths: 34 | - path: /app1 35 | pathType: Exact 36 | backend: 37 | service: 38 | name: appsvc1 39 | port: 40 | number: 8080 41 | - path: /app2 42 | pathType: Exact 43 | backend: 44 | service: 45 | name: appsvc2 46 | port: 47 | number: 8080 48 | 49 | - host: test.edge.com 50 | http: 51 | paths: 52 | - path: /app1 53 | pathType: Exact 54 | backend: 55 | service: 56 | name: appsvc1 57 | port: 58 | number: 8080 59 | 60 | -------------------------------------------------------------------------------- /tests/data/setup/traffic-server/ats-deployment.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | apiVersion: v1 18 | kind: Namespace 19 | metadata: 20 | name: trafficserver-test 21 | 22 | --- 23 | 24 | apiVersion: apps/v1 25 | kind: Deployment 26 | metadata: 27 | labels: 28 | app: trafficserver-test 29 | name: trafficserver-test 30 | namespace: trafficserver-test 31 | spec: 32 | minReadySeconds: 30 33 | 34 | selector: 35 | matchLabels: 36 | app: trafficserver-test 37 | 38 | # DO NOT EXCEED ONE COPY 39 | replicas: 1 40 | # DO NOT EXCEED ONE COPY 41 | template: 42 | metadata: 43 | labels: 44 | app: trafficserver-test 45 | spec: 46 | 47 | containers: 48 | - name: trafficserver-test 49 | image: ats-ingress:latest # Needs to be updated 50 | volumeMounts: 51 | - mountPath: "/etc/ats/ssl" 52 | name: ats-ssl 53 | readOnly: true 54 | - name: varlog 55 | mountPath: /opt/ats/var/log/trafficserver 56 | imagePullPolicy: IfNotPresent 57 | env: 58 | - name: POD_NAME 59 | valueFrom: 60 | fieldRef: 61 | fieldPath: metadata.name 62 | - name: POD_NAMESPACE 63 | valueFrom: 64 | fieldRef: 65 | fieldPath: metadata.namespace 66 | # - name: INGRESS_CLASS 67 | # value: "ats" 68 | - name: SNIPPET 69 | value: "1" 70 | - name: POD_TLS_PATH 71 | value: "/etc/ats/ssl" 72 | ports: 73 | - containerPort: 8080 74 | name: http 75 | protocol: TCP 76 | - containerPort: 8443 77 | name: https 78 | protocol: TCP 79 | - name: log-collector 80 | image: fluent/fluentd:v1.14-debian-1 81 | volumeMounts: 82 | - name: varlog 83 | mountPath: "/var/log" 84 | - name: config-volume 85 | mountPath: "/fluentd/etc" 86 | # - name: trafficserver-exporter 87 | # image: ats-ingress-exporter:latest 88 | # imagePullPolicy: IfNotPresent 89 | # args: ["--endpoint=http://127.0.0.1:8080/_stats"] 90 | # ports: 91 | # - containerPort: 9122 92 | volumes: 93 | - name: ats-ssl 94 | secret: 95 | secretName: tls-secret 96 | - name: varlog 97 | emptyDir: {} 98 | - name: config-volume 99 | configMap: 100 | name: fluentd-config 101 | 102 | --- 103 | 104 | apiVersion: v1 105 | kind: Service 106 | metadata: 107 | name: trafficserver-test 108 | namespace: trafficserver-test 109 | spec: 110 | type: NodePort 111 | ports: 112 | - name: http 113 | port: 8080 114 | protocol: TCP 115 | targetPort: 8080 116 | nodePort: 30080 117 | - name: https 118 | port: 8443 119 | protocol: TCP 120 | targetPort: 8443 121 | nodePort: 30443 122 | selector: 123 | app: trafficserver-test 124 | -------------------------------------------------------------------------------- /tests/data/setup/traffic-server/ats-rbac.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | apiVersion: v1 19 | kind: Namespace 20 | metadata: 21 | name: trafficserver-test 22 | 23 | --- 24 | 25 | apiVersion: rbac.authorization.k8s.io/v1 26 | kind: ClusterRole 27 | metadata: 28 | name: trafficserver-test 29 | rules: 30 | - apiGroups: 31 | - '*' 32 | resources: 33 | - ingresses 34 | - secrets 35 | - services 36 | - pods 37 | - namespaces 38 | - replicationcontrollers 39 | - endpoints 40 | - configmaps 41 | verbs: 42 | - get 43 | - list 44 | - watch 45 | 46 | --- 47 | 48 | apiVersion: rbac.authorization.k8s.io/v1 49 | kind: ClusterRoleBinding 50 | metadata: 51 | name: trafficserver-test 52 | subjects: 53 | - kind: ServiceAccount 54 | name: default 55 | namespace: trafficserver-test 56 | roleRef: 57 | apiGroup: rbac.authorization.k8s.io 58 | kind: ClusterRole 59 | name: trafficserver-test 60 | 61 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest==5.4.3 2 | requests==2.32.0 3 | urllib3==1.26.19 4 | -------------------------------------------------------------------------------- /tests/suite/test_ingress.py: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import requests 18 | import pytest 19 | import os 20 | import time 21 | import textwrap 22 | 23 | def kubectl_apply(yaml_path): 24 | os.system('kubectl apply -f ' + yaml_path) 25 | time.sleep(3) 26 | 27 | def kubectl_create(resource): 28 | os.system('kubectl create ' + resource) 29 | time.sleep(1) 30 | 31 | def kubectl_delete(resource): 32 | os.system('kubectl delete ' + resource) 33 | 34 | def misc_command(command): 35 | os.system(command) 36 | 37 | def setup_module(module): 38 | misc_command('openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=atssvc/O=atssvc"') 39 | kubectl_create('namespace trafficserver-test') 40 | kubectl_create('secret tls tls-secret --key tls.key --cert tls.crt -n trafficserver-test --dry-run=client -o yaml | kubectl apply -f -') 41 | kubectl_apply('data/setup/configmaps/') 42 | kubectl_apply('data/setup/traffic-server/') 43 | kubectl_apply('data/setup/apps/') 44 | kubectl_apply('data/setup/ingresses/') 45 | time.sleep(90) 46 | misc_command('kubectl get all -A') 47 | misc_command('kubectl get pod -A -o wide') 48 | misc_command('kubectl logs $(kubectl get pod -n trafficserver-test-2 -o name | head -1) -n trafficserver-test-2') 49 | misc_command('kubectl exec $(kubectl get pod -n trafficserver-test-2 -o name | head -1) -n trafficserver-test-2 -- ps auxxx') 50 | misc_command('kubectl exec $(kubectl get pod -n trafficserver-test-2 -o name | head -1) -n trafficserver-test-2 -- curl -v localhost:8080/app1') 51 | misc_command('kubectl exec $(kubectl get pod -n trafficserver-test-2 -o name | head -1) -n trafficserver-test-2 -- curl -v $(kubectl get pod -n trafficserver-test-2 -o jsonpath={.items[0].status.podIP}):8080/app1') 52 | misc_command('kubectl exec $(kubectl get pod -n trafficserver-test-3 -o name | head -1) -n trafficserver-test-3 -- curl -v $(kubectl get pod -n trafficserver-test-2 -o jsonpath={.items[0].status.podIP}):8080/app1') 53 | 54 | # misc_command('kubectl logs $(kubectl get pod -n trafficserver-test-3 -o name | head -1) -n trafficserver-test-3') 55 | misc_command('kubectl exec $(kubectl get pod -n trafficserver-test -o name) -n trafficserver-test -- curl -v $(kubectl get pod -n trafficserver-test-2 -o jsonpath={.items[0].status.podIP}):8080/app1') 56 | # misc_command('kubectl exec $(kubectl get pod -n trafficserver-test -o name) -n trafficserver-test -- curl -v $(kubectl get pod -n trafficserver-test-3 -o jsonpath={.items[0].status.podIP}):8080/app1') 57 | misc_command('kubectl exec $(kubectl get pod -n trafficserver-test -o name) -n trafficserver-test -- curl -v $(kubectl get service/appsvc1 -n trafficserver-test-2 -o jsonpath={.spec.clusterIP}):8080/app1') 58 | # misc_command('kubectl exec $(kubectl get pod -n trafficserver-test -o name) -n trafficserver-test -- curl -v $(kubectl get service/appsvc2 -n trafficserver-test-2 -o jsonpath={.spec.clusterIP}):8080/app1') 59 | 60 | def teardown_module(module): 61 | kubectl_delete('namespace trafficserver-test-3') 62 | kubectl_delete('namespace trafficserver-test-2') 63 | kubectl_delete('namespace trafficserver-test') 64 | 65 | def get_expected_response_app1(): 66 | resp = """ 67 | 68 | 69 | 70 | 71 | Hello from app1 72 | 73 | 74 | 75 | 76 |

Hi

77 |

This is very minimal "hello world" HTML document.

78 | 79 | """ 80 | 81 | return ' '.join(resp.split()) 82 | 83 | def get_expected_response_app1_updated(): 84 | resp = """ 85 | 86 | 87 | 88 | 89 | Hello from app1 - Request to path /app2 90 | 91 | 92 | 93 | 94 |

Hi

95 |

This is very minimal "hello world" HTML document.

96 | 97 | """ 98 | 99 | return ' '.join(resp.split()) 100 | 101 | def get_expected_response_app2(): 102 | resp = """ 103 | 104 | 105 | 106 | 107 | A Small Hello 108 | 109 | 110 | 111 | 112 |

Hi

113 |

This is very minimal "hello world" HTML document.

114 | 115 | """ 116 | 117 | return ' '.join(resp.split()) 118 | 119 | class TestIngress: 120 | def test_basic_routing_edge_app1(self, minikubeip): 121 | req_url = "http://" + minikubeip + ":30080/app1" 122 | resp = requests.get(req_url, headers={"host": "test.edge.com"}) 123 | misc_command('kubectl exec $(kubectl get pod -n trafficserver-test -o name) -n trafficserver-test -- cat /opt/ats/var/log/trafficserver/squid.log') 124 | 125 | assert resp.status_code == 200,\ 126 | f"Expected: 200 response code for test_basic_routing" 127 | assert ' '.join(resp.text.split()) == get_expected_response_app1() 128 | 129 | def test_basic_routing_media_app1(self, minikubeip): 130 | req_url = "http://" + minikubeip + ":30080/app1" 131 | resp = requests.get(req_url, headers={"host": "test.media.com"}) 132 | misc_command('kubectl exec $(kubectl get pod -n trafficserver-test -o name) -n trafficserver-test -- cat /opt/ats/var/log/trafficserver/squid.log') 133 | 134 | assert resp.status_code == 200,\ 135 | f"Expected: 200 response code for test_basic_routing" 136 | assert ' '.join(resp.text.split()) == get_expected_response_app1() 137 | 138 | def test_basic_routing_edge_app2(self, minikubeip): 139 | req_url = "http://" + minikubeip + ":30080/app2" 140 | resp = requests.get(req_url, headers={"host": "test.edge.com"}) 141 | misc_command('kubectl exec $(kubectl get pod -n trafficserver-test -o name) -n trafficserver-test -- cat /opt/ats/var/log/trafficserver/squid.log') 142 | 143 | assert resp.status_code == 200,\ 144 | f"Expected: 200 response code for test_basic_routing" 145 | assert ' '.join(resp.text.split()) == get_expected_response_app2() 146 | 147 | def test_basic_routing_media_app2(self, minikubeip): 148 | req_url = "http://" + minikubeip + ":30080/app2" 149 | resp = requests.get(req_url, headers={"host": "test.media.com"}) 150 | misc_command('kubectl exec $(kubectl get pod -n trafficserver-test -o name) -n trafficserver-test -- cat /opt/ats/var/log/trafficserver/squid.log') 151 | 152 | assert resp.status_code == 200,\ 153 | f"Expected: 200 response code for test_basic_routing" 154 | assert ' '.join(resp.text.split()) == get_expected_response_app2() 155 | 156 | def test_basic_routing_edge_app2_https(self, minikubeip): 157 | req_url = "https://" + minikubeip + ":30443/app2" 158 | resp = requests.get(req_url, headers={"host": "test.edge.com"}, verify=False) 159 | misc_command('kubectl exec $(kubectl get pod -n trafficserver-test -o name) -n trafficserver-test -- cat /opt/ats/var/log/trafficserver/squid.log') 160 | 161 | assert resp.status_code == 200,\ 162 | f"Expected: 200 response code for test_basic_routing" 163 | assert ' '.join(resp.text.split()) == get_expected_response_app2() 164 | 165 | def test_updating_ingress_media_app2(self, minikubeip): 166 | kubectl_apply('data/ats-ingress-update.yaml') 167 | req_url = "http://" + minikubeip + ":30080/app2" 168 | resp = requests.get(req_url, headers={"host": "test.media.com"}) 169 | misc_command('kubectl exec $(kubectl get pod -n trafficserver-test -o name) -n trafficserver-test -- cat /opt/ats/var/log/trafficserver/squid.log') 170 | 171 | assert resp.status_code == 200,\ 172 | f"Expected: 200 response code for test_basic_routing" 173 | assert ' '.join(resp.text.split()) == get_expected_response_app1_updated() 174 | 175 | def test_deleting_ingress_media_app2(self, minikubeip): 176 | kubectl_apply('data/ats-ingress-delete.yaml') 177 | req_url = "http://" + minikubeip + ":30080/app2" 178 | resp = requests.get(req_url, headers={"host": "test.media.com"}) 179 | misc_command('kubectl exec $(kubectl get pod -n trafficserver-test -o name) -n trafficserver-test -- cat /opt/ats/var/log/trafficserver/squid.log') 180 | 181 | assert resp.status_code == 404,\ 182 | f"Expected: 400 response code for test_basic_routing_deleted_ingress" 183 | 184 | def test_add_ingress_media(self, minikubeip): 185 | kubectl_apply('data/ats-ingress-add.yaml') 186 | req_url = "http://" + minikubeip + ":30080/test" 187 | resp = requests.get(req_url, headers={"host": "test.media.com"}) 188 | misc_command('kubectl exec $(kubectl get pod -n trafficserver-test -o name) -n trafficserver-test -- cat /opt/ats/var/log/trafficserver/squid.log') 189 | 190 | assert resp.status_code == 200,\ 191 | f"Expected: 200 response code for test_basic_routing" 192 | assert ' '.join(resp.text.split()) == get_expected_response_app1() 193 | 194 | def test_snippet_edge_app2(self, minikubeip): 195 | kubectl_apply('data/ats-ingress-snippet.yaml') 196 | req_url = "http://" + minikubeip + ":30080/app2" 197 | resp = requests.get(req_url, headers={"host": "test.edge.com"},allow_redirects=False) 198 | misc_command('kubectl exec $(kubectl get pod -n trafficserver-test -o name) -n trafficserver-test -- cat /opt/ats/var/log/trafficserver/squid.log') 199 | 200 | assert resp.status_code == 301,\ 201 | f"Expected: 301 response code for test_snippet_edge_app2" 202 | assert resp.headers['Location'] == 'https://test.edge.com/app2' 203 | 204 | 205 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package util 17 | 18 | import ( 19 | "encoding/json" 20 | "fmt" 21 | "log" 22 | "os" 23 | "sync" 24 | 25 | nv1 "k8s.io/api/networking/v1" 26 | ) 27 | 28 | // Writer writes the JSON file synchronously 29 | type Writer struct { 30 | lock sync.Mutex 31 | DirPath string 32 | FileName string 33 | } 34 | 35 | // Perm is default permission bits of JSON file 36 | const Perm os.FileMode = 0755 37 | 38 | const ( 39 | // Define annotations we check for in the watched resources 40 | AnnotationServerSnippet = "ats.ingress.kubernetes.io/server-snippet" 41 | AnnotationIngressClass = "kubernetes.io/ingress.class" 42 | ) 43 | 44 | // SyncWriteJSONFile writes obj, intended to be HostGroup, into a JSON file 45 | // under filename. 46 | func (w *Writer) SyncWriteJSONFile(obj interface{}) error { 47 | w.lock.Lock() 48 | defer w.lock.Unlock() 49 | file, err := w.CreateFileIfNotExist() 50 | if err != nil { 51 | return err 52 | } 53 | defer file.Close() // file is opened, must close 54 | 55 | content, jsonErr := json.MarshalIndent(obj, "", "\t") 56 | if jsonErr != nil { 57 | return jsonErr 58 | } 59 | 60 | // Making sure repeated writes will actually clear the file before each write 61 | err = file.Truncate(0) 62 | if err != nil { 63 | return err 64 | } 65 | _, err = file.Seek(0, 0) 66 | if err != nil { 67 | return err 68 | } 69 | _, writeErr := file.Write(content) 70 | if writeErr != nil { 71 | return writeErr 72 | } 73 | syncErr := file.Sync() // flushing to disk 74 | if syncErr != nil { 75 | return syncErr 76 | } 77 | return nil 78 | } 79 | 80 | // CreateFileIfNotExist checks if fileName in dirPath already exists. 81 | // if not, such file will be created. If the file exists, it will be opened 82 | // and its file descriptor returned. So, Caller needs to close the file! 83 | func (w *Writer) CreateFileIfNotExist() (file *os.File, err error) { 84 | file, err = nil, nil 85 | 86 | if _, e := os.Stat(w.DirPath); e == nil { // dirPath exists 87 | // fall through 88 | } else if os.IsNotExist(e) { // dirPath does not exist 89 | err = os.MkdirAll(w.DirPath, Perm) 90 | } else { // sys error 91 | err = e 92 | } 93 | 94 | if err != nil { 95 | return 96 | } 97 | 98 | // caller is responsible for checking err first before using file anyways 99 | file, err = os.OpenFile(w.DirPath+"/"+w.FileName, os.O_CREATE|os.O_RDWR, Perm) 100 | return 101 | } 102 | 103 | // ConstructHostPathString constructs the string representation of Host + Path 104 | func ConstructHostPathString(scheme, host, path string, pathType nv1.PathType) string { 105 | if path == "" { 106 | path = "/" 107 | } 108 | // default pathType is "Exact" 109 | if pathType == nv1.PathTypePrefix { 110 | return "P+" + scheme + "://" + host + path 111 | } else { 112 | return "E+" + scheme + "://" + host + path 113 | } 114 | //return p.Clean(fmt.Sprintf("%s/%s", host, path)) 115 | } 116 | 117 | // ConstructSvcPortString constructs the string representation of namespace, svc, port 118 | func ConstructSvcPortString(namespace, svc, port string) string { 119 | return namespace + ":" + svc + ":" + port 120 | } 121 | 122 | // ConstructIPPortString constructs the string representation of ip, port 123 | func ConstructIPPortString(ip, port, protocol string) string { 124 | if protocol != "https" { 125 | protocol = "http" 126 | } 127 | return ip + "#" + port + "#" + protocol 128 | } 129 | 130 | func ConstructNameVersionString(namespace, name, version string) string { 131 | return "$" + namespace + "/" + name + "/" + version 132 | } 133 | 134 | // Itos : Interface to String 135 | func Itos(obj interface{}) string { 136 | return fmt.Sprintf("%v", obj) 137 | } 138 | 139 | func ExtractServerSnippet(ann map[string]string) (snippet string, err error) { 140 | 141 | server_snippet, ok := ann[AnnotationServerSnippet] 142 | if !ok { 143 | return "", fmt.Errorf("missing annotation '%s'", AnnotationServerSnippet) 144 | } 145 | 146 | return server_snippet, nil 147 | } 148 | 149 | func ExtractIngressClass(ann map[string]string) (class string, err error) { 150 | 151 | ingress_class, ok := ann[AnnotationIngressClass] 152 | if !ok { 153 | return "", fmt.Errorf("missing annotation '%s'", AnnotationIngressClass) 154 | } 155 | 156 | return ingress_class, nil 157 | } 158 | 159 | func ExtractIngressClassName(obj interface{}) (class string, err error) { 160 | ingressObj, ok := obj.(*nv1.Ingress) 161 | if !ok { 162 | log.Println("Extracting ingress class name; cannot cast to *nv1.Ingress") 163 | return "", fmt.Errorf("extracting ingress class name; cannot cast to *nv1.Ingress") 164 | } 165 | 166 | if ingressObj.Spec.IngressClassName == nil { 167 | return "", fmt.Errorf("extracting ingress class name; missing the field") 168 | } 169 | 170 | return *ingressObj.Spec.IngressClassName, nil 171 | } 172 | 173 | // FmtMarshalled converts json marshalled bytes to string 174 | func FmtMarshalled(marshalled []byte) string { 175 | return fmt.Sprintf("%q", marshalled) 176 | } 177 | 178 | func ReverseSlice(s []string) []string { 179 | for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { 180 | s[i], s[j] = s[j], s[i] 181 | } 182 | 183 | return s 184 | } 185 | 186 | func IsSameMap(x, y map[string][]string) bool { 187 | if len(x) != len(y) { 188 | return false 189 | } 190 | 191 | keysInX := make([]string, 0, len(x)) 192 | for k := range x { 193 | keysInX = append(keysInX, k) 194 | } 195 | 196 | keysInY := make([]string, 0, len(y)) 197 | for k := range y { 198 | keysInY = append(keysInY, k) 199 | } 200 | 201 | if !IsSameSlice(keysInX, keysInY) { 202 | return false 203 | } 204 | 205 | for k := range x { 206 | if !IsSameSlice(x[k], y[k]) { 207 | return false 208 | } 209 | } 210 | 211 | return true 212 | } 213 | 214 | func IsSameSlice(x, y []string) bool { 215 | if len(x) != len(y) { 216 | return false 217 | } 218 | // create a map of string -> int 219 | diff := make(map[string]int, len(x)) 220 | for _, _x := range x { 221 | // 0 value for int is 0, so just increment a counter for the string 222 | diff[_x]++ 223 | } 224 | for _, _y := range y { 225 | // If the string _y is not in diff bail out early 226 | if _, ok := diff[_y]; !ok { 227 | return false 228 | } 229 | diff[_y] -= 1 230 | if diff[_y] == 0 { 231 | delete(diff, _y) 232 | } 233 | } 234 | return len(diff) == 0 235 | } 236 | -------------------------------------------------------------------------------- /watcher/handlerConfigmap.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package watcher 17 | 18 | import ( 19 | "log" 20 | 21 | "github.com/apache/trafficserver-ingress-controller/endpoint" 22 | 23 | v1 "k8s.io/api/core/v1" 24 | ) 25 | 26 | // CMHandler handles Add Update Delete methods on Configmaps 27 | type CMHandler struct { 28 | ResourceName string 29 | Ep *endpoint.Endpoint 30 | } 31 | 32 | // Add for EventHandler 33 | func (c *CMHandler) Add(obj interface{}) { 34 | c.update(obj) 35 | } 36 | 37 | func (c *CMHandler) update(newObj interface{}) { 38 | cm, ok := newObj.(*v1.ConfigMap) 39 | if !ok { 40 | log.Println("In ConfigMapHandler Update; cannot cast to *v1.ConfigMap") 41 | return 42 | } 43 | 44 | annotations := cm.GetAnnotations() 45 | if val, ok := annotations["ats-configmap"]; ok { 46 | if val != "true" { 47 | return 48 | } 49 | } else { 50 | return 51 | } 52 | 53 | for currKey, currVal := range cm.Data { 54 | msg, err := c.Ep.ATSManager.ConfigSet(currKey, currVal) // update ATS 55 | if err != nil { 56 | log.Println(err) 57 | } else { 58 | log.Println(msg) 59 | } 60 | } 61 | } 62 | 63 | // Update for EventHandler 64 | func (c *CMHandler) Update(obj, newObj interface{}) { 65 | c.update(newObj) 66 | } 67 | 68 | // Delete for EventHandler 69 | func (c *CMHandler) Delete(obj interface{}) { 70 | // do not handle delete events for now 71 | } 72 | 73 | // GetResourceName returns the resource name 74 | func (c *CMHandler) GetResourceName() string { 75 | return c.ResourceName 76 | } 77 | -------------------------------------------------------------------------------- /watcher/handlerConfigmap_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | package watcher 15 | 16 | import ( 17 | ep "github.com/apache/trafficserver-ingress-controller/endpoint" 18 | "github.com/apache/trafficserver-ingress-controller/namespace" 19 | "github.com/apache/trafficserver-ingress-controller/proxy" 20 | "github.com/apache/trafficserver-ingress-controller/redis" 21 | "log" 22 | "reflect" 23 | "testing" 24 | 25 | v1 "k8s.io/api/core/v1" 26 | meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | ) 28 | 29 | func TestAdd_BasicConfigMap(t *testing.T) { 30 | cmHandler := createExampleCMHandler() 31 | exampleConfigMap := createExampleConfigMap() 32 | 33 | cmHandler.Add(&exampleConfigMap) 34 | 35 | rEnabled, err := cmHandler.Ep.ATSManager.ConfigGet("proxy.config.output.logfile.rolling_enabled") 36 | 37 | if err != nil { 38 | t.Error(err) 39 | } else if !reflect.DeepEqual(rEnabled, "1") { 40 | t.Errorf("returned \n%s, but expected \n%s", rEnabled, "1") 41 | } 42 | 43 | rInterval, err := cmHandler.Ep.ATSManager.ConfigGet("proxy.config.output.logfile.rolling_interval_sec") 44 | 45 | if err != nil { 46 | t.Error(err) 47 | } else if !reflect.DeepEqual(rInterval, "3000") { 48 | t.Errorf("returned \n%s, but expected \n%s", rInterval, "3000") 49 | } 50 | 51 | threshold, err := cmHandler.Ep.ATSManager.ConfigGet("proxy.config.restart.active_client_threshold") 52 | 53 | if err != nil { 54 | t.Error(err) 55 | } else if !reflect.DeepEqual(threshold, "0") { 56 | t.Errorf("returned \n%s, but expected \n%s", threshold, "0") 57 | } 58 | 59 | } 60 | 61 | func TestShouldNotAdd_BasicConfigMap(t *testing.T) { 62 | cmHandler := createExampleCMHandler() 63 | exampleConfigMap := createExampleConfigMap() 64 | 65 | exampleConfigMap.Annotations = map[string]string{ 66 | "ats-configmap": "false", 67 | } 68 | 69 | cmHandler.Add(&exampleConfigMap) 70 | 71 | rEnabled, err := cmHandler.Ep.ATSManager.ConfigGet("proxy.config.output.logfile.rolling_enabled") 72 | 73 | if err == nil { 74 | t.Errorf("Should not have executed. Instead gives %s", rEnabled) 75 | } 76 | } 77 | 78 | func TestUpdate_BasicConfigMap(t *testing.T) { 79 | cmHandler := createExampleCMHandler() 80 | exampleConfigMap := createExampleConfigMap() 81 | exampleConfigMap.Data["proxy.config.output.logfile.rolling_interval_sec"] = "2000" 82 | 83 | cmHandler.update(&exampleConfigMap) 84 | 85 | rEnabled, err := cmHandler.Ep.ATSManager.ConfigGet("proxy.config.output.logfile.rolling_enabled") 86 | 87 | if err != nil { 88 | t.Error(err) 89 | } else if !reflect.DeepEqual(rEnabled, "1") { 90 | t.Errorf("returned \n%s, but expected \n%s", rEnabled, "1") 91 | } 92 | 93 | rInterval, err := cmHandler.Ep.ATSManager.ConfigGet("proxy.config.output.logfile.rolling_interval_sec") 94 | 95 | if err != nil { 96 | t.Error(err) 97 | } else if !reflect.DeepEqual(rInterval, "2000") { 98 | t.Errorf("returned \n%s, but expected \n%s", rInterval, "2000") 99 | } 100 | 101 | threshold, err := cmHandler.Ep.ATSManager.ConfigGet("proxy.config.restart.active_client_threshold") 102 | 103 | if err != nil { 104 | t.Error(err) 105 | } else if !reflect.DeepEqual(threshold, "0") { 106 | t.Errorf("returned \n%s, but expected \n%s", threshold, "0") 107 | } 108 | 109 | } 110 | 111 | func createExampleConfigMap() v1.ConfigMap { 112 | exampleConfigMap := v1.ConfigMap{ 113 | ObjectMeta: meta_v1.ObjectMeta{ 114 | Name: "testsvc", 115 | Namespace: "trafficserver-test-2", 116 | Annotations: map[string]string{ 117 | "ats-configmap": "true", 118 | }, 119 | }, 120 | Data: map[string]string{ 121 | "proxy.config.output.logfile.rolling_enabled": "1", 122 | "proxy.config.output.logfile.rolling_interval_sec": "3000", 123 | "proxy.config.restart.active_client_threshold": "0", 124 | }, 125 | } 126 | 127 | return exampleConfigMap 128 | } 129 | 130 | func createExampleCMHandler() CMHandler { 131 | exampleEndpoint := createExampleEndpointWithFakeATS() 132 | cmHandler := CMHandler{"configmap", &exampleEndpoint} 133 | 134 | return cmHandler 135 | } 136 | 137 | func createExampleEndpointWithFakeATS() ep.Endpoint { 138 | rClient, err := redis.InitForTesting() 139 | if err != nil { 140 | log.Panicln("Redis Error: ", err) 141 | } 142 | 143 | namespaceMap := make(map[string]bool) 144 | ignoreNamespaceMap := make(map[string]bool) 145 | 146 | nsManager := namespace.NsManager{ 147 | NamespaceMap: namespaceMap, 148 | IgnoreNamespaceMap: ignoreNamespaceMap, 149 | } 150 | 151 | nsManager.Init() 152 | 153 | exampleEndpoint := ep.Endpoint{ 154 | RedisClient: rClient, 155 | ATSManager: &proxy.FakeATSManager{ 156 | Namespace: "default", 157 | IngressClass: "", 158 | Config: make(map[string]string), 159 | }, 160 | NsManager: &nsManager, 161 | } 162 | 163 | return exampleEndpoint 164 | } 165 | -------------------------------------------------------------------------------- /watcher/handlerEndpoint.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package watcher 17 | 18 | import ( 19 | "fmt" 20 | "log" 21 | 22 | "github.com/apache/trafficserver-ingress-controller/endpoint" 23 | "github.com/apache/trafficserver-ingress-controller/util" 24 | 25 | v1 "k8s.io/api/core/v1" 26 | ) 27 | 28 | // EpHandler implements EventHandler 29 | type EpHandler struct { 30 | ResourceName string 31 | Ep *endpoint.Endpoint 32 | } 33 | 34 | func (e *EpHandler) Add(obj interface{}) { 35 | log.Printf("Endpoint ADD %#v \n", obj) 36 | e.add(obj) 37 | e.Ep.RedisClient.PrintAllKeys() 38 | } 39 | 40 | func (e *EpHandler) add(obj interface{}) { 41 | eps, ok := obj.(*v1.Endpoints) 42 | if !ok { 43 | log.Println("In Endpoint Add; cannot cast to *v1.Endpoints.") 44 | return 45 | } 46 | podSvcName := eps.GetObjectMeta().GetName() 47 | namespace := eps.GetNamespace() 48 | 49 | if !e.Ep.NsManager.IncludeNamespace(namespace) { 50 | log.Println("Namespace not included") 51 | return 52 | } 53 | 54 | for _, subset := range eps.Subsets { 55 | for _, port := range subset.Ports { 56 | portnum := fmt.Sprint(port.Port) 57 | portname := port.Name 58 | key := util.ConstructSvcPortString(namespace, podSvcName, portnum) 59 | for _, addr := range subset.Addresses { 60 | v := util.ConstructIPPortString(addr.IP, portnum, portname) 61 | e.Ep.RedisClient.DefaultDBSAdd(key, v) 62 | } 63 | } 64 | 65 | } 66 | } 67 | 68 | // Update for EventHandler 69 | func (e *EpHandler) Update(obj, newObj interface{}) { 70 | log.Printf("Endpoint Update Obj: %#v , newObj: %#v \n", obj, newObj) 71 | e.update(newObj) 72 | e.Ep.RedisClient.PrintAllKeys() 73 | } 74 | 75 | func (e *EpHandler) update(obj interface{}) { 76 | eps, ok := obj.(*v1.Endpoints) 77 | if !ok { 78 | log.Println("In Endpoint Update; cannot cast to *v1.Endpoints.") 79 | return 80 | } 81 | podSvcName := eps.GetObjectMeta().GetName() 82 | namespace := eps.GetNamespace() 83 | 84 | if !e.Ep.NsManager.IncludeNamespace(namespace) { 85 | log.Println("Namespace not included") 86 | return 87 | } 88 | 89 | for _, subset := range eps.Subsets { 90 | for _, port := range subset.Ports { 91 | portnum := fmt.Sprint(port.Port) 92 | portname := port.Name 93 | key := util.ConstructSvcPortString(namespace, podSvcName, portnum) 94 | for _, addr := range subset.Addresses { 95 | k := "temp_" + key 96 | v := util.ConstructIPPortString(addr.IP, portnum, portname) 97 | e.Ep.RedisClient.DefaultDBSAdd(k, v) 98 | } 99 | e.Ep.RedisClient.DefaultDBSUnionStore(key, "temp_"+key) 100 | e.Ep.RedisClient.DefaultDBDel("temp_" + key) 101 | } 102 | 103 | } 104 | } 105 | 106 | // Delete for EventHandler 107 | func (e *EpHandler) Delete(obj interface{}) { 108 | log.Printf("Endpoint Delete: %#v \n", obj) 109 | e.delete(obj) 110 | e.Ep.RedisClient.PrintAllKeys() 111 | } 112 | 113 | func (e *EpHandler) delete(obj interface{}) { 114 | eps, ok := obj.(*v1.Endpoints) 115 | if !ok { 116 | log.Println("In Endpoint DELETE; cannot cast to *v1.Endpoints.") 117 | return 118 | } 119 | podSvcName := eps.GetObjectMeta().GetName() 120 | namespace := eps.GetNamespace() 121 | 122 | if !e.Ep.NsManager.IncludeNamespace(namespace) { 123 | log.Println("Namespace not included") 124 | return 125 | } 126 | 127 | for _, subset := range eps.Subsets { 128 | for _, port := range subset.Ports { 129 | portnum := fmt.Sprint(port.Port) 130 | key := util.ConstructSvcPortString(namespace, podSvcName, portnum) 131 | e.Ep.RedisClient.DefaultDBDel(key) 132 | } 133 | 134 | } 135 | 136 | } 137 | 138 | // GetResourceName returns the resource name 139 | func (e *EpHandler) GetResourceName() string { 140 | return e.ResourceName 141 | } 142 | -------------------------------------------------------------------------------- /watcher/handlerEndpoint_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | package watcher 15 | 16 | import ( 17 | "github.com/apache/trafficserver-ingress-controller/util" 18 | "testing" 19 | 20 | v1 "k8s.io/api/core/v1" 21 | meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | func TestAdd_BasicEndpoint(t *testing.T) { 25 | epHandler := createExampleEpHandler() 26 | exampleV1Endpoint := createExampleV1Endpoint() 27 | 28 | epHandler.add(&exampleV1Endpoint) 29 | 30 | returnedKeys := epHandler.Ep.RedisClient.GetDefaultDBKeyValues() 31 | 32 | expectedKeys := getExpectedKeysForEndpointAdd() 33 | 34 | if !util.IsSameMap(returnedKeys, expectedKeys) { 35 | t.Errorf("returned \n%v, but expected \n%v", returnedKeys, expectedKeys) 36 | } 37 | } 38 | 39 | func TestAdd_IgnoreEndpointNamespace(t *testing.T) { 40 | epHandler := createExampleEpHandler() 41 | exampleV1Endpoint := createExampleV1Endpoint() 42 | 43 | epHandler.Ep.NsManager.IgnoreNamespaceMap["trafficserver-test-2"] = true 44 | 45 | epHandler.add(&exampleV1Endpoint) 46 | 47 | returnedKeys := epHandler.Ep.RedisClient.GetDefaultDBKeyValues() 48 | 49 | expectedKeys := make(map[string][]string) 50 | 51 | if !util.IsSameMap(returnedKeys, expectedKeys) { 52 | t.Errorf("returned \n%v, but expected \n%v", returnedKeys, expectedKeys) 53 | } 54 | } 55 | 56 | func TestUpdate_UpdateAddress(t *testing.T) { 57 | epHandler := createExampleEpHandler() 58 | exampleV1Endpoint := createExampleV1Endpoint() 59 | 60 | epHandler.add(&exampleV1Endpoint) 61 | 62 | exampleV1Endpoint.Subsets[0].Addresses[0].IP = "10.10.3.3" 63 | 64 | epHandler.update(&exampleV1Endpoint) 65 | 66 | returnedKeys := epHandler.Ep.RedisClient.GetDefaultDBKeyValues() 67 | 68 | expectedKeys := getExpectedKeysForAddressUpdate() 69 | 70 | if !util.IsSameMap(returnedKeys, expectedKeys) { 71 | t.Errorf("returned \n%v, but expected \n%v", returnedKeys, expectedKeys) 72 | } 73 | } 74 | 75 | func TestUpdate_UpdatePortNumber(t *testing.T) { 76 | epHandler := createExampleEpHandler() 77 | exampleV1Endpoint := createExampleV1Endpoint() 78 | 79 | epHandler.add(&exampleV1Endpoint) 80 | 81 | exampleV1Endpoint.Subsets[0].Ports[0].Port = 8081 82 | 83 | epHandler.update(&exampleV1Endpoint) 84 | 85 | returnedKeys := epHandler.Ep.RedisClient.GetDefaultDBKeyValues() 86 | 87 | expectedKeys := getExpectedKeysForEndpointAdd() 88 | expectedKeys["trafficserver-test-2:testsvc:8081"] = make([]string, 2) 89 | expectedKeys["trafficserver-test-2:testsvc:8081"][0] = "10.10.2.2#8081#http" 90 | expectedKeys["trafficserver-test-2:testsvc:8081"][1] = "10.10.1.1#8081#http" 91 | 92 | if !util.IsSameMap(returnedKeys, expectedKeys) { 93 | t.Errorf("returned \n%v, but expected \n%v", returnedKeys, expectedKeys) 94 | } 95 | } 96 | 97 | func TestUpdate_UpdatePortName(t *testing.T) { 98 | epHandler := createExampleEpHandler() 99 | exampleV1Endpoint := createExampleV1Endpoint() 100 | 101 | epHandler.add(&exampleV1Endpoint) 102 | 103 | exampleV1Endpoint.Subsets[0].Ports[0].Name = "https" 104 | 105 | epHandler.update(&exampleV1Endpoint) 106 | 107 | returnedKeys := epHandler.Ep.RedisClient.GetDefaultDBKeyValues() 108 | 109 | expectedKeys := getExpectedKeysForEndpointAdd() 110 | expectedKeys["trafficserver-test-2:testsvc:8080"] = make([]string, 2) 111 | expectedKeys["trafficserver-test-2:testsvc:8080"][0] = "10.10.1.1#8080#https" 112 | expectedKeys["trafficserver-test-2:testsvc:8080"][1] = "10.10.2.2#8080#https" 113 | 114 | if !util.IsSameMap(returnedKeys, expectedKeys) { 115 | t.Errorf("returned \n%v, but expected \n%v", returnedKeys, expectedKeys) 116 | } 117 | } 118 | 119 | func TestUpdate_UpdateEndpointName(t *testing.T) { 120 | epHandler := createExampleEpHandler() 121 | exampleV1Endpoint := createExampleV1Endpoint() 122 | 123 | epHandler.add(&exampleV1Endpoint) 124 | 125 | exampleV1Endpoint.ObjectMeta.Name = "testsvc-modified" 126 | 127 | epHandler.update(&exampleV1Endpoint) 128 | 129 | returnedKeys := epHandler.Ep.RedisClient.GetDefaultDBKeyValues() 130 | 131 | expectedKeys := getExpectedKeysForEndpointAdd() 132 | expectedKeys["trafficserver-test-2:testsvc-modified:8080"] = expectedKeys["trafficserver-test-2:testsvc:8080"] 133 | 134 | if !util.IsSameMap(returnedKeys, expectedKeys) { 135 | t.Errorf("returned \n%v, but expected \n%v", returnedKeys, expectedKeys) 136 | } 137 | } 138 | 139 | func TestDelete_DeleteEndpoint(t *testing.T) { 140 | epHandler := createExampleEpHandler() 141 | exampleV1Endpoint := createExampleV1Endpoint() 142 | 143 | epHandler.add(&exampleV1Endpoint) 144 | epHandler.delete(&exampleV1Endpoint) 145 | 146 | returnedKeys := epHandler.Ep.RedisClient.GetDefaultDBKeyValues() 147 | 148 | expectedKeys := make(map[string][]string) 149 | 150 | if !util.IsSameMap(returnedKeys, expectedKeys) { 151 | t.Errorf("returned \n%v, but expected \n%v", returnedKeys, expectedKeys) 152 | } 153 | } 154 | 155 | func TestUpdate_DeleteAddress(t *testing.T) { 156 | epHandler := createExampleEpHandler() 157 | exampleV1Endpoint := createExampleV1Endpoint() 158 | 159 | epHandler.add(&exampleV1Endpoint) 160 | 161 | exampleV1Endpoint.Subsets[0].Addresses = exampleV1Endpoint.Subsets[0].Addresses[:1] 162 | 163 | epHandler.update(&exampleV1Endpoint) 164 | 165 | returnedKeys := epHandler.Ep.RedisClient.GetDefaultDBKeyValues() 166 | 167 | expectedKeys := getExpectedKeysForEndpointAdd() 168 | expectedKeys["trafficserver-test-2:testsvc:8080"] = expectedKeys["trafficserver-test-2:testsvc:8080"][:1] 169 | 170 | if !util.IsSameMap(returnedKeys, expectedKeys) { 171 | t.Errorf("returned \n%v, but expected \n%v", returnedKeys, expectedKeys) 172 | } 173 | } 174 | 175 | func TestUpdate_AddAddress(t *testing.T) { 176 | epHandler := createExampleEpHandler() 177 | exampleV1Endpoint := createExampleV1Endpoint() 178 | 179 | epHandler.add(&exampleV1Endpoint) 180 | 181 | exampleV1Endpoint.Subsets[0].Addresses = append(exampleV1Endpoint.Subsets[0].Addresses, v1.EndpointAddress{ 182 | IP: "10.10.3.3", 183 | }) 184 | 185 | epHandler.update(&exampleV1Endpoint) 186 | 187 | returnedKeys := epHandler.Ep.RedisClient.GetDefaultDBKeyValues() 188 | 189 | expectedKeys := getExpectedKeysForEndpointAdd() 190 | expectedKeys["trafficserver-test-2:testsvc:8080"] = append(expectedKeys["trafficserver-test-2:testsvc:8080"], "10.10.3.3#8080#http") 191 | 192 | if !util.IsSameMap(returnedKeys, expectedKeys) { 193 | t.Errorf("returned \n%v, but expected \n%v", returnedKeys, expectedKeys) 194 | } 195 | } 196 | 197 | func createExampleV1Endpoint() v1.Endpoints { 198 | exampleEndpoint := v1.Endpoints{ 199 | ObjectMeta: meta_v1.ObjectMeta{ 200 | Name: "testsvc", 201 | Namespace: "trafficserver-test-2", 202 | }, 203 | Subsets: []v1.EndpointSubset{ 204 | { 205 | Addresses: []v1.EndpointAddress{ 206 | { 207 | IP: "10.10.1.1", 208 | }, 209 | { 210 | IP: "10.10.2.2", 211 | }, 212 | }, 213 | Ports: []v1.EndpointPort{ 214 | { 215 | Name: "main", 216 | Port: 8080, 217 | Protocol: "TCP", 218 | }, 219 | }, 220 | }, 221 | }, 222 | } 223 | 224 | return exampleEndpoint 225 | } 226 | 227 | func createExampleEpHandler() EpHandler { 228 | exampleEndpoint := createExampleEndpoint() 229 | epHandler := EpHandler{"endpoints", &exampleEndpoint} 230 | 231 | return epHandler 232 | } 233 | 234 | func getExpectedKeysForEndpointAdd() map[string][]string { 235 | expectedKeys := make(map[string][]string) 236 | expectedKeys["trafficserver-test-2:testsvc:8080"] = []string{} 237 | 238 | expectedKeys["trafficserver-test-2:testsvc:8080"] = append(expectedKeys["trafficserver-test-2:testsvc:8080"], "10.10.1.1#8080#http") 239 | expectedKeys["trafficserver-test-2:testsvc:8080"] = append(expectedKeys["trafficserver-test-2:testsvc:8080"], "10.10.2.2#8080#http") 240 | 241 | return expectedKeys 242 | } 243 | 244 | func getExpectedKeysForAddressUpdate() map[string][]string { 245 | expectedKeys := getExpectedKeysForEndpointAdd() 246 | 247 | if expectedKeys["trafficserver-test-2:testsvc:8080"][0] == "10.10.1.1#8080#http" { 248 | expectedKeys["trafficserver-test-2:testsvc:8080"] = expectedKeys["trafficserver-test-2:testsvc:8080"][1:] 249 | } else { 250 | expectedKeys["trafficserver-test-2:testsvc:8080"] = expectedKeys["trafficserver-test-2:testsvc:8080"][:1] 251 | } 252 | 253 | expectedKeys["trafficserver-test-2:testsvc:8080"] = append(expectedKeys["trafficserver-test-2:testsvc:8080"], "10.10.3.3#8080#http") 254 | 255 | return expectedKeys 256 | } 257 | -------------------------------------------------------------------------------- /watcher/watcher.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package watcher 17 | 18 | import ( 19 | "errors" 20 | "fmt" 21 | "log" 22 | "time" 23 | 24 | v1 "k8s.io/api/core/v1" 25 | 26 | "k8s.io/client-go/informers" 27 | "k8s.io/client-go/kubernetes" 28 | "k8s.io/client-go/tools/cache" 29 | 30 | nv1 "k8s.io/api/networking/v1" 31 | 32 | "k8s.io/apimachinery/pkg/fields" 33 | pkgruntime "k8s.io/apimachinery/pkg/runtime" 34 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 35 | 36 | "github.com/apache/trafficserver-ingress-controller/endpoint" 37 | "github.com/apache/trafficserver-ingress-controller/proxy" 38 | ) 39 | 40 | // FIXME: watching all namespace does not work... 41 | 42 | // Watcher stores all essential information to act on HostGroups 43 | type Watcher struct { 44 | Cs kubernetes.Interface 45 | ATSNamespace string 46 | ResyncPeriod time.Duration 47 | Ep *endpoint.Endpoint 48 | StopChan chan struct{} 49 | } 50 | 51 | // EventHandler interface defines the 3 required methods to implement for watchers 52 | type EventHandler interface { 53 | Add(obj interface{}) 54 | Update(obj, newObj interface{}) 55 | Delete(obj interface{}) 56 | GetResourceName() string // EventHandler should store the ResourceName e.g. ingresses, endpoints... 57 | } 58 | 59 | // Watch creates necessary threads to watch over resources 60 | func (w *Watcher) Watch() error { 61 | //================= Watch for Ingress ================== 62 | igHandler := IgHandler{"ingresses", w.Ep} 63 | igListWatch := cache.NewListWatchFromClient(w.Cs.NetworkingV1().RESTClient(), igHandler.GetResourceName(), v1.NamespaceAll, fields.Everything()) 64 | err := w.allNamespacesWatchFor(&igHandler, w.Cs.NetworkingV1().RESTClient(), 65 | fields.Everything(), &nv1.Ingress{}, w.ResyncPeriod, igListWatch) 66 | if err != nil { 67 | return err 68 | } 69 | //================= Watch for Endpoints ================= 70 | epHandler := EpHandler{"endpoints", w.Ep} 71 | epListWatch := cache.NewListWatchFromClient(w.Cs.CoreV1().RESTClient(), epHandler.GetResourceName(), v1.NamespaceAll, fields.Everything()) 72 | err = w.allNamespacesWatchFor(&epHandler, w.Cs.CoreV1().RESTClient(), 73 | fields.Everything(), &v1.Endpoints{}, w.ResyncPeriod, epListWatch) 74 | if err != nil { 75 | return err 76 | } 77 | //================= Watch for ConfigMaps ================= 78 | cmHandler := CMHandler{"configmaps", w.Ep} 79 | targetNs := make([]string, 1) 80 | targetNs[0] = w.Ep.ATSManager.(*proxy.ATSManager).Namespace 81 | err = w.inNamespacesWatchFor(&cmHandler, w.Cs.CoreV1().RESTClient(), 82 | targetNs, fields.Everything(), &v1.ConfigMap{}, w.ResyncPeriod) 83 | if err != nil { 84 | return err 85 | } 86 | return nil 87 | } 88 | 89 | func (w *Watcher) allNamespacesWatchFor(h EventHandler, c cache.Getter, 90 | fieldSelector fields.Selector, objType pkgruntime.Object, 91 | resyncPeriod time.Duration, listerWatcher cache.ListerWatcher) error { 92 | 93 | factory := informers.NewSharedInformerFactory(w.Cs, resyncPeriod) 94 | var sharedInformer cache.SharedIndexInformer 95 | switch objType.(type) { 96 | case *v1.Endpoints: 97 | sharedInformer = factory.Core().V1().Endpoints().Informer() 98 | case *nv1.Ingress: 99 | sharedInformer = factory.Networking().V1().Ingresses().Informer() 100 | } 101 | 102 | _, err := sharedInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ 103 | AddFunc: h.Add, 104 | UpdateFunc: h.Update, 105 | DeleteFunc: h.Delete, 106 | }) 107 | if err != nil { 108 | return err 109 | } 110 | 111 | go sharedInformer.Run(w.StopChan) // new thread 112 | 113 | if !cache.WaitForCacheSync(w.StopChan, sharedInformer.HasSynced) { 114 | s := fmt.Sprintf("Timed out waiting for %s caches to sync", h.GetResourceName()) 115 | utilruntime.HandleError(errors.New(s)) 116 | return errors.New(s) 117 | } 118 | return nil 119 | } 120 | 121 | // This is meant to make it easier to add resource watchers on resources that 122 | // span multiple namespaces 123 | func (w *Watcher) inNamespacesWatchFor(h EventHandler, c cache.Getter, 124 | namespaces []string, fieldSelector fields.Selector, objType pkgruntime.Object, 125 | resyncPeriod time.Duration) error { 126 | if len(namespaces) == 0 { 127 | log.Panicln("inNamespacesWatchFor must have at least 1 namespace") 128 | } 129 | syncFuncs := make([]cache.InformerSynced, len(namespaces)) 130 | for i, ns := range namespaces { 131 | factory := informers.NewSharedInformerFactoryWithOptions(w.Cs, resyncPeriod, informers.WithNamespace(ns)) 132 | 133 | var sharedInformer cache.SharedIndexInformer 134 | switch objType.(type) { 135 | case *v1.Endpoints: 136 | sharedInformer = factory.Core().V1().Endpoints().Informer() 137 | case *nv1.Ingress: 138 | sharedInformer = factory.Networking().V1().Ingresses().Informer() 139 | case *v1.ConfigMap: 140 | sharedInformer = factory.Core().V1().ConfigMaps().Informer() 141 | } 142 | 143 | _, err := sharedInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ 144 | AddFunc: h.Add, 145 | UpdateFunc: h.Update, 146 | DeleteFunc: h.Delete, 147 | }) 148 | if err != nil { 149 | return err 150 | } 151 | 152 | go sharedInformer.Run(w.StopChan) 153 | 154 | syncFuncs[i] = sharedInformer.HasSynced 155 | } 156 | if !cache.WaitForCacheSync(w.StopChan, syncFuncs...) { 157 | s := fmt.Sprintf("Timed out waiting for %s caches to sync", h.GetResourceName()) 158 | utilruntime.HandleError(errors.New(s)) 159 | return errors.New(s) 160 | } 161 | return nil 162 | } 163 | --------------------------------------------------------------------------------