Full Code of Shopify/toxiproxy for AI

main c4e60d7e07b2 cached
87 files
277.7 KB
85.2k tokens
401 symbols
1 requests
Download .txt
Showing preview only (297K chars total). Download the full file or copy to clipboard to get everything.
Repository: Shopify/toxiproxy
Branch: main
Commit: c4e60d7e07b2
Files: 87
Total size: 277.7 KB

Directory structure:
gitextract_8n0j6szh/

├── .editorconfig
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── analysis.yml
│       ├── cla.yml
│       ├── release.yml
│       └── test.yml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── .yamllint.yml
├── CHANGELOG.md
├── CREATING_TOXICS.md
├── Dockerfile
├── LICENSE
├── METRICS.md
├── Makefile
├── README.md
├── RELEASE.md
├── _examples/
│   ├── tests/
│   │   ├── README.md
│   │   ├── cluster.yml
│   │   ├── db.go
│   │   ├── db_test.go
│   │   ├── main.go
│   │   ├── models.go
│   │   └── resources.yml
│   └── toxics/
│       ├── README.md
│       ├── debug_toxic.go
│       └── http_toxic.go
├── api.go
├── api_test.go
├── client/
│   ├── README.md
│   ├── api_error.go
│   ├── client.go
│   ├── client_test.go
│   ├── proxy.go
│   └── toxic.go
├── cmd/
│   ├── cli/
│   │   └── cli.go
│   └── server/
│       └── server.go
├── collectors/
│   ├── common.go
│   ├── proxy.go
│   └── runtime.go
├── dev.yml
├── go.mod
├── go.sum
├── link.go
├── link_test.go
├── metrics.go
├── metrics_test.go
├── proxy.go
├── proxy_collection.go
├── proxy_collection_test.go
├── proxy_test.go
├── scripts/
│   ├── hazelcast.xml
│   ├── test-e2e
│   ├── test-e2e-hazelcast
│   └── test-release
├── share/
│   └── toxiproxy.conf
├── stream/
│   ├── direction.go
│   ├── direction_test.go
│   ├── io_chan.go
│   └── io_chan_test.go
├── test/
│   └── e2e/
│       ├── benchmark_test.go
│       └── endpoint.go
├── testhelper/
│   ├── tcp_server.go
│   ├── tcp_server_test.go
│   ├── timeout_after.go
│   ├── timeout_after_test.go
│   └── upstream.go
├── toxic_collection.go
├── toxics/
│   ├── bandwidth.go
│   ├── bandwidth_test.go
│   ├── latency.go
│   ├── latency_test.go
│   ├── limit_data.go
│   ├── limit_data_test.go
│   ├── noop.go
│   ├── reset_peer.go
│   ├── reset_peer_test.go
│   ├── slicer.go
│   ├── slicer_test.go
│   ├── slow_close.go
│   ├── timeout.go
│   ├── timeout_test.go
│   ├── toxic.go
│   └── toxic_test.go
├── toxiproxy_test.go
└── version.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# EditorConfig is awesome: https://EditorConfig.org

# Unix-style newlines with a newline ending every file
[*]
charset                  = utf-8
end_of_line              = lf
insert_final_newline     = true
trim_trailing_whitespace = true
max_line_length          = 100

[Makefile]
indent_style = tab

