[
  {
    "path": ".editorconfig",
    "content": "# EditorConfig is awesome: https://EditorConfig.org\n\n# Unix-style newlines with a newline ending every file\n[*]\ncharset                  = utf-8\nend_of_line              = lf\ninsert_final_newline     = true\ntrim_trailing_whitespace = true\nmax_line_length          = 100\n\n[Makefile]\nindent_style = tab\n\n[{scripts/*, *.sh}]\nmax_line_length    = 80\nindent_style       = space\nindent_size        = 2\nshell_variant      = bash\nbinary_next_line   = true\nswitch_case_indent = true\nspace_redirects    = true\nkeep_padding       = true\n\n[*.{yaml,yml}]\nindent_style       = space\nindent_size        = 2\n\n[*.go]\nindent_style = tab\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: gomod\n    directory: /\n    schedule:\n      interval: weekly\n    registries: \"*\"\n\n  - package-ecosystem: github-actions\n    directory: /\n    schedule:\n      interval: weekly\n"
  },
  {
    "path": ".github/workflows/analysis.yml",
    "content": "---\n\nname: Analysis\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n  schedule:\n    - cron: 13 7 * * 6\n\njobs:\n  linting:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: golangci-lint\n        uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20\n        with:\n          version: v2.1.6\n\n      - name: shellcheck\n        uses: azohra/shell-linter@30a9cf3f6cf25c08fc98f10d7dc4167f7b5c0c00\n        with:\n          path: scripts/test-*\n          severity: error\n\n      - name: yaml-lint\n        uses: ibiqlik/action-yamllint@2576378a8e339169678f9939646ee3ee325e845c # v3.1.1\n        with:\n          config_file: .yamllint.yml\n\n  vulnerabilities:\n    runs-on: ubuntu-latest\n\n    permissions:\n      security-events: write\n\n    steps:\n      - name: checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: initialize\n        uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v3.29.5\n        with:\n          languages: go\n\n      - name: codeql analyze\n        uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v3.29.5\n"
  },
  {
    "path": ".github/workflows/cla.yml",
    "content": "---\n\nname: Contributor License Agreement (CLA)\n\non:\n  pull_request_target:\n    types: [opened, synchronize]\n  issue_comment:\n    types: [created]\n\njobs:\n  cla:\n    permissions:\n      actions: write\n      pull-requests: write\n\n    runs-on: ubuntu-latest\n    if: |\n      (github.event.issue.pull_request\n      && !github.event.issue.pull_request.merged_at\n      && contains(github.event.comment.body, 'signed')\n      )\n      || (github.event.pull_request && !github.event.pull_request.merged)\n    steps:\n      - uses: Shopify/shopify-cla-action@v1\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          cla-token: ${{ secrets.CLA_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "---\n\nname: Release\n\non:\n  push:\n    tags: [ v*.*.* ]\n\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: ${{ github.repository }}\n\njobs:\n  release:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      packages: write\n\n    steps:\n      -\n        name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n\n      -\n        name: GPG config\n        run: |\n          mkdir -p ~/.gnupg\n          cat << EOF >> ~/.gnupg/gpg.conf\n          keyserver hkps://keys.openpgp.org\n          auto-key-import\n          auto-key-retrieve\n          EOF\n\n      -\n        name: Verify tag signature\n        run: |\n          # NOTE: Solve the problem with Github action checkout\n          # https://github.com/actions/checkout/issues/290\n          git fetch --tags --force\n\n          version=${GITHUB_REF#refs/tags/*}\n          git show $version\n          git tag -v $version\n\n      -\n        name: Log into registry ${{ env.REGISTRY }}\n        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      -\n        name: Set up Go\n        uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0\n        with:\n          go-version: 1.22\n          check-latest: true\n          cache: true\n      -\n        name: Build release changelog\n        run: |\n          version=${GITHUB_REF#refs/tags/v*}\n          mkdir -p tmp\n          sed '/^# \\['$version'\\]/,/^# \\[/!d;//d;/^\\s*$/d' CHANGELOG.md > tmp/release_changelog.md\n\n      -\n        name: Release\n        uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29\n        with:\n          distribution: goreleaser\n          version: v1.10.3\n          args: release --rm-dist --release-notes=tmp/release_changelog.md\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "---\n\nname: Test\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: true\n      matrix:\n        go: [1.22.8]\n    name: go ${{ matrix.go }}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: Setup go\n        uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0\n        with:\n          go-version: ${{ matrix.go }}\n          check-latest: true\n          cache: true\n\n      - name: Tests\n        run: make test\n\n      - name: benchmarks\n        run: make bench\n\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: Setup go\n        uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0\n        with:\n          go-version: 1.22\n          check-latest: true\n          cache: true\n\n      - name: E2E tests\n        run: make test-e2e\n\n      - name: Build\n        uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29\n        with:\n          distribution: goreleaser\n          version: v1.10.3\n          args: build --snapshot --rm-dist --skip-post-hooks --skip-validate --single-target\n        env:\n          GOOS: linux\n"
  },
  {
    "path": ".gitignore",
    "content": "# goreleaser output directory\ndist/\n\n# go mod dependencies\nvendor/\n\ntmp/\n"
  },
  {
    "path": ".golangci.yml",
    "content": "---\nversion: \"2\"\nrun:\n  go: \"1.22\"\nlinters:\n  default: none\n  enable:\n    - bodyclose\n    - dogsled\n    - exhaustive\n    - funlen\n    - gocritic\n    - gocyclo\n    - godot\n    - gosec\n    - govet\n    - ineffassign\n    - lll\n    - misspell\n    - staticcheck\n    - unused\n    - whitespace\n  settings:\n    funlen:\n      lines: 80\n      statements: 30\n    gosec:\n      excludes:\n        - G107\n    lll:\n      line-length: 100\n      tab-width: 2\n    misspell:\n      locale: US\n  exclusions:\n    generated: lax\n    presets:\n      - comments\n      - common-false-positives\n      - legacy\n      - std-error-handling\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\nformatters:\n  enable:\n    - gofmt\n    - goimports\n  exclusions:\n    generated: lax\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\n"
  },
  {
    "path": ".goreleaser.yml",
    "content": "---\n\nproject_name: toxiproxy\n\nenv:\n  - GO111MODULE=on\n\nbefore:\n  hooks:\n    - go mod download\n    - go mod tidy\n\nbuilds:\n  - &build_default\n    id: server\n    main: ./cmd/server\n    binary: toxiproxy-server-{{.Os}}-{{.Arch}}\n    no_unique_dist_dir: true\n    env:\n      - CGO_ENABLED=0\n\n    goos:\n      - darwin\n      - freebsd\n      - linux\n      - netbsd\n      - openbsd\n      - solaris\n      - windows\n\n    goarch:\n      - amd64\n      - arm64\n\n    ignore:\n      - goos: windows\n        goarch: arm64\n\n      - goarch: arm\n\n    ldflags:\n      - -s -w -X github.com/Shopify/toxiproxy/v2.Version={{.Version}}\n\n  - <<: *build_default\n    id: server-arm\n    goarch:\n      - arm\n    goarm:\n      - \"6\"\n      - \"7\"\n    ignore:\n      - goos: windows\n        goarch: arm\n    binary: toxiproxy-server-{{.Os}}-{{.Arch}}v{{.Arm}}\n\n  - &build_client\n    <<: *build_default\n    id: client\n    main: ./cmd/cli\n    binary: toxiproxy-cli-{{.Os}}-{{.Arch}}\n\n  - <<: *build_client\n    id: client-arm\n    goarch:\n      - arm\n    goarm:\n      - \"6\"\n      - \"7\"\n    ignore:\n      - goos: windows\n        goarch: arm\n    binary: toxiproxy-cli-{{.Os}}-{{.Arch}}v{{.Arm}}\n\n  - <<: *build_default\n    id: pkg-server\n    no_unique_dist_dir: false\n    binary: toxiproxy-server\n\n  - <<: *build_default\n    id: pkg-client\n    no_unique_dist_dir: false\n    main: ./cmd/cli\n    binary: toxiproxy-cli\n\nchecksum:\n  name_template: checksums.txt\n\nsnapshot:\n  name_template: \"{{ incpatch .Version }}-next\"\n\nnfpms:\n  - id: packages\n    package_name: toxiproxy\n    homepage: https://github.com/Shopify/toxiproxy\n    maintainer: Shopify Opensource <opensource@shopify.com>\n    description: TCP proxy to simulate network and system conditions.\n    license: MIT\n    bindir: /usr/bin\n    builds:\n      - pkg-server\n      - pkg-client\n    formats:\n      - apk\n      - deb\n      - rpm\n      # NOTE: Starting with Ubuntu 15.04, Upstart will be deprecated in favor of Systemd.\n      # contents:\n      #   - src: share/toxiproxy.conf\n      #     dst: /etc/init/toxiproxy.conf\n\ndockers:\n  - &docker\n    use: buildx\n    dockerfile: Dockerfile\n    ids:\n      - server\n      - client\n    goos: linux\n    goarch: amd64\n    image_templates:\n      - ghcr.io/shopify/toxiproxy:{{ .Version }}-amd64\n      - ghcr.io/shopify/toxiproxy:v{{ .Major }}-amd64\n      - ghcr.io/shopify/toxiproxy:v{{ .Major }}.{{ .Minor }}-amd64\n    build_flag_templates:\n      - --platform=linux/amd64\n      - --no-cache\n      - --label=org.opencontainers.image.title={{ .ProjectName }}\n      - --label=org.opencontainers.image.description={{ .ProjectName }}\n      - --label=org.opencontainers.image.url=https://github.com/Shopify/{{ .ProjectName }}\n      - --label=org.opencontainers.image.source=https://github.com/Shopify/{{ .ProjectName }}\n      - --label=org.opencontainers.image.version={{ .Version }}\n      - --label=org.opencontainers.image.created={{ time \"2006-01-02T15:04:05Z07:00\" }}\n      - --label=org.opencontainers.image.revision={{ .FullCommit }}\n      - --label=org.opencontainers.image.licenses=MIT\n  - <<: *docker\n    goarch: arm64\n    image_templates:\n      - ghcr.io/shopify/toxiproxy:{{ .Version }}-arm64\n      - ghcr.io/shopify/toxiproxy:v{{ .Major }}-arm64\n      - ghcr.io/shopify/toxiproxy:v{{ .Major }}.{{ .Minor }}-arm64\n    build_flag_templates:\n      - --platform=linux/arm64/v8\n      - --no-cache\n      - --label=org.opencontainers.image.title={{ .ProjectName }}\n      - --label=org.opencontainers.image.description={{ .ProjectName }}\n      - --label=org.opencontainers.image.url=https://github.com/Shopify/{{ .ProjectName }}\n      - --label=org.opencontainers.image.source=https://github.com/Shopify/{{ .ProjectName }}\n      - --label=org.opencontainers.image.version={{ .Version }}\n      - --label=org.opencontainers.image.created={{ time \"2006-01-02T15:04:05Z07:00\" }}\n      - --label=org.opencontainers.image.revision={{ .FullCommit }}\n      - --label=org.opencontainers.image.licenses=MIT\n  - <<: *docker\n    goarch: arm\n    goarm: \"7\"\n    image_templates:\n      - ghcr.io/shopify/toxiproxy:{{ .Version }}-armv7\n      - ghcr.io/shopify/toxiproxy:v{{ .Major }}-armv7\n      - ghcr.io/shopify/toxiproxy:v{{ .Major }}.{{ .Minor }}-armv7\n    build_flag_templates:\n      - --platform=linux/arm/v6\n      - --no-cache\n      - --label=org.opencontainers.image.title={{ .ProjectName }}\n      - --label=org.opencontainers.image.description={{ .ProjectName }}\n      - --label=org.opencontainers.image.url=https://github.com/Shopify/{{ .ProjectName }}\n      - --label=org.opencontainers.image.source=https://github.com/Shopify/{{ .ProjectName }}\n      - --label=org.opencontainers.image.version={{ .Version }}\n      - --label=org.opencontainers.image.created={{ time \"2006-01-02T15:04:05Z07:00\" }}\n      - --label=org.opencontainers.image.revision={{ .FullCommit }}\n      - --label=org.opencontainers.image.licenses=MIT\n  - <<: *docker\n    goarch: arm\n    goarm: \"6\"\n    image_templates:\n      - ghcr.io/shopify/toxiproxy:{{ .Version }}-armv6\n      - ghcr.io/shopify/toxiproxy:v{{ .Major }}-armv6\n      - ghcr.io/shopify/toxiproxy:v{{ .Major }}.{{ .Minor }}-armv6\n    build_flag_templates:\n      - --platform=linux/arm/v6\n      - --no-cache\n      - --label=org.opencontainers.image.title={{ .ProjectName }}\n      - --label=org.opencontainers.image.description={{ .ProjectName }}\n      - --label=org.opencontainers.image.url=https://github.com/Shopify/{{ .ProjectName }}\n      - --label=org.opencontainers.image.source=https://github.com/Shopify/{{ .ProjectName }}\n      - --label=org.opencontainers.image.version={{ .Version }}\n      - --label=org.opencontainers.image.created={{ time \"2006-01-02T15:04:05Z07:00\" }}\n      - --label=org.opencontainers.image.revision={{ .FullCommit }}\n      - --label=org.opencontainers.image.licenses=MIT\n\ndocker_manifests:\n  - name_template: ghcr.io/shopify/{{ .ProjectName }}:{{ .Version }}\n    image_templates:\n      - ghcr.io/shopify/{{ .ProjectName }}:{{ .Version }}-amd64\n      - ghcr.io/shopify/{{ .ProjectName }}:{{ .Version }}-arm64\n      - ghcr.io/shopify/{{ .ProjectName }}:{{ .Version }}-armv6\n      - ghcr.io/shopify/{{ .ProjectName }}:{{ .Version }}-armv7\n  - name_template: ghcr.io/shopify/{{ .ProjectName }}:latest\n    image_templates:\n      - ghcr.io/shopify/{{ .ProjectName }}:{{ .Version }}-amd64\n      - ghcr.io/shopify/{{ .ProjectName }}:{{ .Version }}-arm64\n      - ghcr.io/shopify/{{ .ProjectName }}:{{ .Version }}-armv6\n      - ghcr.io/shopify/{{ .ProjectName }}:{{ .Version }}-armv7\n\nchangelog:\n  sort: asc\n  filters:\n    exclude:\n      - \"^docs:\"\n      - \"^test:\"\n      - ^Merge\n\narchives:\n  - id: archive_binaries\n    format: binary\n    name_template: \"{{ .Binary }}\"\n    builds:\n      - server\n      - client\n\n  - id: archive_default\n    format: tar.gz\n    builds:\n      - pkg-server\n      - pkg-client\n"
  },
  {
    "path": ".yamllint.yml",
    "content": "---\n\nyaml-files:\n  - \"*.yaml\"\n  - \"*.yml\"\n\nignore: |\n  vendor/**/*\n  dist/*.yaml\n  .github/**/*\n\nrules:\n  comments:\n    require-starting-space: true\n  comments-indentation: enable\n  document-start:\n    present: true\n  indentation:\n    spaces: 2\n    indent-sequences: true\n    check-multi-line-strings: true\n  line-length:\n    max: 100\n    level: warning\n    allow-non-breakable-words: true\n    allow-non-breakable-inline-mappings: false\n  key-duplicates: enable\n  new-lines:\n    type: unix\n  trailing-spaces: enable\n  quoted-strings:\n    quote-type: double\n    required: only-when-needed\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# [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 specify listen address when updating a proxy\n  and populating a collection (#631, @robinbrandt)\n- Update various go packages\n\n# [2.11.0] - 2024-10-16\n\n- Do not pin minimum patch version in module (#595, @jaimem88)\n\n# [2.10.0] - 2024-10-08\n\n- Update various go packages\n- Update go version to 1.22.8 (#594, @abecevello)\n\n# [2.9.0] - 2024-03-12\n\n- Updated go version to 1.22.1 to fix 3 CVEs (#559, @dianadevasia)\n- Updated the version of golangci to 1.56.2 and disabled depguard rule in golangci (#559, @dianadevasia)\n\n# [2.8.0] - 2024-02-27\n\n- toxiproxy-cli - sortedAttributes sort by attribute.key instead attribute.value (#543, @jesseward)\n\n# [2.7.0] - 2023-10-25\n\n- Fix invalid JSON in /version endpoint response (#538, @whatyouhide)\n- Update minimum supported Go version 1.19. (@abecevello)\n\n# [2.6.0] - 2023-08-22\n\n- Gracefull shutdown of HTTP server. (#439, @miry)\n- Support PATCH HTTP method for Proxy update(`PATCH /proxies/{proxy}`) and\n  Toxic update(`PATCH /proxies/{proxy}/toxics/{toxic}`) endpoints.\n  Deprecat POST HTTP method for those endpoints. (@miry)\n- Client does not parse response body in case of errors for Populate.\n  Requires to get current proxies with new command. (#441, @miry)\n- Client specifies `User-Agent` HTTP header for all requests as\n  \"toxiproxy-cli/<version> <os>/<runtime>\".\n  Specifies client request content type as `application/json`. (#441, @miry)\n- Replace Api.Listen parameters `host` and `port` with single `addr`. (#445, @miry)\n\n# [2.5.0] - 2022-09-10\n\n- Update Release steps. (#369, @neufeldtech)\n- Migrate off probot-CLA to new GitHub Action. (#405, @cursedcoder)\n- Support go 1.18, 1.19. (#415, @miry)\n- `toxiproxy.NewProxy` now accepts `name`, `listen addr` and `upstream addr`. (#418, @miry)\n- Replace logrus with zerolog. (#413, @miry)\n- Log HTTP requests to API server. (#413, #421, @miry)\n- Add TimeoutHandler for the HTTP API server. (#420, @miry)\n- Set Write and Read timeouts for HTTP API server connections. (#423, @miry)\n- Show unique request id in API HTTP response. (#425, @miry)\n- Add method to parse `stream.Direction` from a string.\n  Allow converting `stream.Direction` to string. (#430, @miry)\n- Add the possibility to write to Output with a deadline.\n  On interrupting Bandwidth toxic, use non-blocking writes. (#436, @miry)\n- Update minimum supported Go version 1.17. (#438, @miry)\n\n# [2.4.0] - 2022-03-07\n\n- Verify git tag on release (#347, @miry)\n- Fix MacOS 12 tests for go17 with -race flag (#351, @strech)\n- Rename `testing/` and `bin/` folders (#354, @strech)\n- Added verbose error on proxy upstream dialing (#355, @f-dg)\n- Improve server startup message (#358, @areveny)\n- Introduce yaml linter. (#362, @miry)\n- Handle slicer toxic with zero `SizeVariation` and fix slicing randomization (#359, @areveny)\n- Added /metrics endpoint for exposing Prometheus-compatible internal metrics (#366, @neufeldtech)\n\n# [2.3.0] - 2021-12-23\n\n- Store all the executable `main` packages in `cmd` folder. (#335, @miry)\n- Extract common test helpers to own files. (#336, @miry)\n- Client: Allow HTTPS endpoints. (#338, @chen-anders)\n- client.Populate assign client to proxy. (#291, @hellodudu)\n- fix: The release-test task is always success.\n  add: Allow to run release-test on arm machines. (#340, @miry)\n- Upgrade `goreleaser`. Support `armv7` and `armv6` oses. (#339, @mitchellrj)\n- Allow to change log level for server. (#346, @miry)\n\n# [2.2.0] - 2021-10-17\n\n- Update linux packages to use `/usr/bin` folder as binary destination and change the executable names to\n  exclude ARCH and OS names. New pathes:\n  ```\n  /usr/bin/toxiproxy-cli\n  /usr/bin/toxiproxy-server\n  ```\n  (#331, @miry)\n- A new toxic to simulate TCP RESET (Connection reset by peer) on the connections by closing\n  the stub Input immediately or after a timeout. (#247 and #333, @chaosbox)\n\n# [2.1.7] - 2021-09-23\n\n- Set the valid version during the build process.\n  Verify the correct verion of the built binaries with `make release-dry` (#328, @miry)\n\n# [2.1.6] - 2021-09-23\n\n- Use CHANGELOG.md for release description (#306, @miry)\n- Dependency updates in #294 introduced a breaking change in CLI argument parsing.\n  Now [flags must be specified before arguments](https://github.com/urfave/cli/blob/master/docs/migrate-v1-to-v2.md#flags-before-args).\n  Previously, arguments could be specified prior to flags.\n  Update usage help text and documentation. (#308, @miry)\n- Run e2e tests to validate the command line and basic features of server,\n  client and application (#309, @miry)\n- Add /v2 suffix to module import path (#311, @dnwe)\n- Setup automated checking source code for security vulnerabilities (#312, @miry)\n- Setup code linter (#314, @miry)\n  - Max line length is 100 characters (#316, @miry)\n  - Linter to check whether HTTP response body is closed successfully (#317, @miry)\n  - Make sure the function are not big (#318, @miry)\n    - Extract client flags specs to seprate methods.\n      Introduce a new way to manage toxics with `ToxicOptions` structure (#321, @miry)\n    - Split `Proxy.server` to multiple small (#322, @miry)\n    - Extract initializetion of fake upstream server to test helper (#323, @miry)\n    - Support a list of well knonwn linters (#326, @miry)\n- `--host` flag uses `TOXIPROXY_URL` if it is set (#319, @maaslalani)\n- Run benchmarks in CI/CD (#320, @miry)\n- Use scratch docker base image instead of alpine (#325, @miry)\n\n# [2.1.5] - 2021-09-01\n\n- Move to Go Modules from godeps (#253, @epk)\n- Update the example in `client/README.md` (#251, @nothinux)\n- Update TOC in `README.md` (4ca1eddddfcd0c50c8f6dfb97089bb68e6310fd9, @dwradcliffe)\n- Add an example of `config.json` file to `README.md` (#260, @JesseEstum)\n- Add Link to Elixir Client (#287, @Jcambass)\n- Add Rust client link (#293, @itarato)\n- Renovations: formatting code, update dependicies, make govet/staticcheck pass (#294, @dnwe)\n- Remove `openssl` from `dev.yml` to use `dev` tool (#298, @pedro-stanaka)\n- Update `go` versions in development (#299, @miry)\n- Mention `MacPorts` in `README.md` (#290, @amake)\n- Fix some typos in `README.md` and `CHANGELOG.md` (#222, @jwilk)\n- Replace TravisCI with Github Actions to run tests (#303, @miry)\n- Build and release binaries with `goreleaser`. Support `arm64` and BSD oses. (#301, @miry)\n- Automate release with Github actions (#304, @miry)\n\n# [2.1.4] - 2019-01-11\n\n- Bug fix: Fix OOM in toxic. #232\n- Documentation updates.\n- CI and test updates.\n\n# [2.1.3] - 2018-03-05\n\n- Update `/version` endpoint to also return a charset of utf-8. #204\n- Bug fix: Double http concatenation. #191\n- Update cli examples to be more accurate. #187\n\n# [2.1.2] - 2017-07-10\n\n- go 1.8, make Sirupsen lower case, update godeps (issue #179)\n- Handle SIGTERM to exit cleanly (issue #180)\n- Address security issue by disallowing browsers from accessing API\n\n# [2.1.1] - 2017-05-16\n\n- Fix timeout toxic causing hang (issue #159)\n\n# [2.1.0] - 2016-12-07\n\n- Add -config server option to populate on startup #154\n- Updated CLI for scriptability #133\n- Add `/populate` endpoint to server #111\n- Change error responses from `title` to `error`\n- Allow hostname to be specified in CLI #129\n- Add support for stateful toxics #127\n- Add limit_data toxic\n\n# [2.0.0] - 2016-04-25\n\n- Add CLI (`toxiproxy-cli`) and rename server binary to `toxiproxy-server` #93\n- Fix removing a timeout toxic causing API to hang #89\n- API and client return toxics as array rather than a map of name to toxic #92\n- Fix multiple latency toxics not accumulating #94\n- Change default toxic name to `<type>_<stream>` #96\n- Nest toxic attributes rather than having a flat structure #98\n- 2.0 RFC: #54 and PR #62\n  - Change toxic API endpoints to an Add/Update/Remove structure\n  - Remove `enabled` field, and add `name` and `type` fields to toxics\n  - Add global toxic fields to a wrapper struct\n  - Chain toxics together dynamically instead of in a fixed length chain\n  - Register toxics in `init()` functions instead of a hard-coded list\n  - Clean up API error codes to make them more consistent\n  - Move toxics to their own package to allow 3rd party toxics\n- Remove stream direction from API urls #73\n- Add `toxicity` field for toxics #75\n- Refactor Go client to make usage easier with 2.0 #76\n- Make `ChanReader` in the `stream` package interruptible #77\n- Define proxy buffer sizes per-toxic (Fixes #72)\n- Fix slicer toxic testing race condition #71\n\n# [1.2.1] - 2015-07-24\n\n- Fix proxy name conflicts leaking an open port #69\n\n# [1.2.0] - 2015-07-23\n\n- Add a Toxic and Toxics type for the Go client\n- Add `Dockerfile`\n- Fix latency toxic limiting bandwidth #67\n- Add Slicer toxic\n\n# [1.1.0] - 2015-05-05\n\n- Remove /toxics endpoint in favour of /proxies\n- Add bandwidth toxic\n\n# [1.0.3] - 2015-04-29\n\n- Rename Go library package to Toxiproxy from Client\n- Fix latency toxic send to closed channel panic #46\n- Fix latency toxic accumulating delay #47\n\n# [1.0.2] - 2015-04-12\n\n- Added Toxic support to Go client\n\n# [1.0.1] - 2015-03-31\n\n- Various improvements to the documentation\n- Initial version of Go client\n- Fix toxic disabling bug #42\n\n# [1.0.0] - 2015-01-07\n\nInitial public release.\n\n[Unreleased]: https://github.com/Shopify/toxiproxy/compare/v2.12.0...HEAD\n[2.12.0]: https://github.com/Shopify/toxiproxy/compare/v2.11.0...v2.12.0\n[2.11.0]: https://github.com/Shopify/toxiproxy/compare/v2.10.0...v2.11.0\n[2.10.0]: https://github.com/Shopify/toxiproxy/compare/v2.9.0...v2.10.0\n[2.9.0]: https://github.com/Shopify/toxiproxy/compare/v2.8.0...v2.9.0\n[2.8.0]: https://github.com/Shopify/toxiproxy/compare/v2.7.0...v2.8.0\n[2.7.0]: https://github.com/Shopify/toxiproxy/compare/v2.6.0...v2.7.0\n[2.6.0]: https://github.com/Shopify/toxiproxy/compare/v2.5.0...v2.6.0\n[2.5.0]: https://github.com/Shopify/toxiproxy/compare/v2.4.0...v2.5.0\n[2.4.0]: https://github.com/Shopify/toxiproxy/compare/v2.3.0...v2.4.0\n[2.3.0]: https://github.com/Shopify/toxiproxy/compare/v2.2.0...v2.3.0\n[2.2.0]: https://github.com/Shopify/toxiproxy/compare/v2.1.7...v2.2.0\n[2.1.7]: https://github.com/Shopify/toxiproxy/compare/v2.1.6...v2.1.7\n[2.1.6]: https://github.com/Shopify/toxiproxy/compare/v2.1.5...v2.1.6\n[2.1.5]: https://github.com/Shopify/toxiproxy/compare/v2.1.4...v2.1.5\n[2.1.4]: https://github.com/Shopify/toxiproxy/compare/v2.1.3...v2.1.4\n[2.1.3]: https://github.com/Shopify/toxiproxy/compare/v2.1.2...v2.1.3\n[2.1.2]: https://github.com/Shopify/toxiproxy/compare/v2.1.1...v2.1.2\n[2.1.1]: https://github.com/Shopify/toxiproxy/compare/v2.1.0...v2.1.1\n[2.1.0]: https://github.com/Shopify/toxiproxy/compare/v2.0.0...v2.1.0\n[2.0.0]: https://github.com/Shopify/toxiproxy/compare/v1.2.1...v2.0.0\n[1.2.1]: https://github.com/Shopify/toxiproxy/compare/v1.2.0...v1.2.1\n[1.2.0]: https://github.com/Shopify/toxiproxy/compare/v1.1.0...v1.2.0\n[1.1.0]: https://github.com/Shopify/toxiproxy/compare/v1.0.3...v1.1.0\n[1.0.3]: https://github.com/Shopify/toxiproxy/compare/v1.0.2...v1.0.3\n[1.0.2]: https://github.com/Shopify/toxiproxy/compare/v1.0.1...v1.0.2\n[1.0.1]: https://github.com/Shopify/toxiproxy/compare/v1.0.0...v1.0.1\n[1.0.0]: https://github.com/Shopify/toxiproxy/releases/tag/v1.0.0\n"
  },
  {
    "path": "CREATING_TOXICS.md",
    "content": "# Creating custom toxics\n\nCreating a toxic is done by implementing the `Toxic` interface:\n\n```go\ntype Toxic interface {\n    Pipe(*toxics.ToxicStub)\n}\n```\n\nThe `Pipe()` function defines how data flows through the toxic, and is passed a\n`ToxicStub` to operate on. A `ToxicStub` stores the input and output channels for\nthe toxic, as well as an interrupt channel that is used to pause operation of the\ntoxic.\n\nThe input and output channels in a `ToxicStub` send and receive `StreamChunk` structs,\nwhich are similar to network packets. A `StreamChunk` contains a `byte[]` of stream\ndata, and a timestamp of when Toxiproxy received the data from the client or server.\nThis is used instead of just a plain `byte[]` so that toxics like latency can find out\nhow long a chunk of data has been waiting in the proxy.\n\nToxics are registered in an `init()` function so that they can be used by the server:\n\n```go\nfunc init() {\n    toxics.Register(\"toxic_name\", new(ExampleToxic))\n}\n```\n\nIn order to use your own toxics, you will need to compile your own binary.\nThis can be done by copying [server](./cmd/server/server.go)\ninto a new project and registering your toxic with the server.\nThis will allow you to add toxics without having to make a full fork of the project.\nIf you think your toxics will be useful to others,\ncontribute them back with a Pull Request.\n\nAn example project for building a separate binary can be found here:\n[examples](./_examples/toxics/)\n\n## A basic toxic\n\nThe most basic implementation of a toxic is the [noop toxic](./toxics/noop.go),\nwhich just passes data through without any modifications.\n\n```go\ntype NoopToxic struct{}\n\nfunc (t *NoopToxic) Pipe(stub *toxics.ToxicStub) {\n    for {\n        select {\n        case <-stub.Interrupt:\n            return\n        case c := <-stub.Input:\n            if c == nil {\n                stub.Close()\n                return\n            }\n            stub.Output <- c\n        }\n    }\n}\n```\n\nThe above code reads from `stub.Input` in a loop, and passes the `StreamChunk` along to\n`stub.Output`. Since reading from `stub.Input` will block until a chunk is available,\nwe need to check for interrupts as the same time.\n\nToxics will be interrupted whenever they are being updated, or possibly removed. This can\nhappen at any point within the `Pipe()` function, so all blocking operations (including sleep),\nshould be interruptible. When an interrupt is received, the toxic should return from the `Pipe()`\nfunction after it has written any \"in-flight\" data back to `stub.Output`. It is important that\nall data read from `stub.Input` is passed along to `stub.Output`, otherwise the stream will be\nmissing bytes and become corrupted.\n\nWhen an `end of stream` is reached, `stub.Input` will return a `nil` chunk. Whenever a\nnil chunk is returned, the toxic should call `Close()` on the stub, and return from `Pipe()`.\n\n## Toxic configuration\n\nToxic configuration information can be stored in the toxic struct. The toxic will be json\nencoded and decoded by the api, so all public fields will be api accessible.\n\nAn example of a toxic that uses configuration values is the [latency toxic](./toxics/latency.go)\n\n```go\ntype LatencyToxic struct {\n    Latency int64 `json:\"latency\"`\n    Jitter  int64 `json:\"jitter\"`\n}\n```\n\nThese fields can be used inside the `Pipe()` function, but generally should not be written\nto from the toxic. A separate instance of the toxic exists for each connection through the\nproxy, and may be replaced when updated by the api. If state is required in your toxic, it\nis better to use a local variable at the top of `Pipe()`, since struct fields are not\nguaranteed to be persisted across interrupts.\n\n## Toxic buffering\n\nBy default, toxics are not buffered. This means that writes to `stub.Output` will block until\neither the endpoint or another toxic reads it. Since toxics are chained together, this means\nnot reading from `stub.Input` will block other toxics (and endpoint writes) from operating.\nIf this is not behavior you want your toxic to have, you can specify a buffer size for your\ntoxic's input. The [latency toxic](./toxics/latency.go)\nuses this in order to prevent added latency from limiting the proxy bandwidth.\n\nSpecifying a buffer size is done by implementing the `BufferedToxic` interface, which adds the\n`GetBufferSize()` function:\n\n```go\nfunc (t *LatencyToxic) GetBufferSize() int {\n    return 1024\n}\n```\n\nThe unit used by `GetBufferSize()` is `StreamChunk`s. Chunks are generally anywhere from\n1 byte, up to 32KB, so keep this in mind when thinking about how much buffering you need,\nand how much memory you are comfortable with using.\n\n## Stateful toxics\n\nIf a toxic needs to store extra information for a connection such as the number of bytes\ntransferred (See `limit_data` toxic), a state object can be created by implementing the\n`StatefulToxic` interface. This interface defines the `NewState()` function that can create\na new state object with default values set.\n\nWhen a stateful toxic is created, the state object will be stored on the `ToxicStub` and\ncan be accessed from `toxic.Pipe()`:\n\n```go\nstate := stub.State.(*ExampleToxicState)\n```\n\nIf necessary, some global state can be stored in the toxic struct, which will not be\ninstanced per-connection. These fields cannot have a custom default value set and will\nnot be thread-safe, so proper locking or atomic operations will need to be used.\n\n## Using `io.Reader` and `io.Writer`\n\nIf your toxic involves modifying the data going through a proxy, you can use the `ChanReader`\nand `ChanWriter` interfaces in the [stream package](./stream).\nThese allow reading and writing from the input and output channels as you would a normal data\nstream such as a TCP socket.\n\nAn implementation of the noop toxic above using the stream package would look something like this:\n\n```go\nfunc (t *NoopToxic) Pipe(stub *toxics.ToxicStub) {\n    buf := make([]byte, 32*1024)\n    writer := stream.NewChanWriter(stub.Output)\n    reader := stream.NewChanReader(stub.Input)\n    reader.SetInterrupt(stub.Interrupt)\n    for {\n        n, err := reader.Read(buf)\n        if err == stream.ErrInterrupted {\n            writer.Write(buf[:n])\n            return\n        } else if err == io.EOF {\n            stub.Close()\n            return\n        }\n        writer.Write(buf[:n])\n    }\n}\n```\n\nSee [examples](./_examples/toxics/) for a full example of using\nthe stream package with Go's http package.\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM scratch\n\nEXPOSE 8474\nENTRYPOINT [\"/toxiproxy\"]\nCMD [\"-host=0.0.0.0\"]\n\nENV LOG_LEVEL=info\n\nCOPY toxiproxy-server-linux-* /toxiproxy\nCOPY toxiproxy-cli-linux-* /toxiproxy-cli\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014 Shopify\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "METRICS.md",
    "content": "# Metrics\n\n- [Metrics](#metrics)\n    - [Runtime Metrics](#runtime-metrics)\n    - [Proxy Metrics](#proxy-metrics)\n      - [toxiproxy_proxy_received_bytes_total / toxiproxy_proxy_sent_bytes_total](#toxiproxy_proxy_received_bytes_total--toxiproxy_proxy_sent_bytes_total)\n\n### Runtime Metrics\n\nTo enable runtime metrics related to the state of the go runtime, build version, process info, use the `-runtime-metrics` flag.\n\nFor more details, see below:\n- [NewGoCollector](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus/collectors#NewGoCollector)\n- [NewBuildInfoCollector](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus/collectors#NewBuildInfoCollector)\n- [NewProcessCollector](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus/collectors#NewProcessCollector)\n\n### Proxy Metrics\n\nTo enable metrics related to toxiproxy internals, use the `-proxy-metrics` flag.\n#### toxiproxy_proxy_received_bytes_total / toxiproxy_proxy_sent_bytes_total\n\nThe total number of bytes received/sent on a given proxy link in a given direction\n\n```mermaid\nsequenceDiagram\n    Client->>+Toxiproxy: toxiproxy_proxy_received_bytes_total{direction=\"upstream\"}\n    Toxiproxy->>+Server: toxiproxy_proxy_sent_bytes_total{direction=\"upstream\"}\n    Server->>+Toxiproxy: toxiproxy_proxy_received_bytes_total{direction=\"downstream\"}\n    Toxiproxy->>+Client: toxiproxy_proxy_sent_bytes_total{direction=\"downstream\"}\n```\n\n**Type**\n\nCounter\n\n**Labels**\n\n| Label     | Description                    | Example               |\n|-----------|--------------------------------|-----------------------|\n| direction | Direction of the link          | upstream / downstream |\n| listener  | Listener address of this proxy | 0.0.0.0:8080          |\n| proxy     | Proxy name                     | my-proxy              |\n| upstream  | Upstream address of this proxy | httpbin.org:80        |\n\n"
  },
  {
    "path": "Makefile",
    "content": "OS := $(shell uname -s)\nARCH := $(shell uname -m)\nGO_VERSION := $(shell go version | cut -f3 -d\" \")\nGO_MINOR_VERSION := $(shell echo $(GO_VERSION) | cut -f2 -d.)\nGO_PATCH_VERSION := $(shell echo $(GO_VERSION) | cut -f3 -d. | sed \"s/^\\s*$$/0/\")\nMALLOC_ENV := $(shell [ $(OS) = Darwin -a $(GO_MINOR_VERSION) -eq 17 -a $(GO_PATCH_VERSION) -lt 6 ] && echo \"MallocNanoZone=0\")\n\n.PHONY: all\nall: setup build test bench fmt lint\n\n.PHONY: test\ntest:\n\t# NOTE: https://github.com/golang/go/issues/49138\n\t$(MALLOC_ENV) go test -v -race -timeout 1m ./...\n\n.PHONY: test-e2e\ntest-e2e: build container.build\n\tscripts/test-e2e\n\ttimeout -v --foreground 20m scripts/test-e2e-hazelcast toxiproxy\n\n.PHONY: test-release\ntest-release: lint fmt test bench test-e2e release-dry\n\tscripts/test-release\n\n.PHONY: bench\nbench:\n\t# TODO: Investigate why benchmarks require more sockets: ulimit -n 10240\n\tgo test -bench=. -v *.go\n\tgo test -bench=. -v toxics/*.go\n\n.PHONY: fmt\nfmt:\n\tgo fmt ./...\n\tgoimports -w **/*.go\n\tgolangci-lint run --fix\n\tshfmt -l -s -w -kp -i 2 scripts/test-*\n\n.PHONY: lint\nlint:\n\tgolangci-lint run\n\tshellcheck scripts/test-*\n\tshfmt -l -s -d -kp -i 2 scripts/test-*\n\tyamllint .\n\n.PHONY: build\nbuild: dist clean\n\tgo build -ldflags=\"-s -w\" -o ./dist/toxiproxy-server ./cmd/server\n\tgo build -ldflags=\"-s -w\" -o ./dist/toxiproxy-cli ./cmd/cli\n\n.PHONY: container.build\ncontainer.build:\n\tenv GOOS=linux CGO_ENABLED=0 go build -ldflags=\"-s -w\" -o ./dist/toxiproxy-server-linux-$(ARCH) ./cmd/server\n\tenv GOOS=linux CGO_ENABLED=0 go build -ldflags=\"-s -w\" -o ./dist/toxiproxy-cli-linux-$(ARCH) ./cmd/cli\n\tdocker build -f Dockerfile -t toxiproxy dist\n\tdocker run --rm toxiproxy --version\n\n.PHONY: release\nrelease:\n\tgoreleaser release --rm-dist\n\n.PHONY: release-dry\nrelease-dry:\n\tgoreleaser release --rm-dist --skip-publish --skip-validate\n\n.PHONY: setup\nsetup:\n\tgo mod download\n\tgo mod tidy\n\ndist:\n\tmkdir -p dist\n\n.PHONY: clean\nclean:\n\trm -fr dist/*\n"
  },
  {
    "path": "README.md",
    "content": "# Toxiproxy\n[![GitHub release](https://img.shields.io/github/release/Shopify/toxiproxy.svg)](https://github.com/Shopify/toxiproxy/releases/latest)\n[![Build Status](https://github.com/Shopify/toxiproxy/actions/workflows/test.yml/badge.svg)](https://github.com/Shopify/toxiproxy/actions/workflows/test.yml)\n\n![](http://i.imgur.com/sOaNw0o.png)\n\nToxiproxy is a framework for simulating network conditions. It's made\nspecifically to work in testing, CI and development environments, supporting\ndeterministic tampering with connections, but with support for randomized chaos\nand customization. **Toxiproxy is the tool you need to prove with tests that\nyour application doesn't have single points of failure.** We've been\nsuccessfully using it in all development and test environments at Shopify since\nOctober, 2014. See our [blog post][blog] on resiliency for more information.\n\nToxiproxy usage consists of two parts. A TCP proxy written in Go (what this\nrepository contains) and a client communicating with the proxy over HTTP. You\nconfigure your application to make all test connections go through Toxiproxy\nand can then manipulate their health via HTTP. See [Usage](#usage)\nbelow on how to set up your project.\n\nFor example, to add 1000ms of latency to the response of MySQL from the [Ruby\nclient](https://github.com/Shopify/toxiproxy-ruby):\n\n```ruby\nToxiproxy[:mysql_master].downstream(:latency, latency: 1000).apply do\n  Shop.first # this takes at least 1s\nend\n```\n\nTo take down all Redis instances:\n\n```ruby\nToxiproxy[/redis/].down do\n  Shop.first # this will throw an exception\nend\n```\n\nWhile the examples in this README are currently in Ruby, there's nothing\nstopping you from creating a client in any other language (see\n[Clients](#clients)).\n\n## Table of Contents\n\n- [Toxiproxy](#toxiproxy)\n  - [Table of Contents](#table-of-contents)\n  - [Why yet another chaotic TCP proxy?](#why-yet-another-chaotic-tcp-proxy)\n  - [Clients](#clients)\n  - [Example](#example)\n  - [Usage](#usage)\n    - [1. Installing Toxiproxy](#1-installing-toxiproxy)\n      - [Upgrading from Toxiproxy 1.x](#upgrading-from-toxiproxy-1x)\n    - [2. Populating Toxiproxy](#2-populating-toxiproxy)\n    - [3. Using Toxiproxy](#3-using-toxiproxy)\n    - [4. Logging](#4-logging)\n    - [Toxics](#toxics)\n      - [latency](#latency)\n      - [down](#down)\n      - [bandwidth](#bandwidth)\n      - [slow_close](#slow_close)\n      - [timeout](#timeout)\n      - [reset_peer](#reset_peer)\n      - [slicer](#slicer)\n      - [limit_data](#limit_data)\n    - [HTTP API](#http-api)\n      - [Proxy fields:](#proxy-fields)\n      - [Toxic fields:](#toxic-fields)\n      - [Endpoints](#endpoints)\n      - [Populating Proxies](#populating-proxies)\n    - [CLI Example](#cli-example)\n    - [Metrics](#metrics)\n    - [Frequently Asked Questions](#frequently-asked-questions)\n    - [Development](#development)\n    - [Release](#release)\n\n## Why yet another chaotic TCP proxy?\n\nThe existing ones we found didn't provide the kind of dynamic API we needed for\nintegration and unit testing. Linux tools like `nc` and so on are not\ncross-platform and require root, which makes them problematic in test,\ndevelopment and CI environments.\n\n## Clients\n\n* [toxiproxy-ruby](https://github.com/Shopify/toxiproxy-ruby)\n* [toxiproxy-go](https://github.com/Shopify/toxiproxy/tree/main/client)\n* [toxiproxy-python](https://github.com/douglas/toxiproxy-python)\n* [toxiproxy.net](https://github.com/mdevilliers/Toxiproxy.Net)\n* [toxiproxy-php-client](https://github.com/ihsw/toxiproxy-php-client)\n* [toxiproxy-node-client](https://github.com/ihsw/toxiproxy-node-client)\n* [toxiproxy-java](https://github.com/trekawek/toxiproxy-java)\n* [toxiproxy-haskell](https://github.com/jpittis/toxiproxy-haskell)\n* [toxiproxy-rust](https://github.com/itarato/toxiproxy_rust)\n* [toxiproxy-elixir](https://github.com/Jcambass/toxiproxy_ex)\n\n## Example\n\nLet's walk through an example with a Rails application. Note that Toxiproxy is\nin no way tied to Ruby, it's just been our first use case. You can see the full example at\n[sirupsen/toxiproxy-rails-example](https://github.com/sirupsen/toxiproxy-rails-example).\nTo get started right away, jump down to [Usage](#usage).\n\nFor our popular blog, for some reason we're storing the tags for our posts in\nRedis and the posts themselves in MySQL. We might have a `Post` class that\nincludes some methods to manipulate tags in a [Redis set](http://redis.io/commands#set):\n\n```ruby\nclass Post < ActiveRecord::Base\n  # Return an Array of all the tags.\n  def tags\n    TagRedis.smembers(tag_key)\n  end\n\n  # Add a tag to the post.\n  def add_tag(tag)\n    TagRedis.sadd(tag_key, tag)\n  end\n\n  # Remove a tag from the post.\n  def remove_tag(tag)\n    TagRedis.srem(tag_key, tag)\n  end\n\n  # Return the key in Redis for the set of tags for the post.\n  def tag_key\n    \"post:tags:#{self.id}\"\n  end\nend\n```\n\nWe've decided that erroring while writing to the tag data store\n(adding/removing) is OK. However, if the tag data store is down, we should be\nable to see the post with no tags. We could simply rescue the\n`Redis::CannotConnectError` around the `SMEMBERS` Redis call in the `tags`\nmethod. Let's use Toxiproxy to test that.\n\nSince we've already installed Toxiproxy and it's running on our machine, we can\nskip to step 2. This is where we need to make sure Toxiproxy has a mapping for\nRedis tags. To `config/boot.rb` (before any connection is made) we add:\n\n```ruby\nrequire 'toxiproxy'\n\nToxiproxy.populate([\n  {\n    name: \"toxiproxy_test_redis_tags\",\n    listen: \"127.0.0.1:22222\",\n    upstream: \"127.0.0.1:6379\"\n  }\n])\n```\n\nThen in `config/environments/test.rb` we set the `TagRedis` to be a Redis client\nthat connects to Redis through Toxiproxy by adding this line:\n\n```ruby\nTagRedis = Redis.new(port: 22222)\n```\n\nAll calls in the test environment now go through Toxiproxy. That means we can\nadd a unit test where we simulate a failure:\n\n```ruby\ntest \"should return empty array when tag redis is down when listing tags\" do\n  @post.add_tag \"mammals\"\n\n  # Take down all Redises in Toxiproxy\n  Toxiproxy[/redis/].down do\n    assert_equal [], @post.tags\n  end\nend\n```\n\nThe test fails with `Redis::CannotConnectError`. Perfect! Toxiproxy took down\nthe Redis successfully for the duration of the closure. Let's fix the `tags`\nmethod to be resilient:\n\n```ruby\ndef tags\n  TagRedis.smembers(tag_key)\nrescue Redis::CannotConnectError\n  []\nend\n```\n\nThe tests pass! We now have a unit test that proves fetching the tags when Redis\nis down returns an empty array, instead of throwing an exception. For full\ncoverage you should also write an integration test that wraps fetching the\nentire blog post page when Redis is down.\n\nFull example application is at\n[sirupsen/toxiproxy-rails-example](https://github.com/sirupsen/toxiproxy-rails-example).\n\n## Usage\n\nConfiguring a project to use Toxiproxy consists of three steps:\n\n1. Installing Toxiproxy\n2. Populating Toxiproxy\n3. Using Toxiproxy\n\n### 1. Installing Toxiproxy\n\n**Linux**\n\nSee [`Releases`](https://github.com/Shopify/toxiproxy/releases) for the latest\nbinaries and system packages for your architecture.\n\n**Ubuntu**\n\n```bash\n$ wget -O toxiproxy-2.1.4.deb https://github.com/Shopify/toxiproxy/releases/download/v2.1.4/toxiproxy_2.1.4_amd64.deb\n$ sudo dpkg -i toxiproxy-2.1.4.deb\n$ sudo service toxiproxy start\n```\n\n**OS X**\n\nWith [Homebrew](https://brew.sh/):\n\n```bash\n$ brew tap shopify/shopify\n$ brew install toxiproxy\n```\n\nOr with [MacPorts](https://www.macports.org/):\n\n```bash\n$ port install toxiproxy\n```\n\n**Windows**\n\nToxiproxy for Windows is available for download at https://github.com/Shopify/toxiproxy/releases/download/v2.1.4/toxiproxy-server-windows-amd64.exe\n\n**Docker**\n\nToxiproxy is available on [Github container registry](https://github.com/Shopify/toxiproxy/pkgs/container/toxiproxy).\nOld versions `<= 2.1.4` are available on on [Docker Hub](https://hub.docker.com/r/shopify/toxiproxy/).\n\n```bash\n$ docker pull ghcr.io/shopify/toxiproxy\n$ docker run --rm -it ghcr.io/shopify/toxiproxy\n```\n\nIf using Toxiproxy from the host rather than other containers, enable host networking with `--net=host`.\n\n```shell\n$ docker run --rm --entrypoint=\"/toxiproxy-cli\" -it ghcr.io/shopify/toxiproxy list\n```\n\n**Source**\n\nIf you have Go installed, you can build Toxiproxy from source using the make file:\n```bash\n$ make build\n$ ./toxiproxy-server\n```\n\n#### Upgrading from Toxiproxy 1.x\n\nIn Toxiproxy 2.0 several changes were made to the API that make it incompatible with version 1.x.\nIn order to use version 2.x of the Toxiproxy server, you will need to make sure your client\nlibrary supports the same version. You can check which version of Toxiproxy you are running by\nlooking at the `/version` endpoint.\n\nSee the documentation for your client library for specific library changes. Detailed changes\nfor the Toxiproxy server can been found in [CHANGELOG.md](./CHANGELOG.md).\n\n### 2. Populating Toxiproxy\n\nWhen your application boots, it needs to make sure that Toxiproxy knows which\nendpoints to proxy where. The main parameters are: name, address for Toxiproxy\nto **listen** on and the address of the upstream.\n\nSome client libraries have helpers for this task, which is essentially just\nmaking sure each proxy in a list is created. Example from the Ruby client:\n\n```ruby\n# Make sure `shopify_test_redis_master` and `shopify_test_mysql_master` are\n# present in Toxiproxy\nToxiproxy.populate([\n  {\n    name: \"shopify_test_redis_master\",\n    listen: \"127.0.0.1:22220\",\n    upstream: \"127.0.0.1:6379\"\n  },\n  {\n    name: \"shopify_test_mysql_master\",\n    listen: \"127.0.0.1:24220\",\n    upstream: \"127.0.0.1:3306\"\n  }\n])\n```\n\nThis code needs to run as early in boot as possible, before any code establishes\na connection through Toxiproxy. Please check your client library for\ndocumentation on the population helpers.\n\nAlternatively use the CLI to create proxies, e.g.:\n\n```bash\ntoxiproxy-cli create -l localhost:26379 -u localhost:6379 shopify_test_redis_master\n```\n\nWe recommend a naming such as the above: `<app>_<env>_<data store>_<shard>`.\nThis makes sure there are no clashes between applications using the same\nToxiproxy.\n\nFor large application we recommend storing the Toxiproxy configurations in a\nseparate configuration file. We use `config/toxiproxy.json`. This file can be\npassed to the server using the `-config` option, or loaded by the application\nto use with the `populate` function.\n\nAn example `config/toxiproxy.json`:\n\n```json\n[\n  {\n    \"name\": \"web_dev_frontend_1\",\n    \"listen\": \"[::]:18080\",\n    \"upstream\": \"webapp.domain:8080\",\n    \"enabled\": true\n  },\n  {\n    \"name\": \"web_dev_mysql_1\",\n    \"listen\": \"[::]:13306\",\n    \"upstream\": \"database.domain:3306\",\n    \"enabled\": true\n  }\n]\n```\n\nUse ports outside the ephemeral port range to avoid random port conflicts.\nIt's `32,768` to `61,000` on Linux by default, see\n`/proc/sys/net/ipv4/ip_local_port_range`.\n\n### 3. Using Toxiproxy\n\nTo use Toxiproxy, you now need to configure your application to connect through\nToxiproxy. Continuing with our example from step two, we can configure our Redis\nclient to connect through Toxiproxy:\n\n```ruby\n# old straight to redis\nredis = Redis.new(port: 6380)\n\n# new through toxiproxy\nredis = Redis.new(port: 22220)\n```\n\nNow you can tamper with it through the Toxiproxy API. In Ruby:\n\n```ruby\nredis = Redis.new(port: 22220)\n\nToxiproxy[:shopify_test_redis_master].downstream(:latency, latency: 1000).apply do\n  redis.get(\"test\") # will take 1s\nend\n```\n\nOr via the CLI:\n\n```bash\ntoxiproxy-cli toxic add -t latency -a latency=1000 shopify_test_redis_master\n```\n\nPlease consult your respective client library on usage.\n\n### 4. Logging\n\nThere are the following log levels: panic, fatal, error, warn or warning, info, debug and trace.\nThe level could be updated via environment variable `LOG_LEVEL`.\n\n### Toxics\n\nToxics manipulate the pipe between the client and upstream. They can be added\nand removed from proxies using the [HTTP api](#http-api). Each toxic has its own parameters\nto change how it affects the proxy links.\n\nFor documentation on implementing custom toxics, see [CREATING_TOXICS.md](./CREATING_TOXICS.md)\n\n#### latency\n\nAdd a delay to all data going through the proxy. The delay is equal to `latency` +/- `jitter`.\n\nAttributes:\n\n - `latency`: time in milliseconds\n - `jitter`: time in milliseconds\n\n#### down\n\nBringing a service down is not technically a toxic in the implementation of\nToxiproxy. This is done by `POST`ing to `/proxies/{proxy}` and setting the\n`enabled` field to `false`.\n\n#### bandwidth\n\nLimit a connection to a maximum number of kilobytes per second.\n\nAttributes:\n\n - `rate`: rate in KB/s\n\n#### slow_close\n\nDelay the TCP socket from closing until `delay` has elapsed.\n\nAttributes:\n\n - `delay`: time in milliseconds\n\n#### timeout\n\nStops all data from getting through, and closes the connection after `timeout`. If\n`timeout` is 0, the connection won't close, and data will be dropped until the\ntoxic is removed.\n\nAttributes:\n\n - `timeout`: time in milliseconds\n\n#### reset_peer\n\nSimulate TCP RESET (Connection reset by peer) on the connections by closing the stub Input\nimmediately or after a `timeout`.\n\nAttributes:\n\n - `timeout`: time in milliseconds\n\n#### slicer\n\nSlices TCP data up into small bits, optionally adding a delay between each\nsliced \"packet\".\n\nAttributes:\n\n - `average_size`: size in bytes of an average packet\n - `size_variation`: variation in bytes of an average packet (should be smaller than average_size)\n - `delay`: time in microseconds to delay each packet by\n\n#### limit_data\n\nCloses connection when transmitted data exceeded limit.\n\n - `bytes`: number of bytes it should transmit before connection is closed\n\n### HTTP API\n\nAll communication with the Toxiproxy daemon from the client happens through the\nHTTP interface, which is described here.\n\nToxiproxy listens for HTTP on port **8474**.\n\n#### Proxy fields:\n\n - `name`: proxy name (string)\n - `listen`: listen address (string)\n - `upstream`: proxy upstream address (string)\n - `enabled`: true/false (defaults to true on creation)\n\nTo change a proxy's name, it must be deleted and recreated.\n\nChanging the `listen` or `upstream` fields will restart the proxy and drop any active connections.\n\nIf `listen` is specified with a port of 0, toxiproxy will pick an ephemeral port. The `listen` field\nin the response will be updated with the actual port.\n\nIf you change `enabled` to `false`, it will take down the proxy. You can switch it\nback to `true` to reenable it.\n\n#### Toxic fields:\n\n - `name`: toxic name (string, defaults to `<type>_<stream>`)\n - `type`: toxic type (string)\n - `stream`: link direction to affect (defaults to `downstream`)\n - `toxicity`: probability of the toxic being applied to a link (defaults to 1.0, 100%)\n - `attributes`: a map of toxic-specific attributes\n\nSee [Toxics](#toxics) for toxic-specific attributes.\n\nThe `stream` direction must be either `upstream` or `downstream`. `upstream` applies\nthe toxic on the `client -> server` connection, while `downstream` applies the toxic\non the `server -> client` connection. This can be used to modify requests and responses\nseparately.\n\n#### Endpoints\n\nAll endpoints are JSON.\n\n - **GET /proxies** - List existing proxies and their toxics\n - **POST /proxies** - Create a new proxy\n - **POST /populate** - Create or replace a list of proxies\n - **GET /proxies/{proxy}** - Show the proxy with all its active toxics\n - **POST /proxies/{proxy}** - Update a proxy's fields\n - **DELETE /proxies/{proxy}** - Delete an existing proxy\n - **GET /proxies/{proxy}/toxics** - List active toxics\n - **POST /proxies/{proxy}/toxics** - Create a new toxic\n - **GET /proxies/{proxy}/toxics/{toxic}** - Get an active toxic's fields\n - **POST /proxies/{proxy}/toxics/{toxic}** - Update an active toxic\n - **DELETE /proxies/{proxy}/toxics/{toxic}** - Remove an active toxic\n - **POST /reset** - Enable all proxies and remove all active toxics\n - **GET /version** - Returns the server version number\n - **GET /metrics** - Returns Prometheus-compatible metrics\n\n#### Populating Proxies\n\nProxies can be added and configured in bulk using the `/populate` endpoint. This is done by\npassing a json array of proxies to toxiproxy. If a proxy with the same name already exists,\nit will be compared to the new proxy and replaced if the `upstream` and `listen` address don't match.\n\nA `/populate` call can be included for example at application start to ensure all required proxies\nexist. It is safe to make this call several times, since proxies will be untouched as long as their\nfields are consistent with the new data.\n\n### CLI Example\n\n```bash\n$ toxiproxy-cli create -l localhost:26379 -u localhost:6379 redis\nCreated new proxy redis\n$ toxiproxy-cli list\nListen          Upstream        Name  Enabled Toxics\n======================================================================\n127.0.0.1:26379 localhost:6379  redis true    None\n\nHint: inspect toxics with `toxiproxy-client inspect <proxyName>`\n```\n\n```bash\n$ redis-cli -p 26379\n127.0.0.1:26379> SET omg pandas\nOK\n127.0.0.1:26379> GET omg\n\"pandas\"\n```\n\n```bash\n$ toxiproxy-cli toxic add -t latency -a latency=1000 redis\nAdded downstream latency toxic 'latency_downstream' on proxy 'redis'\n```\n\n```bash\n$ redis-cli -p 26379\n127.0.0.1:26379> GET omg\n\"pandas\"\n(1.00s)\n127.0.0.1:26379> DEL omg\n(integer) 1\n(1.00s)\n```\n\n```bash\n$ toxiproxy-cli toxic remove -n latency_downstream redis\nRemoved toxic 'latency_downstream' on proxy 'redis'\n```\n\n```bash\n$ redis-cli -p 26379\n127.0.0.1:26379> GET omg\n(nil)\n```\n\n```bash\n$ toxiproxy-cli delete redis\nDeleted proxy redis\n```\n\n```bash\n$ redis-cli -p 26379\nCould not connect to Redis at 127.0.0.1:26379: Connection refused\n```\n\n### Metrics\n\nToxiproxy exposes Prometheus-compatible metrics via its HTTP API at /metrics.\nSee [METRICS.md](./METRICS.md) for full descriptions\n\n### Frequently Asked Questions\n\n**How fast is Toxiproxy?** The speed of Toxiproxy depends largely on your hardware,\nbut you can expect a latency of *< 100µs* when no toxics are enabled. When running\nwith `GOMAXPROCS=4` on a Macbook Pro we achieved *~1000MB/s* throughput, and as high\nas *2400MB/s* on a higher end desktop. Basically, you can expect Toxiproxy to move\ndata around at least as fast the app you're testing.\n\n**Can Toxiproxy do randomized testing?** Many of the available toxics can be configured\nto have randomness, such as `jitter` in the `latency` toxic. There is also a\nglobal `toxicity` parameter that specifies the percentage of connections a toxic\nwill affect. This is most useful for things like the `timeout` toxic, which would\nallow X% of connections to timeout.\n\n**I am not seeing my Toxiproxy actions reflected for MySQL**. MySQL will prefer\nthe local Unix domain socket for some clients, no matter which port you pass it\nif the host is set to `localhost`. Configure your MySQL server to not create a\nsocket, and use `127.0.0.1` as the host. Remember to remove the old socket\nafter you restart the server.\n\n**Toxiproxy causes intermittent connection failures**. Use ports outside the\nephemeral port range to avoid random port conflicts. It's `32,768` to `61,000` on\nLinux by default, see `/proc/sys/net/ipv4/ip_local_port_range`.\n\n**Should I run a Toxiproxy for each application?** No, we recommend using the\nsame Toxiproxy for all applications. To distinguish between services we\nrecommend naming your proxies with the scheme: `<app>_<env>_<data store>_<shard>`.\nFor example, `shopify_test_redis_master` or `shopify_development_mysql_1`.\n\n### Development\n\n* `make`. Build a toxiproxy development binary for the current platform.\n* `make all`. Build Toxiproxy binaries and packages for all platforms. Requires\n  to have Go compiled with cross compilation enabled on Linux and Darwin (amd64)\n  as well as [`goreleaser`](https://goreleaser.com/) in your `$PATH` to\n  build binaries the Linux package.\n* `make test`. Run the Toxiproxy tests.\n\n### Release\n\nSee [RELEASE.md](./RELEASE.md)\n\n[blog]: https://shopify.engineering/building-and-testing-resilient-ruby-on-rails-applications\n"
  },
  {
    "path": "RELEASE.md",
    "content": "# Releasing\n\n- [Releasing](#releasing)\n  - [Before You Begin](#before-you-begin)\n  - [Local Release Preparation](#local-release-preparation)\n    - [Checkout latest code](#checkout-latest-code)\n    - [Update the CHANGELOG.md](#update-the-changelogmd)\n    - [Create Release Commit and Tag](#create-release-commit-and-tag)\n    - [Run Pre-Release Tests](#run-pre-release-tests)\n  - [Push Release Tag](#push-release-tag)\n  - [Verify Github Release](#verify-github-release)\n  - [Update Homebrew versions](#update-homebrew-versions)\n\n## Before You Begin\n\nEnsure your local workstation is configured to be able to [Sign commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits)\n\n## Local Release Preparation\n\n### Checkout latest code\n\n```shell\ngit checkout main\ngit pull origin main\n```\n\n### Update the [CHANGELOG.md](CHANGELOG.md)\n\n- Add a new version header at the top of the document, just after `# [Unreleased]`\n- Update links at bottom of changelog\n\n### Create Release Commit and Tag\n\n```shell\nexport RELEASE_VERSION=2.x.y\ngit commit -a -S -m \"Release $RELEASE_VERSION\"\ngit tag -s \"v$RELEASE_VERSION\" # When prompted for a commit message, enter the 'release notes' style message, just like on the releases page\n```\n\n### Run Pre-Release Tests\n\n```shell\nmake test-release\n```\n\n- Push to Main Branch\n```shell\ngit push origin main --follow-tags\n```\n\n## Push Release Tag\n\n- On your local machine again, push your tag to github\n\n```shell\ngit push origin \"v$RELEASE_VERSION\"\n```\n\n## Verify Github Release\n\n- Github Actions should kick off a build and release after the tag is pushed.\n- Verify that a [Release gets created in Github](https://github.com/Shopify/toxiproxy/releases) and verify that the release notes look correct\n- Github Actions should also attatch the built binaries to the release (it might take a few mins)\n\n## Update Homebrew versions\n\n- Update [homebrew-shopify toxiproxy.rb](https://github.com/Shopify/homebrew-shopify/blob/master/toxiproxy.rb#L9) manifest\n  1. Update `app_version` string to your released version\n  2. Update hashes for all platforms (find the hashes in the checksums.txt from your release notes)\n\n- Do a manual check of installing toxiproxy via brew\n  1. While in the homebrew-shopify directory...\n  ```shell\n  brew install ./toxiproxy.rb --debug\n  ```\n  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.\n- PR the version update change and merge\n"
  },
  {
    "path": "_examples/tests/README.md",
    "content": "## Tests with Toxiproxy\n\n### Setup\n\n```shell\n$ brew install shopify/shopify/toxiproxy kind\n$ kind create cluster --config=cluster.yml\n$ kubectl --context kind-kind apply -f resources.yml\n$ kubectl wait deploy postgres --for condition=available --timeout=5m\n$ psql -h 127.0.0.1 -U postgres -c \"DROP DATABASE IF EXISTS sample\"\n$ psql -h 127.0.0.1 -U postgres -c \"CREATE DATABASE sample\"\n$ psql -h 127.0.0.1 -U postgres -c \"CREATE DATABASE sample_test\"\n```\n\n### Run\n\n```shell\n$ go run ./\n```\n\n### Test\n\n```shell\n$ go test -v .\n$ go test -v . -run TestMultipleToxics\n```\n"
  },
  {
    "path": "_examples/tests/cluster.yml",
    "content": "---\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnetworking:\n  ipFamily: ipv4\nnodes:\n  - role: control-plane\n    extraPortMappings:\n      # port forward 5432 on the host to 5432 on this node\n      - containerPort: 30950\n        hostPort: 5432\n        # optional: set the bind address on the host\n        # 0.0.0.0 is the current default\n        listenAddress: 127.0.0.1\n        # optional: set the protocol to one of TCP, UDP, SCTP.\n        # TCP is the default\n        protocol: TCP\n...\n"
  },
  {
    "path": "_examples/tests/db.go",
    "content": "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 string) (*pg.DB, error) {\n\tdb := pg.Connect(&pg.Options{\n\t\tAddr:     addr,\n\t\tUser:     \"postgres\",\n\t\tDatabase: database,\n\t})\n\n\terr := createSchema(db)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = seed(db)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn db, nil\n}\n\nfunc createSchema(db *pg.DB) error {\n\tmodels := []interface{}{\n\t\t(*User)(nil),\n\t\t(*Story)(nil),\n\t}\n\n\tfor _, model := range models {\n\t\terr := db.Model(model).CreateTable(&orm.CreateTableOptions{\n\t\t\tTemp: true,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc seed(db *pg.DB) error {\n\tuser1 := &User{\n\t\tName:   \"admin\",\n\t\tEmails: []string{\"admin1@admin\", \"admin2@admin\"},\n\t}\n\t_, err := db.Model(user1).Insert()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = db.Model(&User{\n\t\tName:   \"root\",\n\t\tEmails: []string{\"root1@root\", \"root2@root\"},\n\t}).Insert()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tstory1 := &Story{\n\t\tTitle:    \"Cool story\",\n\t\tAuthorId: user1.Id,\n\t}\n\t_, err = db.Model(story1).Insert()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "_examples/tests/db_test.go",
    "content": "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 \"github.com/Shopify/toxiproxy/v2/client\"\n\tpg \"github.com/go-pg/pg/v10\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/rs/zerolog\"\n)\n\nvar db *pg.DB\nvar toxi *toxiproxy.Client\nvar proxies map[string]*toxiproxy.Proxy\n\nfunc DB() *pg.DB {\n\tif db == nil {\n\t\tvar err error\n\t\tdb, err = setupDB(\":35432\", \"sample_test\")\n\t\tif err != nil {\n\t\t\tlog.Panicf(\"Could not connect to DB: %+v\", err)\n\t\t}\n\t}\n\treturn db\n}\n\nfunc connectDB(addr string) *pg.DB {\n\treturn pg.Connect(&pg.Options{\n\t\tAddr:     addr,\n\t\tUser:     \"postgres\",\n\t\tDatabase: \"sample_test\",\n\t})\n}\n\nfunc init() {\n\tlog.SetFlags(log.LstdFlags | log.Lshortfile)\n\tfmt.Println(\"=== SETUP\")\n\trunToxiproxyServer()\n\tpopulateProxies()\n}\n\nfunc populateProxies() {\n\tif toxi == nil {\n\t\ttoxi = toxiproxy.NewClient(\"localhost:8474\")\n\t}\n\n\tvar err error\n\t_, err = toxi.Populate([]toxiproxy.Proxy{{\n\t\tName:     \"postgresql\",\n\t\tListen:   \"localhost:35432\",\n\t\tUpstream: \"localhost:5432\",\n\t\tEnabled:  true,\n\t}})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tproxies, err = toxi.Proxies()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc runToxiproxyServer() {\n\tvar err error\n\ttimeout := 5 * time.Second\n\n\t// Check if there is instance run\n\tconn, err := net.DialTimeout(\"tcp\", \"localhost:8474\", timeout)\n\tif err == nil {\n\t\tconn.Close()\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tmetricsContainer := toxiServer.NewMetricsContainer(prometheus.NewRegistry())\n\t\tserver := toxiServer.NewServer(metricsContainer, zerolog.Nop())\n\t\tserver.Listen(\"localhost:8474\")\n\t}()\n\n\tfor i := 0; i < 10; i += 1 {\n\t\tconn, err := net.DialTimeout(\"tcp\", \"localhost:8474\", timeout)\n\t\tif err == nil {\n\t\t\tconn.Close()\n\t\t\treturn\n\t\t}\n\t}\n\tpanic(err)\n}\n\nfunc TestSlowDBConnection(t *testing.T) {\n\tdb := DB()\n\n\t// Add 1s latency to 100% of downstream connections\n\tproxies[\"postgresql\"].AddToxic(\"latency_down\", \"latency\", \"downstream\", 1.0, toxiproxy.Attributes{\n\t\t\"latency\": 10000,\n\t})\n\tdefer proxies[\"postgresql\"].RemoveToxic(\"latency_down\")\n\n\terr := process(db)\n\tif err != nil {\n\t\tt.Fatalf(\"got error %v, wanted no errors\", err)\n\t}\n}\n\nfunc TestOutageResetPeer(t *testing.T) {\n\tdb := DB()\n\n\t// Add broken TCP connection\n\tproxies[\"postgresql\"].AddToxic(\"reset_peer_down\", \"reset_peer\", \"downstream\", 1.0, toxiproxy.Attributes{\n\t\t\"timeout\": 10,\n\t})\n\tdefer proxies[\"postgresql\"].RemoveToxic(\"reset_peer_down\")\n\n\terr := process(db)\n\tif err == nil {\n\t\tt.Fatalf(\"expect error\")\n\t}\n}\n"
  },
  {
    "path": "_examples/tests/main.go",
    "content": "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 != nil {\n\t\tlog.Printf(\"ERROR: %v\", err)\n\t\tos.Exit(1)\n\t}\n}\n\nfunc run() error {\n\tdb, err := setupDB(\":5432\", \"sample\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer db.Close()\n\n\tctx := context.Background()\n\tif err := db.Ping(ctx); err != nil {\n\t\treturn err\n\t}\n\n\tprocess(db)\n\n\treturn nil\n}\n\nfunc process(db *pg.DB) error {\n\tvar users []User\n\terr := db.Model(&users).Select()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, user := range users {\n\t\tlog.Printf(\"user: %v\", user)\n\t}\n\n\t// Select story and associated author in one query.\n\tstory := new(Story)\n\terr = db.Model(story).\n\t\tRelation(\"Author\").\n\t\tWhere(\"story.id = ?\", 1).\n\t\tSelect()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlog.Printf(\"story: %v\", story)\n\n\treturn nil\n}\n"
  },
  {
    "path": "_examples/tests/models.go",
    "content": "package main\n\nimport \"fmt\"\n\ntype User struct {\n\tId     int64\n\tName   string\n\tEmails []string\n}\n\nfunc (u User) String() string {\n\treturn fmt.Sprintf(\"User<%d %s %v>\", u.Id, u.Name, u.Emails)\n}\n\ntype Story struct {\n\tId       int64\n\tTitle    string\n\tAuthorId int64\n\tAuthor   *User `pg:\"rel:has-one\"`\n}\n\nfunc (s Story) String() string {\n\treturn fmt.Sprintf(\"Story<%d %s %s>\", s.Id, s.Title, s.Author)\n}\n"
  },
  {
    "path": "_examples/tests/resources.yml",
    "content": "---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: postgres\n  labels:\n    app: postgres\ndata:\n  POSTGRES_DB: postgres\n  POSTGRES_USER: postgres\n  POSTGRES_PASSWORD: Welcome\n  POSTGRES_HOST_AUTH_METHOD: trust\n...\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: postgres\n  labels:\n    app: postgres\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: postgres\n  template:\n    metadata:\n      labels:\n        app: postgres\n    spec:\n      containers:\n        - name: postgres\n          image: postgres\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 5432\n          envFrom:\n            - configMapRef:\n                name: postgres\n...\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: postgres\n  labels:\n    app: postgres\nspec:\n  type: NodePort\n  ports:\n    - port: 5432\n      nodePort: 30950\n  selector:\n    app: postgres\n...\n"
  },
  {
    "path": "_examples/toxics/README.md",
    "content": "## Create Toxic\n\nExample how to start building own toxics.\n\n### Debug toxic\n\nRun custom toxiserver with DebugToxic.\n\n```shell\n$ go run debug_toxic.go\n```\n\nRun redis-server in separate terminal:\n\n```shell\n$ redis-server\n```\n\nTest toxic with:\n\n```shell\n$ toxiproxy-cli --host \"http://localhost:8484\" create -l :16379 -u localhost:6379 redis\n$ toxiproxy-cli --host \"http://localhost:8484\" toxic add --type debug redis\n$ redis-cli -p 16379 \"keys\" \"*\"\n```\n\nCustom Toxiproxy should print bytes in hex format.\n\n### HTTP toxic\n\nRun custom toxiserver with DebugToxic.\n\n```shell\n$ go run http_toxic.go\n```\n\nTest toxic with command and verify output:\n\n```shell\n$ toxiproxy-cli --host \"http://localhost:8484\" create -l :18080 -u example.com:80 example\n$ toxiproxy-cli --host \"http://localhost:8484\" toxic add --type http example\n$ curl -v localhost:18080/hello\n...\n< HTTP/1.1 404 Not Found\n< Location: https://github.com/Shopify/toxiproxy\n```\n"
  },
  {
    "path": "_examples/toxics/debug_toxic.go",
    "content": "// 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\"\n\t\"io\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/rs/zerolog\"\n\n\t\"github.com/Shopify/toxiproxy/v2\"\n\t\"github.com/Shopify/toxiproxy/v2/toxics\"\n\t\"github.com/Shopify/toxiproxy/v2/stream\"\n)\n\n// DebugToxic prints bytes processed through pipe.\ntype DebugToxic struct{}\n\nfunc (t *DebugToxic) PrintHex(data []byte) {\n\tfor i := 0; i < len(data); {\n\t\tfor j := 0; j < 4; j +=1 {\n\t\t\tx := i + 8\n\t\t\tif x >= len(data) {\n\t\t\t\tx = len(data) - 1\n\t\t\t\tfmt.Printf(\"% x\\n\", data[i:x])\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfmt.Printf(\"% x\\t\\t\", data[i:x])\n\t\t\ti = x\n\t\t}\n\t\tfmt.Println()\n\t}\n}\n\nfunc (t *DebugToxic) Pipe(stub *toxics.ToxicStub) {\n\tbuf := make([]byte, 32*1024)\n\twriter := stream.NewChanWriter(stub.Output)\n\treader := stream.NewChanReader(stub.Input)\n\treader.SetInterrupt(stub.Interrupt)\n\tfor {\n\t\t\tn, err := reader.Read(buf)\n\t\t\tlog.Printf(\"-- [DebugToxic] Processed %d bytes\\n\", n)\n\t\t\tif err == stream.ErrInterrupted {\n\t\t\t\t\twriter.Write(buf[:n])\n\t\t\t\t\treturn\n\t\t\t} else if err == io.EOF {\n\t\t\t\t\tstub.Close()\n\t\t\t\t\treturn\n\t\t\t}\n\t\t\tt.PrintHex(buf[:n])\n\t\t\twriter.Write(buf[:n])\n\t}\n\n}\n\nfunc main() {\n\ttoxics.Register(\"debug\", new(DebugToxic))\n\n\tlogger := zerolog.New(os.Stderr).With().Caller().Timestamp().Logger()\n\tmetrics := toxiproxy.NewMetricsContainer(prometheus.NewRegistry())\n\tserver := toxiproxy.NewServer(metrics, logger)\n\tserver.Listen(\"0.0.0.0:8484\")\n}\n"
  },
  {
    "path": "_examples/toxics/http_toxic.go",
    "content": "// Ported from https://github.com/xthexder/toxic-example/blob/master/http.go\npackage main\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/rs/zerolog\"\n\n\n\t\"github.com/Shopify/toxiproxy/v2\"\n\t\"github.com/Shopify/toxiproxy/v2/stream\"\n\t\"github.com/Shopify/toxiproxy/v2/toxics\"\n)\n\ntype HttpToxic struct{}\n\nfunc (t *HttpToxic) ModifyResponse(resp *http.Response) {\n\tresp.Header.Set(\"Location\", \"https://github.com/Shopify/toxiproxy\")\n}\n\nfunc (t *HttpToxic) Pipe(stub *toxics.ToxicStub) {\n\tbuffer := bytes.NewBuffer(make([]byte, 0, 32*1024))\n\twriter := stream.NewChanWriter(stub.Output)\n\treader := stream.NewChanReader(stub.Input)\n\treader.SetInterrupt(stub.Interrupt)\n\tfor {\n\t\ttee := io.TeeReader(reader, buffer)\n\t\tresp, err := http.ReadResponse(bufio.NewReader(tee), nil)\n\t\tif err == stream.ErrInterrupted {\n\t\t\tbuffer.WriteTo(writer)\n\t\t\treturn\n\t\t} else if err == io.EOF {\n\t\t\tstub.Close()\n\t\t\treturn\n\t\t}\n\t\tif err != nil {\n\t\t\tbuffer.WriteTo(writer)\n\t\t} else {\n\t\t\tt.ModifyResponse(resp)\n\t\t\tresp.Write(writer)\n\t\t}\n\t\tbuffer.Reset()\n\t}\n}\n\nfunc main() {\n\ttoxics.Register(\"http\", new(HttpToxic))\n\n\tlogger := zerolog.New(os.Stderr).With().Caller().Timestamp().Logger()\n\tmetrics := toxiproxy.NewMetricsContainer(prometheus.NewRegistry())\n\tserver := toxiproxy.NewServer(metrics, logger)\n\tserver.Listen(\"0.0.0.0:8484\")\n}\n"
  },
  {
    "path": "api.go",
    "content": "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/gorilla/mux\"\n\t\"github.com/rs/zerolog\"\n\t\"github.com/rs/zerolog/hlog\"\n\n\t\"github.com/Shopify/toxiproxy/v2/toxics\"\n)\n\nfunc stopBrowsersMiddleware(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif strings.HasPrefix(r.UserAgent(), \"Mozilla/\") {\n\t\t\thttp.Error(w, \"User agent not allowed\", http.StatusForbidden)\n\t\t} else {\n\t\t\tnext.ServeHTTP(w, r)\n\t\t}\n\t})\n}\n\nfunc timeoutMiddleware(next http.Handler) http.Handler {\n\treturn http.TimeoutHandler(next, 25*time.Second, \"\")\n}\n\ntype ApiServer struct {\n\tCollection *ProxyCollection\n\tMetrics    *metricsContainer\n\tLogger     *zerolog.Logger\n\thttp       *http.Server\n}\n\nconst (\n\twait_timeout = 30 * time.Second\n\tread_timeout = 15 * time.Second\n)\n\nfunc NewServer(m *metricsContainer, logger zerolog.Logger) *ApiServer {\n\treturn &ApiServer{\n\t\tCollection: NewProxyCollection(),\n\t\tMetrics:    m,\n\t\tLogger:     &logger,\n\t}\n}\n\nfunc (server *ApiServer) Listen(addr string) error {\n\tserver.Logger.\n\t\tInfo().\n\t\tStr(\"address\", addr).\n\t\tMsg(\"Starting Toxiproxy HTTP server\")\n\n\tserver.http = &http.Server{\n\t\tAddr:         addr,\n\t\tHandler:      server.Routes(),\n\t\tWriteTimeout: wait_timeout,\n\t\tReadTimeout:  read_timeout,\n\t\tIdleTimeout:  60 * time.Second,\n\t}\n\n\terr := server.http.ListenAndServe()\n\tif err == http.ErrServerClosed {\n\t\terr = nil\n\t}\n\n\treturn err\n}\n\nfunc (server *ApiServer) Shutdown() error {\n\tif server.http == nil {\n\t\treturn nil\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), wait_timeout)\n\tdefer cancel()\n\n\terr := server.http.Shutdown(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (server *ApiServer) Routes() *mux.Router {\n\tr := mux.NewRouter()\n\tr.Use(hlog.NewHandler(*server.Logger))\n\tr.Use(hlog.RequestIDHandler(\"request_id\", \"X-Toxiproxy-Request-Id\"))\n\tr.Use(hlog.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) {\n\t\thandler := mux.CurrentRoute(r).GetName()\n\t\tzerolog.Ctx(r.Context()).\n\t\t\tDebug().\n\t\t\tStr(\"client\", r.RemoteAddr).\n\t\t\tStr(\"method\", r.Method).\n\t\t\tStringer(\"url\", r.URL).\n\t\t\tStr(\"user_agent\", r.Header.Get(\"User-Agent\")).\n\t\t\tInt(\"status\", status).\n\t\t\tInt(\"size\", size).\n\t\t\tDur(\"duration\", duration).\n\t\t\tStr(\"handler\", handler).\n\t\t\tMsg(\"\")\n\t}))\n\tr.Use(stopBrowsersMiddleware)\n\tr.Use(timeoutMiddleware)\n\n\tr.HandleFunc(\"/reset\", server.ResetState).Methods(\"POST\").\n\t\tName(\"ResetState\")\n\tr.HandleFunc(\"/proxies\", server.ProxyIndex).Methods(\"GET\").\n\t\tName(\"ProxyIndex\")\n\tr.HandleFunc(\"/proxies\", server.ProxyCreate).Methods(\"POST\").\n\t\tName(\"ProxyCreate\")\n\tr.HandleFunc(\"/populate\", server.Populate).Methods(\"POST\").\n\t\tName(\"Populate\")\n\tr.HandleFunc(\"/proxies/{proxy}\", server.ProxyShow).Methods(\"GET\").\n\t\tName(\"ProxyShow\")\n\tr.HandleFunc(\"/proxies/{proxy}\", server.ProxyUpdate).Methods(\"POST\", \"PATCH\").\n\t\tName(\"ProxyUpdate\")\n\tr.HandleFunc(\"/proxies/{proxy}\", server.ProxyDelete).Methods(\"DELETE\").\n\t\tName(\"ProxyDelete\")\n\tr.HandleFunc(\"/proxies/{proxy}/toxics\", server.ToxicIndex).Methods(\"GET\").\n\t\tName(\"ToxicIndex\")\n\tr.HandleFunc(\"/proxies/{proxy}/toxics\", server.ToxicCreate).Methods(\"POST\").\n\t\tName(\"ToxicCreate\")\n\tr.HandleFunc(\"/proxies/{proxy}/toxics/{toxic}\", server.ToxicShow).Methods(\"GET\").\n\t\tName(\"ToxicShow\")\n\tr.HandleFunc(\"/proxies/{proxy}/toxics/{toxic}\", server.ToxicUpdate).Methods(\"POST\", \"PATCH\").\n\t\tName(\"ToxicUpdate\")\n\tr.HandleFunc(\"/proxies/{proxy}/toxics/{toxic}\", server.ToxicDelete).Methods(\"DELETE\").\n\t\tName(\"ToxicDelete\")\n\n\tr.HandleFunc(\"/version\", server.Version).Methods(\"GET\").Name(\"Version\")\n\n\tif server.Metrics.anyMetricsEnabled() {\n\t\tr.Handle(\"/metrics\", server.Metrics.handler()).Name(\"Metrics\")\n\t}\n\n\treturn r\n}\n\nfunc (server *ApiServer) PopulateConfig(filename string) {\n\tfile, err := os.Open(filename)\n\tlogger := server.Logger\n\tif err != nil {\n\t\tlogger.Err(err).Str(\"config\", filename).Msg(\"Error reading config file\")\n\t\treturn\n\t}\n\n\tproxies, err := server.Collection.PopulateJson(server, file)\n\tif err != nil {\n\t\tlogger.Err(err).Msg(\"Failed to populate proxies from file\")\n\t} else {\n\t\tlogger.Info().Int(\"proxies\", len(proxies)).Msg(\"Populated proxies from file\")\n\t}\n}\n\nfunc (server *ApiServer) ProxyIndex(response http.ResponseWriter, request *http.Request) {\n\tproxies := server.Collection.Proxies()\n\tmarshalData := make(map[string]interface{}, len(proxies))\n\n\tfor name, proxy := range proxies {\n\t\tmarshalData[name] = proxyWithToxics(proxy)\n\t}\n\n\tdata, err := json.Marshal(marshalData)\n\tif server.apiError(response, err) {\n\t\treturn\n\t}\n\n\tresponse.Header().Set(\"Content-Type\", \"application/json\")\n\t_, err = response.Write(data)\n\tif err != nil {\n\t\tlog := zerolog.Ctx(request.Context())\n\t\tlog.Warn().Err(err).Msg(\"ProxyIndex: Failed to write response to client\")\n\t}\n}\n\nfunc (server *ApiServer) ResetState(response http.ResponseWriter, request *http.Request) {\n\tctx := request.Context()\n\tproxies := server.Collection.Proxies()\n\n\tfor _, proxy := range proxies {\n\t\terr := proxy.Start()\n\t\tif err != ErrProxyAlreadyStarted && server.apiError(response, err) {\n\t\t\treturn\n\t\t}\n\n\t\tproxy.Toxics.ResetToxics(ctx)\n\t}\n\n\tresponse.WriteHeader(http.StatusNoContent)\n\t_, err := response.Write(nil)\n\tif err != nil {\n\t\tlog := zerolog.Ctx(ctx)\n\t\tlog.Warn().Err(err).Msg(\"ResetState: Failed to write headers to client\")\n\t}\n}\n\nfunc (server *ApiServer) ProxyCreate(response http.ResponseWriter, request *http.Request) {\n\t// Default fields to enable the proxy right away\n\tinput := Proxy{Enabled: true}\n\terr := json.NewDecoder(request.Body).Decode(&input)\n\tif server.apiError(response, joinError(err, ErrBadRequestBody)) {\n\t\treturn\n\t}\n\n\tif len(input.Name) < 1 {\n\t\tserver.apiError(response, joinError(fmt.Errorf(\"name\"), ErrMissingField))\n\t\treturn\n\t}\n\tif len(input.Upstream) < 1 {\n\t\tserver.apiError(response, joinError(fmt.Errorf(\"upstream\"), ErrMissingField))\n\t\treturn\n\t}\n\n\tproxy := NewProxy(server, input.Name, input.Listen, input.Upstream)\n\n\terr = server.Collection.Add(proxy, input.Enabled)\n\tif server.apiError(response, err) {\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(proxyWithToxics(proxy))\n\tif server.apiError(response, err) {\n\t\treturn\n\t}\n\n\tresponse.Header().Set(\"Content-Type\", \"application/json\")\n\tresponse.WriteHeader(http.StatusCreated)\n\t_, err = response.Write(data)\n\tif err != nil {\n\t\tlog := zerolog.Ctx(request.Context())\n\t\tlog.Warn().Err(err).Msg(\"ProxyCreate: Failed to write response to client\")\n\t}\n}\n\nfunc (server *ApiServer) Populate(response http.ResponseWriter, request *http.Request) {\n\tproxies, err := server.Collection.PopulateJson(server, request.Body)\n\tlog := zerolog.Ctx(request.Context())\n\tif err != nil {\n\t\tlog.Warn().Err(err).Msg(\"Populate errors\")\n\t}\n\n\tapiErr, ok := err.(*ApiError)\n\tif !ok && err != nil {\n\t\tlog.Warn().Err(err).Msg(\"Error did not include status code\")\n\t\tapiErr = &ApiError{err.Error(), http.StatusInternalServerError}\n\t}\n\n\tdata, err := json.Marshal(struct {\n\t\t*ApiError `json:\",omitempty\"`\n\t\tProxies   []proxyToxics `json:\"proxies\"`\n\t}{apiErr, proxiesWithToxics(proxies)})\n\tif server.apiError(response, err) {\n\t\treturn\n\t}\n\n\tresponseCode := http.StatusCreated\n\tif apiErr != nil {\n\t\tresponseCode = apiErr.StatusCode\n\t}\n\n\tresponse.Header().Set(\"Content-Type\", \"application/json\")\n\tresponse.WriteHeader(responseCode)\n\t_, err = response.Write(data)\n\tif err != nil {\n\t\tlog.Warn().Err(err).Msg(\"Populate: Failed to write response to client\")\n\t}\n}\n\nfunc (server *ApiServer) ProxyShow(response http.ResponseWriter, request *http.Request) {\n\tvars := mux.Vars(request)\n\n\tproxy, err := server.Collection.Get(vars[\"proxy\"])\n\tif server.apiError(response, err) {\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(proxyWithToxics(proxy))\n\tif server.apiError(response, err) {\n\t\treturn\n\t}\n\n\tresponse.Header().Set(\"Content-Type\", \"application/json\")\n\t_, err = response.Write(data)\n\tif err != nil {\n\t\tserver.Logger.Warn().Err(err).Msg(\"ProxyShow: Failed to write response to client\")\n\t}\n}\n\nfunc (server *ApiServer) ProxyUpdate(response http.ResponseWriter, request *http.Request) {\n\tlog := zerolog.Ctx(request.Context())\n\tif request.Method == \"POST\" {\n\t\tlog.Warn().Msg(\"ProxyUpdate: HTTP method POST is deprecated. Use HTTP PATCH instead.\")\n\t}\n\n\tvars := mux.Vars(request)\n\n\tproxy, err := server.Collection.Get(vars[\"proxy\"])\n\tif server.apiError(response, err) {\n\t\treturn\n\t}\n\n\t// Default fields are the same as existing proxy\n\tinput := Proxy{Listen: proxy.Listen, Upstream: proxy.Upstream, Enabled: proxy.Enabled}\n\terr = json.NewDecoder(request.Body).Decode(&input)\n\tif server.apiError(response, joinError(err, ErrBadRequestBody)) {\n\t\treturn\n\t}\n\n\terr = proxy.Update(&input)\n\tif server.apiError(response, err) {\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(proxyWithToxics(proxy))\n\tif server.apiError(response, err) {\n\t\treturn\n\t}\n\n\tresponse.Header().Set(\"Content-Type\", \"application/json\")\n\t_, err = response.Write(data)\n\tif err != nil {\n\t\tlog.Warn().Err(err).Msg(\"ProxyUpdate: Failed to write response to client\")\n\t}\n}\n\nfunc (server *ApiServer) ProxyDelete(response http.ResponseWriter, request *http.Request) {\n\tvars := mux.Vars(request)\n\n\terr := server.Collection.Remove(vars[\"proxy\"])\n\tif server.apiError(response, err) {\n\t\treturn\n\t}\n\n\tresponse.WriteHeader(http.StatusNoContent)\n\t_, err = response.Write(nil)\n\tif err != nil {\n\t\tlog := zerolog.Ctx(request.Context())\n\t\tlog.Warn().Err(err).Msg(\"ProxyDelete: Failed to write headers to client\")\n\t}\n}\n\nfunc (server *ApiServer) ToxicIndex(response http.ResponseWriter, request *http.Request) {\n\tvars := mux.Vars(request)\n\n\tproxy, err := server.Collection.Get(vars[\"proxy\"])\n\tif server.apiError(response, err) {\n\t\treturn\n\t}\n\n\ttoxics := proxy.Toxics.GetToxicArray()\n\tdata, err := json.Marshal(toxics)\n\tif server.apiError(response, err) {\n\t\treturn\n\t}\n\n\tresponse.Header().Set(\"Content-Type\", \"application/json\")\n\t_, err = response.Write(data)\n\tif err != nil {\n\t\tlog := zerolog.Ctx(request.Context())\n\t\tlog.Warn().Err(err).Msg(\"ToxicIndex: Failed to write response to client\")\n\t}\n}\n\nfunc (server *ApiServer) ToxicCreate(response http.ResponseWriter, request *http.Request) {\n\tvars := mux.Vars(request)\n\n\tproxy, err := server.Collection.Get(vars[\"proxy\"])\n\tif server.apiError(response, err) {\n\t\treturn\n\t}\n\n\ttoxic, err := proxy.Toxics.AddToxicJson(request.Body)\n\tif server.apiError(response, err) {\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(toxic)\n\tif server.apiError(response, err) {\n\t\treturn\n\t}\n\n\tresponse.Header().Set(\"Content-Type\", \"application/json\")\n\t_, err = response.Write(data)\n\tif err != nil {\n\t\tlog := zerolog.Ctx(request.Context())\n\t\tlog.Warn().Err(err).Msg(\"ToxicCreate: Failed to write response to client\")\n\t}\n}\n\nfunc (server *ApiServer) ToxicShow(response http.ResponseWriter, request *http.Request) {\n\tvars := mux.Vars(request)\n\n\tproxy, err := server.Collection.Get(vars[\"proxy\"])\n\tif server.apiError(response, err) {\n\t\treturn\n\t}\n\n\ttoxic := proxy.Toxics.GetToxic(vars[\"toxic\"])\n\tif toxic == nil {\n\t\tserver.apiError(response, ErrToxicNotFound)\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(toxic)\n\tif server.apiError(response, err) {\n\t\treturn\n\t}\n\n\tresponse.Header().Set(\"Content-Type\", \"application/json\")\n\t_, err = response.Write(data)\n\tif err != nil {\n\t\tlog := zerolog.Ctx(request.Context())\n\t\tlog.Warn().Err(err).Msg(\"ToxicShow: Failed to write response to client\")\n\t}\n}\n\nfunc (server *ApiServer) ToxicUpdate(response http.ResponseWriter, request *http.Request) {\n\tlog := zerolog.Ctx(request.Context())\n\tif request.Method == \"POST\" {\n\t\tlog.Warn().Msg(\"ToxicUpdate: HTTP method POST is deprecated. Use HTTP PATCH instead.\")\n\t}\n\n\tvars := mux.Vars(request)\n\n\tproxy, err := server.Collection.Get(vars[\"proxy\"])\n\tif server.apiError(response, err) {\n\t\treturn\n\t}\n\n\ttoxic, err := proxy.Toxics.UpdateToxicJson(vars[\"toxic\"], request.Body)\n\tif server.apiError(response, err) {\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(toxic)\n\tif server.apiError(response, err) {\n\t\treturn\n\t}\n\n\tresponse.Header().Set(\"Content-Type\", \"application/json\")\n\t_, err = response.Write(data)\n\tif err != nil {\n\t\tlog.Warn().Err(err).Msg(\"ToxicUpdate: Failed to write response to client\")\n\t}\n}\n\nfunc (server *ApiServer) ToxicDelete(response http.ResponseWriter, request *http.Request) {\n\tvars := mux.Vars(request)\n\tctx := request.Context()\n\tlog := zerolog.Ctx(ctx)\n\n\tproxy, err := server.Collection.Get(vars[\"proxy\"])\n\tif server.apiError(response, err) {\n\t\treturn\n\t}\n\n\terr = proxy.Toxics.RemoveToxic(ctx, vars[\"toxic\"])\n\tif server.apiError(response, err) {\n\t\treturn\n\t}\n\n\tresponse.WriteHeader(http.StatusNoContent)\n\t_, err = response.Write(nil)\n\tif err != nil {\n\t\tlog.Warn().Err(err).Msg(\"ToxicDelete: Failed to write headers to client\")\n\t}\n}\n\nfunc (server *ApiServer) Version(response http.ResponseWriter, request *http.Request) {\n\tlog := zerolog.Ctx(request.Context())\n\n\tresponse.Header().Set(\"Content-Type\", \"application/json;charset=utf-8\")\n\tversion := fmt.Sprintf(\"{\\\"version\\\": \\\"%s\\\"}\\n\", Version)\n\t_, err := response.Write([]byte(version))\n\tif err != nil {\n\t\tlog.Warn().Err(err).Msg(\"Version: Failed to write response to client\")\n\t}\n}\n\ntype ApiError struct {\n\tMessage    string `json:\"error\"`\n\tStatusCode int    `json:\"status\"`\n}\n\nfunc (e *ApiError) Error() string {\n\treturn e.Message\n}\n\nfunc newError(msg string, status int) *ApiError {\n\treturn &ApiError{msg, status}\n}\n\nfunc joinError(err error, wrapper *ApiError) *ApiError {\n\tif err != nil {\n\t\treturn &ApiError{wrapper.Message + \": \" + err.Error(), wrapper.StatusCode}\n\t}\n\treturn nil\n}\n\nvar (\n\tErrBadRequestBody     = newError(\"bad request body\", http.StatusBadRequest)\n\tErrMissingField       = newError(\"missing required field\", http.StatusBadRequest)\n\tErrProxyNotFound      = newError(\"proxy not found\", http.StatusNotFound)\n\tErrProxyAlreadyExists = newError(\"proxy already exists\", http.StatusConflict)\n\tErrInvalidStream      = newError(\n\t\t\"stream was invalid, can be either upstream or downstream\",\n\t\thttp.StatusBadRequest,\n\t)\n\tErrInvalidToxicType   = newError(\"invalid toxic type\", http.StatusBadRequest)\n\tErrToxicAlreadyExists = newError(\"toxic already exists\", http.StatusConflict)\n\tErrToxicNotFound      = newError(\"toxic not found\", http.StatusNotFound)\n)\n\nfunc (server *ApiServer) apiError(resp http.ResponseWriter, err error) bool {\n\tobj, ok := err.(*ApiError)\n\tif !ok && err != nil {\n\t\tserver.Logger.Warn().Err(err).Msg(\"Error did not include status code\")\n\t\tobj = &ApiError{err.Error(), http.StatusInternalServerError}\n\t}\n\n\tif obj == nil {\n\t\treturn false\n\t}\n\n\tdata, err2 := json.Marshal(obj)\n\tif err2 != nil {\n\t\tserver.Logger.Warn().Err(err2).Msg(\"Error json encoding error (╯°□°）╯︵ ┻━┻ \")\n\t}\n\tresp.Header().Set(\"Content-Type\", \"application/json\")\n\thttp.Error(resp, string(data), obj.StatusCode)\n\n\treturn true\n}\n\ntype proxyToxics struct {\n\t*Proxy\n\tToxics []toxics.Toxic `json:\"toxics\"`\n}\n\nfunc proxyWithToxics(proxy *Proxy) (result proxyToxics) {\n\tresult.Proxy = proxy\n\tresult.Toxics = proxy.Toxics.GetToxicArray()\n\treturn\n}\n\nfunc proxiesWithToxics(proxies []*Proxy) (result []proxyToxics) {\n\tfor _, proxy := range proxies {\n\t\tresult = append(result, proxyWithToxics(proxy))\n\t}\n\treturn\n}\n"
  },
  {
    "path": "api_test.go",
    "content": "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/client_golang/prometheus\"\n\t\"github.com/rs/zerolog\"\n\n\t\"github.com/Shopify/toxiproxy/v2\"\n\ttclient \"github.com/Shopify/toxiproxy/v2/client\"\n)\n\nvar testServer *toxiproxy.ApiServer\n\nvar client = tclient.NewClient(\"http://127.0.0.1:8475\")\n\nfunc WithServer(t *testing.T, f func(string)) {\n\tlog := zerolog.Nop()\n\tif flag.Lookup(\"test.v\").DefValue == \"true\" {\n\t\tlog = zerolog.New(os.Stdout).With().Caller().Timestamp().Logger()\n\t}\n\n\t// Make sure only one server is running at a time. Apparently there's no clean\n\t// way to shut it down between each test run.\n\tif testServer == nil {\n\t\ttestServer = toxiproxy.NewServer(\n\t\t\ttoxiproxy.NewMetricsContainer(prometheus.NewRegistry()),\n\t\t\tlog,\n\t\t)\n\n\t\tgo testServer.Listen(\"localhost:8475\")\n\n\t\t// Allow server to start. There's no clean way to know when it listens.\n\t\ttime.Sleep(50 * time.Millisecond)\n\t}\n\n\tdefer func() {\n\t\terr := testServer.Collection.Clear()\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed to clear collection\", err)\n\t\t}\n\t}()\n\n\tf(\"http://localhost:8475\")\n}\n\nfunc TestRequestId(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\tclient := http.Client{}\n\n\t\treq, _ := http.NewRequest(\"GET\", \"http://localhost:8475/version\", nil)\n\t\treq.Header.Add(\"User-Agent\", \"curl\")\n\n\t\tresp, err := client.Do(req)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Does not expect errors from client: %+v\", err)\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tif _, ok := resp.Header[\"X-Toxiproxy-Request-Id\"]; !ok {\n\t\t\tt.Fatalf(\"Expect http response with header X-Toxiproxy-Request-Id, got %+v\", resp.Header)\n\t\t}\n\t})\n}\n\nfunc TestBrowserGets403(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\tclient := http.Client{}\n\n\t\treq, _ := http.NewRequest(\"GET\", \"http://localhost:8475/proxies\", nil)\n\t\treq.Header.Add(\n\t\t\t\"User-Agent\",\n\t\t\t\"Mozilla/5.0 (Linux; Android 4.4.2); Nexus 5 Build/KOT49H) AppleWebKit/537.36\"+\n\t\t\t\t\"(KHTML, like Gecko) Chrome/33.0.1750.117 Mobile Safari/537.36 OPR/20.0.1396.72047\",\n\t\t)\n\n\t\tresp, err := client.Do(req)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Does not expect errors from client: %v\", err)\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tif resp.StatusCode != 403 {\n\t\t\tt.Fatal(\"Browser-like UserAgent was not denied access to Toxiproxy\")\n\t\t}\n\t})\n}\n\nfunc TestNonBrowserGets200(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\tclient := http.Client{}\n\n\t\treq, _ := http.NewRequest(\"GET\", \"http://localhost:8475/proxies\", nil)\n\t\treq.Header.Add(\"User-Agent\", \"Wget/2.1\")\n\n\t\tresp, err := client.Do(req)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Does not expect errors from client: %v\", err)\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tif resp.StatusCode == 403 {\n\t\t\tt.Fatal(\"Non-Browser-like UserAgent was denied access to Toxiproxy\")\n\t\t}\n\t})\n}\n\nfunc TestIndexWithNoProxies(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\tclient := tclient.NewClient(addr)\n\t\tproxies, err := client.Proxies()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Failed getting proxies:\", err)\n\t\t}\n\n\t\tif len(proxies) > 0 {\n\t\t\tt.Fatal(\"Expected no proxies, got:\", proxies)\n\t\t}\n\t})\n}\n\nfunc TestCreateProxyBlankName(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\t_, err := client.CreateProxy(\"\", \"\", \"\")\n\n\t\texpected := \"create: HTTP 400: missing required field: name\"\n\t\tif err == nil {\n\t\t\tt.Error(\"Expected error creating proxy, got nil\")\n\t\t} else if err.Error() != expected {\n\t\t\tt.Errorf(\"Expected error `%s',\\n\\tgot: `%s'\", expected, err)\n\t\t}\n\t})\n}\n\nfunc TestCreateProxyBlankUpstream(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\t_, err := client.CreateProxy(\"test\", \"\", \"\")\n\t\tif err == nil {\n\t\t\tt.Error(\"Expected error creating proxy, got nil\")\n\t\t} else if err.Error() != \"create: HTTP 400: missing required field: upstream\" {\n\t\t\tt.Error(\"Expected different error creating proxy:\", err)\n\t\t}\n\t})\n}\n\nfunc TestPopulateProxy(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\ttestProxies, err := client.Populate([]tclient.Proxy{\n\t\t\t{\n\t\t\t\tName:     \"one\",\n\t\t\t\tListen:   \"localhost:7070\",\n\t\t\t\tUpstream: \"localhost:7171\",\n\t\t\t\tEnabled:  true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:     \"two\",\n\t\t\t\tListen:   \"localhost:7373\",\n\t\t\t\tUpstream: \"localhost:7474\",\n\t\t\t\tEnabled:  true,\n\t\t\t},\n\t\t})\n\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to populate:\", err)\n\t\t}\n\n\t\tif len(testProxies) != 2 {\n\t\t\tt.Fatalf(\"Wrong number of proxies returned: %d != 2\", len(testProxies))\n\t\t}\n\n\t\tif testProxies[0].Name != \"one\" || testProxies[1].Name != \"two\" {\n\t\t\tt.Fatalf(\"Wrong proxy names returned: %s, %s\", testProxies[0].Name, testProxies[1].Name)\n\t\t}\n\n\t\tfor _, p := range testProxies {\n\t\t\tAssertProxyUp(t, p.Listen, true)\n\t\t}\n\t})\n}\n\nfunc TestPopulateDefaultEnabled(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\trequest := []byte(\n\t\t\t`[{\"name\": \"test\", \"listen\": \"localhost:7070\", \"upstream\": \"localhost:7171\"}]`,\n\t\t)\n\n\t\tresp, err := http.Post(addr+\"/populate\", \"application/json\", bytes.NewReader(request))\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Failed to send POST to /populate:\", err)\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tif resp.StatusCode != http.StatusCreated {\n\t\t\tmessage, _ := io.ReadAll(resp.Body)\n\t\t\tt.Fatalf(\"Failed to populate proxy list: HTTP %s\\n%s\", resp.Status, string(message))\n\t\t}\n\n\t\tproxies, err := client.Proxies()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t} else if len(proxies) != 1 {\n\t\t\tt.Fatalf(\"Wrong number of proxies created: %d != 1\", len(proxies))\n\t\t} else if _, ok := proxies[\"test\"]; !ok {\n\t\t\tt.Fatalf(\"Wrong proxy name returned\")\n\t\t}\n\n\t\tfor _, p := range proxies {\n\t\t\tAssertProxyUp(t, p.Listen, true)\n\t\t}\n\t})\n}\n\nfunc TestPopulateDisabledProxy(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\ttestProxies, err := client.Populate([]tclient.Proxy{\n\t\t\t{\n\t\t\t\tName:     \"one\",\n\t\t\t\tListen:   \"localhost:7070\",\n\t\t\t\tUpstream: \"localhost:7171\",\n\t\t\t\tEnabled:  false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:     \"two\",\n\t\t\t\tListen:   \"localhost:7373\",\n\t\t\t\tUpstream: \"localhost:7474\",\n\t\t\t\tEnabled:  true,\n\t\t\t},\n\t\t})\n\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to populate:\", err)\n\t\t}\n\n\t\tif len(testProxies) != 2 {\n\t\t\tt.Fatalf(\"Wrong number of proxies returned: %d != 2\", len(testProxies))\n\t\t}\n\n\t\tif testProxies[0].Name != \"one\" || testProxies[1].Name != \"two\" {\n\t\t\tt.Fatalf(\"Wrong proxy names returned: %s, %s\", testProxies[0].Name, testProxies[1].Name)\n\t\t}\n\n\t\tAssertProxyUp(t, \"localhost:7070\", false)\n\t\tAssertProxyUp(t, \"localhost:7373\", true)\n\t})\n}\n\nfunc TestPopulateExistingProxy(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\ttestProxy, err := client.CreateProxy(\"one\", \"localhost:7070\", \"localhost:7171\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\n\t\t_, err = client.CreateProxy(\"two\", \"localhost:7373\", \"localhost:7474\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\n\t\t// Create a toxic so we can make sure the proxy wasn't replaced\n\t\t_, err = testProxy.AddToxic(\"\", \"latency\", \"downstream\", 1, nil)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create toxic:\", err)\n\t\t}\n\n\t\ttestProxies, err := client.Populate([]tclient.Proxy{\n\t\t\t{\n\t\t\t\tName:     \"one\",\n\t\t\t\tListen:   \"localhost:7070\", // intentional: this should be resolved to 127.0.0.1:7070\n\t\t\t\tUpstream: \"localhost:7171\",\n\t\t\t\tEnabled:  true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:     \"two\",\n\t\t\t\tListen:   \"127.0.0.1:7575\",\n\t\t\t\tUpstream: \"localhost:7676\",\n\t\t\t\tEnabled:  true,\n\t\t\t},\n\t\t})\n\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to populate:\", err)\n\t\t}\n\n\t\tif len(testProxies) != 2 {\n\t\t\tt.Fatalf(\"Wrong number of proxies returned: %d != 2\", len(testProxies))\n\t\t}\n\n\t\tif testProxies[0].Name != \"one\" || testProxies[1].Name != \"two\" {\n\t\t\tt.Fatalf(\"Wrong proxy names returned: %s, %s\", testProxies[0].Name, testProxies[1].Name)\n\t\t}\n\n\t\tif testProxies[0].Listen != \"127.0.0.1:7070\" ||\n\t\t\ttestProxies[1].Listen != \"127.0.0.1:7575\" {\n\t\t\tt.Fatalf(\"Wrong proxy listen addresses returned: %s, %s\",\n\t\t\t\ttestProxies[0].Listen, testProxies[1].Listen,\n\t\t\t)\n\t\t}\n\n\t\ttoxics, err := testProxy.Toxics()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to get toxics:\", err)\n\t\t}\n\t\tif len(toxics) != 1 || toxics[0].Type != \"latency\" {\n\t\t\tt.Fatalf(\"Populate did not preseve existing proxy. (%d toxics)\", len(toxics))\n\t\t}\n\n\t\tfor _, p := range testProxies {\n\t\t\tAssertProxyUp(t, p.Listen, true)\n\t\t}\n\t})\n}\n\nfunc TestPopulateWithBadName(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\ttestProxies, err := client.Populate([]tclient.Proxy{\n\t\t\t{\n\t\t\t\tName:     \"one\",\n\t\t\t\tListen:   \"localhost:7070\",\n\t\t\t\tUpstream: \"localhost:7171\",\n\t\t\t\tEnabled:  true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:    \"\",\n\t\t\t\tListen:  \"\",\n\t\t\t\tEnabled: true,\n\t\t\t},\n\t\t})\n\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Expected Populate to fail.\")\n\t\t}\n\n\t\texpected := \"Populate: HTTP 400: missing required field: name at proxy 2\"\n\t\tif err.Error() != expected {\n\t\t\tt.Fatalf(\"Expected error `%s',\\n\\tgot: `%s'\", expected, err)\n\t\t}\n\n\t\tif len(testProxies) != 0 {\n\t\t\tt.Fatalf(\"Wrong number of proxies returned: %d != 0\", len(testProxies))\n\t\t}\n\n\t\tproxies, err := client.Proxies()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t} else if len(proxies) != 0 {\n\t\t\tt.Fatalf(\"Expected no proxies to be created: %d != 0\", len(proxies))\n\t\t}\n\t})\n}\n\nfunc TestPopulateProxyWithBadDataShouldReturnError(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\ttestProxies, err := client.Populate([]tclient.Proxy{\n\t\t\t{\n\t\t\t\tName:     \"one\",\n\t\t\t\tListen:   \"localhost:7070\",\n\t\t\t\tUpstream: \"localhost:7171\",\n\t\t\t\tEnabled:  true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:     \"two\",\n\t\t\t\tListen:   \"local373\",\n\t\t\t\tUpstream: \"localhost:7474\",\n\t\t\t\tEnabled:  true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:     \"three\",\n\t\t\t\tListen:   \"localhost:7575\",\n\t\t\t\tUpstream: \"localhost:7676\",\n\t\t\t\tEnabled:  true,\n\t\t\t},\n\t\t})\n\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Expected Populate to fail.\")\n\t\t}\n\n\t\tif len(testProxies) != 0 {\n\t\t\tt.Fatalf(\"Expected Proxies to be empty, got %v\", testProxies)\n\t\t}\n\n\t\tproxies, err := client.Proxies()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Expected no error, got: %v\", err)\n\t\t}\n\n\t\tif len(proxies) != 1 {\n\t\t\tt.Fatalf(\"Wrong number of proxies returned: %d != %d\", len(proxies), 1)\n\t\t}\n\n\t\tif _, ok := proxies[\"one\"]; !ok {\n\t\t\tt.Fatal(\"Proxy `one' was not created!\")\n\t\t}\n\n\t\tfor _, p := range testProxies {\n\t\t\tAssertProxyUp(t, p.Listen, true)\n\t\t}\n\n\t\tfor _, p := range proxies {\n\t\t\tif p.Name == \"two\" || p.Name == \"three\" {\n\t\t\t\tt.Fatalf(\"Proxy %s exists, populate did not fail correctly.\", p.Name)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc TestPopulateAddToxic(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\ttestProxies, err := client.Populate([]tclient.Proxy{\n\t\t\t{\n\t\t\t\tName:     \"one\",\n\t\t\t\tListen:   \"localhost:7070\",\n\t\t\t\tUpstream: \"localhost:7171\",\n\t\t\t\tEnabled:  true,\n\t\t\t},\n\t\t})\n\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to populate:\", err)\n\t\t}\n\n\t\tif len(testProxies) != 1 {\n\t\t\tt.Fatalf(\"Wrong number of proxies returned: %d != %d\", len(testProxies), 1)\n\t\t}\n\n\t\tif testProxies[0].Name != \"one\" {\n\t\t\tt.Fatalf(\"Wrong proxy name returned: %s != one\", testProxies[0].Name)\n\t\t}\n\n\t\t_, err = testProxies[0].AddToxic(\"\", \"latency\", \"downstream\", 1, nil)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Failed to AddToxic.\")\n\t\t}\n\t})\n}\n\nfunc TestListingProxies(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\t_, err := client.CreateProxy(\"mysql_master\", \"localhost:3310\", \"localhost:20001\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\n\t\tproxies, err := client.Proxies()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error listing proxies:\", err)\n\t\t}\n\n\t\tif len(proxies) == 0 {\n\t\t\tt.Fatal(\"Expected new proxy in list\")\n\t\t}\n\t\tproxy, ok := proxies[\"mysql_master\"]\n\t\tif !ok {\n\t\t\tt.Fatal(\"Expected to see mysql_master proxy in list\")\n\t\t}\n\t\tif proxy.Name != \"mysql_master\" || proxy.Listen != \"127.0.0.1:3310\" ||\n\t\t\tproxy.Upstream != \"localhost:20001\" {\n\t\t\tt.Fatalf(\n\t\t\t\t\"Unexpected proxy metadata: %s, %s, %s\",\n\t\t\t\tproxy.Name,\n\t\t\t\tproxy.Listen,\n\t\t\t\tproxy.Upstream,\n\t\t\t)\n\t\t}\n\t\tAssertToxicExists(t, proxy.ActiveToxics, \"latency\", \"\", \"\", false)\n\t})\n}\n\nfunc TestCreateAndGetProxy(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\t_, err := client.CreateProxy(\"mysql_master\", \"localhost:3310\", \"localhost:20001\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\n\t\tproxy, err := client.Proxy(\"mysql_master\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to retriecve proxy:\", err)\n\t\t}\n\n\t\tif proxy.Name != \"mysql_master\" || proxy.Listen != \"127.0.0.1:3310\" ||\n\t\t\tproxy.Upstream != \"localhost:20001\" ||\n\t\t\t!proxy.Enabled {\n\t\t\tt.Fatalf(\n\t\t\t\t\"Unexpected proxy metadata: %s, %s, %s, %v\",\n\t\t\t\tproxy.Name,\n\t\t\t\tproxy.Listen,\n\t\t\t\tproxy.Upstream,\n\t\t\t\tproxy.Enabled,\n\t\t\t)\n\t\t}\n\n\t\tAssertToxicExists(t, proxy.ActiveToxics, \"latency\", \"\", \"\", false)\n\t})\n}\n\nfunc TestCreateProxyWithSave(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\ttestProxy := client.NewProxy()\n\t\ttestProxy.Name = \"mysql_master\"\n\t\ttestProxy.Listen = \"localhost:3310\"\n\t\ttestProxy.Upstream = \"localhost:20001\"\n\t\ttestProxy.Enabled = true\n\n\t\terr := testProxy.Save()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\n\t\tproxy, err := client.Proxy(\"mysql_master\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to retriecve proxy:\", err)\n\t\t}\n\n\t\tif proxy.Name != \"mysql_master\" || proxy.Listen != \"127.0.0.1:3310\" ||\n\t\t\tproxy.Upstream != \"localhost:20001\" ||\n\t\t\t!proxy.Enabled {\n\t\t\tt.Fatalf(\n\t\t\t\t\"Unexpected proxy metadata: %s, %s, %s, %v\",\n\t\t\t\tproxy.Name,\n\t\t\t\tproxy.Listen,\n\t\t\t\tproxy.Upstream,\n\t\t\t\tproxy.Enabled,\n\t\t\t)\n\t\t}\n\n\t\tAssertProxyUp(t, proxy.Listen, true)\n\t})\n}\n\nfunc TestCreateDisabledProxy(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\tdisabledProxy := client.NewProxy()\n\t\tdisabledProxy.Name = \"mysql_master\"\n\t\tdisabledProxy.Listen = \"localhost:3310\"\n\t\tdisabledProxy.Upstream = \"localhost:20001\"\n\n\t\terr := disabledProxy.Save()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\n\t\tproxy, err := client.Proxy(\"mysql_master\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to retriecve proxy:\", err)\n\t\t}\n\n\t\tif proxy.Name != \"mysql_master\" || proxy.Listen != \"localhost:3310\" ||\n\t\t\tproxy.Upstream != \"localhost:20001\" ||\n\t\t\tproxy.Enabled {\n\t\t\tt.Fatalf(\n\t\t\t\t\"Unexpected proxy metadata: %s, %s, %s, %v\",\n\t\t\t\tproxy.Name,\n\t\t\t\tproxy.Listen,\n\t\t\t\tproxy.Upstream,\n\t\t\t\tproxy.Enabled,\n\t\t\t)\n\t\t}\n\n\t\tAssertProxyUp(t, proxy.Listen, false)\n\t})\n}\n\nfunc TestCreateDisabledProxyAndEnable(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\tdisabledProxy := client.NewProxy()\n\t\tdisabledProxy.Name = \"mysql_master\"\n\t\tdisabledProxy.Listen = \"localhost:3310\"\n\t\tdisabledProxy.Upstream = \"localhost:20001\"\n\n\t\terr := disabledProxy.Save()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\n\t\tproxy, err := client.Proxy(\"mysql_master\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to retriecve proxy:\", err)\n\t\t}\n\n\t\tif proxy.Name != \"mysql_master\" || proxy.Listen != \"localhost:3310\" ||\n\t\t\tproxy.Upstream != \"localhost:20001\" ||\n\t\t\tproxy.Enabled {\n\t\t\tt.Fatalf(\n\t\t\t\t\"Unexpected proxy metadata: %s, %s, %s, %v\",\n\t\t\t\tproxy.Name,\n\t\t\t\tproxy.Listen,\n\t\t\t\tproxy.Upstream,\n\t\t\t\tproxy.Enabled,\n\t\t\t)\n\t\t}\n\n\t\tproxy.Enabled = true\n\n\t\terr = proxy.Save()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Failed to update proxy:\", err)\n\t\t}\n\n\t\tAssertProxyUp(t, proxy.Listen, true)\n\n\t\tproxy.Enabled = false\n\n\t\terr = proxy.Save()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Failed to update proxy:\", err)\n\t\t}\n\n\t\tAssertProxyUp(t, proxy.Listen, false)\n\t})\n}\n\nfunc TestDeleteProxy(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\ttestProxy, err := client.CreateProxy(\"mysql_master\", \"localhost:3310\", \"localhost:20001\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\n\t\tproxies, err := client.Proxies()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error listing proxies:\", err)\n\t\t}\n\n\t\tif len(proxies) == 0 {\n\t\t\tt.Fatal(\"Expected new proxy in list\")\n\t\t}\n\n\t\tAssertProxyUp(t, testProxy.Listen, true)\n\n\t\terr = testProxy.Delete()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Failed deleting proxy:\", err)\n\t\t}\n\n\t\tAssertProxyUp(t, testProxy.Listen, false)\n\n\t\tproxies, err = client.Proxies()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error listing proxies:\", err)\n\t\t}\n\n\t\tif len(proxies) > 0 {\n\t\t\tt.Fatal(\"Expected proxy to be deleted from list\")\n\t\t}\n\n\t\texpected := \"Delete: HTTP 404: proxy not found\"\n\t\terr = testProxy.Delete()\n\t\tif err == nil {\n\t\t\tt.Error(\"Proxy did not result in not found.\")\n\t\t} else if err.Error() != expected {\n\t\t\tt.Errorf(\"Expected error `%s',\\n\\tgot: `%s'\", expected, err)\n\t\t}\n\t})\n}\n\nfunc TestCreateProxyPortConflict(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\ttestProxy, err := client.CreateProxy(\"mysql_master\", \"localhost:3310\", \"localhost:20001\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\n\t\texpected := \"create: HTTP 500: listen tcp 127.0.0.1:3310: bind: address already in use\"\n\t\t_, err = client.CreateProxy(\"test\", \"localhost:3310\", \"localhost:20001\")\n\t\tif err == nil {\n\t\t\tt.Error(\"Proxy did not result in conflict.\")\n\t\t} else if err.Error() != expected {\n\t\t\tt.Errorf(\"Expected error `%s',\\n\\tgot: `%s'\", expected, err)\n\t\t}\n\n\t\terr = testProxy.Delete()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to delete proxy:\", err)\n\t\t}\n\t\t_, err = client.CreateProxy(\"test\", \"localhost:3310\", \"localhost:20001\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\t})\n}\n\nfunc TestCreateProxyNameConflict(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\ttestProxy, err := client.CreateProxy(\"mysql_master\", \"localhost:3310\", \"localhost:20001\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\n\t\texpected := \"create: HTTP 409: proxy already exists\"\n\t\t_, err = client.CreateProxy(\"mysql_master\", \"localhost:3311\", \"localhost:20001\")\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Proxy did not result in conflict.\")\n\t\t} else if err.Error() != expected {\n\t\t\tt.Fatalf(\"Expected error `%s',\\n\\tgot: `%s'\", expected, err)\n\t\t}\n\n\t\terr = testProxy.Delete()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to delete proxy:\", err)\n\t\t}\n\t\t_, err = client.CreateProxy(\"mysql_master\", \"localhost:3311\", \"localhost:20001\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\t})\n}\n\nfunc TestResetState(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\ttestProxy, err := client.CreateProxy(\"mysql_master\", \"localhost:3310\", \"localhost:20001\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\n\t\tlatency, err := testProxy.AddToxic(\"\", \"latency\", \"downstream\", 1, tclient.Attributes{\n\t\t\t\"latency\": 100,\n\t\t\t\"jitter\":  10,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error setting toxic:\", err)\n\t\t}\n\n\t\tif latency.Attributes[\"latency\"] != 100.0 || latency.Attributes[\"jitter\"] != 10.0 {\n\t\t\tt.Fatal(\"Latency toxic did not start up with correct settings\")\n\t\t}\n\n\t\terr = client.ResetState()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unable to reset state:\", err)\n\t\t}\n\n\t\tproxies, err := client.Proxies()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error listing proxies:\", err)\n\t\t}\n\n\t\tproxy, ok := proxies[\"mysql_master\"]\n\t\tif !ok {\n\t\t\tt.Fatal(\"Expected proxy to still exist\")\n\t\t}\n\t\tif !proxy.Enabled {\n\t\t\tt.Fatal(\"Expected proxy to be enabled\")\n\t\t}\n\n\t\ttoxics, err := proxy.Toxics()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error requesting toxics:\", err)\n\t\t}\n\n\t\tAssertToxicExists(t, toxics, \"latency\", \"\", \"\", false)\n\n\t\tAssertProxyUp(t, proxy.Listen, true)\n\t})\n}\n\nfunc TestListingToxics(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\ttestProxy, err := client.CreateProxy(\"mysql_master\", \"localhost:3310\", \"localhost:20001\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\n\t\ttoxics, err := testProxy.Toxics()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error returning toxics:\", err)\n\t\t}\n\n\t\tAssertToxicExists(t, toxics, \"latency\", \"\", \"\", false)\n\t})\n}\n\nfunc TestAddToxic(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\ttestProxy, err := client.CreateProxy(\"mysql_master\", \"localhost:3310\", \"localhost:20001\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\n\t\tlatency, err := testProxy.AddToxic(\"foobar\", \"latency\", \"downstream\", 1, tclient.Attributes{\n\t\t\t\"latency\": 100,\n\t\t\t\"jitter\":  10,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error setting toxic:\", err)\n\t\t}\n\n\t\tif latency.Attributes[\"latency\"] != 100.0 || latency.Attributes[\"jitter\"] != 10.0 {\n\t\t\tt.Fatal(\"Latency toxic did not start up with correct settings\")\n\t\t}\n\n\t\ttoxics, err := testProxy.Toxics()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error returning toxics:\", err)\n\t\t}\n\t\ttoxic := AssertToxicExists(t, toxics, \"foobar\", \"latency\", \"downstream\", true)\n\t\tif toxic.Toxicity != 1.0 || toxic.Attributes[\"latency\"] != 100.0 ||\n\t\t\ttoxic.Attributes[\"jitter\"] != 10.0 {\n\t\t\tt.Fatal(\"Toxic was not read back correctly:\", toxic)\n\t\t}\n\t})\n}\n\nfunc TestAddMultipleToxics(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\ttestProxy, err := client.CreateProxy(\"mysql_master\", \"localhost:3310\", \"localhost:20001\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\n\t\t_, err = testProxy.AddToxic(\"latency1\", \"latency\", \"downstream\", 1, nil)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error setting toxic:\", err)\n\t\t}\n\n\t\t_, err = testProxy.AddToxic(\"latency2\", \"latency\", \"downstream\", 1, nil)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error setting toxic:\", err)\n\t\t}\n\n\t\ttoxics, err := testProxy.Toxics()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error returning toxics:\", err)\n\t\t}\n\t\tAssertToxicExists(t, toxics, \"latency1\", \"latency\", \"downstream\", true)\n\t\ttoxic := AssertToxicExists(t, toxics, \"latency2\", \"latency\", \"downstream\", true)\n\t\tif toxic.Toxicity != 1.0 || toxic.Attributes[\"latency\"] != 0.0 ||\n\t\t\ttoxic.Attributes[\"jitter\"] != 0.0 {\n\t\t\tt.Fatal(\"Toxic was not read back correctly:\", toxic)\n\t\t}\n\t\tAssertToxicExists(t, toxics, \"latency1\", \"\", \"upstream\", false)\n\t\tAssertToxicExists(t, toxics, \"latency2\", \"\", \"upstream\", false)\n\t})\n}\n\nfunc TestAddConflictingToxic(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\ttestProxy, err := client.CreateProxy(\"mysql_master\", \"localhost:3310\", \"localhost:20001\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\n\t\t_, err = testProxy.AddToxic(\"foobar\", \"latency\", \"downstream\", 1, nil)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error setting toxic:\", err)\n\t\t}\n\n\t\t_, err = testProxy.AddToxic(\"foobar\", \"slow_close\", \"downstream\", 1, nil)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Toxic did not result in conflict.\")\n\t\t} else if err.Error() != \"AddToxic: HTTP 409: toxic already exists\" {\n\t\t\tt.Fatal(\"Incorrect error setting toxic:\", err)\n\t\t}\n\n\t\ttoxics, err := testProxy.Toxics()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error returning toxics:\", err)\n\t\t}\n\t\ttoxic := AssertToxicExists(t, toxics, \"foobar\", \"latency\", \"downstream\", true)\n\t\tif toxic.Toxicity != 1.0 || toxic.Attributes[\"latency\"] != 0.0 ||\n\t\t\ttoxic.Attributes[\"jitter\"] != 0.0 {\n\t\t\tt.Fatal(\"Toxic was not read back correctly:\", toxic)\n\t\t}\n\t\tAssertToxicExists(t, toxics, \"foobar\", \"\", \"upstream\", false)\n\t})\n}\n\nfunc TestAddConflictingToxicsMultistream(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\ttestProxy, err := client.CreateProxy(\"mysql_master\", \"localhost:3310\", \"localhost:20001\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\n\t\t_, err = testProxy.AddToxic(\"foobar\", \"latency\", \"upstream\", 1, nil)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error setting toxic:\", err)\n\t\t}\n\n\t\t_, err = testProxy.AddToxic(\"foobar\", \"latency\", \"downstream\", 1, nil)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Toxic did not result in conflict.\")\n\t\t} else if err.Error() != \"AddToxic: HTTP 409: toxic already exists\" {\n\t\t\tt.Fatal(\"Incorrect error setting toxic:\", err)\n\t\t}\n\n\t\ttoxics, err := testProxy.Toxics()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error returning toxics:\", err)\n\t\t}\n\n\t\ttoxic := AssertToxicExists(t, toxics, \"foobar\", \"latency\", \"upstream\", true)\n\t\tif toxic.Toxicity != 1.0 || toxic.Attributes[\"latency\"] != 0.0 ||\n\t\t\ttoxic.Attributes[\"jitter\"] != 0.0 {\n\t\t\tt.Fatal(\"Toxic was not read back correctly:\", toxic)\n\t\t}\n\t\tAssertToxicExists(t, toxics, \"foobar\", \"\", \"downstream\", false)\n\t})\n}\n\nfunc TestAddConflictingToxicsMultistreamDefaults(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\ttestProxy, err := client.CreateProxy(\"mysql_master\", \"localhost:3310\", \"localhost:20001\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\n\t\t_, err = testProxy.AddToxic(\"\", \"latency\", \"upstream\", 1, nil)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error setting toxic:\", err)\n\t\t}\n\n\t\t_, err = testProxy.AddToxic(\"\", \"latency\", \"downstream\", 1, nil)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error setting toxic:\", err)\n\t\t}\n\n\t\ttoxics, err := testProxy.Toxics()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error returning toxics:\", err)\n\t\t}\n\t\ttoxic := AssertToxicExists(t, toxics, \"latency_upstream\", \"latency\", \"upstream\", true)\n\t\tif toxic.Toxicity != 1.0 || toxic.Attributes[\"latency\"] != 0.0 ||\n\t\t\ttoxic.Attributes[\"jitter\"] != 0.0 {\n\t\t\tt.Fatal(\"Toxic was not read back correctly:\", toxic)\n\t\t}\n\t\ttoxic = AssertToxicExists(t, toxics, \"latency_downstream\", \"latency\", \"downstream\", true)\n\t\tif toxic.Toxicity != 1.0 || toxic.Attributes[\"latency\"] != 0.0 ||\n\t\t\ttoxic.Attributes[\"jitter\"] != 0.0 {\n\t\t\tt.Fatal(\"Toxic was not read back correctly:\", toxic)\n\t\t}\n\t})\n}\n\nfunc TestAddToxicWithToxicity(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\ttestProxy, err := client.CreateProxy(\"mysql_master\", \"localhost:3310\", \"localhost:20001\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\n\t\tlatency, err := testProxy.AddToxic(\"\", \"latency\", \"downstream\", 0.2, tclient.Attributes{\n\t\t\t\"latency\": 100,\n\t\t\t\"jitter\":  10,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error setting toxic:\", err)\n\t\t}\n\n\t\tif latency.Toxicity != 0.2 || latency.Attributes[\"latency\"] != 100.0 ||\n\t\t\tlatency.Attributes[\"jitter\"] != 10.0 {\n\t\t\tt.Fatal(\"Latency toxic did not start up with correct settings:\", latency)\n\t\t}\n\n\t\ttoxics, err := testProxy.Toxics()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error returning toxics:\", err)\n\t\t}\n\t\ttoxic := AssertToxicExists(t, toxics, \"latency_downstream\", \"latency\", \"downstream\", true)\n\t\tif toxic.Toxicity != 0.2 || toxic.Attributes[\"latency\"] != 100.0 ||\n\t\t\ttoxic.Attributes[\"jitter\"] != 10.0 {\n\t\t\tt.Fatal(\"Toxic was not read back correctly:\", toxic)\n\t\t}\n\t})\n}\n\nfunc TestAddNoop(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\ttestProxy, err := client.CreateProxy(\"mysql_master\", \"localhost:3310\", \"localhost:20001\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\n\t\tnoop, err := testProxy.AddToxic(\"foobar\", \"noop\", \"\", 1, nil)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error setting toxic:\", err)\n\t\t}\n\n\t\tif noop.Toxicity != 1.0 || noop.Name != \"foobar\" || noop.Type != \"noop\" ||\n\t\t\tnoop.Stream != \"downstream\" {\n\t\t\tt.Fatal(\"Noop toxic did not start up with correct settings:\", noop)\n\t\t}\n\n\t\ttoxics, err := testProxy.Toxics()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error returning toxics:\", err)\n\t\t}\n\t\ttoxic := AssertToxicExists(t, toxics, \"foobar\", \"noop\", \"downstream\", true)\n\t\tif toxic.Toxicity != 1.0 {\n\t\t\tt.Fatal(\"Toxic was not read back correctly:\", toxic)\n\t\t}\n\t})\n}\n\nfunc TestUpdateToxics(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\ttestProxy, err := client.CreateProxy(\"mysql_master\", \"localhost:3310\", \"localhost:20001\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\n\t\tlatency, err := testProxy.AddToxic(\"\", \"latency\", \"downstream\", -1, tclient.Attributes{\n\t\t\t\"latency\": 100,\n\t\t\t\"jitter\":  10,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error setting toxic:\", err)\n\t\t}\n\n\t\tif latency.Toxicity != 1.0 || latency.Attributes[\"latency\"] != 100.0 ||\n\t\t\tlatency.Attributes[\"jitter\"] != 10.0 {\n\t\t\tt.Fatal(\"Latency toxic did not start up with correct settings:\", latency)\n\t\t}\n\n\t\tlatency, err = testProxy.UpdateToxic(\"latency_downstream\", 0.5, tclient.Attributes{\n\t\t\t\"latency\": 1000,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error setting toxic:\", err)\n\t\t}\n\n\t\tif latency.Toxicity != 0.5 || latency.Attributes[\"latency\"] != 1000.0 ||\n\t\t\tlatency.Attributes[\"jitter\"] != 10.0 {\n\t\t\tt.Fatal(\"Latency toxic did not get updated with the correct settings:\", latency)\n\t\t}\n\n\t\tlatency, err = testProxy.UpdateToxic(\"latency_downstream\", -1, tclient.Attributes{\n\t\t\t\"latency\": 500,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error setting toxic:\", err)\n\t\t}\n\n\t\tif latency.Toxicity != 0.5 || latency.Attributes[\"latency\"] != 500.0 ||\n\t\t\tlatency.Attributes[\"jitter\"] != 10.0 {\n\t\t\tt.Fatal(\"Latency toxic did not get updated with the correct settings:\", latency)\n\t\t}\n\n\t\ttoxics, err := testProxy.Toxics()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error returning toxics:\", err)\n\t\t}\n\n\t\ttoxic := AssertToxicExists(t, toxics, \"latency_downstream\", \"latency\", \"downstream\", true)\n\t\tif toxic.Toxicity != 0.5 || toxic.Attributes[\"latency\"] != 500.0 ||\n\t\t\ttoxic.Attributes[\"jitter\"] != 10.0 {\n\t\t\tt.Fatal(\"Toxic was not read back correctly:\", toxic)\n\t\t}\n\t})\n}\n\nfunc TestRemoveToxic(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\ttestProxy, err := client.CreateProxy(\"mysql_master\", \"localhost:3310\", \"localhost:20001\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\n\t\t_, err = testProxy.AddToxic(\"\", \"latency\", \"downstream\", 1, nil)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error setting toxic:\", err)\n\t\t}\n\n\t\ttoxics, err := testProxy.Toxics()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error returning toxics:\", err)\n\t\t}\n\n\t\ttoxic := AssertToxicExists(t, toxics, \"latency_downstream\", \"latency\", \"downstream\", true)\n\t\tif toxic.Toxicity != 1.0 || toxic.Attributes[\"latency\"] != 0.0 ||\n\t\t\ttoxic.Attributes[\"jitter\"] != 0.0 {\n\t\t\tt.Fatal(\"Toxic was not read back correctly:\", toxic)\n\t\t}\n\n\t\terr = testProxy.RemoveToxic(\"latency_downstream\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error removing toxic:\", err)\n\t\t}\n\n\t\ttoxics, err = testProxy.Toxics()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error returning toxics:\", err)\n\t\t}\n\t\tAssertToxicExists(t, toxics, \"latency_downstream\", \"\", \"\", false)\n\t})\n}\n\nfunc TestVersionEndpointReturnsVersion(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\tresp, err := http.Get(addr + \"/version\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Failed to get index\", err)\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to read body from response\")\n\t\t}\n\n\t\tif string(body) != \"{\\\"version\\\": \\\"git\\\"}\\n\" {\n\t\t\tt.Fatal(\"Expected to return Version from /version, got:\", string(body))\n\t\t}\n\t})\n}\n\nfunc TestInvalidStream(t *testing.T) {\n\tWithServer(t, func(addr string) {\n\t\ttestProxy, err := client.CreateProxy(\"mysql_master\", \"localhost:3310\", \"localhost:20001\")\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unable to create proxy:\", err)\n\t\t}\n\n\t\t_, err = testProxy.AddToxic(\"\", \"latency\", \"walrustream\", 1, nil)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Error setting toxic:\", err)\n\t\t}\n\t})\n}\n\nfunc AssertToxicExists(\n\tt *testing.T,\n\ttoxics tclient.Toxics,\n\tname, typeName, stream string,\n\texists bool,\n) *tclient.Toxic {\n\tvar toxic *tclient.Toxic\n\tvar actualType, actualStream string\n\n\tfor i, tox := range toxics {\n\t\tif name == tox.Name {\n\t\t\ttoxic = &toxics[i]\n\t\t\tactualType = tox.Type\n\t\t\tactualStream = tox.Stream\n\t\t}\n\t}\n\tif exists {\n\t\tif toxic == nil {\n\t\t\tt.Fatalf(\"Expected to see %s toxic in list\", name)\n\t\t}\n\n\t\tif actualType != typeName {\n\t\t\tt.Fatalf(\"Expected %s to be of type %s, found %s\", name, typeName, actualType)\n\t\t}\n\n\t\tif actualStream != stream {\n\t\t\tt.Fatalf(\"Expected %s to be in stream %s, found %s\", name, stream, actualStream)\n\t\t}\n\t} else if toxic != nil && actualStream == stream {\n\t\tt.Fatalf(\"Expected %s toxic to be missing from list, found type %s\", name, actualType)\n\t}\n\treturn toxic\n}\n"
  },
  {
    "path": "client/README.md",
    "content": "# toxiproxy-go\n\nThis is the Go client library for the\n[Toxiproxy](https://github.com/shopify/toxiproxy) API. Please read the [usage\nsection in the Toxiproxy README](https://github.com/shopify/toxiproxy#usage)\nbefore attempting to use the client.\n\nThis client is compatible with Toxiproxy 2.x, for the latest 1.x client see\n[v1.2.1](https://github.com/Shopify/toxiproxy/tree/v1.2.1/client).\n\n## Changes in Toxiproxy-go Client 2.x\n\nIn order to make use of the 2.0 api, and to make usage a little easier, the\nclient api has changed:\n\n - `client.NewProxy()` no longer accepts a proxy as an argument.\n - `proxy.Create()` is removed in favour of using `proxy.Save()`.\n - Proxies can be created in a single call using `client.CreateProxy()`.\n - `proxy.Disable()` and `proxy.Enable()` have been added to simplify taking\n    down a proxy.\n - `proxy.ToxicsUpstream` and `proxy.ToxicsDownstream` have been merged into a\n    single `ActiveToxics` list.\n - `proxy.Toxics()` no longer requires a direction to be specified, and will\n    return toxics for both directions.\n - `proxy.SetToxic()` has been replaced by `proxy.AddToxic()`,\n   `proxy.UpdateToxic()`, and `proxy.RemoveToxic()`.\n\n## Usage\n\nFor detailed API docs please [see the Godoc\ndocumentation](http://godoc.org/github.com/Shopify/toxiproxy/client).\n\nFirst import toxiproxy and create a new client:\n```go\nimport toxiproxy \"github.com/Shopify/toxiproxy/v2/client\"\n\nclient := toxiproxy.NewClient(\"localhost:8474\")\n```\n\nYou can then create a new proxy using the client:\n```go\nproxy, err := client.CreateProxy(\"redis\", \"localhost:26379\", \"localhost:6379\")\nif err != nil {\n    panic(err)\n}\n```\n\nFor large amounts of proxies, they can also be created using a configuration file:\n```go\nvar config []toxiproxy.Proxy\ndata, _ := ioutil.ReadFile(\"config.json\")\njson.Unmarshal(data, &config)\nproxies, err = client.Populate(config)\n```\n```json\n[{\n  \"name\": \"redis\",\n  \"listen\": \"localhost:26379\",\n  \"upstream\": \"localhost:6379\"\n}]\n```\n\nToxics can be added as follows:\n```go\n// Add 1s latency to 100% of downstream connections\nproxy.AddToxic(\"latency_down\", \"latency\", \"downstream\", 1.0, toxiproxy.Attributes{\n    \"latency\": 1000,\n})\n\n// Change downstream latency to add 100ms of jitter\nproxy.UpdateToxic(\"latency_down\", 1.0, toxiproxy.Attributes{\n    \"jitter\": 100,\n})\n\n// Remove the latency toxic\nproxy.RemoveToxic(\"latency_down\")\n```\n\n\nThe proxy can be taken down using `Disable()`:\n```go\nproxy.Disable()\n```\n\nWhen a proxy is no longer needed, it can be cleaned up with `Delete()`:\n```go\nproxy.Delete()\n```\n\n## Full Example\n\n```go\nimport (\n    \"testing\"\n    \"time\"\n\n    toxiproxy \"github.com/Shopify/toxiproxy/v2/client\"\n    \"github.com/gomodule/redigo/redis\"\n)\n\nvar toxiClient *toxiproxy.Client\n\nfunc init() {\n    var err error\n    toxiClient = toxiproxy.NewClient(\"localhost:8474\")\n    _, err = toxiClient.Populate([]toxiproxy.Proxy{{\n        Name:     \"redis\",\n        Listen:   \"localhost:26379\",\n        Upstream: \"localhost:6379\",\n        // note: you cannot set toxics here via ActiveToxics\n    }})\n    if err != nil {\n        panic(err)\n    }\n    // Alternatively, create the proxies manually with\n    // toxiClient.CreateProxy(\"redis\", \"localhost:26379\", \"localhost:6379\")\n}\n\nfunc TestRedisBackendDown(t *testing.T) {\n    var proxy, _ = toxiClient.Proxy(\"redis\")\n    proxy.Disable()\n    defer proxy.Enable()\n\n    // Test that redis is down\n    _, err := redis.Dial(\"tcp\", \":26379\")\n    if err == nil {\n        t.Fatal(\"Connection to redis did not fail\")\n    }\n}\n\nfunc TestRedisBackendSlow(t *testing.T) {\n    var proxy, _ = toxiClient.Proxy(\"redis\")\n    proxy.AddToxic(\"\", \"latency\", \"\", 1, toxiproxy.Attributes{\n        \"latency\": 1000,\n    })\n    proxy.Save()\n    defer removeToxic(proxy, \"latency_downstream\")\n\n    // Test that redis is slow\n    start := time.Now()\n    conn, err := redis.Dial(\"tcp\", \":26379\")\n    if err != nil {\n        t.Fatal(\"Connection to redis failed\", err)\n    }\n\n    _, err = conn.Do(\"GET\", \"test\")\n    if err != nil {\n        t.Fatal(\"Redis command failed\", err)\n    } else if time.Since(start) < 900*time.Millisecond {\n        t.Fatal(\"Redis command did not take long enough:\", time.Since(start))\n    }\n}\n\nfunc removeToxic(p *toxiproxy.Proxy, n string) {\n    p.RemoveToxic(n)\n    p.Save()\n}\n```\n"
  },
  {
    "path": "client/api_error.go",
    "content": "// Package Toxiproxy provides a client wrapper around the Toxiproxy HTTP API for\n// testing the resiliency of Go applications.\n//\n// For use with Toxiproxy 2.x\n\npackage toxiproxy\n\nimport (\n\t\"fmt\"\n)\n\ntype ApiError struct {\n\tMessage string `json:\"error\"`\n\tStatus  int    `json:\"status\"`\n}\n\nfunc (err *ApiError) Error() string {\n\treturn fmt.Sprintf(\"HTTP %d: %s\", err.Status, err.Message)\n}\n"
  },
  {
    "path": "client/client.go",
    "content": "// Package Toxiproxy provides a client wrapper around the Toxiproxy HTTP API for\n// testing the resiliency of Go applications.\n//\n// For use with Toxiproxy 2.x\npackage toxiproxy\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Client holds information about where to connect to Toxiproxy.\ntype Client struct {\n\tUserAgent string\n\tendpoint  string\n\thttp      *http.Client\n}\n\n// NewClient creates a new client which provides the base of all communication\n// with Toxiproxy. Endpoint is the address to the proxy (e.g. localhost:8474 if\n// not overridden).\nfunc NewClient(endpoint string) *Client {\n\tif !strings.HasPrefix(endpoint, \"https://\") &&\n\t\t!strings.HasPrefix(endpoint, \"http://\") {\n\t\tendpoint = \"http://\" + endpoint\n\t}\n\n\thttp := &http.Client{\n\t\tTimeout: 30 * time.Second,\n\t}\n\n\treturn &Client{\n\t\tUserAgent: \"toxiproxy-cli\",\n\t\tendpoint:  endpoint,\n\t\thttp:      http,\n\t}\n}\n\n// Version returns a Toxiproxy running version.\nfunc (client *Client) Version() ([]byte, error) {\n\treturn client.get(\"/version\")\n}\n\n// Proxies returns a map with all the proxies and their toxics.\nfunc (client *Client) Proxies() (map[string]*Proxy, error) {\n\tresp, err := client.get(\"/proxies\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tproxies := make(map[string]*Proxy)\n\terr = json.Unmarshal(resp, &proxies)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, proxy := range proxies {\n\t\tproxy.client = client\n\t\tproxy.created = true\n\t}\n\n\treturn proxies, nil\n}\n\n// Generates a new uncommitted proxy instance. In order to use the result, the\n// proxy fields will need to be set and have `Save()` called.\nfunc (client *Client) NewProxy() *Proxy {\n\treturn &Proxy{\n\t\tclient: client,\n\t}\n}\n\n// CreateProxy instantiates a new proxy and starts listening on the specified address.\n// This is an alias for `NewProxy()` + `proxy.Save()`.\nfunc (client *Client) CreateProxy(name, listen, upstream string) (*Proxy, error) {\n\tproxy := &Proxy{\n\t\tName:     name,\n\t\tListen:   listen,\n\t\tUpstream: upstream,\n\t\tEnabled:  true,\n\t\tclient:   client,\n\t}\n\n\terr := proxy.Save()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"create: %w\", err)\n\t}\n\n\treturn proxy, nil\n}\n\n// Proxy returns a proxy by name.\nfunc (client *Client) Proxy(name string) (*Proxy, error) {\n\tresp, err := client.get(\"/proxies/\" + name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tproxy := new(Proxy)\n\terr = json.Unmarshal(resp, &proxy)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tproxy.client = client\n\tproxy.created = true\n\n\treturn proxy, nil\n}\n\n// Create a list of proxies using a configuration list. If a proxy already exists,\n// it will be replaced with the specified configuration.\n// For large amounts of proxies, `config` can be loaded from a file.\n// Returns a list of the successfully created proxies.\nfunc (client *Client) Populate(config []Proxy) ([]*Proxy, error) {\n\tproxies := struct {\n\t\tProxies []*Proxy `json:\"proxies\"`\n\t}{}\n\trequest, err := json.Marshal(config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresp, err := client.post(\"/populate\", bytes.NewReader(request))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Populate: %w\", err)\n\t}\n\n\terr = json.Unmarshal(resp, &proxies)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, proxy := range proxies.Proxies {\n\t\tproxy.client = client\n\t}\n\n\treturn proxies.Proxies, err\n}\n\n// AddToxic creates a toxic to proxy.\nfunc (client *Client) AddToxic(options *ToxicOptions) (*Toxic, error) {\n\tproxy, err := client.Proxy(options.ProxyName)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to retrieve proxy with name `%s`: %v\", options.ProxyName, err)\n\t}\n\n\ttoxic, err := proxy.AddToxic(\n\t\toptions.ToxicName,\n\t\toptions.ToxicType,\n\t\toptions.Stream,\n\t\toptions.Toxicity,\n\t\toptions.Attributes,\n\t)\n\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to add toxic to proxy %s: %v\", options.ProxyName, err)\n\t}\n\n\treturn toxic, nil\n}\n\n// UpdateToxic update a toxic in proxy.\nfunc (client *Client) UpdateToxic(options *ToxicOptions) (*Toxic, error) {\n\tproxy, err := client.Proxy(options.ProxyName)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to retrieve proxy with name `%s`: %v\", options.ProxyName, err)\n\t}\n\n\ttoxic, err := proxy.UpdateToxic(\n\t\toptions.ToxicName,\n\t\toptions.Toxicity,\n\t\toptions.Attributes,\n\t)\n\n\tif err != nil {\n\t\treturn nil,\n\t\t\tfmt.Errorf(\n\t\t\t\t\"failed to update toxic '%s' of proxy '%s': %v\",\n\t\t\t\toptions.ToxicName, options.ProxyName, err,\n\t\t\t)\n\t}\n\n\treturn toxic, nil\n}\n\n// RemoveToxic removes toxic from proxy.\nfunc (client *Client) RemoveToxic(options *ToxicOptions) error {\n\tproxy, err := client.Proxy(options.ProxyName)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to retrieve proxy with name `%s`: %v\", options.ProxyName, err)\n\t}\n\n\terr = proxy.RemoveToxic(options.ToxicName)\n\tif err != nil {\n\t\treturn fmt.Errorf(\n\t\t\t\"failed to remove toxic '%s' from proxy '%s': %v\",\n\t\t\toptions.ToxicName, options.ProxyName, err,\n\t\t)\n\t}\n\n\treturn nil\n}\n\n// ResetState resets the state of all proxies and toxics in Toxiproxy.\nfunc (client *Client) ResetState() error {\n\t_, err := client.post(\"/reset\", bytes.NewReader([]byte{}))\n\treturn err\n}\n\nfunc (c *Client) get(path string) ([]byte, error) {\n\treturn c.send(\"GET\", path, nil)\n}\n\nfunc (c *Client) post(path string, body io.Reader) ([]byte, error) {\n\treturn c.send(\"POST\", path, body)\n}\n\nfunc (c *Client) patch(path string, body io.Reader) ([]byte, error) {\n\treturn c.send(\"PATCH\", path, body)\n}\n\nfunc (c *Client) delete(path string) error {\n\t_, err := c.send(\"DELETE\", path, nil)\n\treturn err\n}\n\nfunc (c *Client) send(verb, path string, body io.Reader) ([]byte, error) {\n\treq, err := http.NewRequest(verb, c.endpoint+path, body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treq.Header.Set(\"User-Agent\", c.UserAgent)\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tresp, err := c.http.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"fail to request: %w\", err)\n\t}\n\n\terr = c.validateResponse(resp)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\tresult, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn result, nil\n}\n\nfunc (c *Client) validateResponse(resp *http.Response) error {\n\tif resp.StatusCode < 300 && resp.StatusCode >= 200 {\n\t\treturn nil\n\t}\n\n\tapiError := new(ApiError)\n\terr := json.NewDecoder(resp.Body).Decode(&apiError)\n\tif err != nil {\n\t\treturn err\n\t}\n\tresp.Body.Close()\n\n\tif err != nil {\n\t\tapiError.Message = fmt.Sprintf(\n\t\t\t\"Unexpected response code %d\",\n\t\t\tresp.StatusCode,\n\t\t)\n\t\tapiError.Status = resp.StatusCode\n\t}\n\treturn apiError\n}\n"
  },
  {
    "path": "client/client_test.go",
    "content": "package toxiproxy_test\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\ttoxiproxy \"github.com/Shopify/toxiproxy/v2/client\"\n)\n\nfunc TestClient_Headers(t *testing.T) {\n\tt.Parallel()\n\n\texpected := \"toxiproxy-cli/v1.25.0 (darwin/arm64)\"\n\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusOK)\n\t\tua := r.Header.Get(\"User-Agent\")\n\n\t\tif ua != expected {\n\t\t\tt.Errorf(\"User-Agent for %s %s is expected `%s', got: `%s'\",\n\t\t\t\tr.Method,\n\t\t\t\tr.URL,\n\t\t\t\texpected,\n\t\t\t\tua)\n\t\t}\n\n\t\tcontentType := r.Header.Get(\"Content-Type\")\n\t\tif contentType != \"application/json\" {\n\t\t\tt.Errorf(\"Content-Type for %s %s is expected `application/json', got: `%s'\",\n\t\t\t\tr.Method,\n\t\t\t\tr.URL,\n\t\t\t\tcontentType)\n\t\t}\n\t\tw.Write([]byte(`foo`))\n\t}))\n\tdefer server.Close()\n\n\tclient := toxiproxy.NewClient(server.URL)\n\tclient.UserAgent = expected\n\n\tcases := []struct {\n\t\tname string\n\t\tfn   func(c *toxiproxy.Client)\n\t}{\n\t\t{\"get version\", func(c *toxiproxy.Client) { c.Version() }},\n\t\t{\"get proxies\", func(c *toxiproxy.Client) { c.Proxies() }},\n\t\t{\"create proxy\", func(c *toxiproxy.Client) {\n\t\t\tc.CreateProxy(\"foo\", \"example.com:0\", \"example.com:0\")\n\t\t}},\n\t\t{\"get proxy\", func(c *toxiproxy.Client) { c.Proxy(\"foo\") }},\n\t\t{\"post populate\", func(c *toxiproxy.Client) {\n\t\t\tc.Populate([]toxiproxy.Proxy{{}})\n\t\t}},\n\t\t{\"create toxic\", func(c *toxiproxy.Client) {\n\t\t\tc.AddToxic(&toxiproxy.ToxicOptions{})\n\t\t}},\n\t\t{\"update toxic\", func(c *toxiproxy.Client) {\n\t\t\tc.UpdateToxic(&toxiproxy.ToxicOptions{})\n\t\t}},\n\t\t{\"delete toxic\", func(c *toxiproxy.Client) {\n\t\t\tc.RemoveToxic(&toxiproxy.ToxicOptions{})\n\t\t}},\n\t\t{\"reset state\", func(c *toxiproxy.Client) {\n\t\t\tc.ResetState()\n\t\t}},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttc.fn(client)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "client/proxy.go",
    "content": "// Package Toxiproxy provides a client wrapper around the Toxiproxy HTTP API for\n// testing the resiliency of Go applications.\n//\n// For use with Toxiproxy 2.x\npackage toxiproxy\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n)\n\ntype Proxy struct {\n\tName     string `json:\"name\"`     // The name of the proxy\n\tListen   string `json:\"listen\"`   // The address the proxy listens on\n\tUpstream string `json:\"upstream\"` // The upstream address to proxy to\n\tEnabled  bool   `json:\"enabled\"`  // Whether the proxy is enabled\n\n\t// The toxics active on this proxy. Note: you cannot set this\n\t// when passing Proxy into Populate()\n\tActiveToxics Toxics `json:\"toxics\"`\n\n\tclient  *Client\n\tcreated bool // True if this proxy exists on the server\n}\n\n// Save saves changes to a proxy such as its enabled status or upstream port.\nfunc (proxy *Proxy) Save() error {\n\trequest, err := json.Marshal(proxy)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdata := bytes.NewReader(request)\n\n\tvar resp []byte\n\tif proxy.created {\n\t\t// TODO: Release PATCH only for v3.0\n\t\t// resp, err = proxy.client.patch(\"/proxies/\"+proxy.Name, data)\n\t\tresp, err = proxy.client.post(\"/proxies/\"+proxy.Name, data)\n\t} else {\n\t\tresp, err = proxy.client.post(\"/proxies\", data)\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = json.Unmarshal(resp, proxy)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tproxy.created = true\n\n\treturn nil\n}\n\n// Enable a proxy again after it has been disabled.\nfunc (proxy *Proxy) Enable() error {\n\tproxy.Enabled = true\n\treturn proxy.Save()\n}\n\n// Disable a proxy so that no connections can pass through. This will drop all active connections.\nfunc (proxy *Proxy) Disable() error {\n\tproxy.Enabled = false\n\treturn proxy.Save()\n}\n\n// Delete a proxy complete and close all existing connections through it. All information about\n// the proxy such as listen port and active toxics will be deleted as well. If you just wish to\n// stop and later enable a proxy, use `Enable()` and `Disable()`.\nfunc (proxy *Proxy) Delete() error {\n\terr := proxy.client.delete(\"/proxies/\" + proxy.Name)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Delete: %w\", err)\n\t}\n\treturn nil\n}\n\n// Toxics returns a map of all the active toxics and their attributes.\nfunc (proxy *Proxy) Toxics() (Toxics, error) {\n\tresp, err := proxy.client.get(\"/proxies/\" + proxy.Name + \"/toxics\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttoxics := make(Toxics, 0)\n\terr = json.Unmarshal(resp, &toxics)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn toxics, nil\n}\n\n// AddToxic adds a toxic to the given stream direction.\n// If a name is not specified, it will default to <type>_<stream>.\n// If a stream is not specified, it will default to downstream.\n// See https://github.com/Shopify/toxiproxy#toxics for a list of all Toxic types.\nfunc (proxy *Proxy) AddToxic(\n\tname, typeName, stream string,\n\ttoxicity float32,\n\tattrs Attributes,\n) (*Toxic, error) {\n\ttoxic := Toxic{name, typeName, stream, toxicity, attrs}\n\tif toxic.Toxicity == -1 {\n\t\ttoxic.Toxicity = 1 // Just to be consistent with a toxicity of -1 using the default\n\t}\n\n\trequest, err := json.Marshal(&toxic)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresp, err := proxy.client.post(\n\t\t\"/proxies/\"+proxy.Name+\"/toxics\",\n\t\tbytes.NewReader(request),\n\t)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"AddToxic: %w\", err)\n\t}\n\n\tresult := &Toxic{}\n\terr = json.Unmarshal(resp, result)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn result, nil\n}\n\n// UpdateToxic sets the parameters for an existing toxic with the given name.\n// If toxicity is set to -1, the current value will be used.\nfunc (proxy *Proxy) UpdateToxic(name string, toxicity float32, attrs Attributes) (*Toxic, error) {\n\ttoxic := map[string]interface{}{\n\t\t\"attributes\": attrs,\n\t}\n\tif toxicity != -1 {\n\t\ttoxic[\"toxicity\"] = toxicity\n\t}\n\trequest, err := json.Marshal(&toxic)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresp, err := proxy.client.patch(\n\t\t\"/proxies/\"+proxy.Name+\"/toxics/\"+name,\n\t\tbytes.NewReader(request),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresult := &Toxic{}\n\terr = json.Unmarshal(resp, result)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn result, nil\n}\n\n// RemoveToxic renives the toxic with the given name.\nfunc (proxy *Proxy) RemoveToxic(name string) error {\n\treturn proxy.client.delete(\"/proxies/\" + proxy.Name + \"/toxics/\" + name)\n}\n"
  },
  {
    "path": "client/toxic.go",
    "content": "// Package Toxiproxy provides a client wrapper around the Toxiproxy HTTP API for\n// testing the resiliency of Go applications.\n//\n// For use with Toxiproxy 2.x\npackage toxiproxy\n\ntype Attributes map[string]interface{}\n\ntype Toxic struct {\n\tName       string     `json:\"name\"`\n\tType       string     `json:\"type\"`\n\tStream     string     `json:\"stream,omitempty\"`\n\tToxicity   float32    `json:\"toxicity\"`\n\tAttributes Attributes `json:\"attributes\"`\n}\n\ntype Toxics []Toxic\n\ntype ToxicOptions struct {\n\tProxyName,\n\tToxicName,\n\tToxicType,\n\tStream string\n\tToxicity   float32\n\tAttributes Attributes\n}\n"
  },
  {
    "path": "cmd/cli/cli.go",
    "content": "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 \"golang.org/x/term\"\n\n\ttoxiproxyServer \"github.com/Shopify/toxiproxy/v2\"\n\ttoxiproxy \"github.com/Shopify/toxiproxy/v2/client\"\n)\n\nconst (\n\tRED    = \"\\x1b[31m\"\n\tGREEN  = \"\\x1b[32m\"\n\tYELLOW = \"\\x1b[33m\"\n\tBLUE   = \"\\x1b[34m\"\n\tPURPLE = \"\\x1b[35m\"\n\tNONE   = \"\\x1b[0m\"\n)\n\nfunc color(color string) string {\n\tif isTTY {\n\t\treturn color\n\t} else {\n\t\treturn \"\"\n\t}\n}\n\nvar toxicDescription = `\n  Default Toxics:\n  latency:    delay all data +/- jitter\n              latency=<ms>,jitter=<ms>\n\n  bandwidth:  limit to max kb/s\n              rate=<KB/s>\n\n  slow_close: delay from closing\n              delay=<ms>\n\n  timeout:    stop all data and close after timeout\n              timeout=<ms>\n\n  reset_peer: simulate TCP RESET (Connection reset by peer) on the connections by closing\n              the stub Input immediately or after a timeout\n              timeout=<ms>\n\n  slicer:     slice data into bits with optional delay\n              average_size=<bytes>,size_variation=<bytes>,delay=<microseconds>\n\n  toxic add:\n    usage: toxiproxy-cli toxic add --type <toxicType> [--downstream|--upstream] \\\n            --toxicName <toxicName> [--toxicity <float>] \\\n            --attribute <key=value> [--attribute <key2=value2>] <proxyName>\n\n\n    example: toxiproxy-cli toxic add -t latency -n myToxic -a latency=100 -a jitter=50 myProxy\n\n  toxic update:\n    usage: toxiproxy-cli toxic update --toxicName <toxicName> [--toxicity <float>] \\\n            --attribute <key1=value1> [--attribute <key2=value2>] <proxyName>\n\n    example: toxiproxy-cli toxic update -n myToxic -a jitter=25 myProxy\n\n  toxic delete:\n    usage: toxiproxy-cli toxic delete --toxicName <toxicName> <proxyName>\n\n    example: toxiproxy-cli toxic delete -n myToxic myProxy\n`\n\nvar (\n\thostname string\n\tisTTY    bool\n)\n\nfunc main() {\n\tapp := cli.NewApp()\n\tapp.Name = \"toxiproxy-cli\"\n\tapp.Version = toxiproxyServer.Version\n\tapp.Usage = \"Simulate network and system conditions\"\n\tapp.Commands = cliCommands()\n\tcli.HelpFlag = &cli.BoolFlag{\n\t\tName:  \"help\",\n\t\tUsage: \"show help\",\n\t}\n\tapp.Flags = []cli.Flag{\n\t\t&cli.StringFlag{\n\t\t\tName:        \"host\",\n\t\t\tAliases:     []string{\"h\"},\n\t\t\tValue:       \"http://localhost:8474\",\n\t\t\tUsage:       \"toxiproxy host to connect to\",\n\t\t\tDestination: &hostname,\n\t\t\tEnvVars:     []string{\"TOXIPROXY_URL\"},\n\t\t},\n\t}\n\n\tisTTY = terminal.IsTerminal(int(os.Stdout.Fd()))\n\n\tapp.Run(os.Args)\n}\n\nfunc cliCommands() []*cli.Command {\n\treturn []*cli.Command{\n\t\t{\n\t\t\tName:    \"list\",\n\t\t\tUsage:   \"list all proxies\\n\\tusage: 'toxiproxy-cli list'\\n\",\n\t\t\tAliases: []string{\"l\", \"li\", \"ls\"},\n\t\t\tAction:  withToxi(list),\n\t\t},\n\t\t{\n\t\t\tName:    \"inspect\",\n\t\t\tAliases: []string{\"i\", \"ins\"},\n\t\t\tUsage:   \"inspect a single proxy\\n\\tusage: 'toxiproxy-cli inspect <proxyName>'\\n\",\n\t\t\tAction:  withToxi(inspectProxy),\n\t\t},\n\t\t{\n\t\t\tName: \"create\",\n\t\t\tUsage: \"create a new proxy\\n\\t\" +\n\t\t\t\t\"usage: 'toxiproxy-cli create --listen <addr> --upstream <addr> <proxyName>'\\n\",\n\t\t\tAliases: []string{\"c\", \"new\"},\n\t\t\tFlags: []cli.Flag{\n\t\t\t\t&cli.StringFlag{\n\t\t\t\t\tName:    \"listen\",\n\t\t\t\t\tAliases: []string{\"l\"},\n\t\t\t\t\tUsage:   \"proxy will listen on this address\",\n\t\t\t\t},\n\t\t\t\t&cli.StringFlag{\n\t\t\t\t\tName:    \"upstream\",\n\t\t\t\t\tAliases: []string{\"u\"},\n\t\t\t\t\tUsage:   \"proxy will forward to this address\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tAction: withToxi(createProxy),\n\t\t},\n\t\t{\n\t\t\tName: \"toggle\",\n\t\t\tUsage: \"\\ttoggle enabled status on a proxy\\n\" +\n\t\t\t\t\"\\t\\tusage: 'toxiproxy-cli toggle <proxyName>'\\n\",\n\t\t\tAliases: []string{\"tog\"},\n\t\t\tAction:  withToxi(toggleProxy),\n\t\t},\n\t\t{\n\t\t\tName:    \"delete\",\n\t\t\tUsage:   \"\\tdelete a proxy\\n\\t\\tusage: 'toxiproxy-cli delete <proxyName>'\\n\",\n\t\t\tAliases: []string{\"d\"},\n\t\t\tAction:  withToxi(deleteProxy),\n\t\t},\n\t\t{\n\t\t\tName:        \"toxic\",\n\t\t\tAliases:     []string{\"t\"},\n\t\t\tUsage:       \"\\tadd, remove or update a toxic\\n\\t\\tusage: see 'toxiproxy-cli toxic'\\n\",\n\t\t\tDescription: toxicDescription,\n\t\t\tSubcommands: cliToxiSubCommands(),\n\t\t},\n\t}\n}\n\nfunc cliToxiSubCommands() []*cli.Command {\n\treturn []*cli.Command{\n\t\tcliToxiAddSubCommand(),\n\t\tcliToxiUpdateSubCommand(),\n\t\tcliToxiRemoveSubCommand(),\n\t}\n}\n\nfunc cliToxiAddSubCommand() *cli.Command {\n\treturn &cli.Command{\n\t\tName:      \"add\",\n\t\tAliases:   []string{\"a\"},\n\t\tUsage:     \"add a new toxic\",\n\t\tArgsUsage: \"<proxyName>\",\n\t\tFlags: []cli.Flag{\n\t\t\t&cli.StringFlag{\n\t\t\t\tName:    \"toxicName\",\n\t\t\t\tAliases: []string{\"n\"},\n\t\t\t\tUsage:   \"name of the toxic\",\n\t\t\t},\n\t\t\t&cli.StringFlag{\n\t\t\t\tName:    \"type\",\n\t\t\t\tAliases: []string{\"t\"},\n\t\t\t\tUsage:   \"type of toxic\",\n\t\t\t},\n\t\t\t&cli.StringFlag{\n\t\t\t\tName:        \"toxicity\",\n\t\t\t\tAliases:     []string{\"tox\"},\n\t\t\t\tUsage:       \"toxicity of toxic should be a float between 0 and 1\",\n\t\t\t\tDefaultText: \"1.0\",\n\t\t\t},\n\t\t\t&cli.StringSliceFlag{\n\t\t\t\tName:    \"attribute\",\n\t\t\t\tAliases: []string{\"a\"},\n\t\t\t\tUsage:   \"toxic attribute in key=value format\",\n\t\t\t},\n\t\t\t&cli.BoolFlag{\n\t\t\t\tName:        \"upstream\",\n\t\t\t\tAliases:     []string{\"u\"},\n\t\t\t\tUsage:       \"add toxic to upstream\",\n\t\t\t\tDefaultText: \"false\",\n\t\t\t},\n\t\t\t&cli.BoolFlag{\n\t\t\t\tName:        \"downstream\",\n\t\t\t\tAliases:     []string{\"d\"},\n\t\t\t\tUsage:       \"add toxic to downstream\",\n\t\t\t\tDefaultText: \"true\",\n\t\t\t},\n\t\t},\n\t\tAction: withToxi(addToxic),\n\t}\n}\n\nfunc cliToxiUpdateSubCommand() *cli.Command {\n\treturn &cli.Command{\n\t\tName:      \"update\",\n\t\tAliases:   []string{\"u\"},\n\t\tUsage:     \"update an enabled toxic\",\n\t\tArgsUsage: \"<proxyName>\",\n\t\tFlags: []cli.Flag{\n\t\t\t&cli.StringFlag{\n\t\t\t\tName:    \"toxicName\",\n\t\t\t\tAliases: []string{\"n\"},\n\t\t\t\tUsage:   \"name of the toxic\",\n\t\t\t},\n\t\t\t&cli.StringFlag{\n\t\t\t\tName:        \"toxicity\",\n\t\t\t\tAliases:     []string{\"tox\"},\n\t\t\t\tUsage:       \"toxicity of toxic should be a float between 0 and 1\",\n\t\t\t\tDefaultText: \"1.0\",\n\t\t\t},\n\t\t\t&cli.StringSliceFlag{\n\t\t\t\tName:    \"attribute\",\n\t\t\t\tAliases: []string{\"a\"},\n\t\t\t\tUsage:   \"toxic attribute in key=value format\",\n\t\t\t},\n\t\t},\n\t\tAction: withToxi(updateToxic),\n\t}\n}\n\nfunc cliToxiRemoveSubCommand() *cli.Command {\n\treturn &cli.Command{\n\t\tName:      \"remove\",\n\t\tAliases:   []string{\"r\", \"delete\", \"d\"},\n\t\tUsage:     \"remove an enabled toxic\",\n\t\tArgsUsage: \"<proxyName>\",\n\t\tFlags: []cli.Flag{\n\t\t\t&cli.StringFlag{\n\t\t\t\tName:    \"toxicName\",\n\t\t\t\tAliases: []string{\"n\"},\n\t\t\t\tUsage:   \"name of the toxic\",\n\t\t\t},\n\t\t},\n\t\tAction: withToxi(removeToxic),\n\t}\n}\n\ntype toxiAction func(*cli.Context, *toxiproxy.Client) error\n\nfunc withToxi(f toxiAction) func(*cli.Context) error {\n\treturn func(c *cli.Context) error {\n\t\ttoxiproxyClient := toxiproxy.NewClient(hostname)\n\t\ttoxiproxyClient.UserAgent = fmt.Sprintf(\n\t\t\t\"toxiproxy-cli/%s (%s/%s)\",\n\t\t\tc.App.Version,\n\t\t\truntime.GOOS,\n\t\t\truntime.GOARCH,\n\t\t)\n\t\treturn f(c, toxiproxyClient)\n\t}\n}\n\nfunc list(c *cli.Context, t *toxiproxy.Client) error {\n\tproxies, err := t.Proxies()\n\tif err != nil {\n\t\treturn errorf(\"Failed to retrieve proxies: %s\", err)\n\t}\n\n\tvar proxyNames []string\n\tfor proxyName := range proxies {\n\t\tproxyNames = append(proxyNames, proxyName)\n\t}\n\tsort.Strings(proxyNames)\n\n\tif isTTY {\n\t\tfmt.Printf(\n\t\t\t\"%sName\\t\\t\\t%sListen\\t\\t%sUpstream\\t\\t%sEnabled\\t\\t%sToxics\\n%s\",\n\t\t\tcolor(GREEN),\n\t\t\tcolor(BLUE),\n\t\t\tcolor(YELLOW),\n\t\t\tcolor(PURPLE),\n\t\t\tcolor(RED),\n\t\t\tcolor(NONE),\n\t\t)\n\t\tfmt.Printf(\n\t\t\t\"%s======================================================================================\\n\",\n\t\t\tcolor(NONE),\n\t\t)\n\n\t\tif len(proxyNames) == 0 {\n\t\t\tfmt.Printf(\"%sno proxies\\n%s\", color(RED), color(NONE))\n\t\t\thint(\"create a proxy with `toxiproxy-cli create`\")\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tfor _, proxyName := range proxyNames {\n\t\tproxy := proxies[proxyName]\n\t\tnumToxics := strconv.Itoa(len(proxy.ActiveToxics))\n\t\tif numToxics == \"0\" && isTTY {\n\t\t\tnumToxics = \"None\"\n\t\t}\n\t\tprintWidth(color(colorEnabled(proxy.Enabled)), proxy.Name, 3)\n\t\tprintWidth(BLUE, proxy.Listen, 2)\n\t\tprintWidth(YELLOW, proxy.Upstream, 3)\n\t\tprintWidth(PURPLE, enabledText(proxy.Enabled), 2)\n\t\tfmt.Printf(\"%s%s%s\\n\", color(RED), numToxics, color(NONE))\n\t}\n\thint(\"inspect toxics with `toxiproxy-cli inspect <proxyName>`\")\n\treturn nil\n}\n\nfunc inspectProxy(c *cli.Context, t *toxiproxy.Client) error {\n\tproxyName := c.Args().First()\n\tif proxyName == \"\" {\n\t\tcli.ShowSubcommandHelp(c)\n\t\treturn errorf(\"Proxy name is required as the first argument.\\n\")\n\t}\n\n\tproxy, err := t.Proxy(proxyName)\n\tif err != nil {\n\t\treturn errorf(\"Failed to retrieve proxy %s: %s\\n\", proxyName, err.Error())\n\t}\n\n\tif isTTY {\n\t\tfmt.Printf(\"%sName: %s%s\\t\", color(PURPLE), color(NONE), proxy.Name)\n\t\tfmt.Printf(\"%sListen: %s%s\\t\", color(BLUE), color(NONE), proxy.Listen)\n\t\tfmt.Printf(\"%sUpstream: %s%s\\n\", color(YELLOW), color(NONE), proxy.Upstream)\n\t\tfmt.Printf(\n\t\t\t\"%s======================================================================\\n\",\n\t\t\tcolor(NONE),\n\t\t)\n\n\t\tsplitToxics := func(toxics toxiproxy.Toxics) (toxiproxy.Toxics, toxiproxy.Toxics) {\n\t\t\tupstream := make(toxiproxy.Toxics, 0)\n\t\t\tdownstream := make(toxiproxy.Toxics, 0)\n\t\t\tfor _, toxic := range toxics {\n\t\t\t\tif toxic.Stream == \"upstream\" {\n\t\t\t\t\tupstream = append(upstream, toxic)\n\t\t\t\t} else {\n\t\t\t\t\tdownstream = append(downstream, toxic)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn upstream, downstream\n\t\t}\n\n\t\tif len(proxy.ActiveToxics) == 0 {\n\t\t\tfmt.Printf(\"%sProxy has no toxics enabled.\\n%s\", color(RED), color(NONE))\n\t\t} else {\n\t\t\tup, down := splitToxics(proxy.ActiveToxics)\n\t\t\tlistToxics(up, \"Upstream\")\n\t\t\tfmt.Println()\n\t\t\tlistToxics(down, \"Downstream\")\n\t\t}\n\n\t\thint(\"add a toxic with `toxiproxy-cli toxic add`\")\n\t} else {\n\t\tlistToxics(proxy.ActiveToxics, \"\")\n\t}\n\treturn nil\n}\n\nfunc toggleProxy(c *cli.Context, t *toxiproxy.Client) error {\n\tproxyName := c.Args().First()\n\tif proxyName == \"\" {\n\t\tcli.ShowSubcommandHelp(c)\n\t\treturn errorf(\"Proxy name is required as the first argument.\\n\")\n\t}\n\n\tproxy, err := t.Proxy(proxyName)\n\tif err != nil {\n\t\treturn errorf(\"Failed to retrieve proxy %s: %s\\n\", proxyName, err.Error())\n\t}\n\n\tproxy.Enabled = !proxy.Enabled\n\n\terr = proxy.Save()\n\tif err != nil {\n\t\treturn errorf(\"Failed to toggle proxy %s: %s\\n\", proxyName, err.Error())\n\t}\n\n\tfmt.Printf(\n\t\t\"Proxy %s%s%s is now %s%s%s\\n\",\n\t\tcolorEnabled(proxy.Enabled),\n\t\tproxyName,\n\t\tcolor(NONE),\n\t\tcolorEnabled(proxy.Enabled),\n\t\tenabledText(proxy.Enabled),\n\t\tcolor(NONE),\n\t)\n\treturn nil\n}\n\nfunc createProxy(c *cli.Context, t *toxiproxy.Client) error {\n\tproxyName := c.Args().First()\n\tif proxyName == \"\" {\n\t\tcli.ShowSubcommandHelp(c)\n\t\treturn errorf(\"Proxy name is required as the first argument.\\n\")\n\t}\n\tlisten, err := getArgOrFail(c, \"listen\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tupstream, err := getArgOrFail(c, \"upstream\")\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = t.CreateProxy(proxyName, listen, upstream)\n\tif err != nil {\n\t\treturn errorf(\"Failed to create proxy: %s\\n\", err.Error())\n\t}\n\tfmt.Printf(\"Created new proxy %s\\n\", proxyName)\n\treturn nil\n}\n\nfunc deleteProxy(c *cli.Context, t *toxiproxy.Client) error {\n\tproxyName := c.Args().First()\n\tif proxyName == \"\" {\n\t\tcli.ShowSubcommandHelp(c)\n\t\treturn errorf(\"Proxy name is required as the first argument.\\n\")\n\t}\n\tp, err := t.Proxy(proxyName)\n\tif err != nil {\n\t\treturn errorf(\"Failed to retrieve proxy %s: %s\\n\", proxyName, err.Error())\n\t}\n\n\terr = p.Delete()\n\tif err != nil {\n\t\treturn errorf(\"Failed to delete proxy: %s\\n\", err.Error())\n\t}\n\tfmt.Printf(\"Deleted proxy %s\\n\", proxyName)\n\treturn nil\n}\n\nfunc parseToxicity(c *cli.Context, defaultToxicity float32) (float32, error) {\n\ttoxicity := defaultToxicity\n\ttoxicityString := c.String(\"toxicity\")\n\tif toxicityString != \"\" {\n\t\ttox, err := strconv.ParseFloat(toxicityString, 32)\n\t\tif err != nil || tox > 1 || tox < 0 {\n\t\t\treturn 0, errorf(\"toxicity should be a float between 0 and 1.\\n\")\n\t\t}\n\t\ttoxicity = float32(tox)\n\t}\n\treturn toxicity, nil\n}\n\nfunc addToxic(c *cli.Context, t *toxiproxy.Client) error {\n\ttoxicParams, err := parseAddToxicParams(c)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttoxic, err := t.AddToxic(toxicParams)\n\tif err != nil {\n\t\treturn errorf(\"Failed to add toxic: %v\\n\", err)\n\t}\n\n\tfmt.Printf(\n\t\t\"Added %s %s toxic '%s' on proxy '%s'\\n\",\n\t\ttoxic.Stream,\n\t\ttoxic.Type,\n\t\ttoxic.Name,\n\t\ttoxicParams.ProxyName,\n\t)\n\n\treturn nil\n}\n\nfunc updateToxic(c *cli.Context, t *toxiproxy.Client) error {\n\ttoxicParams, err := parseUpdateToxicParams(c)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttoxic, err := t.UpdateToxic(toxicParams)\n\tif err != nil {\n\t\treturn errorf(\"Failed to update toxic: %v\\n\", err)\n\t}\n\n\tfmt.Printf(\n\t\t\"Updated toxic '%s' on proxy '%s'\\n\",\n\t\ttoxic.Name,\n\t\ttoxicParams.ProxyName,\n\t)\n\treturn nil\n}\n\nfunc removeToxic(c *cli.Context, t *toxiproxy.Client) error {\n\ttoxicParams, err := parseToxicCommonParams(c)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = t.RemoveToxic(toxicParams)\n\tif err != nil {\n\t\treturn errorf(\"Failed to remove toxic: %v\\n\", err)\n\t}\n\n\tfmt.Printf(\"Removed toxic '%s' on proxy '%s'\\n\", toxicParams.ToxicName, toxicParams.ProxyName)\n\treturn nil\n}\n\nfunc parseToxicCommonParams(context *cli.Context) (*toxiproxy.ToxicOptions, error) {\n\tproxyName := context.Args().First()\n\tif proxyName == \"\" {\n\t\tcli.ShowSubcommandHelp(context)\n\t\treturn nil, errorf(\"Proxy name is missing.\\n\")\n\t}\n\n\ttoxicName := context.String(\"toxicName\")\n\n\treturn &toxiproxy.ToxicOptions{\n\t\tProxyName: proxyName,\n\t\tToxicName: toxicName,\n\t}, nil\n}\n\nfunc parseUpdateToxicParams(c *cli.Context) (*toxiproxy.ToxicOptions, error) {\n\tresult, err := parseToxicCommonParams(c)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresult.Toxicity, err = parseToxicity(c, 1.0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresult.Attributes = parseAttributes(c, \"attribute\")\n\n\treturn result, nil\n}\n\nfunc parseAddToxicParams(c *cli.Context) (*toxiproxy.ToxicOptions, error) {\n\tresult, err := parseToxicCommonParams(c)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresult.ToxicType, err = getArgOrFail(c, \"type\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tupstream := c.Bool(\"upstream\")\n\tdownstream := c.Bool(\"downstream\")\n\tif upstream && downstream {\n\t\treturn nil, errorf(\"Only one should be specified: upstream or downstream.\\n\")\n\t}\n\n\tstream := \"downstream\"\n\tif upstream {\n\t\tstream = \"upstream\"\n\t}\n\tresult.Stream = stream\n\n\tresult.Toxicity, err = parseToxicity(c, 1.0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresult.Attributes = parseAttributes(c, \"attribute\")\n\n\treturn result, nil\n}\n\nfunc parseAttributes(c *cli.Context, name string) toxiproxy.Attributes {\n\tparsed := map[string]interface{}{}\n\targs := c.StringSlice(name)\n\n\tfor _, raw := range args {\n\t\tkv := strings.SplitN(raw, \"=\", 2)\n\t\tif len(kv) < 2 {\n\t\t\tcontinue\n\t\t}\n\t\tif float, err := strconv.ParseFloat(kv[1], 64); err == nil {\n\t\t\tparsed[kv[0]] = float\n\t\t} else {\n\t\t\tparsed[kv[0]] = kv[1]\n\t\t}\n\t}\n\treturn parsed\n}\n\nfunc colorEnabled(enabled bool) string {\n\tif enabled {\n\t\treturn color(GREEN)\n\t}\n\n\treturn color(RED)\n}\n\nfunc enabledText(enabled bool) string {\n\tif enabled {\n\t\treturn \"enabled\"\n\t}\n\n\treturn \"disabled\"\n}\n\ntype attribute struct {\n\tkey   string\n\tvalue interface{}\n}\n\ntype attributeList []attribute\n\nfunc sortedAttributes(attrs toxiproxy.Attributes) attributeList {\n\tli := make(attributeList, 0, len(attrs))\n\tfor k, v := range attrs {\n\t\tli = append(li, attribute{k, v})\n\t}\n\tsort.Slice(li, func(i, j int) bool {\n\t\treturn li[i].key < li[j].key\n\t})\n\n\treturn li\n}\n\nfunc listToxics(toxics toxiproxy.Toxics, stream string) {\n\tif isTTY {\n\t\tfmt.Printf(\"%s%s toxics:\\n%s\", color(GREEN), stream, color(NONE))\n\t\tif len(toxics) == 0 {\n\t\t\tfmt.Printf(\"%sProxy has no %s toxics enabled.\\n%s\", color(RED), stream, color(NONE))\n\t\t\treturn\n\t\t}\n\t}\n\tfor _, t := range toxics {\n\t\tif isTTY {\n\t\t\tfmt.Printf(\"%s%s:%s\\t\", color(BLUE), t.Name, color(NONE))\n\t\t} else {\n\t\t\tfmt.Printf(\"%s\\t\", t.Name)\n\t\t}\n\t\tfmt.Printf(\"type=%s\\t\", t.Type)\n\t\tfmt.Printf(\"stream=%s\\t\", t.Stream)\n\t\tfmt.Printf(\"toxicity=%.2f\\t\", t.Toxicity)\n\t\tfmt.Printf(\"attributes=[\")\n\t\tsorted := sortedAttributes(t.Attributes)\n\t\tfor _, a := range sorted {\n\t\t\tfmt.Printf(\"\\t%s=\", a.key)\n\t\t\tfmt.Print(a.value)\n\t\t}\n\t\tfmt.Printf(\"\\t]\\n\")\n\t}\n}\n\nfunc getArgOrFail(c *cli.Context, name string) (string, error) {\n\targ := c.String(name)\n\tif arg == \"\" {\n\t\tcli.ShowSubcommandHelp(c)\n\t\treturn \"\", errorf(\"Required argument '%s' was empty.\\n\", name)\n\t}\n\treturn arg, nil\n}\n\nfunc hint(m string) {\n\tif isTTY {\n\t\tfmt.Printf(\"\\n%sHint: %s\\n\", color(NONE), m)\n\t}\n}\n\nfunc errorf(m string, args ...interface{}) error {\n\treturn cli.Exit(fmt.Sprintf(m, args...), 1)\n}\n\nfunc printWidth(col string, m string, numTabs int) {\n\tif isTTY {\n\t\tnumTabs -= len(m)/8 + 1\n\t\tif numTabs < 0 {\n\t\t\tnumTabs = 0\n\t\t}\n\t} else {\n\t\tnumTabs = 0\n\t}\n\tfmt.Printf(\"%s%s%s\\t%s\", color(col), m, color(NONE), strings.Repeat(\"\\t\", numTabs))\n}\n"
  },
  {
    "path": "cmd/server/server.go",
    "content": "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/prometheus/client_golang/prometheus\"\n\t\"github.com/rs/zerolog\"\n\t\"github.com/rs/zerolog/log\"\n\n\t\"github.com/Shopify/toxiproxy/v2\"\n\t\"github.com/Shopify/toxiproxy/v2/collectors\"\n)\n\ntype cliArguments struct {\n\thost           string\n\tport           string\n\tconfig         string\n\tseed           int64\n\tprintVersion   bool\n\tproxyMetrics   bool\n\truntimeMetrics bool\n}\n\nfunc parseArguments() cliArguments {\n\tresult := cliArguments{}\n\tflag.StringVar(&result.host, \"host\", \"localhost\",\n\t\t\"Host for toxiproxy's API to listen on\")\n\tflag.StringVar(&result.port, \"port\", \"8474\",\n\t\t\"Port for toxiproxy's API to listen on\")\n\tflag.StringVar(&result.config, \"config\", \"\",\n\t\t\"JSON file containing proxies to create on startup\")\n\tflag.Int64Var(&result.seed, \"seed\", time.Now().UTC().UnixNano(),\n\t\t\"Seed for randomizing toxics with\")\n\tflag.BoolVar(&result.runtimeMetrics, \"runtime-metrics\", false,\n\t\t`enable runtime-related prometheus metrics (default \"false\")`)\n\tflag.BoolVar(&result.proxyMetrics, \"proxy-metrics\", false,\n\t\t`enable toxiproxy-specific prometheus metrics (default \"false\")`)\n\tflag.BoolVar(&result.printVersion, \"version\", false,\n\t\t`print the version (default \"false\")`)\n\tflag.Parse()\n\n\treturn result\n}\n\nfunc main() {\n\terr := run()\n\tif err != nil {\n\t\tfmt.Printf(\"error: %v\", err)\n\t\tos.Exit(1)\n\t}\n\tos.Exit(0)\n}\n\nfunc run() error {\n\tcli := parseArguments()\n\n\tif cli.printVersion {\n\t\tfmt.Printf(\"toxiproxy-server version %s\\n\", toxiproxy.Version)\n\t\treturn nil\n\t}\n\n\trand.New(rand.NewSource(cli.seed)) // #nosec G404 -- ignoring this rule\n\n\tlogger := setupLogger()\n\tlog.Logger = logger\n\n\tlogger.\n\t\tInfo().\n\t\tStr(\"version\", toxiproxy.Version).\n\t\tMsg(\"Starting Toxiproxy\")\n\n\tmetrics := toxiproxy.NewMetricsContainer(prometheus.NewRegistry())\n\tserver := toxiproxy.NewServer(metrics, logger)\n\tif cli.proxyMetrics {\n\t\tserver.Metrics.ProxyMetrics = collectors.NewProxyMetricCollectors()\n\t}\n\tif cli.runtimeMetrics {\n\t\tserver.Metrics.RuntimeMetrics = collectors.NewRuntimeMetricCollectors()\n\t}\n\n\tif len(cli.config) > 0 {\n\t\tserver.PopulateConfig(cli.config)\n\t}\n\n\taddr := net.JoinHostPort(cli.host, cli.port)\n\tgo func(server *toxiproxy.ApiServer, addr string) {\n\t\terr := server.Listen(addr)\n\t\tif err != nil {\n\t\t\tserver.Logger.Err(err).Msg(\"Server finished with error\")\n\t\t}\n\t}(server, addr)\n\n\tsignals := make(chan os.Signal, 1)\n\tsignal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)\n\t<-signals\n\tserver.Logger.Info().Msg(\"Shutdown started\")\n\terr := server.Shutdown()\n\tif err != nil {\n\t\tlogger.Err(err).Msg(\"Shutdown finished with error\")\n\t}\n\treturn nil\n}\n\nfunc setupLogger() zerolog.Logger {\n\tzerolog.TimestampFunc = func() time.Time {\n\t\treturn time.Now().UTC()\n\t}\n\n\tzerolog.CallerMarshalFunc = func(pc uintptr, file string, line int) string {\n\t\tshort := file\n\t\tfor i := len(file) - 1; i > 0; i-- {\n\t\t\tif file[i] == '/' {\n\t\t\t\tshort = file[i+1:]\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfile = short\n\t\treturn file + \":\" + strconv.Itoa(line)\n\t}\n\n\tlogger := zerolog.New(os.Stdout).With().Caller().Timestamp().Logger()\n\n\tval, ok := os.LookupEnv(\"LOG_LEVEL\")\n\tif !ok {\n\t\treturn logger\n\t}\n\n\tlvl, err := zerolog.ParseLevel(val)\n\tif err == nil {\n\t\tlogger = logger.Level(lvl)\n\t} else {\n\t\tl := &logger\n\t\tl.Err(err).Msgf(\"unknown LOG_LEVEL value: \\\"%s\\\"\", val)\n\t}\n\n\treturn logger\n}\n"
  },
  {
    "path": "collectors/common.go",
    "content": "package collectors\n\nconst (\n\tnamespace string = \"toxiproxy\"\n)\n"
  },
  {
    "path": "collectors/proxy.go",
    "content": "package collectors\n\nimport (\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\ntype ProxyMetricCollectors struct {\n\tcollectors  []prometheus.Collector\n\tproxyLabels []string\n\n\tReceivedBytesTotal *prometheus.CounterVec\n\tSentBytesTotal     *prometheus.CounterVec\n}\n\nfunc (c *ProxyMetricCollectors) Collectors() []prometheus.Collector {\n\treturn c.collectors\n}\n\nfunc NewProxyMetricCollectors() *ProxyMetricCollectors {\n\tvar m ProxyMetricCollectors\n\tm.proxyLabels = []string{\n\t\t\"direction\",\n\t\t\"proxy\",\n\t\t\"listener\",\n\t\t\"upstream\",\n\t}\n\tm.ReceivedBytesTotal = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: namespace,\n\t\t\tSubsystem: \"proxy\",\n\t\t\tName:      \"received_bytes_total\",\n\t\t},\n\t\tm.proxyLabels)\n\tm.collectors = append(m.collectors, m.ReceivedBytesTotal)\n\n\tm.SentBytesTotal = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: namespace,\n\t\t\tSubsystem: \"proxy\",\n\t\t\tName:      \"sent_bytes_total\",\n\t\t},\n\t\tm.proxyLabels)\n\tm.collectors = append(m.collectors, m.SentBytesTotal)\n\n\treturn &m\n}\n"
  },
  {
    "path": "collectors/runtime.go",
    "content": "package collectors\n\nimport (\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/collectors\"\n)\n\ntype RuntimeMetricCollectors struct {\n\tcollectors []prometheus.Collector\n}\n\nfunc (c *RuntimeMetricCollectors) Collectors() []prometheus.Collector {\n\treturn c.collectors\n}\n\nfunc NewRuntimeMetricCollectors() *RuntimeMetricCollectors {\n\tvar m RuntimeMetricCollectors\n\tm.collectors = append(m.collectors, collectors.NewGoCollector())\n\tm.collectors = append(m.collectors, collectors.NewBuildInfoCollector())\n\tm.collectors = append(m.collectors,\n\t\tcollectors.NewProcessCollector(collectors.ProcessCollectorOpts{}))\n\treturn &m\n}\n"
  },
  {
    "path": "dev.yml",
    "content": "---\n\nname: toxiproxy\n\ntype: go\n\nup:\n  - packages:\n      - gnu-tar\n      - golangci-lint\n      - goreleaser\n      - shellcheck\n      - shfmt\n      - yamllint\n  - go:\n      version: 1.22.8\n      modules: true\n"
  },
  {
    "path": "go.mod",
    "content": "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/client_golang v1.23.2\n\tgithub.com/rs/zerolog v1.34.0\n\tgithub.com/urfave/cli/v2 v2.27.7\n\tgolang.org/x/term v0.34.0\n\tgopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7\n)\n\nrequire (\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect\n\tgithub.com/kr/text v0.2.0 // indirect\n\tgithub.com/mattn/go-colorable v0.1.13 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/prometheus/client_model v0.6.2 // indirect\n\tgithub.com/prometheus/common v0.66.1 // indirect\n\tgithub.com/prometheus/procfs v0.16.1 // indirect\n\tgithub.com/rs/xid v1.6.0 // indirect\n\tgithub.com/russross/blackfriday/v2 v2.1.0 // indirect\n\tgithub.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.2 // indirect\n\tgolang.org/x/sys v0.35.0 // indirect\n\tgoogle.golang.org/protobuf v1.36.8 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=\ngithub.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=\ngithub.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=\ngithub.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=\ngithub.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=\ngithub.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=\ngithub.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=\ngithub.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=\ngithub.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=\ngithub.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=\ngithub.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=\ngithub.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=\ngithub.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=\ngithub.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=\ngithub.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=\ngithub.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=\ngithub.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=\ngithub.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU=\ngithub.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4=\ngithub.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=\ngithub.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=\ngo.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=\ngolang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=\ngolang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=\ngolang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=\ngoogle.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=\ngoogle.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "link.go",
    "content": "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/toxiproxy/v2/stream\"\n\t\"github.com/Shopify/toxiproxy/v2/toxics\"\n)\n\n// ToxicLinks are single direction pipelines that connects an input and output via\n// a chain of toxics. The chain always starts with a NoopToxic, and toxics are added\n// and removed as they are enabled/disabled. New toxics are always added to the end\n// of the chain.\n//\n// |         NoopToxic  LatencyToxic\n// |             v           v\n// | Input > ToxicStub > ToxicStub > Output.\ntype ToxicLink struct {\n\tstubs     []*toxics.ToxicStub\n\tproxy     *Proxy\n\ttoxics    *ToxicCollection\n\tinput     *stream.ChanWriter\n\toutput    *stream.ChanReader\n\tdirection stream.Direction\n\tLogger    *zerolog.Logger\n}\n\nfunc NewToxicLink(\n\tproxy *Proxy,\n\tcollection *ToxicCollection,\n\tdirection stream.Direction,\n\tlogger zerolog.Logger,\n) *ToxicLink {\n\tlink := &ToxicLink{\n\t\tstubs: make(\n\t\t\t[]*toxics.ToxicStub,\n\t\t\tlen(collection.chain[direction]),\n\t\t\tcap(collection.chain[direction]),\n\t\t),\n\t\tproxy:     proxy,\n\t\ttoxics:    collection,\n\t\tdirection: direction,\n\t\tLogger:    &logger,\n\t}\n\t// Initialize the link with ToxicStubs\n\tlast := make(chan *stream.StreamChunk) // The first toxic is always a noop\n\tlink.input = stream.NewChanWriter(last)\n\tfor i := 0; i < len(link.stubs); i++ {\n\t\tvar next chan *stream.StreamChunk\n\t\tif i+1 < len(link.stubs) {\n\t\t\tnext = make(chan *stream.StreamChunk, link.toxics.chain[direction][i+1].BufferSize)\n\t\t} else {\n\t\t\tnext = make(chan *stream.StreamChunk)\n\t\t}\n\n\t\tlink.stubs[i] = toxics.NewToxicStub(last, next)\n\t\tlast = next\n\t}\n\tlink.output = stream.NewChanReader(last)\n\treturn link\n}\n\n// Start the link with the specified toxics.\nfunc (link *ToxicLink) Start(\n\tserver *ApiServer,\n\tname string,\n\tsource io.Reader,\n\tdest io.WriteCloser,\n) {\n\tlogger := link.Logger\n\tlogger.\n\t\tDebug().\n\t\tStr(\"direction\", link.Direction()).\n\t\tMsg(\"Setup connection\")\n\n\tlabels := []string{\n\t\tlink.Direction(),\n\t\tlink.proxy.Name,\n\t\tlink.proxy.Listen,\n\t\tlink.proxy.Upstream}\n\n\tgo link.read(labels, server, source)\n\n\tfor i, toxic := range link.toxics.chain[link.direction] {\n\t\tif stateful, ok := toxic.Toxic.(toxics.StatefulToxic); ok {\n\t\t\tlink.stubs[i].State = stateful.NewState()\n\t\t}\n\n\t\tif _, ok := toxic.Toxic.(*toxics.ResetToxic); ok {\n\t\t\tif err := source.(*net.TCPConn).SetLinger(0); err != nil {\n\t\t\t\tlogger.Err(err).\n\t\t\t\t\tStr(\"toxic\", toxic.Type).\n\t\t\t\t\tMsg(\"source: Unable to setLinger(ms)\")\n\t\t\t}\n\n\t\t\tif err := dest.(*net.TCPConn).SetLinger(0); err != nil {\n\t\t\t\tlogger.Err(err).\n\t\t\t\t\tStr(\"toxic\", toxic.Type).\n\t\t\t\t\tMsg(\"dest: Unable to setLinger(ms)\")\n\t\t\t}\n\t\t}\n\n\t\tgo link.stubs[i].Run(toxic)\n\t}\n\n\tgo link.write(labels, name, server, dest)\n}\n\n// read copies bytes from a source to the link's input channel.\nfunc (link *ToxicLink) read(\n\tmetricLabels []string,\n\tserver *ApiServer,\n\tsource io.Reader,\n) {\n\tlogger := link.Logger\n\tbytes, err := io.Copy(link.input, source)\n\tif err != nil {\n\t\tlogger.Warn().\n\t\t\tInt64(\"bytes\", bytes).\n\t\t\tErr(err).\n\t\t\tMsg(\"Source terminated\")\n\t}\n\tif server.Metrics.proxyMetricsEnabled() {\n\t\tserver.Metrics.ProxyMetrics.ReceivedBytesTotal.\n\t\t\tWithLabelValues(metricLabels...).Add(float64(bytes))\n\t}\n\tlink.input.Close()\n}\n\n// write copies bytes from the link's output channel to a destination.\nfunc (link *ToxicLink) write(\n\tmetricLabels []string,\n\tname string,\n\tserver *ApiServer, // TODO: Replace with AppConfig for Metrics and Logger\n\tdest io.WriteCloser,\n) {\n\tlogger := link.Logger.\n\t\tWith().\n\t\tStr(\"component\", \"ToxicLink\").\n\t\tStr(\"method\", \"write\").\n\t\tStr(\"link\", name).\n\t\tStr(\"proxy\", link.proxy.Name).\n\t\tStr(\"link_addr\", fmt.Sprintf(\"%p\", link)).\n\t\tLogger()\n\n\tbytes, err := io.Copy(dest, link.output)\n\tif err != nil {\n\t\tlogger.Warn().\n\t\t\tInt64(\"bytes\", bytes).\n\t\t\tErr(err).\n\t\t\tMsg(\"Could not write to destination\")\n\t} else if server.Metrics.proxyMetricsEnabled() {\n\t\tserver.Metrics.ProxyMetrics.SentBytesTotal.\n\t\t\tWithLabelValues(metricLabels...).Add(float64(bytes))\n\t}\n\n\tdest.Close()\n\tlogger.Trace().Msgf(\"Remove link %s from ToxicCollection\", name)\n\tlink.toxics.RemoveLink(name)\n\tlogger.Trace().Msgf(\"RemoveConnection %s from Proxy %s\", name, link.proxy.Name)\n\tlink.proxy.RemoveConnection(name)\n}\n\n// Add a toxic to the end of the chain.\nfunc (link *ToxicLink) AddToxic(toxic *toxics.ToxicWrapper) {\n\ti := len(link.stubs)\n\n\tnewin := make(chan *stream.StreamChunk, toxic.BufferSize)\n\tlink.stubs = append(link.stubs, toxics.NewToxicStub(newin, link.stubs[i-1].Output))\n\n\t// Interrupt the last toxic so that we don't have a race when moving channels\n\tif link.stubs[i-1].InterruptToxic() {\n\t\tlink.stubs[i-1].Output = newin\n\n\t\tif stateful, ok := toxic.Toxic.(toxics.StatefulToxic); ok {\n\t\t\tlink.stubs[i].State = stateful.NewState()\n\t\t}\n\n\t\tgo link.stubs[i].Run(toxic)\n\t\tgo link.stubs[i-1].Run(link.toxics.chain[link.direction][i-1])\n\t} else {\n\t\t// This link is already closed, make sure the new toxic matches\n\t\tlink.stubs[i].Output = newin // The real output is already closed, close this instead\n\t\tlink.stubs[i].Close()\n\t}\n}\n\n// Update an existing toxic in the chain.\nfunc (link *ToxicLink) UpdateToxic(toxic *toxics.ToxicWrapper) {\n\tif link.stubs[toxic.Index].InterruptToxic() {\n\t\tgo link.stubs[toxic.Index].Run(toxic)\n\t}\n}\n\n// Remove an existing toxic from the chain.\nfunc (link *ToxicLink) RemoveToxic(ctx context.Context, toxic *toxics.ToxicWrapper) {\n\ttoxic_index := toxic.Index\n\tlog := zerolog.Ctx(ctx).\n\t\tWith().\n\t\tStr(\"component\", \"ToxicLink\").\n\t\tStr(\"method\", \"RemoveToxic\").\n\t\tStr(\"toxic\", toxic.Name).\n\t\tStr(\"toxic_type\", toxic.Type).\n\t\tInt(\"toxic_index\", toxic.Index).\n\t\tStr(\"link_addr\", fmt.Sprintf(\"%p\", link)).\n\t\tStr(\"toxic_stub_addr\", fmt.Sprintf(\"%p\", link.stubs[toxic_index])).\n\t\tStr(\"prev_toxic_stub_addr\", fmt.Sprintf(\"%p\", link.stubs[toxic_index-1])).\n\t\tLogger()\n\n\tif link.stubs[toxic_index].InterruptToxic() {\n\t\tcleanup, ok := toxic.Toxic.(toxics.CleanupToxic)\n\t\tif ok {\n\t\t\tcleanup.Cleanup(link.stubs[toxic_index])\n\t\t\t// Cleanup could have closed the stub.\n\t\t\tif link.stubs[toxic_index].Closed() {\n\t\t\t\tlog.Trace().Msg(\"Cleanup closed toxic and removed toxic\")\n\t\t\t\t// TODO: Check if cleanup happen would link.stubs recalculated?\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tlog.Trace().Msg(\"Interrupting the previous toxic to update its output\")\n\t\tstop := make(chan bool)\n\t\tgo func(stub *toxics.ToxicStub, stop chan bool) {\n\t\t\tstop <- stub.InterruptToxic()\n\t\t}(link.stubs[toxic_index-1], stop)\n\n\t\t// Unblock the previous toxic if it is trying to flush\n\t\t// If the previous toxic is closed, continue flusing until we reach the end.\n\t\tinterrupted := false\n\t\tstopped := false\n\t\tfor !interrupted {\n\t\t\tselect {\n\t\t\tcase interrupted = <-stop:\n\t\t\t\tstopped = true\n\t\t\tcase tmp := <-link.stubs[toxic_index].Input:\n\t\t\t\tif tmp == nil {\n\t\t\t\t\tlink.stubs[toxic_index].Close()\n\t\t\t\t\tif !stopped {\n\t\t\t\t\t\t<-stop\n\t\t\t\t\t}\n\t\t\t\t\treturn // TODO: There are some steps after this to clean buffer\n\t\t\t\t}\n\n\t\t\t\terr := link.stubs[toxic_index].WriteOutput(tmp, 5*time.Second)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Err(err).\n\t\t\t\t\t\tMsg(\"Could not write last packets after interrupt to Output\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Empty the toxic's buffer if necessary\n\t\tfor len(link.stubs[toxic_index].Input) > 0 {\n\t\t\ttmp := <-link.stubs[toxic_index].Input\n\t\t\tif tmp == nil {\n\t\t\t\tlink.stubs[toxic_index].Close()\n\t\t\t\treturn\n\t\t\t}\n\t\t\terr := link.stubs[toxic_index].WriteOutput(tmp, 5*time.Second)\n\t\t\tif err != nil {\n\t\t\t\tlog.Err(err).\n\t\t\t\t\tMsg(\"Could not write last packets after interrupt to Output\")\n\t\t\t}\n\t\t}\n\n\t\tlink.stubs[toxic_index-1].Output = link.stubs[toxic_index].Output\n\t\tlink.stubs = append(link.stubs[:toxic_index], link.stubs[toxic_index+1:]...)\n\n\t\tgo link.stubs[toxic_index-1].Run(link.toxics.chain[link.direction][toxic_index-1])\n\t}\n}\n\n// Direction returns the direction of the link (upstream or downstream).\nfunc (link *ToxicLink) Direction() string {\n\treturn link.direction.String()\n}\n"
  },
  {
    "path": "link_test.go",
    "content": "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/zerolog\"\n\n\t\"github.com/Shopify/toxiproxy/v2/stream\"\n\t\"github.com/Shopify/toxiproxy/v2/testhelper\"\n\t\"github.com/Shopify/toxiproxy/v2/toxics\"\n)\n\nfunc TestToxicsAreLoaded(t *testing.T) {\n\tif toxics.Count() < 1 {\n\t\tt.Fatal(\"No toxics loaded!\")\n\t}\n}\n\nfunc TestStubInitializaation(t *testing.T) {\n\tcollection := NewToxicCollection(nil)\n\tlink := NewToxicLink(nil, collection, stream.Downstream, zerolog.Nop())\n\tif len(link.stubs) != 1 {\n\t\tt.Fatalf(\"Link created with wrong number of stubs: %d != 1\", len(link.stubs))\n\t}\n\n\tif cap(link.stubs) != toxics.Count()+1 {\n\t\tt.Fatalf(\"Link created with wrong capacity: %d != %d\", cap(link.stubs), toxics.Count()+1)\n\t}\n\n\tif cap(link.stubs[0].Input) != 0 {\n\t\tt.Fatalf(\"Noop buffer was not initialized as 0: %d\", cap(link.stubs[0].Input))\n\t}\n\n\tif cap(link.stubs[0].Output) != 0 {\n\t\tt.Fatalf(\"Link output buffer was not initialized as 0: %d\", cap(link.stubs[0].Output))\n\t}\n}\n\nfunc TestStubInitializaationWithToxics(t *testing.T) {\n\tcollection := NewToxicCollection(nil)\n\tcollection.chainAddToxic(&toxics.ToxicWrapper{\n\t\tToxic:      new(toxics.LatencyToxic),\n\t\tType:       \"latency\",\n\t\tDirection:  stream.Downstream,\n\t\tBufferSize: 1024,\n\t\tToxicity:   1,\n\t})\n\tcollection.chainAddToxic(&toxics.ToxicWrapper{\n\t\tToxic:     new(toxics.BandwidthToxic),\n\t\tType:      \"bandwidth\",\n\t\tDirection: stream.Downstream,\n\t\tToxicity:  1,\n\t})\n\tlink := NewToxicLink(nil, collection, stream.Downstream, zerolog.Nop())\n\n\tif len(link.stubs) != 3 {\n\t\tt.Fatalf(\"Link created with wrong number of stubs: %d != 3\", len(link.stubs))\n\t}\n\n\tif cap(link.stubs) != toxics.Count()+1 {\n\t\tt.Fatalf(\"Link created with wrong capacity: %d != %d\", cap(link.stubs), toxics.Count()+1)\n\t}\n\n\tif cap(link.stubs[len(link.stubs)-1].Output) != 0 {\n\t\tt.Fatalf(\"Link output buffer was not initialized as 0: %d\", cap(link.stubs[0].Output))\n\t}\n\n\tfor i, toxic := range collection.chain[stream.Downstream] {\n\t\tif cap(link.stubs[i].Input) != toxic.BufferSize {\n\t\t\tt.Fatalf(\n\t\t\t\t\"%s buffer was not initialized as %d: %d\",\n\t\t\t\ttoxic.Type,\n\t\t\t\ttoxic.BufferSize,\n\t\t\t\tcap(link.stubs[i].Input),\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestAddRemoveStubs(t *testing.T) {\n\tctx := context.Background()\n\tcollection := NewToxicCollection(nil)\n\tlink := NewToxicLink(nil, collection, stream.Downstream, zerolog.Nop())\n\tgo link.stubs[0].Run(collection.chain[stream.Downstream][0])\n\tcollection.links[\"test\"] = link\n\n\t// Add stubs\n\tcollection.chainAddToxic(&toxics.ToxicWrapper{\n\t\tToxic:      new(toxics.LatencyToxic),\n\t\tType:       \"latency\",\n\t\tDirection:  stream.Downstream,\n\t\tBufferSize: 1024,\n\t\tToxicity:   1,\n\t})\n\ttoxic := &toxics.ToxicWrapper{\n\t\tToxic:      new(toxics.BandwidthToxic),\n\t\tType:       \"bandwidth\",\n\t\tDirection:  stream.Downstream,\n\t\tBufferSize: 2048,\n\t\tToxicity:   1,\n\t}\n\tcollection.chainAddToxic(toxic)\n\tif cap(link.stubs[len(link.stubs)-1].Output) != 0 {\n\t\tt.Fatalf(\"Link output buffer was not initialized as 0: %d\", cap(link.stubs[0].Output))\n\t}\n\tfor i, toxic := range collection.chain[stream.Downstream] {\n\t\tif cap(link.stubs[i].Input) != toxic.BufferSize {\n\t\t\tt.Fatalf(\n\t\t\t\t\"%s buffer was not initialized as %d: %d\",\n\t\t\t\ttoxic.Type,\n\t\t\t\ttoxic.BufferSize,\n\t\t\t\tcap(link.stubs[i].Input),\n\t\t\t)\n\t\t}\n\t}\n\n\t// Remove stubs\n\tcollection.chainRemoveToxic(ctx, toxic)\n\tif cap(link.stubs[len(link.stubs)-1].Output) != 0 {\n\t\tt.Fatalf(\"Link output buffer was not initialized as 0: %d\", cap(link.stubs[0].Output))\n\t}\n\tfor i, toxic := range collection.chain[stream.Downstream] {\n\t\tif cap(link.stubs[i].Input) != toxic.BufferSize {\n\t\t\tt.Fatalf(\n\t\t\t\t\"%s buffer was not initialized as %d: %d\",\n\t\t\t\ttoxic.Type,\n\t\t\t\ttoxic.BufferSize,\n\t\t\t\tcap(link.stubs[i].Input),\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestNoDataDropped(t *testing.T) {\n\tctx := context.Background()\n\tcollection := NewToxicCollection(nil)\n\tlink := NewToxicLink(nil, collection, stream.Downstream, zerolog.Nop())\n\tgo link.stubs[0].Run(collection.chain[stream.Downstream][0])\n\tcollection.links[\"test\"] = link\n\n\ttoxic := &toxics.ToxicWrapper{\n\t\tToxic: &toxics.LatencyToxic{\n\t\t\tLatency: 1000,\n\t\t},\n\t\tType:       \"latency\",\n\t\tDirection:  stream.Downstream,\n\t\tBufferSize: 1024,\n\t\tToxicity:   1,\n\t}\n\n\tdone := make(chan struct{})\n\tdefer close(done)\n\tgo func() {\n\t\tfor i := uint16(0); i < 65535; i++ {\n\t\t\tbuf := make([]byte, 2)\n\t\t\tbinary.BigEndian.PutUint16(buf, i)\n\t\t\tlink.input.Write(buf)\n\t\t}\n\t\tlink.input.Close()\n\t}()\n\tgo func(ctx context.Context) {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-done:\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\tcollection.chainAddToxic(toxic)\n\t\t\t\tcollection.chainRemoveToxic(ctx, toxic)\n\t\t\t}\n\t\t}\n\t}(ctx)\n\n\tbuf := make([]byte, 2)\n\tfor i := uint16(0); i < 65535; i++ {\n\t\tn, err := link.output.Read(buf)\n\t\tif n != 2 || err != nil {\n\t\t\tt.Fatalf(\"Read failed: %d %v\", n, err)\n\t\t} else {\n\t\t\tval := binary.BigEndian.Uint16(buf)\n\t\t\tif val != i {\n\t\t\t\tt.Fatalf(\"Read incorrect bytes: %v != %d\", val, i)\n\t\t\t}\n\t\t}\n\t}\n\tn, err := link.output.Read(buf)\n\tif n != 0 || err != io.EOF {\n\t\tt.Fatalf(\"Expected EOF: %d %v\", n, err)\n\t}\n}\n\nfunc TestToxicity(t *testing.T) {\n\tcollection := NewToxicCollection(nil)\n\tlink := NewToxicLink(nil, collection, stream.Downstream, zerolog.Nop())\n\tgo link.stubs[0].Run(collection.chain[stream.Downstream][0])\n\tcollection.links[\"test\"] = link\n\n\ttoxic := &toxics.ToxicWrapper{\n\t\tToxic:     new(toxics.TimeoutToxic),\n\t\tName:      \"timeout1\",\n\t\tType:      \"timeout\",\n\t\tDirection: stream.Downstream,\n\t\tToxicity:  0,\n\t}\n\tcollection.chainAddToxic(toxic)\n\n\t// Toxic should be a Noop because of toxicity\n\tn, err := link.input.Write([]byte{42})\n\tif n != 1 || err != nil {\n\t\tt.Fatalf(\"Write failed: %d %v\", n, err)\n\t}\n\tbuf := make([]byte, 2)\n\tn, err = link.output.Read(buf)\n\tif n != 1 || err != nil {\n\t\tt.Fatalf(\"Read failed: %d %v\", n, err)\n\t} else if buf[0] != 42 {\n\t\tt.Fatalf(\"Read wrong byte: %x\", buf[0])\n\t}\n\n\ttoxic.Toxicity = 1\n\ttoxic.Toxic.(*toxics.TimeoutToxic).Timeout = 100\n\tcollection.chainUpdateToxic(toxic)\n\n\terr = testhelper.TimeoutAfter(150*time.Millisecond, func() {\n\t\tn, err = link.input.Write([]byte{42})\n\t\tif n != 1 || err != nil {\n\t\t\tt.Fatalf(\"Write failed: %d %v\", n, err)\n\t\t}\n\t\tn, err = link.output.Read(buf)\n\t\tif n != 0 || err != io.EOF {\n\t\t\tt.Fatalf(\"Read did not get EOF: %d %v\", n, err)\n\t\t}\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestStateCreated(t *testing.T) {\n\tcollection := NewToxicCollection(nil)\n\tlog := zerolog.Nop()\n\tif flag.Lookup(\"test.v\").DefValue == \"true\" {\n\t\tlog = zerolog.New(os.Stdout).With().Caller().Timestamp().Logger()\n\t}\n\n\tlink := NewToxicLink(nil, collection, stream.Downstream, log)\n\tgo link.stubs[0].Run(collection.chain[stream.Downstream][0])\n\tcollection.links[\"test\"] = link\n\n\tcollection.chainAddToxic(&toxics.ToxicWrapper{\n\t\tToxic:     new(toxics.LimitDataToxic),\n\t\tType:      \"limit_data\",\n\t\tDirection: stream.Downstream,\n\t\tToxicity:  1,\n\t})\n\tif link.stubs[len(link.stubs)-1].State == nil {\n\t\tt.Fatalf(\"New toxic did not have state object created.\")\n\t}\n}\n\nfunc TestRemoveToxicWithBrokenConnection(t *testing.T) {\n\tctx := context.Background()\n\n\tlog := zerolog.Nop()\n\tif flag.Lookup(\"test.v\").DefValue == \"true\" {\n\t\tlog = zerolog.New(os.Stdout).With().Caller().Timestamp().Logger()\n\t}\n\tctx = log.WithContext(ctx)\n\n\tcollection := NewToxicCollection(nil)\n\tlink := NewToxicLink(nil, collection, stream.Downstream, log)\n\tgo link.stubs[0].Run(collection.chain[stream.Downstream][0])\n\tcollection.links[\"test\"] = link\n\n\ttoxics := [2]*toxics.ToxicWrapper{\n\t\t{\n\t\t\tToxic: &toxics.BandwidthToxic{\n\t\t\t\tRate: 0,\n\t\t\t},\n\t\t\tType:      \"bandwidth\",\n\t\t\tDirection: stream.Downstream,\n\t\t\tToxicity:  1,\n\t\t},\n\t\t{\n\t\t\tToxic: &toxics.BandwidthToxic{\n\t\t\t\tRate: 0,\n\t\t\t},\n\t\t\tType:      \"bandwidth\",\n\t\t\tDirection: stream.Upstream,\n\t\t\tToxicity:  1,\n\t\t},\n\t}\n\n\tcollection.chainAddToxic(toxics[0])\n\tcollection.chainAddToxic(toxics[1])\n\n\tdone := make(chan struct{})\n\tdefer close(done)\n\n\tvar data uint16 = 42\n\tgo func(log zerolog.Logger) {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-done:\n\t\t\t\tlink.input.Close()\n\t\t\t\treturn\n\t\t\tcase <-time.After(10 * time.Second):\n\t\t\t\tlog.Print(\"Finish load\")\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\tbuf := make([]byte, 2)\n\t\t\t\tbinary.BigEndian.PutUint16(buf, data)\n\t\t\t\tlink.input.Write(buf)\n\t\t\t}\n\t\t}\n\t}(log)\n\n\tcollection.chainRemoveToxic(ctx, toxics[0])\n\tcollection.chainRemoveToxic(ctx, toxics[1])\n}\n"
  },
  {
    "path": "metrics.go",
    "content": "package toxiproxy\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/Shopify/toxiproxy/v2/collectors\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n)\n\n// NewMetricsContainer initializes a container for storing all prometheus metrics.\nfunc NewMetricsContainer(registry *prometheus.Registry) *metricsContainer {\n\tif registry == nil {\n\t\tregistry = prometheus.NewRegistry()\n\t}\n\treturn &metricsContainer{\n\t\tregistry: registry,\n\t}\n}\n\ntype metricsContainer struct {\n\tRuntimeMetrics *collectors.RuntimeMetricCollectors\n\tProxyMetrics   *collectors.ProxyMetricCollectors\n\n\tregistry *prometheus.Registry\n}\n\nfunc (m *metricsContainer) runtimeMetricsEnabled() bool {\n\treturn m.RuntimeMetrics != nil\n}\n\nfunc (m *metricsContainer) proxyMetricsEnabled() bool {\n\treturn m.ProxyMetrics != nil\n}\n\n// anyMetricsEnabled determines whether we have any prometheus metrics registered for exporting.\nfunc (m *metricsContainer) anyMetricsEnabled() bool {\n\treturn m.runtimeMetricsEnabled() || m.proxyMetricsEnabled()\n}\n\n// handler returns an HTTP handler with the necessary collectors registered\n// via a global prometheus registry.\nfunc (m *metricsContainer) handler() http.Handler {\n\tif m.runtimeMetricsEnabled() {\n\t\tm.registry.MustRegister(m.RuntimeMetrics.Collectors()...)\n\t}\n\tif m.proxyMetricsEnabled() {\n\t\tm.registry.MustRegister(m.ProxyMetrics.Collectors()...)\n\t}\n\treturn promhttp.HandlerFor(\n\t\tm.registry, promhttp.HandlerOpts{Registry: m.registry})\n}\n"
  },
  {
    "path": "metrics_test.go",
    "content": "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\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/rs/zerolog\"\n\n\t\"github.com/Shopify/toxiproxy/v2/collectors\"\n\t\"github.com/Shopify/toxiproxy/v2/stream\"\n)\n\nfunc TestProxyMetricsReceivedSentBytes(t *testing.T) {\n\tsrv := NewServer(NewMetricsContainer(prometheus.NewRegistry()), zerolog.Nop())\n\tsrv.Metrics.ProxyMetrics = collectors.NewProxyMetricCollectors()\n\n\tproxy := NewProxy(srv, \"test_proxy_metrics_received_sent_bytes\", \"localhost:0\", \"upstream\")\n\n\tr := bufio.NewReader(bytes.NewBufferString(\"hello\"))\n\tw := &testWriteCloser{\n\t\tbufio.NewWriter(bytes.NewBuffer([]byte{})),\n\t}\n\tlinkName := \"testupstream\"\n\tproxy.Toxics.StartLink(srv, linkName, r, w, stream.Upstream)\n\tproxy.Toxics.RemoveLink(linkName)\n\n\tactual := prometheusOutput(t, srv, \"toxiproxy_proxy\")\n\n\texpected := []string{\n\t\t`toxiproxy_proxy_received_bytes_total{` +\n\t\t\t`direction=\"upstream\",listener=\"localhost:0\",` +\n\t\t\t`proxy=\"test_proxy_metrics_received_sent_bytes\",upstream=\"upstream\"` +\n\t\t\t`} 5`,\n\n\t\t`toxiproxy_proxy_sent_bytes_total{` +\n\t\t\t`direction=\"upstream\",listener=\"localhost:0\",` +\n\t\t\t`proxy=\"test_proxy_metrics_received_sent_bytes\",upstream=\"upstream\"` +\n\t\t\t`} 5`,\n\t}\n\n\tif !reflect.DeepEqual(actual, expected) {\n\t\tt.Fatalf(\n\t\t\t\"\\nexpected:\\n  [%v]\\ngot:\\n  [%v]\",\n\t\t\tstrings.Join(expected, \"\\n  \"),\n\t\t\tstrings.Join(actual, \"\\n  \"),\n\t\t)\n\t}\n}\n\nfunc TestRuntimeMetricsBuildInfo(t *testing.T) {\n\tsrv := NewServer(NewMetricsContainer(prometheus.NewRegistry()), zerolog.Nop())\n\tsrv.Metrics.RuntimeMetrics = collectors.NewRuntimeMetricCollectors()\n\n\texpected := `go_build_info{checksum=\"[^\"]*\",path=\"[^\"]*\",version=\"[^\"]*\"} 1`\n\n\tactual := prometheusOutput(t, srv, \"go_build_info\")\n\n\tif len(actual) != 1 {\n\t\tt.Fatalf(\n\t\t\t\"\\nexpected: 1 item\\ngot: %d item(s)\\nmetrics:\\n  %+v\",\n\t\t\tlen(actual),\n\t\t\tstrings.Join(actual, \"\\n  \"),\n\t\t)\n\t}\n\n\tmatched, err := regexp.MatchString(expected, actual[0])\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n\tif !matched {\n\t\tt.Fatalf(\"\\nexpected:\\n  %v\\nto match:\\n  %v\", actual[0], expected)\n\t}\n}\n\ntype testWriteCloser struct {\n\t*bufio.Writer\n}\n\nfunc (t *testWriteCloser) Close() error {\n\treturn t.Flush()\n}\n\nfunc prometheusOutput(t *testing.T, apiServer *ApiServer, prefix string) []string {\n\tt.Helper()\n\n\ttestServer := httptest.NewServer(apiServer.Metrics.handler())\n\tdefer testServer.Close()\n\n\tresp, err := http.Get(testServer.URL)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer resp.Body.Close()\n\n\tvar selected []string\n\ts := bufio.NewScanner(resp.Body)\n\tfor s.Scan() {\n\t\tif strings.HasPrefix(s.Text(), prefix) {\n\t\t\tselected = append(selected, s.Text())\n\t\t}\n\t}\n\treturn selected\n}\n"
  },
  {
    "path": "proxy.go",
    "content": "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/Shopify/toxiproxy/v2/stream\"\n)\n\n// Proxy represents the proxy in its entirety with all its links. The main\n// responsibility of Proxy is to accept new client and create Links between the\n// client and upstream.\n//\n// Client <-> toxiproxy <-> Upstream.\ntype Proxy struct {\n\tsync.Mutex\n\n\tName     string `json:\"name\"`\n\tListen   string `json:\"listen\"`\n\tUpstream string `json:\"upstream\"`\n\tEnabled  bool   `json:\"enabled\"`\n\n\tlistener net.Listener\n\tstarted  chan error\n\n\ttomb        tomb.Tomb\n\tconnections ConnectionList\n\tToxics      *ToxicCollection `json:\"-\"`\n\tapiServer   *ApiServer\n\tLogger      *zerolog.Logger\n}\n\ntype ConnectionList struct {\n\tlist map[string]net.Conn\n\tlock sync.Mutex\n}\n\nfunc (c *ConnectionList) Lock() {\n\tc.lock.Lock()\n}\n\nfunc (c *ConnectionList) Unlock() {\n\tc.lock.Unlock()\n}\n\nvar ErrProxyAlreadyStarted = errors.New(\"Proxy already started\")\n\nfunc NewProxy(server *ApiServer, name, listen, upstream string) *Proxy {\n\tl := server.Logger.\n\t\tWith().\n\t\tStr(\"name\", name).\n\t\tStr(\"listen\", listen).\n\t\tStr(\"upstream\", upstream).\n\t\tLogger()\n\n\tproxy := &Proxy{\n\t\tName:        name,\n\t\tListen:      listen,\n\t\tUpstream:    upstream,\n\t\tstarted:     make(chan error),\n\t\tconnections: ConnectionList{list: make(map[string]net.Conn)},\n\t\tapiServer:   server,\n\t\tLogger:      &l,\n\t}\n\tproxy.Toxics = NewToxicCollection(proxy)\n\treturn proxy\n}\n\nfunc (proxy *Proxy) Start() error {\n\tproxy.Lock()\n\tdefer proxy.Unlock()\n\n\treturn start(proxy)\n}\n\nfunc (proxy *Proxy) Update(input *Proxy) error {\n\tproxy.Lock()\n\tdefer proxy.Unlock()\n\n\tdiffers, err := proxy.Differs(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif differs {\n\t\tstop(proxy)\n\t\tproxy.Listen = input.Listen\n\t\tproxy.Upstream = input.Upstream\n\t}\n\n\tif input.Enabled != proxy.Enabled {\n\t\tif input.Enabled {\n\t\t\treturn start(proxy)\n\t\t}\n\t\tstop(proxy)\n\t}\n\treturn nil\n}\n\nfunc (proxy *Proxy) Stop() {\n\tproxy.Lock()\n\tdefer proxy.Unlock()\n\n\tstop(proxy)\n}\n\nfunc (proxy *Proxy) listen() error {\n\tvar err error\n\tproxy.listener, err = net.Listen(\"tcp\", proxy.Listen)\n\tif err != nil {\n\t\tproxy.started <- err\n\t\treturn err\n\t}\n\tproxy.Listen = proxy.listener.Addr().String()\n\tproxy.started <- nil\n\n\tproxy.Logger.\n\t\tInfo().\n\t\tMsg(\"Started proxy\")\n\n\treturn nil\n}\n\nfunc (proxy *Proxy) close() {\n\t// Unblock proxy.listener.Accept()\n\terr := proxy.listener.Close()\n\tif err != nil {\n\t\tproxy.Logger.\n\t\t\tWarn().\n\t\t\tErr(err).\n\t\t\tMsg(\"Attempted to close an already closed proxy server\")\n\t}\n}\n\nfunc (proxy *Proxy) Differs(other *Proxy) (bool, error) {\n\tnewResolvedListen, err := net.ResolveTCPAddr(\"tcp\", other.Listen)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif proxy.Listen != newResolvedListen.String() || proxy.Upstream != other.Upstream {\n\t\treturn true, nil\n\t}\n\n\treturn false, nil\n}\n\n// This channel is to kill the blocking Accept() call below by closing the\n// net.Listener.\nfunc (proxy *Proxy) freeBlocker(acceptTomb *tomb.Tomb) {\n\t<-proxy.tomb.Dying()\n\n\t// Notify ln.Accept() that the shutdown was safe\n\tacceptTomb.Killf(\"Shutting down from stop()\")\n\n\tproxy.close()\n\n\t// Wait for the accept loop to finish processing\n\tacceptTomb.Wait()\n\tproxy.tomb.Done()\n}\n\n// server runs the Proxy server, accepting new clients and creating Links to\n// connect them to upstreams.\nfunc (proxy *Proxy) server() {\n\terr := proxy.listen()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tacceptTomb := &tomb.Tomb{}\n\tdefer acceptTomb.Done()\n\n\t// This channel is to kill the blocking Accept() call below by closing the\n\t// net.Listener.\n\tgo proxy.freeBlocker(acceptTomb)\n\n\tfor {\n\t\tclient, err := proxy.listener.Accept()\n\t\tif err != nil {\n\t\t\t// This is to confirm we're being shut down in a legit way. Unfortunately,\n\t\t\t// Go doesn't export the error when it's closed from Close() so we have to\n\t\t\t// sync up with a channel here.\n\t\t\t//\n\t\t\t// See http://zhen.org/blog/graceful-shutdown-of-go-net-dot-listeners/\n\t\t\tselect {\n\t\t\tcase <-acceptTomb.Dying():\n\t\t\tdefault:\n\t\t\t\tproxy.Logger.\n\t\t\t\t\tWarn().\n\t\t\t\t\tErr(err).\n\t\t\t\t\tMsg(\"Error while accepting client\")\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tproxy.Logger.\n\t\t\tInfo().\n\t\t\tStr(\"client\", client.RemoteAddr().String()).\n\t\t\tMsg(\"Accepted client\")\n\n\t\tupstream, err := net.Dial(\"tcp\", proxy.Upstream)\n\t\tif err != nil {\n\t\t\tproxy.Logger.\n\t\t\t\tErr(err).\n\t\t\t\tStr(\"client\", client.RemoteAddr().String()).\n\t\t\t\tMsg(\"Unable to open connection to upstream\")\n\t\t\tclient.Close()\n\t\t\tcontinue\n\t\t}\n\n\t\tname := client.RemoteAddr().String()\n\t\tproxy.connections.Lock()\n\t\tproxy.connections.list[name+\"upstream\"] = upstream\n\t\tproxy.connections.list[name+\"downstream\"] = client\n\t\tproxy.connections.Unlock()\n\t\tproxy.Toxics.StartLink(proxy.apiServer, name+\"upstream\", client, upstream, stream.Upstream)\n\t\tproxy.Toxics.StartLink(proxy.apiServer, name+\"downstream\", upstream, client, stream.Downstream)\n\t}\n}\n\nfunc (proxy *Proxy) RemoveConnection(name string) {\n\tproxy.connections.Lock()\n\tdefer proxy.connections.Unlock()\n\tdelete(proxy.connections.list, name)\n}\n\n// Starts a proxy, assumes the lock has already been taken.\nfunc start(proxy *Proxy) error {\n\tif proxy.Enabled {\n\t\treturn ErrProxyAlreadyStarted\n\t}\n\n\tproxy.tomb = tomb.Tomb{} // Reset tomb, from previous starts/stops\n\tgo proxy.server()\n\terr := <-proxy.started\n\t// Only enable the proxy if it successfully started\n\tproxy.Enabled = err == nil\n\treturn err\n}\n\n// Stops a proxy, assumes the lock has already been taken.\nfunc stop(proxy *Proxy) {\n\tif !proxy.Enabled {\n\t\treturn\n\t}\n\tproxy.Enabled = false\n\n\tproxy.tomb.Killf(\"Shutting down from stop()\")\n\tproxy.tomb.Wait() // Wait until we stop accepting new connections\n\n\tproxy.connections.Lock()\n\tdefer proxy.connections.Unlock()\n\tfor _, conn := range proxy.connections.list {\n\t\tconn.Close()\n\t}\n\n\tproxy.Logger.\n\t\tInfo().\n\t\tMsg(\"Terminated proxy\")\n}\n"
  },
  {
    "path": "proxy_collection.go",
    "content": "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 the interface for anything\n// to add and remove proxies from the toxiproxy instance. It's responsibility is\n// to maintain the integrity of the proxy set, by guarding for things such as\n// duplicate names.\ntype ProxyCollection struct {\n\tsync.RWMutex\n\n\tproxies map[string]*Proxy\n}\n\nfunc NewProxyCollection() *ProxyCollection {\n\treturn &ProxyCollection{\n\t\tproxies: make(map[string]*Proxy),\n\t}\n}\n\nfunc (collection *ProxyCollection) Add(proxy *Proxy, start bool) error {\n\tcollection.Lock()\n\tdefer collection.Unlock()\n\n\tif _, exists := collection.proxies[proxy.Name]; exists {\n\t\treturn ErrProxyAlreadyExists\n\t}\n\n\tif start {\n\t\terr := proxy.Start()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tcollection.proxies[proxy.Name] = proxy\n\n\treturn nil\n}\n\nfunc (collection *ProxyCollection) AddOrReplace(proxy *Proxy, start bool) (*Proxy, error) {\n\tcollection.Lock()\n\tdefer collection.Unlock()\n\n\tif existing, exists := collection.proxies[proxy.Name]; exists {\n\t\tdiffers, err := existing.Differs(proxy)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif !differs {\n\t\t\treturn existing, nil\n\t\t}\n\t\texisting.Stop()\n\t}\n\n\tif start {\n\t\terr := proxy.Start()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tcollection.proxies[proxy.Name] = proxy\n\n\treturn proxy, nil\n}\n\nfunc (collection *ProxyCollection) PopulateJson(\n\tserver *ApiServer,\n\tdata io.Reader,\n) ([]*Proxy, error) {\n\tinput := []struct {\n\t\tProxy\n\t\tEnabled *bool `json:\"enabled\"` // Overrides Proxy field to make field nullable\n\t}{}\n\n\terr := json.NewDecoder(data).Decode(&input)\n\tif err != nil {\n\t\treturn nil, joinError(err, ErrBadRequestBody)\n\t}\n\n\t// Check for valid input before creating any proxies\n\tt := true\n\tfor i := range input {\n\t\tif len(input[i].Name) < 1 {\n\t\t\treturn nil, joinError(fmt.Errorf(\"name at proxy %d\", i+1), ErrMissingField)\n\t\t}\n\t\tif len(input[i].Upstream) < 1 {\n\t\t\treturn nil, joinError(fmt.Errorf(\"upstream at proxy %d\", i+1), ErrMissingField)\n\t\t}\n\t\tif input[i].Enabled == nil {\n\t\t\tinput[i].Enabled = &t\n\t\t}\n\t}\n\n\tproxies := make([]*Proxy, 0, len(input))\n\n\tfor i := range input {\n\t\tproxy := NewProxy(server, input[i].Name, input[i].Listen, input[i].Upstream)\n\t\taddedOrReplaced, err := collection.AddOrReplace(proxy, *input[i].Enabled)\n\t\tif err != nil {\n\t\t\treturn proxies, err\n\t\t}\n\n\t\tproxies = append(proxies, addedOrReplaced)\n\t}\n\treturn proxies, err\n}\n\nfunc (collection *ProxyCollection) Proxies() map[string]*Proxy {\n\tcollection.RLock()\n\tdefer collection.RUnlock()\n\n\t// Copy the map since using the existing one isn't thread-safe\n\tproxies := make(map[string]*Proxy, len(collection.proxies))\n\tfor k, v := range collection.proxies {\n\t\tproxies[k] = v\n\t}\n\treturn proxies\n}\n\nfunc (collection *ProxyCollection) Get(name string) (*Proxy, error) {\n\tcollection.RLock()\n\tdefer collection.RUnlock()\n\n\treturn collection.getByName(name)\n}\n\nfunc (collection *ProxyCollection) Remove(name string) error {\n\tcollection.Lock()\n\tdefer collection.Unlock()\n\n\tproxy, err := collection.getByName(name)\n\tif err != nil {\n\t\treturn err\n\t}\n\tproxy.Stop()\n\n\tdelete(collection.proxies, proxy.Name)\n\treturn nil\n}\n\nfunc (collection *ProxyCollection) Clear() error {\n\tcollection.Lock()\n\tdefer collection.Unlock()\n\n\tfor _, proxy := range collection.proxies {\n\t\tproxy.Stop()\n\n\t\tdelete(collection.proxies, proxy.Name)\n\t}\n\n\treturn nil\n}\n\n// getByName returns a proxy by its name. Its used from #remove and #get.\n// It assumes the lock has already been acquired.\nfunc (collection *ProxyCollection) getByName(name string) (*Proxy, error) {\n\tproxy, exists := collection.proxies[name]\n\tif !exists {\n\t\treturn nil, ErrProxyNotFound\n\t}\n\treturn proxy, nil\n}\n"
  },
  {
    "path": "proxy_collection_test.go",
    "content": "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 TestAddProxyToCollection(t *testing.T) {\n\tcollection := toxiproxy.NewProxyCollection()\n\tproxy := NewTestProxy(\"test\", \"localhost:20000\")\n\n\tif _, err := collection.Get(proxy.Name); err == nil {\n\t\tt.Error(\"Expected proxies to be empty\")\n\t}\n\n\terr := collection.Add(proxy, false)\n\tif err != nil {\n\t\tt.Error(\"Expected to be able to add first proxy to collection\")\n\t}\n\n\tif _, err := collection.Get(proxy.Name); err != nil {\n\t\tt.Error(\"Expected proxy to be added to map\")\n\t}\n}\n\nfunc TestAddTwoProxiesToCollection(t *testing.T) {\n\tcollection := toxiproxy.NewProxyCollection()\n\tproxy := NewTestProxy(\"test\", \"localhost:20000\")\n\n\terr := collection.Add(proxy, false)\n\tif err != nil {\n\t\tt.Error(\"Expected to be able to add first proxy to collection\")\n\t}\n\n\terr = collection.Add(proxy, false)\n\tif err == nil {\n\t\tt.Error(\"Expected to not be able to add proxy with same name\")\n\t}\n}\n\nfunc TestListProxies(t *testing.T) {\n\tcollection := toxiproxy.NewProxyCollection()\n\tproxy := NewTestProxy(\"test\", \"localhost:20000\")\n\n\terr := collection.Add(proxy, false)\n\tif err != nil {\n\t\tt.Error(\"Expected to be able to add first proxy to collection\")\n\t}\n\n\tproxies := collection.Proxies()\n\tproxy, ok := proxies[proxy.Name]\n\tif !ok {\n\t\tt.Error(\"Expected to be able to see existing proxy\")\n\t} else if proxy.Enabled {\n\t\tt.Error(\"Expected proxy not to be running\")\n\t}\n}\n\nfunc TestAddProxyAndStart(t *testing.T) {\n\tcollection := toxiproxy.NewProxyCollection()\n\tproxy := NewTestProxy(\"test\", \"localhost:20000\")\n\n\terr := collection.Add(proxy, true)\n\tif err != nil {\n\t\tt.Error(\"Expected to be able to add proxy to collection:\", err)\n\t}\n\n\tproxies := collection.Proxies()\n\tproxy, ok := proxies[proxy.Name]\n\tif !ok {\n\t\tt.Error(\"Expected to be able to see existing proxy\")\n\t} else if !proxy.Enabled {\n\t\tt.Error(\"Expected proxy to be running\")\n\t}\n}\n\nfunc TestAddAndRemoveProxyFromCollection(t *testing.T) {\n\tWithTCPProxy(t, func(conn net.Conn, response chan []byte, proxy *toxiproxy.Proxy) {\n\t\tcollection := toxiproxy.NewProxyCollection()\n\n\t\tif _, err := collection.Get(proxy.Name); err == nil {\n\t\t\tt.Error(\"Expected proxies to be empty\")\n\t\t}\n\n\t\terr := collection.Add(proxy, false)\n\t\tif err != nil {\n\t\t\tt.Error(\"Expected to be able to add first proxy to collection\")\n\t\t}\n\n\t\tif _, err := collection.Get(proxy.Name); err != nil {\n\t\t\tt.Error(\"Expected proxy to be added to map\")\n\t\t}\n\n\t\tmsg := []byte(\"go away\")\n\n\t\t_, err = conn.Write(msg)\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed writing to socket to shut down server\")\n\t\t}\n\n\t\tconn.Close()\n\n\t\tresp := <-response\n\t\tif !bytes.Equal(resp, msg) {\n\t\t\tt.Error(\"Server didn't read bytes from client\")\n\t\t}\n\n\t\terr = collection.Remove(proxy.Name)\n\t\tif err != nil {\n\t\t\tt.Error(\"Expected to remove proxy from collection\")\n\t\t}\n\n\t\tif _, err := collection.Get(proxy.Name); err == nil {\n\t\t\tt.Error(\"Expected proxies to be empty\")\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "proxy_test.go",
    "content": "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/Shopify/toxiproxy/v2\"\n\t\"github.com/Shopify/toxiproxy/v2/testhelper\"\n)\n\nfunc TestProxySimpleMessage(t *testing.T) {\n\tWithTCPProxy(t, func(conn net.Conn, response chan []byte, proxy *toxiproxy.Proxy) {\n\t\tmsg := []byte(\"hello world\")\n\n\t\t_, err := conn.Write(msg)\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed writing to TCP server\", err)\n\t\t}\n\n\t\terr = conn.Close()\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed to close TCP connection\", err)\n\t\t}\n\n\t\tresp := <-response\n\t\tif !bytes.Equal(resp, msg) {\n\t\t\tt.Error(\"Server didn't read correct bytes from client\", resp)\n\t\t}\n\t})\n}\n\nfunc TestProxyToDownUpstream(t *testing.T) {\n\tproxy := NewTestProxy(\"test\", \"localhost:20009\")\n\tproxy.Start()\n\n\tconn := AssertProxyUp(t, proxy.Listen, true)\n\t// Check to make sure the connection is closed\n\tconn.SetReadDeadline(time.Now().Add(500 * time.Millisecond))\n\t_, err := conn.Read(make([]byte, 1))\n\tif err != io.EOF {\n\t\tt.Error(\"Proxy did not close connection when upstream down\", err)\n\t}\n\n\tproxy.Stop()\n}\n\nfunc TestProxyBigMessage(t *testing.T) {\n\tWithTCPProxy(t, func(conn net.Conn, response chan []byte, proxy *toxiproxy.Proxy) {\n\t\tbuf := make([]byte, 32*1024)\n\t\tmsg := make([]byte, len(buf)*2)\n\t\thex.Encode(msg, buf)\n\n\t\t_, err := conn.Write(msg)\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed writing to TCP server\", err)\n\t\t}\n\n\t\terr = conn.Close()\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed to close TCP connection\", err)\n\t\t}\n\n\t\tresp := <-response\n\t\tif !bytes.Equal(resp, msg) {\n\t\t\tt.Error(\"Server didn't read correct bytes from client\", resp)\n\t\t}\n\t})\n}\n\nfunc TestProxyTwoPartMessage(t *testing.T) {\n\tWithTCPProxy(t, func(conn net.Conn, response chan []byte, proxy *toxiproxy.Proxy) {\n\t\tmsg1 := []byte(\"hello world\")\n\t\tmsg2 := []byte(\"hello world\")\n\n\t\t_, err := conn.Write(msg1)\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed writing to TCP server\", err)\n\t\t}\n\n\t\t_, err = conn.Write(msg2)\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed writing to TCP server\", err)\n\t\t}\n\n\t\terr = conn.Close()\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed to close TCP connection\", err)\n\t\t}\n\n\t\tmsg1 = append(msg1, msg2...)\n\n\t\tresp := <-response\n\t\tif !bytes.Equal(resp, msg1) {\n\t\t\tt.Error(\"Server didn't read correct bytes from client\", resp)\n\t\t}\n\t})\n}\n\nfunc TestClosingProxyMultipleTimes(t *testing.T) {\n\tWithTCPProxy(t, func(conn net.Conn, response chan []byte, proxy *toxiproxy.Proxy) {\n\t\tproxy.Stop()\n\t\tproxy.Stop()\n\t\tproxy.Stop()\n\t})\n}\n\nfunc TestStartTwoProxiesOnSameAddress(t *testing.T) {\n\tWithTCPProxy(t, func(conn net.Conn, response chan []byte, proxy *toxiproxy.Proxy) {\n\t\tproxy2 := NewTestProxy(\"proxy_2\", \"localhost:3306\")\n\t\tproxy2.Listen = proxy.Listen\n\t\tif err := proxy2.Start(); err == nil {\n\t\t\tt.Fatal(\"Expected an err back from start\")\n\t\t}\n\t})\n}\n\nfunc TestStopProxyBeforeStarting(t *testing.T) {\n\ttesthelper.WithTCPServer(t, func(upstream string, response chan []byte) {\n\t\tproxy := NewTestProxy(\"test\", upstream)\n\t\tAssertProxyUp(t, proxy.Listen, false)\n\n\t\tproxy.Stop()\n\t\terr := proxy.Start()\n\t\tif err != nil {\n\t\t\tt.Error(\"Proxy failed to start\", err)\n\t\t}\n\n\t\terr = proxy.Start()\n\t\tif err != toxiproxy.ErrProxyAlreadyStarted {\n\t\t\tt.Error(\"Proxy did not fail to start when already started\", err)\n\t\t}\n\t\tAssertProxyUp(t, proxy.Listen, true)\n\n\t\tproxy.Stop()\n\t\tAssertProxyUp(t, proxy.Listen, false)\n\t})\n}\n\nfunc TestProxyUpdate(t *testing.T) {\n\ttesthelper.WithTCPServer(t, func(upstream string, response chan []byte) {\n\t\tproxy := NewTestProxy(\"test\", upstream)\n\t\terr := proxy.Start()\n\t\tif err != nil {\n\t\t\tt.Error(\"Proxy failed to start\", err)\n\t\t}\n\t\tAssertProxyUp(t, proxy.Listen, true)\n\n\t\tbefore := proxy.Listen\n\n\t\tinput := &toxiproxy.Proxy{Listen: \"localhost:0\", Upstream: proxy.Upstream, Enabled: true}\n\t\terr = proxy.Update(input)\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed to update proxy\", err)\n\t\t}\n\t\tif proxy.Listen == before || proxy.Listen == input.Listen {\n\t\t\tt.Errorf(\"Proxy update didn't change listen address: %s to %s\", before, proxy.Listen)\n\t\t}\n\t\tAssertProxyUp(t, proxy.Listen, true)\n\n\t\tinput.Listen = proxy.Listen\n\t\terr = proxy.Update(input)\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed to update proxy\", err)\n\t\t}\n\t\tAssertProxyUp(t, proxy.Listen, true)\n\n\t\tinput.Enabled = false\n\t\terr = proxy.Update(input)\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed to update proxy\", err)\n\t\t}\n\t\tAssertProxyUp(t, proxy.Listen, false)\n\t})\n}\n\nfunc TestProxyUpdateWithHostname(t *testing.T) {\n\ttesthelper.WithTCPServer(t, func(upstream string, response chan []byte) {\n\t\tproxy := NewTestProxy(\"test\", upstream)\n\t\terr := proxy.Start()\n\t\tif err != nil {\n\t\t\tt.Error(\"Proxy failed to start\", err)\n\t\t}\n\t\tAssertProxyUp(t, proxy.Listen, true)\n\n\t\tconnectionLost := make(chan bool)\n\n\t\t// Start a goroutine to check if connection is maintained\n\t\tgo func() {\n\t\t\tconn, err := net.Dial(\"tcp\", proxy.Listen)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(\"Failed to connect to proxy\", err)\n\t\t\t}\n\t\t\tdefer conn.Close()\n\n\t\t\t// Try to read from the connection\n\t\t\tbuf := make([]byte, 1024)\n\t\t\tconn.SetReadDeadline(time.Now().Add(500 * time.Millisecond))\n\t\t\t_, err = conn.Read(buf)\n\t\t\tif err != nil && !errors.Is(err, os.ErrDeadlineExceeded) {\n\t\t\t\tconnectionLost <- true\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconnectionLost <- false\n\t\t}()\n\n\t\t_, port, err := net.SplitHostPort(proxy.Listen)\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed to split host and port\", err)\n\t\t}\n\n\t\tinput := &toxiproxy.Proxy{\n\t\t\tListen:   net.JoinHostPort(\"localhost\", port),\n\t\t\tUpstream: proxy.Upstream,\n\t\t\tEnabled:  true,\n\t\t}\n\t\terr = proxy.Update(input)\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed to update proxy\", err)\n\t\t}\n\n\t\t// Check if the connection was lost during the update\n\t\tif lost := <-connectionLost; lost {\n\t\t\tt.Error(\"Connection was lost during proxy update\")\n\t\t}\n\n\t\t// Verify proxy is still up after the update\n\t\tAssertProxyUp(t, proxy.Listen, true)\n\t})\n}\n\nfunc TestRestartFailedToStartProxy(t *testing.T) {\n\ttesthelper.WithTCPServer(t, func(upstream string, response chan []byte) {\n\t\tproxy := NewTestProxy(\"test\", upstream)\n\t\tconflict := NewTestProxy(\"test2\", upstream)\n\n\t\terr := conflict.Start()\n\t\tif err != nil {\n\t\t\tt.Error(\"Proxy failed to start\", err)\n\t\t}\n\t\tAssertProxyUp(t, conflict.Listen, true)\n\n\t\tproxy.Listen = conflict.Listen\n\t\terr = proxy.Start()\n\t\tif err == nil || err == toxiproxy.ErrProxyAlreadyStarted {\n\t\t\tt.Error(\"Proxy started when it should have conflicted\")\n\t\t}\n\n\t\tconflict.Stop()\n\t\tAssertProxyUp(t, conflict.Listen, false)\n\n\t\terr = proxy.Start()\n\t\tif err != nil {\n\t\t\tt.Error(\"Proxy failed to start after conflict went away\", err)\n\t\t}\n\t\tAssertProxyUp(t, proxy.Listen, true)\n\n\t\tproxy.Stop()\n\t\tAssertProxyUp(t, proxy.Listen, false)\n\t})\n}\n\nfunc TestProxyDiffers(t *testing.T) {\n\ttesthelper.WithTCPServer(t, func(upstream string, response chan []byte) {\n\t\tproxy := NewTestProxy(\"test\", upstream)\n\t\tproxy.Start()\n\t\t_, port, err := net.SplitHostPort(proxy.Listen)\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed to split host and port\", err)\n\t\t}\n\t\totherProxy := &toxiproxy.Proxy{\n\t\t\tName:     \"other\",\n\t\t\tListen:   net.JoinHostPort(\"localhost\", port),\n\t\t\tUpstream: upstream,\n\t\t\tEnabled:  true,\n\t\t}\n\n\t\tdiffers, err := proxy.Differs(otherProxy)\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed to check if proxy differs\", err)\n\t\t}\n\t\tif differs {\n\t\t\tt.Error(\"Proxy should not differ \")\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "scripts/hazelcast.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<hazelcast xmlns=\"http://www.hazelcast.com/schema/config\"\n           xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n           xsi:schemaLocation=\"http://www.hazelcast.com/schema/config\n           http://www.hazelcast.com/schema/config/hazelcast-config-5.1.xsd\">\n\n    <properties>\n        <property name=\"hazelcast.merge.next.run.delay.seconds\">15</property>\n        <property name=\"hazelcast.merge.first.run.delay.seconds\">20</property>\n        <property name=\"hazelcast.partition.migration.chunks.enabled\">false</property>\n        <property name=\"hazelcast.heartbeat.failuredetector.type\">deadline</property>\n        <property name=\"hazelcast.heartbeat.interval.seconds\">3</property>\n        <property name=\"hazelcast.max.no.heartbeat.seconds\">10</property>\n    </properties>\n\n    <network>\n        <public-address>member-proxy:${proxyPort}</public-address>\n        <port auto-increment=\"false\">5701</port>\n        <join>\n            <auto-detection enabled=\"false\"/>\n            <tcp-ip enabled=\"true\">\n                <member-list>\n                    <member>member-proxy:${proxyPort0}</member>\n                    <member>member-proxy:${proxyPort1}</member>\n                    <member>member-proxy:${proxyPort2}</member>\n                </member-list>\n            </tcp-ip>\n        </join>\n    </network>\n</hazelcast>\n"
  },
  {
    "path": "scripts/test-e2e",
    "content": "#!/usr/bin/env bash\n\nset -ueo pipefail\n\ncd \"$(dirname \"$0\")\"\n\nserver=\"../dist/toxiproxy-server\"\nstate=\"started\"\n\nbenchmark() {\n  go test -bench=.. ../test/e2e -v\n}\n\ncli() {\n  ../dist/toxiproxy-cli \"$@\" 2>&1 | sed -e 's/^/[client] /'\n}\n\nwait_for_url() {\n  curl -s --retry-connrefused --retry 5 --retry-delay 2 --retry-max-time 30 \\\n       --max-time 1 -L -I -X GET \"${1}\"\n}\n\n# Stop all background jobs on exit\nfunction cleanup() {\n  echo -e \"\\n\\n== Teardown: state=${state}\"\n  pkill -15 -f \"toxiproxy-server -proxy-metrics -runtime-metrics$\"\n  pkill -15 -f \"exe/endpoint$\"\n}\ntrap \"cleanup\" EXIT SIGINT SIGTERM\n\necho \"= Toxiproxy E2E tests\"\necho\necho \"== Setup\"\necho\necho \"=== Starting Web service\"\n\npkill -15 \"toxiproxy-server\" || true\npkill -15 -f \"exe/endpoint$\" || true\n\ngo run ../test/e2e/endpoint.go 2>&1 | sed -e 's/^/[web] /' &\n\necho \"=== Starting Toxiproxy\"\n\nLOG_LEVEL=trace $server -proxy-metrics -runtime-metrics 2>&1 | sed -e 's/^/[toxiproxy] /' &\n\necho \"=== Wait when services are available\"\n\nwait_for_url http://localhost:20002/test2\nwait_for_url http://localhost:8474/version\n\necho \"=== Test client to manipulate proxy\"\n\ncli -h http://localhost:8474 \\\n                     create -l localhost:20000 -u localhost:20002 shopify_http\ncli list\ncli toggle shopify_http\ncli inspect shopify_http\ncli toggle shopify_http\n\necho -e \"-----------------\\n\"\n\necho \"== Benchmarking\"\necho\necho \"=== Without toxics\"\n\nbenchmark\n\necho -e \"-----------------\\n\"\n\necho \"=== Latency toxic downstream\"\n\ncli toxic add --downstream \\\n              --type=latency \\\n              --toxicName=\"latency_downstream\" \\\n              --attribute=\"latency=1000\" \\\n              --attribute=\"jitter=50\" \\\n              --toxicity=0.99 \\\n              shopify_http\ncli inspect shopify_http\n\nbenchmark\n\ncli toxic update --toxicName=\"latency_downstream\" \\\n                 --attribute=\"jitter=20\" \\\n                 --toxicity=0.7 \\\n                 shopify_http\ncli inspect shopify_http\n\ncli toxic delete --toxicName=\"latency_downstream\" shopify_http\n\necho -e \"-----------------\\n\"\n\necho \"=== Latency toxic upstream\"\n\ncli toxic add --upstream \\\n              --type=latency \\\n              --toxicName=\"latency_upstream\" \\\n              --attribute=\"latency=1000\" \\\n              --attribute=\"jitter=50\" \\\n              --toxicity=1 \\\n              shopify_http\ncli inspect shopify_http\n\nbenchmark\n\ncli toxic update --toxicName=\"latency_upstream\" \\\n                 --attribute=\"jitter=20\" \\\n                 --toxicity=0.3 \\\n                 shopify_http\ncli inspect shopify_http\n\ncli toxic delete --toxicName=\"latency_upstream\" shopify_http\n\necho -e \"-----------------\\n\"\n\necho \"=== Bandwidth toxic\"\n\ncli toxic add --type=bandwidth \\\n              --toxicName=\"bandwidth_kb_per_second\" \\\n              --attribute=\"rate=1\" \\\n              --toxicity=0.5 \\\n              shopify_http\ncli toxic update --toxicName=\"bandwidth_kb_per_second\" \\\n                 --attribute=\"rate=10\" \\\n                 --toxicity=1.0 \\\n                 shopify_http\n\nbenchmark\n\ncli toxic delete --toxicName=\"bandwidth_kb_per_second\" \\\n                                  shopify_http\n\necho -e \"-----------------\\n\"\n\necho \"=== Timeout toxic\"\n\ncli toxic add --type=timeout \\\n              --toxicName=\"timeout_ms\" \\\n              --attribute=\"timeout=10\" \\\n              --toxicity=0.1 \\\n              shopify_http\ncli toxic delete --toxicName=\"timeout_ms\" shopify_http\n\necho -e \"-----------------\\n\"\n\necho \"=== Slicer toxic\"\n\ncli toxic add --type=slicer \\\n               --toxicName=\"slicer_us\" \\\n               --attribute=\"average_size=64\" \\\n               --attribute=\"size_variation=32\" \\\n               --attribute=\"delay=10\" \\\n               --toxicity=1.0 \\\n               shopify_http\nbenchmark\ncli toxic delete --toxicName=\"slicer_us\" shopify_http\n\necho -e \"-----------------\\n\"\n\necho \"=== Reset peer toxic\"\n\ncli toxic add --type=reset_peer \\\n              --toxicName=\"reset_peer\" \\\n              --attribute=\"timeout=2000\" \\\n              --toxicity=1.0 \\\n              shopify_http\ncli inspect shopify_http\ncli toxic delete --toxicName=\"reset_peer\" shopify_http\n\necho -e \"-----------------\\n\"\n\necho \"== Metrics test\"\nwait_for_url http://localhost:20000/test1\ncurl -s http://localhost:8474/metrics | grep -E '^toxiproxy_proxy_sent_bytes_total{direction=\"downstream\",listener=\"127.0.0.1:20000\",proxy=\"shopify_http\",upstream=\"localhost:20002\"} [0-9]+'\ncurl -s http://localhost:8474/metrics | grep -E '^go_info'\ncurl -s http://localhost:8474/metrics | grep -E '^go_goroutines'\necho -e \"-----------------\\n\"\n\necho -e \"=================\\n\"\n\necho \"Succcess!\"\nstate=\"success\"\n"
  },
  {
    "path": "scripts/test-e2e-hazelcast",
    "content": "#!/bin/bash\n\n# Usage:\n#   test-e2e-hazelcast [docker image name for toxiproxy]\n\nset -ueo pipefail\n\ncd \"$(dirname \"$0\")\"\n\nstate=\"started\"\n\ncli() {\n  ../dist/toxiproxy-cli \"$@\" 2>&1 | sed -e 's/^/[client] /'\n}\n\nwait_for_url() {\n  curl -s --retry-connrefused --retry 5 --retry-delay 2 --retry-max-time 30 \\\n       --max-time 1 -L -I -X GET \"${1}\"\n}\n\n# Stop all background jobs on exit\nfunction cleanup() {\n  echo -e \"\\n\\n== Teardown: state=${state}\"\n  if [[ $state != \"success\" ]]; then\n    docker kill -s SIGQUIT member-proxy\n    docker logs -t member-proxy\n  fi\n  docker stop member-proxy member0 member1 member2 &>/dev/null || true\n  docker network rm toxiproxy-e2e &>/dev/null || true\n}\ntrap \"cleanup\" EXIT SIGINT SIGTERM\n\nLATEST_TAG=$(git describe --tags --abbrev=0)\nIMAGE_HAZELCAST=\"hazelcast/hazelcast:5.1.2-slim\"\nIMAGE_TOXIPROXY=\"${1:-ghcr.io/shopify/toxiproxy:${LATEST_TAG:1}}\"\nTOXIPROXY_BASE_URL=\"http://localhost:8474\"\n\necho \"= Toxiproxy E2E tests with Hazelcast cluster\"\necho\necho \"== Setup\"\necho\necho \"=== Starting Toxiproxy\"\n\ndocker rm -f member-proxy member0 member1 member2 &>/dev/null\ndocker network rm toxiproxy-e2e &>/dev/null || true\n\ndocker network create toxiproxy-e2e\n\ndocker run --rm -t \"${IMAGE_TOXIPROXY}\" --version\ndocker run -d \\\n        --name member-proxy \\\n        --network toxiproxy-e2e \\\n        -p 8474:8474 \\\n        -e LOG_LEVEL=trace \\\n        \"$IMAGE_TOXIPROXY\"\n\necho \"=== Wait Toxiproxy API is available\"\nwait_for_url \"${TOXIPROXY_BASE_URL}/version\"\n\necho \"=== Prepare proxies for Hazelcast cluster\"\nfor i in {0..2}; do\n  echo \"> Create proxy for member${i} on port 600${i}\"\n  # curl --data \"{\\\"name\\\": \\\"member${i}\\\", \\\"upstream\\\": \\\"member${i}:5701\\\", \\\"listen\\\": \\\"0.0.0.0:600${i}\\\"}\" \"${TOXIPROXY_BASE_URL}/proxies\"\n  cli create -l \"0.0.0.0:600${i}\" -u \"member${i}:5701\" \"member${i}\"\n  echo\ndone\n\necho\necho \"=== Strating Hazelcast containers\"\nfor i in {0..2}; do\n  echo \"> Start Hazelcast on host member${i}\"\n  docker run -d --rm \\\n            --name \"member${i}\" \\\n            --network toxiproxy-e2e \\\n            --volume \"${PWD}/hazelcast.xml:/opt/hazelcast/config/hazelcast-docker.xml\" \\\n            --env HZ_PHONE_HOME_ENABLED=false \\\n            --env JAVA_OPTS=\"-DproxyPort=600${i} -DproxyPort0=6000 -DproxyPort1=6001 -DproxyPort2=6002\" \\\n            \"$IMAGE_HAZELCAST\"\ndone\n\necho \"> Wait for cluster join (30s)...\"\nsleep 30\n\necho \"> Output of member0\"\ndocker logs -t -n 10 member0\n\necho\necho \"=== Initialize toxics for cluster\"\nfor i in {0..2}; do\n  echo \"> Adding toxics to member${i} proxy\"\n  # curl --data \"{\\\"name\\\": \\\"member${i}_downstream\\\", \\\"stream\\\": \\\"downstream\\\", \\\"toxicity\\\": 1.0, \\\"type\\\": \\\"bandwidth\\\", \\\"attributes\\\": { \\\"rate\\\": 0 }}\" \"${TOXIPROXY_BASE_URL}/proxies/member${i}/toxics\"\n  cli toxic add --type=bandwidth \\\n                      --downstream \\\n                      --toxicName=\"member${i}_downstream\" \\\n                      --attribute=\"rate=0\" \\\n                      --toxicity=1 \\\n                      \"member${i}\"\n  # curl --data \"{\\\"name\\\": \\\"member${i}_upstream\\\", \\\"stream\\\": \\\"upstream\\\", \\\"toxicity\\\": 1.0, \\\"type\\\": \\\"bandwidth\\\", \\\"attributes\\\": { \\\"rate\\\": 0 }}\" \"${TOXIPROXY_BASE_URL}/proxies/member${i}/toxics\"\n  cli toxic add --type=bandwidth \\\n                      --upstream \\\n                      --toxicName=\"member${i}_upstream\" \\\n                      --attribute=\"rate=0\" \\\n                      --toxicity=1 \\\n                      \"member${i}\"\n  echo\n  cli inspect \"member${i}\"\n  echo\ndone\n\necho \"=== Wait for a the Hazelcast cluster split-brain (60s)...\"\nsleep 60\n\necho \"=== Validate output of Toxiproxy and single member\"\ndocker logs -t -n 10 member0\ndocker logs -t -n 10 member-proxy\n\necho \"=== Removing toxics from proxies\"\nfor i in {0..2}; do\n  echo \"[$(date)] > Remove downstream bandwith Toxic for member${i} proxy\"\n  # curl -v -X DELETE \"${TOXIPROXY_BASE_URL}/proxies/member${i}/toxics/member${i}_downstream\"\n  cli toxic delete --toxicName=\"member${i}_downstream\" \"member${i}\"\n  echo \"[$(date)] > Remove ustream bandwith Toxic for member${i} proxy\"\n  # curl -v -X DELETE \"${TOXIPROXY_BASE_URL}/proxies/member${i}/toxics/member${i}_upstream\"\n  cli toxic delete --toxicName=\"member${i}_upstream\" \"member${i}\"\ndone\n\necho \"=== Validate output of Toxiproxy and single member after removing toxics\"\ndocker logs -t -n 10 member0\ndocker logs -t -n 10 member-proxy\n\ncli list\ncli inspect member0\ncli inspect member1\ncli inspect member2\n\necho -e \"=================\\n\"\n\necho \"Succcess!\"\nstate=\"success\"\n"
  },
  {
    "path": "scripts/test-release",
    "content": "#!/usr/bin/env bash\n\nset -ueo pipefail\n\nVERSION_FULL=\"$(git describe --abbrev=0 --tags)\"\nVERSION=${VERSION_FULL:1}\nARCH=$(uname -m)\nif [ \"${ARCH}\" == \"x86_64\" ]; then\n  ARCH=\"amd64\"\nfi\n\ngoreleaser release --rm-dist --skip-publish --skip-validate\n\ndocker run -v \"$(PWD)\"/dist:/dist --pull always --rm -it ubuntu bash -c \\\n  \"set -xe;\n  dpkg -i /dist/toxiproxy_*_linux_${ARCH}.deb;\n  ls -1 /usr/bin/toxiproxy-*;\n  /usr/bin/toxiproxy-server --version;\n  /usr/bin/toxiproxy-server --version \\\n    | grep -o -e 'toxiproxy-server version ${VERSION}';\n  /usr/bin/toxiproxy-cli --version;\n  /usr/bin/toxiproxy-cli --version \\\n    | grep -o -e 'toxiproxy-cli version ${VERSION}'\"\n\ndocker run -v \"$(PWD)\"/dist:/dist --pull always --rm -it fedora bash -c \\\n  \"set -xe;\n  yum localinstall -y /dist/toxiproxy_*_linux_${ARCH}.rpm;\n  ls -1 /usr/bin/toxiproxy-*;\n  /usr/bin/toxiproxy-server --version;\n  /usr/bin/toxiproxy-server --version \\\n    | grep -o -e 'toxiproxy-server version ${VERSION}';\n  /usr/bin/toxiproxy-cli --version;\n  /usr/bin/toxiproxy-cli --version \\\n    | grep -o -e 'toxiproxy-cli version ${VERSION}'\"\n\ndocker run -v \"$(PWD)\"/dist:/dist --pull always --rm -it alpine sh -c \\\n  \"set -xe;\n  apk add --allow-untrusted --no-cache /dist/toxiproxy_*_linux_${ARCH}.apk;\n  ls -1 /usr/bin/toxiproxy-*;\n  /usr/bin/toxiproxy-server --version;\n  /usr/bin/toxiproxy-server --version \\\n    | grep -o -e 'toxiproxy-server version ${VERSION}';\n  /usr/bin/toxiproxy-cli --version;\n  /usr/bin/toxiproxy-cli --version \\\n    | grep -o -e 'toxiproxy-cli version ${VERSION}'\"\n\ntar -ztvf dist/toxiproxy_*_linux_amd64.tar.gz | grep -o -e toxiproxy-server\ntar -ztvf dist/toxiproxy_*_linux_amd64.tar.gz | grep -o -e toxiproxy-cli\n\ngoreleaser build --rm-dist --single-target --skip-validate --id server\nbineries=(./dist/toxiproxy-server-*)\nserver=\"${bineries[0]}\"\n$server --help 2>&1 | grep -o -e \"Usage of ./dist/toxiproxy-server\"\n$server --version | grep -o -e \"toxiproxy-server version ${VERSION}\"\n\ngoreleaser build --rm-dist --single-target --skip-validate --id client\nbineries=(./dist/toxiproxy-cli-*)\ncli=\"${bineries[0]}\"\n$cli --help 2>&1 |\n  grep -o -e \"toxiproxy-cli - Simulate network and system conditions\"\n$cli --version | grep -o -e \"toxiproxy-cli version ${VERSION}\"\n"
  },
  {
    "path": "share/toxiproxy.conf",
    "content": "description \"TCP proxy to simulate network and system conditions\"\nauthor \"Simon Eskildsen & Jacob Wirth\"\n\nstart on startup\nstop on shutdown\n\nenv HOST=\"localhost\"\nenv PORT=\"8474\"\nenv BINARY=\"/usr/bin/toxiproxy-server\"\n\nscript\n  exec $BINARY -port $PORT -host $HOST\nend script\n"
  },
  {
    "path": "stream/direction.go",
    "content": "package stream\n\nimport (\n\t\"errors\"\n\t\"strings\"\n)\n\ntype Direction uint8\n\nvar ErrInvalidDirectionParameter error = errors.New(\"stream: invalid direction\")\n\nconst (\n\tUpstream Direction = iota\n\tDownstream\n\tNumDirections\n)\n\nfunc (d Direction) String() string {\n\tif d >= NumDirections {\n\t\treturn \"num_directions\"\n\t}\n\treturn [...]string{\"upstream\", \"downstream\"}[d]\n}\n\nfunc ParseDirection(value string) (Direction, error) {\n\tswitch strings.ToLower(value) {\n\tcase \"downstream\":\n\t\treturn Downstream, nil\n\tcase \"upstream\":\n\t\treturn Upstream, nil\n\t}\n\n\treturn NumDirections, ErrInvalidDirectionParameter\n}\n"
  },
  {
    "path": "stream/direction_test.go",
    "content": "package stream_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/Shopify/toxiproxy/v2/stream\"\n)\n\nfunc TestDirection_String(t *testing.T) {\n\ttestCases := []struct {\n\t\tname      string\n\t\tdirection stream.Direction\n\t\texpected  string\n\t}{\n\t\t{\"Downstream to string\", stream.Downstream, \"downstream\"},\n\t\t{\"Upstream to string\", stream.Upstream, \"upstream\"},\n\t\t{\"NumDirections to string\", stream.NumDirections, \"num_directions\"},\n\t\t{\"Upstream via number direction to string\", stream.Direction(0), \"upstream\"},\n\t\t{\"Downstream via number direction to string\", stream.Direction(1), \"downstream\"},\n\t\t{\"High number direction to string\", stream.Direction(5), \"num_directions\"},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc // capture range variable\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tactual := tc.direction.String()\n\t\t\tif actual != tc.expected {\n\t\t\t\tt.Errorf(\"got \\\"%s\\\"; expected \\\"%s\\\"\", actual, tc.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestParseDirection(t *testing.T) {\n\ttestCases := []struct {\n\t\tname     string\n\t\tinput    string\n\t\texpected stream.Direction\n\t\terr      error\n\t}{\n\t\t{\"parse empty\", \"\", stream.NumDirections, stream.ErrInvalidDirectionParameter},\n\t\t{\"parse upstream\", \"upstream\", stream.Upstream, nil},\n\t\t{\"parse downstream\", \"downstream\", stream.Downstream, nil},\n\t\t{\"parse unknown\", \"unknown\", stream.NumDirections, stream.ErrInvalidDirectionParameter},\n\t\t{\"parse number\", \"-123\", stream.NumDirections, stream.ErrInvalidDirectionParameter},\n\t\t{\"parse upper case\", \"DOWNSTREAM\", stream.Downstream, nil},\n\t\t{\"parse camel case\", \"UpStream\", stream.Upstream, nil},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc // capture range variable\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tactual, err := stream.ParseDirection(tc.input)\n\t\t\tif actual != tc.expected {\n\t\t\t\tt.Errorf(\"got \\\"%s\\\"; expected \\\"%s\\\"\", actual, tc.expected)\n\t\t\t}\n\n\t\t\tif err != tc.err {\n\t\t\t\tt.Errorf(\"got \\\"%s\\\"; expected \\\"%s\\\"\", err, tc.err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "stream/io_chan.go",
    "content": "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 struct {\n\tData      []byte\n\tTimestamp time.Time\n}\n\n// Implements the io.WriteCloser interface for a chan []byte.\ntype ChanWriter struct {\n\toutput chan<- *StreamChunk\n}\n\nfunc NewChanWriter(output chan<- *StreamChunk) *ChanWriter {\n\treturn &ChanWriter{output}\n}\n\n// Write `buf` as a StreamChunk to the channel. The full buffer is always written, and error\n// will always be nil. Calling `Write()` after closing the channel will panic.\nfunc (c *ChanWriter) Write(buf []byte) (int, error) {\n\tpacket := &StreamChunk{make([]byte, len(buf)), time.Now()}\n\tcopy(packet.Data, buf) // Make a copy before sending it to the channel\n\tc.output <- packet\n\treturn len(buf), nil\n}\n\n// Close the output channel.\nfunc (c *ChanWriter) Close() error {\n\tclose(c.output)\n\treturn nil\n}\n\n// Implements the io.Reader interface for a chan []byte.\ntype ChanReader struct {\n\tinput     <-chan *StreamChunk\n\tinterrupt <-chan struct{}\n\tbuffer    []byte\n}\n\nvar ErrInterrupted = fmt.Errorf(\"read interrupted by channel\")\n\nfunc NewChanReader(input <-chan *StreamChunk) *ChanReader {\n\treturn &ChanReader{input, make(chan struct{}), []byte{}}\n}\n\n// Specify a channel that can interrupt a read if it is blocking.\nfunc (c *ChanReader) SetInterrupt(interrupt <-chan struct{}) {\n\tc.interrupt = interrupt\n}\n\n// Read from the channel into `out`. This will block until data is available,\n// and can be interrupted with a channel using `SetInterrupt()`. If the read\n// was interrupted, `ErrInterrupted` will be returned.\nfunc (c *ChanReader) Read(out []byte) (int, error) {\n\tif c.buffer == nil {\n\t\treturn 0, io.EOF\n\t}\n\tn := copy(out, c.buffer)\n\tc.buffer = c.buffer[n:]\n\tif len(out) <= len(c.buffer) {\n\t\treturn n, nil\n\t} else if n > 0 {\n\t\t// We have some data to return, so make the channel read optional\n\t\tselect {\n\t\tcase p := <-c.input:\n\t\t\tif p == nil { // Stream was closed\n\t\t\t\tc.buffer = nil\n\t\t\t\tif n > 0 {\n\t\t\t\t\treturn n, nil\n\t\t\t\t}\n\t\t\t\treturn 0, io.EOF\n\t\t\t}\n\t\t\tn2 := copy(out[n:], p.Data)\n\t\t\tc.buffer = p.Data[n2:]\n\t\t\treturn n + n2, nil\n\t\tdefault:\n\t\t\treturn n, nil\n\t\t}\n\t}\n\tvar p *StreamChunk\n\tselect {\n\tcase p = <-c.input:\n\tcase <-c.interrupt:\n\t\tc.buffer = c.buffer[:0]\n\t\treturn n, ErrInterrupted\n\t}\n\tif p == nil { // Stream was closed\n\t\tc.buffer = nil\n\t\treturn 0, io.EOF\n\t}\n\tn2 := copy(out[n:], p.Data)\n\tc.buffer = p.Data[n2:]\n\treturn n + n2, nil\n}\n"
  },
  {
    "path": "stream/io_chan_test.go",
    "content": "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(\"hello world\")\n\tc := make(chan *StreamChunk)\n\twriter := NewChanWriter(c)\n\treader := NewChanReader(c)\n\tgo writer.Write(send)\n\tbuf := make([]byte, len(send))\n\tn, err := reader.Read(buf)\n\tif n != len(send) {\n\t\tt.Fatalf(\"Read wrong number of bytes: %d expected %d\", n, len(send))\n\t}\n\tif err != nil {\n\t\tt.Fatal(\"Couldn't read from stream\", err)\n\t}\n\tif !bytes.Equal(buf, send) {\n\t\tt.Fatal(\"Got wrong message from stream\", string(buf))\n\t}\n\twriter.Close()\n\tn, err = reader.Read(buf)\n\tif err != io.EOF {\n\t\tt.Fatal(\"Read returned wrong error after close:\", err)\n\t}\n\tif n != 0 {\n\t\tt.Fatalf(\"Read still returned data after close: %d bytes\", n)\n\t}\n}\n\nfunc TestReadMoreThanWrite(t *testing.T) {\n\tsend := []byte(\"hello world\")\n\tc := make(chan *StreamChunk)\n\twriter := NewChanWriter(c)\n\treader := NewChanReader(c)\n\tgo writer.Write(send)\n\tbuf := make([]byte, len(send)+10)\n\tn, err := reader.Read(buf)\n\tif err != nil {\n\t\tt.Fatal(\"Couldn't read from stream\", err)\n\t}\n\tif n != len(send) {\n\t\tt.Fatalf(\"Read wrong number of bytes: %d expected %d\", n, len(send))\n\t}\n\tif !bytes.Equal(buf[:n], send) {\n\t\tt.Fatal(\"Got wrong message from stream\", string(buf[:n]))\n\t}\n\twriter.Close()\n\tn, err = reader.Read(buf)\n\tif err != io.EOF {\n\t\tt.Fatal(\"Read returned wrong error after close:\", err)\n\t}\n\tif n != 0 {\n\t\tt.Fatalf(\"Read still returned data after close: %d bytes\", n)\n\t}\n}\n\nfunc TestReadLessThanWrite(t *testing.T) {\n\tsend := []byte(\"hello world\")\n\tc := make(chan *StreamChunk)\n\twriter := NewChanWriter(c)\n\treader := NewChanReader(c)\n\tgo writer.Write(send)\n\tbuf := make([]byte, 6)\n\tn, err := reader.Read(buf)\n\tif err != nil {\n\t\tt.Fatal(\"Couldn't read from stream\", err)\n\t}\n\tif n != len(buf) {\n\t\tt.Fatalf(\"Read wrong number of bytes: %d expected %d\", n, len(buf))\n\t}\n\tif !bytes.Equal(buf, send[:len(buf)]) {\n\t\tt.Fatal(\"Got wrong message from stream\", string(buf))\n\t}\n\twriter.Close()\n\tn, err = reader.Read(buf)\n\tif err != nil {\n\t\tt.Fatal(\"Couldn't read from stream\", err)\n\t}\n\tif n != len(send)-len(buf) {\n\t\tt.Fatalf(\"Read wrong number of bytes: %d expected %d\", n, len(send)-len(buf))\n\t}\n\tif !bytes.Equal(buf[:n], send[len(buf):]) {\n\t\tt.Fatal(\"Got wrong message from stream\", string(buf[:n]))\n\t}\n\tn, err = reader.Read(buf)\n\tif err != io.EOF {\n\t\tt.Fatal(\"Read returned wrong error after close:\", err)\n\t}\n\tif n != 0 {\n\t\tt.Fatalf(\"Read still returned data after close: %d bytes\", n)\n\t}\n}\n\nfunc TestMultiReadWrite(t *testing.T) {\n\tsend := []byte(\"hello world, this message is longer\")\n\tc := make(chan *StreamChunk)\n\twriter := NewChanWriter(c)\n\treader := NewChanReader(c)\n\tgo func() {\n\t\twriter.Write(send[:9])\n\t\twriter.Write(send[9:19])\n\t\twriter.Write(send[19:])\n\t\twriter.Close()\n\t}()\n\tbuf := make([]byte, 10)\n\tfor read := 0; read < len(send); {\n\t\tn, err := reader.Read(buf)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Couldn't read from stream\", err, n)\n\t\t}\n\t\tif !bytes.Equal(buf[:n], send[read:read+n]) {\n\t\t\tt.Fatal(\"Got wrong message from stream\", string(buf))\n\t\t}\n\t\tread += n\n\t}\n\tn, err := reader.Read(buf)\n\tif err != io.EOF {\n\t\tt.Fatal(\"Read returned wrong error after close:\", err, string(buf[:n]))\n\t}\n\tif !bytes.Equal(buf[:n], send[len(send)-n:]) {\n\t\tt.Fatal(\"Got wrong message from stream\", string(buf[:n]))\n\t}\n}\n\nfunc TestMultiWriteWithCopy(t *testing.T) {\n\tsend := []byte(\"hello world, this message is longer\")\n\tc := make(chan *StreamChunk)\n\twriter := NewChanWriter(c)\n\treader := NewChanReader(c)\n\tgo func() {\n\t\twriter.Write(send[:9])\n\t\twriter.Write(send[9:19])\n\t\twriter.Write(send[19:])\n\t\twriter.Close()\n\t}()\n\tbuf := new(bytes.Buffer)\n\tn, err := io.Copy(buf, reader)\n\tif int(n) != len(send) {\n\t\tt.Fatalf(\"Read wrong number of bytes: %d expected %d\", n, len(send))\n\t}\n\tif err != nil {\n\t\tt.Fatal(\"Couldn't read from stream\", err)\n\t}\n\tif !bytes.Equal(buf.Bytes(), send) {\n\t\tt.Fatal(\"Got wrong message from stream\", buf.String())\n\t}\n}\n\nfunc TestStream_ReadCorrectness(t *testing.T) {\n\tsendMsg := []byte(\"hello world\")\n\tc := make(chan *StreamChunk)\n\tinterrupt := make(chan struct{})\n\twriter := NewChanWriter(c)\n\treader := NewChanReader(c)\n\treader.SetInterrupt(interrupt)\n\n\tgo writer.Write(sendMsg)\n\treadMsg := make([]byte, len(sendMsg))\n\tn, err := reader.Read(readMsg)\n\tif err != nil {\n\t\tt.Fatalf(\"Couldn't read from stream: %v\", err)\n\t}\n\n\tif n != len(sendMsg) {\n\t\tt.Fatalf(\"Read wrong number of bytes: %d expected %d\", n, len(sendMsg))\n\t}\n\n\tif !bytes.Equal(readMsg, sendMsg) {\n\t\tt.Fatal(\"Got wrong message from stream\", string(readMsg))\n\t}\n}\n\nfunc TestStream_ReadInterrupt(t *testing.T) {\n\tsendMsg := []byte(\"hello world\")\n\tc := make(chan *StreamChunk)\n\tinterrupt := make(chan struct{})\n\twriter := NewChanWriter(c)\n\treader := NewChanReader(c)\n\treader.SetInterrupt(interrupt)\n\n\tgo writer.Write(sendMsg)\n\treadMsg := make([]byte, len(sendMsg))\n\treader.Read(readMsg)\n\n\t// Interrupting the stream mid-read\n\tgo func() {\n\t\ttime.Sleep(50 * time.Millisecond)\n\t\tinterrupt <- struct{}{}\n\t}()\n\n\tn, err := reader.Read(readMsg)\n\n\tif err != ErrInterrupted {\n\t\tt.Fatalf(\"Read returned wrong error after interrupt: %v\", err)\n\t}\n\n\tif n != 0 {\n\t\tt.Fatalf(\"Read still returned data after interrput: %d bytes\", n)\n\t}\n\n\t// Try writing again after the channel was interrupted\n\tgo writer.Write(sendMsg)\n\tn, err = reader.Read(readMsg)\n\n\tif n != len(sendMsg) {\n\t\tt.Fatalf(\"Read wrong number of bytes: %d expected %d\", n, len(sendMsg))\n\t}\n\n\tif err != nil {\n\t\tt.Fatalf(\"Couldn't read from stream: %v\", err)\n\t}\n\n\tif !bytes.Equal(readMsg, sendMsg) {\n\t\tt.Fatal(\"Got wrong message from stream\", string(readMsg))\n\t}\n}\n"
  },
  {
    "path": "test/e2e/benchmark_test.go",
    "content": "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//     BenchmarkDirect             3000            588148 ns/op\n//     BenchmarkProxy              2000            999949 ns/op\n//     BenchmarkDirectSmall        5000            291324 ns/op\n//     BenchmarkProxySmall         3000            504501 ns/op\n//\n// 10x Toxic Types:\n//     BenchmarkDirect             3000            599519 ns/op\n//     BenchmarkProxy              2000           1044746 ns/op\n//     BenchmarkDirectSmall        5000            280713 ns/op\n//     BenchmarkProxySmall         3000            574816 ns/op\n//\n// Toxiproxy 2.0\n//\n// No Enabled Toxics:\n//     BenchmarkDirect             2000            597998 ns/op\n//     BenchmarkProxy              2000            964510 ns/op\n//     BenchmarkDirectSmall       10000            287448 ns/op\n//     BenchmarkProxySmall         5000            560694 ns/op\n\n// Test the backend server directly, use 64k random endpoint.\nfunc BenchmarkDirect(b *testing.B) {\n\tclient := http.Client{}\n\tfor i := 0; i < b.N; i++ {\n\t\tresp, err := client.Get(\"http://localhost:20002/test1\")\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\t_, err = io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\tresp.Body.Close()\n\t}\n\tclient.CloseIdleConnections()\n}\n\n// Test the backend through toxiproxy, use 64k random endpoint.\nfunc BenchmarkProxy(b *testing.B) {\n\tclient := http.Client{}\n\tfor i := 0; i < b.N; i++ {\n\t\tresp, err := client.Get(\"http://localhost:20000/test1\")\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\t_, err = io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\tresp.Body.Close()\n\t}\n\tclient.CloseIdleConnections()\n}\n\n// Test the backend server directly, use \"hello world\" endpoint.\nfunc BenchmarkDirectSmall(b *testing.B) {\n\tclient := http.Client{}\n\tfor i := 0; i < b.N; i++ {\n\t\tresp, err := client.Get(\"http://localhost:20002/test2\")\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\t_, err = io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\tresp.Body.Close()\n\t}\n\tclient.CloseIdleConnections()\n}\n\n// Test the backend through toxiproxy, use \"hello world\" endpoint.\nfunc BenchmarkProxySmall(b *testing.B) {\n\tclient := http.Client{}\n\tfor i := 0; i < b.N; i++ {\n\t\tresp, err := client.Get(\"http://localhost:20000/test2\")\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\t_, err = io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\tresp.Body.Close()\n\t}\n\tclient.CloseIdleConnections()\n}\n"
  },
  {
    "path": "test/e2e/endpoint.go",
    "content": "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 []byte\n\tout   []byte\n\tout2  []byte\n)\n\nfunc handler1(w http.ResponseWriter, r *http.Request) {\n\tn, err := w.Write(out)\n\tif n != len(out) {\n\t\tfmt.Println(\"Short write!\")\n\t}\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n\nfunc handler2(w http.ResponseWriter, r *http.Request) {\n\tn, err := w.Write(out2)\n\tif n != len(out2) {\n\t\tfmt.Println(\"Short write!\")\n\t}\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n\nfunc main() {\n\tstuff = make([]byte, 32*1024)\n\tout = make([]byte, len(stuff)*2)\n\tout2 = []byte(\"hello world\")\n\tfor i := 0; i < len(stuff); i++ {\n\t\tstuff[i] = byte(i % 256)\n\t}\n\thex.Encode(out, stuff)\n\n\tr := mux.NewRouter()\n\tr.HandleFunc(\"/test1\", handler1)\n\tr.HandleFunc(\"/test2\", handler2)\n\n\tlog.Println(\"Listening :20002\")\n\n\tsrv := &http.Server{\n\t\tHandler:      r,\n\t\tAddr:         \":20002\",\n\t\tWriteTimeout: 3 * time.Second,\n\t\tReadTimeout:  3 * time.Second,\n\t}\n\n\tlog.Fatal(srv.ListenAndServe())\n}\n"
  },
  {
    "path": "testhelper/tcp_server.go",
    "content": "package testhelper\n\nimport (\n\t\"io\"\n\t\"net\"\n\t\"testing\"\n)\n\nfunc NewTCPServer() (*TCPServer, error) {\n\tresult := &TCPServer{\n\t\taddr:     \"localhost:0\",\n\t\tresponse: make(chan []byte, 1),\n\t}\n\terr := result.Run()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn result, nil\n}\n\ntype TCPServer struct {\n\taddr     string\n\tserver   net.Listener\n\tresponse chan []byte\n}\n\nfunc (server *TCPServer) Run() (err error) {\n\tserver.server, err = net.Listen(\"tcp\", server.addr)\n\tif err != nil {\n\t\treturn\n\t}\n\tserver.addr = server.server.Addr().String()\n\treturn\n}\n\nfunc (server *TCPServer) handle_connection() (err error) {\n\tconn, err := server.server.Accept()\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer conn.Close()\n\n\tval, err := io.ReadAll(conn)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tserver.response <- val\n\treturn\n}\n\nfunc (server *TCPServer) Close() (err error) {\n\treturn server.server.Close()\n}\n\nfunc WithTCPServer(t *testing.T, block func(string, chan []byte)) {\n\tserver, err := NewTCPServer()\n\tif err != nil {\n\t\tt.Fatal(\"Failed to create TCP server\", err)\n\t}\n\tgo func(t *testing.T, server *TCPServer) {\n\t\terr := server.handle_connection()\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed to handle connection\", err)\n\t\t}\n\t}(t, server)\n\tblock(server.addr, server.response)\n}\n"
  },
  {
    "path": "testhelper/tcp_server_test.go",
    "content": "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 TestSimpleServer(t *testing.T) {\n\ttesthelper.WithTCPServer(t, func(addr string, response chan []byte) {\n\t\tconn, err := net.Dial(\"tcp\", addr)\n\t\tif err != nil {\n\t\t\tt.Error(\"Unable to dial TCP server\", err)\n\t\t}\n\n\t\tmsg := []byte(\"hello world\")\n\n\t\t_, err = conn.Write(msg)\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed writing to TCP server\", err)\n\t\t}\n\n\t\terr = conn.Close()\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed to close TCP connection\", err)\n\t\t}\n\n\t\tresp := <-response\n\t\tif !bytes.Equal(resp, msg) {\n\t\t\tt.Error(\"Server didn't read bytes from client\")\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "testhelper/timeout_after.go",
    "content": "package testhelper\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\nfunc TimeoutAfter(after time.Duration, f func()) error {\n\tsuccess := make(chan struct{})\n\tgo func() {\n\t\tf()\n\t\tclose(success)\n\t}()\n\tselect {\n\tcase <-success:\n\t\treturn nil\n\tcase <-time.After(after):\n\t\treturn fmt.Errorf(\"timed out after %s\", after)\n\t}\n}\n"
  },
  {
    "path": "testhelper/timeout_after_test.go",
    "content": "package testhelper_test\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/Shopify/toxiproxy/v2/testhelper\"\n)\n\nfunc TestTimeoutAfter(t *testing.T) {\n\terr := testhelper.TimeoutAfter(5*time.Millisecond, func() {})\n\tif err != nil {\n\t\tt.Fatal(\"Non blocking function should not timeout.\")\n\t}\n\n\terr = testhelper.TimeoutAfter(5*time.Millisecond, func() {\n\t\ttime.Sleep(time.Second)\n\t})\n\tif err == nil {\n\t\tt.Fatal(\"Blocking function should timeout.\")\n\t}\n}\n"
  },
  {
    "path": "testhelper/upstream.go",
    "content": "package testhelper\n\nimport (\n\t\"net\"\n\t\"testing\"\n)\n\ntype Upstream struct {\n\tlistener    net.Listener\n\tlogger      testing.TB\n\tConnections chan net.Conn\n}\n\nfunc NewUpstream(t testing.TB, ignoreData bool) *Upstream {\n\tresult := &Upstream{\n\t\tlogger: t,\n\t}\n\n\tresult.listen()\n\tresult.accept(ignoreData)\n\n\treturn result\n}\n\nfunc (u *Upstream) listen() {\n\tlistener, err := net.Listen(\"tcp\", \"localhost:0\")\n\tif err != nil {\n\t\tu.logger.Fatalf(\"Failed to create TCP server: %v\", err)\n\t}\n\tu.listener = listener\n}\n\nfunc (u *Upstream) accept(ignoreData bool) {\n\tu.Connections = make(chan net.Conn)\n\tgo func(u *Upstream) {\n\t\tconn, err := u.listener.Accept()\n\t\tif err != nil {\n\t\t\tu.logger.Fatalf(\"Unable to accept TCP connection: %v\", err)\n\t\t}\n\t\tif ignoreData {\n\t\t\tbuf := make([]byte, 4000)\n\t\t\tfor err == nil {\n\t\t\t\t_, err = conn.Read(buf)\n\t\t\t}\n\t\t} else {\n\t\t\tu.Connections <- conn\n\t\t}\n\t}(u)\n}\n\nfunc (u *Upstream) Close() {\n\tu.listener.Close()\n}\n\nfunc (u *Upstream) Addr() string {\n\treturn u.listener.Addr().String()\n}\n"
  },
  {
    "path": "toxic_collection.go",
    "content": "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\"github.com/Shopify/toxiproxy/v2/stream\"\n\t\"github.com/Shopify/toxiproxy/v2/toxics\"\n)\n\n// ToxicCollection contains a list of toxics that are chained together. Each proxy\n// has its own collection. A hidden noop toxic is always maintained at the beginning\n// of each chain so toxics have a method of pausing incoming data (by interrupting\n// the preceding toxic).\ntype ToxicCollection struct {\n\tsync.Mutex\n\n\tnoop  *toxics.ToxicWrapper\n\tproxy *Proxy\n\tchain [][]*toxics.ToxicWrapper\n\tlinks map[string]*ToxicLink\n}\n\nfunc NewToxicCollection(proxy *Proxy) *ToxicCollection {\n\tcollection := &ToxicCollection{\n\t\tnoop: &toxics.ToxicWrapper{\n\t\t\tToxic: new(toxics.NoopToxic),\n\t\t\tType:  \"noop\",\n\t\t},\n\t\tproxy: proxy,\n\t\tchain: make([][]*toxics.ToxicWrapper, stream.NumDirections),\n\t\tlinks: make(map[string]*ToxicLink),\n\t}\n\tfor dir := range collection.chain {\n\t\tcollection.chain[dir] = make([]*toxics.ToxicWrapper, 1, toxics.Count()+1)\n\t\tcollection.chain[dir][0] = collection.noop\n\t}\n\treturn collection\n}\n\nfunc (c *ToxicCollection) ResetToxics(ctx context.Context) {\n\tc.Lock()\n\tdefer c.Unlock()\n\n\t// Remove all but the first noop toxic\n\tfor dir := range c.chain {\n\t\tfor len(c.chain[dir]) > 1 {\n\t\t\tc.chainRemoveToxic(ctx, c.chain[dir][1])\n\t\t}\n\t}\n}\n\nfunc (c *ToxicCollection) GetToxic(name string) *toxics.ToxicWrapper {\n\tc.Lock()\n\tdefer c.Unlock()\n\n\treturn c.findToxicByName(name)\n}\n\nfunc (c *ToxicCollection) GetToxicArray() []toxics.Toxic {\n\tc.Lock()\n\tdefer c.Unlock()\n\n\tresult := make([]toxics.Toxic, 0)\n\tfor dir := range c.chain {\n\t\tfor i, toxic := range c.chain[dir] {\n\t\t\tif i == 0 {\n\t\t\t\t// Skip the first noop toxic, it should not be visible\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tresult = append(result, toxic)\n\t\t}\n\t}\n\treturn result\n}\n\nfunc (c *ToxicCollection) AddToxicJson(data io.Reader) (*toxics.ToxicWrapper, error) {\n\tc.Lock()\n\tdefer c.Unlock()\n\n\tvar buffer bytes.Buffer\n\n\t// Default to a downstream toxic with a toxicity of 1.\n\twrapper := &toxics.ToxicWrapper{\n\t\tStream:   \"downstream\",\n\t\tToxicity: 1.0,\n\t\tToxic:    new(toxics.NoopToxic),\n\t}\n\n\terr := json.NewDecoder(io.TeeReader(data, &buffer)).Decode(wrapper)\n\tif err != nil {\n\t\treturn nil, joinError(err, ErrBadRequestBody)\n\t}\n\n\twrapper.Direction, err = stream.ParseDirection(wrapper.Stream)\n\tif err != nil {\n\t\treturn nil, ErrInvalidStream\n\t}\n\n\tif wrapper.Name == \"\" {\n\t\twrapper.Name = fmt.Sprintf(\"%s_%s\", wrapper.Type, wrapper.Stream)\n\t}\n\n\tif toxics.New(wrapper) == nil {\n\t\treturn nil, ErrInvalidToxicType\n\t}\n\n\tfound := c.findToxicByName(wrapper.Name)\n\tif found != nil {\n\t\treturn nil, ErrToxicAlreadyExists\n\t}\n\n\t// Parse attributes because we now know the toxics type.\n\tattrs := &struct {\n\t\tAttributes interface{} `json:\"attributes\"`\n\t}{\n\t\twrapper.Toxic,\n\t}\n\terr = json.NewDecoder(&buffer).Decode(attrs)\n\tif err != nil {\n\t\treturn nil, joinError(err, ErrBadRequestBody)\n\t}\n\n\tc.chainAddToxic(wrapper)\n\treturn wrapper, nil\n}\n\nfunc (c *ToxicCollection) UpdateToxicJson(\n\tname string,\n\tdata io.Reader,\n) (*toxics.ToxicWrapper, error) {\n\tc.Lock()\n\tdefer c.Unlock()\n\n\ttoxic := c.findToxicByName(name)\n\tif toxic != nil {\n\t\tattrs := &struct {\n\t\t\tAttributes interface{} `json:\"attributes\"`\n\t\t\tToxicity   float32     `json:\"toxicity\"`\n\t\t}{\n\t\t\ttoxic.Toxic,\n\t\t\ttoxic.Toxicity,\n\t\t}\n\t\terr := json.NewDecoder(data).Decode(attrs)\n\t\tif err != nil {\n\t\t\treturn nil, joinError(err, ErrBadRequestBody)\n\t\t}\n\t\ttoxic.Toxicity = attrs.Toxicity\n\n\t\tc.chainUpdateToxic(toxic)\n\t\treturn toxic, nil\n\t}\n\treturn nil, ErrToxicNotFound\n}\n\nfunc (c *ToxicCollection) RemoveToxic(ctx context.Context, name string) error {\n\tlog := zerolog.Ctx(ctx).\n\t\tWith().\n\t\tStr(\"component\", \"ToxicCollection\").\n\t\tStr(\"method\", \"RemoveToxic\").\n\t\tStr(\"toxic\", name).\n\t\tStr(\"proxy\", c.proxy.Name).\n\t\tLogger()\n\tlog.Trace().Msg(\"Acquire locking...\")\n\tc.Lock()\n\tdefer c.Unlock()\n\n\tlog.Trace().Msg(\"Getting toxic by name...\")\n\ttoxic := c.findToxicByName(name)\n\tif toxic == nil {\n\t\tlog.Trace().Msg(\"Could not find toxic by name\")\n\t\treturn ErrToxicNotFound\n\t}\n\n\tc.chainRemoveToxic(ctx, toxic)\n\tlog.Trace().Msg(\"Finished\")\n\treturn nil\n}\n\nfunc (c *ToxicCollection) StartLink(\n\tserver *ApiServer,\n\tname string,\n\tinput io.Reader,\n\toutput io.WriteCloser,\n\tdirection stream.Direction,\n) {\n\tc.Lock()\n\tdefer c.Unlock()\n\n\tvar logger zerolog.Logger\n\tif c.proxy.Logger != nil {\n\t\tlogger = *c.proxy.Logger\n\t} else {\n\t\tlogger = zerolog.Nop()\n\t}\n\n\tlink := NewToxicLink(c.proxy, c, direction, logger)\n\tlink.Start(server, name, input, output)\n\tc.links[name] = link\n}\n\nfunc (c *ToxicCollection) RemoveLink(name string) {\n\tc.Lock()\n\tdefer c.Unlock()\n\tdelete(c.links, name)\n}\n\n// All following functions assume the lock is already grabbed.\nfunc (c *ToxicCollection) findToxicByName(name string) *toxics.ToxicWrapper {\n\tfor dir := range c.chain {\n\t\t// Skip the first noop toxic, it has no name\n\t\tfor _, toxic := range c.chain[dir][1:] {\n\t\t\tif toxic.Name == name {\n\t\t\t\treturn toxic\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (c *ToxicCollection) chainAddToxic(toxic *toxics.ToxicWrapper) {\n\tdir := toxic.Direction\n\ttoxic.Index = len(c.chain[dir])\n\tc.chain[dir] = append(c.chain[dir], toxic)\n\n\t// Asynchronously add the toxic to each link\n\twg := sync.WaitGroup{}\n\tfor _, link := range c.links {\n\t\tif link.direction == dir {\n\t\t\twg.Add(1)\n\t\t\tgo func(link *ToxicLink, wg *sync.WaitGroup) {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tlink.AddToxic(toxic)\n\t\t\t}(link, &wg)\n\t\t}\n\t}\n\twg.Wait()\n}\n\nfunc (c *ToxicCollection) chainUpdateToxic(toxic *toxics.ToxicWrapper) {\n\tc.chain[toxic.Direction][toxic.Index] = toxic\n\n\t// Asynchronously update the toxic in each link\n\tgroup := sync.WaitGroup{}\n\tfor _, link := range c.links {\n\t\tif link.direction == toxic.Direction {\n\t\t\tgroup.Add(1)\n\t\t\tgo func(link *ToxicLink) {\n\t\t\t\tdefer group.Done()\n\t\t\t\tlink.UpdateToxic(toxic)\n\t\t\t}(link)\n\t\t}\n\t}\n\tgroup.Wait()\n}\n\nfunc (c *ToxicCollection) chainRemoveToxic(ctx context.Context, toxic *toxics.ToxicWrapper) {\n\tlog := zerolog.Ctx(ctx).\n\t\tWith().\n\t\tStr(\"component\", \"ToxicCollection\").\n\t\tStr(\"method\", \"chainRemoveToxic\").\n\t\tStr(\"toxic\", toxic.Name).\n\t\tStr(\"direction\", toxic.Direction.String()).\n\t\tLogger()\n\n\tdir := toxic.Direction\n\tc.chain[dir] = append(c.chain[dir][:toxic.Index], c.chain[dir][toxic.Index+1:]...)\n\tfor i := toxic.Index; i < len(c.chain[dir]); i++ {\n\t\tc.chain[dir][i].Index = i\n\t}\n\n\t// Asynchronously remove the toxic from each link\n\twg := sync.WaitGroup{}\n\n\tevent_array := zerolog.Arr()\n\tfor _, link := range c.links {\n\t\tif link.direction == dir {\n\t\t\tevent_array = event_array.Str(fmt.Sprintf(\"Link[%p] %s\", link, link.Direction()))\n\t\t\twg.Add(1)\n\t\t\tgo func(ctx context.Context, link *ToxicLink, log zerolog.Logger) {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tlink.RemoveToxic(ctx, toxic)\n\t\t\t}(ctx, link, log)\n\t\t}\n\t}\n\n\tlog.Trace().\n\t\tArray(\"links\", event_array).\n\t\tMsg(\"Waiting to update links\")\n\twg.Wait()\n\n\ttoxic.Index = -1\n}\n"
  },
  {
    "path": "toxics/bandwidth.go",
    "content": "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// The BandwidthToxic passes data through at a limited rate.\ntype BandwidthToxic struct {\n\t// Rate in KB/s\n\tRate int64 `json:\"rate\"`\n}\n\nfunc (t *BandwidthToxic) Pipe(stub *ToxicStub) {\n\tlogger := log.With().\n\t\tStr(\"component\", \"BandwidthToxic\").\n\t\tStr(\"method\", \"Pipe\").\n\t\tStr(\"toxic_type\", \"bandwidth\").\n\t\tStr(\"addr\", fmt.Sprintf(\"%p\", t)).\n\t\tLogger()\n\tvar sleep time.Duration = 0\n\tfor {\n\t\tselect {\n\t\tcase <-stub.Interrupt:\n\t\t\tlogger.Trace().Msg(\"BandwidthToxic was interrupted\")\n\t\t\treturn\n\t\tcase p := <-stub.Input:\n\t\t\tif p == nil {\n\t\t\t\tstub.Close()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif t.Rate <= 0 {\n\t\t\t\tsleep = 0\n\t\t\t} else {\n\t\t\t\tsleep += time.Duration(len(p.Data)) * time.Millisecond / time.Duration(t.Rate)\n\t\t\t}\n\t\t\t// If the rate is low enough, split the packet up and send in 100 millisecond intervals\n\t\t\tfor int64(len(p.Data)) > t.Rate*100 {\n\t\t\t\tselect {\n\t\t\t\tcase <-time.After(100 * time.Millisecond):\n\t\t\t\t\tstub.Output <- &stream.StreamChunk{\n\t\t\t\t\t\tData:      p.Data[:t.Rate*100],\n\t\t\t\t\t\tTimestamp: p.Timestamp,\n\t\t\t\t\t}\n\t\t\t\t\tp.Data = p.Data[t.Rate*100:]\n\t\t\t\t\tsleep -= 100 * time.Millisecond\n\t\t\t\tcase <-stub.Interrupt:\n\t\t\t\t\tlogger.Trace().Msg(\"BandwidthToxic was interrupted during writing data\")\n\t\t\t\t\terr := stub.WriteOutput(p, 5*time.Second) // Don't drop any data on the floor\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlogger.Warn().Err(err).\n\t\t\t\t\t\t\tMsg(\"Could not write last packets after interrupt to Output\")\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tstart := time.Now()\n\t\t\tselect {\n\t\t\tcase <-time.After(sleep):\n\t\t\t\t// time.After only seems to have ~1ms prevision, so offset the next sleep by the error\n\t\t\t\tsleep -= time.Since(start)\n\t\t\t\tstub.Output <- p\n\t\t\tcase <-stub.Interrupt:\n\t\t\t\tlogger.Trace().Msg(\"BandwidthToxic was interrupted during writing data\")\n\t\t\t\terr := stub.WriteOutput(p, 5*time.Second) // Don't drop any data on the floor\n\t\t\t\tif err != nil {\n\t\t\t\t\tlogger.Warn().Err(err).\n\t\t\t\t\t\tMsg(\"Could not write last packets after interrupt to Output\")\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc init() {\n\tRegister(\"bandwidth\", new(BandwidthToxic))\n}\n"
  },
  {
    "path": "toxics/bandwidth_test.go",
    "content": "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/testhelper\"\n\t\"github.com/Shopify/toxiproxy/v2/toxics\"\n)\n\nfunc TestBandwidthToxic(t *testing.T) {\n\tupstream := testhelper.NewUpstream(t, false)\n\tdefer upstream.Close()\n\n\tproxy := NewTestProxy(\"test\", upstream.Addr())\n\tproxy.Start()\n\tdefer proxy.Stop()\n\n\tclient, err := net.Dial(\"tcp\", proxy.Listen)\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to dial TCP server: %v\", err)\n\t}\n\n\tupstreamConn := <-upstream.Connections\n\n\trate := 1000 // 1MB/s\n\tproxy.Toxics.AddToxicJson(\n\t\tToxicToJson(t, \"\", \"bandwidth\", \"upstream\", &toxics.BandwidthToxic{Rate: int64(rate)}),\n\t)\n\n\twrittenPayload := []byte(strings.Repeat(\"hello world \", 40000)) // 480KB\n\tgo func() {\n\t\tn, err := client.Write(writtenPayload)\n\t\tclient.Close()\n\t\tif n != len(writtenPayload) || err != nil {\n\t\t\tt.Errorf(\"Failed to write buffer: (%d == %d) %v\", n, len(writtenPayload), err)\n\t\t}\n\t}()\n\n\tserverRecvPayload := make([]byte, len(writtenPayload))\n\tstart := time.Now()\n\t_, err = io.ReadAtLeast(upstreamConn, serverRecvPayload, len(serverRecvPayload))\n\tif err != nil {\n\t\tt.Errorf(\"Proxy read failed: %v\", err)\n\t} else if !bytes.Equal(writtenPayload, serverRecvPayload) {\n\t\tt.Errorf(\"Server did not read correct buffer from client!\")\n\t}\n\n\tAssertDeltaTime(t,\n\t\t\"Bandwidth\",\n\t\ttime.Since(start),\n\t\ttime.Duration(len(writtenPayload))*time.Second/time.Duration(rate*1000),\n\t\t10*time.Millisecond,\n\t)\n}\n\nfunc BenchmarkBandwidthToxic100MB(b *testing.B) {\n\tupstream := testhelper.NewUpstream(b, true)\n\tdefer upstream.Close()\n\n\tproxy := NewTestProxy(\"test\", upstream.Addr())\n\tproxy.Start()\n\tdefer proxy.Stop()\n\n\tclient, err := net.Dial(\"tcp\", proxy.Listen)\n\tif err != nil {\n\t\tb.Error(\"Unable to dial TCP server\", err)\n\t}\n\n\twrittenPayload := []byte(strings.Repeat(\"hello world \", 1000))\n\n\tproxy.Toxics.AddToxicJson(\n\t\tToxicToJson(nil, \"\", \"bandwidth\", \"upstream\", &toxics.BandwidthToxic{Rate: 100 * 1000}),\n\t)\n\n\tb.SetBytes(int64(len(writtenPayload)))\n\tb.ReportAllocs()\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tn, err := client.Write(writtenPayload)\n\t\tif err != nil || n != len(writtenPayload) {\n\t\t\tb.Errorf(\"%v, %d == %d\", err, n, len(writtenPayload))\n\t\t\tbreak\n\t\t}\n\t}\n\n\terr = client.Close()\n\tif err != nil {\n\t\tb.Error(\"Failed to close TCP connection\", err)\n\t}\n}\n"
  },
  {
    "path": "toxics/latency.go",
    "content": "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 +/- jitter added.\ntype LatencyToxic struct {\n\t// Times in milliseconds\n\tLatency int64 `json:\"latency\"`\n\tJitter  int64 `json:\"jitter\"`\n}\n\nfunc (t *LatencyToxic) GetBufferSize() int {\n\treturn 1024\n}\n\nfunc (t *LatencyToxic) delay() time.Duration {\n\t// Delay = t.Latency +/- t.Jitter\n\tdelay := t.Latency\n\tjitter := t.Jitter\n\tif jitter > 0 {\n\t\t// #nosec G404 -- was ignored before too\n\t\tdelay += rand.Int63n(jitter*2) - jitter\n\t}\n\treturn time.Duration(delay) * time.Millisecond\n}\n\nfunc (t *LatencyToxic) Pipe(stub *ToxicStub) {\n\tfor {\n\t\tselect {\n\t\tcase <-stub.Interrupt:\n\t\t\treturn\n\t\tcase c := <-stub.Input:\n\t\t\tif c == nil {\n\t\t\t\tstub.Close()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tsleep := t.delay() - time.Since(c.Timestamp)\n\t\t\tselect {\n\t\t\tcase <-time.After(sleep):\n\t\t\t\tc.Timestamp = c.Timestamp.Add(sleep)\n\t\t\t\tstub.Output <- c\n\t\t\tcase <-stub.Interrupt:\n\t\t\t\t// Exit fast without applying latency.\n\t\t\t\tstub.Output <- c // Don't drop any data on the floor\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc init() {\n\tRegister(\"latency\", new(LatencyToxic))\n}\n"
  },
  {
    "path": "toxics/latency_test.go",
    "content": "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\"github.com/Shopify/toxiproxy/v2\"\n\t\"github.com/Shopify/toxiproxy/v2/testhelper\"\n\t\"github.com/Shopify/toxiproxy/v2/toxics\"\n)\n\nfunc AssertDeltaTime(t *testing.T, message string, actual, expected, delta time.Duration) {\n\tdiff := actual - expected\n\tif diff < 0 {\n\t\tdiff *= -1\n\t}\n\tif diff > delta {\n\t\tt.Errorf(\n\t\t\t\"[%s] Time was more than %v off: got %v expected %v\",\n\t\t\tmessage,\n\t\t\tdelta,\n\t\t\tactual,\n\t\t\texpected,\n\t\t)\n\t} else {\n\t\tt.Logf(\"[%s] Time was correct: %v (expected %v)\", message, actual, expected)\n\t}\n}\n\nfunc DoLatencyTest(t *testing.T, upLatency, downLatency *toxics.LatencyToxic) {\n\tWithEchoProxy(t, func(conn net.Conn, response chan []byte, proxy *toxiproxy.Proxy) {\n\t\tif upLatency == nil {\n\t\t\tupLatency = &toxics.LatencyToxic{}\n\t\t} else {\n\t\t\t_, err := proxy.Toxics.AddToxicJson(\n\t\t\t\tToxicToJson(t, \"latency_up\", \"latency\", \"upstream\", upLatency),\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(\"AddToxicJson returned error:\", err)\n\t\t\t}\n\t\t}\n\t\tif downLatency == nil {\n\t\t\tdownLatency = &toxics.LatencyToxic{}\n\t\t} else {\n\t\t\t_, err := proxy.Toxics.AddToxicJson(\n\t\t\t\tToxicToJson(t, \"latency_down\", \"latency\", \"downstream\", downLatency),\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(\"AddToxicJson returned error:\", err)\n\t\t\t}\n\t\t}\n\t\tt.Logf(\n\t\t\t\"Using latency: Up: %dms +/- %dms, Down: %dms +/- %dms\",\n\t\t\tupLatency.Latency,\n\t\t\tupLatency.Jitter,\n\t\t\tdownLatency.Latency,\n\t\t\tdownLatency.Jitter,\n\t\t)\n\n\t\tmsg := []byte(\"hello world \" + strings.Repeat(\"a\", 32*1024) + \"\\n\")\n\n\t\ttimer := time.Now()\n\t\t_, err := conn.Write(msg)\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed writing to TCP server\", err)\n\t\t}\n\n\t\tresp := <-response\n\t\tif !bytes.Equal(resp, msg) {\n\t\t\tt.Error(\"Server didn't read correct bytes from client:\", string(resp))\n\t\t}\n\t\tAssertDeltaTime(t,\n\t\t\t\"Server read\",\n\t\t\ttime.Since(timer),\n\t\t\ttime.Duration(upLatency.Latency)*time.Millisecond,\n\t\t\ttime.Duration(upLatency.Jitter+10)*time.Millisecond,\n\t\t)\n\t\ttimer2 := time.Now()\n\n\t\tscan := bufio.NewScanner(conn)\n\t\tif scan.Scan() {\n\t\t\tresp = append(scan.Bytes(), '\\n')\n\t\t\tif !bytes.Equal(resp, msg) {\n\t\t\t\tt.Error(\"Client didn't read correct bytes from server:\", string(resp))\n\t\t\t}\n\t\t}\n\t\tAssertDeltaTime(t,\n\t\t\t\"Client read\",\n\t\t\ttime.Since(timer2),\n\t\t\ttime.Duration(downLatency.Latency)*time.Millisecond,\n\t\t\ttime.Duration(downLatency.Jitter+10)*time.Millisecond,\n\t\t)\n\t\tAssertDeltaTime(t,\n\t\t\t\"Round trip\",\n\t\t\ttime.Since(timer),\n\t\t\ttime.Duration(upLatency.Latency+downLatency.Latency)*time.Millisecond,\n\t\t\ttime.Duration(upLatency.Jitter+downLatency.Jitter+20)*time.Millisecond,\n\t\t)\n\n\t\tctx := context.Background()\n\t\tproxy.Toxics.RemoveToxic(ctx, \"latency_up\")\n\t\tproxy.Toxics.RemoveToxic(ctx, \"latency_down\")\n\n\t\terr = conn.Close()\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed to close TCP connection\", err)\n\t\t}\n\t})\n}\n\nfunc TestUpstreamLatency(t *testing.T) {\n\tDoLatencyTest(t, &toxics.LatencyToxic{Latency: 100}, nil)\n}\n\nfunc TestDownstreamLatency(t *testing.T) {\n\tDoLatencyTest(t, nil, &toxics.LatencyToxic{Latency: 100})\n}\n\nfunc TestFullstreamLatencyEven(t *testing.T) {\n\tDoLatencyTest(t, &toxics.LatencyToxic{Latency: 100}, &toxics.LatencyToxic{Latency: 100})\n}\n\nfunc TestFullstreamLatencyBiasUp(t *testing.T) {\n\tDoLatencyTest(t, &toxics.LatencyToxic{Latency: 1000}, &toxics.LatencyToxic{Latency: 100})\n}\n\nfunc TestFullstreamLatencyBiasDown(t *testing.T) {\n\tDoLatencyTest(t, &toxics.LatencyToxic{Latency: 100}, &toxics.LatencyToxic{Latency: 1000})\n}\n\nfunc TestZeroLatency(t *testing.T) {\n\tDoLatencyTest(t, &toxics.LatencyToxic{Latency: 0}, &toxics.LatencyToxic{Latency: 0})\n}\n\nfunc TestLatencyToxicCloseRace(t *testing.T) {\n\tln, err := net.Listen(\"tcp\", \"localhost:0\")\n\tif err != nil {\n\t\tt.Fatal(\"Failed to create TCP server\", err)\n\t}\n\n\tdefer ln.Close()\n\n\tproxy := NewTestProxy(\"test\", ln.Addr().String())\n\tproxy.Start()\n\tdefer proxy.Stop()\n\n\tgo func() {\n\t\tfor {\n\t\t\t_, err := ln.Accept()\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\t// Check for potential race conditions when interrupting toxics\n\tfor i := 0; i < 1000; i++ {\n\t\tproxy.Toxics.AddToxicJson(\n\t\t\tToxicToJson(t, \"\", \"latency\", \"upstream\", &toxics.LatencyToxic{Latency: 10}),\n\t\t)\n\t\tconn, err := net.Dial(\"tcp\", proxy.Listen)\n\t\tif err != nil {\n\t\t\tt.Error(\"Unable to dial TCP server\", err)\n\t\t}\n\t\tconn.Write([]byte(\"hello\"))\n\t\tconn.Close()\n\t\tproxy.Toxics.RemoveToxic(context.Background(), \"latency\")\n\t}\n}\n\nfunc TestTwoLatencyToxics(t *testing.T) {\n\tWithEchoProxy(t, func(conn net.Conn, response chan []byte, proxy *toxiproxy.Proxy) {\n\t\ttoxics := []*toxics.LatencyToxic{{Latency: 500}, {Latency: 500}}\n\t\tfor i, toxic := range toxics {\n\t\t\t_, err := proxy.Toxics.AddToxicJson(\n\t\t\t\tToxicToJson(t, \"latency_\"+strconv.Itoa(i), \"latency\", \"upstream\", toxic),\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(\"AddToxicJson returned error:\", err)\n\t\t\t}\n\t\t}\n\n\t\tmsg := []byte(\"hello world \" + strings.Repeat(\"a\", 32*1024) + \"\\n\")\n\n\t\ttimer := time.Now()\n\t\t_, err := conn.Write(msg)\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed writing to TCP server\", err)\n\t\t}\n\n\t\tresp := <-response\n\t\tif !bytes.Equal(resp, msg) {\n\t\t\tt.Error(\"Server didn't read correct bytes from client:\", string(resp))\n\t\t}\n\n\t\tAssertDeltaTime(t,\n\t\t\t\"Upstream two latency toxics\",\n\t\t\ttime.Since(timer),\n\t\t\ttime.Duration(1000)*time.Millisecond,\n\t\t\ttime.Duration(10)*time.Millisecond,\n\t\t)\n\n\t\tfor i := range toxics {\n\t\t\tproxy.Toxics.RemoveToxic(context.Background(), \"latency_\"+strconv.Itoa(i))\n\t\t}\n\n\t\terr = conn.Close()\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed to close TCP connection\", err)\n\t\t}\n\t})\n}\n\nfunc TestLatencyToxicBandwidth(t *testing.T) {\n\tupstream := testhelper.NewUpstream(t, false)\n\tdefer upstream.Close()\n\n\tproxy := NewTestProxy(\"test\", upstream.Addr())\n\tproxy.Start()\n\tdefer proxy.Stop()\n\n\tclient, err := net.Dial(\"tcp\", proxy.Listen)\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to dial TCP server: %v\", err)\n\t}\n\n\twrittenPayload := []byte(strings.Repeat(\"hello world \", 1000))\n\tupstreamConn := <-upstream.Connections\n\tgo func(conn net.Conn, payload []byte) {\n\t\tvar err error\n\t\tfor err == nil {\n\t\t\t_, err = conn.Write(payload)\n\t\t}\n\t}(upstreamConn, writtenPayload)\n\n\tproxy.Toxics.AddToxicJson(ToxicToJson(t, \"\", \"latency\", \"\", &toxics.LatencyToxic{Latency: 100}))\n\n\ttime.Sleep(150 * time.Millisecond) // Wait for latency toxic\n\tresponse := make([]byte, len(writtenPayload))\n\n\tstart := time.Now()\n\tcount := 0\n\tfor i := 0; i < 100; i++ {\n\t\tn, err := io.ReadFull(client, response)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Could not read from socket: %v\", err)\n\t\t\tbreak\n\t\t}\n\t\tcount += n\n\t}\n\n\t// Assert the transfer was at least 100MB/s\n\tAssertDeltaTime(\n\t\tt,\n\t\t\"Latency toxic bandwidth\",\n\t\ttime.Since(start),\n\t\t0,\n\t\ttime.Duration(count/100000)*time.Millisecond,\n\t)\n\n\terr = client.Close()\n\tif err != nil {\n\t\tt.Error(\"Failed to close TCP connection\", err)\n\t}\n}\n"
  },
  {
    "path": "toxics/limit_data.go",
    "content": "package toxics\n\nimport \"github.com/Shopify/toxiproxy/v2/stream\"\n\n// LimitDataToxic has limit in bytes.\ntype LimitDataToxic struct {\n\tBytes int64 `json:\"bytes\"`\n}\n\ntype LimitDataToxicState struct {\n\tbytesTransmitted int64\n}\n\nfunc (t *LimitDataToxic) Pipe(stub *ToxicStub) {\n\tstate := stub.State.(*LimitDataToxicState)\n\tbytesRemaining := t.Bytes - state.bytesTransmitted\n\n\tfor {\n\t\tselect {\n\t\tcase <-stub.Interrupt:\n\t\t\treturn\n\t\tcase c := <-stub.Input:\n\t\t\tif c == nil {\n\t\t\t\tstub.Close()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif bytesRemaining < 0 {\n\t\t\t\tbytesRemaining = 0\n\t\t\t}\n\n\t\t\tif bytesRemaining < int64(len(c.Data)) {\n\t\t\t\tc = &stream.StreamChunk{\n\t\t\t\t\tTimestamp: c.Timestamp,\n\t\t\t\t\tData:      c.Data[0:bytesRemaining],\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif len(c.Data) > 0 {\n\t\t\t\tstub.Output <- c\n\t\t\t\tstate.bytesTransmitted += int64(len(c.Data))\n\t\t\t}\n\n\t\t\tbytesRemaining = t.Bytes - state.bytesTransmitted\n\n\t\t\tif bytesRemaining <= 0 {\n\t\t\t\tstub.Close()\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (t *LimitDataToxic) NewState() interface{} {\n\treturn new(LimitDataToxicState)\n}\n\nfunc init() {\n\tRegister(\"limit_data\", new(LimitDataToxic))\n}\n"
  },
  {
    "path": "toxics/limit_data_test.go",
    "content": "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/Shopify/toxiproxy/v2/toxics\"\n)\n\nfunc buffer(size int) []byte {\n\tbuf := make([]byte, size)\n\t// #nosec G404 -- used only in tests\n\trand.Read(buf)\n\n\treturn buf\n}\n\nfunc checkOutgoingChunk(t *testing.T, output chan *stream.StreamChunk, expected []byte) {\n\tchunk := <-output\n\tif !bytes.Equal(chunk.Data, expected) {\n\t\tt.Error(\"Data in outgoing chunk doesn't match expected values\")\n\t}\n}\n\nfunc checkRemainingChunks(t *testing.T, output chan *stream.StreamChunk) {\n\tif len(output) != 0 {\n\t\tt.Errorf(\"There is %d chunks in output channel. 0 is expected.\", len(output))\n\t}\n}\n\nfunc check(t *testing.T, toxic *toxics.LimitDataToxic, chunks [][]byte, expectedChunks [][]byte) {\n\tinput := make(chan *stream.StreamChunk)\n\toutput := make(chan *stream.StreamChunk, 100)\n\tstub := toxics.NewToxicStub(input, output)\n\tstub.State = toxic.NewState()\n\n\tgo toxic.Pipe(stub)\n\n\tfor _, buf := range chunks {\n\t\tinput <- &stream.StreamChunk{Data: buf}\n\t}\n\n\tfor _, expected := range expectedChunks {\n\t\tcheckOutgoingChunk(t, output, expected)\n\t}\n\n\tcheckRemainingChunks(t, output)\n}\n\nfunc TestLimitDataToxicMayBeRestarted(t *testing.T) {\n\ttoxic := &toxics.LimitDataToxic{Bytes: 100}\n\n\tinput := make(chan *stream.StreamChunk)\n\toutput := make(chan *stream.StreamChunk, 100)\n\tstub := toxics.NewToxicStub(input, output)\n\tstub.State = toxic.NewState()\n\n\tbuf := buffer(90)\n\tbuf2 := buffer(20)\n\n\t// Send chunk with data not exceeding limit and interrupt\n\tgo func() {\n\t\tinput <- &stream.StreamChunk{Data: buf}\n\t\tstub.Interrupt <- struct{}{}\n\t}()\n\n\ttoxic.Pipe(stub)\n\tcheckOutgoingChunk(t, output, buf)\n\n\t// Send 2nd chunk to exceed limit\n\tgo func() {\n\t\tinput <- &stream.StreamChunk{Data: buf2}\n\t}()\n\n\ttoxic.Pipe(stub)\n\tcheckOutgoingChunk(t, output, buf2[0:10])\n\n\tcheckRemainingChunks(t, output)\n}\n\nfunc TestLimitDataToxicMayBeInterrupted(t *testing.T) {\n\ttoxic := &toxics.LimitDataToxic{Bytes: 100}\n\n\tinput := make(chan *stream.StreamChunk)\n\toutput := make(chan *stream.StreamChunk)\n\tstub := toxics.NewToxicStub(input, output)\n\tstub.State = toxic.NewState()\n\n\tgo func() {\n\t\tstub.Interrupt <- struct{}{}\n\t}()\n\n\ttoxic.Pipe(stub)\n}\n\nfunc TestLimitDataToxicNilShouldClosePipe(t *testing.T) {\n\ttoxic := &toxics.LimitDataToxic{Bytes: 100}\n\n\tinput := make(chan *stream.StreamChunk)\n\toutput := make(chan *stream.StreamChunk)\n\tstub := toxics.NewToxicStub(input, output)\n\tstub.State = toxic.NewState()\n\n\tgo func() {\n\t\tinput <- nil\n\t}()\n\n\ttoxic.Pipe(stub)\n}\n\nfunc TestLimitDataToxicChunkSmallerThanLimit(t *testing.T) {\n\ttoxic := &toxics.LimitDataToxic{Bytes: 100}\n\n\tbuf := buffer(50)\n\tcheck(t, toxic, [][]byte{buf}, [][]byte{buf})\n}\n\nfunc TestLimitDataToxicChunkLengthMatchesLimit(t *testing.T) {\n\ttoxic := &toxics.LimitDataToxic{Bytes: 100}\n\n\tbuf := buffer(100)\n\tcheck(t, toxic, [][]byte{buf}, [][]byte{buf})\n}\n\nfunc TestLimitDataToxicChunkBiggerThanLimit(t *testing.T) {\n\ttoxic := &toxics.LimitDataToxic{Bytes: 100}\n\n\tbuf := buffer(150)\n\texpected := buf[0:100]\n\n\tcheck(t, toxic, [][]byte{buf}, [][]byte{expected})\n}\n\nfunc TestLimitDataToxicMultipleChunksMatchThanLimit(t *testing.T) {\n\ttoxic := &toxics.LimitDataToxic{Bytes: 100}\n\n\tbuf := buffer(25)\n\n\tcheck(t, toxic, [][]byte{buf, buf, buf, buf}, [][]byte{buf, buf, buf, buf})\n}\n\nfunc TestLimitDataToxicSecondChunkWouldOverflowLimit(t *testing.T) {\n\ttoxic := &toxics.LimitDataToxic{Bytes: 100}\n\n\tbuf := buffer(90)\n\tbuf2 := buffer(20)\n\texpected := buf2[0:10]\n\n\tcheck(t, toxic, [][]byte{buf, buf2}, [][]byte{buf, expected})\n}\n\nfunc TestLimitDataToxicLimitIsSetToZero(t *testing.T) {\n\ttoxic := &toxics.LimitDataToxic{Bytes: 0}\n\n\tbuf := buffer(100)\n\n\tcheck(t, toxic, [][]byte{buf}, [][]byte{})\n}\n"
  },
  {
    "path": "toxics/noop.go",
    "content": "package toxics\n\n// The NoopToxic passes all data through without any toxic effects.\ntype NoopToxic struct{}\n\nfunc (t *NoopToxic) Pipe(stub *ToxicStub) {\n\tfor {\n\t\tselect {\n\t\tcase <-stub.Interrupt:\n\t\t\treturn\n\t\tcase c := <-stub.Input:\n\t\t\tif c == nil {\n\t\t\t\tstub.Close()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tstub.Output <- c\n\t\t}\n\t}\n}\n\nfunc init() {\n\tRegister(\"noop\", new(NoopToxic))\n}\n"
  },
  {
    "path": "toxics/reset_peer.go",
    "content": "package toxics\n\nimport (\n\t\"time\"\n)\n\n/*\nThe ResetToxic sends closes the connection abruptly after a timeout (in ms).\nThe behavior of Close is set to discard any unsent/unacknowledged data by setting SetLinger to 0,\n~= sets TCP RST flag and resets the connection.\nIf the timeout is set to 0, then the connection will be reset immediately.\n\nDrop data since it will initiate a graceful close by sending the FIN/ACK. (io.EOF)\n*/\n\ntype ResetToxic struct {\n\t// Timeout in milliseconds\n\tTimeout int64 `json:\"timeout\"`\n}\n\nfunc (t *ResetToxic) Pipe(stub *ToxicStub) {\n\ttimeout := time.Duration(t.Timeout) * time.Millisecond\n\n\tfor {\n\t\tselect {\n\t\tcase <-stub.Interrupt:\n\t\t\treturn\n\t\tcase <-stub.Input:\n\t\t\t<-time.After(timeout)\n\t\t\tstub.Close()\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc init() {\n\tRegister(\"reset_peer\", new(ResetToxic))\n}\n"
  },
  {
    "path": "toxics/reset_peer_test.go",
    "content": "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/v2/toxics\"\n)\n\nconst msg = \"reset toxic payload\\n\"\n\nfunc TestResetToxicNoTimeout(t *testing.T) {\n\tresetTCPHelper(t, ToxicToJson(t, \"resettcp\", \"reset_peer\", \"upstream\", &toxics.ResetToxic{}))\n}\n\nfunc TestResetToxicWithTimeout(t *testing.T) {\n\tstart := time.Now()\n\tresetToxic := toxics.ResetToxic{Timeout: 100}\n\tresetTCPHelper(t, ToxicToJson(t, \"resettcp\", \"reset_peer\", \"upstream\", &resetToxic))\n\tAssertDeltaTime(t,\n\t\t\"Reset after timeout\",\n\t\ttime.Since(start),\n\t\ttime.Duration(resetToxic.Timeout)*time.Millisecond,\n\t\ttime.Duration(resetToxic.Timeout+10)*time.Millisecond,\n\t)\n}\n\nfunc TestResetToxicWithTimeoutDownstream(t *testing.T) {\n\tstart := time.Now()\n\tresetToxic := toxics.ResetToxic{Timeout: 100}\n\tresetTCPHelper(t, ToxicToJson(t, \"resettcp\", \"reset_peer\", \"downstream\", &resetToxic))\n\tAssertDeltaTime(t,\n\t\t\"Reset after timeout\",\n\t\ttime.Since(start),\n\t\ttime.Duration(resetToxic.Timeout)*time.Millisecond,\n\t\ttime.Duration(resetToxic.Timeout+10)*time.Millisecond,\n\t)\n}\n\nfunc checkConnectionState(t *testing.T, listenAddress string) {\n\tconn, err := net.Dial(\"tcp\", listenAddress)\n\tif err != nil {\n\t\tt.Error(\"Unable to dial TCP server\", err)\n\t}\n\tif _, err := conn.Write([]byte(msg)); err != nil {\n\t\tt.Error(\"Failed writing TCP payload\", err)\n\t}\n\ttmp := make([]byte, 1000)\n\t_, err = conn.Read(tmp)\n\tdefer conn.Close()\n\tif opErr, ok := err.(*net.OpError); ok {\n\t\tsyscallErr, _ := opErr.Err.(*os.SyscallError)\n\t\tif syscallErr.Err != syscall.ECONNRESET {\n\t\t\tt.Error(\"Expected: connection reset by peer. Got:\", err)\n\t\t}\n\t} else {\n\t\tt.Error(\n\t\t\t\"Expected: connection reset by peer. Got:\",\n\t\t\terr, \"conn:\", conn.RemoteAddr(), conn.LocalAddr(),\n\t\t)\n\t}\n\t_, err = conn.Read(tmp)\n\tif err != io.EOF {\n\t\tt.Error(\"expected EOF from closed connection\")\n\t}\n}\n\nfunc resetTCPHelper(t *testing.T, toxicJSON io.Reader) {\n\tln, err := net.Listen(\"tcp\", \"localhost:0\")\n\tif err != nil {\n\t\tt.Fatal(\"Failed to create TCP server\", err)\n\t}\n\tdefer ln.Close()\n\tproxy := NewTestProxy(\"test\", ln.Addr().String())\n\tproxy.Start()\n\tproxy.Toxics.AddToxicJson(toxicJSON)\n\tdefer proxy.Stop()\n\n\tgo func() {\n\t\tconn, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tt.Error(\"Unable to accept TCP connection\", err)\n\t\t}\n\t\tdefer ln.Close()\n\t\tscan := bufio.NewScanner(conn)\n\t\tif scan.Scan() {\n\t\t\tconn.Write([]byte(msg))\n\t\t}\n\t}()\n\tcheckConnectionState(t, proxy.Listen)\n}\n"
  },
  {
    "path": "toxics/slicer.go",
    "content": "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 data into multiple smaller packets\n// to simulate real-world TCP behavior.\ntype SlicerToxic struct {\n\t// Average number of bytes to slice at\n\tAverageSize int `json:\"average_size\"`\n\t// +/- bytes to vary sliced amounts. Must be less than\n\t// the average size\n\tSizeVariation int `json:\"size_variation\"`\n\t// Microseconds to delay each packet. May be useful since there's\n\t// usually some kind of buffering of network data\n\tDelay int `json:\"delay\"`\n}\n\n// Returns a list of chunk offsets to slice up a packet of the\n// given total size. For example, for a size of 100, output might be:\n//\n// |    []int{0, 18, 18, 43, 43, 67, 67, 77, 77, 100}\n// |          ^---^  ^----^  ^----^  ^----^  ^-----^\n//\n// This tries to get fairly evenly-varying chunks (no tendency\n// to have a small/large chunk at the start/end).\nfunc (t *SlicerToxic) chunk(start int, end int) []int {\n\t// Base case:\n\t// If the size is within the random varation, _or already\n\t// less than the average size_, just return it.\n\t// Otherwise split the chunk in about two, and recurse.\n\tif (end-start)-t.AverageSize <= t.SizeVariation {\n\t\treturn []int{start, end}\n\t}\n\n\tmid := start + (end-start)/2\n\n\tif t.SizeVariation > 0 {\n\t\tmid += rand.Intn(t.SizeVariation*2) - t.SizeVariation // #nosec G404 -- was ignored before too\n\t}\n\tleft := t.chunk(start, mid)\n\tright := t.chunk(mid, end)\n\n\treturn append(left, right...)\n}\n\nfunc (t *SlicerToxic) Pipe(stub *ToxicStub) {\n\tfor {\n\t\tselect {\n\t\tcase <-stub.Interrupt:\n\t\t\treturn\n\t\tcase c := <-stub.Input:\n\t\t\tif c == nil {\n\t\t\t\tstub.Close()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tchunks := t.chunk(0, len(c.Data))\n\t\t\tfor i := 1; i < len(chunks); i += 2 {\n\t\t\t\tstub.Output <- &stream.StreamChunk{\n\t\t\t\t\tData:      c.Data[chunks[i-1]:chunks[i]],\n\t\t\t\t\tTimestamp: c.Timestamp,\n\t\t\t\t}\n\n\t\t\t\tselect {\n\t\t\t\tcase <-stub.Interrupt:\n\t\t\t\t\tstub.Output <- &stream.StreamChunk{\n\t\t\t\t\t\tData:      c.Data[chunks[i]:],\n\t\t\t\t\t\tTimestamp: c.Timestamp,\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\tcase <-time.After(time.Duration(t.Delay) * time.Microsecond):\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc init() {\n\tRegister(\"slicer\", new(SlicerToxic))\n}\n"
  },
  {
    "path": "toxics/slicer_test.go",
    "content": "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.com/Shopify/toxiproxy/v2/toxics\"\n)\n\nfunc TestSlicerToxic(t *testing.T) {\n\tdata := []byte(strings.Repeat(\"hello world \", 40000)) // 480 kb\n\tslicer := &toxics.SlicerToxic{AverageSize: 1024, SizeVariation: 512, Delay: 10}\n\n\tinput := make(chan *stream.StreamChunk)\n\toutput := make(chan *stream.StreamChunk)\n\tstub := toxics.NewToxicStub(input, output)\n\n\tdone := make(chan bool)\n\tgo func() {\n\t\tslicer.Pipe(stub)\n\t\tdone <- true\n\t}()\n\tdefer func() {\n\t\tclose(input)\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-done:\n\t\t\t\treturn\n\t\t\tcase <-output:\n\t\t\t}\n\t\t}\n\t}()\n\n\tinput <- &stream.StreamChunk{Data: data}\n\n\tbuf := make([]byte, 0, len(data))\n\treads := 0\n\n\tfor timeout := false; !timeout; {\n\t\tselect {\n\t\tcase c := <-output:\n\t\t\treads++\n\t\t\tbuf = append(buf, c.Data...)\n\t\tcase <-time.After(10 * time.Millisecond):\n\t\t\ttimeout = true\n\t\t}\n\t}\n\n\tif reads < 480/2 || reads > 480/2+480 {\n\t\tt.Errorf(\"Expected to read about 480 times, but read %d times.\", reads)\n\t}\n\tif !bytes.Equal(buf, data) {\n\t\tt.Errorf(\"Server did not read correct buffer from client!\")\n\t}\n}\n\nfunc TestSlicerToxicZeroSizeVariation(t *testing.T) {\n\tdata := []byte(strings.Repeat(\"hello world \", 2)) // 24 bytes\n\t// SizeVariation: 0 by default\n\tslicer := &toxics.SlicerToxic{AverageSize: 1, Delay: 10}\n\n\tinput := make(chan *stream.StreamChunk)\n\toutput := make(chan *stream.StreamChunk)\n\tstub := toxics.NewToxicStub(input, output)\n\n\tdone := make(chan bool)\n\tgo func() {\n\t\tslicer.Pipe(stub)\n\t\tdone <- true\n\t}()\n\tdefer func() {\n\t\tclose(input)\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-done:\n\t\t\t\treturn\n\t\t\tcase <-output:\n\t\t\t}\n\t\t}\n\t}()\n\n\tinput <- &stream.StreamChunk{Data: data}\n\n\tbuf := make([]byte, 0, len(data))\n\treads := 0\n\n\tfor timeout := false; !timeout; {\n\t\tselect {\n\t\tcase c := <-output:\n\t\t\treads++\n\t\t\tbuf = append(buf, c.Data...)\n\t\tcase <-time.After(10 * time.Millisecond):\n\t\t\ttimeout = true\n\t\t}\n\t}\n\n\tif reads != 24 {\n\t\tt.Errorf(\"Expected to read 24 times, but read %d times.\", reads)\n\t}\n\tif !bytes.Equal(buf, data) {\n\t\tt.Errorf(\"Server did not read correct buffer from client!\")\n\t}\n}\n"
  },
  {
    "path": "toxics/slow_close.go",
    "content": "package toxics\n\nimport \"time\"\n\n// The SlowCloseToxic stops the TCP connection from closing until after a delay.\ntype SlowCloseToxic struct {\n\t// Times in milliseconds\n\tDelay int64 `json:\"delay\"`\n}\n\nfunc (t *SlowCloseToxic) Pipe(stub *ToxicStub) {\n\tfor {\n\t\tselect {\n\t\tcase <-stub.Interrupt:\n\t\t\treturn\n\t\tcase c := <-stub.Input:\n\t\t\tif c == nil {\n\t\t\t\tdelay := time.Duration(t.Delay) * time.Millisecond\n\t\t\t\tselect {\n\t\t\t\tcase <-time.After(delay):\n\t\t\t\t\tstub.Close()\n\t\t\t\t\treturn\n\t\t\t\tcase <-stub.Interrupt:\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tstub.Output <- c\n\t\t}\n\t}\n}\n\nfunc init() {\n\tRegister(\"slow_close\", new(SlowCloseToxic))\n}\n"
  },
  {
    "path": "toxics/timeout.go",
    "content": "package toxics\n\nimport \"time\"\n\n// The TimeoutToxic stops any data from flowing through,\n// and will close the connection after a timeout.\n// If the timeout is set to 0, then the connection will not be closed.\ntype TimeoutToxic struct {\n\t// Times in milliseconds\n\tTimeout int64 `json:\"timeout\"`\n}\n\nfunc (t *TimeoutToxic) Pipe(stub *ToxicStub) {\n\ttimeout := time.Duration(t.Timeout) * time.Millisecond\n\tif timeout > 0 {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-time.After(timeout):\n\t\t\t\tstub.Close()\n\t\t\t\treturn\n\t\t\tcase <-stub.Interrupt:\n\t\t\t\treturn\n\t\t\tcase c := <-stub.Input:\n\t\t\t\tif c == nil {\n\t\t\t\t\tstub.Close()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// Drop the data on the ground.\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-stub.Interrupt:\n\t\t\t\treturn\n\t\t\tcase c := <-stub.Input:\n\t\t\t\tif c == nil {\n\t\t\t\t\tstub.Close()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// Drop the data on the ground.\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (t *TimeoutToxic) Cleanup(stub *ToxicStub) {\n\tstub.Close()\n}\n\nfunc init() {\n\tRegister(\"timeout\", new(TimeoutToxic))\n}\n"
  },
  {
    "path": "toxics/timeout_test.go",
    "content": "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\"github.com/Shopify/toxiproxy/v2/testhelper\"\n\t\"github.com/Shopify/toxiproxy/v2/toxics\"\n)\n\nfunc WithEstablishedProxy(t *testing.T, f func(net.Conn, net.Conn, *toxiproxy.Proxy)) {\n\tln, err := net.Listen(\"tcp\", \"localhost:0\")\n\tif err != nil {\n\t\tt.Fatal(\"Failed to create TCP server\", err)\n\t}\n\tdefer ln.Close()\n\n\tproxy := NewTestProxy(\"test\", ln.Addr().String())\n\tproxy.Start()\n\tdefer proxy.Stop()\n\n\tserverConnRecv := make(chan net.Conn)\n\tgo func() {\n\t\tconn, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tt.Error(\"Unable to accept TCP connection\", err)\n\t\t}\n\t\tserverConnRecv <- conn\n\t}()\n\n\tconn, err := net.Dial(\"tcp\", proxy.Listen)\n\tif err != nil {\n\t\tt.Fatal(\"Unable to dial TCP server\", err)\n\t}\n\tdefer conn.Close()\n\n\tserverConn := <-serverConnRecv\n\tdefer serverConn.Close()\n\n\twriteAndReceive := func(from, to net.Conn) {\n\t\tdata := []byte(\"foobar\")\n\t\t_, err := from.Write(data)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\terr = testhelper.TimeoutAfter(time.Second, func() {\n\t\t\tresp := make([]byte, len(data))\n\t\t\tto.Read(resp)\n\t\t\tif !bytes.Equal(resp, data) {\n\t\t\t\tt.Fatalf(\"expected '%s' but got '%s'\", string(data), string(resp))\n\t\t\t}\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\n\t// Make sure we can send and receive data before continuing.\n\twriteAndReceive(conn, serverConn)\n\twriteAndReceive(serverConn, conn)\n\n\tf(conn, serverConn, proxy)\n}\n\nfunc TestTimeoutToxicDoesNotCauseHang(t *testing.T) {\n\tWithEstablishedProxy(t, func(conn, _ net.Conn, proxy *toxiproxy.Proxy) {\n\t\tproxy.Toxics.AddToxicJson(\n\t\t\tToxicToJson(t, \"might_block\", \"latency\", \"upstream\", &toxics.LatencyToxic{Latency: 10}),\n\t\t)\n\t\tproxy.Toxics.AddToxicJson(\n\t\t\tToxicToJson(t, \"timeout\", \"timeout\", \"upstream\", &toxics.TimeoutToxic{Timeout: 0}),\n\t\t)\n\n\t\tfor i := 0; i < 5; i++ {\n\t\t\t_, err := conn.Write([]byte(\"hello\"))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"Unable to write to proxy\", err)\n\t\t\t}\n\t\t\ttime.Sleep(200 * time.Millisecond) // Shitty sync waiting for latency toxi to get data.\n\t\t}\n\n\t\terr := testhelper.TimeoutAfter(time.Second, func() {\n\t\t\tproxy.Toxics.RemoveToxic(context.Background(), \"might_block\")\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n}\n\nfunc TestTimeoutToxicClosesConnectionOnRemove(t *testing.T) {\n\tWithEstablishedProxy(t, func(conn, serverConn net.Conn, proxy *toxiproxy.Proxy) {\n\t\tproxy.Toxics.AddToxicJson(\n\t\t\tToxicToJson(t, \"to_delete\", \"timeout\", \"upstream\", &toxics.TimeoutToxic{Timeout: 0}),\n\t\t)\n\n\t\tproxy.Toxics.RemoveToxic(context.Background(), \"to_delete\")\n\n\t\terr := testhelper.TimeoutAfter(time.Second, func() {\n\t\t\tbuf := make([]byte, 1)\n\t\t\t_, err := conn.Read(buf)\n\t\t\tif err != io.EOF {\n\t\t\t\tt.Fatal(\"expected EOF from closed connetion\")\n\t\t\t}\n\t\t\t_, err = serverConn.Read(buf)\n\t\t\tif err != io.EOF {\n\t\t\t\tt.Fatal(\"expected EOF from closed server connetion\")\n\t\t\t}\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "toxics/toxic.go",
    "content": "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// A Toxic is something that can be attatched to a link to modify the way\n// data can be passed through (for example, by adding latency)\n//\n//              Toxic\n//                v\n// Client <-> ToxicStub <-> Upstream\n//\n// Toxic's work in a pipeline fashion, and can be chained together\n// with channels. The toxic itself only defines the settings and\n// Pipe() function definition, and uses the ToxicStub struct to store\n// per-connection information. This allows the same toxic to be used\n// for multiple connections.\n\ntype Toxic interface {\n\t// Defines how packets flow through a ToxicStub.\n\t// Pipe() blocks until the link is closed or interrupted.\n\tPipe(*ToxicStub)\n}\n\ntype CleanupToxic interface {\n\t// Cleanup is called before a toxic is removed.\n\tCleanup(*ToxicStub)\n}\n\ntype BufferedToxic interface {\n\t// Defines the size of buffer this toxic should use\n\tGetBufferSize() int\n}\n\n// Stateful toxics store a per-connection state object on the ToxicStub.\n// The state is created once when the toxic is added and persists until the\n// toxic is removed or the connection is closed.\ntype StatefulToxic interface {\n\t// Creates a new object to store toxic state in\n\tNewState() interface{}\n}\n\ntype ToxicWrapper struct {\n\tToxic      `json:\"attributes\"`\n\tName       string           `json:\"name\"`\n\tType       string           `json:\"type\"`\n\tStream     string           `json:\"stream\"`\n\tToxicity   float32          `json:\"toxicity\"`\n\tDirection  stream.Direction `json:\"-\"`\n\tIndex      int              `json:\"-\"`\n\tBufferSize int              `json:\"-\"`\n}\n\ntype ToxicStub struct {\n\tInput     <-chan *stream.StreamChunk\n\tOutput    chan<- *stream.StreamChunk\n\tState     interface{}\n\tInterrupt chan struct{}\n\trunning   chan struct{}\n\tclosed    chan struct{}\n}\n\nfunc NewToxicStub(input <-chan *stream.StreamChunk, output chan<- *stream.StreamChunk) *ToxicStub {\n\treturn &ToxicStub{\n\t\tInterrupt: make(chan struct{}),\n\t\tclosed:    make(chan struct{}),\n\t\tInput:     input,\n\t\tOutput:    output,\n\t}\n}\n\n// Begin running a toxic on this stub, can be interrupted.\n// Runs a noop toxic randomly depending on toxicity.\nfunc (s *ToxicStub) Run(toxic *ToxicWrapper) {\n\ts.running = make(chan struct{})\n\tdefer close(s.running)\n\trandomToxicity := rand.Float32() // #nosec G404 -- was ignored before too\n\tif randomToxicity < toxic.Toxicity {\n\t\ttoxic.Pipe(s)\n\t} else {\n\t\tnew(NoopToxic).Pipe(s)\n\t}\n}\n\n// WriteOutput allows to write to Output with timeout to avoid deadlocks.\n// If duration is 0, then wait until other goroutines finish reading from Output.\nfunc (s *ToxicStub) WriteOutput(p *stream.StreamChunk, d time.Duration) error {\n\tif d == 0 {\n\t\ts.Output <- p\n\t\treturn nil\n\t}\n\n\tselect {\n\tcase s.Output <- p:\n\t\treturn nil\n\tcase <-time.After(d):\n\t\treturn fmt.Errorf(\"timeout: could not write to output in %d seconds\", int(d.Seconds()))\n\t}\n}\n\n// Interrupt the flow of data so that the toxic controlling the stub can be replaced.\n// Returns true if the stream was successfully interrupted, or false if the stream is closed.\nfunc (s *ToxicStub) InterruptToxic() bool {\n\tselect {\n\tcase <-s.closed:\n\t\treturn false\n\tcase s.Interrupt <- struct{}{}:\n\t\t<-s.running // Wait for the running toxic to exit\n\t\treturn true\n\t}\n}\n\nfunc (s *ToxicStub) Closed() bool {\n\tselect {\n\tcase <-s.closed:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc (s *ToxicStub) Close() {\n\tif !s.Closed() {\n\t\tclose(s.closed)\n\t\tclose(s.Output)\n\t}\n}\n\nvar (\n\tToxicRegistry map[string]Toxic\n\tregistryMutex sync.RWMutex\n)\n\nfunc Register(typeName string, toxic Toxic) {\n\tregistryMutex.Lock()\n\tdefer registryMutex.Unlock()\n\n\tif ToxicRegistry == nil {\n\t\tToxicRegistry = make(map[string]Toxic)\n\t}\n\tToxicRegistry[typeName] = toxic\n}\n\nfunc New(wrapper *ToxicWrapper) Toxic {\n\tregistryMutex.RLock()\n\tdefer registryMutex.RUnlock()\n\n\torig, ok := ToxicRegistry[wrapper.Type]\n\tif !ok {\n\t\treturn nil\n\t}\n\twrapper.Toxic = reflect.New(reflect.TypeOf(orig).Elem()).Interface().(Toxic)\n\tif buffered, ok := wrapper.Toxic.(BufferedToxic); ok {\n\t\twrapper.BufferSize = buffered.GetBufferSize()\n\t} else {\n\t\twrapper.BufferSize = 0\n\t}\n\treturn wrapper.Toxic\n}\n\nfunc Count() int {\n\tregistryMutex.RLock()\n\tdefer registryMutex.RUnlock()\n\n\treturn len(ToxicRegistry)\n}\n"
  },
  {
    "path": "toxics/toxic_test.go",
    "content": "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\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/rs/zerolog\"\n\ttomb \"gopkg.in/tomb.v1\"\n\n\t\"github.com/Shopify/toxiproxy/v2\"\n\t\"github.com/Shopify/toxiproxy/v2/collectors\"\n\t\"github.com/Shopify/toxiproxy/v2/stream\"\n\t\"github.com/Shopify/toxiproxy/v2/toxics\"\n)\n\nfunc NewTestProxy(name, upstream string) *toxiproxy.Proxy {\n\tlog := zerolog.Nop()\n\tif flag.Lookup(\"test.v\").DefValue == \"true\" {\n\t\tlog = zerolog.New(os.Stdout).With().Caller().Timestamp().Logger()\n\t}\n\tsrv := toxiproxy.NewServer(\n\t\ttoxiproxy.NewMetricsContainer(prometheus.NewRegistry()),\n\t\tlog,\n\t)\n\tsrv.Metrics.ProxyMetrics = collectors.NewProxyMetricCollectors()\n\tproxy := toxiproxy.NewProxy(srv, name, \"localhost:0\", upstream)\n\n\treturn proxy\n}\n\nfunc WithEchoServer(t *testing.T, f func(string, chan []byte)) {\n\tln, err := net.Listen(\"tcp\", \"localhost:0\")\n\tif err != nil {\n\t\tt.Fatal(\"Failed to create TCP server\", err)\n\t}\n\n\tdefer ln.Close()\n\n\tresponse := make(chan []byte, 1)\n\ttomb := tomb.Tomb{}\n\n\tgo func() {\n\t\tdefer tomb.Done()\n\t\tsrc, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tselect {\n\t\t\tcase <-tomb.Dying():\n\t\t\tdefault:\n\t\t\t\tt.Error(\"Failed to accept client\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tln.Close()\n\n\t\tscan := bufio.NewScanner(src)\n\t\tif scan.Scan() {\n\t\t\treceived := append(scan.Bytes(), '\\n')\n\t\t\tresponse <- received\n\n\t\t\tsrc.Write(received)\n\t\t}\n\t}()\n\n\tf(ln.Addr().String(), response)\n\n\ttomb.Killf(\"Function body finished\")\n\tln.Close()\n\ttomb.Wait()\n\n\tclose(response)\n}\n\nfunc WithEchoProxy(\n\tt *testing.T,\n\tf func(proxy net.Conn, response chan []byte, proxyServer *toxiproxy.Proxy),\n) {\n\tWithEchoServer(t, func(upstream string, response chan []byte) {\n\t\tproxy := NewTestProxy(\"test\", upstream)\n\t\tproxy.Start()\n\n\t\tconn, err := net.Dial(\"tcp\", proxy.Listen)\n\t\tif err != nil {\n\t\t\tt.Error(\"Unable to dial TCP server\", err)\n\t\t}\n\n\t\tf(conn, response, proxy)\n\n\t\tproxy.Stop()\n\t})\n}\n\nfunc ToxicToJson(t *testing.T, name, typeName, stream string, toxic toxics.Toxic) io.Reader {\n\tdata := map[string]interface{}{\n\t\t\"name\":       name,\n\t\t\"type\":       typeName,\n\t\t\"stream\":     stream,\n\t\t\"attributes\": toxic,\n\t}\n\trequest, err := json.Marshal(data)\n\tif err != nil {\n\t\tt.Errorf(\"Failed to marshal toxic for api (1): %v\", toxic)\n\t}\n\n\treturn bytes.NewReader(request)\n}\n\nfunc AssertEchoResponse(t *testing.T, client, server net.Conn) {\n\tmsg := []byte(\"hello world\\n\")\n\n\t_, err := client.Write(msg)\n\tif err != nil {\n\t\tt.Error(\"Failed writing to TCP server\", err)\n\t}\n\n\tscan := bufio.NewScanner(server)\n\tif !scan.Scan() {\n\t\tt.Error(\"Client unexpectedly closed connection\")\n\t}\n\n\tresp := append(scan.Bytes(), '\\n')\n\tif !bytes.Equal(resp, msg) {\n\t\tt.Error(\"Server didn't read correct bytes from client:\", string(resp))\n\t}\n\n\t_, err = server.Write(resp)\n\tif err != nil {\n\t\tt.Error(\"Failed writing to TCP client\", err)\n\t}\n\n\tscan = bufio.NewScanner(client)\n\tif !scan.Scan() {\n\t\tt.Error(\"Server unexpectedly closed connection\")\n\t}\n\n\tresp = append(scan.Bytes(), '\\n')\n\tif !bytes.Equal(resp, msg) {\n\t\tt.Error(\"Client didn't read correct bytes from server:\", string(resp))\n\t}\n}\n\nfunc TestPersistentConnections(t *testing.T) {\n\tctx := context.Background()\n\n\tln, err := net.Listen(\"tcp\", \"localhost:0\")\n\tif err != nil {\n\t\tt.Fatal(\"Failed to create TCP server\", err)\n\t}\n\n\tdefer ln.Close()\n\n\tproxy := NewTestProxy(\"test\", ln.Addr().String())\n\tproxy.Start()\n\tdefer proxy.Stop()\n\n\tserverConnRecv := make(chan net.Conn)\n\n\tgo func() {\n\t\tconn, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tt.Error(\"Unable to accept TCP connection\", err)\n\t\t}\n\t\tserverConnRecv <- conn\n\t}()\n\n\tconn, err := net.Dial(\"tcp\", proxy.Listen)\n\tif err != nil {\n\t\tt.Error(\"Unable to dial TCP server\", err)\n\t}\n\n\tserverConn := <-serverConnRecv\n\n\tproxy.Toxics.AddToxicJson(ToxicToJson(t, \"noop_up\", \"noop\", \"upstream\", &toxics.NoopToxic{}))\n\tproxy.Toxics.AddToxicJson(\n\t\tToxicToJson(t, \"noop_down\", \"noop\", \"downstream\", &toxics.NoopToxic{}),\n\t)\n\n\tAssertEchoResponse(t, conn, serverConn)\n\n\tproxy.Toxics.ResetToxics(ctx)\n\n\tAssertEchoResponse(t, conn, serverConn)\n\n\tproxy.Toxics.ResetToxics(ctx)\n\n\tAssertEchoResponse(t, conn, serverConn)\n\n\terr = conn.Close()\n\tif err != nil {\n\t\tt.Error(\"Failed to close TCP connection\", err)\n\t}\n}\n\nfunc TestToxicAddRemove(t *testing.T) {\n\tln, err := net.Listen(\"tcp\", \"localhost:0\")\n\tif err != nil {\n\t\tt.Fatal(\"Failed to create TCP server\", err)\n\t}\n\n\tdefer ln.Close()\n\n\tproxy := NewTestProxy(\"test\", ln.Addr().String())\n\tproxy.Start()\n\tdefer proxy.Stop()\n\n\tserverConnRecv := make(chan net.Conn)\n\n\tgo func() {\n\t\tconn, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tt.Error(\"Unable to accept TCP connection\", err)\n\t\t}\n\t\tserverConnRecv <- conn\n\t}()\n\n\tconn, err := net.Dial(\"tcp\", proxy.Listen)\n\tif err != nil {\n\t\tt.Error(\"Unable to dial TCP server\", err)\n\t}\n\n\tserverConn := <-serverConnRecv\n\n\trunning := make(chan struct{})\n\tgo func() {\n\t\tenabled := false\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-running:\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\tif enabled {\n\t\t\t\t\tproxy.Toxics.AddToxicJson(\n\t\t\t\t\t\tToxicToJson(t, \"noop_up\", \"noop\", \"upstream\", &toxics.NoopToxic{}),\n\t\t\t\t\t)\n\t\t\t\t\tproxy.Toxics.RemoveToxic(context.Background(), \"noop_down\")\n\t\t\t\t} else {\n\t\t\t\t\tproxy.Toxics.RemoveToxic(context.Background(), \"noop_up\")\n\t\t\t\t\tproxy.Toxics.AddToxicJson(\n\t\t\t\t\t\tToxicToJson(t, \"noop_down\", \"noop\", \"downstream\", &toxics.NoopToxic{}),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tenabled = !enabled\n\t\t\t}\n\t\t}\n\t}()\n\n\tfor i := 0; i < 100; i++ {\n\t\tAssertEchoResponse(t, conn, serverConn)\n\t}\n\tclose(running)\n\n\terr = conn.Close()\n\tif err != nil {\n\t\tt.Error(\"Failed to close TCP connection\", err)\n\t}\n}\n\nfunc TestProxyLatency(t *testing.T) {\n\tln, err := net.Listen(\"tcp\", \"localhost:0\")\n\tif err != nil {\n\t\tt.Fatal(\"Failed to create TCP server\", err)\n\t}\n\n\tdefer ln.Close()\n\n\tproxy := NewTestProxy(\"test\", ln.Addr().String())\n\tproxy.Start()\n\tdefer proxy.Stop()\n\n\tserverConnRecv := make(chan net.Conn)\n\n\tgo func() {\n\t\tconn, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tt.Error(\"Unable to accept TCP connection\", err)\n\t\t}\n\t\tserverConnRecv <- conn\n\t}()\n\n\tconn, err := net.Dial(\"tcp\", proxy.Listen)\n\tif err != nil {\n\t\tt.Error(\"Unable to dial TCP server\", err)\n\t}\n\n\tserverConn := <-serverConnRecv\n\n\tstart := time.Now()\n\tfor i := 0; i < 100; i++ {\n\t\tAssertEchoResponse(t, conn, serverConn)\n\t}\n\tlatency := time.Since(start) / 200\n\tif latency > 300*time.Microsecond {\n\t\tt.Errorf(\"Average proxy latency > 300µs (%v)\", latency)\n\t} else {\n\t\tt.Logf(\"Average proxy latency: %v\", latency)\n\t}\n\n\terr = conn.Close()\n\tif err != nil {\n\t\tt.Error(\"Failed to close TCP connection\", err)\n\t}\n}\n\nfunc BenchmarkProxyBandwidth(b *testing.B) {\n\tln, err := net.Listen(\"tcp\", \"localhost:0\")\n\tif err != nil {\n\t\tb.Fatal(\"Failed to create TCP server\", err)\n\t}\n\n\tdefer ln.Close()\n\n\tproxy := NewTestProxy(\"test\", ln.Addr().String())\n\tproxy.Start()\n\tdefer proxy.Stop()\n\n\tbuf := []byte(strings.Repeat(\"hello world \", 1000))\n\n\tgo func() {\n\t\tconn, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tb.Error(\"Unable to accept TCP connection\", err)\n\t\t}\n\t\tbuf2 := make([]byte, len(buf))\n\t\tfor err == nil {\n\t\t\t_, err = conn.Read(buf2)\n\t\t}\n\t}()\n\n\tconn, err := net.Dial(\"tcp\", proxy.Listen)\n\tif err != nil {\n\t\tb.Error(\"Unable to dial TCP server\", err)\n\t}\n\n\tb.SetBytes(int64(len(buf)))\n\tb.ReportAllocs()\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tn, err := conn.Write(buf)\n\t\tif err != nil || n != len(buf) {\n\t\t\tb.Errorf(\"%v, %d == %d\", err, n, len(buf))\n\t\t\tbreak\n\t\t}\n\t}\n\n\terr = conn.Close()\n\tif err != nil {\n\t\tb.Error(\"Failed to close TCP connection\", err)\n\t}\n}\n\nfunc TestToxicStub_WriteOutput(t *testing.T) {\n\tinput := make(chan *stream.StreamChunk)\n\toutput := make(chan *stream.StreamChunk)\n\tstub := toxics.NewToxicStub(input, output)\n\n\tbuf := make([]byte, 42)\n\t// #nosec G404 -- used only in tests\n\trand.Read(buf)\n\n\tt.Run(\"when no read in 1 second\", func(t *testing.T) {\n\t\terr := stub.WriteOutput(&stream.StreamChunk{Data: buf}, time.Second)\n\t\tif err == nil {\n\t\t\tt.Error(\"Expected to have error\")\n\t\t}\n\n\t\texpected := \"timeout: could not write to output in 1 seconds\"\n\t\tif err.Error() != expected {\n\t\t\tt.Errorf(\"Expected error: %s, got %s\", expected, err)\n\t\t}\n\t})\n\n\tt.Run(\"when read is available\", func(t *testing.T) {\n\t\tgo func(t *testing.T, stub *toxics.ToxicStub, expected []byte) {\n\t\t\tselect {\n\t\t\tcase <-time.After(5 * time.Second):\n\t\t\t\tt.Error(\"Timeout of running test to read from output.\")\n\t\t\tcase chunk := <-output:\n\t\t\t\tif !bytes.Equal(chunk.Data, buf) {\n\t\t\t\t\tt.Error(\"Data in Output different from Write\")\n\t\t\t\t}\n\t\t\t}\n\t\t}(t, stub, buf)\n\n\t\terr := stub.WriteOutput(&stream.StreamChunk{Data: buf}, 5*time.Second)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Unexpected error: %+v\", err)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "toxiproxy_test.go",
    "content": "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\"github.com/rs/zerolog\"\n\n\t\"github.com/Shopify/toxiproxy/v2\"\n\t\"github.com/Shopify/toxiproxy/v2/collectors\"\n\t\"github.com/Shopify/toxiproxy/v2/testhelper\"\n)\n\nfunc NewTestProxy(name, upstream string) *toxiproxy.Proxy {\n\tlog := zerolog.Nop()\n\tif flag.Lookup(\"test.v\").DefValue == \"true\" {\n\t\tlog = zerolog.New(os.Stdout).With().Caller().Timestamp().Logger()\n\t}\n\tsrv := toxiproxy.NewServer(\n\t\ttoxiproxy.NewMetricsContainer(prometheus.NewRegistry()),\n\t\tlog,\n\t)\n\tsrv.Metrics.ProxyMetrics = collectors.NewProxyMetricCollectors()\n\tproxy := toxiproxy.NewProxy(srv, name, \"localhost:0\", upstream)\n\n\treturn proxy\n}\n\nfunc WithTCPProxy(\n\tt *testing.T,\n\tf func(proxy net.Conn, response chan []byte, proxyServer *toxiproxy.Proxy),\n) {\n\ttesthelper.WithTCPServer(t, func(upstream string, response chan []byte) {\n\t\tproxy := NewTestProxy(\"test\", upstream)\n\t\tproxy.Start()\n\n\t\tconn := AssertProxyUp(t, proxy.Listen, true)\n\n\t\tf(conn, response, proxy)\n\n\t\tproxy.Stop()\n\t})\n}\n\nfunc AssertProxyUp(t *testing.T, addr string, up bool) net.Conn {\n\tconn, err := net.Dial(\"tcp\", addr)\n\tif err != nil && up {\n\t\tt.Error(\"Expected proxy to be up:\", err)\n\t} else if err == nil && !up {\n\t\tt.Error(\"Expected proxy to be down\")\n\t}\n\treturn conn\n}\n"
  },
  {
    "path": "version.go",
    "content": "package toxiproxy\n\nvar Version = \"git\"\n"
  }
]