[{scripts/*, *.sh}]
max_line_length    = 80
indent_style       = space
indent_size        = 2
shell_variant      = bash
binary_next_line   = true
switch_case_indent = true
space_redirects    = true
keep_padding       = true

[*.{yaml,yml}]
indent_style       = space
indent_size        = 2

[*.go]
indent_style = tab


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  - package-ecosystem: gomod
    directory: /
    schedule:
      interval: weekly
    registries: "*"

  - package-ecosystem: github-actions
    directory: /
    schedule:
      interval: weekly


================================================
FILE: .github/workflows/analysis.yml
================================================
---

name: Analysis

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
  schedule:
    - cron: 13 7 * * 6

jobs:
  linting:
    runs-on: ubuntu-latest

    steps:
      - name: checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0

      - name: golangci-lint
        uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20
        with:
          version: v2.1.6

      - name: shellcheck
        uses: azohra/shell-linter@30a9cf3f6cf25c08fc98f10d7dc4167f7b5c0c00
        with:
          path: scripts/test-*
          severity: error

      - name: yaml-lint
        uses: ibiqlik/action-yamllint@2576378a8e339169678f9939646ee3ee325e845c # v3.1.1
        with:
          config_file: .yamllint.yml

  vulnerabilities:
    runs-on: ubuntu-latest

    permissions:
      security-events: write

    steps:
      - name: checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0

      - name: initialize
        uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v3.29.5
        with:
          languages: go

      - name: codeql analyze
        uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v3.29.5


================================================
FILE: .github/workflows/cla.yml
================================================
---

name: Contributor License Agreement (CLA)

on:
  pull_request_target:
    types: [opened, synchronize]
  issue_comment:
    types: [created]

jobs:
  cla:
    permissions:
      actions: write
      pull-requests: write

    runs-on: ubuntu-latest
    if: |
      (github.event.issue.pull_request
      && !github.event.issue.pull_request.merged_at
      && contains(github.event.comment.body, 'signed')
      )
      || (github.event.pull_request && !github.event.pull_request.merged)
    steps:
      - uses: Shopify/shopify-cla-action@v1
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          cla-token: ${{ secrets.CLA_TOKEN }}


================================================
FILE: .github/workflows/release.yml
================================================
---

name: Release

on:
  push:
    tags: [ v*.*.* ]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      packages: write

    steps:
      -
        name: Checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0

      -
        name: GPG config
        run: |
          mkdir -p ~/.gnupg
          cat << EOF >> ~/.gnupg/gpg.conf
          keyserver hkps://keys.openpgp.org
          auto-key-import
          auto-key-retrieve
          EOF

      -
        name: Verify tag signature
        run: |
          # NOTE: Solve the problem with Github action checkout
          # https://github.com/actions/checkout/issues/290
          git fetch --tags --force

          version=${GITHUB_REF#refs/tags/*}
          git show $version
          git tag -v $version

      -
        name: Log into registry ${{ env.REGISTRY }}
        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      -
        name: Set up Go
        uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
        with:
          go-version: 1.22
          check-latest: true
          cache: true
      -
        name: Build release changelog
        run: |
          version=${GITHUB_REF#refs/tags/v*}
          mkdir -p tmp
          sed '/^# \['$version'\]/,/^# \[/!d;//d;/^\s*$/d' CHANGELOG.md > tmp/release_changelog.md

      -
        name: Release
        uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29
        with:
          distribution: goreleaser
          version: v1.10.3
          args: release --rm-dist --release-notes=tmp/release_changelog.md
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .github/workflows/test.yml
================================================
---

name: Test

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: true
      matrix:
        go: [1.22.8]
    name: go ${{ matrix.go }}
    steps:
      - name: Checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0

      - name: Setup go
        uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
        with:
          go-version: ${{ matrix.go }}
          check-latest: true
          cache: true

      - name: Tests
        run: make test

      - name: benchmarks
        run: make bench

  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0

      - name: Setup go
        uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
        with:
          go-version: 1.22
          check-latest: true
          cache: true

      - name: E2E tests
        run: make test-e2e

      - name: Build
        uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29
        with:
          distribution: goreleaser
          version: v1.10.3
          args: build --snapshot --rm-dist --skip-post-hooks --skip-validate --single-target
        env:
          GOOS: linux


================================================
FILE: .gitignore
================================================
# goreleaser output directory
dist/

# go mod dependencies
vendor/

tmp/


================================================
FILE: .golangci.yml
================================================
---
version: "2"
run:
  go: "1.22"
linters:
  default: none
  enable:
    - bodyclose
    - dogsled
    - exhaustive
    - funlen
    - gocritic
    - gocyclo
    - godot
    - gosec
    - govet
    - ineffassign
    - lll
    - misspell
    - staticcheck
    - unused
    - whitespace
  settings:
    funlen:
      lines: 80
      statements: 30
    gosec:
      excludes:
        - G107
    lll:
      line-length: 100
      tab-width: 2
    misspell:
      locale: US
  exclusions:
    generated: lax
    presets:
      - comments
      - common-false-positives
      - legacy
      - std-error-handling
    paths:
      - third_party$
      - builtin$
      - examples$
formatters:
  enable:
    - gofmt
    - goimports
  exclusions:
    generated: lax
    paths:
      - third_party$
      - builtin$
      - examples$


================================================
FILE: .goreleaser.yml
================================================
---

project_name: toxiproxy

env:
  - GO111MODULE=on

before:
  hooks:
    - go mod download
    - go mod tidy

builds:
  - &build_default
    id: server
    main: ./cmd/server
    binary: toxiproxy-server-{{.Os}}-{{.Arch}}
    no_unique_dist_dir: true
    env:
      - CGO_ENABLED=0

    goos:
      - darwin
      - freebsd
      - linux
      - netbsd
      - openbsd
      - solaris
      - windows

    goarch:
      - amd64
      - arm64

    ignore:
      - goos: windows
        goarch: arm64

      - goarch: arm

    ldflags:
      - -s -w -X github.com/Shopify/toxiproxy/v2.Version={{.Version}}

  - <<: *build_default
    id: server-arm
    goarch:
      - arm
    goarm:
      - "6"
      - "7"
    ignore:
      - goos: windows
        goarch: arm
    binary: toxiproxy-server-{{.Os}}-{{.Arch}}v{{.Arm}}

  - &build_client
    <<: *build_default
    id: client
    main: ./cmd/cli
    binary: toxiproxy-cli-{{.Os}}-{{.Arch}}

  - <<: *build_client
    id: client-arm
    goarch:
      - arm
    goarm:
      - "6"
      - "7"
    ignore:
      - goos: windows
        goarch: arm
    binary: toxiproxy-cli-{{.Os}}-{{.Arch}}v{{.Arm}}

  - <<: *build_default
    id: pkg-server
    no_unique_dist_dir: false
    binary: toxiproxy-server

  - <<: *build_default
    id: pkg-client
    no_unique_dist_dir: false
    main: ./cmd/cli
    binary: toxiproxy-cli

checksum:
  name_template: checksums.txt

snapshot:
  name_template: "{{ incpatch .Version }}-next"

nfpms:
  - id: packages
    package_name: toxiproxy
    homepage: https://github.com/Shopify/toxiproxy
    maintainer: Shopify Opensource <opensource@shopify.com>
    description: TCP proxy to simulate network and system conditions.
    license: MIT
    bindir: /usr/bin
    builds:
      - pkg-server
      - pkg-client
    formats:
      - apk
      - deb
      - rpm
      # NOTE: Starting with Ubuntu 15.04, Upstart will be deprecated in favor of Systemd.
      # contents:
      #   - src: share/toxiproxy.conf
      #     dst: /etc/init/toxiproxy.conf

dockers:
  - &docker
    use: buildx
    dockerfile: Dockerfile
    ids:
      - server
      - client
    goos: linux
    goarch: amd64
    image_templates:
      - ghcr.io/shopify/toxiproxy:{{ .Version }}-amd64
      - ghcr.io/shopify/toxiproxy:v{{ .Major }}-amd64
      - ghcr.io/shopify/toxiproxy:v{{ .Major }}.{{ .Minor }}-amd64
    build_flag_templates:
      - --platform=linux/amd64
      - --no-cache
      - --label=org.opencontainers.image.title={{ .ProjectName }}
      - --label=org.opencontainers.image.description={{ .ProjectName }}
      - --label=org.opencontainers.image.url=https://github.com/Shopify/{{ .ProjectName }}
      - --label=org.opencontainers.image.source=https://github.com/Shopify/{{ .ProjectName }}
      - --label=org.opencontainers.image.version={{ .Version }}
      - --label=org.opencontainers.image.created={{ time "2006-01-02T15:04:05Z07:00" }}
      - --label=org.opencontainers.image.revision={{ .FullCommit }}
      - --label=org.opencontainers.image.licenses=MIT
  - <<: *docker
    goarch: arm64
    image_templates:
      - ghcr.io/shopify/toxiproxy:{{ .Version }}-arm64
      - ghcr.io/shopify/toxiproxy:v{{ .Major }}-arm64
      - ghcr.io/shopify/toxiproxy:v{{ .Major }}.{{ .Minor }}-arm64
    build_flag_templates:
      - --platform=linux/arm64/v8
      - --no-cache
      - --label=org.opencontainers.image.title={{ .ProjectName }}
      - --label=org.opencontainers.image.description={{ .ProjectName }}
      - --label=org.opencontainers.image.url=https://github.com/Shopify/{{ .ProjectName }}
      - --label=org.opencontainers.image.source=https://github.com/Shopify/{{ .ProjectName }}
      - --label=org.opencontainers.image.version={{ .Version }}
      - --label=org.opencontainers.image.created={{ time "2006-01-02T15:04:05Z07:00" }}
      - --label=org.opencontainers.image.revision={{ .FullCommit }}
      - --label=org.opencontainers.image.licenses=MIT
  - <<: *docker
    goarch: arm
    goarm: "7"
    image_templates:
      - ghcr.io/shopify/toxiproxy:{{ .Version }}-armv7
      - ghcr.io/shopify/toxiproxy:v{{ .Major }}-armv7
      - ghcr.io/shopify/toxiproxy:v{{ .Major }}.{{ .Minor }}-armv7
    build_flag_templates:
      - --platform=linux/arm/v6
      - --no-cache
      - --label=org.opencontainers.image.title={{ .ProjectName }}
      - --label=org.opencontainers.image.description={{ .ProjectName }}
      - --label=org.opencontainers.image.url=https://github.com/Shopify/{{ .ProjectName }}
      - --label=org.opencontainers.image.source=https://github.com/Shopify/{{ .ProjectName }}
      - --label=org.opencontainers.image.version={{ .Version }}
      - --label=org.opencontainers.image.created={{ time "2006-01-02T15:04:05Z07:00" }}
      - --label=org.opencontainers.image.revision={{ .FullCommit }}
      - --label=org.opencontainers.image.licenses=MIT
  - <<: *docker
    goarch: arm
    goarm: "6"
    image_templates:
      - ghcr.io/shopify/toxiproxy:{{ .Version }}-armv6
      - ghcr.io/shopify/toxiproxy:v{{ .Major }}-armv6
      - ghcr.io/shopify/toxiproxy:v{{ .Major }}.{{ .Minor }}-armv6
    build_flag_templates:
      - --platform=linux/arm/v6
      - --no-cache
      - --label=org.opencontainers.image.title={{ .ProjectName }}
      - --label=org.opencontainers.image.description={{ .ProjectName }}
      - --label=org.opencontainers.image.url=https://github.com/Shopify/{{ .ProjectName }}
      - --label=org.opencontainers.image.source=https://github.com/Shopify/{{ .ProjectName }}
      - --label=org.opencontainers.image.version={{ .Version }}
      - --label=org.opencontainers.image.created={{ time "2006-01-02T15:04:05Z07:00" }}
      - --label=org.opencontainers.image.revision={{ .FullCommit }}
      - --label=org.opencontainers.image.licenses=MIT

docker_manifests:
  - name_template: ghcr.io/shopify/{{ .ProjectName }}:{{ .Version }}
    image_templates:
      - ghcr.io/shopify/{{ .ProjectName }}:{{ .Version }}-amd64
      - ghcr.io/shopify/{{ .ProjectName }}:{{ .Version }}-arm64
      - ghcr.io/shopify/{{ .ProjectName }}:{{ .Version }}-armv6
      - ghcr.io/shopify/{{ .ProjectName }}:{{ .Version }}-armv7
  - name_template: ghcr.io/shopify/{{ .ProjectName }}:latest
    image_templates:
      - ghcr.io/shopify/{{ .ProjectName }}:{{ .Version }}-amd64
      - ghcr.io/shopify/{{ .ProjectName }}:{{ .Version }}-arm64
      - ghcr.io/shopify/{{ .ProjectName }}:{{ .Version }}-armv6
      - ghcr.io/shopify/{{ .ProjectName }}:{{ .Version }}-armv7

changelog:
  sort: asc
  filters:
    exclude:
      - "^docs:"
      - "^test:"
      - ^Merge

archives:
  - id: archive_binaries
    format: binary
    name_template: "{{ .Binary }}"
    builds:
      - server
      - client

  - id: archive_default
    format: tar.gz
    builds:
      - pkg-server
      - pkg-client


================================================
FILE: .yamllint.yml
================================================
---

yaml-files:
  - "*.yaml"
  - "*.yml"

ignore: |
  vendor/**/*
  dist/*.yaml
  .github/**/*

rules:
  comments:
    require-starting-space: true
  comments-indentation: enable
  document-start:
    present: true
  indentation:
    spaces: 2
    indent-sequences: true
    check-multi-line-strings: true
  line-length:
    max: 100
    level: warning
    allow-non-breakable-words: true
    allow-non-breakable-inline-mappings: false
  key-duplicates: enable
  new-lines:
    type: unix
  trailing-spaces: enable
  quoted-strings:
    quote-type: double
    required: only-when-needed


================================================
FILE: CHANGELOG.md
================================================
# [Unreleased]

# [2.12.0]

- Update go version to 1.23.0 (#628)
- Do not restart proxies when using hostnames to specify listen address when updating a proxy
  and populating a collection (#631, @robinbrandt)
- Update various go packages

# [2.11.0] - 2024-10-16

- Do not pin minimum patch version in module (#595, @jaimem88)

# [2.10.0] - 2024-10-08

- Update various go packages
- Update go version to 1.22.8 (#594, @abecevello)

# [2.9.0] - 2024-03-12

- Updated go version to 1.22.1 to fix 3 CVEs (#559, @dianadevasia)
- Updated the version of golangci to 1.56.2 and disabled depguard rule in golangci (#559, @dianadevasia)

# [2.8.0] - 2024-02-27

- toxiproxy-cli - sortedAttributes sort by attribute.key instead attribute.value (#543, @jesseward)

# [2.7.0] - 2023-10-25

- Fix invalid JSON in /version endpoint response (#538, @whatyouhide)
- Update minimum supported Go version 1.19. (@abecevello)

# [2.6.0] - 2023-08-22

- Gracefull shutdown of HTTP server. (#439, @miry)
- Support PATCH HTTP method for Proxy update(`PATCH /proxies/{proxy}`) and
  Toxic update(`PATCH /proxies/{proxy}/toxics/{toxic}`) endpoints.
  Deprecat POST HTTP method for those endpoints. (@miry)
- Client does not parse response body in case of errors for Populate.
  Requires to get current proxies with new command. (#441, @miry)
- Client specifies `User-Agent` HTTP header for all requests as
  "toxiproxy-cli/<version> <os>/<runtime>".
  Specifies client request content type as `application/json`. (#441, @miry)
- Replace Api.Listen parameters `host` and `port` with single `addr`. (#445, @miry)

# [2.5.0] - 2022-09-10

- Update Release steps. (#369, @neufeldtech)
- Migrate off probot-CLA to new GitHub Action. (#405, @cursedcoder)
- Support go 1.18, 1.19. (#415, @miry)
- `toxiproxy.NewProxy` now accepts `name`, `listen addr` and `upstream addr`. (#418, @miry)
- Replace logrus with zerolog. (#413, @miry)
- Log HTTP requests to API server. (#413, #421, @miry)
- Add TimeoutHandler for the HTTP API server. (#420, @miry)
- Set Write and Read timeouts for HTTP API server connections. (#423, @miry)
- Show unique request id in API HTTP response. (#425, @miry)
- Add method to parse `stream.Direction` from a string.
  Allow converting `stream.Direction` to string. (#430, @miry)
- Add the possibility to write to Output with a deadline.
  On interrupting Bandwidth toxic, use non-blocking writes. (#436, @miry)
- Update minimum supported Go version 1.17. (#438, @miry)

# [2.4.0] - 2022-03-07

- Verify git tag on release (#347, @miry)
- Fix MacOS 12 tests for go17 with -race flag (#351, @strech)
- Rename `testing/` and `bin/` folders (#354, @strech)
- Added verbose error on proxy upstream dialing (#355, @f-dg)
- Improve server startup message (#358, @areveny)
- Introduce yaml linter. (#362, @miry)
- Handle slicer toxic with zero `SizeVariation` and fix slicing randomization (#359, @areveny)
- Added /metrics endpoint for exposing Prometheus-compatible internal metrics (#366, @neufeldtech)

# [2.3.0] - 2021-12-23

- Store all the executable `main` packages in `cmd` folder. (#335, @miry)
- Extract common test helpers to own files. (#336, @miry)
- Client: Allow HTTPS endpoints. (#338, @chen-anders)
- client.Populate assign client to proxy. (#291, @hellodudu)
- fix: The release-test task is always success.
  add: Allow to run release-test on arm machines. (#340, @miry)
- Upgrade `goreleaser`. Support `armv7` and `armv6` oses. (#339, @mitchellrj)
- Allow to change log level for server. (#346, @miry)

# [2.2.0] - 2021-10-17

- Update linux packages to use `/usr/bin` folder as binary destination and change the executable names to
  exclude ARCH and OS names. New pathes:
  ```
  /usr/bin/toxiproxy-cli
  /usr/bin/toxiproxy-server
  ```
  (#331, @miry)
- A new toxic to simulate TCP RESET (Connection reset by peer) on the connections by closing
  the stub Input immediately or after a timeout. (#247 and #333, @chaosbox)

# [2.1.7] - 2021-09-23

- Set the valid version during the build process.
  Verify the correct verion of the built binaries with `make release-dry` (#328, @miry)

# [2.1.6] - 2021-09-23

- Use CHANGELOG.md for release description (#306, @miry)
- Dependency updates in #294 introduced a breaking change in CLI argument parsing.
  Now [flags must be specified before arguments](https://github.com/urfave/cli/blob/master/docs/migrate-v1-to-v2.md#flags-before-args).
  Previously, arguments could be specified prior to flags.
  Update usage help text and documentation. (#308, @miry)
- Run e2e tests to validate the command line and basic features of server,
  client and application (#309, @miry)
- Add /v2 suffix to module import path (#311, @dnwe)
- Setup automated checking source code for security vulnerabilities (#312, @miry)
- Setup code linter (#314, @miry)
  - Max line length is 100 characters (#316, @miry)
  - Linter to check whether HTTP response body is closed successfully (#317, @miry)
  - Make sure the function are not big (#318, @miry)
    - Extract client flags specs to seprate methods.
      Introduce a new way to manage toxics with `ToxicOptions` structure (#321, @miry)
    - Split `Proxy.server` to multiple small (#322, @miry)
    - Extract initializetion of fake upstream server to test helper (#323, @miry)
    - Support a list of well knonwn linters (#326, @miry)
- `--host` flag uses `TOXIPROXY_URL` if it is set (#319, @maaslalani)
- Run benchmarks in CI/CD (#320, @miry)
- Use scratch docker base image instead of alpine (#325, @miry)

# [2.1.5] - 2021-09-01

- Move to Go Modules from godeps (#253, @epk)
- Update the example in `client/README.md` (#251, @nothinux)
- Update TOC in `README.md` (4ca1eddddfcd0c50c8f6dfb97089bb68e6310fd9, @dwradcliffe)
- Add an example of `config.json` file to `README.md` (#260, @JesseEstum)
- Add Link to Elixir Client (#287, @Jcambass)
- Add Rust client link (#293, @itarato)
- Renovations: formatting code, update dependicies, make govet/staticcheck pass (#294, @dnwe)
- Remove `openssl` from `dev.yml` to use `dev` tool (#298, @pedro-stanaka)
- Update `go` versions in development (#299, @miry)
- Mention `MacPorts` in `README.md` (#290, @amake)
- Fix some typos in `README.md` and `CHANGELOG.md` (#222, @jwilk)
- Replace TravisCI with Github Actions to run tests (#303, @miry)
- Build and release binaries with `goreleaser`. Support `arm64` and BSD oses. (#301, @miry)
- Automate release with Github actions (#304, @miry)

# [2.1.4] - 2019-01-11

- Bug fix: Fix OOM in toxic. #232
- Documentation updates.
- CI and test updates.

# [2.1.3] - 2018-03-05

- Update `/version` endpoint to also return a charset of utf-8. #204
- Bug fix: Double http concatenation. #191
- Update cli examples to be more accurate. #187

# [2.1.2] - 2017-07-10

- go 1.8, make Sirupsen lower case, update godeps (issue #179)
- Handle SIGTERM to exit cleanly (issue #180)
- Address security issue by disallowing browsers from accessing API

# [2.1.1] - 2017-05-16

- Fix timeout toxic causing hang (issue #159)

# [2.1.0] - 2016-12-07

- Add -config server option to populate on startup #154
- Updated CLI for scriptability #133
- Add `/populate` endpoint to server #111
- Change error responses from `title` to `error`
- Allow hostname to be specified in CLI #129
- Add support for stateful toxics #127
- Add limit_data toxic

# [2.0.0] - 2016-04-25

- Add CLI (`toxiproxy-cli`) and rename server binary to `toxiproxy-server` #93
- Fix removing a timeout toxic causing API to hang #89
- API and client return toxics as array rather than a map of name to toxic #92
- Fix multiple latency toxics not accumulating #94
- Change default toxic name to `<type>_<stream>` #96
- Nest toxic attributes rather than having a flat structure #98
- 2.0 RFC: #54 and PR #62
  - Change toxic API endpoints to an Add/Update/Remove structure
  - Remove `enabled` field, and add `name` and `type` fields to toxics
  - Add global toxic fields to a wrapper struct
  - Chain toxics together dynamically instead of in a fixed length chain
  - Register toxics in `init()` functions instead of a hard-coded list
  - Clean up API error codes to make them more consistent
  - Move toxics to their own package to allow 3rd party toxics
- Remove stream direction from API urls #73
- Add `toxicity` field for toxics #75
- Refactor Go client to make usage easier with 2.0 #76
- Make `ChanReader` in the `stream` package interruptible #77
- Define proxy buffer sizes per-toxic (Fixes #72)
- Fix slicer toxic testing race condition #71

# [1.2.1] - 2015-07-24

- Fix proxy name conflicts leaking an open port #69

# [1.2.0] - 2015-07-23

- Add a Toxic and Toxics type for the Go client
- Add `Dockerfile`
- Fix latency toxic limiting bandwidth #67
- Add Slicer toxic

# [1.1.0] - 2015-05-05

- Remove /toxics endpoint in favour of /proxies
- Add bandwidth toxic

# [1.0.3] - 2015-04-29

- Rename Go library package to Toxiproxy from Client
- Fix latency toxic send to closed channel panic #46
- Fix latency toxic accumulating delay #47

# [1.0.2] - 2015-04-12

- Added Toxic support to Go client

# [1.0.1] - 2015-03-31

- Various improvements to the documentation
- Initial version of Go client
- Fix toxic disabling bug #42

# [1.0.0] - 2015-01-07

Initial public release.

[Unreleased]: https://github.com/Shopify/toxiproxy/compare/v2.12.0...HEAD
[2.12.0]: https://github.com/Shopify/toxiproxy/compare/v2.11.0...v2.12.0
[2.11.0]: https://github.com/Shopify/toxiproxy/compare/v2.10.0...v2.11.0
[2.10.0]: https://github.com/Shopify/toxiproxy/compare/v2.9.0...v2.10.0
[2.9.0]: https://github.com/Shopify/toxiproxy/compare/v2.8.0...v2.9.0
[2.8.0]: https://github.com/Shopify/toxiproxy/compare/v2.7.0...v2.8.0
[2.7.0]: https://github.com/Shopify/toxiproxy/compare/v2.6.0...v2.7.0
[2.6.0]: https://github.com/Shopify/toxiproxy/compare/v2.5.0...v2.6.0
[2.5.0]: https://github.com/Shopify/toxiproxy/compare/v2.4.0...v2.5.0
[2.4.0]: https://github.com/Shopify/toxiproxy/compare/v2.3.0...v2.4.0
[2.3.0]: https://github.com/Shopify/toxiproxy/compare/v2.2.0...v2.3.0
[2.2.0]: https://github.com/Shopify/toxiproxy/compare/v2.1.7...v2.2.0
[2.1.7]: https://github.com/Shopify/toxiproxy/compare/v2.1.6...v2.1.7
[2.1.6]: https://github.com/Shopify/toxiproxy/compare/v2.1.5...v2.1.6
[2.1.5]: https://github.com/Shopify/toxiproxy/compare/v2.1.4...v2.1.5
[2.1.4]: https://github.com/Shopify/toxiproxy/compare/v2.1.3...v2.1.4
[2.1.3]: https://github.com/Shopify/toxiproxy/compare/v2.1.2...v2.1.3
[2.1.2]: https://github.com/Shopify/toxiproxy/compare/v2.1.1...v2.1.2
[2.1.1]: https://github.com/Shopify/toxiproxy/compare/v2.1.0...v2.1.1
[2.1.0]: https://github.com/Shopify/toxiproxy/compare/v2.0.0...v2.1.0
[2.0.0]: https://github.com/Shopify/toxiproxy/compare/v1.2.1...v2.0.0
[1.2.1]: https://github.com/Shopify/toxiproxy/compare/v1.2.0...v1.2.1
[1.2.0]: https://github.com/Shopify/toxiproxy/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/Shopify/toxiproxy/compare/v1.0.3...v1.1.0
[1.0.3]: https://github.com/Shopify/toxiproxy/compare/v1.0.2...v1.0.3
[1.0.2]: https://github.com/Shopify/toxiproxy/compare/v1.0.1...v1.0.2
[1.0.1]: https://github.com/Shopify/toxiproxy/compare/v1.0.0...v1.0.1
[1.0.0]: https://github.com/Shopify/toxiproxy/releases/tag/v1.0.0


================================================
FILE: CREATING_TOXICS.md
================================================
# Creating custom toxics

Creating a toxic is done by implementing the `Toxic` interface:

```go
type Toxic interface {
    Pipe(*toxics.ToxicStub)
}
```

The `Pipe()` function defines how data flows through the toxic, and is passed a
`ToxicStub` to operate on. A `ToxicStub` stores the input and output channels for
the toxic, as well as an interrupt channel that is used to pause operation of the
toxic.

The input and output channels in a `ToxicStub` send and receive `StreamChunk` structs,
which are similar to network packets. A `StreamChunk` contains a `byte[]` of stream
data, and a timestamp of when Toxiproxy received the data from the client or server.
This is used instead of just a plain `byte[]` so that toxics like latency can find out
how long a chunk of data has been waiting in the proxy.

Toxics are registered in an `init()` function so that they can be used by the server:

```go
func init() {
    toxics.Register("toxic_name", new(ExampleToxic))
}
```

In order to use your own toxics, you will need to compile your own binary.
This can be done by copying [server](./cmd/server/server.go)
into a new project and registering your toxic with the server.
This will allow you to add toxics without having to make a full fork of the project.
If you think your toxics will be useful to others,
contribute them back with a Pull Request.

An example project for building a separate binary can be found here:
[examples](./_examples/toxics/)

## A basic toxic

The most basic implementation of a toxic is the [noop toxic](./toxics/noop.go),
which just passes data through without any modifications.

```go
type NoopToxic struct{}

func (t *NoopToxic) Pipe(stub *toxics.ToxicStub) {
    for {
        select {
        case <-stub.Interrupt:
            return
        case c := <-stub.Input:
            if c == nil {
                stub.Close()
                return
            }
            stub.Output <- c
        }
    }
}
```

The above code reads from `stub.Input` in a loop, and passes the `StreamChunk` along to
`stub.Output`. Since reading from `stub.Input` will block until a chunk is available,
we need to check for interrupts as the same time.

Toxics will be interrupted whenever they are being updated, or possibly removed. This can
happen at any point within the `Pipe()` function, so all blocking operations (including sleep),
should be interruptible. When an interrupt is received, the toxic should return from the `Pipe()`
function after it has written any "in-flight" data back to `stub.Output`. It is important that
all data read from `stub.Input` is passed along to `stub.Output`, otherwise the stream will be
missing bytes and become corrupted.

When an `end of stream` is reached, `stub.Input` will return a `nil` chunk. Whenever a
nil chunk is returned, the toxic should call `Close()` on the stub, and return from `Pipe()`.

## Toxic configuration

Toxic configuration information can be stored in the toxic struct. The toxic will be json
encoded and decoded by the api, so all public fields will be api accessible.

An example of a toxic that uses configuration values is the [latency toxic](./toxics/latency.go)

```go
type LatencyToxic struct {
    Latency int64 `json:"latency"`
    Jitter  int64 `json:"jitter"`
}
```

These fields can be used inside the `Pipe()` function, but generally should not be written
to from the toxic. A separate instance of the toxic exists for each connection through the
proxy, and may be replaced when updated by the api. If state is required in your toxic, it
is better to use a local variable at the top of `Pipe()`, since struct fields are not
guaranteed to be persisted across interrupts.

## Toxic buffering

By default, toxics are not buffered. This means that writes to `stub.Output` will block until
either the endpoint or another toxic reads it. Since toxics are chained together, this means
not reading from `stub.Input` will block other toxics (and endpoint writes) from operating.
If this is not behavior you want your toxic to have, you can specify a buffer size for your
toxic's input. The [latency toxic](./toxics/latency.go)
uses this in order to prevent added latency from limiting the proxy bandwidth.

Specifying a buffer size is done by implementing the `BufferedToxic` interface, which adds the
`GetBufferSize()` function:

```go
func (t *LatencyToxic) GetBufferSize() int {
    return 1024
}
```

The unit used by `GetBufferSize()` is `StreamChunk`s. Chunks are generally anywhere from
1 byte, up to 32KB, so keep this in mind when thinking about how much buffering you need,
and how much memory you are comfortable with using.

## Stateful toxics

If a toxic needs to store extra information for a connection such as the number of bytes
transferred (See `limit_data` toxic), a state object can be created by implementing the
`StatefulToxic` interface. This interface defines the `NewState()` function that can create
a new state object with default values set.

When a stateful toxic is created, the state object will be stored on the `ToxicStub` and
can be accessed from `toxic.Pipe()`:

```go
state := stub.State.(*ExampleToxicState)
```

If necessary, some global state can be stored in the toxic struct, which will not be
instanced per-connection. These fields cannot have a custom default value set and will
not be thread-safe, so proper locking or atomic operations will need to be used.

## Using `io.Reader` and `io.Writer`

If your toxic involves modifying the data going through a proxy, you can use the `ChanReader`
and `ChanWriter` interfaces in the [stream package](./stream).
These allow reading and writing from the input and output channels as you would a normal data
stream such as a TCP socket.

An implementation of the noop toxic above using the stream package would look something like this:

```go
func (t *NoopToxic) Pipe(stub *toxics.ToxicStub) {
    buf := make([]byte, 32*1024)
    writer := stream.NewChanWriter(stub.Output)
    reader := stream.NewChanReader(stub.Input)
    reader.SetInterrupt(stub.Interrupt)
    for {
        n, err := reader.Read(buf)
        if err == stream.ErrInterrupted {
            writer.Write(buf[:n])
            return
        } else if err == io.EOF {
            stub.Close()
            return
        }
        writer.Write(buf[:n])
    }
}
```

See [examples](./_examples/toxics/) for a full example of using
the stream package with Go's http package.


================================================
FILE: Dockerfile
================================================
FROM scratch

EXPOSE 8474
ENTRYPOINT ["/toxiproxy"]
CMD ["-host=0.0.0.0"]

ENV LOG_LEVEL=info

COPY toxiproxy-server-linux-* /toxiproxy
COPY toxiproxy-cli-linux-* /toxiproxy-cli


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2014 Shopify

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.



================================================
FILE: METRICS.md
================================================
# Metrics

- [Metrics](#metrics)
    - [Runtime Metrics](#runtime-metrics)
    - [Proxy Metrics](#proxy-metrics)
      - [toxiproxy_proxy_received_bytes_total / toxiproxy_proxy_sent_bytes_total](#toxiproxy_proxy_received_bytes_total--toxiproxy_proxy_sent_bytes_total)

### Runtime Metrics

To enable runtime metrics related to the state of the go runtime, build version, process info, use the `-runtime-metrics` flag.

For more details, see below:
- [NewGoCollector](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus/collectors#NewGoCollector)
- [NewBuildInfoCollector](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus/collectors#NewBuildInfoCollector)
- [NewProcessCollector](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus/collectors#NewProcessCollector)

### Proxy Metrics

To enable metrics related to toxiproxy internals, use the `-proxy-metrics` flag.
#### toxiproxy_proxy_received_bytes_total / toxiproxy_proxy_sent_bytes_total

The total number of bytes received/sent on a given proxy link in a given direction

```mermaid
sequenceDiagram
    Client->>+Toxiproxy: toxiproxy_proxy_received_bytes_total{direction="upstream"}
    Toxiproxy->>+Server: toxiproxy_proxy_sent_bytes_total{direction="upstream"}
    Server->>+Toxiproxy: toxiproxy_proxy_received_bytes_total{direction="downstream"}
    Toxiproxy->>+Client: toxiproxy_proxy_sent_bytes_total{direction="downstream"}
```

**Type**

Counter

**Labels**

| Label     | Description                    | Example               |
|-----------|--------------------------------|-----------------------|
| direction | Direction of the link          | upstream / downstream |
| listener  | Listener address of this proxy | 0.0.0.0:8080          |
| proxy     | Proxy name                     | my-proxy              |
| upstream  | Upstream address of this proxy | httpbin.org:80        |



================================================
FILE: Makefile
================================================
OS := $(shell uname -s)
ARCH := $(shell uname -m)
GO_VERSION := $(shell go version | cut -f3 -d" ")
GO_MINOR_VERSION := $(shell echo $(GO_VERSION) | cut -f2 -d.)
GO_PATCH_VERSION := $(shell echo $(GO_VERSION) | cut -f3 -d. | sed "s/^\s*$$/0/")
MALLOC_ENV := $(shell [ $(OS) = Darwin -a $(GO_MINOR_VERSION) -eq 17 -a $(GO_PATCH_VERSION) -lt 6 ] && echo "MallocNanoZone=0")

.PHONY: all
all: setup build test bench fmt lint

.PHONY: test
test:
	# NOTE: https://github.com/golang/go/issues/49138
	$(MALLOC_ENV) go test -v -race -timeout 1m ./...

.PHONY: test-e2e
test-e2e: build container.build
	scripts/test-e2e
	timeout -v --foreground 20m scripts/test-e2e-hazelcast toxiproxy

.PHONY: test-release
test-release: lint fmt test bench test-e2e release-dry
	scripts/test-release

.PHONY: bench
bench:
	# TODO: Investigate why benchmarks require more sockets: ulimit -n 10240
	go test -bench=. -v *.go
	go test -bench=. -v toxics/*.go

.PHONY: fmt
fmt:
	go fmt ./...
	goimports -w **/*.go
	golangci-lint run --fix
	shfmt -l -s -w -kp -i 2 scripts/test-*

.PHONY: lint
lint:
	golangci-lint run
	shellcheck scripts/test-*
	shfmt -l -s -d -kp -i 2 scripts/test-*
	yamllint .

.PHONY: build
build: dist clean
	go build -ldflags="-s -w" -o ./dist/toxiproxy-server ./cmd/server
	go build -ldflags="-s -w" -o ./dist/toxiproxy-cli ./cmd/cli

.PHONY: container.build
container.build:
	env GOOS=linux CGO_ENABLED=0 go build -ldflags="-s -w" -o ./dist/toxiproxy-server-linux-$(ARCH) ./cmd/server
	env GOOS=linux CGO_ENABLED=0 go build -ldflags="-s -w" -o ./dist/toxiproxy-cli-linux-$(ARCH) ./cmd/cli
	docker build -f Dockerfile -t toxiproxy dist
	docker run --rm toxiproxy --version

.PHONY: release
release:
	goreleaser release --rm-dist

.PHONY: release-dry
release-dry:
	goreleaser release --rm-dist --skip-publish --skip-validate

.PHONY: setup
setup:
	go mod download
	go mod tidy

dist:
	mkdir -p dist

.PHONY: clean
clean:
	rm -fr dist/*


================================================
FILE: README.md
================================================
# Toxiproxy
[![GitHub release](https://img.shields.io/github/release/Shopify/toxiproxy.svg)](https://github.com/Shopify/toxiproxy/releases/latest)
[![Build Status](https://github.com/Shopify/toxiproxy/actions/workflows/test.yml/badge.svg)](https://github.com/Shopify/toxiproxy/actions/workflows/test.yml)

![](http://i.imgur.com/sOaNw0o.png)

Toxiproxy is a framework for simulating network conditions. It's made
specifically to work in testing, CI and development environments, supporting
deterministic tampering with connections, but with support for randomized chaos
and customization. **Toxiproxy is the tool you need to prove with tests that
your application doesn't have single points of failure.** We've been
successfully using it in all development and test environments at Shopify since
October, 2014. See our [blog post][blog] on resiliency for more information.

Toxiproxy usage consists of two parts. A TCP proxy written in Go (what this
repository contains) and a client communicating with the proxy over HTTP. You
configure your application to make all test connections go through Toxiproxy
and can then manipulate their health via HTTP. See [Usage](#usage)
below on how to set up your project.

For example, to add 1000ms of latency to the response of MySQL from the [Ruby
client](https://github.com/Shopify/toxiproxy-ruby):

```ruby
Toxiproxy[:mysql_master].downstream(:latency, latency: 1000).apply do
  Shop.first # this takes at least 1s
end
```

To take down all Redis instances:

```ruby
Toxiproxy[/redis/].down do
  Shop.first # this will throw an exception
end
```

While the examples in this README are currently in Ruby, there's nothing
stopping you from creating a client in any other language (see
[Clients](#clients)).

## Table of Contents

- [Toxiproxy](#toxiproxy)
  - [Table of Contents](#table-of-contents)
  - [Why yet another chaotic TCP proxy?](#why-yet-another-chaotic-tcp-proxy)
  - [Clients](#clients)
  - [Example](#example)
  - [Usage](#usage)
    - [1. Installing Toxiproxy](#1-installing-toxiproxy)
      - [Upgrading from Toxiproxy 1.x](#upgrading-from-toxiproxy-1x)
    - [2. Populating Toxiproxy](#2-populating-toxiproxy)
    - [3. Using Toxiproxy](#3-using-toxiproxy)
    - [4. Logging](#4-logging)
    - [Toxics](#toxics)
      - [latency](#latency)
      - [down](#down)
      - [bandwidth](#bandwidth)
      - [slow_close](#slow_close)
      - [timeout](#timeout)
      - [reset_peer](#reset_peer)
      - [slicer](#slicer)
      - [limit_data](#limit_data)
    - [HTTP API](#http-api)
      - [Proxy fields:](#proxy-fields)
      - [Toxic fields:](#toxic-fields)
      - [Endpoints](#endpoints)
      - [Populating Proxies](#populating-proxies)
    - [CLI Example](#cli-example)
    - [Metrics](#metrics)
    - [Frequently Asked Questions](#frequently-asked-questions)
    - [Development](#development)
    - [Release](#release)

## Why yet another chaotic TCP proxy?

The existing ones we found didn't provide the kind of dynamic API we needed for
integration and unit testing. Linux tools like `nc` and so on are not
cross-platform and require root, which makes them problematic in test,
development and CI environments.

## Clients

* [toxiproxy-ruby](https://github.com/Shopify/toxiproxy-ruby)
* [toxiproxy-go](https://github.com/Shopify/toxiproxy/tree/main/client)
* [toxiproxy-python](https://github.com/douglas/toxiproxy-python)
* [toxiproxy.net](https://github.com/mdevilliers/Toxiproxy.Net)
* [toxiproxy-php-client](https://github.com/ihsw/toxiproxy-php-client)
* [toxiproxy-node-client](https://github.com/ihsw/toxiproxy-node-client)
* [toxiproxy-java](https://github.com/trekawek/toxiproxy-java)
* [toxiproxy-haskell](https://github.com/jpittis/toxiproxy-haskell)
* [toxiproxy-rust](https://github.com/itarato/toxiproxy_rust)
* [toxiproxy-elixir](https://github.com/Jcambass/toxiproxy_ex)

## Example

Let's walk through an example with a Rails application. Note that Toxiproxy is
in no way tied to Ruby, it's just been our first use case. You can see the full example at
[sirupsen/toxiproxy-rails-example](https://github.com/sirupsen/toxiproxy-rails-example).
To get started right away, jump down to [Usage](#usage).

For our popular blog, for some reason we're storing the tags for our posts in
Redis and the posts themselves in MySQL. We might have a `Post` class that
includes some methods to manipulate tags in a [Redis set](http://redis.io/commands#set):

```ruby
class Post < ActiveRecord::Base
  # Return an Array of all the tags.
  def tags
    TagRedis.smembers(tag_key)
  end

  # Add a tag to the post.
  def add_tag(tag)
    TagRedis.sadd(tag_key, tag)
  end

  # Remove a tag from the post.
  def remove_tag(tag)
    TagRedis.srem(tag_key, tag)
  end

  # Return the key in Redis for the set of tags for the post.
  def tag_key
    "post:tags:#{self.id}"
  end
end
```

We've decided that erroring while writing to the tag data store
(adding/removing) is OK. However, if the tag data store is down, we should be
able to see the post with no tags. We could simply rescue the
`Redis::CannotConnectError` around the `SMEMBERS` Redis call in the `tags`
method. Let's use Toxiproxy to test that.

Since we've already installed Toxiproxy and it's running on our machine, we can
skip to step 2. This is where we need to make sure Toxiproxy has a mapping for
Redis tags. To `config/boot.rb` (before any connection is made) we add:

```ruby
require 'toxiproxy'

Toxiproxy.populate([
  {
    name: "toxiproxy_test_redis_tags",
    listen: "127.0.0.1:22222",
    upstream: "127.0.0.1:6379"
  }
])
```

Then in `config/environments/test.rb` we set the `TagRedis` to be a Redis client
that connects to Redis through Toxiproxy by adding this line:

```ruby
TagRedis = Redis.new(port: 22222)
```

All calls in the test environment now go through Toxiproxy. That means we can
add a unit test where we simulate a failure:

```ruby
test "should return empty array when tag redis is down when listing tags" do
  @post.add_tag "mammals"

  # Take down all Redises in Toxiproxy
  Toxiproxy[/redis/].down do
    assert_equal [], @post.tags
  end
end
```

The test fails with `Redis::CannotConnectError`. Perfect! Toxiproxy took down
the Redis successfully for the duration of the closure. Let's fix the `tags`
method to be resilient:

```ruby
def tags
  TagRedis.smembers(tag_key)
rescue Redis::CannotConnectError
  []
end
```

The tests pass! We now have a unit test that proves fetching the tags when Redis
is down returns an empty array, instead of throwing an exception. For full
coverage you should also write an integration test that wraps fetching the
entire blog post page when Redis is down.

Full example application is at
[sirupsen/toxiproxy-rails-example](https://github.com/sirupsen/toxiproxy-rails-example).

## Usage

Configuring a project to use Toxiproxy consists of three steps:

1. Installing Toxiproxy
2. Populating Toxiproxy
3. Using Toxiproxy

### 1. Installing Toxiproxy

**Linux**

See [`Releases`](https://github.com/Shopify/toxiproxy/releases) for the latest
binaries and system packages for your architecture.

**Ubuntu**

```bash
$ wget -O toxiproxy-2.1.4.deb https://github.com/Shopify/toxiproxy/releases/download/v2.1.4/toxiproxy_2.1.4_amd64.deb
$ sudo dpkg -i toxiproxy-2.1.4.deb
$ sudo service toxiproxy start
```

**OS X**

With [Homebrew](https://brew.sh/):

```bash
$ brew tap shopify/shopify
$ brew install toxiproxy
```

Or with [MacPorts](https://www.macports.org/):

```bash
$ port install toxiproxy
```

**Windows**

Toxiproxy for Windows is available for download at https://github.com/Shopify/toxiproxy/releases/download/v2.1.4/toxiproxy-server-windows-amd64.exe

**Docker**

Toxiproxy is available on [Github container registry](https://github.com/Shopify/toxiproxy/pkgs/container/toxiproxy).
Old versions `<= 2.1.4` are available on on [Docker Hub](https://hub.docker.com/r/shopify/toxiproxy/).

```bash
$ docker pull ghcr.io/shopify/toxiproxy
$ docker run --rm -it ghcr.io/shopify/toxiproxy
```

If using Toxiproxy from the host rather than other containers, enable host networking with `--net=host`.

```shell
$ docker run --rm --entrypoint="/toxiproxy-cli" -it ghcr.io/shopify/toxiproxy list
```

**Source**

If you have Go installed, you can build Toxiproxy from source using the make file:
```bash
$ make build
$ ./toxiproxy-server
```

#### Upgrading from Toxiproxy 1.x

In Toxiproxy 2.0 several changes were made to the API that make it incompatible with version 1.x.
In order to use version 2.x of the Toxiproxy server, you will need to make sure your client
library supports the same version. You can check which version of Toxiproxy you are running by
looking at the `/version` endpoint.

See the documentation for your client library for specific library changes. Detailed changes
for the Toxiproxy server can been found in [CHANGELOG.md](./CHANGELOG.md).

### 2. Populating Toxiproxy

When your application boots, it needs to make sure that Toxiproxy knows which
endpoints to proxy where. The main parameters are: name, address for Toxiproxy
to **listen** on and the address of the upstream.

Some client libraries have helpers for this task, which is essentially just
making sure each proxy in a list is created. Example from the Ruby client:

```ruby
# Make sure `shopify_test_redis_master` and `shopify_test_mysql_master` are
# present in Toxiproxy
Toxiproxy.populate([
  {
    name: "shopify_test_redis_master",
    listen: "127.0.0.1:22220",
    upstream: "127.0.0.1:6379"
  },
  {
    name: "shopify_test_mysql_master",
    listen: "127.0.0.1:24220",
    upstream: "127.0.0.1:3306"
  }
])
```

This code needs to run as early in boot as possible, before any code establishes
a connection through Toxiproxy. Please check your client library for
documentation on the population helpers.

Alternatively use the CLI to create proxies, e.g.:

```bash
toxiproxy-cli create -l localhost:26379 -u localhost:6379 shopify_test_redis_master
```

We recommend a naming such as the above: `<app>_<env>_<data store>_<shard>`.
This makes sure there are no clashes between applications using the same
Toxiproxy.

For large application we recommend storing the Toxiproxy configurations in a
separate configuration file. We use `config/toxiproxy.json`. This file can be
passed to the server using the `-config` option, or loaded by the application
to use with the `populate` function.

An example `config/toxiproxy.json`:

```json
[
  {
    "name": "web_dev_frontend_1",
    "listen": "[::]:18080",
    "upstream": "webapp.domain:8080",
    "enabled": true
  },
  {
    "name": "web_dev_mysql_1",
    "listen": "[::]:13306",
    "upstream": "database.domain:3306",
    "enabled": true
  }
]
```

Use ports outside the ephemeral port range to avoid random port conflicts.
It's `32,768` to `61,000` on Linux by default, see
`/proc/sys/net/ipv4/ip_local_port_range`.

### 3. Using Toxiproxy

To use Toxiproxy, you now need to configure your application to connect through
Toxiproxy. Continuing with our example from step two, we can configure our Redis
client to connect through Toxiproxy:

```ruby
# old straight to redis
redis = Redis.new(port: 6380)

# new through toxiproxy
redis = Redis.new(port: 22220)
```

Now you can tamper with it through the Toxiproxy API. In Ruby:

```ruby
redis = Redis.new(port: 22220)

Toxiproxy[:shopify_test_redis_master].downstream(:latency, latency: 1000).apply do
  redis.get("test") # will take 1s
end
```

Or via the CLI:

```bash
toxiproxy-cli toxic add -t latency -a latency=1000 shopify_test_redis_master
```

Please consult your respective client library on usage.

### 4. Logging

There are the following log levels: panic, fatal, error, warn or warning, info, debug and trace.
The level could be updated via environment variable `LOG_LEVEL`.

### Toxics

Toxics manipulate the pipe between the client and upstream. They can be added
and removed from proxies using the [HTTP api](#http-api). Each toxic has its own parameters
to change how it affects the proxy links.

For documentation on implementing custom toxics, see [CREATING_TOXICS.md](./CREATING_TOXICS.md)

#### latency

Add a delay to all data going through the proxy. The delay is equal to `latency` +/- `jitter`.

Attributes:

 - `latency`: time in milliseconds
 - `jitter`: time in milliseconds

#### down

Bringing a service down is not technically a toxic in the implementation of
Toxiproxy. This is done by `POST`ing to `/proxies/{proxy}` and setting the
`enabled` field to `false`.

#### bandwidth

Limit a connection to a maximum number of kilobytes per second.

Attributes:

 - `rate`: rate in KB/s

#### slow_close

Delay the TCP socket from closing until `delay` has elapsed.

Attributes:

 - `delay`: time in milliseconds

#### timeout

Stops all data from getting through, and closes the connection after `timeout`. If
`timeout` is 0, the connection won't close, and data will be dropped until the
toxic is removed.

Attributes:

 - `timeout`: time in milliseconds

#### reset_peer

Simulate TCP RESET (Connection reset by peer) on the connections by closing the stub Input
immediately or after a `timeout`.

Attributes:

 - `timeout`: time in milliseconds

#### slicer

Slices TCP data up into small bits, optionally adding a delay between each
sliced "packet".

Attributes:

 - `average_size`: size in bytes of an average packet
 - `size_variation`: variation in bytes of an average packet (should be smaller than average_size)
 - `delay`: time in microseconds to delay each packet by

#### limit_data

Closes connection when transmitted data exceeded limit.

 - `bytes`: number of bytes it should transmit before connection is closed

### HTTP API

All communication with the Toxiproxy daemon from the client happens through the
HTTP interface, which is described here.

Toxiproxy listens for HTTP on port **8474**.

#### Proxy fields:

 - `name`: proxy name (string)
 - `listen`: listen address (string)
 - `upstream`: proxy upstream address (string)
 - `enabled`: true/false (defaults to true on creation)

To change a proxy's name, it must be deleted and recreated.

Changing the `listen` or `upstream` fields will restart the proxy and drop any active connections.

If `listen` is specified with a port of 0, toxiproxy will pick an ephemeral port. The `listen` field
in the response will be updated with the actual port.

If you change `enabled` to `false`, it will take down the proxy. You can switch it
back to `true` to reenable it.

#### Toxic fields:

 - `name`: toxic name (string, defaults to `<type>_<stream>`)
 - `type`: toxic type (string)
 - `stream`: link direction to affect (defaults to `downstream`)
 - `toxicity`: probability of the toxic being applied to a link (defaults to 1.0, 100%)
 - `attributes`: a map of toxic-specific attributes

See [Toxics](#toxics) for toxic-specific attributes.

The `stream` direction must be either `upstream` or `downstream`. `upstream` applies
the toxic on the `client -> server` connection, while `downstream` applies the toxic
on the `server -> client` connection. This can be used to modify requests and responses
separately.

#### Endpoints

All endpoints are JSON.

 - **GET /proxies** - List existing proxies and their toxics
 - **POST /proxies** - Create a new proxy
 - **POST /populate** - Create or replace a list of proxies
 - **GET /proxies/{proxy}** - Show the proxy with all its active toxics
 - **POST /proxies/{proxy}** - Update a proxy's fields
 - **DELETE /proxies/{proxy}** - Delete an existing proxy
 - **GET /proxies/{proxy}/toxics** - List active toxics
 - **POST /proxies/{proxy}/toxics** - Create a new toxic
 - **GET /proxies/{proxy}/toxics/{toxic}** - Get an active toxic's fields
 - **POST /proxies/{proxy}/toxics/{toxic}** - Update an active toxic
 - **DELETE /proxies/{proxy}/toxics/{toxic}** - Remove an active toxic
 - **POST /reset** - Enable all proxies and remove all active toxics
 - **GET /version** - Returns the server version number
 - **GET /metrics** - Returns Prometheus-compatible metrics

#### Populating Proxies

Proxies can be added and configured in bulk using the `/populate` endpoint. This is done by
passing a json array of proxies to toxiproxy. If a proxy with the same name already exists,
it will be compared to the new proxy and replaced if the `upstream` and `listen` address don't match.

A `/populate` call can be included for example at application start to ensure all required proxies
exist. It is safe to make this call several times, since proxies will be untouched as long as their
fields are consistent with the new data.

### CLI Example

```bash
$ toxiproxy-cli create -l localhost:26379 -u localhost:6379 redis
Created new proxy redis
$ toxiproxy-cli list
Listen          Upstream        Name  Enabled Toxics
======================================================================
127.0.0.1:26379 localhost:6379  redis true    None

Hint: inspect toxics with `toxiproxy-client inspect <proxyName>`
```

```bash
$ redis-cli -p 26379
127.0.0.1:26379> SET omg pandas
OK
127.0.0.1:26379> GET omg
"pandas"
```

```bash
$ toxiproxy-cli toxic add -t latency -a latency=1000 redis
Added downstream latency toxic 'latency_downstream' on proxy 'redis'
```

```bash
$ redis-cli -p 26379
127.0.0.1:26379> GET omg
"pandas"
(1.00s)
127.0.0.1:26379> DEL omg
(integer) 1
(1.00s)
```

```bash
$ toxiproxy-cli toxic remove -n latency_downstream redis
Removed toxic 'latency_downstream' on proxy 'redis'
```

```bash
$ redis-cli -p 26379
127.0.0.1:26379> GET omg
(nil)
```

```bash
$ toxiproxy-cli delete redis
Deleted proxy redis
```

```bash
$ redis-cli -p 26379
Could not connect to Redis at 127.0.0.1:26379: Connection refused
```

### Metrics

Toxiproxy exposes Prometheus-compatible metrics via its HTTP API at /metrics.
See [METRICS.md](./METRICS.md) for full descriptions

### Frequently Asked Questions

**How fast is Toxiproxy?** The speed of Toxiproxy depends largely on your hardware,
but you can expect a latency of *< 100µs* when no toxics are enabled. When running
with `GOMAXPROCS=4` on a Macbook Pro we achieved *~1000MB/s* throughput, and as high
as *2400MB/s* on a higher end desktop. Basically, you can expect Toxiproxy to move
data around at least as fast the app you're testing.

**Can Toxiproxy do randomized testing?** Many of the available toxics can be configured
to have randomness, such as `jitter` in the `latency` toxic. There is also a
global `toxicity` parameter that specifies the percentage of connections a toxic
will affect. This is most useful for things like the `timeout` toxic, which would
allow X% of connections to timeout.

**I am not seeing my Toxiproxy actions reflected for MySQL**. MySQL will prefer
the local Unix domain socket for some clients, no matter which port you pass it
if the host is set to `localhost`. Configure your MySQL server to not create a
socket, and use `127.0.0.1` as the host. Remember to remove the old socket
after you restart the server.

**Toxiproxy causes intermittent connection failures**. Use ports outside the
ephemeral port range to avoid random port conflicts. It's `32,768` to `61,000` on
Linux by default, see `/proc/sys/net/ipv4/ip_local_port_range`.

**Should I run a Toxiproxy for each application?** No, we recommend using the
same Toxiproxy for all applications. To distinguish between services we
recommend naming your proxies with the scheme: `<app>_<env>_<data store>_<shard>`.
For example, `shopify_test_redis_master` or `shopify_development_mysql_1`.

### Development

* `make`. Build a toxiproxy development binary for the current platform.
* `make all`. Build Toxiproxy binaries and packages for all platforms. Requires
  to have Go compiled with cross compilation enabled on Linux and Darwin (amd64)
  as well as [`goreleaser`](https://goreleaser.com/) in your `$PATH` to
  build binaries the Linux package.
* `make test`. Run the Toxiproxy tests.

### Release

See [RELEASE.md](./RELEASE.md)

[blog]: https://shopify.engineering/building-and-testing-resilient-ruby-on-rails-applications


================================================
FILE: RELEASE.md
================================================
# Releasing

- [Releasing](#releasing)
  - [Before You Begin](#before-you-begin)
  - [Local Release Preparation](#local-release-preparation)
    - [Checkout latest code](#checkout-latest-code)
    - [Update the CHANGELOG.md](#update-the-changelogmd)
    - [Create Release Commit and Tag](#create-release-commit-and-tag)
    - [Run Pre-Release Tests](#run-pre-release-tests)
  - [Push Release Tag](#push-release-tag)
  - [Verify Github Release](#verify-github-release)
  - [Update Homebrew versions](#update-homebrew-versions)

## Before You Begin

Ensure your local workstation is configured to be able to [Sign commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits)

## Local Release Preparation

### Checkout latest code

```shell
git checkout main
git pull origin main
```

### Update the [CHANGELOG.md](CHANGELOG.md)

- Add a new version header at the top of the document, just after `# [Unreleased]`
- Update links at bottom of changelog

### Create Release Commit and Tag

```shell
export RELEASE_VERSION=2.x.y
git commit -a -S -m "Release $RELEASE_VERSION"
git tag -s "v$RELEASE_VERSION" # When prompted for a commit message, enter the 'release notes' style message, just like on the releases page
```

### Run Pre-Release Tests

```shell
make test-release
```

- Push to Main Branch
```shell
git push origin main --follow-tags
```

## Push Release Tag

- On your local machine again, push your tag to github

```shell
git push origin "v$RELEASE_VERSION"
```

## Verify Github Release

- Github Actions should kick off a build and release after the tag is pushed.
- Verify that a [Release gets created in Github](https://github.com/Shopify/toxiproxy/releases) and verify that the release notes look correct
- Github Actions should also attatch the built binaries to the release (it might take a few mins)

## Update Homebrew versions

- Update [homebrew-shopify toxiproxy.rb](https://github.com/Shopify/homebrew-shopify/blob/master/toxiproxy.rb#L9) manifest
  1. Update `app_version` string to your released version
  2. Update hashes for all platforms (find the hashes in the checksums.txt from your release notes)

- Do a manual check of installing toxiproxy via brew
  1. While in the homebrew-shopify directory...
  ```shell
  brew install ./toxiproxy.rb --debug
  ```
  Note: it's normal to get some errors when homebrew attempts to load the file as a Cask instead of a formula, just make sure that it still gets installed.
- PR the version update change and merge


================================================
FILE: _examples/tests/README.md
================================================
## Tests with Toxiproxy

### Setup

```shell
$ brew install shopify/shopify/toxiproxy kind
$ kind create cluster --config=cluster.yml
$ kubectl --context kind-kind apply -f resources.yml
$ kubectl wait deploy postgres --for condition=available --timeout=5m
$ psql -h 127.0.0.1 -U postgres -c "DROP DATABASE IF EXISTS sample"
$ psql -h 127.0.0.1 -U postgres -c "CREATE DATABASE sample"
$ psql -h 127.0.0.1 -U postgres -c "CREATE DATABASE sample_test"
```

### Run

```shell
$ go run ./
```

### Test

```shell
$ go test -v .
$ go test -v . -run TestMultipleToxics
```


================================================
FILE: _examples/tests/cluster.yml
================================================
---
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  ipFamily: ipv4
nodes:
  - role: control-plane
    extraPortMappings:
      # port forward 5432 on the host to 5432 on this node
      - containerPort: 30950
        hostPort: 5432
        # optional: set the bind address on the host
        # 0.0.0.0 is the current default
        listenAddress: 127.0.0.1
        # optional: set the protocol to one of TCP, UDP, SCTP.
        # TCP is the default
        protocol: TCP
...


================================================
FILE: _examples/tests/db.go
================================================
package main

import (
	pg "github.com/go-pg/pg/v10"
	"github.com/go-pg/pg/v10/orm"
)

func setupDB(addr, database string) (*pg.DB, error) {
	db := pg.Connect(&pg.Options{
		Addr:     addr,
		User:     "postgres",
		Database: database,
	})

	err := createSchema(db)
	if err != nil {
		return nil, err
	}

	err = seed(db)
	if err != nil {
		return nil, err
	}

	return db, nil
}

func createSchema(db *pg.DB) error {
	models := []interface{}{
		(*User)(nil),
		(*Story)(nil),
	}

	for _, model := range models {
		err := db.Model(model).CreateTable(&orm.CreateTableOptions{
			Temp: true,
		})
		if err != nil {
			return err
		}
	}
	return nil
}

func seed(db *pg.DB) error {
	user1 := &User{
		Name:   "admin",
		Emails: []string{"admin1@admin", "admin2@admin"},
	}
	_, err := db.Model(user1).Insert()
	if err != nil {
		return err
	}

	_, err = db.Model(&User{
		Name:   "root",
		Emails: []string{"root1@root", "root2@root"},
	}).Insert()
	if err != nil {
		return err
	}

	story1 := &Story{
		Title:    "Cool story",
		AuthorId: user1.Id,
	}
	_, err = db.Model(story1).Insert()
	if err != nil {
		return err
	}

	return nil
}


================================================
FILE: _examples/tests/db_test.go
================================================
package main

import (
	"fmt"
	"log"
	"net"
	"testing"
	"time"

	toxiServer "github.com/Shopify/toxiproxy/v2"
	toxiproxy "github.com/Shopify/toxiproxy/v2/client"
	pg "github.com/go-pg/pg/v10"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/rs/zerolog"
)

var db *pg.DB
var toxi *toxiproxy.Client
var proxies map[string]*toxiproxy.Proxy

func DB() *pg.DB {
	if db == nil {
		var err error
		db, err = setupDB(":35432", "sample_test")
		if err != nil {
			log.Panicf("Could not connect to DB: %+v", err)
		}
	}
	return db
}

func connectDB(addr string) *pg.DB {
	return pg.Connect(&pg.Options{
		Addr:     addr,
		User:     "postgres",
		Database: "sample_test",
	})
}

func init() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	fmt.Println("=== SETUP")
	runToxiproxyServer()
	populateProxies()
}

func populateProxies() {
	if toxi == nil {
		toxi = toxiproxy.NewClient("localhost:8474")
	}

	var err error
	_, err = toxi.Populate([]toxiproxy.Proxy{{
		Name:     "postgresql",
		Listen:   "localhost:35432",
		Upstream: "localhost:5432",
		Enabled:  true,
	}})
	if err != nil {
		panic(err)
	}

	proxies, err = toxi.Proxies()
	if err != nil {
		panic(err)
	}
}

func runToxiproxyServer() {
	var err error
	timeout := 5 * time.Second

	// Check if there is instance run
	conn, err := net.DialTimeout("tcp", "localhost:8474", timeout)
	if err == nil {
		conn.Close()
		return
	}

	go func() {
		metricsContainer := toxiServer.NewMetricsContainer(prometheus.NewRegistry())
		server := toxiServer.NewServer(metricsContainer, zerolog.Nop())
		server.Listen("localhost:8474")
	}()

	for i := 0; i < 10; i += 1 {
		conn, err := net.DialTimeout("tcp", "localhost:8474", timeout)
		if err == nil {
			conn.Close()
			return
		}
	}
	panic(err)
}

func TestSlowDBConnection(t *testing.T) {
	db := DB()

	// Add 1s latency to 100% of downstream connections
	proxies["postgresql"].AddToxic("latency_down", "latency", "downstream", 1.0, toxiproxy.Attributes{
		"latency": 10000,
	})
	defer proxies["postgresql"].RemoveToxic("latency_down")

	err := process(db)
	if err != nil {
		t.Fatalf("got error %v, wanted no errors", err)
	}
}

func TestOutageResetPeer(t *testing.T) {
	db := DB()

	// Add broken TCP connection
	proxies["postgresql"].AddToxic("reset_peer_down", "reset_peer", "downstream", 1.0, toxiproxy.Attributes{
		"timeout": 10,
	})
	defer proxies["postgresql"].RemoveToxic("reset_peer_down")

	err := process(db)
	if err == nil {
		t.Fatalf("expect error")
	}
}


================================================
FILE: _examples/tests/main.go
================================================
package main

import (
	"context"
	"log"
	"os"

	pg "github.com/go-pg/pg/v10"
)

func main() {
	err := run()
	if err != nil {
		log.Printf("ERROR: %v", err)
		os.Exit(1)
	}
}

func run() error {
	db, err := setupDB(":5432", "sample")
	if err != nil {
		return err
	}
	defer db.Close()

	ctx := context.Background()
	if err := db.Ping(ctx); err != nil {
		return err
	}

	process(db)

	return nil
}

func process(db *pg.DB) error {
	var users []User
	err := db.Model(&users).Select()
	if err != nil {
		return err
	}

	for _, user := range users {
		log.Printf("user: %v", user)
	}

	// Select story and associated author in one query.
	story := new(Story)
	err = db.Model(story).
		Relation("Author").
		Where("story.id = ?", 1).
		Select()
	if err != nil {
		return err
	}

	log.Printf("story: %v", story)

	return nil
}


================================================
FILE: _examples/tests/models.go
================================================
package main

import "fmt"

type User struct {
	Id     int64
	Name   string
	Emails []string
}

func (u User) String() string {
	return fmt.Sprintf("User<%d %s %v>", u.Id, u.Name, u.Emails)
}

type Story struct {
	Id       int64
	Title    string
	AuthorId int64
	Author   *User `pg:"rel:has-one"`
}

func (s Story) String() string {
	return fmt.Sprintf("Story<%d %s %s>", s.Id, s.Title, s.Author)
}


================================================
FILE: _examples/tests/resources.yml
================================================
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: postgres
  labels:
    app: postgres
data:
  POSTGRES_DB: postgres
  POSTGRES_USER: postgres
  POSTGRES_PASSWORD: Welcome
  POSTGRES_HOST_AUTH_METHOD: trust
...
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
  labels:
    app: postgres
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 5432
          envFrom:
            - configMapRef:
                name: postgres
...
---
apiVersion: v1
kind: Service
metadata:
  name: postgres
  labels:
    app: postgres
spec:
  type: NodePort
  ports:
    - port: 5432
      nodePort: 30950
  selector:
    app: postgres
...


================================================
FILE: _examples/toxics/README.md
================================================
## Create Toxic

Example how to start building own toxics.

### Debug toxic

Run custom toxiserver with DebugToxic.

```shell
$ go run debug_toxic.go
```

Run redis-server in separate terminal:

```shell
$ redis-server
```

Test toxic with:

```shell
$ toxiproxy-cli --host "http://localhost:8484" create -l :16379 -u localhost:6379 redis
$ toxiproxy-cli --host "http://localhost:8484" toxic add --type debug redis
$ redis-cli -p 16379 "keys" "*"
```

Custom Toxiproxy should print bytes in hex format.

### HTTP toxic

Run custom toxiserver with DebugToxic.

```shell
$ go run http_toxic.go
```

Test toxic with command and verify output:

```shell
$ toxiproxy-cli --host "http://localhost:8484" create -l :18080 -u example.com:80 example
$ toxiproxy-cli --host "http://localhost:8484" toxic add --type http example
$ curl -v localhost:18080/hello
...
< HTTP/1.1 404 Not Found
< Location: https://github.com/Shopify/toxiproxy
```


================================================
FILE: _examples/toxics/debug_toxic.go
================================================
// Ported from https://github.com/xthexder/toxic-example/blob/master/noop.go

package main

import (
	"os"
	"log"
	"fmt"
	"io"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/rs/zerolog"

	"github.com/Shopify/toxiproxy/v2"
	"github.com/Shopify/toxiproxy/v2/toxics"
	"github.com/Shopify/toxiproxy/v2/stream"
)

// DebugToxic prints bytes processed through pipe.
type DebugToxic struct{}

func (t *DebugToxic) PrintHex(data []byte) {
	for i := 0; i < len(data); {
		for j := 0; j < 4; j +=1 {
			x := i + 8
			if x >= len(data) {
				x = len(data) - 1
				fmt.Printf("% x\n", data[i:x])
				return
			}
			fmt.Printf("% x\t\t", data[i:x])
			i = x
		}
		fmt.Println()
	}
}

func (t *DebugToxic) Pipe(stub *toxics.ToxicStub) {
	buf := make([]byte, 32*1024)
	writer := stream.NewChanWriter(stub.Output)
	reader := stream.NewChanReader(stub.Input)
	reader.SetInterrupt(stub.Interrupt)
	for {
			n, err := reader.Read(buf)
			log.Printf("-- [DebugToxic] Processed %d bytes\n", n)
			if err == stream.ErrInterrupted {
					writer.Write(buf[:n])
					return
			} else if err == io.EOF {
					stub.Close()
					return
			}
			t.PrintHex(buf[:n])
			writer.Write(buf[:n])
	}

}

func main() {
	toxics.Register("debug", new(DebugToxic))

	logger := zerolog.New(os.Stderr).With().Caller().Timestamp().Logger()
	metrics := toxiproxy.NewMetricsContainer(prometheus.NewRegistry())
	server := toxiproxy.NewServer(metrics, logger)
	server.Listen("0.0.0.0:8484")
}


================================================
FILE: _examples/toxics/http_toxic.go
================================================
// Ported from https://github.com/xthexder/toxic-example/blob/master/http.go
package main

import (
	"bufio"
	"bytes"
	"io"
	"net/http"
	"os"


	"github.com/prometheus/client_golang/prometheus"
	"github.com/rs/zerolog"


	"github.com/Shopify/toxiproxy/v2"
	"github.com/Shopify/toxiproxy/v2/stream"
	"github.com/Shopify/toxiproxy/v2/toxics"
)

type HttpToxic struct{}

func (t *HttpToxic) ModifyResponse(resp *http.Response) {
	resp.Header.Set("Location", "https://github.com/Shopify/toxiproxy")
}

func (t *HttpToxic) Pipe(stub *toxics.ToxicStub) {
	buffer := bytes.NewBuffer(make([]byte, 0, 32*1024))
	writer := stream.NewChanWriter(stub.Output)
	reader := stream.NewChanReader(stub.Input)
	reader.SetInterrupt(stub.Interrupt)
	for {
		tee := io.TeeReader(reader, buffer)
		resp, err := http.ReadResponse(bufio.NewReader(tee), nil)
		if err == stream.ErrInterrupted {
			buffer.WriteTo(writer)
			return
		} else if err == io.EOF {
			stub.Close()
			return
		}
		if err != nil {
			buffer.WriteTo(writer)
		} else {
			t.ModifyResponse(resp)
			resp.Write(writer)
		}
		buffer.Reset()
	}
}

func main() {
	toxics.Register("http", new(HttpToxic))

	logger := zerolog.New(os.Stderr).With().Caller().Timestamp().Logger()
	metrics := toxiproxy.NewMetricsContainer(prometheus.NewRegistry())
	server := toxiproxy.NewServer(metrics, logger)
	server.Listen("0.0.0.0:8484")
}


================================================
FILE: api.go
================================================
package toxiproxy

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/gorilla/mux"
	"github.com/rs/zerolog"
	"github.com/rs/zerolog/hlog"

	"github.com/Shopify/toxiproxy/v2/toxics"
)

func stopBrowsersMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if strings.HasPrefix(r.UserAgent(), "Mozilla/") {
			http.Error(w, "User agent not allowed", http.StatusForbidden)
		} else {
			next.ServeHTTP(w, r)
		}
	})
}

func timeoutMiddleware(next http.Handler) http.Handler {
	return http.TimeoutHandler(next, 25*time.Second, "")
}

type ApiServer struct {
	Collection *ProxyCollection
	Metrics    *metricsContainer
	Logger     *zerolog.Logger
	http       *http.Server
}

const (
	wait_timeout = 30 * time.Second
	read_timeout = 15 * time.Second
)

func NewServer(m *metricsContainer, logger zerolog.Logger) *ApiServer {
	return &ApiServer{
		Collection: NewProxyCollection(),
		Metrics:    m,
		Logger:     &logger,
	}
}

func (server *ApiServer) Listen(addr string) error {
	server.Logger.
		Info().
		Str("address", addr).
		Msg("Starting Toxiproxy HTTP server")

	server.http = &http.Server{
		Addr:         addr,
		Handler:      server.Routes(),
		WriteTimeout: wait_timeout,
		ReadTimeout:  read_timeout,
		IdleTimeout:  60 * time.Second,
	}

	err := server.http.ListenAndServe()
	if err == http.ErrServerClosed {
		err = nil
	}

	return err
}

func (server *ApiServer) Shutdown() error {
	if server.http == nil {
		return nil
	}

	ctx, cancel := context.WithTimeout(context.Background(), wait_timeout)
	defer cancel()

	err := server.http.Shutdown(ctx)
	if err != nil {
		return err
	}

	return nil
}

func (server *ApiServer) Routes() *mux.Router {
	r := mux.NewRouter()
	r.Use(hlog.NewHandler(*server.Logger))
	r.Use(hlog.RequestIDHandler("request_id", "X-Toxiproxy-Request-Id"))
	r.Use(hlog.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) {
		handler := mux.CurrentRoute(r).GetName()
		zerolog.Ctx(r.Context()).
			Debug().
			Str("client", r.RemoteAddr).
			Str("method", r.Method).
			Stringer("url", r.URL).
			Str("user_agent", r.Header.Get("User-Agent")).
			Int("status", status).
			Int("size", size).
			Dur("duration", duration).
			Str("handler", handler).
			Msg("")
	}))
	r.Use(stopBrowsersMiddleware)
	r.Use(timeoutMiddleware)

	r.HandleFunc("/reset", server.ResetState).Methods("POST").
		Name("ResetState")
	r.HandleFunc("/proxies", server.ProxyIndex).Methods("GET").
		Name("ProxyIndex")
	r.HandleFunc("/proxies", server.ProxyCreate).Methods("POST").
		Name("ProxyCreate")
	r.HandleFunc("/populate", server.Populate).Methods("POST").
		Name("Populate")
	r.HandleFunc("/proxies/{proxy}", server.ProxyShow).Methods("GET").
		Name("ProxyShow")
	r.HandleFunc("/proxies/{proxy}", server.ProxyUpdate).Methods("POST", "PATCH").
		Name("ProxyUpdate")
	r.HandleFunc("/proxies/{proxy}", server.ProxyDelete).Methods("DELETE").
		Name("ProxyDelete")
	r.HandleFunc("/proxies/{proxy}/toxics", server.ToxicIndex).Methods("GET").
		Name("ToxicIndex")
	r.HandleFunc("/proxies/{proxy}/toxics", server.ToxicCreate).Methods("POST").
		Name("ToxicCreate")
	r.HandleFunc("/proxies/{proxy}/toxics/{toxic}", server.ToxicShow).Methods("GET").
		Name("ToxicShow")
	r.HandleFunc("/proxies/{proxy}/toxics/{toxic}", server.ToxicUpdate).Methods("POST", "PATCH").
		Name("ToxicUpdate")
	r.HandleFunc("/proxies/{proxy}/toxics/{toxic}", server.ToxicDelete).Methods("DELETE").
		Name("ToxicDelete")

	r.HandleFunc("/version", server.Version).Methods("GET").Name("Version")

	if server.Metrics.anyMetricsEnabled() {
		r.Handle("/metrics", server.Metrics.handler()).Name("Metrics")
	}

	return r
}

func (server *ApiServer) PopulateConfig(filename string) {
	file, err := os.Open(filename)
	logger := server.Logger
	if err != nil {
		logger.Err(err).Str("config", filename).Msg("Error reading config file")
		return
	}

	proxies, err := server.Collection.PopulateJson(server, file)
	if err != nil {
		logger.Err(err).Msg("Failed to populate proxies from file")
	} else {
		logger.Info().Int("proxies", len(proxies)).Msg("Populated proxies from file")
	}
}

func (server *ApiServer) ProxyIndex(response http.ResponseWriter, request *http.Request) {
	proxies := server.Collection.Proxies()
	marshalData := make(map[string]interface{}, len(proxies))

	for name, proxy := range proxies {
		marshalData[name] = proxyWithToxics(proxy)
	}

	data, err := json.Marshal(marshalData)
	if server.apiError(response, err) {
		return
	}

	response.Header().Set("Content-Type", "application/json")
	_, err = response.Write(data)
	if err != nil {
		log := zerolog.Ctx(request.Context())
		log.Warn().Err(err).Msg("ProxyIndex: Failed to write response to client")
	}
}

func (server *ApiServer) ResetState(response http.ResponseWriter, request *http.Request) {
	ctx := request.Context()
	proxies := server.Collection.Proxies()

	for _, proxy := range proxies {
		err := proxy.Start()
		if err != ErrProxyAlreadyStarted && server.apiError(response, err) {
			return
		}

		proxy.Toxics.ResetToxics(ctx)
	}

	response.WriteHeader(http.StatusNoContent)
	_, err := response.Write(nil)
	if err != nil {
		log := zerolog.Ctx(ctx)
		log.Warn().Err(err).Msg("ResetState: Failed to write headers to client")
	}
}

func (server *ApiServer) ProxyCreate(response http.ResponseWriter, request *http.Request) {
	// Default fields to enable the proxy right away
	input := Proxy{Enabled: true}
	err := json.NewDecoder(request.Body).Decode(&input)
	if server.apiError(response, joinError(err, ErrBadRequestBody)) {
		return
	}

	if len(input.Name) < 1 {
		server.apiError(response, joinError(fmt.Errorf("name"), ErrMissingField))
		return
	}
	if len(input.Upstream) < 1 {
		server.apiError(response, joinError(fmt.Errorf("upstream"), ErrMissingField))
		return
	}

	proxy := NewProxy(server, input.Name, input.Listen, input.Upstream)

	err = server.Collection.Add(proxy, input.Enabled)
	if server.apiError(response, err) {
		return
	}

	data, err := json.Marshal(proxyWithToxics(proxy))
	if server.apiError(response, err) {
		return
	}

	response.Header().Set("Content-Type", "application/json")
	response.WriteHeader(http.StatusCreated)
	_, err = response.Write(data)
	if err != nil {
		log := zerolog.Ctx(request.Context())
		log.Warn().Err(err).Msg("ProxyCreate: Failed to write response to client")
	}
}

func (server *ApiServer) Populate(response http.ResponseWriter, request *http.Request) {
	proxies, err := server.Collection.PopulateJson(server, request.Body)
	log := zerolog.Ctx(request.Context())
	if err != nil {
		log.Warn().Err(err).Msg("Populate errors")
	}

	apiErr, ok := err.(*ApiError)
	if !ok && err != nil {
		log.Warn().Err(err).Msg("Error did not include status code")
		apiErr = &ApiError{err.Error(), http.StatusInternalServerError}
	}

	data, err := json.Marshal(struct {
		*ApiError `json:",omitempty"`
		Proxies   []proxyToxics `json:"proxies"`
	}{apiErr, proxiesWithToxics(proxies)})
	if server.apiError(response, err) {
		return
	}

	responseCode := http.StatusCreated
	if apiErr != nil {
		responseCode = apiErr.StatusCode
	}

	response.Header().Set("Content-Type", "application/json")
	response.WriteHeader(responseCode)
	_, err = response.Write(data)
	if err != nil {
		log.Warn().Err(err).Msg("Populate: Failed to write response to client")
	}
}

func (server *ApiServer) ProxyShow(response http.ResponseWriter, request *http.Request) {
	vars := mux.Vars(request)

	proxy, err := server.Collection.Get(vars["proxy"])
	if server.apiError(response, err) {
		return
	}

	data, err := json.Marshal(proxyWithToxics(proxy))
	if server.apiError(response, err) {
		return
	}

	response.Header().Set("Content-Type", "application/json")
	_, err = response.Write(data)
	if err != nil {
		server.Logger.Warn().Err(err).Msg("ProxyShow: Failed to write response to client")
	}
}

func (server *ApiServer) ProxyUpdate(response http.ResponseWriter, request *http.Request) {
	log := zerolog.Ctx(request.Context())
	if request.Method == "POST" {
		log.Warn().Msg("ProxyUpdate: HTTP method POST is deprecated. Use HTTP PATCH instead.")
	}

	vars := mux.Vars(request)

	proxy, err := server.Collection.Get(vars["proxy"])
	if server.apiError(response, err) {
		return
	}

	// Default fields are the same as existing proxy
	input := Proxy{Listen: proxy.Listen, Upstream: proxy.Upstream, Enabled: proxy.Enabled}
	err = json.NewDecoder(request.Body).Decode(&input)
	if server.apiError(response, joinError(err, ErrBadRequestBody)) {
		return
	}

	err = proxy.Update(&input)
	if server.apiError(response, err) {
		return
	}

	data, err := json.Marshal(proxyWithToxics(proxy))
	if server.apiError(response, err) {
		return
	}

	response.Header().Set("Content-Type", "application/json")
	_, err = response.Write(data)
	if err != nil {
		log.Warn().Err(err).Msg("ProxyUpdate: Failed to write response to client")
	}
}

func (server *ApiServer) ProxyDelete(response http.ResponseWriter, request *http.Request) {
	vars := mux.Vars(request)

	err := server.Collection.Remove(vars["proxy"])
	if server.apiError(response, err) {
		return
	}

	response.WriteHeader(http.StatusNoContent)
	_, err = response.Write(nil)
	if err != nil {
		log := zerolog.Ctx(request.Context())
		log.Warn().Err(err).Msg("ProxyDelete: Failed to write headers to client")
	}
}

func (server *ApiServer) ToxicIndex(response http.ResponseWriter, request *http.Request) {
	vars := mux.Vars(request)

	proxy, err := server.Collection.Get(vars["proxy"])
	if server.apiError(response, err) {
		return
	}

	toxics := proxy.Toxics.GetToxicArray()
	data, err := json.Marshal(toxics)
	if server.apiError(response, err) {
		return
	}

	response.Header().Set("Content-Type", "application/json")
	_, err = response.Write(data)
	if err != nil {
		log := zerolog.Ctx(request.Context())
		log.Warn().Err(err).Msg("ToxicIndex: Failed to write response to client")
	}
}

func (server *ApiServer) ToxicCreate(response http.ResponseWriter, request *http.Request) {
	vars := mux.Vars(request)

	proxy, err := server.Collection.Get(vars["proxy"])
	if server.apiError(response, err) {
		return
	}

	toxic, err := proxy.Toxics.AddToxicJson(request.Body)
	if server.apiError(response, err) {
		return
	}

	data, err := json.Marshal(toxic)
	if server.apiError(response, err) {
		return
	}

	response.Header().Set("Content-Type", "application/json")
	_, err = response.Write(data)
	if err != nil {
		log := zerolog.Ctx(request.Context())
		log.Warn().Err(err).Msg("ToxicCreate: Failed to write response to client")
	}
}

func (server *ApiServer) ToxicShow(response http.ResponseWriter, request *http.Request) {
	vars := mux.Vars(request)

	proxy, err := server.Collection.Get(vars["proxy"])
	if server.apiError(response, err) {
		return
	}

	toxic := proxy.Toxics.GetToxic(vars["toxic"])
	if toxic == nil {
		server.apiError(response, ErrToxicNotFound)
		return
	}

	data, err := json.Marshal(toxic)
	if server.apiError(response, err) {
		return
	}

	response.Header().Set("Content-Type", "application/json")
	_, err = response.Write(data)
	if err != nil {
		log := zerolog.Ctx(request.Context())
		log.Warn().Err(err).Msg("ToxicShow: Failed to write response to client")
	}
}

func (server *ApiServer) ToxicUpdate(response http.ResponseWriter, request *http.Request) {
	log := zerolog.Ctx(request.Context())
	if request.Method == "POST" {
		log.Warn().Msg("ToxicUpdate: HTTP method POST is deprecated. Use HTTP PATCH instead.")
	}

	vars := mux.Vars(request)

	proxy, err := server.Collection.Get(vars["proxy"])
	if server.apiError(response, err) {
		return
	}

	toxic, err := proxy.Toxics.UpdateToxicJson(vars["toxic"], request.Body)
	if server.apiError(response, err) {
		return
	}

	data, err := json.Marshal(toxic)
	if server.apiError(response, err) {
		return
	}

	response.Header().Set("Content-Type", "application/json")
	_, err = response.Write(data)
	if err != nil {
		log.Warn().Err(err).Msg("ToxicUpdate: Failed to write response to client")
	}
}

func (server *ApiServer) ToxicDelete(response http.ResponseWriter, request *http.Request) {
	vars := mux.Vars(request)
	ctx := request.Context()
	log := zerolog.Ctx(ctx)

	proxy, err := server.Collection.Get(vars["proxy"])
	if server.apiError(response, err) {
		return
	}

	err = proxy.Toxics.RemoveToxic(ctx, vars["toxic"])
	if server.apiError(response, err) {
		return
	}

	response.WriteHeader(http.StatusNoContent)
	_, err = response.Write(nil)
	if err != nil {
		log.Warn().Err(err).Msg("ToxicDelete: Failed to write headers to client")
	}
}

func (server *ApiServer) Version(response http.ResponseWriter, request *http.Request) {
	log := zerolog.Ctx(request.Context())

	response.Header().Set("Content-Type", "application/json;charset=utf-8")
	version := fmt.Sprintf("{\"version\": \"%s\"}\n", Version)
	_, err := response.Write([]byte(version))
	if err != nil {
		log.Warn().Err(err).Msg("Version: Failed to write response to client")
	}
}

type ApiError struct {
	Message    string `json:"error"`
	StatusCode int    `json:"status"`
}

func (e *ApiError) Error() string {
	return e.Message
}

func newError(msg string, status int) *ApiError {
	return &ApiError{msg, status}
}

func joinError(err error, wrapper *ApiError) *ApiError {
	if err != nil {
		return &ApiError{wrapper.Message + ": " + err.Error(), wrapper.StatusCode}
	}
	return nil
}

var (
	ErrBadRequestBody     = newError("bad request body", http.StatusBadRequest)
	ErrMissingField       = newError("missing required field", http.StatusBadRequest)
	ErrProxyNotFound      = newError("proxy not found", http.StatusNotFound)
	ErrProxyAlreadyExists = newError("proxy already exists", http.StatusConflict)
	ErrInvalidStream      = newError(
		"stream was invalid, can be either upstream or downstream",
		http.StatusBadRequest,
	)
	ErrInvalidToxicType   = newError("invalid toxic type", http.StatusBadRequest)
	ErrToxicAlreadyExists = newError("toxic already exists", http.StatusConflict)
	ErrToxicNotFound      = newError("toxic not found", http.StatusNotFound)
)

func (server *ApiServer) apiError(resp http.ResponseWriter, err error) bool {
	obj, ok := err.(*ApiError)
	if !ok && err != nil {
		server.Logger.Warn().Err(err).Msg("Error did not include status code")
		obj = &ApiError{err.Error(), http.StatusInternalServerError}
	}

	if obj == nil {
		return false
	}

	data, err2 := json.Marshal(obj)
	if err2 != nil {
		server.Logger.Warn().Err(err2).Msg("Error json encoding error (╯°□°)╯︵ ┻━┻ ")
	}
	resp.Header().Set("Content-Type", "application/json")
	http.Error(resp, string(data), obj.StatusCode)

	return true
}

type proxyToxics struct {
	*Proxy
	Toxics []toxics.Toxic `json:"toxics"`
}

func proxyWithToxics(proxy *Proxy) (result proxyToxics) {
	result.Proxy = proxy
	result.Toxics = proxy.Toxics.GetToxicArray()
	return
}

func proxiesWithToxics(proxies []*Proxy) (result []proxyToxics) {
	for _, proxy := range proxies {
		result = append(result, proxyWithToxics(proxy))
	}
	return
}


================================================
FILE: api_test.go
================================================
package toxiproxy_test

import (
	"bytes"
	"flag"
	"io"
	"net/http"
	"os"
	"testing"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/rs/zerolog"

	"github.com/Shopify/toxiproxy/v2"
	tclient "github.com/Shopify/toxiproxy/v2/client"
)

var testServer *toxiproxy.ApiServer

var client = tclient.NewClient("http://127.0.0.1:8475")

func WithServer(t *testing.T, f func(string)) {
	log := zerolog.Nop()
	if flag.Lookup("test.v").DefValue == "true" {
		log = zerolog.New(os.Stdout).With().Caller().Timestamp().Logger()
	}

	// Make sure only one server is running at a time. Apparently there's no clean
	// way to shut it down between each test run.
	if testServer == nil {
		testServer = toxiproxy.NewServer(
			toxiproxy.NewMetricsContainer(prometheus.NewRegistry()),
			log,
		)

		go testServer.Listen("localhost:8475")

		// Allow server to start. There's no clean way to know when it listens.
		time.Sleep(50 * time.Millisecond)
	}

	defer func() {
		err := testServer.Collection.Clear()
		if err != nil {
			t.Error("Failed to clear collection", err)
		}
	}()

	f("http://localhost:8475")
}

func TestRequestId(t *testing.T) {
	WithServer(t, func(addr string) {
		client := http.Client{}

		req, _ := http.NewRequest("GET", "http://localhost:8475/version", nil)
		req.Header.Add("User-Agent", "curl")

		resp, err := client.Do(req)
		if err != nil {
			t.Fatalf("Does not expect errors from client: %+v", err)
		}
		defer resp.Body.Close()

		if _, ok := resp.Header["X-Toxiproxy-Request-Id"]; !ok {
			t.Fatalf("Expect http response with header X-Toxiproxy-Request-Id, got %+v", resp.Header)
		}
	})
}

func TestBrowserGets403(t *testing.T) {
	WithServer(t, func(addr string) {
		client := http.Client{}

		req, _ := http.NewRequest("GET", "http://localhost:8475/proxies", nil)
		req.Header.Add(
			"User-Agent",
			"Mozilla/5.0 (Linux; Android 4.4.2); Nexus 5 Build/KOT49H) AppleWebKit/537.36"+
				"(KHTML, like Gecko) Chrome/33.0.1750.117 Mobile Safari/537.36 OPR/20.0.1396.72047",
		)

		resp, err := client.Do(req)
		if err != nil {
			t.Fatalf("Does not expect errors from client: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != 403 {
			t.Fatal("Browser-like UserAgent was not denied access to Toxiproxy")
		}
	})
}

func TestNonBrowserGets200(t *testing.T) {
	WithServer(t, func(addr string) {
		client := http.Client{}

		req, _ := http.NewRequest("GET", "http://localhost:8475/proxies", nil)
		req.Header.Add("User-Agent", "Wget/2.1")

		resp, err := client.Do(req)
		if err != nil {
			t.Fatalf("Does not expect errors from client: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode == 403 {
			t.Fatal("Non-Browser-like UserAgent was denied access to Toxiproxy")
		}
	})
}

func TestIndexWithNoProxies(t *testing.T) {
	WithServer(t, func(addr string) {
		client := tclient.NewClient(addr)
		proxies, err := client.Proxies()
		if err != nil {
			t.Fatal("Failed getting proxies:", err)
		}

		if len(proxies) > 0 {
			t.Fatal("Expected no proxies, got:", proxies)
		}
	})
}

func TestCreateProxyBlankName(t *testing.T) {
	WithServer(t, func(addr string) {
		_, err := client.CreateProxy("", "", "")

		expected := "create: HTTP 400: missing required field: name"
		if err == nil {
			t.Error("Expected error creating proxy, got nil")
		} else if err.Error() != expected {
			t.Errorf("Expected error `%s',\n\tgot: `%s'", expected, err)
		}
	})
}

func TestCreateProxyBlankUpstream(t *testing.T) {
	WithServer(t, func(addr string) {
		_, err := client.CreateProxy("test", "", "")
		if err == nil {
			t.Error("Expected error creating proxy, got nil")
		} else if err.Error() != "create: HTTP 400: missing required field: upstream" {
			t.Error("Expected different error creating proxy:", err)
		}
	})
}

func TestPopulateProxy(t *testing.T) {
	WithServer(t, func(addr string) {
		testProxies, err := client.Populate([]tclient.Proxy{
			{
				Name:     "one",
				Listen:   "localhost:7070",
				Upstream: "localhost:7171",
				Enabled:  true,
			},
			{
				Name:     "two",
				Listen:   "localhost:7373",
				Upstream: "localhost:7474",
				Enabled:  true,
			},
		})

		if err != nil {
			t.Fatal("Unable to populate:", err)
		}

		if len(testProxies) != 2 {
			t.Fatalf("Wrong number of proxies returned: %d != 2", len(testProxies))
		}

		if testProxies[0].Name != "one" || testProxies[1].Name != "two" {
			t.Fatalf("Wrong proxy names returned: %s, %s", testProxies[0].Name, testProxies[1].Name)
		}

		for _, p := range testProxies {
			AssertProxyUp(t, p.Listen, true)
		}
	})
}

func TestPopulateDefaultEnabled(t *testing.T) {
	WithServer(t, func(addr string) {
		request := []byte(
			`[{"name": "test", "listen": "localhost:7070", "upstream": "localhost:7171"}]`,
		)

		resp, err := http.Post(addr+"/populate", "application/json", bytes.NewReader(request))
		if err != nil {
			t.Fatal("Failed to send POST to /populate:", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusCreated {
			message, _ := io.ReadAll(resp.Body)
			t.Fatalf("Failed to populate proxy list: HTTP %s\n%s", resp.Status, string(message))
		}

		proxies, err := client.Proxies()
		if err != nil {
			t.Fatal(err)
		} else if len(proxies) != 1 {
			t.Fatalf("Wrong number of proxies created: %d != 1", len(proxies))
		} else if _, ok := proxies["test"]; !ok {
			t.Fatalf("Wrong proxy name returned")
		}

		for _, p := range proxies {
			AssertProxyUp(t, p.Listen, true)
		}
	})
}

func TestPopulateDisabledProxy(t *testing.T) {
	WithServer(t, func(addr string) {
		testProxies, err := client.Populate([]tclient.Proxy{
			{
				Name:     "one",
				Listen:   "localhost:7070",
				Upstream: "localhost:7171",
				Enabled:  false,
			},
			{
				Name:     "two",
				Listen:   "localhost:7373",
				Upstream: "localhost:7474",
				Enabled:  true,
			},
		})

		if err != nil {
			t.Fatal("Unable to populate:", err)
		}

		if len(testProxies) != 2 {
			t.Fatalf("Wrong number of proxies returned: %d != 2", len(testProxies))
		}

		if testProxies[0].Name != "one" || testProxies[1].Name != "two" {
			t.Fatalf("Wrong proxy names returned: %s, %s", testProxies[0].Name, testProxies[1].Name)
		}

		AssertProxyUp(t, "localhost:7070", false)
		AssertProxyUp(t, "localhost:7373", true)
	})
}

func TestPopulateExistingProxy(t *testing.T) {
	WithServer(t, func(addr string) {
		testProxy, err := client.CreateProxy("one", "localhost:7070", "localhost:7171")
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}

		_, err = client.CreateProxy("two", "localhost:7373", "localhost:7474")
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}

		// Create a toxic so we can make sure the proxy wasn't replaced
		_, err = testProxy.AddToxic("", "latency", "downstream", 1, nil)
		if err != nil {
			t.Fatal("Unable to create toxic:", err)
		}

		testProxies, err := client.Populate([]tclient.Proxy{
			{
				Name:     "one",
				Listen:   "localhost:7070", // intentional: this should be resolved to 127.0.0.1:7070
				Upstream: "localhost:7171",
				Enabled:  true,
			},
			{
				Name:     "two",
				Listen:   "127.0.0.1:7575",
				Upstream: "localhost:7676",
				Enabled:  true,
			},
		})

		if err != nil {
			t.Fatal("Unable to populate:", err)
		}

		if len(testProxies) != 2 {
			t.Fatalf("Wrong number of proxies returned: %d != 2", len(testProxies))
		}

		if testProxies[0].Name != "one" || testProxies[1].Name != "two" {
			t.Fatalf("Wrong proxy names returned: %s, %s", testProxies[0].Name, testProxies[1].Name)
		}

		if testProxies[0].Listen != "127.0.0.1:7070" ||
			testProxies[1].Listen != "127.0.0.1:7575" {
			t.Fatalf("Wrong proxy listen addresses returned: %s, %s",
				testProxies[0].Listen, testProxies[1].Listen,
			)
		}

		toxics, err := testProxy.Toxics()
		if err != nil {
			t.Fatal("Unable to get toxics:", err)
		}
		if len(toxics) != 1 || toxics[0].Type != "latency" {
			t.Fatalf("Populate did not preseve existing proxy. (%d toxics)", len(toxics))
		}

		for _, p := range testProxies {
			AssertProxyUp(t, p.Listen, true)
		}
	})
}

func TestPopulateWithBadName(t *testing.T) {
	WithServer(t, func(addr string) {
		testProxies, err := client.Populate([]tclient.Proxy{
			{
				Name:     "one",
				Listen:   "localhost:7070",
				Upstream: "localhost:7171",
				Enabled:  true,
			},
			{
				Name:    "",
				Listen:  "",
				Enabled: true,
			},
		})

		if err == nil {
			t.Fatal("Expected Populate to fail.")
		}

		expected := "Populate: HTTP 400: missing required field: name at proxy 2"
		if err.Error() != expected {
			t.Fatalf("Expected error `%s',\n\tgot: `%s'", expected, err)
		}

		if len(testProxies) != 0 {
			t.Fatalf("Wrong number of proxies returned: %d != 0", len(testProxies))
		}

		proxies, err := client.Proxies()
		if err != nil {
			t.Fatal(err)
		} else if len(proxies) != 0 {
			t.Fatalf("Expected no proxies to be created: %d != 0", len(proxies))
		}
	})
}

func TestPopulateProxyWithBadDataShouldReturnError(t *testing.T) {
	WithServer(t, func(addr string) {
		testProxies, err := client.Populate([]tclient.Proxy{
			{
				Name:     "one",
				Listen:   "localhost:7070",
				Upstream: "localhost:7171",
				Enabled:  true,
			},
			{
				Name:     "two",
				Listen:   "local373",
				Upstream: "localhost:7474",
				Enabled:  true,
			},
			{
				Name:     "three",
				Listen:   "localhost:7575",
				Upstream: "localhost:7676",
				Enabled:  true,
			},
		})

		if err == nil {
			t.Fatal("Expected Populate to fail.")
		}

		if len(testProxies) != 0 {
			t.Fatalf("Expected Proxies to be empty, got %v", testProxies)
		}

		proxies, err := client.Proxies()
		if err != nil {
			t.Fatalf("Expected no error, got: %v", err)
		}

		if len(proxies) != 1 {
			t.Fatalf("Wrong number of proxies returned: %d != %d", len(proxies), 1)
		}

		if _, ok := proxies["one"]; !ok {
			t.Fatal("Proxy `one' was not created!")
		}

		for _, p := range testProxies {
			AssertProxyUp(t, p.Listen, true)
		}

		for _, p := range proxies {
			if p.Name == "two" || p.Name == "three" {
				t.Fatalf("Proxy %s exists, populate did not fail correctly.", p.Name)
			}
		}
	})
}

func TestPopulateAddToxic(t *testing.T) {
	WithServer(t, func(addr string) {
		testProxies, err := client.Populate([]tclient.Proxy{
			{
				Name:     "one",
				Listen:   "localhost:7070",
				Upstream: "localhost:7171",
				Enabled:  true,
			},
		})

		if err != nil {
			t.Fatal("Unable to populate:", err)
		}

		if len(testProxies) != 1 {
			t.Fatalf("Wrong number of proxies returned: %d != %d", len(testProxies), 1)
		}

		if testProxies[0].Name != "one" {
			t.Fatalf("Wrong proxy name returned: %s != one", testProxies[0].Name)
		}

		_, err = testProxies[0].AddToxic("", "latency", "downstream", 1, nil)
		if err != nil {
			t.Fatal("Failed to AddToxic.")
		}
	})
}

func TestListingProxies(t *testing.T) {
	WithServer(t, func(addr string) {
		_, err := client.CreateProxy("mysql_master", "localhost:3310", "localhost:20001")
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}

		proxies, err := client.Proxies()
		if err != nil {
			t.Fatal("Error listing proxies:", err)
		}

		if len(proxies) == 0 {
			t.Fatal("Expected new proxy in list")
		}
		proxy, ok := proxies["mysql_master"]
		if !ok {
			t.Fatal("Expected to see mysql_master proxy in list")
		}
		if proxy.Name != "mysql_master" || proxy.Listen != "127.0.0.1:3310" ||
			proxy.Upstream != "localhost:20001" {
			t.Fatalf(
				"Unexpected proxy metadata: %s, %s, %s",
				proxy.Name,
				proxy.Listen,
				proxy.Upstream,
			)
		}
		AssertToxicExists(t, proxy.ActiveToxics, "latency", "", "", false)
	})
}

func TestCreateAndGetProxy(t *testing.T) {
	WithServer(t, func(addr string) {
		_, err := client.CreateProxy("mysql_master", "localhost:3310", "localhost:20001")
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}

		proxy, err := client.Proxy("mysql_master")
		if err != nil {
			t.Fatal("Unable to retriecve proxy:", err)
		}

		if proxy.Name != "mysql_master" || proxy.Listen != "127.0.0.1:3310" ||
			proxy.Upstream != "localhost:20001" ||
			!proxy.Enabled {
			t.Fatalf(
				"Unexpected proxy metadata: %s, %s, %s, %v",
				proxy.Name,
				proxy.Listen,
				proxy.Upstream,
				proxy.Enabled,
			)
		}

		AssertToxicExists(t, proxy.ActiveToxics, "latency", "", "", false)
	})
}

func TestCreateProxyWithSave(t *testing.T) {
	WithServer(t, func(addr string) {
		testProxy := client.NewProxy()
		testProxy.Name = "mysql_master"
		testProxy.Listen = "localhost:3310"
		testProxy.Upstream = "localhost:20001"
		testProxy.Enabled = true

		err := testProxy.Save()
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}

		proxy, err := client.Proxy("mysql_master")
		if err != nil {
			t.Fatal("Unable to retriecve proxy:", err)
		}

		if proxy.Name != "mysql_master" || proxy.Listen != "127.0.0.1:3310" ||
			proxy.Upstream != "localhost:20001" ||
			!proxy.Enabled {
			t.Fatalf(
				"Unexpected proxy metadata: %s, %s, %s, %v",
				proxy.Name,
				proxy.Listen,
				proxy.Upstream,
				proxy.Enabled,
			)
		}

		AssertProxyUp(t, proxy.Listen, true)
	})
}

func TestCreateDisabledProxy(t *testing.T) {
	WithServer(t, func(addr string) {
		disabledProxy := client.NewProxy()
		disabledProxy.Name = "mysql_master"
		disabledProxy.Listen = "localhost:3310"
		disabledProxy.Upstream = "localhost:20001"

		err := disabledProxy.Save()
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}

		proxy, err := client.Proxy("mysql_master")
		if err != nil {
			t.Fatal("Unable to retriecve proxy:", err)
		}

		if proxy.Name != "mysql_master" || proxy.Listen != "localhost:3310" ||
			proxy.Upstream != "localhost:20001" ||
			proxy.Enabled {
			t.Fatalf(
				"Unexpected proxy metadata: %s, %s, %s, %v",
				proxy.Name,
				proxy.Listen,
				proxy.Upstream,
				proxy.Enabled,
			)
		}

		AssertProxyUp(t, proxy.Listen, false)
	})
}

func TestCreateDisabledProxyAndEnable(t *testing.T) {
	WithServer(t, func(addr string) {
		disabledProxy := client.NewProxy()
		disabledProxy.Name = "mysql_master"
		disabledProxy.Listen = "localhost:3310"
		disabledProxy.Upstream = "localhost:20001"

		err := disabledProxy.Save()
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}

		proxy, err := client.Proxy("mysql_master")
		if err != nil {
			t.Fatal("Unable to retriecve proxy:", err)
		}

		if proxy.Name != "mysql_master" || proxy.Listen != "localhost:3310" ||
			proxy.Upstream != "localhost:20001" ||
			proxy.Enabled {
			t.Fatalf(
				"Unexpected proxy metadata: %s, %s, %s, %v",
				proxy.Name,
				proxy.Listen,
				proxy.Upstream,
				proxy.Enabled,
			)
		}

		proxy.Enabled = true

		err = proxy.Save()
		if err != nil {
			t.Fatal("Failed to update proxy:", err)
		}

		AssertProxyUp(t, proxy.Listen, true)

		proxy.Enabled = false

		err = proxy.Save()
		if err != nil {
			t.Fatal("Failed to update proxy:", err)
		}

		AssertProxyUp(t, proxy.Listen, false)
	})
}

func TestDeleteProxy(t *testing.T) {
	WithServer(t, func(addr string) {
		testProxy, err := client.CreateProxy("mysql_master", "localhost:3310", "localhost:20001")
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}

		proxies, err := client.Proxies()
		if err != nil {
			t.Fatal("Error listing proxies:", err)
		}

		if len(proxies) == 0 {
			t.Fatal("Expected new proxy in list")
		}

		AssertProxyUp(t, testProxy.Listen, true)

		err = testProxy.Delete()
		if err != nil {
			t.Fatal("Failed deleting proxy:", err)
		}

		AssertProxyUp(t, testProxy.Listen, false)

		proxies, err = client.Proxies()
		if err != nil {
			t.Fatal("Error listing proxies:", err)
		}

		if len(proxies) > 0 {
			t.Fatal("Expected proxy to be deleted from list")
		}

		expected := "Delete: HTTP 404: proxy not found"
		err = testProxy.Delete()
		if err == nil {
			t.Error("Proxy did not result in not found.")
		} else if err.Error() != expected {
			t.Errorf("Expected error `%s',\n\tgot: `%s'", expected, err)
		}
	})
}

func TestCreateProxyPortConflict(t *testing.T) {
	WithServer(t, func(addr string) {
		testProxy, err := client.CreateProxy("mysql_master", "localhost:3310", "localhost:20001")
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}

		expected := "create: HTTP 500: listen tcp 127.0.0.1:3310: bind: address already in use"
		_, err = client.CreateProxy("test", "localhost:3310", "localhost:20001")
		if err == nil {
			t.Error("Proxy did not result in conflict.")
		} else if err.Error() != expected {
			t.Errorf("Expected error `%s',\n\tgot: `%s'", expected, err)
		}

		err = testProxy.Delete()
		if err != nil {
			t.Fatal("Unable to delete proxy:", err)
		}
		_, err = client.CreateProxy("test", "localhost:3310", "localhost:20001")
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}
	})
}

func TestCreateProxyNameConflict(t *testing.T) {
	WithServer(t, func(addr string) {
		testProxy, err := client.CreateProxy("mysql_master", "localhost:3310", "localhost:20001")
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}

		expected := "create: HTTP 409: proxy already exists"
		_, err = client.CreateProxy("mysql_master", "localhost:3311", "localhost:20001")
		if err == nil {
			t.Fatal("Proxy did not result in conflict.")
		} else if err.Error() != expected {
			t.Fatalf("Expected error `%s',\n\tgot: `%s'", expected, err)
		}

		err = testProxy.Delete()
		if err != nil {
			t.Fatal("Unable to delete proxy:", err)
		}
		_, err = client.CreateProxy("mysql_master", "localhost:3311", "localhost:20001")
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}
	})
}

func TestResetState(t *testing.T) {
	WithServer(t, func(addr string) {
		testProxy, err := client.CreateProxy("mysql_master", "localhost:3310", "localhost:20001")
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}

		latency, err := testProxy.AddToxic("", "latency", "downstream", 1, tclient.Attributes{
			"latency": 100,
			"jitter":  10,
		})
		if err != nil {
			t.Fatal("Error setting toxic:", err)
		}

		if latency.Attributes["latency"] != 100.0 || latency.Attributes["jitter"] != 10.0 {
			t.Fatal("Latency toxic did not start up with correct settings")
		}

		err = client.ResetState()
		if err != nil {
			t.Fatal("unable to reset state:", err)
		}

		proxies, err := client.Proxies()
		if err != nil {
			t.Fatal("Error listing proxies:", err)
		}

		proxy, ok := proxies["mysql_master"]
		if !ok {
			t.Fatal("Expected proxy to still exist")
		}
		if !proxy.Enabled {
			t.Fatal("Expected proxy to be enabled")
		}

		toxics, err := proxy.Toxics()
		if err != nil {
			t.Fatal("Error requesting toxics:", err)
		}

		AssertToxicExists(t, toxics, "latency", "", "", false)

		AssertProxyUp(t, proxy.Listen, true)
	})
}

func TestListingToxics(t *testing.T) {
	WithServer(t, func(addr string) {
		testProxy, err := client.CreateProxy("mysql_master", "localhost:3310", "localhost:20001")
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}

		toxics, err := testProxy.Toxics()
		if err != nil {
			t.Fatal("Error returning toxics:", err)
		}

		AssertToxicExists(t, toxics, "latency", "", "", false)
	})
}

func TestAddToxic(t *testing.T) {
	WithServer(t, func(addr string) {
		testProxy, err := client.CreateProxy("mysql_master", "localhost:3310", "localhost:20001")
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}

		latency, err := testProxy.AddToxic("foobar", "latency", "downstream", 1, tclient.Attributes{
			"latency": 100,
			"jitter":  10,
		})
		if err != nil {
			t.Fatal("Error setting toxic:", err)
		}

		if latency.Attributes["latency"] != 100.0 || latency.Attributes["jitter"] != 10.0 {
			t.Fatal("Latency toxic did not start up with correct settings")
		}

		toxics, err := testProxy.Toxics()
		if err != nil {
			t.Fatal("Error returning toxics:", err)
		}
		toxic := AssertToxicExists(t, toxics, "foobar", "latency", "downstream", true)
		if toxic.Toxicity != 1.0 || toxic.Attributes["latency"] != 100.0 ||
			toxic.Attributes["jitter"] != 10.0 {
			t.Fatal("Toxic was not read back correctly:", toxic)
		}
	})
}

func TestAddMultipleToxics(t *testing.T) {
	WithServer(t, func(addr string) {
		testProxy, err := client.CreateProxy("mysql_master", "localhost:3310", "localhost:20001")
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}

		_, err = testProxy.AddToxic("latency1", "latency", "downstream", 1, nil)
		if err != nil {
			t.Fatal("Error setting toxic:", err)
		}

		_, err = testProxy.AddToxic("latency2", "latency", "downstream", 1, nil)
		if err != nil {
			t.Fatal("Error setting toxic:", err)
		}

		toxics, err := testProxy.Toxics()
		if err != nil {
			t.Fatal("Error returning toxics:", err)
		}
		AssertToxicExists(t, toxics, "latency1", "latency", "downstream", true)
		toxic := AssertToxicExists(t, toxics, "latency2", "latency", "downstream", true)
		if toxic.Toxicity != 1.0 || toxic.Attributes["latency"] != 0.0 ||
			toxic.Attributes["jitter"] != 0.0 {
			t.Fatal("Toxic was not read back correctly:", toxic)
		}
		AssertToxicExists(t, toxics, "latency1", "", "upstream", false)
		AssertToxicExists(t, toxics, "latency2", "", "upstream", false)
	})
}

func TestAddConflictingToxic(t *testing.T) {
	WithServer(t, func(addr string) {
		testProxy, err := client.CreateProxy("mysql_master", "localhost:3310", "localhost:20001")
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}

		_, err = testProxy.AddToxic("foobar", "latency", "downstream", 1, nil)
		if err != nil {
			t.Fatal("Error setting toxic:", err)
		}

		_, err = testProxy.AddToxic("foobar", "slow_close", "downstream", 1, nil)
		if err == nil {
			t.Fatal("Toxic did not result in conflict.")
		} else if err.Error() != "AddToxic: HTTP 409: toxic already exists" {
			t.Fatal("Incorrect error setting toxic:", err)
		}

		toxics, err := testProxy.Toxics()
		if err != nil {
			t.Fatal("Error returning toxics:", err)
		}
		toxic := AssertToxicExists(t, toxics, "foobar", "latency", "downstream", true)
		if toxic.Toxicity != 1.0 || toxic.Attributes["latency"] != 0.0 ||
			toxic.Attributes["jitter"] != 0.0 {
			t.Fatal("Toxic was not read back correctly:", toxic)
		}
		AssertToxicExists(t, toxics, "foobar", "", "upstream", false)
	})
}

func TestAddConflictingToxicsMultistream(t *testing.T) {
	WithServer(t, func(addr string) {
		testProxy, err := client.CreateProxy("mysql_master", "localhost:3310", "localhost:20001")
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}

		_, err = testProxy.AddToxic("foobar", "latency", "upstream", 1, nil)
		if err != nil {
			t.Fatal("Error setting toxic:", err)
		}

		_, err = testProxy.AddToxic("foobar", "latency", "downstream", 1, nil)
		if err == nil {
			t.Fatal("Toxic did not result in conflict.")
		} else if err.Error() != "AddToxic: HTTP 409: toxic already exists" {
			t.Fatal("Incorrect error setting toxic:", err)
		}

		toxics, err := testProxy.Toxics()
		if err != nil {
			t.Fatal("Error returning toxics:", err)
		}

		toxic := AssertToxicExists(t, toxics, "foobar", "latency", "upstream", true)
		if toxic.Toxicity != 1.0 || toxic.Attributes["latency"] != 0.0 ||
			toxic.Attributes["jitter"] != 0.0 {
			t.Fatal("Toxic was not read back correctly:", toxic)
		}
		AssertToxicExists(t, toxics, "foobar", "", "downstream", false)
	})
}

func TestAddConflictingToxicsMultistreamDefaults(t *testing.T) {
	WithServer(t, func(addr string) {
		testProxy, err := client.CreateProxy("mysql_master", "localhost:3310", "localhost:20001")
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}

		_, err = testProxy.AddToxic("", "latency", "upstream", 1, nil)
		if err != nil {
			t.Fatal("Error setting toxic:", err)
		}

		_, err = testProxy.AddToxic("", "latency", "downstream", 1, nil)
		if err != nil {
			t.Fatal("Error setting toxic:", err)
		}

		toxics, err := testProxy.Toxics()
		if err != nil {
			t.Fatal("Error returning toxics:", err)
		}
		toxic := AssertToxicExists(t, toxics, "latency_upstream", "latency", "upstream", true)
		if toxic.Toxicity != 1.0 || toxic.Attributes["latency"] != 0.0 ||
			toxic.Attributes["jitter"] != 0.0 {
			t.Fatal("Toxic was not read back correctly:", toxic)
		}
		toxic = AssertToxicExists(t, toxics, "latency_downstream", "latency", "downstream", true)
		if toxic.Toxicity != 1.0 || toxic.Attributes["latency"] != 0.0 ||
			toxic.Attributes["jitter"] != 0.0 {
			t.Fatal("Toxic was not read back correctly:", toxic)
		}
	})
}

func TestAddToxicWithToxicity(t *testing.T) {
	WithServer(t, func(addr string) {
		testProxy, err := client.CreateProxy("mysql_master", "localhost:3310", "localhost:20001")
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}

		latency, err := testProxy.AddToxic("", "latency", "downstream", 0.2, tclient.Attributes{
			"latency": 100,
			"jitter":  10,
		})
		if err != nil {
			t.Fatal("Error setting toxic:", err)
		}

		if latency.Toxicity != 0.2 || latency.Attributes["latency"] != 100.0 ||
			latency.Attributes["jitter"] != 10.0 {
			t.Fatal("Latency toxic did not start up with correct settings:", latency)
		}

		toxics, err := testProxy.Toxics()
		if err != nil {
			t.Fatal("Error returning toxics:", err)
		}
		toxic := AssertToxicExists(t, toxics, "latency_downstream", "latency", "downstream", true)
		if toxic.Toxicity != 0.2 || toxic.Attributes["latency"] != 100.0 ||
			toxic.Attributes["jitter"] != 10.0 {
			t.Fatal("Toxic was not read back correctly:", toxic)
		}
	})
}

func TestAddNoop(t *testing.T) {
	WithServer(t, func(addr string) {
		testProxy, err := client.CreateProxy("mysql_master", "localhost:3310", "localhost:20001")
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}

		noop, err := testProxy.AddToxic("foobar", "noop", "", 1, nil)
		if err != nil {
			t.Fatal("Error setting toxic:", err)
		}

		if noop.Toxicity != 1.0 || noop.Name != "foobar" || noop.Type != "noop" ||
			noop.Stream != "downstream" {
			t.Fatal("Noop toxic did not start up with correct settings:", noop)
		}

		toxics, err := testProxy.Toxics()
		if err != nil {
			t.Fatal("Error returning toxics:", err)
		}
		toxic := AssertToxicExists(t, toxics, "foobar", "noop", "downstream", true)
		if toxic.Toxicity != 1.0 {
			t.Fatal("Toxic was not read back correctly:", toxic)
		}
	})
}

func TestUpdateToxics(t *testing.T) {
	WithServer(t, func(addr string) {
		testProxy, err := client.CreateProxy("mysql_master", "localhost:3310", "localhost:20001")
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}

		latency, err := testProxy.AddToxic("", "latency", "downstream", -1, tclient.Attributes{
			"latency": 100,
			"jitter":  10,
		})
		if err != nil {
			t.Fatal("Error setting toxic:", err)
		}

		if latency.Toxicity != 1.0 || latency.Attributes["latency"] != 100.0 ||
			latency.Attributes["jitter"] != 10.0 {
			t.Fatal("Latency toxic did not start up with correct settings:", latency)
		}

		latency, err = testProxy.UpdateToxic("latency_downstream", 0.5, tclient.Attributes{
			"latency": 1000,
		})
		if err != nil {
			t.Fatal("Error setting toxic:", err)
		}

		if latency.Toxicity != 0.5 || latency.Attributes["latency"] != 1000.0 ||
			latency.Attributes["jitter"] != 10.0 {
			t.Fatal("Latency toxic did not get updated with the correct settings:", latency)
		}

		latency, err = testProxy.UpdateToxic("latency_downstream", -1, tclient.Attributes{
			"latency": 500,
		})
		if err != nil {
			t.Fatal("Error setting toxic:", err)
		}

		if latency.Toxicity != 0.5 || latency.Attributes["latency"] != 500.0 ||
			latency.Attributes["jitter"] != 10.0 {
			t.Fatal("Latency toxic did not get updated with the correct settings:", latency)
		}

		toxics, err := testProxy.Toxics()
		if err != nil {
			t.Fatal("Error returning toxics:", err)
		}

		toxic := AssertToxicExists(t, toxics, "latency_downstream", "latency", "downstream", true)
		if toxic.Toxicity != 0.5 || toxic.Attributes["latency"] != 500.0 ||
			toxic.Attributes["jitter"] != 10.0 {
			t.Fatal("Toxic was not read back correctly:", toxic)
		}
	})
}

func TestRemoveToxic(t *testing.T) {
	WithServer(t, func(addr string) {
		testProxy, err := client.CreateProxy("mysql_master", "localhost:3310", "localhost:20001")
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}

		_, err = testProxy.AddToxic("", "latency", "downstream", 1, nil)
		if err != nil {
			t.Fatal("Error setting toxic:", err)
		}

		toxics, err := testProxy.Toxics()
		if err != nil {
			t.Fatal("Error returning toxics:", err)
		}

		toxic := AssertToxicExists(t, toxics, "latency_downstream", "latency", "downstream", true)
		if toxic.Toxicity != 1.0 || toxic.Attributes["latency"] != 0.0 ||
			toxic.Attributes["jitter"] != 0.0 {
			t.Fatal("Toxic was not read back correctly:", toxic)
		}

		err = testProxy.RemoveToxic("latency_downstream")
		if err != nil {
			t.Fatal("Error removing toxic:", err)
		}

		toxics, err = testProxy.Toxics()
		if err != nil {
			t.Fatal("Error returning toxics:", err)
		}
		AssertToxicExists(t, toxics, "latency_downstream", "", "", false)
	})
}

func TestVersionEndpointReturnsVersion(t *testing.T) {
	WithServer(t, func(addr string) {
		resp, err := http.Get(addr + "/version")
		if err != nil {
			t.Fatal("Failed to get index", err)
		}
		defer resp.Body.Close()

		body, err := io.ReadAll(resp.Body)
		if err != nil {
			t.Fatal("Unable to read body from response")
		}

		if string(body) != "{\"version\": \"git\"}\n" {
			t.Fatal("Expected to return Version from /version, got:", string(body))
		}
	})
}

func TestInvalidStream(t *testing.T) {
	WithServer(t, func(addr string) {
		testProxy, err := client.CreateProxy("mysql_master", "localhost:3310", "localhost:20001")
		if err != nil {
			t.Fatal("Unable to create proxy:", err)
		}

		_, err = testProxy.AddToxic("", "latency", "walrustream", 1, nil)
		if err == nil {
			t.Fatal("Error setting toxic:", err)
		}
	})
}

func AssertToxicExists(
	t *testing.T,
	toxics tclient.Toxics,
	name, typeName, stream string,
	exists bool,
) *tclient.Toxic {
	var toxic *tclient.Toxic
	var actualType, actualStream string

	for i, tox := range toxics {
		if name == tox.Name {
			toxic = &toxics[i]
			actualType = tox.Type
			actualStream = tox.Stream
		}
	}
	if exists {
		if toxic == nil {
			t.Fatalf("Expected to see %s toxic in list", name)
		}

		if actualType != typeName {
			t.Fatalf("Expected %s to be of type %s, found %s", name, typeName, actualType)
		}

		if actualStream != stream {
			t.Fatalf("Expected %s to be in stream %s, found %s", name, stream, actualStream)
		}
	} else if toxic != nil && actualStream == stream {
		t.Fatalf("Expected %s toxic to be missing from list, found type %s", name, actualType)
	}
	return toxic
}


================================================
FILE: client/README.md
================================================
# toxiproxy-go

This is the Go client library for the
[Toxiproxy](https://github.com/shopify/toxiproxy) API. Please read the [usage
section in the Toxiproxy README](https://github.com/shopify/toxiproxy#usage)
before attempting to use the client.

This client is compatible with Toxiproxy 2.x, for the latest 1.x client see
[v1.2.1](https://github.com/Shopify/toxiproxy/tree/v1.2.1/client).

## Changes in Toxiproxy-go Client 2.x

In order to make use of the 2.0 api, and to make usage a little easier, the
client api has changed:

 - `client.NewProxy()` no longer accepts a proxy as an argument.
 - `proxy.Create()` is removed in favour of using `proxy.Save()`.
 - Proxies can be created in a single call using `client.CreateProxy()`.
 - `proxy.Disable()` and `proxy.Enable()` have been added to simplify taking
    down a proxy.
 - `proxy.ToxicsUpstream` and `proxy.ToxicsDownstream` have been merged into a
    single `ActiveToxics` list.
 - `proxy.Toxics()` no longer requires a direction to be specified, and will
    return toxics for both directions.
 - `proxy.SetToxic()` has been replaced by `proxy.AddToxic()`,
   `proxy.UpdateToxic()`, and `proxy.RemoveToxic()`.

## Usage

For detailed API docs please [see the Godoc
documentation](http://godoc.org/github.com/Shopify/toxiproxy/client).

First import toxiproxy and create a new client:
```go
import toxiproxy "github.com/Shopify/toxiproxy/v2/client"

client := toxiproxy.NewClient("localhost:8474")
```

You can then create a new proxy using the client:
```go
proxy, err := client.CreateProxy("redis", "localhost:26379", "localhost:6379")
if err != nil {
    panic(err)
}
```

For large amounts of proxies, they can also be created using a configuration file:
```go
var config []toxiproxy.Proxy
data, _ := ioutil.ReadFile("config.json")
json.Unmarshal(data, &config)
proxies, err = client.Populate(config)
```
```json
[{
  "name": "redis",
  "listen": "localhost:26379",
  "upstream": "localhost:6379"
}]
```

Toxics can be added as follows:
```go
// Add 1s latency to 100% of downstream connections
proxy.AddToxic("latency_down", "latency", "downstream", 1.0, toxiproxy.Attributes{
    "latency": 1000,
})

// Change downstream latency to add 100ms of jitter
proxy.UpdateToxic("latency_down", 1.0, toxiproxy.Attributes{
    "jitter": 100,
})

// Remove the latency toxic
proxy.RemoveToxic("latency_down")
```


The proxy can be taken down using `Disable()`:
```go
proxy.Disable()
```

When a proxy is no longer needed, it can be cleaned up with `Delete()`:
```go
proxy.Delete()
```

## Full Example

```go
import (
    "testing"
    "time"

    toxiproxy "github.com/Shopify/toxiproxy/v2/client"
    "github.com/gomodule/redigo/redis"
)

var toxiClient *toxiproxy.Client

func init() {
    var err error
    toxiClient = toxiproxy.NewClient("localhost:8474")
    _, err = toxiClient.Populate([]toxiproxy.Proxy{{
        Name:     "redis",
        Listen:   "localhost:26379",
        Upstream: "localhost:6379",
        // note: you cannot set toxics here via ActiveToxics
    }})
    if err != nil {
        panic(err)
    }
    // Alternatively, create the proxies manually with
    // toxiClient.CreateProxy("redis", "localhost:26379", "localhost:6379")
}

func TestRedisBackendDown(t *testing.T) {
    var proxy, _ = toxiClient.Proxy("redis")
    proxy.Disable()
    defer proxy.Enable()

    // Test that redis is down
    _, err := redis.Dial("tcp", ":26379")
    if err == nil {
        t.Fatal("Connection to redis did not fail")
    }
}

func TestRedisBackendSlow(t *testing.T) {
    var proxy, _ = toxiClient.Proxy("redis")
    proxy.AddToxic("", "latency", "", 1, toxiproxy.Attributes{
        "latency": 1000,
    })
    proxy.Save()
    defer removeToxic(proxy, "latency_downstream")

    // Test that redis is slow
    start := time.Now()
    conn, err := redis.Dial("tcp", ":26379")
    if err != nil {
        t.Fatal("Connection to redis failed", err)
    }

    _, err = conn.Do("GET", "test")
    if err != nil {
        t.Fatal("Redis command failed", err)
    } else if time.Since(start) < 900*time.Millisecond {
        t.Fatal("Redis command did not take long enough:", time.Since(start))
    }
}

func removeToxic(p *toxiproxy.Proxy, n string) {
    p.RemoveToxic(n)
    p.Save()
}
```


================================================
FILE: client/api_error.go
================================================
// Package Toxiproxy provides a client wrapper around the Toxiproxy HTTP API for
// testing the resiliency of Go applications.
//
// For use with Toxiproxy 2.x

package toxiproxy

import (
	"fmt"
)

type ApiError struct {
	Message string `json:"error"`
	Status  int    `json:"status"`
}

func (err *ApiError) Error() string {
	return fmt.Sprintf("HTTP %d: %s", err.Status, err.Message)
}


================================================
FILE: client/client.go
================================================
// Package Toxiproxy provides a client wrapper around the Toxiproxy HTTP API for
// testing the resiliency of Go applications.
//
// For use with Toxiproxy 2.x
package toxiproxy

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"
)

// Client holds information about where to connect to Toxiproxy.
type Client struct {
	UserAgent string
	endpoint  string
	http      *http.Client
}

// NewClient creates a new client which provides the base of all communication
// with Toxiproxy. Endpoint is the address to the proxy (e.g. localhost:8474 if
// not overridden).
func NewClient(endpoint string) *Client {
	if !strings.HasPrefix(endpoint, "https://") &&
		!strings.HasPrefix(endpoint, "http://") {
		endpoint = "http://" + endpoint
	}

	http := &http.Client{
		Timeout: 30 * time.Second,
	}

	return &Client{
		UserAgent: "toxiproxy-cli",
		endpoint:  endpoint,
		http:      http,
	}
}

// Version returns a Toxiproxy running version.
func (client *Client) Version() ([]byte, error) {
	return client.get("/version")
}

// Proxies returns a map with all the proxies and their toxics.
func (client *Client) Proxies() (map[string]*Proxy, error) {
	resp, err := client.get("/proxies")
	if err != nil {
		return nil, err
	}

	proxies := make(map[string]*Proxy)
	err = json.Unmarshal(resp, &proxies)
	if err != nil {
		return nil, err
	}

	for _, proxy := range proxies {
		proxy.client = client
		proxy.created = true
	}

	return proxies, nil
}

// Generates a new uncommitted proxy instance. In order to use the result, the
// proxy fields will need to be set and have `Save()` called.
func (client *Client) NewProxy() *Proxy {
	return &Proxy{
		client: client,
	}
}

// CreateProxy instantiates a new proxy and starts listening on the specified address.
// This is an alias for `NewProxy()` + `proxy.Save()`.
func (client *Client) CreateProxy(name, listen, upstream string) (*Proxy, error) {
	proxy := &Proxy{
		Name:     name,
		Listen:   listen,
		Upstream: upstream,
		Enabled:  true,
		client:   client,
	}

	err := proxy.Save()
	if err != nil {
		return nil, fmt.Errorf("create: %w", err)
	}

	return proxy, nil
}

// Proxy returns a proxy by name.
func (client *Client) Proxy(name string) (*Proxy, error) {
	resp, err := client.get("/proxies/" + name)
	if err != nil {
		return nil, err
	}

	proxy := new(Proxy)
	err = json.Unmarshal(resp, &proxy)
	if err != nil {
		return nil, err
	}
	proxy.client = client
	proxy.created = true

	return proxy, nil
}

// Create a list of proxies using a configuration list. If a proxy already exists,
// it will be replaced with the specified configuration.
// For large amounts of proxies, `config` can be loaded from a file.
// Returns a list of the successfully created proxies.
func (client *Client) Populate(config []Proxy) ([]*Proxy, error) {
	proxies := struct {
		Proxies []*Proxy `json:"proxies"`
	}{}
	request, err := json.Marshal(config)
	if err != nil {
		return nil, err
	}

	resp, err := client.post("/populate", bytes.NewReader(request))
	if err != nil {
		return nil, fmt.Errorf("Populate: %w", err)
	}

	err = json.Unmarshal(resp, &proxies)
	if err != nil {
		return nil, err
	}

	for _, proxy := range proxies.Proxies {
		proxy.client = client
	}

	return proxies.Proxies, err
}

// AddToxic creates a toxic to proxy.
func (client *Client) AddToxic(options *ToxicOptions) (*Toxic, error) {
	proxy, err := client.Proxy(options.ProxyName)
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve proxy with name `%s`: %v", options.ProxyName, err)
	}

	toxic, err := proxy.AddToxic(
		options.ToxicName,
		options.ToxicType,
		options.Stream,
		options.Toxicity,
		options.Attributes,
	)

	if err != nil {
		return nil, fmt.Errorf("failed to add toxic to proxy %s: %v", options.ProxyName, err)
	}

	return toxic, nil
}

// UpdateToxic update a toxic in proxy.
func (client *Client) UpdateToxic(options *ToxicOptions) (*Toxic, error) {
	proxy, err := client.Proxy(options.ProxyName)
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve proxy with name `%s`: %v", options.ProxyName, err)
	}

	toxic, err := proxy.UpdateToxic(
		options.ToxicName,
		options.Toxicity,
		options.Attributes,
	)

	if err != nil {
		return nil,
			fmt.Errorf(
				"failed to update toxic '%s' of proxy '%s': %v",
				options.ToxicName, options.ProxyName, err,
			)
	}

	return toxic, nil
}

// RemoveToxic removes toxic from proxy.
func (client *Client) RemoveToxic(options *ToxicOptions) error {
	proxy, err := client.Proxy(options.ProxyName)
	if err != nil {
		return fmt.Errorf("failed to retrieve proxy with name `%s`: %v", options.ProxyName, err)
	}

	err = proxy.RemoveToxic(options.ToxicName)
	if err != nil {
		return fmt.Errorf(
			"failed to remove toxic '%s' from proxy '%s': %v",
			options.ToxicName, options.ProxyName, err,
		)
	}

	return nil
}

// ResetState resets the state of all proxies and toxics in Toxiproxy.
func (client *Client) ResetState() error {
	_, err := client.post("/reset", bytes.NewReader([]byte{}))
	return err
}

func (c *Client) get(path string) ([]byte, error) {
	return c.send("GET", path, nil)
}

func (c *Client) post(path string, body io.Reader) ([]byte, error) {
	return c.send("POST", path, body)
}

func (c *Client) patch(path string, body io.Reader) ([]byte, error) {
	return c.send("PATCH", path, body)
}

func (c *Client) delete(path string) error {
	_, err := c.send("DELETE", path, nil)
	return err
}

func (c *Client) send(verb, path string, body io.Reader) ([]byte, error) {
	req, err := http.NewRequest(verb, c.endpoint+path, body)
	if err != nil {
		return nil, err
	}

	req.Header.Set("User-Agent", c.UserAgent)
	req.Header.Set("Content-Type", "application/json")

	resp, err := c.http.Do(req)
	if err != nil {
		return nil, fmt.Errorf("fail to request: %w", err)
	}

	err = c.validateResponse(resp)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	result, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return result, nil
}

func (c *Client) validateResponse(resp *http.Response) error {
	if resp.StatusCode < 300 && resp.StatusCode >= 200 {
		return nil
	}

	apiError := new(ApiError)
	err := json.NewDecoder(resp.Body).Decode(&apiError)
	if err != nil {
		return err
	}
	resp.Body.Close()

	if err != nil {
		apiError.Message = fmt.Sprintf(
			"Unexpected response code %d",
			resp.StatusCode,
		)
		apiError.Status = resp.StatusCode
	}
	return apiError
}


================================================
FILE: client/client_test.go
================================================
package toxiproxy_test

import (
	"net/http"
	"net/http/httptest"
	"testing"

	toxiproxy "github.com/Shopify/toxiproxy/v2/client"
)

func TestClient_Headers(t *testing.T) {
	t.Parallel()

	expected := "toxiproxy-cli/v1.25.0 (darwin/arm64)"

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		ua := r.Header.Get("User-Agent")

		if ua != expected {
			t.Errorf("User-Agent for %s %s is expected `%s', got: `%s'",
				r.Method,
				r.URL,
				expected,
				ua)
		}

		contentType := r.Header.Get("Content-Type")
		if contentType != "application/json" {
			t.Errorf("Content-Type for %s %s is expected `application/json', got: `%s'",
				r.Method,
				r.URL,
				contentType)
		}
		w.Write([]byte(`foo`))
	}))
	defer server.Close()

	client := toxiproxy.NewClient(server.URL)
	client.UserAgent = expected

	cases := []struct {
		name string
		fn   func(c *toxiproxy.Client)
	}{
		{"get version", func(c *toxiproxy.Client) { c.Version() }},
		{"get proxies", func(c *toxiproxy.Client) { c.Proxies() }},
		{"create proxy", func(c *toxiproxy.Client) {
			c.CreateProxy("foo", "example.com:0", "example.com:0")
		}},
		{"get proxy", func(c *toxiproxy.Client) { c.Proxy("foo") }},
		{"post populate", func(c *toxiproxy.Client) {
			c.Populate([]toxiproxy.Proxy{{}})
		}},
		{"create toxic", func(c *toxiproxy.Client) {
			c.AddToxic(&toxiproxy.ToxicOptions{})
		}},
		{"update toxic", func(c *toxiproxy.Client) {
			c.UpdateToxic(&toxiproxy.ToxicOptions{})
		}},
		{"delete toxic", func(c *toxiproxy.Client) {
			c.RemoveToxic(&toxiproxy.ToxicOptions{})
		}},
		{"reset state", func(c *toxiproxy.Client) {
			c.ResetState()
		}},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			tc.fn(client)
		})
	}
}


================================================
FILE: client/proxy.go
================================================
// Package Toxiproxy provides a client wrapper around the Toxiproxy HTTP API for
// testing the resiliency of Go applications.
//
// For use with Toxiproxy 2.x
package toxiproxy

import (
	"bytes"
	"encoding/json"
	"fmt"
)

type Proxy struct {
	Name     string `json:"name"`     // The name of the proxy
	Listen   string `json:"listen"`   // The address the proxy listens on
	Upstream string `json:"upstream"` // The upstream address to proxy to
	Enabled  bool   `json:"enabled"`  // Whether the proxy is enabled

	// The toxics active on this proxy. Note: you cannot set this
	// when passing Proxy into Populate()
	ActiveToxics Toxics `json:"toxics"`

	client  *Client
	created bool // True if this proxy exists on the server
}

// Save saves changes to a proxy such as its enabled status or upstream port.
func (proxy *Proxy) Save() error {
	request, err := json.Marshal(proxy)
	if err != nil {
		return err
	}
	data := bytes.NewReader(request)

	var resp []byte
	if proxy.created {
		// TODO: Release PATCH only for v3.0
		// resp, err = proxy.client.patch("/proxies/"+proxy.Name, data)
		resp, err = proxy.client.post("/proxies/"+proxy.Name, data)
	} else {
		resp, err = proxy.client.post("/proxies", data)
	}
	if err != nil {
		return err
	}

	err = json.Unmarshal(resp, proxy)
	if err != nil {
		return err
	}

	proxy.created = true

	return nil
}

// Enable a proxy again after it has been disabled.
func (proxy *Proxy) Enable() error {
	proxy.Enabled = true
	return proxy.Save()
}

// Disable a proxy so that no connections can pass through. This will drop all active connections.
func (proxy *Proxy) Disable() error {
	proxy.Enabled = false
	return proxy.Save()
}

// Delete a proxy complete and close all existing connections through it. All information about
// the proxy such as listen port and active toxics will be deleted as well. If you just wish to
// stop and later enable a proxy, use `Enable()` and `Disable()`.
func (proxy *Proxy) Delete() error {
	err := proxy.client.delete("/proxies/" + proxy.Name)
	if err != nil {
		return fmt.Errorf("Delete: %w", err)
	}
	return nil
}

// Toxics returns a map of all the active toxics and their attributes.
func (proxy *Proxy) Toxics() (Toxics, error) {
	resp, err := proxy.client.get("/proxies/" + proxy.Name + "/toxics")
	if err != nil {
		return nil, err
	}

	toxics := make(Toxics, 0)
	err = json.Unmarshal(resp, &toxics)
	if err != nil {
		return nil, err
	}

	return toxics, nil
}

// AddToxic adds a toxic to the given stream direction.
// If a name is not specified, it will default to <type>_<stream>.
// If a stream is not specified, it will default to downstream.
// See https://github.com/Shopify/toxiproxy#toxics for a list of all Toxic types.
func (proxy *Proxy) AddToxic(
	name, typeName, stream string,
	toxicity float32,
	attrs Attributes,
) (*Toxic, error) {
	toxic := Toxic{name, typeName, stream, toxicity, attrs}
	if toxic.Toxicity == -1 {
		toxic.Toxicity = 1 // Just to be consistent with a toxicity of -1 using the default
	}

	request, err := json.Marshal(&toxic)
	if err != nil {
		return nil, err
	}

	resp, err := proxy.client.post(
		"/proxies/"+proxy.Name+"/toxics",
		bytes.NewReader(request),
	)
	if err != nil {
		return nil, fmt.Errorf("AddToxic: %w", err)
	}

	result := &Toxic{}
	err = json.Unmarshal(resp, result)
	if err != nil {
		return nil, err
	}

	return result, nil
}

// UpdateToxic sets the parameters for an existing toxic with the given name.
// If toxicity is set to -1, the current value will be used.
func (proxy *Proxy) UpdateToxic(name string, toxicity float32, attrs Attributes) (*Toxic, error) {
	toxic := map[string]interface{}{
		"attributes": attrs,
	}
	if toxicity != -1 {
		toxic["toxicity"] = toxicity
	}
	request, err := json.Marshal(&toxic)
	if err != nil {
		return nil, err
	}

	resp, err := proxy.client.patch(
		"/proxies/"+proxy.Name+"/toxics/"+name,
		bytes.NewReader(request),
	)
	if err != nil {
		return nil, err
	}

	result := &Toxic{}
	err = json.Unmarshal(resp, result)
	if err != nil {
		return nil, err
	}

	return result, nil
}

// RemoveToxic renives the toxic with the given name.
func (proxy *Proxy) RemoveToxic(name string) error {
	return proxy.client.delete("/proxies/" + proxy.Name + "/toxics/" + name)
}


================================================
FILE: client/toxic.go
================================================
// Package Toxiproxy provides a client wrapper around the Toxiproxy HTTP API for
// testing the resiliency of Go applications.
//
// For use with Toxiproxy 2.x
package toxiproxy

type Attributes map[string]interface{}

type Toxic struct {
	Name       string     `json:"name"`
	Type       string     `json:"type"`
	Stream     string     `json:"stream,omitempty"`
	Toxicity   float32    `json:"toxicity"`
	Attributes Attributes `json:"attributes"`
}

type Toxics []Toxic

type ToxicOptions struct {
	ProxyName,
	ToxicName,
	ToxicType,
	Stream string
	Toxicity   float32
	Attributes Attributes
}


================================================
FILE: cmd/cli/cli.go
================================================
package main

import (
	"fmt"
	"os"
	"runtime"
	"sort"
	"strconv"
	"strings"

	"github.com/urfave/cli/v2"
	terminal "golang.org/x/term"

	toxiproxyServer "github.com/Shopify/toxiproxy/v2"
	toxiproxy "github.com/Shopify/toxiproxy/v2/client"
)

const (
	RED    = "\x1b[31m"
	GREEN  = "\x1b[32m"
	YELLOW = "\x1b[33m"
	BLUE   = "\x1b[34m"
	PURPLE = "\x1b[35m"
	NONE   = "\x1b[0m"
)

func color(color string) string {
	if isTTY {
		return color
	} else {
		return ""
	}
}

var toxicDescription = `
  Default Toxics:
  latency:    delay all data +/- jitter
              latency=<ms>,jitter=<ms>

  bandwidth:  limit to max kb/s
              rate=<KB/s>

  slow_close: delay from closing
              delay=<ms>

  timeout:    stop all data and close after timeout
              timeout=<ms>

  reset_peer: simulate TCP RESET (Connection reset by peer) on the connections by closing
              the stub Input immediately or after a timeout
              timeout=<ms>

  slicer:     slice data into bits with optional delay
              average_size=<bytes>,size_variation=<bytes>,delay=<microseconds>

  toxic add:
    usage: toxiproxy-cli toxic add --type <toxicType> [--downstream|--upstream] \
            --toxicName <toxicName> [--toxicity <float>] \
            --attribute <key=value> [--attribute <key2=value2>] <proxyName>


    example: toxiproxy-cli toxic add -t latency -n myToxic -a latency=100 -a jitter=50 myProxy

  toxic update:
    usage: toxiproxy-cli toxic update --toxicName <toxicName> [--toxicity <float>] \
            --attribute <key1=value1> [--attribute <key2=value2>] <proxyName>

    example: toxiproxy-cli toxic update -n myToxic -a jitter=25 myProxy

  toxic delete:
    usage: toxiproxy-cli toxic delete --toxicName <toxicName> <proxyName>

    example: toxiproxy-cli toxic delete -n myToxic myProxy
`

var (
	hostname string
	isTTY    bool
)

func main() {
	app := cli.NewApp()
	app.Name = "toxiproxy-cli"
	app.Version = toxiproxyServer.Version
	app.Usage = "Simulate network and system conditions"
	app.Commands = cliCommands()
	cli.HelpFlag = &cli.BoolFlag{
		Name:  "help",
		Usage: "show help",
	}
	app.Flags = []cli.Flag{
		&cli.StringFlag{
			Name:        "host",
			Aliases:     []string{"h"},
			Value:       "http://localhost:8474",
			Usage:       "toxiproxy host to connect to",
			Destination: &hostname,
			EnvVars:     []string{"TOXIPROXY_URL"},
		},
	}

	isTTY = terminal.IsTerminal(int(os.Stdout.Fd()))

	app.Run(os.Args)
}

func cliCommands() []*cli.Command {
	return []*cli.Command{
		{
			Name:    "list",
			Usage:   "list all proxies\n\tusage: 'toxiproxy-cli list'\n",
			Aliases: []string{"l", "li", "ls"},
			Action:  withToxi(list),
		},
		{
			Name:    "inspect",
			Aliases: []string{"i", "ins"},
			Usage:   "inspect a single proxy\n\tusage: 'toxiproxy-cli inspect <proxyName>'\n",
			Action:  withToxi(inspectProxy),
		},
		{
			Name: "create",
			Usage: "create a new proxy\n\t" +
				"usage: 'toxiproxy-cli create --listen <addr> --upstream <addr> <proxyName>'\n",
			Aliases: []string{"c", "new"},
			Flags: []cli.Flag{
				&cli.StringFlag{
					Name:    "listen",
					Aliases: []string{"l"},
					Usage:   "proxy will listen on this address",
				},
				&cli.StringFlag{
					Name:    "upstream",
					Aliases: []string{"u"},
					Usage:   "proxy will forward to this address",
				},
			},
			Action: withToxi(createProxy),
		},
		{
			Name: "toggle",
			Usage: "\ttoggle enabled status on a proxy\n" +
				"\t\tusage: 'toxiproxy-cli toggle <proxyName>'\n",
			Aliases: []string{"tog"},
			Action:  withToxi(toggleProxy),
		},
		{
			Name:    "delete",
			Usage:   "\tdelete a proxy\n\t\tusage: 'toxiproxy-cli delete <proxyName>'\n",
			Aliases: []string{"d"},
			Action:  withToxi(deleteProxy),
		},
		{
			Name:        "toxic",
			Aliases:     []string{"t"},
			Usage:       "\tadd, remove or update a toxic\n\t\tusage: see 'toxiproxy-cli toxic'\n",
			Description: toxicDescription,
			Subcommands: cliToxiSubCommands(),
		},
	}
}

func cliToxiSubCommands() []*cli.Command {
	return []*cli.Command{
		cliToxiAddSubCommand(),
		cliToxiUpdateSubCommand(),
		cliToxiRemoveSubCommand(),
	}
}

func cliToxiAddSubCommand() *cli.Command {
	return &cli.Command{
		Name:      "add",
		Aliases:   []string{"a"},
		Usage:     "add a new toxic",
		ArgsUsage: "<proxyName>",
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:    "toxicName",
				Aliases: []string{"n"},
				Usage:   "name of the toxic",
			},
			&cli.StringFlag{
				Name:    "type",
				Aliases: []string{"t"},
				Usage:   "type of toxic",
			},
			&cli.StringFlag{
				Name:        "toxicity",
				Aliases:     []string{"tox"},
				Usage:       "toxicity of toxic should be a float between 0 and 1",
				DefaultText: "1.0",
			},
			&cli.StringSliceFlag{
				Name:    "attribute",
				Aliases: []string{"a"},
				Usage:   "toxic attribute in key=value format",
			},
			&cli.BoolFlag{
				Name:        "upstream",
				Aliases:     []string{"u"},
				Usage:       "add toxic to upstream",
				DefaultText: "false",
			},
			&cli.BoolFlag{
				Name:        "downstream",
				Aliases:     []string{"d"},
				Usage:       "add toxic to downstream",
				DefaultText: "true",
			},
		},
		Action: withToxi(addToxic),
	}
}

func cliToxiUpdateSubCommand() *cli.Command {
	return &cli.Command{
		Name:      "update",
		Aliases:   []string{"u"},
		Usage:     "update an enabled toxic",
		ArgsUsage: "<proxyName>",
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:    "toxicName",
				Aliases: []string{"n"},
				Usage:   "name of the toxic",
			},
			&cli.StringFlag{
				Name:        "toxicity",
				Aliases:     []string{"tox"},
				Usage:       "toxicity of toxic should be a float between 0 and 1",
				DefaultText: "1.0",
			},
			&cli.StringSliceFlag{
				Name:    "attribute",
				Aliases: []string{"a"},
				Usage:   "toxic attribute in key=value format",
			},
		},
		Action: withToxi(updateToxic),
	}
}

func cliToxiRemoveSubCommand() *cli.Command {
	return &cli.Command{
		Name:      "remove",
		Aliases:   []string{"r", "delete", "d"},
		Usage:     "remove an enabled toxic",
		ArgsUsage: "<proxyName>",
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:    "toxicName",
				Aliases: []string{"n"},
				Usage:   "name of the toxic",
			},
		},
		Action: withToxi(removeToxic),
	}
}

type toxiAction func(*cli.Context, *toxiproxy.Client) error

func withToxi(f toxiAction) func(*cli.Context) error {
	return func(c *cli.Context) error {
		toxiproxyClient := toxiproxy.NewClient(hostname)
		toxiproxyClient.UserAgent = fmt.Sprintf(
			"toxiproxy-cli/%s (%s/%s)",
			c.App.Version,
			runtime.GOOS,
			runtime.GOARCH,
		)
		return f(c, toxiproxyClient)
	}
}

func list(c *cli.Context, t *toxiproxy.Client) error {
	proxies, err := t.Proxies()
	if err != nil {
		return errorf("Failed to retrieve proxies: %s", err)
	}

	var proxyNames []string
	for proxyName := range proxies {
		proxyNames = append(proxyNames, proxyName)
	}
	sort.Strings(proxyNames)

	if isTTY {
		fmt.Printf(
			"%sName\t\t\t%sListen\t\t%sUpstream\t\t%sEnabled\t\t%sToxics\n%s",
			color(GREEN),
			color(BLUE),
			color(YELLOW),
			color(PURPLE),
			color(RED),
			color(NONE),
		)
		fmt.Printf(
			"%s======================================================================================\n",
			color(NONE),
		)

		if len(proxyNames) == 0 {
			fmt.Printf("%sno proxies\n%s", color(RED), color(NONE))
			hint("create a proxy with `toxiproxy-cli create`")
			return nil
		}
	}

	for _, proxyName := range proxyNames {
		proxy := proxies[proxyName]
		numToxics := strconv.Itoa(len(proxy.ActiveToxics))
		if numToxics == "0" && isTTY {
			numToxics = "None"
		}
		printWidth(color(colorEnabled(proxy.Enabled)), proxy.Name, 3)
		printWidth(BLUE, proxy.Listen, 2)
		printWidth(YELLOW, proxy.Upstream, 3)
		printWidth(PURPLE, enabledText(proxy.Enabled), 2)
		fmt.Printf("%s%s%s\n", color(RED), numToxics, color(NONE))
	}
	hint("inspect toxics with `toxiproxy-cli inspect <proxyName>`")
	return nil
}

func inspectProxy(c *cli.Context, t *toxiproxy.Client) error {
	proxyName := c.Args().First()
	if proxyName == "" {
		cli.ShowSubcommandHelp(c)
		return errorf("Proxy name is required as the first argument.\n")
	}

	proxy, err := t.Proxy(proxyName)
	if err != nil {
		return errorf("Failed to retrieve proxy %s: %s\n", proxyName, err.Error())
	}

	if isTTY {
		fmt.Printf("%sName: %s%s\t", color(PURPLE), color(NONE), proxy.Name)
		fmt.Printf("%sListen: %s%s\t", color(BLUE), color(NONE), proxy.Listen)
		fmt.Printf("%sUpstream: %s%s\n", color(YELLOW), color(NONE), proxy.Upstream)
		fmt.Printf(
			"%s======================================================================\n",
			color(NONE),
		)

		splitToxics := func(toxics toxiproxy.Toxics) (toxiproxy.Toxics, toxiproxy.Toxics) {
			upstream := make(toxiproxy.Toxics, 0)
			downstream := make(toxiproxy.Toxics, 0)
			for _, toxic := range toxics {
				if toxic.Stream == "upstream" {
					upstream = append(upstream, toxic)
				} else {
					downstream = append(downstream, toxic)
				}
			}
			return upstream, downstream
		}

		if len(proxy.ActiveToxics) == 0 {
			fmt.Printf("%sProxy has no toxics enabled.\n%s", color(RED), color(NONE))
		} else {
			up, down := splitToxics(proxy.ActiveToxics)
			listToxics(up, "Upstream")
			fmt.Println()
			listToxics(down, "Downstream")
		}

		hint("add a toxic with `toxiproxy-cli toxic add`")
	} else {
		listToxics(proxy.ActiveToxics, "")
	}
	return nil
}

func toggleProxy(c *cli.Context, t *toxiproxy.Client) error {
	proxyName := c.Args().First()
	if proxyName == "" {
		cli.ShowSubcommandHelp(c)
		return errorf("Proxy name is required as the first argument.\n")
	}

	proxy, err := t.Proxy(proxyName)
	if err != nil {
		return errorf("Failed to retrieve proxy %s: %s\n", proxyName, err.Error())
	}

	proxy.Enabled = !proxy.Enabled

	err = proxy.Save()
	if err != nil {
		return errorf("Failed to toggle proxy %s: %s\n", proxyName, err.Error())
	}

	fmt.Printf(
		"Proxy %s%s%s is now %s%s%s\n",
		colorEnabled(proxy.Enabled),
		proxyName,
		color(NONE),
		colorEnabled(proxy.Enabled),
		enabledText(proxy.Enabled),
		color(NONE),
	)
	return nil
}

func createProxy(c *cli.Context, t *toxiproxy.Client) error {
	proxyName := c.Args().First()
	if proxyName == "" {
		cli.ShowSubcommandHelp(c)
		return errorf("Proxy name is required as the first argument.\n")
	}
	listen, err := getArgOrFail(c, "listen")
	if err != nil {
		return err
	}
	upstream, err := getArgOrFail(c, "upstream")
	if err != nil {
		return err
	}
	_, err = t.CreateProxy(proxyName, listen, upstream)
	if err != nil {
		return errorf("Failed to create proxy: %s\n", err.Error())
	}
	fmt.Printf("Created new proxy %s\n", proxyName)
	return nil
}

func deleteProxy(c *cli.Context, t *toxiproxy.Client) error {
	proxyName := c.Args().First()
	if proxyName == "" {
		cli.ShowSubcommandHelp(c)
		return errorf("Proxy name is required as the first argument.\n")
	}
	p, err := t.Proxy(proxyName)
	if err != nil {
		return errorf("Failed to retrieve proxy %s: %s\n", proxyName, err.Error())
	}

	err = p.Delete()
	if err != nil {
		return errorf("Failed to delete proxy: %s\n", err.Error())
	}
	fmt.Printf("Deleted proxy %s\n", proxyName)
	return nil
}

func parseToxicity(c *cli.Context, defaultToxicity float32) (float32, error) {
	toxicity := defaultToxicity
	toxicityString := c.String("toxicity")
	if toxicityString != "" {
		tox, err := strconv.ParseFloat(toxicityString, 32)
		if err != nil || tox > 1 || tox < 0 {
			return 0, errorf("toxicity should be a float between 0 and 1.\n")
		}
		toxicity = float32(tox)
	}
	return toxicity, nil
}

func addToxic(c *cli.Context, t *toxiproxy.Client) error {
	toxicParams, err := parseAddToxicParams(c)
	if err != nil {
		return err
	}

	toxic, err := t.AddToxic(toxicParams)
	if err != nil {
		return errorf("Failed to add toxic: %v\n", err)
	}

	fmt.Printf(
		"Added %s %s toxic '%s' on proxy '%s'\n",
		toxic.Stream,
		toxic.Type,
		toxic.Name,
		toxicParams.ProxyName,
	)

	return nil
}

func updateToxic(c *cli.Context, t *toxiproxy.Client) error {
	toxicParams, err := parseUpdateToxicParams(c)
	if err != nil {
		return err
	}

	toxic, err := t.UpdateToxic(toxicParams)
	if err != nil {
		return errorf("Failed to update toxic: %v\n", err)
	}

	fmt.Printf(
		"Updated toxic '%s' on proxy '%s'\n",
		toxic.Name,
		toxicParams.ProxyName,
	)
	return nil
}

func removeToxic(c *cli.Context, t *toxiproxy.Client) error {
	toxicParams, err := parseToxicCommonParams(c)
	if err != nil {
		return err
	}

	err = t.RemoveToxic(toxicParams)
	if err != nil {
		return errorf("Failed to remove toxic: %v\n", err)
	}

	fmt.Printf("Removed toxic '%s' on proxy '%s'\n", toxicParams.ToxicName, toxicParams.ProxyName)
	return nil
}

func parseToxicCommonParams(context *cli.Context) (*toxiproxy.ToxicOptions, error) {
	proxyName := context.Args().First()
	if proxyName == "" {
		cli.ShowSubcommandHelp(context)
		return nil, errorf("Proxy name is missing.\n")
	}

	toxicName := context.String("toxicName")

	return &toxiproxy.ToxicOptions{
		ProxyName: proxyName,
		ToxicName: toxicName,
	}, nil
}

func parseUpdateToxicParams(c *cli.Context) (*toxiproxy.ToxicOptions, error) {
	result, err := parseToxicCommonParams(c)
	if err != nil {
		return nil, err
	}

	result.Toxicity, err = parseToxicity(c, 1.0)
	if err != nil {
		return nil, err
	}

	result.Attributes = parseAttributes(c, "attribute")

	return result, nil
}

func parseAddToxicParams(c *cli.Context) (*toxiproxy.ToxicOptions, error) {
	result, err := parseToxicCommonParams(c)
	if err != nil {
		return nil, err
	}

	result.ToxicType, err = getArgOrFail(c, "type")
	if err != nil {
		return nil, err
	}

	upstream := c.Bool("upstream")
	downstream := c.Bool("downstream")
	if upstream && downstream {
		return nil, errorf("Only one should be specified: upstream or downstream.\n")
	}

	stream := "downstream"
	if upstream {
		stream = "upstream"
	}
	result.Stream = stream

	result.Toxicity, err = parseToxicity(c, 1.0)
	if err != nil {
		return nil, err
	}

	result.Attributes = parseAttributes(c, "attribute")

	return result, nil
}

func parseAttributes(c *cli.Context, name string) toxiproxy.Attributes {
	parsed := map[string]interface{}{}
	args := c.StringSlice(name)

	for _, raw := range args {
		kv := strings.SplitN(raw, "=", 2)
		if len(kv) < 2 {
			continue
		}
		if float, err := strconv.ParseFloat(kv[1], 64); err == nil {
			parsed[kv[0]] = float
		} else {
			parsed[kv[0]] = kv[1]
		}
	}
	return parsed
}

func colorEnabled(enabled bool) string {
	if enabled {
		return color(GREEN)
	}

	return color(RED)
}

func enabledText(enabled bool) string {
	if enabled {
		return "enabled"
	}

	return "disabled"
}

type attribute struct {
	key   string
	value interface{}
}

type attributeList []attribute

func sortedAttributes(attrs toxiproxy.Attributes) attributeList {
	li := make(attributeList, 0, len(attrs))
	for k, v := range attrs {
		li = append(li, attribute{k, v})
	}
	sort.Slice(li, func(i, j int) bool {
		return li[i].key < li[j].key
	})

	return li
}

func listToxics(toxics toxiproxy.Toxics, stream string) {
	if isTTY {
		fmt.Printf("%s%s toxics:\n%s", color(GREEN), stream, color(NONE))
		if len(toxics) == 0 {
			fmt.Printf("%sProxy has no %s toxics enabled.\n%s", color(RED), stream, color(NONE))
			return
		}
	}
	for _, t := range toxics {
		if isTTY {
			fmt.Printf("%s%s:%s\t", color(BLUE), t.Name, color(NONE))
		} else {
			fmt.Printf("%s\t", t.Name)
		}
		fmt.Printf("type=%s\t", t.Type)
		fmt.Printf("stream=%s\t", t.Stream)
		fmt.Printf("toxicity=%.2f\t", t.Toxicity)
		fmt.Printf("attributes=[")
		sorted := sortedAttributes(t.Attributes)
		for _, a := range sorted {
			fmt.Printf("\t%s=", a.key)
			fmt.Print(a.value)
		}
		fmt.Printf("\t]\n")
	}
}

func getArgOrFail(c *cli.Context, name string) (string, error) {
	arg := c.String(name)
	if arg == "" {
		cli.ShowSubcommandHelp(c)
		return "", errorf("Required argument '%s' was empty.\n", name)
	}
	return arg, nil
}

func hint(m string) {
	if isTTY {
		fmt.Printf("\n%sHint: %s\n", color(NONE), m)
	}
}

func errorf(m string, args ...interface{}) error {
	return cli.Exit(fmt.Sprintf(m, args...), 1)
}

func printWidth(col string, m string, numTabs int) {
	if isTTY {
		numTabs -= len(m)/8 + 1
		if numTabs < 0 {
			numTabs = 0
		}
	} else {
		numTabs = 0
	}
	fmt.Printf("%s%s%s\t%s", color(col), m, color(NONE), strings.Repeat("\t", numTabs))
}


================================================
FILE: cmd/server/server.go
================================================
package main

import (
	"flag"
	"fmt"
	"math/rand"
	"net"
	"os"
	"os/signal"
	"strconv"
	"syscall"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"

	"github.com/Shopify/toxiproxy/v2"
	"github.com/Shopify/toxiproxy/v2/collectors"
)

type cliArguments struct {
	host           string
	port           string
	config         string
	seed           int64
	printVersion   bool
	proxyMetrics   bool
	runtimeMetrics bool
}

func parseArguments() cliArguments {
	result := cliArguments{}
	flag.StringVar(&result.host, "host", "localhost",
		"Host for toxiproxy's API to listen on")
	flag.StringVar(&result.port, "port", "8474",
		"Port for toxiproxy's API to listen on")
	flag.StringVar(&result.config, "config", "",
		"JSON file containing proxies to create on startup")
	flag.Int64Var(&result.seed, "seed", time.Now().UTC().UnixNano(),
		"Seed for randomizing toxics with")
	flag.BoolVar(&result.runtimeMetrics, "runtime-metrics", false,
		`enable runtime-related prometheus metrics (default "false")`)
	flag.BoolVar(&result.proxyMetrics, "proxy-metrics", false,
		`enable toxiproxy-specific prometheus metrics (default "false")`)
	flag.BoolVar(&result.printVersion, "version", false,
		`print the version (default "false")`)
	flag.Parse()

	return result
}

func main() {
	err := run()
	if err != nil {
		fmt.Printf("error: %v", err)
		os.Exit(1)
	}
	os.Exit(0)
}

func run() error {
	cli := parseArguments()

	if cli.printVersion {
		fmt.Printf("toxiproxy-server version %s\n", toxiproxy.Version)
		return nil
	}

	rand.New(rand.NewSource(cli.seed)) // #nosec G404 -- ignoring this rule

	logger := setupLogger()
	log.Logger = logger

	logger.
		Info().
		Str("version", toxiproxy.Version).
		Msg("Starting Toxiproxy")

	metrics := toxiproxy.NewMetricsContainer(prometheus.NewRegistry())
	server := toxiproxy.NewServer(metrics, logger)
	if cli.proxyMetrics {
		server.Metrics.ProxyMetrics = collectors.NewProxyMetricCollectors()
	}
	if cli.runtimeMetrics {
		server.Metrics.RuntimeMetrics = collectors.NewRuntimeMetricCollectors()
	}

	if len(cli.config) > 0 {
		server.PopulateConfig(cli.config)
	}

	addr := net.JoinHostPort(cli.host, cli.port)
	go func(server *toxiproxy.ApiServer, addr string) {
		err := server.Listen(addr)
		if err != nil {
			server.Logger.Err(err).Msg("Server finished with error")
		}
	}(server, addr)

	signals := make(chan os.Signal, 1)
	signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
	<-signals
	server.Logger.Info().Msg("Shutdown started")
	err := server.Shutdown()
	if err != nil {
		logger.Err(err).Msg("Shutdown finished with error")
	}
	return nil
}

func setupLogger() zerolog.Logger {
	zerolog.TimestampFunc = func() time.Time {
		return time.Now().UTC()
	}

	zerolog.CallerMarshalFunc = func(pc uintptr, file string, line int) string {
		short := file
		for i := len(file) - 1; i > 0; i-- {
			if file[i] == '/' {
				short = file[i+1:]
				break
			}
		}
		file = short
		return file + ":" + strconv.Itoa(line)
	}

	logger := zerolog.New(os.Stdout).With().Caller().Timestamp().Logger()

	val, ok := os.LookupEnv("LOG_LEVEL")
	if !ok {
		return logger
	}

	lvl, err := zerolog.ParseLevel(val)
	if err == nil {
		logger = logger.Level(lvl)
	} else {
		l := &logger
		l.Err(err).Msgf("unknown LOG_LEVEL value: \"%s\"", val)
	}

	return logger
}


================================================
FILE: collectors/common.go
================================================
package collectors

const (
	namespace string = "toxiproxy"
)


================================================
FILE: collectors/proxy.go
================================================
package collectors

import (
	"github.com/prometheus/client_golang/prometheus"
)

type ProxyMetricCollectors struct {
	collectors  []prometheus.Collector
	proxyLabels []string

	ReceivedBytesTotal *prometheus.CounterVec
	SentBytesTotal     *prometheus.CounterVec
}

func (c *ProxyMetricCollectors) Collectors() []prometheus.Collector {
	return c.collectors
}

func NewProxyMetricCollectors() *ProxyMetricCollectors {
	var m ProxyMetricCollectors
	m.proxyLabels = []string{
		"direction",
		"proxy",
		"listener",
		"upstream",
	}
	m.ReceivedBytesTotal = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: "proxy",
			Name:      "received_bytes_total",
		},
		m.proxyLabels)
	m.collectors = append(m.collectors, m.ReceivedBytesTotal)

	m.SentBytesTotal = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: "proxy",
			Name:      "sent_bytes_total",
		},
		m.proxyLabels)
	m.collectors = append(m.collectors, m.SentBytesTotal)

	return &m
}


================================================
FILE: collectors/runtime.go
================================================
package collectors

import (
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/collectors"
)

type RuntimeMetricCollectors struct {
	collectors []prometheus.Collector
}

func (c *RuntimeMetricCollectors) Collectors() []prometheus.Collector {
	return c.collectors
}

func NewRuntimeMetricCollectors() *RuntimeMetricCollectors {
	var m RuntimeMetricCollectors
	m.collectors = append(m.collectors, collectors.NewGoCollector())
	m.collectors = append(m.collectors, collectors.NewBuildInfoCollector())
	m.collectors = append(m.collectors,
		collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}))
	return &m
}


================================================
FILE: dev.yml
================================================
---

name: toxiproxy

type: go

up:
  - packages:
      - gnu-tar
      - golangci-lint
      - goreleaser
      - shellcheck
      - shfmt
      - yamllint
  - go:
      version: 1.22.8
      modules: true


================================================
FILE: go.mod
================================================
module github.com/Shopify/toxiproxy/v2

go 1.23.0

require (
	github.com/gorilla/mux v1.8.1
	github.com/prometheus/client_golang v1.23.2
	github.com/rs/zerolog v1.34.0
	github.com/urfave/cli/v2 v2.27.7
	golang.org/x/term v0.34.0
	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7
)

require (
	github.com/beorn7/perks v1.0.1 // indirect
	github.com/cespare/xxhash/v2 v2.3.0 // indirect
	github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
	github.com/kr/text v0.2.0 // indirect
	github.com/mattn/go-colorable v0.1.13 // indirect
	github.com/mattn/go-isatty v0.0.20 // indirect
	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
	github.com/prometheus/client_model v0.6.2 // indirect
	github.com/prometheus/common v0.66.1 // indirect
	github.com/prometheus/procfs v0.16.1 // indirect
	github.com/rs/xid v1.6.0 // indirect
	github.com/russross/blackfriday/v2 v2.1.0 // indirect
	github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
	go.yaml.in/yaml/v2 v2.4.2 // indirect
	golang.org/x/sys v0.35.0 // indirect
	google.golang.org/protobuf v1.36.8 // indirect
)


================================================
FILE: go.sum
================================================
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU=
github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: link.go
================================================
package toxiproxy

import (
	"context"
	"fmt"
	"io"
	"net"
	"time"

	"github.com/rs/zerolog"

	"github.com/Shopify/toxiproxy/v2/stream"
	"github.com/Shopify/toxiproxy/v2/toxics"
)

// ToxicLinks are single direction pipelines that connects an input and output via
// a chain of toxics. The chain always starts with a NoopToxic, and toxics are added
// and removed as they are enabled/disabled. New toxics are always added to the end
// of the chain.
//
// |         NoopToxic  LatencyToxic
// |             v           v
// | Input > ToxicStub > ToxicStub > Output.
type ToxicLink struct {
	stubs     []*toxics.ToxicStub
	proxy     *Proxy
	toxics    *ToxicCollection
	input     *stream.ChanWriter
	output    *stream.ChanReader
	direction stream.Direction
	Logger    *zerolog.Logger
}

func NewToxicLink(
	proxy *Proxy,
	collection *ToxicCollection,
	direction stream.Direction,
	logger zerolog.Logger,
) *ToxicLink {
	link := &ToxicLink{
		stubs: make(
			[]*toxics.ToxicStub,
			len(collection.chain[direction]),
			cap(collection.chain[direction]),
		),
		proxy:     proxy,
		toxics:    collection,
		direction: direction,
		Logger:    &logger,
	}
	// Initialize the link with ToxicStubs
	last := make(chan *stream.StreamChunk) // The first toxic is always a noop
	link.input = stream.NewChanWriter(last)
	for i := 0; i < len(link.stubs); i++ {
		var next chan *stream.StreamChunk
		if i+1 < len(link.stubs) {
			next = make(chan *stream.StreamChunk, link.toxics.chain[direction][i+1].BufferSize)
		} else {
			next = make(chan *stream.StreamChunk)
		}

		link.stubs[i] = toxics.NewToxicStub(last, next)
		last = next
	}
	link.output = stream.NewChanReader(last)
	return link
}

// Start the link with the specified toxics.
func (link *ToxicLink) Start(
	server *ApiServer,
	name string,
	source io.Reader,
	dest io.WriteCloser,
) {
	logger := link.Logger
	logger.
		Debug().
		Str("direction", link.Direction()).
		Msg("Setup connection")

	labels := []string{
		link.Direction(),
		link.proxy.Name,
		link.proxy.Listen,
		link.proxy.Upstream}

	go link.read(labels, server, source)

	for i, toxic := range link.toxics.chain[link.direction] {
		if stateful, ok := toxic.Toxic.(toxics.StatefulToxic); ok {
			link.stubs[i].State = stateful.NewState()
		}

		if _, ok := toxic.Toxic.(*toxics.ResetToxic); ok {
			if err := source.(*net.TCPConn).SetLinger(0); err != nil {
				logger.Err(err).
					Str("toxic", toxic.Type).
					Msg("source: Unable to setLinger(ms)")
			}

			if err := dest.(*net.TCPConn).SetLinger(0); err != nil {
				logger.Err(err).
					Str("toxic", toxic.Type).
					Msg("dest: Unable to setLinger(ms)")
			}
		}

		go link.stubs[i].Run(toxic)
	}

	go link.write(labels, name, server, dest)
}

// read copies bytes from a source to the link's input channel.
func (link *ToxicLink) read(
	metricLabels []string,
	server *ApiServer,
	source io.Reader,
) {
	logger := link.Logger
	bytes, err := io.Copy(link.input, source)
	if err != nil {
		logger.Warn().
			Int64("bytes", bytes).
			Err(err).
			Msg("Source terminated")
	}
	if server.Metrics.proxyMetricsEnabled() {
		server.Metrics.ProxyMetrics.ReceivedBytesTotal.
			WithLabelValues(metricLabels...).Add(float64(bytes))
	}
	link.input.Close()
}

// write copies bytes from the link's output channel to a destination.
func (link *ToxicLink) write(
	metricLabels []string,
	name string,
	server *ApiServer, // TODO: Replace with AppConfig for Metrics and Logger
	dest io.WriteCloser,
) {
	logger := link.Logger.
		With().
		Str("component", "ToxicLink").
		Str("method", "write").
		Str("link", name).
		Str("proxy", link.proxy.Name).
		Str("link_addr", fmt.Sprintf("%p", link)).
		Logger()

	bytes, err := io.Copy(dest, link.output)
	if err != nil {
		logger.Warn().
			Int64("bytes", bytes).
			Err(err).
			Msg("Could not write to destination")
	} else if server.Metrics.proxyMetricsEnabled() {
		server.Metrics.ProxyMetrics.SentBytesTotal.
			WithLabelValues(metricLabels...).Add(float64(bytes))
	}

	dest.Close()
	logger.Trace().Msgf("Remove link %s from ToxicCollection", name)
	link.toxics.RemoveLink(name)
	logger.Trace().Msgf("RemoveConnection %s from Proxy %s", name, link.proxy.Name)
	link.proxy.RemoveConnection(name)
}

// Add a toxic to the end of the chain.
func (link *ToxicLink) AddToxic(toxic *toxics.ToxicWrapper) {
	i := len(link.stubs)

	newin := make(chan *stream.StreamChunk, toxic.BufferSize)
	link.stubs = append(link.stubs, toxics.NewToxicStub(newin, link.stubs[i-1].Output))

	// Interrupt the last toxic so that we don't have a race when moving channels
	if link.stubs[i-1].InterruptToxic() {
		link.stubs[i-1].Output = newin

		if stateful, ok := toxic.Toxic.(toxics.StatefulToxic); ok {
			link.stubs[i].State = stateful.NewState()
		}

		go link.stubs[i].Run(toxic)
		go link.stubs[i-1].Run(link.toxics.chain[link.direction][i-1])
	} else {
		// This link is already closed, make sure the new toxic matches
		link.stubs[i].Output = newin // The real output is already closed, close this instead
		link.stubs[i].Close()
	}
}

// Update an existing toxic in the chain.
func (link *ToxicLink) UpdateToxic(toxic *toxics.ToxicWrapper) {
	if link.stubs[toxic.Index].InterruptToxic() {
		go link.stubs[toxic.Index].Run(toxic)
	}
}

// Remove an existing toxic from the chain.
func (link *ToxicLink) RemoveToxic(ctx context.Context, toxic *toxics.ToxicWrapper) {
	toxic_index := toxic.Index
	log := zerolog.Ctx(ctx).
		With().
		Str("component", "ToxicLink").
		Str("method", "RemoveToxic").
		Str("toxic", toxic.Name).
		Str("toxic_type", toxic.Type).
		Int("toxic_index", toxic.Index).
		Str("link_addr", fmt.Sprintf("%p", link)).
		Str("toxic_stub_addr", fmt.Sprintf("%p", link.stubs[toxic_index])).
		Str("prev_toxic_stub_addr", fmt.Sprintf("%p", link.stubs[toxic_index-1])).
		Logger()

	if link.stubs[toxic_index].InterruptToxic() {
		cleanup, ok := toxic.Toxic.(toxics.CleanupToxic)
		if ok {
			cleanup.Cleanup(link.stubs[toxic_index])
			// Cleanup could have closed the stub.
			if link.stubs[toxic_index].Closed() {
				log.Trace().Msg("Cleanup closed toxic and removed toxic")
				// TODO: Check if cleanup happen would link.stubs recalculated?
				return
			}
		}

		log.Trace().Msg("Interrupting the previous toxic to update its output")
		stop := make(chan bool)
		go func(stub *toxics.ToxicStub, stop chan bool) {
			stop <- stub.InterruptToxic()
		}(link.stubs[toxic_index-1], stop)

		// Unblock the previous toxic if it is trying to flush
		// If the previous toxic is closed, continue flusing until we reach the end.
		interrupted := false
		stopped := false
		for !interrupted {
			select {
			case interrupted = <-stop:
				stopped = true
			case tmp := <-link.stubs[toxic_index].Input:
				if tmp == nil {
					link.stubs[toxic_index].Close()
					if !stopped {
						<-stop
					}
					return // TODO: There are some steps after this to clean buffer
				}

				err := link.stubs[toxic_index].WriteOutput(tmp, 5*time.Second)
				if err != nil {
					log.Err(err).
						Msg("Could not write last packets after interrupt to Output")
				}
			}
		}

		// Empty the toxic's buffer if necessary
		for len(link.stubs[toxic_index].Input) > 0 {
			tmp := <-link.stubs[toxic_index].Input
			if tmp == nil {
				link.stubs[toxic_index].Close()
				return
			}
			err := link.stubs[toxic_index].WriteOutput(tmp, 5*time.Second)
			if err != nil {
				log.Err(err).
					Msg("Could not write last packets after interrupt to Output")
			}
		}

		link.stubs[toxic_index-1].Output = link.stubs[toxic_index].Output
		link.stubs = append(link.stubs[:toxic_index], link.stubs[toxic_index+1:]...)

		go link.stubs[toxic_index-1].Run(link.toxics.chain[link.direction][toxic_index-1])
	}
}

// Direction returns the direction of the link (upstream or downstream).
func (link *ToxicLink) Direction() string {
	return link.direction.String()
}


================================================
FILE: link_test.go
================================================
package toxiproxy

import (
	"context"
	"encoding/binary"
	"flag"
	"io"
	"os"
	"testing"
	"time"

	"github.com/rs/zerolog"

	"github.com/Shopify/toxiproxy/v2/stream"
	"github.com/Shopify/toxiproxy/v2/testhelper"
	"github.com/Shopify/toxiproxy/v2/toxics"
)

func TestToxicsAreLoaded(t *testing.T) {
	if toxics.Count() < 1 {
		t.Fatal("No toxics loaded!")
	}
}

func TestStubInitializaation(t *testing.T) {
	collection := NewToxicCollection(nil)
	link := NewToxicLink(nil, collection, stream.Downstream, zerolog.Nop())
	if len(link.stubs) != 1 {
		t.Fatalf("Link created with wrong number of stubs: %d != 1", len(link.stubs))
	}

	if cap(link.stubs) != toxics.Count()+1 {
		t.Fatalf("Link created with wrong capacity: %d != %d", cap(link.stubs), toxics.Count()+1)
	}

	if cap(link.stubs[0].Input) != 0 {
		t.Fatalf("Noop buffer was not initialized as 0: %d", cap(link.stubs[0].Input))
	}

	if cap(link.stubs[0].Output) != 0 {
		t.Fatalf("Link output buffer was not initialized as 0: %d", cap(link.stubs[0].Output))
	}
}

func TestStubInitializaationWithToxics(t *testing.T) {
	collection := NewToxicCollection(nil)
	collection.chainAddToxic(&toxics.ToxicWrapper{
		Toxic:      new(toxics.LatencyToxic),
		Type:       "latency",
		Direction:  stream.Downstream,
		BufferSize: 1024,
		Toxicity:   1,
	})
	collection.chainAddToxic(&toxics.ToxicWrapper{
		Toxic:     new(toxics.BandwidthToxic),
		Type:      "bandwidth",
		Direction: stream.Downstream,
		Toxicity:  1,
	})
	link := NewToxicLink(nil, collection, stream.Downstream, zerolog.Nop())

	if len(link.stubs) != 3 {
		t.Fatalf("Link created with wrong number of stubs: %d != 3", len(link.stubs))
	}

	if cap(link.stubs) != toxics.Count()+1 {
		t.Fatalf("Link created with wrong capacity: %d != %d", cap(link.stubs), toxics.Count()+1)
	}

	if cap(link.stubs[len(link.stubs)-1].Output) != 0 {
		t.Fatalf("Link output buffer was not initialized as 0: %d", cap(link.stubs[0].Output))
	}

	for i, toxic := range collection.chain[stream.Downstream] {
		if cap(link.stubs[i].Input) != toxic.BufferSize {
			t.Fatalf(
				"%s buffer was not initialized as %d: %d",
				toxic.Type,
				toxic.BufferSize,
				cap(link.stubs[i].Input),
			)
		}
	}
}

func TestAddRemoveStubs(t *testing.T) {
	ctx := context.Background()
	collection := NewToxicCollection(nil)
	link := NewToxicLink(nil, collection, stream.Downstream, zerolog.Nop())
	go link.stubs[0].Run(collection.chain[stream.Downstream][0])
	collection.links["test"] = link

	// Add stubs
	collection.chainAddToxic(&toxics.ToxicWrapper{
		Toxic:      new(toxics.LatencyToxic),
		Type:       "latency",
		Direction:  stream.Downstream,
		BufferSize: 1024,
		Toxicity:   1,
	})
	toxic := &toxics.ToxicWrapper{
		Toxic:      new(toxics.BandwidthToxic),
		Type:       "bandwidth",
		Direction:  stream.Downstream,
		BufferSize: 2048,
		Toxicity:   1,
	}
	collection.chainAddToxic(toxic)
	if cap(link.stubs[len(link.stubs)-1].Output) != 0 {
		t.Fatalf("Link output buffer was not initialized as 0: %d", cap(link.stubs[0].Output))
	}
	for i, toxic := range collection.chain[stream.Downstream] {
		if cap(link.stubs[i].Input) != toxic.BufferSize {
			t.Fatalf(
				"%s buffer was not initialized as %d: %d",
				toxic.Type,
				toxic.BufferSize,
				cap(link.stubs[i].Input),
			)
		}
	}

	// Remove stubs
	collection.chainRemoveToxic(ctx, toxic)
	if cap(link.stubs[len(link.stubs)-1].Output) != 0 {
		t.Fatalf("Link output buffer was not initialized as 0: %d", cap(link.stubs[0].Output))
	}
	for i, toxic := range collection.chain[stream.Downstream] {
		if cap(link.stubs[i].Input) != toxic.BufferSize {
			t.Fatalf(
				"%s buffer was not initialized as %d: %d",
				toxic.Type,
				toxic.BufferSize,
				cap(link.stubs[i].Input),
			)
		}
	}
}

func TestNoDataDropped(t *testing.T) {
	ctx := context.Background()
	collection := NewToxicCollection(nil)
	link := NewToxicLink(nil, collection, stream.Downstream, zerolog.Nop())
	go link.stubs[0].Run(collection.chain[stream.Downstream][0])
	collection.links["test"] = link

	toxic := &toxics.ToxicWrapper{
		Toxic: &toxics.LatencyToxic{
			Latency: 1000,
		},
		Type:       "latency",
		Direction:  stream.Downstream,
		BufferSize: 1024,
		Toxicity:   1,
	}

	done := make(chan struct{})
	defer close(done)
	go func() {
		for i := uint16(0); i < 65535; i++ {
			buf := make([]byte, 2)
			binary.BigEndian.PutUint16(buf, i)
			link.input.Write(buf)
		}
		link.input.Close()
	}()
	go func(ctx context.Context) {
		for {
			select {
			case <-done:
				return
			default:
				collection.chainAddToxic(toxic)
				collection.chainRemoveToxic(ctx, toxic)
			}
		}
	}(ctx)

	buf := make([]byte, 2)
	for i := uint16(0); i < 65535; i++ {
		n, err := link.output.Read(buf)
		if n != 2 || err != nil {
			t.Fatalf("Read failed: %d %v", n, err)
		} else {
			val := binary.BigEndian.Uint16(buf)
			if val != i {
				t.Fatalf("Read incorrect bytes: %v != %d", val, i)
			}
		}
	}
	n, err := link.output.Read(buf)
	if n != 0 || err != io.EOF {
		t.Fatalf("Expected EOF: %d %v", n, err)
	}
}

func TestToxicity(t *testing.T) {
	collection := NewToxicCollection(nil)
	link := NewToxicLink(nil, collection, stream.Downstream, zerolog.Nop())
	go link.stubs[0].Run(collection.chain[stream.Downstream][0])
	collection.links["test"] = link

	toxic := &toxics.ToxicWrapper{
		Toxic:     new(toxics.TimeoutToxic),
		Name:      "timeout1",
		Type:      "timeout",
		Direction: stream.Downstream,
		Toxicity:  0,
	}
	collection.chainAddToxic(toxic)

	// Toxic should be a Noop because of toxicity
	n, err := link.input.Write([]byte{42})
	if n != 1 || err != nil {
		t.Fatalf("Write failed: %d %v", n, err)
	}
	buf := make([]byte, 2)
	n, err = link.output.Read(buf)
	if n != 1 || err != nil {
		t.Fatalf("Read failed: %d %v", n, err)
	} else if buf[0] != 42 {
		t.Fatalf("Read wrong byte: %x", buf[0])
	}

	toxic.Toxicity = 1
	toxic.Toxic.(*toxics.TimeoutToxic).Timeout = 100
	collection.chainUpdateToxic(toxic)

	err = testhelper.TimeoutAfter(150*time.Millisecond, func() {
		n, err = link.input.Write([]byte{42})
		if n != 1 || err != nil {
			t.Fatalf("Write failed: %d %v", n, err)
		}
		n, err = link.output.Read(buf)
		if n != 0 || err != io.EOF {
			t.Fatalf("Read did not get EOF: %d %v", n, err)
		}
	})
	if err != nil {
		t.Fatal(err)
	}
}

func TestStateCreated(t *testing.T) {
	collection := NewToxicCollection(nil)
	log := zerolog.Nop()
	if flag.Lookup("test.v").DefValue == "true" {
		log = zerolog.New(os.Stdout).With().Caller().Timestamp().Logger()
	}

	link := NewToxicLink(nil, collection, stream.Downstream, log)
	go link.stubs[0].Run(collection.chain[stream.Downstream][0])
	collection.links["test"] = link

	collection.chainAddToxic(&toxics.ToxicWrapper{
		Toxic:     new(toxics.LimitDataToxic),
		Type:      "limit_data",
		Direction: stream.Downstream,
		Toxicity:  1,
	})
	if link.stubs[len(link.stubs)-1].State == nil {
		t.Fatalf("New toxic did not have state object created.")
	}
}

func TestRemoveToxicWithBrokenConnection(t *testing.T) {
	ctx := context.Background()

	log := zerolog.Nop()
	if flag.Lookup("test.v").DefValue == "true" {
		log = zerolog.New(os.Stdout).With().Caller().Timestamp().Logger()
	}
	ctx = log.WithContext(ctx)

	collection := NewToxicCollection(nil)
	link := NewToxicLink(nil, collection, stream.Downstream, log)
	go link.stubs[0].Run(collection.chain[stream.Downstream][0])
	collection.links["test"] = link

	toxics := [2]*toxics.ToxicWrapper{
		{
			Toxic: &toxics.BandwidthToxic{
				Rate: 0,
			},
			Type:      "bandwidth",
			Direction: stream.Downstream,
			Toxicity:  1,
		},
		{
			Toxic: &toxics.BandwidthToxic{
				Rate: 0,
			},
			Type:      "bandwidth",
			Direction: stream.Upstream,
			Toxicity:  1,
		},
	}

	collection.chainAddToxic(toxics[0])
	collection.chainAddToxic(toxics[1])

	done := make(chan struct{})
	defer close(done)

	var data uint16 = 42
	go func(log zerolog.Logger) {
		for {
			select {
			case <-done:
				link.input.Close()
				return
			case <-time.After(10 * time.Second):
				log.Print("Finish load")
				return
			default:
				buf := make([]byte, 2)
				binary.BigEndian.PutUint16(buf, data)
				link.input.Write(buf)
			}
		}
	}(log)

	collection.chainRemoveToxic(ctx, toxics[0])
	collection.chainRemoveToxic(ctx, toxics[1])
}


================================================
FILE: metrics.go
================================================
package toxiproxy

import (
	"net/http"

	"github.com/Shopify/toxiproxy/v2/collectors"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

// NewMetricsContainer initializes a container for storing all prometheus metrics.
func NewMetricsContainer(registry *prometheus.Registry) *metricsContainer {
	if registry == nil {
		registry = prometheus.NewRegistry()
	}
	return &metricsContainer{
		registry: registry,
	}
}

type metricsContainer struct {
	RuntimeMetrics *collectors.RuntimeMetricCollectors
	ProxyMetrics   *collectors.ProxyMetricCollectors

	registry *prometheus.Registry
}

func (m *metricsContainer) runtimeMetricsEnabled() bool {
	return m.RuntimeMetrics != nil
}

func (m *metricsContainer) proxyMetricsEnabled() bool {
	return m.ProxyMetrics != nil
}

// anyMetricsEnabled determines whether we have any prometheus metrics registered for exporting.
func (m *metricsContainer) anyMetricsEnabled() bool {
	return m.runtimeMetricsEnabled() || m.proxyMetricsEnabled()
}

// handler returns an HTTP handler with the necessary collectors registered
// via a global prometheus registry.
func (m *metricsContainer) handler() http.Handler {
	if m.runtimeMetricsEnabled() {
		m.registry.MustRegister(m.RuntimeMetrics.Collectors()...)
	}
	if m.proxyMetricsEnabled() {
		m.registry.MustRegister(m.ProxyMetrics.Collectors()...)
	}
	return promhttp.HandlerFor(
		m.registry, promhttp.HandlerOpts{Registry: m.registry})
}


================================================
FILE: metrics_test.go
================================================
package toxiproxy

import (
	"bufio"
	"bytes"
	"net/http"
	"net/http/httptest"
	"reflect"
	"regexp"
	"strings"
	"testing"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/rs/zerolog"

	"github.com/Shopify/toxiproxy/v2/collectors"
	"github.com/Shopify/toxiproxy/v2/stream"
)

func TestProxyMetricsReceivedSentBytes(t *testing.T) {
	srv := NewServer(NewMetricsContainer(prometheus.NewRegistry()), zerolog.Nop())
	srv.Metrics.ProxyMetrics = collectors.NewProxyMetricCollectors()

	proxy := NewProxy(srv, "test_proxy_metrics_received_sent_bytes", "localhost:0", "upstream")

	r := bufio.NewReader(bytes.NewBufferString("hello"))
	w := &testWriteCloser{
		bufio.NewWriter(bytes.NewBuffer([]byte{})),
	}
	linkName := "testupstream"
	proxy.Toxics.StartLink(srv, linkName, r, w, stream.Upstream)
	proxy.Toxics.RemoveLink(linkName)

	actual := prometheusOutput(t, srv, "toxiproxy_proxy")

	expected := []string{
		`toxiproxy_proxy_received_bytes_total{` +
			`direction="upstream",listener="localhost:0",` +
			`proxy="test_proxy_metrics_received_sent_bytes",upstream="upstream"` +
			`} 5`,

		`toxiproxy_proxy_sent_bytes_total{` +
			`direction="upstream",listener="localhost:0",` +
			`proxy="test_proxy_metrics_received_sent_bytes",upstream="upstream"` +
			`} 5`,
	}

	if !reflect.DeepEqual(actual, expected) {
		t.Fatalf(
			"\nexpected:\n  [%v]\ngot:\n  [%v]",
			strings.Join(expected, "\n  "),
			strings.Join(actual, "\n  "),
		)
	}
}

func TestRuntimeMetricsBuildInfo(t *testing.T) {
	srv := NewServer(NewMetricsContainer(prometheus.NewRegistry()), zerolog.Nop())
	srv.Metrics.RuntimeMetrics = collectors.NewRuntimeMetricCollectors()

	expected := `go_build_info{checksum="[^"]*",path="[^"]*",version="[^"]*"} 1`

	actual := prometheusOutput(t, srv, "go_build_info")

	if len(actual) != 1 {
		t.Fatalf(
			"\nexpected: 1 item\ngot: %d item(s)\nmetrics:\n  %+v",
			len(actual),
			strings.Join(actual, "\n  "),
		)
	}

	matched, err := regexp.MatchString(expected, actual[0])
	if err != nil {
		t.Fatalf("Unexpected error: %s", err)
	}
	if !matched {
		t.Fatalf("\nexpected:\n  %v\nto match:\n  %v", actual[0], expected)
	}
}

type testWriteCloser struct {
	*bufio.Writer
}

func (t *testWriteCloser) Close() error {
	return t.Flush()
}

func prometheusOutput(t *testing.T, apiServer *ApiServer, prefix string) []string {
	t.Helper()

	testServer := httptest.NewServer(apiServer.Metrics.handler())
	defer testServer.Close()

	resp, err := http.Get(testServer.URL)
	if err != nil {
		t.Fatal(err)
	}
	defer resp.Body.Close()

	var selected []string
	s := bufio.NewScanner(resp.Body)
	for s.Scan() {
		if strings.HasPrefix(s.Text(), prefix) {
			selected = append(selected, s.Text())
		}
	}
	return selected
}


================================================
FILE: proxy.go
================================================
package toxiproxy

import (
	"errors"
	"net"
	"sync"

	"github.com/rs/zerolog"
	tomb "gopkg.in/tomb.v1"

	"github.com/Shopify/toxiproxy/v2/stream"
)

// Proxy represents the proxy in its entirety with all its links. The main
// responsibility of Proxy is to accept new client and create Links between the
// client and upstream.
//
// Client <-> toxiproxy <-> Upstream.
type Proxy struct {
	sync.Mutex

	Name     string `json:"name"`
	Listen   string `json:"listen"`
	Upstream string `json:"upstream"`
	Enabled  bool   `json:"enabled"`

	listener net.Listener
	started  chan error

	tomb        tomb.Tomb
	connections ConnectionList
	Toxics      *ToxicCollection `json:"-"`
	apiServer   *ApiServer
	Logger      *zerolog.Logger
}

type ConnectionList struct {
	list map[string]net.Conn
	lock sync.Mutex
}

func (c *ConnectionList) Lock() {
	c.lock.Lock()
}

func (c *ConnectionList) Unlock() {
	c.lock.Unlock()
}

var ErrProxyAlreadyStarted = errors.New("Proxy already started")

func NewProxy(server *ApiServer, name, listen, upstream string) *Proxy {
	l := server.Logger.
		With().
		Str("name", name).
		Str("listen", listen).
		Str("upstream", upstream).
		Logger()

	proxy := &Proxy{
		Name:        name,
		Listen:      listen,
		Upstream:    upstream,
		started:     make(chan error),
		connections: ConnectionList{list: make(map[string]net.Conn)},
		apiServer:   server,
		Logger:      &l,
	}
	proxy.Toxics = NewToxicCollection(proxy)
	return proxy
}

func (proxy *Proxy) Start() error {
	proxy.Lock()
	defer proxy.Unlock()

	return start(proxy)
}

func (proxy *Proxy) Update(input *Proxy) error {
	proxy.Lock()
	defer proxy.Unlock()

	differs, err := proxy.Differs(input)
	if err != nil {
		return err
	}

	if differs {
		stop(proxy)
		proxy.Listen = input.Listen
		proxy.Upstream = input.Upstream
	}

	if input.Enabled != proxy.Enabled {
		if input.Enabled {
			return start(proxy)
		}
		stop(proxy)
	}
	return nil
}

func (proxy *Proxy) Stop() {
	proxy.Lock()
	defer proxy.Unlock()

	stop(proxy)
}

func (proxy *Proxy) listen() error {
	var err error
	proxy.listener, err = net.Listen("tcp", proxy.Listen)
	if err != nil {
		proxy.started <- err
		return err
	}
	proxy.Listen = proxy.listener.Addr().String()
	proxy.started <- nil

	proxy.Logger.
		Info().
		Msg("Started proxy")

	return nil
}

func (proxy *Proxy) close() {
	// Unblock proxy.listener.Accept()
	err := proxy.listener.Close()
	if err != nil {
		proxy.Logger.
			Warn().
			Err(err).
			Msg("Attempted to close an already closed proxy server")
	}
}

func (proxy *Proxy) Differs(other *Proxy) (bool, error) {
	newResolvedListen, err := net.ResolveTCPAddr("tcp", other.Listen)
	if err != nil {
		return false, err
	}

	if proxy.Listen != newResolvedListen.String() || proxy.Upstream != other.Upstream {
		return true, nil
	}

	return false, nil
}

// This channel is to kill the blocking Accept() call below by closing the
// net.Listener.
func (proxy *Proxy) freeBlocker(acceptTomb *tomb.Tomb) {
	<-proxy.tomb.Dying()

	// Notify ln.Accept() that the shutdown was safe
	acceptTomb.Killf("Shutting down from stop()")

	proxy.close()

	// Wait for the accept loop to finish processing
	acceptTomb.Wait()
	proxy.tomb.Done()
}

// server runs the Proxy server, accepting new clients and creating Links to
// connect them to upstreams.
func (proxy *Proxy) server() {
	err := proxy.listen()
	if err != nil {
		return
	}

	acceptTomb := &tomb.Tomb{}
	defer acceptTomb.Done()

	// This channel is to kill the blocking Accept() call below by closing the
	// net.Listener.
	go proxy.freeBlocker(acceptTomb)

	for {
		client, err := proxy.listener.Accept()
		if err != nil {
			// This is to confirm we're being shut down in a legit way. Unfortunately,
			// Go doesn't export the error when it's closed from Close() so we have to
			// sync up with a channel here.
			//
			// See http://zhen.org/blog/graceful-shutdown-of-go-net-dot-listeners/
			select {
			case <-acceptTomb.Dying():
			default:
				proxy.Logger.
					Warn().
					Err(err).
					Msg("Error while accepting client")
			}
			return
		}

		proxy.Logger.
			Info().
			Str("client", client.RemoteAddr().String()).
			Msg("Accepted client")

		upstream, err := net.Dial("tcp", proxy.Upstream)
		if err != nil {
			proxy.Logger.
				Err(err).
				Str("client", client.RemoteAddr().String()).
				Msg("Unable to open connection to upstream")
			client.Close()
			continue
		}

		name := client.RemoteAddr().String()
		proxy.connections.Lock()
		proxy.connections.list[name+"upstream"] = upstream
		proxy.connections.list[name+"downstream"] = client
		proxy.connections.Unlock()
		proxy.Toxics.StartLink(proxy.apiServer, name+"upstream", client, upstream, stream.Upstream)
		proxy.Toxics.StartLink(proxy.apiServer, name+"downstream", upstream, client, stream.Downstream)
	}
}

func (proxy *Proxy) RemoveConnection(name string) {
	proxy.connections.Lock()
	defer proxy.connections.Unlock()
	delete(proxy.connections.list, name)
}

// Starts a proxy, assumes the lock has already been taken.
func start(proxy *Proxy) error {
	if proxy.Enabled {
		return ErrProxyAlreadyStarted
	}

	proxy.tomb = tomb.Tomb{} // Reset tomb, from previous starts/stops
	go proxy.server()
	err := <-proxy.started
	// Only enable the proxy if it successfully started
	proxy.Enabled = err == nil
	return err
}

// Stops a proxy, assumes the lock has already been taken.
func stop(proxy *Proxy) {
	if !proxy.Enabled {
		return
	}
	proxy.Enabled = false

	proxy.tomb.Killf("Shutting down from stop()")
	proxy.tomb.Wait() // Wait until we stop accepting new connections

	proxy.connections.Lock()
	defer proxy.connections.Unlock()
	for _, conn := range proxy.connections.list {
		conn.Close()
	}

	proxy.Logger.
		Info().
		Msg("Terminated proxy")
}


================================================
FILE: proxy_collection.go
================================================
package toxiproxy

import (
	"encoding/json"
	"fmt"
	"io"
	"sync"
)

// ProxyCollection is a collection of proxies. It's the interface for anything
// to add and remove proxies from the toxiproxy instance. It's responsibility is
// to maintain the integrity of the proxy set, by guarding for things such as
// duplicate names.
type ProxyCollection struct {
	sync.RWMutex

	proxies map[string]*Proxy
}

func NewProxyCollection() *ProxyCollection {
	return &ProxyCollection{
		proxies: make(map[string]*Proxy),
	}
}

func (collection *ProxyCollection) Add(proxy *Proxy, start bool) error {
	collection.Lock()
	defer collection.Unlock()

	if _, exists := collection.proxies[proxy.Name]; exists {
		return ErrProxyAlreadyExists
	}

	if start {
		err := proxy.Start()
		if err != nil {
			return err
		}
	}

	collection.proxies[proxy.Name] = proxy

	return nil
}

func (collection *ProxyCollection) AddOrReplace(proxy *Proxy, start bool) (*Proxy, error) {
	collection.Lock()
	defer collection.Unlock()

	if existing, exists := collection.proxies[proxy.Name]; exists {
		differs, err := existing.Differs(proxy)
		if err != nil {
			return nil, err
		}

		if !differs {
			return existing, nil
		}
		existing.Stop()
	}

	if start {
		err := proxy.Start()
		if err != nil {
			return nil, err
		}
	}

	collection.proxies[proxy.Name] = proxy

	return proxy, nil
}

func (collection *ProxyCollection) PopulateJson(
	server *ApiServer,
	data io.Reader,
) ([]*Proxy, error) {
	input := []struct {
		Proxy
		Enabled *bool `json:"enabled"` // Overrides Proxy field to make field nullable
	}{}

	err := json.NewDecoder(data).Decode(&input)
	if err != nil {
		return nil, joinError(err, ErrBadRequestBody)
	}

	// Check for valid input before creating any proxies
	t := true
	for i := range input {
		if len(input[i].Name) < 1 {
			return nil, joinError(fmt.Errorf("name at proxy %d", i+1), ErrMissingField)
		}
		if len(input[i].Upstream) < 1 {
			return nil, joinError(fmt.Errorf("upstream at proxy %d", i+1), ErrMissingField)
		}
		if input[i].Enabled == nil {
			input[i].Enabled = &t
		}
	}

	proxies := make([]*Proxy, 0, len(input))

	for i := range input {
		proxy := NewProxy(server, input[i].Name, input[i].Listen, input[i].Upstream)
		addedOrReplaced, err := collection.AddOrReplace(proxy, *input[i].Enabled)
		if err != nil {
			return proxies, err
		}

		proxies = append(proxies, addedOrReplaced)
	}
	return proxies, err
}

func (collection *ProxyCollection) Proxies() map[string]*Proxy {
	collection.RLock()
	defer collection.RUnlock()

	// Copy the map since using the existing one isn't thread-safe
	proxies := make(map[string]*Proxy, len(collection.proxies))
	for k, v := range collection.proxies {
		proxies[k] = v
	}
	return proxies
}

func (collection *ProxyCollection) Get(name string) (*Proxy, error) {
	collection.RLock()
	defer collection.RUnlock()

	return collection.getByName(name)
}

func (collection *ProxyCollection) Remove(name string) error {
	collection.Lock()
	defer collection.Unlock()

	proxy, err := collection.getByName(name)
	if err != nil {
		return err
	}
	proxy.Stop()

	delete(collection.proxies, proxy.Name)
	return nil
}

func (collection *ProxyCollection) Clear() error {
	collection.Lock()
	defer collection.Unlock()

	for _, proxy := range collection.proxies {
		proxy.Stop()

		delete(collection.proxies, proxy.Name)
	}

	return nil
}

// getByName returns a proxy by its name. Its used from #remove and #get.
// It assumes the lock has already been acquired.
func (collection *ProxyCollection) getByName(name string) (*Proxy, error) {
	proxy, exists := collection.proxies[name]
	if !exists {
		return nil, ErrProxyNotFound
	}
	return proxy, nil
}


================================================
FILE: proxy_collection_test.go
================================================
package toxiproxy_test

import (
	"bytes"
	"net"
	"testing"

	"github.com/Shopify/toxiproxy/v2"
)

func TestAddProxyToCollection(t *testing.T) {
	collection := toxiproxy.NewProxyCollection()
	proxy := NewTestProxy("test", "localhost:20000")

	if _, err := collection.Get(proxy.Name); err == nil {
		t.Error("Expected proxies to be empty")
	}

	err := collection.Add(proxy, false)
	if err != nil {
		t.Error("Expected to be able to add first proxy to collection")
	}

	if _, err := collection.Get(proxy.Name); err != nil {
		t.Error("Expected proxy to be added to map")
	}
}

func TestAddTwoProxiesToCollection(t *testing.T) {
	collection := toxiproxy.NewProxyCollection()
	proxy := NewTestProxy("test", "localhost:20000")

	err := collection.Add(proxy, false)
	if err != nil {
		t.Error("Expected to be able to add first proxy to collectio
Download .txt
gitextract_8n0j6szh/

├── .editorconfig
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── analysis.yml
│       ├── cla.yml
│       ├── release.yml
│       └── test.yml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── .yamllint.yml
├── CHANGELOG.md
├── CREATING_TOXICS.md
├── Dockerfile
├── LICENSE
├── METRICS.md
├── Makefile
├── README.md
├── RELEASE.md
├── _examples/
│   ├── tests/
│   │   ├── README.md
│   │   ├── cluster.yml
│   │   ├── db.go
│   │   ├── db_test.go
│   │   ├── main.go
│   │   ├── models.go
│   │   └── resources.yml
│   └── toxics/
│       ├── README.md
│       ├── debug_toxic.go
│       └── http_toxic.go
├── api.go
├── api_test.go
├── client/
│   ├── README.md
│   ├── api_error.go
│   ├── client.go
│   ├── client_test.go
│   ├── proxy.go
│   └── toxic.go
├── cmd/
│   ├── cli/
│   │   └── cli.go
│   └── server/
│       └── server.go
├── collectors/
│   ├── common.go
│   ├── proxy.go
│   └── runtime.go
├── dev.yml
├── go.mod
├── go.sum
├── link.go
├── link_test.go
├── metrics.go
├── metrics_test.go
├── proxy.go
├── proxy_collection.go
├── proxy_collection_test.go
├── proxy_test.go
├── scripts/
│   ├── hazelcast.xml
│   ├── test-e2e
│   ├── test-e2e-hazelcast
│   └── test-release
├── share/
│   └── toxiproxy.conf
├── stream/
│   ├── direction.go
│   ├── direction_test.go
│   ├── io_chan.go
│   └── io_chan_test.go
├── test/
│   └── e2e/
│       ├── benchmark_test.go
│       └── endpoint.go
├── testhelper/
│   ├── tcp_server.go
│   ├── tcp_server_test.go
│   ├── timeout_after.go
│   ├── timeout_after_test.go
│   └── upstream.go
├── toxic_collection.go
├── toxics/
│   ├── bandwidth.go
│   ├── bandwidth_test.go
│   ├── latency.go
│   ├── latency_test.go
│   ├── limit_data.go
│   ├── limit_data_test.go
│   ├── noop.go
│   ├── reset_peer.go
│   ├── reset_peer_test.go
│   ├── slicer.go
│   ├── slicer_test.go
│   ├── slow_close.go
│   ├── timeout.go
│   ├── timeout_test.go
│   ├── toxic.go
│   └── toxic_test.go
├── toxiproxy_test.go
└── version.go
Download .txt
SYMBOL INDEX (401 symbols across 55 files)

FILE: _examples/tests/db.go
  function setupDB (line 8) | func setupDB(addr, database string) (*pg.DB, error) {
  function createSchema (line 28) | func createSchema(db *pg.DB) error {
  function seed (line 45) | func seed(db *pg.DB) error {

FILE: _examples/tests/db_test.go
  function DB (line 21) | func DB() *pg.DB {
  function connectDB (line 32) | func connectDB(addr string) *pg.DB {
  function init (line 40) | func init() {
  function populateProxies (line 47) | func populateProxies() {
  function runToxiproxyServer (line 69) | func runToxiproxyServer() {
  function TestSlowDBConnection (line 96) | func TestSlowDBConnection(t *testing.T) {
  function TestOutageResetPeer (line 111) | func TestOutageResetPeer(t *testing.T) {

FILE: _examples/tests/main.go
  function main (line 11) | func main() {
  function run (line 19) | func run() error {
  function process (line 36) | func process(db *pg.DB) error {

FILE: _examples/tests/models.go
  type User (line 5) | type User struct
    method String (line 11) | func (u User) String() string {
  type Story (line 15) | type Story struct
    method String (line 22) | func (s Story) String() string {

FILE: _examples/toxics/debug_toxic.go
  type DebugToxic (line 20) | type DebugToxic struct
    method PrintHex (line 22) | func (t *DebugToxic) PrintHex(data []byte) {
    method Pipe (line 38) | func (t *DebugToxic) Pipe(stub *toxics.ToxicStub) {
  function main (line 59) | func main() {

FILE: _examples/toxics/http_toxic.go
  type HttpToxic (line 21) | type HttpToxic struct
    method ModifyResponse (line 23) | func (t *HttpToxic) ModifyResponse(resp *http.Response) {
    method Pipe (line 27) | func (t *HttpToxic) Pipe(stub *toxics.ToxicStub) {
  function main (line 52) | func main() {

FILE: api.go
  function stopBrowsersMiddleware (line 19) | func stopBrowsersMiddleware(next http.Handler) http.Handler {
  function timeoutMiddleware (line 29) | func timeoutMiddleware(next http.Handler) http.Handler {
  type ApiServer (line 33) | type ApiServer struct
    method Listen (line 53) | func (server *ApiServer) Listen(addr string) error {
    method Shutdown (line 75) | func (server *ApiServer) Shutdown() error {
    method Routes (line 91) | func (server *ApiServer) Routes() *mux.Router {
    method PopulateConfig (line 146) | func (server *ApiServer) PopulateConfig(filename string) {
    method ProxyIndex (line 162) | func (server *ApiServer) ProxyIndex(response http.ResponseWriter, requ...
    method ResetState (line 183) | func (server *ApiServer) ResetState(response http.ResponseWriter, requ...
    method ProxyCreate (line 204) | func (server *ApiServer) ProxyCreate(response http.ResponseWriter, req...
    method Populate (line 242) | func (server *ApiServer) Populate(response http.ResponseWriter, reques...
    method ProxyShow (line 276) | func (server *ApiServer) ProxyShow(response http.ResponseWriter, reque...
    method ProxyUpdate (line 296) | func (server *ApiServer) ProxyUpdate(response http.ResponseWriter, req...
    method ProxyDelete (line 333) | func (server *ApiServer) ProxyDelete(response http.ResponseWriter, req...
    method ToxicIndex (line 349) | func (server *ApiServer) ToxicIndex(response http.ResponseWriter, requ...
    method ToxicCreate (line 371) | func (server *ApiServer) ToxicCreate(response http.ResponseWriter, req...
    method ToxicShow (line 397) | func (server *ApiServer) ToxicShow(response http.ResponseWriter, reque...
    method ToxicUpdate (line 424) | func (server *ApiServer) ToxicUpdate(response http.ResponseWriter, req...
    method ToxicDelete (line 454) | func (server *ApiServer) ToxicDelete(response http.ResponseWriter, req...
    method Version (line 476) | func (server *ApiServer) Version(response http.ResponseWriter, request...
    method apiError (line 521) | func (server *ApiServer) apiError(resp http.ResponseWriter, err error)...
  constant wait_timeout (line 41) | wait_timeout = 30 * time.Second
  constant read_timeout (line 42) | read_timeout = 15 * time.Second
  function NewServer (line 45) | func NewServer(m *metricsContainer, logger zerolog.Logger) *ApiServer {
  type ApiError (line 487) | type ApiError struct
    method Error (line 492) | func (e *ApiError) Error() string {
  function newError (line 496) | func newError(msg string, status int) *ApiError {
  function joinError (line 500) | func joinError(err error, wrapper *ApiError) *ApiError {
  type proxyToxics (line 542) | type proxyToxics struct
  function proxyWithToxics (line 547) | func proxyWithToxics(proxy *Proxy) (result proxyToxics) {
  function proxiesWithToxics (line 553) | func proxiesWithToxics(proxies []*Proxy) (result []proxyToxics) {

FILE: api_test.go
  function WithServer (line 23) | func WithServer(t *testing.T, f func(string)) {
  function TestRequestId (line 53) | func TestRequestId(t *testing.T) {
  function TestBrowserGets403 (line 72) | func TestBrowserGets403(t *testing.T) {
  function TestNonBrowserGets200 (line 95) | func TestNonBrowserGets200(t *testing.T) {
  function TestIndexWithNoProxies (line 114) | func TestIndexWithNoProxies(t *testing.T) {
  function TestCreateProxyBlankName (line 128) | func TestCreateProxyBlankName(t *testing.T) {
  function TestCreateProxyBlankUpstream (line 141) | func TestCreateProxyBlankUpstream(t *testing.T) {
  function TestPopulateProxy (line 152) | func TestPopulateProxy(t *testing.T) {
  function TestPopulateDefaultEnabled (line 187) | func TestPopulateDefaultEnabled(t *testing.T) {
  function TestPopulateDisabledProxy (line 219) | func TestPopulateDisabledProxy(t *testing.T) {
  function TestPopulateExistingProxy (line 253) | func TestPopulateExistingProxy(t *testing.T) {
  function TestPopulateWithBadName (line 319) | func TestPopulateWithBadName(t *testing.T) {
  function TestPopulateProxyWithBadDataShouldReturnError (line 357) | func TestPopulateProxyWithBadDataShouldReturnError(t *testing.T) {
  function TestPopulateAddToxic (line 413) | func TestPopulateAddToxic(t *testing.T) {
  function TestListingProxies (line 443) | func TestListingProxies(t *testing.T) {
  function TestCreateAndGetProxy (line 475) | func TestCreateAndGetProxy(t *testing.T) {
  function TestCreateProxyWithSave (line 503) | func TestCreateProxyWithSave(t *testing.T) {
  function TestCreateDisabledProxy (line 537) | func TestCreateDisabledProxy(t *testing.T) {
  function TestCreateDisabledProxyAndEnable (line 570) | func TestCreateDisabledProxyAndEnable(t *testing.T) {
  function TestDeleteProxy (line 619) | func TestDeleteProxy(t *testing.T) {
  function TestCreateProxyPortConflict (line 663) | func TestCreateProxyPortConflict(t *testing.T) {
  function TestCreateProxyNameConflict (line 689) | func TestCreateProxyNameConflict(t *testing.T) {
  function TestResetState (line 715) | func TestResetState(t *testing.T) {
  function TestListingToxics (line 763) | func TestListingToxics(t *testing.T) {
  function TestAddToxic (line 779) | func TestAddToxic(t *testing.T) {
  function TestAddMultipleToxics (line 810) | func TestAddMultipleToxics(t *testing.T) {
  function TestAddConflictingToxic (line 842) | func TestAddConflictingToxic(t *testing.T) {
  function TestAddConflictingToxicsMultistream (line 874) | func TestAddConflictingToxicsMultistream(t *testing.T) {
  function TestAddConflictingToxicsMultistreamDefaults (line 907) | func TestAddConflictingToxicsMultistreamDefaults(t *testing.T) {
  function TestAddToxicWithToxicity (line 941) | func TestAddToxicWithToxicity(t *testing.T) {
  function TestAddNoop (line 973) | func TestAddNoop(t *testing.T) {
  function TestUpdateToxics (line 1001) | func TestUpdateToxics(t *testing.T) {
  function TestRemoveToxic (line 1058) | func TestRemoveToxic(t *testing.T) {
  function TestVersionEndpointReturnsVersion (line 1094) | func TestVersionEndpointReturnsVersion(t *testing.T) {
  function TestInvalidStream (line 1113) | func TestInvalidStream(t *testing.T) {
  function AssertToxicExists (line 1127) | func AssertToxicExists(

FILE: client/api_error.go
  type ApiError (line 12) | type ApiError struct
    method Error (line 17) | func (err *ApiError) Error() string {

FILE: client/client.go
  type Client (line 18) | type Client struct
    method Version (line 45) | func (client *Client) Version() ([]byte, error) {
    method Proxies (line 50) | func (client *Client) Proxies() (map[string]*Proxy, error) {
    method NewProxy (line 72) | func (client *Client) NewProxy() *Proxy {
    method CreateProxy (line 80) | func (client *Client) CreateProxy(name, listen, upstream string) (*Pro...
    method Proxy (line 98) | func (client *Client) Proxy(name string) (*Proxy, error) {
    method Populate (line 119) | func (client *Client) Populate(config []Proxy) ([]*Proxy, error) {
    method AddToxic (line 146) | func (client *Client) AddToxic(options *ToxicOptions) (*Toxic, error) {
    method UpdateToxic (line 168) | func (client *Client) UpdateToxic(options *ToxicOptions) (*Toxic, erro...
    method RemoveToxic (line 192) | func (client *Client) RemoveToxic(options *ToxicOptions) error {
    method ResetState (line 210) | func (client *Client) ResetState() error {
    method get (line 215) | func (c *Client) get(path string) ([]byte, error) {
    method post (line 219) | func (c *Client) post(path string, body io.Reader) ([]byte, error) {
    method patch (line 223) | func (c *Client) patch(path string, body io.Reader) ([]byte, error) {
    method delete (line 227) | func (c *Client) delete(path string) error {
    method send (line 232) | func (c *Client) send(verb, path string, body io.Reader) ([]byte, erro...
    method validateResponse (line 260) | func (c *Client) validateResponse(resp *http.Response) error {
  function NewClient (line 27) | func NewClient(endpoint string) *Client {

FILE: client/client_test.go
  function TestClient_Headers (line 11) | func TestClient_Headers(t *testing.T) {

FILE: client/proxy.go
  type Proxy (line 13) | type Proxy struct
    method Save (line 28) | func (proxy *Proxy) Save() error {
    method Enable (line 58) | func (proxy *Proxy) Enable() error {
    method Disable (line 64) | func (proxy *Proxy) Disable() error {
    method Delete (line 72) | func (proxy *Proxy) Delete() error {
    method Toxics (line 81) | func (proxy *Proxy) Toxics() (Toxics, error) {
    method AddToxic (line 100) | func (proxy *Proxy) AddToxic(
    method UpdateToxic (line 134) | func (proxy *Proxy) UpdateToxic(name string, toxicity float32, attrs A...
    method RemoveToxic (line 164) | func (proxy *Proxy) RemoveToxic(name string) error {

FILE: client/toxic.go
  type Attributes (line 7) | type Attributes
  type Toxic (line 9) | type Toxic struct
  type Toxics (line 17) | type Toxics
  type ToxicOptions (line 19) | type ToxicOptions struct

FILE: cmd/cli/cli.go
  constant RED (line 19) | RED    = "\x1b[31m"
  constant GREEN (line 20) | GREEN  = "\x1b[32m"
  constant YELLOW (line 21) | YELLOW = "\x1b[33m"
  constant BLUE (line 22) | BLUE   = "\x1b[34m"
  constant PURPLE (line 23) | PURPLE = "\x1b[35m"
  constant NONE (line 24) | NONE   = "\x1b[0m"
  function color (line 27) | func color(color string) string {
  function main (line 81) | func main() {
  function cliCommands (line 107) | func cliCommands() []*cli.Command {
  function cliToxiSubCommands (line 163) | func cliToxiSubCommands() []*cli.Command {
  function cliToxiAddSubCommand (line 171) | func cliToxiAddSubCommand() *cli.Command {
  function cliToxiUpdateSubCommand (line 216) | func cliToxiUpdateSubCommand() *cli.Command {
  function cliToxiRemoveSubCommand (line 244) | func cliToxiRemoveSubCommand() *cli.Command {
  type toxiAction (line 261) | type toxiAction
  function withToxi (line 263) | func withToxi(f toxiAction) func(*cli.Context) error {
  function list (line 276) | func list(c *cli.Context, t *toxiproxy.Client) error {
  function inspectProxy (line 326) | func inspectProxy(c *cli.Context, t *toxiproxy.Client) error {
  function toggleProxy (line 376) | func toggleProxy(c *cli.Context, t *toxiproxy.Client) error {
  function createProxy (line 407) | func createProxy(c *cli.Context, t *toxiproxy.Client) error {
  function deleteProxy (line 429) | func deleteProxy(c *cli.Context, t *toxiproxy.Client) error {
  function parseToxicity (line 448) | func parseToxicity(c *cli.Context, defaultToxicity float32) (float32, er...
  function addToxic (line 461) | func addToxic(c *cli.Context, t *toxiproxy.Client) error {
  function updateToxic (line 483) | func updateToxic(c *cli.Context, t *toxiproxy.Client) error {
  function removeToxic (line 502) | func removeToxic(c *cli.Context, t *toxiproxy.Client) error {
  function parseToxicCommonParams (line 517) | func parseToxicCommonParams(context *cli.Context) (*toxiproxy.ToxicOptio...
  function parseUpdateToxicParams (line 532) | func parseUpdateToxicParams(c *cli.Context) (*toxiproxy.ToxicOptions, er...
  function parseAddToxicParams (line 548) | func parseAddToxicParams(c *cli.Context) (*toxiproxy.ToxicOptions, error) {
  function parseAttributes (line 581) | func parseAttributes(c *cli.Context, name string) toxiproxy.Attributes {
  function colorEnabled (line 599) | func colorEnabled(enabled bool) string {
  function enabledText (line 607) | func enabledText(enabled bool) string {
  type attribute (line 615) | type attribute struct
  type attributeList (line 620) | type attributeList
  function sortedAttributes (line 622) | func sortedAttributes(attrs toxiproxy.Attributes) attributeList {
  function listToxics (line 634) | func listToxics(toxics toxiproxy.Toxics, stream string) {
  function getArgOrFail (line 661) | func getArgOrFail(c *cli.Context, name string) (string, error) {
  function hint (line 670) | func hint(m string) {
  function errorf (line 676) | func errorf(m string, args ...interface{}) error {
  function printWidth (line 680) | func printWidth(col string, m string, numTabs int) {

FILE: cmd/server/server.go
  type cliArguments (line 22) | type cliArguments struct
  function parseArguments (line 32) | func parseArguments() cliArguments {
  function main (line 53) | func main() {
  function run (line 62) | func run() error {
  function setupLogger (line 112) | func setupLogger() zerolog.Logger {

FILE: collectors/common.go
  constant namespace (line 4) | namespace string = "toxiproxy"

FILE: collectors/proxy.go
  type ProxyMetricCollectors (line 7) | type ProxyMetricCollectors struct
    method Collectors (line 15) | func (c *ProxyMetricCollectors) Collectors() []prometheus.Collector {
  function NewProxyMetricCollectors (line 19) | func NewProxyMetricCollectors() *ProxyMetricCollectors {

FILE: collectors/runtime.go
  type RuntimeMetricCollectors (line 8) | type RuntimeMetricCollectors struct
    method Collectors (line 12) | func (c *RuntimeMetricCollectors) Collectors() []prometheus.Collector {
  function NewRuntimeMetricCollectors (line 16) | func NewRuntimeMetricCollectors() *RuntimeMetricCollectors {

FILE: link.go
  type ToxicLink (line 24) | type ToxicLink struct
    method Start (line 70) | func (link *ToxicLink) Start(
    method read (line 116) | func (link *ToxicLink) read(
    method write (line 137) | func (link *ToxicLink) write(
    method AddToxic (line 171) | func (link *ToxicLink) AddToxic(toxic *toxics.ToxicWrapper) {
    method UpdateToxic (line 195) | func (link *ToxicLink) UpdateToxic(toxic *toxics.ToxicWrapper) {
    method RemoveToxic (line 202) | func (link *ToxicLink) RemoveToxic(ctx context.Context, toxic *toxics....
    method Direction (line 281) | func (link *ToxicLink) Direction() string {
  function NewToxicLink (line 34) | func NewToxicLink(

FILE: link_test.go
  function TestToxicsAreLoaded (line 19) | func TestToxicsAreLoaded(t *testing.T) {
  function TestStubInitializaation (line 25) | func TestStubInitializaation(t *testing.T) {
  function TestStubInitializaationWithToxics (line 45) | func TestStubInitializaationWithToxics(t *testing.T) {
  function TestAddRemoveStubs (line 86) | func TestAddRemoveStubs(t *testing.T) {
  function TestNoDataDropped (line 140) | func TestNoDataDropped(t *testing.T) {
  function TestToxicity (line 197) | func TestToxicity(t *testing.T) {
  function TestStateCreated (line 244) | func TestStateCreated(t *testing.T) {
  function TestRemoveToxicWithBrokenConnection (line 266) | func TestRemoveToxicWithBrokenConnection(t *testing.T) {

FILE: metrics.go
  function NewMetricsContainer (line 12) | func NewMetricsContainer(registry *prometheus.Registry) *metricsContainer {
  type metricsContainer (line 21) | type metricsContainer struct
    method runtimeMetricsEnabled (line 28) | func (m *metricsContainer) runtimeMetricsEnabled() bool {
    method proxyMetricsEnabled (line 32) | func (m *metricsContainer) proxyMetricsEnabled() bool {
    method anyMetricsEnabled (line 37) | func (m *metricsContainer) anyMetricsEnabled() bool {
    method handler (line 43) | func (m *metricsContainer) handler() http.Handler {

FILE: metrics_test.go
  function TestProxyMetricsReceivedSentBytes (line 20) | func TestProxyMetricsReceivedSentBytes(t *testing.T) {
  function TestRuntimeMetricsBuildInfo (line 57) | func TestRuntimeMetricsBuildInfo(t *testing.T) {
  type testWriteCloser (line 82) | type testWriteCloser struct
    method Close (line 86) | func (t *testWriteCloser) Close() error {
  function prometheusOutput (line 90) | func prometheusOutput(t *testing.T, apiServer *ApiServer, prefix string)...

FILE: proxy.go
  type Proxy (line 19) | type Proxy struct
    method Start (line 73) | func (proxy *Proxy) Start() error {
    method Update (line 80) | func (proxy *Proxy) Update(input *Proxy) error {
    method Stop (line 104) | func (proxy *Proxy) Stop() {
    method listen (line 111) | func (proxy *Proxy) listen() error {
    method close (line 128) | func (proxy *Proxy) close() {
    method Differs (line 139) | func (proxy *Proxy) Differs(other *Proxy) (bool, error) {
    method freeBlocker (line 154) | func (proxy *Proxy) freeBlocker(acceptTomb *tomb.Tomb) {
    method server (line 169) | func (proxy *Proxy) server() {
    method RemoveConnection (line 226) | func (proxy *Proxy) RemoveConnection(name string) {
  type ConnectionList (line 37) | type ConnectionList struct
    method Lock (line 42) | func (c *ConnectionList) Lock() {
    method Unlock (line 46) | func (c *ConnectionList) Unlock() {
  function NewProxy (line 52) | func NewProxy(server *ApiServer, name, listen, upstream string) *Proxy {
  function start (line 233) | func start(proxy *Proxy) error {
  function stop (line 247) | func stop(proxy *Proxy) {

FILE: proxy_collection.go
  type ProxyCollection (line 14) | type ProxyCollection struct
    method Add (line 26) | func (collection *ProxyCollection) Add(proxy *Proxy, start bool) error {
    method AddOrReplace (line 46) | func (collection *ProxyCollection) AddOrReplace(proxy *Proxy, start bo...
    method PopulateJson (line 74) | func (collection *ProxyCollection) PopulateJson(
    method Proxies (line 116) | func (collection *ProxyCollection) Proxies() map[string]*Proxy {
    method Get (line 128) | func (collection *ProxyCollection) Get(name string) (*Proxy, error) {
    method Remove (line 135) | func (collection *ProxyCollection) Remove(name string) error {
    method Clear (line 149) | func (collection *ProxyCollection) Clear() error {
    method getByName (line 164) | func (collection *ProxyCollection) getByName(name string) (*Proxy, err...
  function NewProxyCollection (line 20) | func NewProxyCollection() *ProxyCollection {

FILE: proxy_collection_test.go
  function TestAddProxyToCollection (line 11) | func TestAddProxyToCollection(t *testing.T) {
  function TestAddTwoProxiesToCollection (line 29) | func TestAddTwoProxiesToCollection(t *testing.T) {
  function TestListProxies (line 44) | func TestListProxies(t *testing.T) {
  function TestAddProxyAndStart (line 62) | func TestAddProxyAndStart(t *testing.T) {
  function TestAddAndRemoveProxyFromCollection (line 80) | func TestAddAndRemoveProxyFromCollection(t *testing.T) {

FILE: proxy_test.go
  function TestProxySimpleMessage (line 17) | func TestProxySimpleMessage(t *testing.T) {
  function TestProxyToDownUpstream (line 38) | func TestProxyToDownUpstream(t *testing.T) {
  function TestProxyBigMessage (line 53) | func TestProxyBigMessage(t *testing.T) {
  function TestProxyTwoPartMessage (line 76) | func TestProxyTwoPartMessage(t *testing.T) {
  function TestClosingProxyMultipleTimes (line 105) | func TestClosingProxyMultipleTimes(t *testing.T) {
  function TestStartTwoProxiesOnSameAddress (line 113) | func TestStartTwoProxiesOnSameAddress(t *testing.T) {
  function TestStopProxyBeforeStarting (line 123) | func TestStopProxyBeforeStarting(t *testing.T) {
  function TestProxyUpdate (line 145) | func TestProxyUpdate(t *testing.T) {
  function TestProxyUpdateWithHostname (line 182) | func TestProxyUpdateWithHostname(t *testing.T) {
  function TestRestartFailedToStartProxy (line 238) | func TestRestartFailedToStartProxy(t *testing.T) {
  function TestProxyDiffers (line 269) | func TestProxyDiffers(t *testing.T) {

FILE: stream/direction.go
  type Direction (line 8) | type Direction
    method String (line 18) | func (d Direction) String() string {
  constant Upstream (line 13) | Upstream Direction = iota
  constant Downstream (line 14) | Downstream
  constant NumDirections (line 15) | NumDirections
  function ParseDirection (line 25) | func ParseDirection(value string) (Direction, error) {

FILE: stream/direction_test.go
  function TestDirection_String (line 9) | func TestDirection_String(t *testing.T) {
  function TestParseDirection (line 36) | func TestParseDirection(t *testing.T) {

FILE: stream/io_chan.go
  type StreamChunk (line 10) | type StreamChunk struct
  type ChanWriter (line 16) | type ChanWriter struct
    method Write (line 26) | func (c *ChanWriter) Write(buf []byte) (int, error) {
    method Close (line 34) | func (c *ChanWriter) Close() error {
  function NewChanWriter (line 20) | func NewChanWriter(output chan<- *StreamChunk) *ChanWriter {
  type ChanReader (line 40) | type ChanReader struct
    method SetInterrupt (line 53) | func (c *ChanReader) SetInterrupt(interrupt <-chan struct{}) {
    method Read (line 60) | func (c *ChanReader) Read(out []byte) (int, error) {
  function NewChanReader (line 48) | func NewChanReader(input <-chan *StreamChunk) *ChanReader {

FILE: stream/io_chan_test.go
  function TestBasicReadWrite (line 10) | func TestBasicReadWrite(t *testing.T) {
  function TestReadMoreThanWrite (line 37) | func TestReadMoreThanWrite(t *testing.T) {
  function TestReadLessThanWrite (line 64) | func TestReadLessThanWrite(t *testing.T) {
  function TestMultiReadWrite (line 101) | func TestMultiReadWrite(t *testing.T) {
  function TestMultiWriteWithCopy (line 132) | func TestMultiWriteWithCopy(t *testing.T) {
  function TestStream_ReadCorrectness (line 156) | func TestStream_ReadCorrectness(t *testing.T) {
  function TestStream_ReadInterrupt (line 180) | func TestStream_ReadInterrupt(t *testing.T) {

FILE: test/e2e/benchmark_test.go
  function BenchmarkDirect (line 34) | func BenchmarkDirect(b *testing.B) {
  function BenchmarkProxy (line 51) | func BenchmarkProxy(b *testing.B) {
  function BenchmarkDirectSmall (line 68) | func BenchmarkDirectSmall(b *testing.B) {
  function BenchmarkProxySmall (line 85) | func BenchmarkProxySmall(b *testing.B) {

FILE: test/e2e/endpoint.go
  function handler1 (line 19) | func handler1(w http.ResponseWriter, r *http.Request) {
  function handler2 (line 29) | func handler2(w http.ResponseWriter, r *http.Request) {
  function main (line 39) | func main() {

FILE: testhelper/tcp_server.go
  function NewTCPServer (line 9) | func NewTCPServer() (*TCPServer, error) {
  type TCPServer (line 21) | type TCPServer struct
    method Run (line 27) | func (server *TCPServer) Run() (err error) {
    method handle_connection (line 36) | func (server *TCPServer) handle_connection() (err error) {
    method Close (line 52) | func (server *TCPServer) Close() (err error) {
  function WithTCPServer (line 56) | func WithTCPServer(t *testing.T, block func(string, chan []byte)) {

FILE: testhelper/tcp_server_test.go
  function TestSimpleServer (line 11) | func TestSimpleServer(t *testing.T) {

FILE: testhelper/timeout_after.go
  function TimeoutAfter (line 8) | func TimeoutAfter(after time.Duration, f func()) error {

FILE: testhelper/timeout_after_test.go
  function TestTimeoutAfter (line 10) | func TestTimeoutAfter(t *testing.T) {

FILE: testhelper/upstream.go
  type Upstream (line 8) | type Upstream struct
    method listen (line 25) | func (u *Upstream) listen() {
    method accept (line 33) | func (u *Upstream) accept(ignoreData bool) {
    method Close (line 51) | func (u *Upstream) Close() {
    method Addr (line 55) | func (u *Upstream) Addr() string {
  function NewUpstream (line 14) | func NewUpstream(t testing.TB, ignoreData bool) *Upstream {

FILE: toxic_collection.go
  type ToxicCollection (line 21) | type ToxicCollection struct
    method ResetToxics (line 47) | func (c *ToxicCollection) ResetToxics(ctx context.Context) {
    method GetToxic (line 59) | func (c *ToxicCollection) GetToxic(name string) *toxics.ToxicWrapper {
    method GetToxicArray (line 66) | func (c *ToxicCollection) GetToxicArray() []toxics.Toxic {
    method AddToxicJson (line 83) | func (c *ToxicCollection) AddToxicJson(data io.Reader) (*toxics.ToxicW...
    method UpdateToxicJson (line 134) | func (c *ToxicCollection) UpdateToxicJson(
    method RemoveToxic (line 162) | func (c *ToxicCollection) RemoveToxic(ctx context.Context, name string...
    method StartLink (line 186) | func (c *ToxicCollection) StartLink(
    method RemoveLink (line 208) | func (c *ToxicCollection) RemoveLink(name string) {
    method findToxicByName (line 215) | func (c *ToxicCollection) findToxicByName(name string) *toxics.ToxicWr...
    method chainAddToxic (line 227) | func (c *ToxicCollection) chainAddToxic(toxic *toxics.ToxicWrapper) {
    method chainUpdateToxic (line 246) | func (c *ToxicCollection) chainUpdateToxic(toxic *toxics.ToxicWrapper) {
    method chainRemoveToxic (line 263) | func (c *ToxicCollection) chainRemoveToxic(ctx context.Context, toxic ...
  function NewToxicCollection (line 30) | func NewToxicCollection(proxy *Proxy) *ToxicCollection {

FILE: toxics/bandwidth.go
  type BandwidthToxic (line 13) | type BandwidthToxic struct
    method Pipe (line 18) | func (t *BandwidthToxic) Pipe(stub *ToxicStub) {
  function init (line 80) | func init() {

FILE: toxics/bandwidth_test.go
  function TestBandwidthToxic (line 15) | func TestBandwidthToxic(t *testing.T) {
  function BenchmarkBandwidthToxic100MB (line 61) | func BenchmarkBandwidthToxic100MB(b *testing.B) {

FILE: toxics/latency.go
  type LatencyToxic (line 9) | type LatencyToxic struct
    method GetBufferSize (line 15) | func (t *LatencyToxic) GetBufferSize() int {
    method delay (line 19) | func (t *LatencyToxic) delay() time.Duration {
    method Pipe (line 30) | func (t *LatencyToxic) Pipe(stub *ToxicStub) {
  function init (line 54) | func init() {

FILE: toxics/latency_test.go
  function AssertDeltaTime (line 19) | func AssertDeltaTime(t *testing.T, message string, actual, expected, del...
  function DoLatencyTest (line 37) | func DoLatencyTest(t *testing.T, upLatency, downLatency *toxics.LatencyT...
  function TestUpstreamLatency (line 118) | func TestUpstreamLatency(t *testing.T) {
  function TestDownstreamLatency (line 122) | func TestDownstreamLatency(t *testing.T) {
  function TestFullstreamLatencyEven (line 126) | func TestFullstreamLatencyEven(t *testing.T) {
  function TestFullstreamLatencyBiasUp (line 130) | func TestFullstreamLatencyBiasUp(t *testing.T) {
  function TestFullstreamLatencyBiasDown (line 134) | func TestFullstreamLatencyBiasDown(t *testing.T) {
  function TestZeroLatency (line 138) | func TestZeroLatency(t *testing.T) {
  function TestLatencyToxicCloseRace (line 142) | func TestLatencyToxicCloseRace(t *testing.T) {
  function TestTwoLatencyToxics (line 178) | func TestTwoLatencyToxics(t *testing.T) {
  function TestLatencyToxicBandwidth (line 221) | func TestLatencyToxicBandwidth(t *testing.T) {

FILE: toxics/limit_data.go
  type LimitDataToxic (line 6) | type LimitDataToxic struct
    method Pipe (line 14) | func (t *LimitDataToxic) Pipe(stub *ToxicStub) {
    method NewState (line 54) | func (t *LimitDataToxic) NewState() interface{} {
  type LimitDataToxicState (line 10) | type LimitDataToxicState struct
  function init (line 58) | func init() {

FILE: toxics/limit_data_test.go
  function buffer (line 12) | func buffer(size int) []byte {
  function checkOutgoingChunk (line 20) | func checkOutgoingChunk(t *testing.T, output chan *stream.StreamChunk, e...
  function checkRemainingChunks (line 27) | func checkRemainingChunks(t *testing.T, output chan *stream.StreamChunk) {
  function check (line 33) | func check(t *testing.T, toxic *toxics.LimitDataToxic, chunks [][]byte, ...
  function TestLimitDataToxicMayBeRestarted (line 52) | func TestLimitDataToxicMayBeRestarted(t *testing.T) {
  function TestLimitDataToxicMayBeInterrupted (line 83) | func TestLimitDataToxicMayBeInterrupted(t *testing.T) {
  function TestLimitDataToxicNilShouldClosePipe (line 98) | func TestLimitDataToxicNilShouldClosePipe(t *testing.T) {
  function TestLimitDataToxicChunkSmallerThanLimit (line 113) | func TestLimitDataToxicChunkSmallerThanLimit(t *testing.T) {
  function TestLimitDataToxicChunkLengthMatchesLimit (line 120) | func TestLimitDataToxicChunkLengthMatchesLimit(t *testing.T) {
  function TestLimitDataToxicChunkBiggerThanLimit (line 127) | func TestLimitDataToxicChunkBiggerThanLimit(t *testing.T) {
  function TestLimitDataToxicMultipleChunksMatchThanLimit (line 136) | func TestLimitDataToxicMultipleChunksMatchThanLimit(t *testing.T) {
  function TestLimitDataToxicSecondChunkWouldOverflowLimit (line 144) | func TestLimitDataToxicSecondChunkWouldOverflowLimit(t *testing.T) {
  function TestLimitDataToxicLimitIsSetToZero (line 154) | func TestLimitDataToxicLimitIsSetToZero(t *testing.T) {

FILE: toxics/noop.go
  type NoopToxic (line 4) | type NoopToxic struct
    method Pipe (line 6) | func (t *NoopToxic) Pipe(stub *ToxicStub) {
  function init (line 21) | func init() {

FILE: toxics/reset_peer.go
  type ResetToxic (line 16) | type ResetToxic struct
    method Pipe (line 21) | func (t *ResetToxic) Pipe(stub *ToxicStub) {
  function init (line 36) | func init() {

FILE: toxics/reset_peer_test.go
  constant msg (line 15) | msg = "reset toxic payload\n"
  function TestResetToxicNoTimeout (line 17) | func TestResetToxicNoTimeout(t *testing.T) {
  function TestResetToxicWithTimeout (line 21) | func TestResetToxicWithTimeout(t *testing.T) {
  function TestResetToxicWithTimeoutDownstream (line 33) | func TestResetToxicWithTimeoutDownstream(t *testing.T) {
  function checkConnectionState (line 45) | func checkConnectionState(t *testing.T, listenAddress string) {
  function resetTCPHelper (line 73) | func resetTCPHelper(t *testing.T, toxicJSON io.Reader) {

FILE: toxics/slicer.go
  type SlicerToxic (line 12) | type SlicerToxic struct
    method chunk (line 31) | func (t *SlicerToxic) chunk(start int, end int) []int {
    method Pipe (line 51) | func (t *SlicerToxic) Pipe(stub *ToxicStub) {
  function init (line 83) | func init() {

FILE: toxics/slicer_test.go
  function TestSlicerToxic (line 13) | func TestSlicerToxic(t *testing.T) {
  function TestSlicerToxicZeroSizeVariation (line 60) | func TestSlicerToxicZeroSizeVariation(t *testing.T) {

FILE: toxics/slow_close.go
  type SlowCloseToxic (line 6) | type SlowCloseToxic struct
    method Pipe (line 11) | func (t *SlowCloseToxic) Pipe(stub *ToxicStub) {
  function init (line 32) | func init() {

FILE: toxics/timeout.go
  type TimeoutToxic (line 8) | type TimeoutToxic struct
    method Pipe (line 13) | func (t *TimeoutToxic) Pipe(stub *ToxicStub) {
    method Cleanup (line 47) | func (t *TimeoutToxic) Cleanup(stub *ToxicStub) {
  function init (line 51) | func init() {

FILE: toxics/timeout_test.go
  function WithEstablishedProxy (line 16) | func WithEstablishedProxy(t *testing.T, f func(net.Conn, net.Conn, *toxi...
  function TestTimeoutToxicDoesNotCauseHang (line 71) | func TestTimeoutToxicDoesNotCauseHang(t *testing.T) {
  function TestTimeoutToxicClosesConnectionOnRemove (line 97) | func TestTimeoutToxicClosesConnectionOnRemove(t *testing.T) {

FILE: toxics/toxic.go
  type Toxic (line 26) | type Toxic interface
  type CleanupToxic (line 32) | type CleanupToxic interface
  type BufferedToxic (line 37) | type BufferedToxic interface
  type StatefulToxic (line 45) | type StatefulToxic interface
  type ToxicWrapper (line 50) | type ToxicWrapper struct
  type ToxicStub (line 61) | type ToxicStub struct
    method Run (line 81) | func (s *ToxicStub) Run(toxic *ToxicWrapper) {
    method WriteOutput (line 94) | func (s *ToxicStub) WriteOutput(p *stream.StreamChunk, d time.Duration...
    method InterruptToxic (line 110) | func (s *ToxicStub) InterruptToxic() bool {
    method Closed (line 120) | func (s *ToxicStub) Closed() bool {
    method Close (line 129) | func (s *ToxicStub) Close() {
  function NewToxicStub (line 70) | func NewToxicStub(input <-chan *stream.StreamChunk, output chan<- *strea...
  function Register (line 141) | func Register(typeName string, toxic Toxic) {
  function New (line 151) | func New(wrapper *ToxicWrapper) Toxic {
  function Count (line 168) | func Count() int {

FILE: toxics/toxic_test.go
  function NewTestProxy (line 27) | func NewTestProxy(name, upstream string) *toxiproxy.Proxy {
  function WithEchoServer (line 42) | func WithEchoServer(t *testing.T, f func(string, chan []byte)) {
  function WithEchoProxy (line 86) | func WithEchoProxy(
  function ToxicToJson (line 105) | func ToxicToJson(t *testing.T, name, typeName, stream string, toxic toxi...
  function AssertEchoResponse (line 120) | func AssertEchoResponse(t *testing.T, client, server net.Conn) {
  function TestPersistentConnections (line 154) | func TestPersistentConnections(t *testing.T) {
  function TestToxicAddRemove (line 206) | func TestToxicAddRemove(t *testing.T) {
  function TestProxyLatency (line 270) | func TestProxyLatency(t *testing.T) {
  function BenchmarkProxyBandwidth (line 316) | func BenchmarkProxyBandwidth(b *testing.B) {
  function TestToxicStub_WriteOutput (line 363) | func TestToxicStub_WriteOutput(t *testing.T) {

FILE: toxiproxy_test.go
  function NewTestProxy (line 17) | func NewTestProxy(name, upstream string) *toxiproxy.Proxy {
  function WithTCPProxy (line 32) | func WithTCPProxy(
  function AssertProxyUp (line 48) | func AssertProxyUp(t *testing.T, addr string, up bool) net.Conn {
Condensed preview — 87 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (315K chars).
[
  {
    "path": ".editorconfig",
    "chars": 619,
    "preview": "# EditorConfig is awesome: https://EditorConfig.org\n\n# Unix-style newlines with a newline ending every file\n[*]\ncharset "
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 216,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: gomod\n    directory: /\n    schedule:\n      interval: weekly\n    registries: \""
  },
  {
    "path": ".github/workflows/analysis.yml",
    "chars": 1348,
    "preview": "---\n\nname: Analysis\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n  schedule:\n    - cron: 1"
  },
  {
    "path": ".github/workflows/cla.yml",
    "chars": 658,
    "preview": "---\n\nname: Contributor License Agreement (CLA)\n\non:\n  pull_request_target:\n    types: [opened, synchronize]\n  issue_comm"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 1981,
    "preview": "---\n\nname: Release\n\non:\n  push:\n    tags: [ v*.*.* ]\n\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: ${{ github.repository }}\n\nj"
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 1436,
    "preview": "---\n\nname: Test\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  test:\n    runs-on: ubunt"
  },
  {
    "path": ".gitignore",
    "chars": 73,
    "preview": "# goreleaser output directory\ndist/\n\n# go mod dependencies\nvendor/\n\ntmp/\n"
  },
  {
    "path": ".golangci.yml",
    "chars": 824,
    "preview": "---\nversion: \"2\"\nrun:\n  go: \"1.22\"\nlinters:\n  default: none\n  enable:\n    - bodyclose\n    - dogsled\n    - exhaustive\n   "
  },
  {
    "path": ".goreleaser.yml",
    "chars": 6806,
    "preview": "---\n\nproject_name: toxiproxy\n\nenv:\n  - GO111MODULE=on\n\nbefore:\n  hooks:\n    - go mod download\n    - go mod tidy\n\nbuilds:"
  },
  {
    "path": ".yamllint.yml",
    "chars": 588,
    "preview": "---\n\nyaml-files:\n  - \"*.yaml\"\n  - \"*.yml\"\n\nignore: |\n  vendor/**/*\n  dist/*.yaml\n  .github/**/*\n\nrules:\n  comments:\n    "
  },
  {
    "path": "CHANGELOG.md",
    "chars": 11188,
    "preview": "# [Unreleased]\n\n# [2.12.0]\n\n- Update go version to 1.23.0 (#628)\n- Do not restart proxies when using hostnames to specif"
  },
  {
    "path": "CREATING_TOXICS.md",
    "chars": 6415,
    "preview": "# Creating custom toxics\n\nCreating a toxic is done by implementing the `Toxic` interface:\n\n```go\ntype Toxic interface {\n"
  },
  {
    "path": "Dockerfile",
    "chars": 178,
    "preview": "FROM scratch\n\nEXPOSE 8474\nENTRYPOINT [\"/toxiproxy\"]\nCMD [\"-host=0.0.0.0\"]\n\nENV LOG_LEVEL=info\n\nCOPY toxiproxy-server-lin"
  },
  {
    "path": "LICENSE",
    "chars": 1075,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2014 Shopify\n\nPermission is hereby granted, free of charge, to any person obtaining"
  },
  {
    "path": "METRICS.md",
    "chars": 1893,
    "preview": "# Metrics\n\n- [Metrics](#metrics)\n    - [Runtime Metrics](#runtime-metrics)\n    - [Proxy Metrics](#proxy-metrics)\n      -"
  },
  {
    "path": "Makefile",
    "chars": 1930,
    "preview": "OS := $(shell uname -s)\nARCH := $(shell uname -m)\nGO_VERSION := $(shell go version | cut -f3 -d\" \")\nGO_MINOR_VERSION := "
  },
  {
    "path": "README.md",
    "chars": 19999,
    "preview": "# Toxiproxy\n[![GitHub release](https://img.shields.io/github/release/Shopify/toxiproxy.svg)](https://github.com/Shopify/"
  },
  {
    "path": "RELEASE.md",
    "chars": 2529,
    "preview": "# Releasing\n\n- [Releasing](#releasing)\n  - [Before You Begin](#before-you-begin)\n  - [Local Release Preparation](#local-"
  },
  {
    "path": "_examples/tests/README.md",
    "chars": 567,
    "preview": "## Tests with Toxiproxy\n\n### Setup\n\n```shell\n$ brew install shopify/shopify/toxiproxy kind\n$ kind create cluster --confi"
  },
  {
    "path": "_examples/tests/cluster.yml",
    "chars": 492,
    "preview": "---\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnetworking:\n  ipFamily: ipv4\nnodes:\n  - role: control-plane\n    ext"
  },
  {
    "path": "_examples/tests/db.go",
    "chars": 1130,
    "preview": "package main\n\nimport (\n\tpg \"github.com/go-pg/pg/v10\"\n\t\"github.com/go-pg/pg/v10/orm\"\n)\n\nfunc setupDB(addr, database strin"
  },
  {
    "path": "_examples/tests/db_test.go",
    "chars": 2476,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\ttoxiServer \"github.com/Shopify/toxiproxy/v2\"\n\ttoxiproxy"
  },
  {
    "path": "_examples/tests/main.go",
    "chars": 822,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"os\"\n\n\tpg \"github.com/go-pg/pg/v10\"\n)\n\nfunc main() {\n\terr := run()\n\tif err != "
  },
  {
    "path": "_examples/tests/models.go",
    "chars": 399,
    "preview": "package main\n\nimport \"fmt\"\n\ntype User struct {\n\tId     int64\n\tName   string\n\tEmails []string\n}\n\nfunc (u User) String() s"
  },
  {
    "path": "_examples/tests/resources.yml",
    "chars": 886,
    "preview": "---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: postgres\n  labels:\n    app: postgres\ndata:\n  POSTGRES_DB: postgres\n"
  },
  {
    "path": "_examples/toxics/README.md",
    "chars": 931,
    "preview": "## Create Toxic\n\nExample how to start building own toxics.\n\n### Debug toxic\n\nRun custom toxiserver with DebugToxic.\n\n```"
  },
  {
    "path": "_examples/toxics/debug_toxic.go",
    "chars": 1463,
    "preview": "// Ported from https://github.com/xthexder/toxic-example/blob/master/noop.go\n\npackage main\n\nimport (\n\t\"os\"\n\t\"log\"\n\t\"fmt\""
  },
  {
    "path": "_examples/toxics/http_toxic.go",
    "chars": 1369,
    "preview": "// Ported from https://github.com/xthexder/toxic-example/blob/master/http.go\npackage main\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\""
  },
  {
    "path": "api.go",
    "chars": 14967,
    "preview": "package toxiproxy\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gorill"
  },
  {
    "path": "api_test.go",
    "chars": 30627,
    "preview": "package toxiproxy_test\n\nimport (\n\t\"bytes\"\n\t\"flag\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/prometheus/cl"
  },
  {
    "path": "client/README.md",
    "chars": 4272,
    "preview": "# toxiproxy-go\n\nThis is the Go client library for the\n[Toxiproxy](https://github.com/shopify/toxiproxy) API. Please read"
  },
  {
    "path": "client/api_error.go",
    "chars": 388,
    "preview": "// Package Toxiproxy provides a client wrapper around the Toxiproxy HTTP API for\n// testing the resiliency of Go applica"
  },
  {
    "path": "client/client.go",
    "chars": 6424,
    "preview": "// Package Toxiproxy provides a client wrapper around the Toxiproxy HTTP API for\n// testing the resiliency of Go applica"
  },
  {
    "path": "client/client_test.go",
    "chars": 1795,
    "preview": "package toxiproxy_test\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\ttoxiproxy \"github.com/Shopify/toxiproxy/v"
  },
  {
    "path": "client/proxy.go",
    "chars": 4253,
    "preview": "// Package Toxiproxy provides a client wrapper around the Toxiproxy HTTP API for\n// testing the resiliency of Go applica"
  },
  {
    "path": "client/toxic.go",
    "chars": 593,
    "preview": "// Package Toxiproxy provides a client wrapper around the Toxiproxy HTTP API for\n// testing the resiliency of Go applica"
  },
  {
    "path": "cmd/cli/cli.go",
    "chars": 16400,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"runtime\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/urfave/cli/v2\"\n\tterminal \"gol"
  },
  {
    "path": "cmd/server/server.go",
    "chars": 3349,
    "preview": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strconv\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com"
  },
  {
    "path": "collectors/common.go",
    "chars": 62,
    "preview": "package collectors\n\nconst (\n\tnamespace string = \"toxiproxy\"\n)\n"
  },
  {
    "path": "collectors/proxy.go",
    "chars": 1019,
    "preview": "package collectors\n\nimport (\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\ntype ProxyMetricCollectors struct {\n\tc"
  },
  {
    "path": "collectors/runtime.go",
    "chars": 665,
    "preview": "package collectors\n\nimport (\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/pro"
  },
  {
    "path": "dev.yml",
    "chars": 207,
    "preview": "---\n\nname: toxiproxy\n\ntype: go\n\nup:\n  - packages:\n      - gnu-tar\n      - golangci-lint\n      - goreleaser\n      - shell"
  },
  {
    "path": "go.mod",
    "chars": 1105,
    "preview": "module github.com/Shopify/toxiproxy/v2\n\ngo 1.23.0\n\nrequire (\n\tgithub.com/gorilla/mux v1.8.1\n\tgithub.com/prometheus/clien"
  },
  {
    "path": "go.sum",
    "chars": 6705,
    "preview": "github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:"
  },
  {
    "path": "link.go",
    "chars": 7843,
    "preview": "package toxiproxy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/rs/zerolog\"\n\n\t\"github.com/Shopify/toxip"
  },
  {
    "path": "link_test.go",
    "chars": 8235,
    "preview": "package toxiproxy\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"flag\"\n\t\"io\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/rs/zerolo"
  },
  {
    "path": "metrics.go",
    "chars": 1481,
    "preview": "package toxiproxy\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/Shopify/toxiproxy/v2/collectors\"\n\t\"github.com/prometheus/client_go"
  },
  {
    "path": "metrics_test.go",
    "chars": 2725,
    "preview": "package toxiproxy\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing"
  },
  {
    "path": "proxy.go",
    "chars": 5749,
    "preview": "package toxiproxy\n\nimport (\n\t\"errors\"\n\t\"net\"\n\t\"sync\"\n\n\t\"github.com/rs/zerolog\"\n\ttomb \"gopkg.in/tomb.v1\"\n\n\t\"github.com/Sh"
  },
  {
    "path": "proxy_collection.go",
    "chars": 3681,
    "preview": "package toxiproxy\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"sync\"\n)\n\n// ProxyCollection is a collection of proxies. It's"
  },
  {
    "path": "proxy_collection_test.go",
    "chars": 2927,
    "preview": "package toxiproxy_test\n\nimport (\n\t\"bytes\"\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/Shopify/toxiproxy/v2\"\n)\n\nfunc TestAddProxyToCo"
  },
  {
    "path": "proxy_test.go",
    "chars": 7205,
    "preview": "package toxiproxy_test\n\nimport (\n\t\"bytes\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/"
  },
  {
    "path": "scripts/hazelcast.xml",
    "chars": 1372,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<hazelcast xmlns=\"http://www.hazelcast.com/schema/config\"\n           xmlns:xsi=\""
  },
  {
    "path": "scripts/test-e2e",
    "chars": 4635,
    "preview": "#!/usr/bin/env bash\n\nset -ueo pipefail\n\ncd \"$(dirname \"$0\")\"\n\nserver=\"../dist/toxiproxy-server\"\nstate=\"started\"\n\nbenchma"
  },
  {
    "path": "scripts/test-e2e-hazelcast",
    "chars": 4510,
    "preview": "#!/bin/bash\n\n# Usage:\n#   test-e2e-hazelcast [docker image name for toxiproxy]\n\nset -ueo pipefail\n\ncd \"$(dirname \"$0\")\"\n"
  },
  {
    "path": "scripts/test-release",
    "chars": 2259,
    "preview": "#!/usr/bin/env bash\n\nset -ueo pipefail\n\nVERSION_FULL=\"$(git describe --abbrev=0 --tags)\"\nVERSION=${VERSION_FULL:1}\nARCH="
  },
  {
    "path": "share/toxiproxy.conf",
    "chars": 275,
    "preview": "description \"TCP proxy to simulate network and system conditions\"\nauthor \"Simon Eskildsen & Jacob Wirth\"\n\nstart on start"
  },
  {
    "path": "stream/direction.go",
    "chars": 593,
    "preview": "package stream\n\nimport (\n\t\"errors\"\n\t\"strings\"\n)\n\ntype Direction uint8\n\nvar ErrInvalidDirectionParameter error = errors.N"
  },
  {
    "path": "stream/direction_test.go",
    "chars": 1949,
    "preview": "package stream_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/Shopify/toxiproxy/v2/stream\"\n)\n\nfunc TestDirection_String(t *test"
  },
  {
    "path": "stream/io_chan.go",
    "chars": 2428,
    "preview": "package stream\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n)\n\n// Stores a slice of bytes with its receive timestamp.\ntype StreamChunk"
  },
  {
    "path": "stream/io_chan_test.go",
    "chars": 5561,
    "preview": "package stream\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestBasicReadWrite(t *testing.T) {\n\tsend := []byte(\"h"
  },
  {
    "path": "test/e2e/benchmark_test.go",
    "chars": 2477,
    "preview": "package main\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"testing\"\n)\n\n// Benchmark numbers:\n//\n// Toxiproxy 1.1\n//\n// 1x Toxic Types:\n/"
  },
  {
    "path": "test/e2e/endpoint.go",
    "chars": 1002,
    "preview": "package main\n\nimport (\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gorilla/mux\"\n)\n\nvar (\n\tstuff []by"
  },
  {
    "path": "testhelper/tcp_server.go",
    "chars": 1227,
    "preview": "package testhelper\n\nimport (\n\t\"io\"\n\t\"net\"\n\t\"testing\"\n)\n\nfunc NewTCPServer() (*TCPServer, error) {\n\tresult := &TCPServer{"
  },
  {
    "path": "testhelper/tcp_server_test.go",
    "chars": 662,
    "preview": "package testhelper_test\n\nimport (\n\t\"bytes\"\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/Shopify/toxiproxy/v2/testhelper\"\n)\n\nfunc Test"
  },
  {
    "path": "testhelper/timeout_after.go",
    "chars": 298,
    "preview": "package testhelper\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\nfunc TimeoutAfter(after time.Duration, f func()) error {\n\tsuccess := make"
  },
  {
    "path": "testhelper/timeout_after_test.go",
    "chars": 439,
    "preview": "package testhelper_test\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/Shopify/toxiproxy/v2/testhelper\"\n)\n\nfunc TestTimeoutA"
  },
  {
    "path": "testhelper/upstream.go",
    "chars": 999,
    "preview": "package testhelper\n\nimport (\n\t\"net\"\n\t\"testing\"\n)\n\ntype Upstream struct {\n\tlistener    net.Listener\n\tlogger      testing."
  },
  {
    "path": "toxic_collection.go",
    "chars": 6836,
    "preview": "package toxiproxy\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"sync\"\n\n\t\"github.com/rs/zerolog\"\n\n\t\"githu"
  },
  {
    "path": "toxics/bandwidth.go",
    "chars": 2135,
    "preview": "package toxics\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/rs/zerolog/log\"\n\n\t\"github.com/Shopify/toxiproxy/v2/stream\"\n)\n\n// T"
  },
  {
    "path": "toxics/bandwidth_test.go",
    "chars": 2317,
    "preview": "package toxics_test\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"net\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/Shopify/toxiproxy/v2/tes"
  },
  {
    "path": "toxics/latency.go",
    "chars": 1128,
    "preview": "package toxics\n\nimport (\n\t\"math/rand\"\n\t\"time\"\n)\n\n// The LatencyToxic passes data through with the a delay of latency +/-"
  },
  {
    "path": "toxics/latency_test.go",
    "chars": 6755,
    "preview": "package toxics_test\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gith"
  },
  {
    "path": "toxics/limit_data.go",
    "chars": 1084,
    "preview": "package toxics\n\nimport \"github.com/Shopify/toxiproxy/v2/stream\"\n\n// LimitDataToxic has limit in bytes.\ntype LimitDataTox"
  },
  {
    "path": "toxics/limit_data_test.go",
    "chars": 3715,
    "preview": "package toxics_test\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"testing\"\n\n\t\"github.com/Shopify/toxiproxy/v2/stream\"\n\t\"github.com"
  },
  {
    "path": "toxics/noop.go",
    "chars": 362,
    "preview": "package toxics\n\n// The NoopToxic passes all data through without any toxic effects.\ntype NoopToxic struct{}\n\nfunc (t *No"
  },
  {
    "path": "toxics/reset_peer.go",
    "chars": 807,
    "preview": "package toxics\n\nimport (\n\t\"time\"\n)\n\n/*\nThe ResetToxic sends closes the connection abruptly after a timeout (in ms).\nThe "
  },
  {
    "path": "toxics/reset_peer_test.go",
    "chars": 2438,
    "preview": "package toxics_test\n\nimport (\n\t\"bufio\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"syscall\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/Shopify/toxiproxy/"
  },
  {
    "path": "toxics/slicer.go",
    "chars": 2176,
    "preview": "package toxics\n\nimport (\n\t\"math/rand\"\n\t\"time\"\n\n\t\"github.com/Shopify/toxiproxy/v2/stream\"\n)\n\n// The SlicerToxic slices da"
  },
  {
    "path": "toxics/slicer_test.go",
    "chars": 2128,
    "preview": "package toxics_test\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/Shopify/toxiproxy/v2/stream\"\n\t\"github"
  },
  {
    "path": "toxics/slow_close.go",
    "chars": 612,
    "preview": "package toxics\n\nimport \"time\"\n\n// The SlowCloseToxic stops the TCP connection from closing until after a delay.\ntype Slo"
  },
  {
    "path": "toxics/timeout.go",
    "chars": 982,
    "preview": "package toxics\n\nimport \"time\"\n\n// The TimeoutToxic stops any data from flowing through,\n// and will close the connection"
  },
  {
    "path": "toxics/timeout_test.go",
    "chars": 2912,
    "preview": "package toxics_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/Shopify/toxiproxy/v2\"\n\t\""
  },
  {
    "path": "toxics/toxic.go",
    "chars": 4293,
    "preview": "package toxics\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"reflect\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/Shopify/toxiproxy/v2/stream\"\n)\n\n//"
  },
  {
    "path": "toxics/toxic_test.go",
    "chars": 8623,
    "preview": "package toxics_test\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\""
  },
  {
    "path": "toxiproxy_test.go",
    "chars": 1322,
    "preview": "package toxiproxy_test\n\nimport (\n\t\"flag\"\n\t\"net\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"gi"
  },
  {
    "path": "version.go",
    "chars": 39,
    "preview": "package toxiproxy\n\nvar Version = \"git\"\n"
  }
]

About this extraction

This page contains the full source code of the Shopify/toxiproxy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 87 files (277.7 KB), approximately 85.2k tokens, and a symbol index with 401 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!