diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index af0bdb979a..ed777696ba 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -9,7 +9,7 @@ ## New Contributor Tutorial For new contributors, we've put together a detailed tutorial -[here](https://github.com/gophercloud/gophercloud/tree/master/docs/contributor-tutorial)! +[here](https://github.com/gophercloud/gophercloud/tree/main/docs/contributor-tutorial)! ## 3 ways to get involved @@ -89,7 +89,7 @@ fork as `origin` instead: 4. Checkout the latest development branch: ```bash - git checkout master + git checkout main ``` 5. If you're working on something (discussed more in detail below), you will @@ -107,7 +107,7 @@ need to checkout a new feature branch: git commit ``` -7. Submit your branch as a [Pull Request](https://help.github.com/articles/creating-a-pull-request/). When submitting a Pull Request, please follow our [Style Guide](https://github.com/gophercloud/gophercloud/blob/master/docs/STYLEGUIDE.md). +7. Submit your branch as a [Pull Request](https://help.github.com/articles/creating-a-pull-request/). When submitting a Pull Request, please follow our [Style Guide](https://github.com/gophercloud/gophercloud/blob/main/docs/STYLEGUIDE.md). > Further information about using Git can be found [here](https://git-scm.com/book/en/v2). diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE deleted file mode 100644 index 2c3be773ad..0000000000 --- a/.github/ISSUE_TEMPLATE +++ /dev/null @@ -1 +0,0 @@ -Before starting a PR, please read our [contributor tutorial](https://github.com/gophercloud/gophercloud/tree/master/docs/contributor-tutorial). diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml new file mode 100644 index 0000000000..b25d573119 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -0,0 +1,28 @@ +name: Bug Report +description: File a bug report. +type: Bug +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! Check out our [contributor guide](https://github.com/gophercloud/gophercloud/blob/main/docs/contributor-tutorial/step-02-issues.md) to create effective issues. + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Also tell us, what did you expect to happen? + placeholder: What happened, and what did you expect to happen? + validations: + required: true + - type: dropdown + id: version + attributes: + label: Version + description: What version of our Gophercloud are you running? + options: + - v2 (current) + - v1 (legacy) + - main (development) + default: 0 + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/rfe.yaml b/.github/ISSUE_TEMPLATE/rfe.yaml new file mode 100644 index 0000000000..ab50e2b2ad --- /dev/null +++ b/.github/ISSUE_TEMPLATE/rfe.yaml @@ -0,0 +1,17 @@ +name: Feature request +description: Suggest an idea for this project. +type: Feature +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this enhancement request! Check out our [contributor guide](https://github.com/gophercloud/gophercloud/blob/main/docs/contributor-tutorial/step-02-issues.md) to create effective issues. + - type: textarea + id: what + attributes: + label: What is missing? + description: | + Is your feature request related to a problem? Please describe. Also feel free to describe the solution you'd like. + If you've found a missing field in an existing struct, please provide a link to the actual service's Python code which defines the missing field. + validations: + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE index 6978cc2c53..03aa4bb3dd 100644 --- a/.github/PULL_REQUEST_TEMPLATE +++ b/.github/PULL_REQUEST_TEMPLATE @@ -1,9 +1,11 @@ + Fixes #[PUT ISSUE NUMBER HERE] Links to the line numbers/files in the OpenStack source code that support the diff --git a/.github/actions/file-filter/Dockerfile b/.github/actions/file-filter/Dockerfile new file mode 100644 index 0000000000..4994652170 --- /dev/null +++ b/.github/actions/file-filter/Dockerfile @@ -0,0 +1,5 @@ +FROM python:3.11-slim +WORKDIR /app +RUN pip install 'PyGithub~=2.7.0' 'requests~=2.32' +COPY file_filter_action.py ./ +ENTRYPOINT ["python", "/app/file_filter_action.py"] diff --git a/.github/actions/file-filter/action.yml b/.github/actions/file-filter/action.yml new file mode 100644 index 0000000000..afd9fac6c8 --- /dev/null +++ b/.github/actions/file-filter/action.yml @@ -0,0 +1,24 @@ +name: 'File Filter' +description: 'Filter PR files by glob patterns and return true/false if any files match' +inputs: + patterns: + description: 'String containing one or more glob patterns separated by spaces' + required: true + exclude: + description: 'Whether patterns is a list of glob patterns to *exclude* rather than *include*' + required: false + default: false + token: + description: 'GitHub token for API access' + required: false + default: ${{ github.token }} +outputs: + matches: + description: 'True if any files match the patterns or if event_type is schedule, false otherwise (boolean)' + count: + description: 'Number of files that matched the patterns; will be unset if event_type is schedule (integer)' + files: + description: 'Files that matched the patterns; will unset if event_type is schedule (JSON array of strings)' +runs: + using: 'docker' + image: 'Dockerfile' diff --git a/.github/actions/file-filter/file_filter_action.py b/.github/actions/file-filter/file_filter_action.py new file mode 100755 index 0000000000..75bb0960c3 --- /dev/null +++ b/.github/actions/file-filter/file_filter_action.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 + +import argparse +import fnmatch +import json +import os +import sys + +import github + + +def parse_patterns(patterns_input: str) -> list[str]: + """Parse glob patterns from newline- or space-separated string input.""" + if not patterns_input: + return [] + + patterns = [p.strip() for p in patterns_input.split() if p.strip()] + + if not patterns: + raise ValueError('No valid patterns found in input') + + return patterns + + +def get_changed_files( + github_client: github.Github, repo_name: str, +) -> list[str]: + """Get list of changed files between base and head refs.""" + try: + repo = github_client.get_repo(repo_name) + + # Try to get PR context first + if 'GITHUB_EVENT_PATH' in os.environ: + try: + with open(os.environ['GITHUB_EVENT_PATH']) as f: + event_data = json.load(f) + + if 'pull_request' in event_data: + pr_number = event_data['pull_request']['number'] + pr = repo.get_pull(pr_number) + files = pr.get_files() + return [f.filename for f in files] + except (FileNotFoundError, KeyError, json.JSONDecodeError): + pass + + base_ref = os.environ.get('GITHUB_BASE_REF', 'main') + head_ref = os.environ.get('GITHUB_HEAD_REF', os.environ.get('GITHUB_SHA')) + + if head_ref: + comparison = repo.compare(base_ref, head_ref) + return [f.filename for f in comparison.files] + + print('Warning: Could not determine changed files', file=sys.stderr) + return [] + except github.GithubException as e: + print(f'GitHub API error: {e}', file=sys.stderr) + return [] + except Exception as e: + print(f'Error getting changed files: {e}', file=sys.stderr) + return [] + + +def match_files(files: list[str], patterns: list[str], exclude: bool) -> list[str]: + """Match files against glob patterns.""" + matches = [] + + for file_path in files: + for pattern in patterns: + if (fnmatch.fnmatch(file_path, pattern) and not exclude) or exclude: + matches.append(file_path) + break + + return matches + + +def set_output(name: str, value: str) -> None: + """Set GitHub Actions output.""" + if 'GITHUB_OUTPUT' in os.environ: + with open(os.environ['GITHUB_OUTPUT'], 'a') as f: + f.write(f'{name}={value}\n') + else: + # Fallback for older runners + print(f'::set-output name={name}::{value}') + + +def main() -> None: + """Main function.""" + parser = argparse.ArgumentParser( + description=( + 'A utility script to retrieve the list of changed files in the ' + 'current PR, intended to be run as part of a GitHub Actions ' + 'pipeline.' + ), + ) + parser.parse_args() + + try: + patterns_input = os.environ.get('INPUT_PATTERNS', '') + token = os.environ.get('INPUT_TOKEN', os.environ.get('GITHUB_TOKEN', '')) + exclude = os.environ.get('INPUT_EXCLUDE') + repo_name = os.environ.get('GITHUB_REPOSITORY', '') + event_type = os.environ.get('GITHUB_EVENT_NAME') + + print(f'Event type: {event_type}') + if event_type in ('schedule',): + print(f'Skipping file check for event_type={event_type}') + set_output('matches', 'true') + set_output('count', '') + set_output('files', '') + sys.exit(0) + + if not patterns_input: + print('Error: No patterns provided', file=sys.stderr) + sys.exit(1) + + if not token: + print('Error: No GitHub token provided', file=sys.stderr) + sys.exit(1) + + if not repo_name: + print('Error: No repository name found', file=sys.stderr) + sys.exit(1) + + if exclude and exclude not in ('true', 'false'): + print('Error: exclude must be one of: true, false', file=sys.stderr) + sys.exit(1) + + patterns = parse_patterns(patterns_input) + print(f'Parsed patterns: {patterns}') + + github_client = github.Github(token) + changed_files = get_changed_files(github_client, repo_name) + matched_files = match_files(changed_files, patterns, exclude == 'true') + + print(f'Has matches? {"true" if matched_files else "false"}') + print(f'Matched files: {matched_files}') + + set_output('matches', 'true' if matched_files else 'false') + set_output('count', str(len(matched_files))) + set_output('files', json.dumps(matched_files)) + sys.exit(0) + except Exception as e: + print(f'Error: {e}', file=sys.stderr) + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 0000000000..83e79253ce --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,34 @@ +version: 2 +updates: +- package-ecosystem: gomod + directory: "/" + target-branch: "main" + schedule: + interval: daily + cooldown: + default-days: 7 + open-pull-requests-limit: 10 +- package-ecosystem: "github-actions" + directory: "/" + target-branch: "main" + schedule: + interval: daily + cooldown: + default-days: 7 + open-pull-requests-limit: 10 +- package-ecosystem: gomod + directory: "/" + target-branch: "v2" + schedule: + interval: daily + cooldown: + default-days: 7 + open-pull-requests-limit: 10 +- package-ecosystem: "github-actions" + directory: "/" + target-branch: "v2" + schedule: + interval: daily + cooldown: + default-days: 7 + open-pull-requests-limit: 10 diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 2b5c704536..0000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,12 +0,0 @@ -version: 2 -updates: -- package-ecosystem: gomod - directory: "/" - schedule: - interval: daily - open-pull-requests-limit: 10 -- package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: daily - open-pull-requests-limit: 10 diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000000..a07b39fe3e --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,144 @@ +edit:dependencies: +- changed-files: + - any-glob-to-any-file: + - 'go.mod' + - 'go.sum' + - '.github/dependabot.yml' +edit:actions: +- changed-files: + - any-glob-to-any-file: + - '.github/**' +edit:gophercloud: +- changed-files: + - any-glob-to-any-file: + - '*.go' + - 'testing/**' + - 'pagination/**' +edit:openstack: +- changed-files: + - any-glob-to-any-file: + - 'openstack/*' + - 'openstack/testing/**' +edit:baremetal: +- changed-files: + - any-glob-to-any-file: + - 'openstack/baremetal/**' + - 'internal/acceptance/openstack/baremetal/**' +edit:baremetalintrospection: +- changed-files: + - any-glob-to-any-file: + - 'openstack/baremetalintrospection/**' +edit:blockstorage: +- changed-files: + - any-glob-to-any-file: + - 'openstack/blockstorage/**' + - 'internal/acceptance/openstack/blockstorage/**' +edit:common: +- changed-files: + - any-glob-to-any-file: + - 'openstack/common/**' +edit:compute: +- changed-files: + - any-glob-to-any-file: + - 'openstack/compute/**' + - 'internal/acceptance/openstack/compute/**' +edit:config: +- changed-files: + - any-glob-to-any-file: + - 'openstack/config/**' +edit:container: +- changed-files: + - any-glob-to-any-file: + - 'openstack/container/**' + - 'internal/acceptance/openstack/container/**' +edit:containerinfra: +- changed-files: + - any-glob-to-any-file: + - 'openstack/containerinfra/**' + - 'internal/acceptance/openstack/containerinfra/**' +edit:db: +- changed-files: + - any-glob-to-any-file: + - 'openstack/db/**' + - 'internal/acceptance/openstack/db/**' +edit:dns: +- changed-files: + - any-glob-to-any-file: + - 'openstack/dns/**' + - 'internal/acceptance/openstack/dns/**' +edit:identity: +- changed-files: + - any-glob-to-any-file: + - 'openstack/identity/**' + - 'internal/acceptance/openstack/identity/**' +edit:image: +- changed-files: + - any-glob-to-any-file: + - 'openstack/image/**' + - 'internal/acceptance/openstack/image/**' +edit:keymanager: +- changed-files: + - any-glob-to-any-file: + - 'openstack/keymanager/**' + - 'internal/acceptance/openstack/keymanager/**' +edit:loadbalancer: +- changed-files: + - any-glob-to-any-file: + - 'openstack/loadbalancer/**' + - 'internal/acceptance/openstack/loadbalancer/**' +edit:messaging: +- changed-files: + - any-glob-to-any-file: + - 'openstack/messaging/**' + - 'internal/acceptance/openstack/messaging/**' +edit:networking: +- changed-files: + - any-glob-to-any-file: + - 'openstack/networking/**' + - 'internal/acceptance/openstack/networking/**' +edit:objectstorage: +- changed-files: + - any-glob-to-any-file: + - 'openstack/objectstorage/**' + - 'internal/acceptance/openstack/objectstorage/**' +edit:orchestration: +- changed-files: + - any-glob-to-any-file: + - 'openstack/orchestration/**' + - 'internal/acceptance/openstack/orchestration/**' +edit:placement: +- changed-files: + - any-glob-to-any-file: + - 'openstack/placement/**' + - 'internal/acceptance/openstack/placement/**' +edit:sharedfilesystems: +- changed-files: + - any-glob-to-any-file: + - 'openstack/sharedfilesystems/**' + - 'internal/acceptance/openstack/sharedfilesystems/**' +edit:testinfra: +- changed-files: + - any-glob-to-any-file: + - 'testhelper/**' + - 'internal/acceptance/*' + - 'internal/acceptance/openstack/*' + - 'internal/acceptance/clients/**' + - 'internal/acceptance/tools/**' + - '.github/workflows/functional-*.yaml' + - '.github/workflows/unit.yaml' + - '.github/workflows/lint.yaml' + - 'script/**' +edit:utils: +- changed-files: + - any-glob-to-any-file: + - 'openstack/utils/**' +edit:workflow: +- changed-files: + - any-glob-to-any-file: + - 'openstack/workflow/**' + - 'internal/acceptance/openstack/workflow/**' + +v1: +- base-branch: 'v1' +v2: +- base-branch: 'v2' diff --git a/.github/labels.yaml b/.github/labels.yaml index 479fb5f3a7..f0f9eefd80 100644 --- a/.github/labels.yaml +++ b/.github/labels.yaml @@ -4,12 +4,6 @@ - color: 'E99695' description: This PR will be backported to v2 name: backport-v2 -- color: '0366d6' - description: Pull requests that update a dependency file - name: dependencies -- color: '000000' - description: Pull requests that update GitHub Actions code - name: github_actions - color: 'BCF611' description: A good issue for first-time contributors name: good first issue @@ -22,9 +16,104 @@ - color: '6E7624' description: No API change name: semver:patch -- color: 'EC0101' - description: Unable to figure out the semver type - name: semver:unknown - color: 'D73A4A' description: Do not merge name: hold +- color: 'F9D0C4' + description: Additional information requested + name: needinfo +- color: 'FEF2C0' + description: This PR lacks tests before it can be merged + name: just-needs-tests + +- color: '30ABB9' + description: This PR targets v1 + name: v1 +- color: 'E99695' + description: This PR targets v2 + name: v2 + +- color: '000000' + description: This PR updates dependencies + name: edit:dependencies +- color: '000000' + description: This PR updates GitHub Actions code + name: edit:actions +- color: '000000' + description: This PR updates common Gophercloud code + name: edit:gophercloud +- color: '000000' + description: This PR updates common OpenStack code + name: edit:openstack +- color: '000000' + description: This PR updates baremetal code + name: edit:baremetal +- color: '000000' + description: This PR updates baremetalintrospection code + name: edit:baremetalintrospection +- color: '000000' + description: This PR updates blockstorage code + name: edit:blockstorage +- color: '000000' + description: This PR updates common code + name: edit:common +- color: '000000' + description: This PR updates compute code + name: edit:compute +- color: '000000' + description: This PR updates config code + name: edit:config +- color: '000000' + description: This PR updates container code + name: edit:container +- color: '000000' + description: This PR updates containerinfra code + name: edit:containerinfra +- color: '000000' + description: This PR updates db code + name: edit:db +- color: '000000' + description: This PR updates dns code + name: edit:dns +- color: '000000' + description: This PR updates identity code + name: edit:identity +- color: '000000' + description: This PR updates image code + name: edit:image +- color: '000000' + description: This PR updates keymanager code + name: edit:keymanager +- color: '000000' + description: This PR updates loadbalancer code + name: edit:loadbalancer +- color: '000000' + description: This PR updates messaging code + name: edit:messaging +- color: '000000' + description: This PR updates networking code + name: edit:networking +- color: '000000' + description: This PR updates objectstorage code + name: edit:objectstorage +- color: '000000' + description: This PR updates orchestration code + name: edit:orchestration +- color: '000000' + description: This PR updates placement code + name: edit:placement +- color: '000000' + description: This PR updates sharedfilesystems code + name: edit:sharedfilesystems +- color: '000000' + description: This PR updates testing infrastructure code + name: edit:testinfra +- color: '000000' + description: This PR updates testing code + name: edit:testing +- color: '000000' + description: This PR updates utils code + name: edit:utils +- color: '000000' + description: This PR updates workflow code + name: edit:workflow diff --git a/.github/workflows/backport.yaml b/.github/workflows/backport.yaml index f690f497fe..7e23e840f4 100644 --- a/.github/workflows/backport.yaml +++ b/.github/workflows/backport.yaml @@ -1,7 +1,9 @@ name: Pull Request backporting on: - pull_request_target: + # Safe: only runs on merged PRs, does not checkout PR code, and + # needs secrets to generate the backport bot token. + pull_request_target: # zizmor: ignore[dangerous-triggers] types: - closed - labeled @@ -9,10 +11,15 @@ on: jobs: backport_v1: name: "Backport to v1" + permissions: + contents: read + issues: write + pull-requests: write # Only react to merged PRs for security reasons. # See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target. if: > github.event.pull_request.merged + && !contains(github.event.pull_request.labels.*.name, 'dependencies') && ( github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, 'backport-v1') @@ -25,16 +32,16 @@ jobs: steps: - name: Generate a token from the gophercloud-backport-bot github-app id: generate_token - uses: getsentry/action-github-app-token@d4b5da6c5e37703f8c3b3e43abb5705b46e159cc + uses: getsentry/action-github-app-token@5c1e90706fe007857338ac1bfbd7a4177db2f789 # v4.0.0 with: - app_id: ${{ secrets.BACKPORT_APP_ID }} - private_key: ${{ secrets.BACKPORT_APP_PRIVATE_KEY }} + app_id: ${{ secrets.BACKPORT_APP_ID }} # zizmor: ignore[secrets-outside-env] + private_key: ${{ secrets.BACKPORT_APP_PRIVATE_KEY }} # zizmor: ignore[secrets-outside-env] - name: Backporting if: > contains(github.event.pull_request.labels.*.name, 'semver:patch') || contains(github.event.label.name, 'semver:patch') - uses: kiegroup/git-backporting@c5d7f0ea567c9f997ba4d15e442ad3204abe2030 + uses: kiegroup/git-backporting@08da0b07ef2330d189f6074ec8db736b3aa9f465 # v4.9.1 with: target-branch: v1 pull-request: ${{ github.event.pull_request.url }} @@ -60,18 +67,25 @@ jobs: || contains(github.event.label.name, 'semver:major') || contains(github.event.label.name, 'semver:minor') || contains(github.event.label.name, 'semver:unknown') - uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 - with: - message: | - Labels `semver-major`, `semver-minor` and `semver-unknown` block - backports to the legacy branch `v1`. + run: gh pr comment "$NUMBER" --body "$BODY" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.pull_request.number }} + BODY: > + Labels `semver-major`, `semver-minor` and `semver-unknown` block backports to the legacy branch `v1`. backport_v2: name: "Backport to v2" + permissions: + contents: read + issues: write + pull-requests: write # Only react to merged PRs for security reasons. # See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target. if: > github.event.pull_request.merged + && !contains(github.event.pull_request.labels.*.name, 'dependencies') && ( github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, 'backport-v2') @@ -84,10 +98,10 @@ jobs: steps: - name: Generate a token from the gophercloud-backport-bot github-app id: generate_token - uses: getsentry/action-github-app-token@d4b5da6c5e37703f8c3b3e43abb5705b46e159cc + uses: getsentry/action-github-app-token@5c1e90706fe007857338ac1bfbd7a4177db2f789 # v4.0.0 with: - app_id: ${{ secrets.BACKPORT_APP_ID }} - private_key: ${{ secrets.BACKPORT_APP_PRIVATE_KEY }} + app_id: ${{ secrets.BACKPORT_APP_ID }} # zizmor: ignore[secrets-outside-env] + private_key: ${{ secrets.BACKPORT_APP_PRIVATE_KEY }} # zizmor: ignore[secrets-outside-env] - name: Backporting if: > @@ -95,7 +109,7 @@ jobs: || contains(github.event.pull_request.labels.*.name, 'semver:minor') || contains(github.event.label.name, 'semver:patch') || contains(github.event.label.name, 'semver:minor') - uses: kiegroup/git-backporting@c5d7f0ea567c9f997ba4d15e442ad3204abe2030 + uses: kiegroup/git-backporting@08da0b07ef2330d189f6074ec8db736b3aa9f465 # v4.9.1 with: target-branch: v2 pull-request: ${{ github.event.pull_request.url }} @@ -119,8 +133,10 @@ jobs: || contains(github.event.pull_request.labels.*.name, 'semver:unknown') || contains(github.event.label.name, 'semver:major') || contains(github.event.label.name, 'semver:unknown') - uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 - with: - message: | - Labels `semver-major` and `semver-unknown` block backports to the - stable branch `v2`. + run: gh pr comment "$NUMBER" --body "$BODY" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.pull_request.number }} + BODY: > + Labels `semver-major` and `semver-unknown` block backports to the stable branch `v2`. diff --git a/.github/workflows/check-pr-labels.yaml b/.github/workflows/check-pr-labels.yaml index ea8068f602..a81d2b223a 100644 --- a/.github/workflows/check-pr-labels.yaml +++ b/.github/workflows/check-pr-labels.yaml @@ -1,12 +1,17 @@ name: Ready on: - pull_request_target: + merge_group: + pull_request: types: - - opened - labeled + - opened + - reopened + - synchronize + - unlabeled jobs: hold: + permissions: {} if: github.event.pull_request.merged == false runs-on: ubuntu-latest steps: diff --git a/.github/workflows/codeql-analysis.yaml b/.github/workflows/codeql-analysis.yaml new file mode 100644 index 0000000000..f057cdd5de --- /dev/null +++ b/.github/workflows/codeql-analysis.yaml @@ -0,0 +1,40 @@ +name: "CodeQL" + +on: [push, pull_request] + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + + steps: + - name: Checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Setup Go + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version-file: 'go.mod' + cache: true + + - name: Initialize CodeQL + uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 + with: + languages: ${{ matrix.language }} + + - name: Autobuild + uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index b9492ca444..0000000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: "CodeQL" - -on: [push, pull_request] - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'go' ] - - steps: - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: "1.22" - - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - - - name: Autobuild - uses: github/codeql-action/autobuild@v3 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/ensure-labels.yaml b/.github/workflows/ensure-labels.yaml index 4528512c0f..10be137be0 100644 --- a/.github/workflows/ensure-labels.yaml +++ b/.github/workflows/ensure-labels.yaml @@ -2,16 +2,21 @@ name: Apply labels in .github/labels.yaml on: push: branches: - - master + - main paths: - .github/labels.yaml - .github/workflows/ensure-labels.yaml jobs: ensure: + permissions: + contents: read + issues: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: micnncim/action-label-syncer@v1 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + - uses: micnncim/action-label-syncer@3abd5ab72fda571e69fffd97bd4e0033dd5f495c # v1.3.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/functional-baremetal.yaml b/.github/workflows/functional-baremetal.yaml index c383c47286..19a55101de 100644 --- a/.github/workflows/functional-baremetal.yaml +++ b/.github/workflows/functional-baremetal.yaml @@ -1,63 +1,70 @@ name: functional-baremetal on: + merge_group: pull_request: - paths: - - '**baremetal**' schedule: - cron: '0 0 */3 * *' jobs: functional-baremetal: + permissions: + contents: read strategy: fail-fast: false matrix: include: - name: "master" openstack_version: "master" - ubuntu_version: "22.04" - # NOTE(dtantsur): this forces running tests with a system scope - # token, which is required for certain actions. Use it on versions - # starting with 2024.1. - use_system_scope: true - additional_services: "openstack-cli-server" - - name: "caracal" - openstack_version: "stable/2024.1" - ubuntu_version: "22.04" - use_system_scope: true - additional_services: "" - - name: "bobcat" - openstack_version: "stable/2023.2" - ubuntu_version: "22.04" - use_system_scope: false - additional_services: "" - - name: "antelope" - openstack_version: "stable/2023.1" - ubuntu_version: "22.04" - use_system_scope: false - additional_services: "" + ubuntu_version: "24.04" + - name: "gazpacho" + openstack_version: "stable/2026.1" + ubuntu_version: "24.04" + - name: "epoxy" + openstack_version: "stable/2025.1" + ubuntu_version: "24.04" runs-on: ubuntu-${{ matrix.ubuntu_version }} - name: Deploy OpenStack ${{ matrix.name }} with Ironic and run baremetal acceptance tests + name: Ironic on OpenStack ${{ matrix.name }} steps: - name: Checkout Gophercloud - uses: actions/checkout@v4 - - name: Workaround for grub-efi-amd64-signed - run: sudo rm /var/cache/debconf/config.dat - shell: bash - if: matrix.ubuntu_version == '20.04' - - name: Ensure update and upgrade - run: sudo apt update && sudo apt -y upgrade - shell: bash - if: matrix.ubuntu_version == '20.04' + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Check changed files + uses: ./.github/actions/file-filter + id: changed-files + with: + patterns: | + openstack/auth_env.go + openstack/client.go + openstack/endpoint.go + openstack/endpoint_location.go + openstack/config/provider_client.go + openstack/utils/choose_version.go + openstack/utils/discovery.go + **baremetal** + .github/workflows/functional-baremetal.yaml + + - name: Skip tests for unrelated changed-files + if: ${{ ! fromJSON(steps.changed-files.outputs.matches) }} + run: | + echo "No relevant files changed - skipping tests for ${{ matrix.name }}" + echo "TESTS_SKIPPED=true" >> $GITHUB_ENV + - name: Work around broken dnsmasq + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} run: sudo apt-get purge -y dnsmasq-base + + # NOTE(sharpz7) IRONIC_BAREMETAL_BASIC_OPS was originally set to false as it + # was failing (https://review.opendev.org/c/openstack/ironic/+/960299) + # See https://github.com/gophercloud/gophercloud/pull/3500 + # This change however is suitable longer-term as it is not necessary for SDK testing. - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 # v0.19 with: branch: ${{ matrix.openstack_version }} conf_overrides: | - # pyghmi is not mirrored on github - PYGHMI_REPO=https://opendev.org/x/pyghmi enable_plugin ironic https://github.com/openstack/ironic ${{ matrix.openstack_version }} - LIBS_FROM_GIT=pyghmi,virtualbmc FORCE_CONFIG_DRIVE=True Q_AGENT=openvswitch Q_ML2_TENANT_NETWORK_TYPE=vxlan @@ -70,7 +77,7 @@ jobs: GLANCE_LIMIT_IMAGE_SIZE_TOTAL=5000 Q_USE_SECGROUP=False API_WORKERS=1 - IRONIC_BAREMETAL_BASIC_OPS=True + IRONIC_BAREMETAL_BASIC_OPS=False IRONIC_BUILD_DEPLOY_RAMDISK=False IRONIC_AUTOMATED_CLEAN_ENABLED=False IRONIC_CALLBACK_TIMEOUT=600 @@ -87,25 +94,43 @@ jobs: IRONIC_ENABLED_DEPLOY_INTERFACES=direct,fake SWIFT_ENABLE_TEMPURLS=True SWIFT_TEMPURL_KEY=secretkey - enabled_services: "ir-api,ir-cond,s-account,s-container,s-object,s-proxy,q-svc,q-agt,q-dhcp,q-l3,q-meta,-cinder,-c-sch,-c-api,-c-vol,-c-bak,-ovn,-ovn-controller,-ovn-northd,-q-ovn-metadata-agent,${{ matrix.additional_services }}" + enabled_services: "ir-api,ir-cond,s-account,s-container,s-object,s-proxy,q-svc,q-agt,q-dhcp,q-l3,q-meta,-cinder,-c-sch,-c-api,-c-vol,-c-bak,-ovn,-ovn-controller,-ovn-northd,-q-ovn-metadata-agent,openstack-cli-server" + - name: Checkout go - uses: actions/setup-go@v5 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: '^1.22' + go-version-file: 'go.mod' + cache: true + - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-baremetal + echo "TESTS_RUN=true" >> $GITHUB_ENV env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/baremetal/..." OS_BRANCH: ${{ matrix.openstack_version }} - # TODO(dtantsur): default to true when no longer supporting versions before 2024.1 - USE_SYSTEM_SCOPE: ${{ matrix.use_system_scope }} + USE_SYSTEM_SCOPE: true + - name: Generate logs on failure run: ./script/collectlogs - if: failure() + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + - name: Upload logs artifacts on failure - if: failure() - uses: actions/upload-artifact@v4 + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: functional-baremetal-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* + + - name: Set job status + run: | + if [[ "$TESTS_SKIPPED" == "true" || "$TESTS_RUN" == "true" ]]; then + echo "Job completed successfully (either ran tests or skipped appropriately)" + exit 0 + else + echo "Job failed - neither tests ran nor were properly skipped" + exit 1 + fi diff --git a/.github/workflows/functional-basic.yaml b/.github/workflows/functional-basic.yaml index 810a63868c..781d0f6db8 100644 --- a/.github/workflows/functional-basic.yaml +++ b/.github/workflows/functional-basic.yaml @@ -1,61 +1,92 @@ name: functional-basic on: + merge_group: pull_request: - paths-ignore: - - 'docs/**' - - '**.md' - - '**.gitignore' - - '**LICENSE' schedule: - cron: '0 0 */3 * *' jobs: functional-basic: + permissions: + contents: read strategy: fail-fast: false matrix: include: - name: "master" openstack_version: "master" - ubuntu_version: "22.04" - additional_services: "openstack-cli-server" - - name: "caracal" - openstack_version: "stable/2024.1" - ubuntu_version: "22.04" - additional_services: "" - - name: "bobcat" - openstack_version: "stable/2023.2" - ubuntu_version: "22.04" - additional_services: "" - - name: "antelope" - openstack_version: "stable/2023.1" - ubuntu_version: "22.04" - additional_services: "" + ubuntu_version: "24.04" + - name: "gazpacho" + openstack_version: "stable/2026.1" + ubuntu_version: "24.04" + - name: "epoxy" + openstack_version: "stable/2025.1" + ubuntu_version: "24.04" runs-on: ubuntu-${{ matrix.ubuntu_version }} - name: Deploy OpenStack ${{ matrix.name }} with defaults and run basic acceptance tests + name: basic tests on OpenStack ${{ matrix.name }} steps: - name: Checkout Gophercloud - uses: actions/checkout@v4 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Check changed files + uses: ./.github/actions/file-filter + id: changed-files + with: + patterns: | + docs/** + **.md + **.gitignore + **LICENSE + exclude: true + + - name: Skip tests for unrelated changed-files + if: ${{ ! fromJSON(steps.changed-files.outputs.matches) }} + run: | + echo "No relevant files changed - skipping tests for ${{ matrix.name }}" + echo "TESTS_SKIPPED=true" >> $GITHUB_ENV + - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 # v0.19 with: branch: ${{ matrix.openstack_version }} - enabled_services: 's-account,s-container,s-object,s-proxy,${{ matrix.additional_services }}' + enabled_services: 's-account,s-container,s-object,s-proxy,openstack-cli-server' + - name: Checkout go - uses: actions/setup-go@v5 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: '^1.22' + go-version-file: 'go.mod' + cache: true + - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-basic + echo "TESTS_RUN=true" >> $GITHUB_ENV env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: './internal/acceptance/openstack' OS_BRANCH: ${{ matrix.openstack_version }} + - name: Generate logs on failure run: ./script/collectlogs - if: failure() + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + - name: Upload logs artifacts on failure - if: failure() - uses: actions/upload-artifact@v4 + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: functional-basic-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* + + - name: Set job status + run: | + if [[ "$TESTS_SKIPPED" == "true" || "$TESTS_RUN" == "true" ]]; then + echo "Job completed successfully (either ran tests or skipped appropriately)" + exit 0 + else + echo "Job failed - neither tests ran nor were properly skipped" + exit 1 + fi diff --git a/.github/workflows/functional-blockstorage.yaml b/.github/workflows/functional-blockstorage.yaml index 42ac4edb4d..6ec0cc9c15 100644 --- a/.github/workflows/functional-blockstorage.yaml +++ b/.github/workflows/functional-blockstorage.yaml @@ -1,60 +1,98 @@ name: functional-blockstorage on: + merge_group: pull_request: - paths: - - '**blockstorage**' schedule: - cron: '0 0 */3 * *' jobs: functional-blockstorage: + permissions: + contents: read strategy: fail-fast: false matrix: include: - name: "master" openstack_version: "master" - ubuntu_version: "22.04" - additional_services: "openstack-cli-server" - - name: "caracal" - openstack_version: "stable/2024.1" - ubuntu_version: "22.04" - additional_services: "" - - name: "bobcat" - openstack_version: "stable/2023.2" - ubuntu_version: "22.04" - additional_services: "" - - name: "antelope" - openstack_version: "stable/2023.1" - ubuntu_version: "22.04" - additional_services: "" + ubuntu_version: "24.04" + - name: "gazpacho" + openstack_version: "stable/2026.1" + ubuntu_version: "24.04" + - name: "epoxy" + openstack_version: "stable/2025.1" + ubuntu_version: "24.04" runs-on: ubuntu-${{ matrix.ubuntu_version }} - name: Deploy OpenStack ${{ matrix.name }} with Cinder and run blockstorage acceptance tests + name: Cinder on OpenStack ${{ matrix.name }} steps: - name: Checkout Gophercloud - uses: actions/checkout@v4 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Check changed files + uses: ./.github/actions/file-filter + id: changed-files + with: + patterns: | + openstack/auth_env.go + openstack/client.go + openstack/endpoint.go + openstack/endpoint_location.go + openstack/config/provider_client.go + openstack/utils/choose_version.go + openstack/utils/discovery.go + **blockstorage** + .github/workflows/functional-blockstorage.yaml + + - name: Skip tests for unrelated changed-files + if: ${{ ! fromJSON(steps.changed-files.outputs.matches) }} + run: | + echo "No relevant files changed - skipping tests for ${{ matrix.name }}" + echo "TESTS_SKIPPED=true" >> $GITHUB_ENV + - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 # v0.19 with: branch: ${{ matrix.openstack_version }} conf_overrides: | CINDER_ISCSI_HELPER=lioadm - enabled_services: "s-account,s-container,s-object,s-proxy,c-bak,${{ matrix.additional_services }}" + enabled_services: "s-account,s-container,s-object,s-proxy,c-bak,openstack-cli-server" + - name: Checkout go - uses: actions/setup-go@v5 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: '^1.22' + go-version-file: 'go.mod' + cache: true + - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-blockstorage + echo "TESTS_RUN=true" >> $GITHUB_ENV env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/blockstorage/..." OS_BRANCH: ${{ matrix.openstack_version }} + - name: Generate logs on failure run: ./script/collectlogs - if: failure() + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + - name: Upload logs artifacts on failure - if: failure() - uses: actions/upload-artifact@v4 + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: functional-blockstorage-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* + + - name: Set job status + run: | + if [[ "$TESTS_SKIPPED" == "true" || "$TESTS_RUN" == "true" ]]; then + echo "Job completed successfully (either ran tests or skipped appropriately)" + exit 0 + else + echo "Job failed - neither tests ran nor were properly skipped" + exit 1 + fi diff --git a/.github/workflows/functional-compute.yaml b/.github/workflows/functional-compute.yaml index 6cd9962c1c..50edc26ed5 100644 --- a/.github/workflows/functional-compute.yaml +++ b/.github/workflows/functional-compute.yaml @@ -1,60 +1,98 @@ name: functional-compute on: + merge_group: pull_request: - paths: - - '**compute**' schedule: - cron: '0 0 */3 * *' jobs: functional-compute: + permissions: + contents: read strategy: fail-fast: false matrix: include: - name: "master" openstack_version: "master" - ubuntu_version: "22.04" - additional_services: "openstack-cli-server" - - name: "caracal" - openstack_version: "stable/2024.1" - ubuntu_version: "22.04" - additional_services: "" - - name: "bobcat" - openstack_version: "stable/2023.2" - ubuntu_version: "22.04" - additional_services: "" - - name: "antelope" - openstack_version: "stable/2023.1" - ubuntu_version: "22.04" - additional_services: "" + ubuntu_version: "24.04" + - name: "gazpacho" + openstack_version: "stable/2026.1" + ubuntu_version: "24.04" + - name: "epoxy" + openstack_version: "stable/2025.1" + ubuntu_version: "24.04" runs-on: ubuntu-${{ matrix.ubuntu_version }} - name: Deploy OpenStack ${{ matrix.name }} with Nova and run compute acceptance tests + name: Nova on OpenStack ${{ matrix.name }} steps: - name: Checkout Gophercloud - uses: actions/checkout@v4 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Check changed files + uses: ./.github/actions/file-filter + id: changed-files + with: + patterns: | + openstack/auth_env.go + openstack/client.go + openstack/endpoint.go + openstack/endpoint_location.go + openstack/config/provider_client.go + openstack/utils/choose_version.go + openstack/utils/discovery.go + **compute** + .github/workflows/functional-compute.yaml + + - name: Skip tests for unrelated changed-files + if: ${{ ! fromJSON(steps.changed-files.outputs.matches) }} + run: | + echo "No relevant files changed - skipping tests for ${{ matrix.name }}" + echo "TESTS_SKIPPED=true" >> $GITHUB_ENV + - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 # v0.19 with: branch: ${{ matrix.openstack_version }} conf_overrides: | CINDER_ISCSI_HELPER=lioadm - enabled_services: "${{ matrix.additional_services }}" + enabled_services: "openstack-cli-server" + - name: Checkout go - uses: actions/setup-go@v5 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: '^1.22' + go-version-file: 'go.mod' + cache: true + - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-compute + echo "TESTS_RUN=true" >> $GITHUB_ENV env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/compute/..." OS_BRANCH: ${{ matrix.openstack_version }} + - name: Generate logs on failure run: ./script/collectlogs - if: failure() + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + - name: Upload logs artifacts on failure - if: failure() - uses: actions/upload-artifact@v4 + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: functional-compute-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* + + - name: Set job status + run: | + if [[ "$TESTS_SKIPPED" == "true" || "$TESTS_RUN" == "true" ]]; then + echo "Job completed successfully (either ran tests or skipped appropriately)" + exit 0 + else + echo "Job failed - neither tests ran nor were properly skipped" + exit 1 + fi diff --git a/.github/workflows/functional-container.yaml b/.github/workflows/functional-container.yaml new file mode 100644 index 0000000000..36f179e0a1 --- /dev/null +++ b/.github/workflows/functional-container.yaml @@ -0,0 +1,109 @@ +name: functional-container +on: + merge_group: + pull_request: + schedule: + - cron: '0 0 */3 * *' +jobs: + functional-container: + permissions: + contents: read + strategy: + fail-fast: false + matrix: + include: + - name: "master" + openstack_version: "master" + ubuntu_version: "24.04" + - name: "gazpacho" + openstack_version: "stable/2026.1" + ubuntu_version: "24.04" + - name: "epoxy" + openstack_version: "stable/2025.1" + ubuntu_version: "24.04" + runs-on: ubuntu-${{ matrix.ubuntu_version }} + name: Zun on OpenStack ${{ matrix.name }} + steps: + - name: Checkout Gophercloud + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Check changed files + uses: ./.github/actions/file-filter + id: changed-files + with: + patterns: | + openstack/auth_env.go + openstack/client.go + openstack/endpoint.go + openstack/endpoint_location.go + openstack/config/provider_client.go + openstack/utils/choose_version.go + openstack/utils/discovery.go + **container/** + .github/workflows/functional-container.yaml + + - name: Skip tests for unrelated changed-files + if: ${{ ! fromJSON(steps.changed-files.outputs.matches) }} + run: | + echo "No relevant files changed - skipping tests for ${{ matrix.name }}" + echo "TESTS_SKIPPED=true" >> $GITHUB_ENV + + - name: Deploy devstack + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 # v0.19 + with: + branch: ${{ matrix.openstack_version }} + enable_workaround_docker_io: false + conf_overrides: | + enable_plugin zun https://github.com/openstack/zun ${{ matrix.openstack_version }} + enable_plugin devstack-plugin-container https://github.com/openstack/devstack-plugin-container ${{ matrix.openstack_version }} + enable_plugin kuryr-libnetwork https://github.com/openstack/kuryr-libnetwork ${{ matrix.openstack_version }} + KURYR_CONFIG_DIR=/etc/kuryr-libnetwork + ZUN_DRIVER=docker + ZUN_CAPSULE_DRIVER=docker + ZUN_DB_TYPE=sql + ENABLE_CONTAINERD_CRI=true + [[post-config|\$ZUN_CONF]] + [DEFAULT] + sandbox_image = registry.k8s.io/pause:3.9 + enabled_services: "zun-api,zun-compute,zun-wsproxy,openstack-cli-server" + + - name: Checkout go + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version-file: 'go.mod' + cache: true + + - name: Run Gophercloud acceptance tests + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-container + echo "TESTS_RUN=true" >> $GITHUB_ENV + env: + DEVSTACK_PATH: ${{ github.workspace }}/devstack + OS_BRANCH: ${{ matrix.openstack_version }} + + - name: Generate logs on failure + run: ./script/collectlogs + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + + - name: Upload logs artifacts on failure + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: functional-container-${{ matrix.name }}-${{ github.run_id }} + path: /tmp/devstack-logs/* + + - name: Set job status + run: | + if [[ "$TESTS_SKIPPED" == "true" || "$TESTS_RUN" == "true" ]]; then + echo "Job completed successfully (either ran tests or skipped appropriately)" + exit 0 + else + echo "Job failed - neither tests ran nor were properly skipped" + exit 1 + fi diff --git a/.github/workflows/functional-containerinfra.yaml b/.github/workflows/functional-containerinfra.yaml index a5f25b2d68..a6d9d1617a 100644 --- a/.github/workflows/functional-containerinfra.yaml +++ b/.github/workflows/functional-containerinfra.yaml @@ -1,51 +1,67 @@ name: functional-containerinfra on: + merge_group: pull_request: - paths: - - '**containerinfra**' schedule: - cron: '0 0 */3 * *' jobs: functional-containerinfra: + permissions: + contents: read strategy: fail-fast: false matrix: include: - name: "master" openstack_version: "master" - ubuntu_version: "22.04" + ubuntu_version: "24.04" devstack_conf_overrides: | enable_plugin magnum https://github.com/openstack/magnum master MAGNUMCLIENT_BRANCH=master - additional_services: "openstack-cli-server" - - name: "caracal" - openstack_version: "stable/2024.1" - ubuntu_version: "22.04" + - name: "gazpacho" + openstack_version: "stable/2026.1" + ubuntu_version: "24.04" devstack_conf_overrides: | - enable_plugin magnum https://github.com/openstack/magnum stable/2024.1 - MAGNUMCLIENT_BRANCH=stable/2024.1 - additional_services: "" - - name: "bobcat" - openstack_version: "stable/2023.2" - ubuntu_version: "22.04" + enable_plugin magnum https://github.com/openstack/magnum stable/2026.1 + MAGNUMCLIENT_BRANCH=stable/2026.1 + - name: "epoxy" + openstack_version: "stable/2025.1" + ubuntu_version: "24.04" devstack_conf_overrides: | - enable_plugin magnum https://github.com/openstack/magnum stable/2023.2 - MAGNUMCLIENT_BRANCH=stable/2023.2 - additional_services: "" - - name: "antelope" - openstack_version: "stable/2023.1" - ubuntu_version: "22.04" - devstack_conf_overrides: | - enable_plugin magnum https://github.com/openstack/magnum stable/2023.1 - MAGNUMCLIENT_BRANCH=stable/2023.1 - additional_services: "" + enable_plugin magnum https://github.com/openstack/magnum stable/2025.1 + MAGNUMCLIENT_BRANCH=stable/2025.1 runs-on: ubuntu-${{ matrix.ubuntu_version }} - name: Deploy OpenStack ${{ matrix.name }} with Magnum and run containerinfra acceptance tests + name: Magnum on OpenStack ${{ matrix.name }} steps: - name: Checkout Gophercloud - uses: actions/checkout@v4 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Check changed files + uses: ./.github/actions/file-filter + id: changed-files + with: + patterns: | + openstack/auth_env.go + openstack/client.go + openstack/endpoint.go + openstack/endpoint_location.go + openstack/config/provider_client.go + openstack/utils/choose_version.go + openstack/utils/discovery.go + **containerinfra** + .github/workflows/functional-containerinfra.yaml + + - name: Skip tests for unrelated changed-files + if: ${{ ! fromJSON(steps.changed-files.outputs.matches) }} + run: | + echo "No relevant files changed - skipping tests for ${{ matrix.name }}" + echo "TESTS_SKIPPED=true" >> $GITHUB_ENV + - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 # v0.19 with: branch: ${{ matrix.openstack_version }} conf_overrides: | @@ -54,24 +70,44 @@ jobs: GLANCE_LIMIT_IMAGE_SIZE_TOTAL=5000 SWIFT_MAX_FILE_SIZE=5368709122 KEYSTONE_ADMIN_ENDPOINT=true + ${{ matrix.devstack_conf_overrides }} - enabled_services: "h-eng,h-api,h-api-cfn,h-api-cw,${{ matrix.additional_services }}" + enabled_services: "h-eng,h-api,h-api-cfn,h-api-cw,openstack-cli-server" + - name: Checkout go - uses: actions/setup-go@v5 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: '^1.22' + go-version-file: 'go.mod' + cache: true + - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-containerinfra + echo "TESTS_RUN=true" >> $GITHUB_ENV env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/containerinfra/..." OS_BRANCH: ${{ matrix.openstack_version }} + - name: Generate logs on failure run: ./script/collectlogs - if: failure() + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + - name: Upload logs artifacts on failure - if: failure() - uses: actions/upload-artifact@v4 + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: functional-containerinfra-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* + + - name: Set job status + run: | + if [[ "$TESTS_SKIPPED" == "true" || "$TESTS_RUN" == "true" ]]; then + echo "Job completed successfully (either ran tests or skipped appropriately)" + exit 0 + else + echo "Job failed - neither tests ran nor were properly skipped" + exit 1 + fi diff --git a/.github/workflows/functional-db.yaml b/.github/workflows/functional-db.yaml new file mode 100644 index 0000000000..677f41a0dc --- /dev/null +++ b/.github/workflows/functional-db.yaml @@ -0,0 +1,105 @@ +name: functional-db +on: + merge_group: + pull_request: + schedule: + - cron: '0 0 */3 * *' +jobs: + functional-db: + permissions: + contents: read + strategy: + fail-fast: false + matrix: + include: + - name: "master" + openstack_version: "master" + ubuntu_version: "24.04" + - name: "gazpacho" + openstack_version: "stable/2026.1" + ubuntu_version: "24.04" + - name: "epoxy" + openstack_version: "stable/2025.1" + ubuntu_version: "24.04" + runs-on: ubuntu-${{ matrix.ubuntu_version }} + name: Trove on OpenStack ${{ matrix.name }} + steps: + - name: Checkout Gophercloud + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Check changed files + uses: ./.github/actions/file-filter + id: changed-files + with: + patterns: | + openstack/auth_env.go + openstack/client.go + openstack/endpoint.go + openstack/endpoint_location.go + openstack/config/provider_client.go + openstack/utils/choose_version.go + openstack/utils/discovery.go + **db** + .github/workflows/functional-db.yaml + + - name: Skip tests for unrelated changed-files + if: ${{ ! fromJSON(steps.changed-files.outputs.matches) }} + run: | + echo "No relevant files changed - skipping tests for ${{ matrix.name }}" + echo "TESTS_SKIPPED=true" >> $GITHUB_ENV + + - name: Prepare trove-tempest-plugin + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + sudo mkdir -p /opt/stack + sudo chown -R $(whoami) /opt/stack + git clone https://github.com/openstack/trove-tempest-plugin.git /opt/stack/trove-tempest-plugin + + - name: Deploy devstack + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 # v0.19 + with: + branch: ${{ matrix.openstack_version }} + conf_overrides: | + enable_plugin trove https://github.com/openstack/trove ${{ matrix.openstack_version }} + enabled_services: "trove,tr-api,tr-tmgr,tr-cond,openstack-cli-server" + + - name: Checkout go + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version-file: 'go.mod' + cache: true + + - name: Run Gophercloud acceptance tests + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-db GOTESTFLAGS=-short + echo "TESTS_RUN=true" >> $GITHUB_ENV + env: + DEVSTACK_PATH: ${{ github.workspace }}/devstack + OS_BRANCH: ${{ matrix.openstack_version }} + + - name: Generate logs on failure + run: ./script/collectlogs + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + + - name: Upload logs artifacts on failure + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: functional-db-${{ matrix.name }}-${{ github.run_id }} + path: /tmp/devstack-logs/* + + - name: Set job status + run: | + if [[ "$TESTS_SKIPPED" == "true" || "$TESTS_RUN" == "true" ]]; then + echo "Job completed successfully (either ran tests or skipped appropriately)" + exit 0 + else + echo "Job failed - neither tests ran nor were properly skipped" + exit 1 + fi diff --git a/.github/workflows/functional-dns.yaml b/.github/workflows/functional-dns.yaml index 1a06d3d80e..75a1bc08e6 100644 --- a/.github/workflows/functional-dns.yaml +++ b/.github/workflows/functional-dns.yaml @@ -1,61 +1,100 @@ name: functional-dns on: + merge_group: pull_request: - paths: - - '**openstack/dns**' - - '**functional-dns.yaml' schedule: - cron: '0 0 */3 * *' jobs: functional-dns: + permissions: + contents: read strategy: fail-fast: false matrix: include: - name: "master" openstack_version: "master" - ubuntu_version: "22.04" - additional_services: "openstack-cli-server" - - name: "caracal" - openstack_version: "stable/2024.1" - ubuntu_version: "22.04" - additional_services: "" - - name: "bobcat" - openstack_version: "stable/2023.2" - ubuntu_version: "22.04" - additional_services: "" - - name: "antelope" - openstack_version: "stable/2023.1" - ubuntu_version: "22.04" - additional_services: "" + ubuntu_version: "24.04" + - name: "gazpacho" + openstack_version: "stable/2026.1" + ubuntu_version: "24.04" + - name: "epoxy" + openstack_version: "stable/2025.1" + ubuntu_version: "24.04" runs-on: ubuntu-${{ matrix.ubuntu_version }} - name: Deploy OpenStack ${{ matrix.name }} with Designate and run dns acceptance tests + name: Designate on OpenStack ${{ matrix.name }} steps: - name: Checkout Gophercloud - uses: actions/checkout@v4 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Check changed files + uses: ./.github/actions/file-filter + id: changed-files + with: + patterns: | + openstack/auth_env.go + openstack/client.go + openstack/endpoint.go + openstack/endpoint_location.go + openstack/config/provider_client.go + openstack/utils/choose_version.go + openstack/utils/discovery.go + **dns** + .github/workflows/functional-dns.yaml + + - name: Skip tests for unrelated changed-files + if: ${{ ! fromJSON(steps.changed-files.outputs.matches) }} + run: | + echo "No relevant files changed - skipping tests for ${{ matrix.name }}" + echo "TESTS_SKIPPED=true" >> $GITHUB_ENV + - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 # v0.19 with: branch: ${{ matrix.openstack_version }} conf_overrides: | enable_plugin designate https://github.com/openstack/designate ${{ matrix.openstack_version }} - enabled_services: "designate,designate-central,designate-api,designate-worker,designate-producer,designate-mdns,${{ matrix.additional_services }}" + + ${{ matrix.devstack_conf_overrides }} + enabled_services: "designate,designate-central,designate-api,designate-worker,designate-producer,designate-mdns,openstack-cli-server" + - name: Checkout go - uses: actions/setup-go@v5 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: '^1.22' + go-version-file: 'go.mod' + cache: true + - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-dns + echo "TESTS_RUN=true" >> $GITHUB_ENV env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/dns/..." OS_BRANCH: ${{ matrix.openstack_version }} + - name: Generate logs on failure run: ./script/collectlogs - if: failure() + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + - name: Upload logs artifacts on failure - if: failure() - uses: actions/upload-artifact@v4 + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: functional-dns-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* + + - name: Set job status + run: | + if [[ "$TESTS_SKIPPED" == "true" || "$TESTS_RUN" == "true" ]]; then + echo "Job completed successfully (either ran tests or skipped appropriately)" + exit 0 + else + echo "Job failed - neither tests ran nor were properly skipped" + exit 1 + fi diff --git a/.github/workflows/functional-fwaas_v2.yaml b/.github/workflows/functional-fwaas_v2.yaml index 5beeb4da1f..4a4a81972d 100644 --- a/.github/workflows/functional-fwaas_v2.yaml +++ b/.github/workflows/functional-fwaas_v2.yaml @@ -1,65 +1,121 @@ +# TODO(stephenfin): neutron-fwaas may support OVN now. If so, we can combine +# this job with the functional-networking job. See [1] +# +# [1] https://bugs.launchpad.net/neutron/+bug/1971958 name: functional-fwaas_v2 on: + merge_group: pull_request: - paths: - - '**networking/v2/extensions/fwaas_v2**' schedule: - cron: '0 0 */3 * *' jobs: functional-fwaas_v2: + permissions: + contents: read strategy: fail-fast: false matrix: include: - name: "master" openstack_version: "master" - ubuntu_version: "22.04" - additional_services: "openstack-cli-server" - - name: "caracal" - openstack_version: "stable/2024.1" - ubuntu_version: "22.04" - additional_services: "" - - name: "bobcat" - openstack_version: "stable/2023.2" - ubuntu_version: "22.04" - additional_services: "" - - name: "antelope" - openstack_version: "stable/2023.1" - ubuntu_version: "22.04" - additional_services: "" + ubuntu_version: "24.04" + - name: "gazpacho" + openstack_version: "stable/2026.1" + ubuntu_version: "24.04" + - name: "epoxy" + openstack_version: "stable/2025.1" + ubuntu_version: "24.04" runs-on: ubuntu-${{ matrix.ubuntu_version }} - name: Deploy OpenStack ${{ matrix.name }} with enabled FWaaS_v2 and run networking acceptance tests + name: FWaaS_v2 on OpenStack ${{ matrix.name }} steps: - name: Checkout Gophercloud - uses: actions/checkout@v4 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Check changed files + uses: ./.github/actions/file-filter + id: changed-files + with: + patterns: | + openstack/auth_env.go + openstack/client.go + openstack/endpoint.go + openstack/endpoint_location.go + openstack/config/provider_client.go + openstack/utils/choose_version.go + openstack/utils/discovery.go + **networking/v2/extensions/fwaas_v2** + .github/workflows/functional-fwaas_v2.yaml + + - name: Skip tests for unrelated changed-files + if: ${{ ! fromJSON(steps.changed-files.outputs.matches) }} + run: | + echo "No relevant files changed - skipping tests for ${{ matrix.name }}" + echo "TESTS_SKIPPED=true" >> $GITHUB_ENV + + - name: Create additional neutron policies + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + mkdir /tmp/neutron-policies + cat << EOF >> /tmp/neutron-policies/port_binding.yaml + --- + "create_port:binding:profile": "rule:admin_only or rule:service_api" + "update_port:binding:profile": "rule:admin_only or rule:service_api" + EOF + - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 # v0.19 with: branch: ${{ matrix.openstack_version }} conf_overrides: | - enable_plugin neutron-fwaas https://opendev.org/openstack/neutron-fwaas ${{ matrix.openstack_version }} + enable_plugin neutron-fwaas https://github.com/openstack/neutron-fwaas ${{ matrix.openstack_version }} Q_AGENT=openvswitch Q_ML2_PLUGIN_MECHANISM_DRIVERS=openvswitch,l2population Q_ML2_PLUGIN_TYPE_DRIVERS=flat,gre,vlan,vxlan Q_ML2_TENANT_NETWORK_TYPE=vxlan Q_TUNNEL_TYPES=vxlan,gre - enabled_services: 'q-svc,q-agt,q-dhcp,q-l3,q-meta,q-fwaas-v2,-cinder,-horizon,-tempest,-swift,-c-sch,-c-api,-c-vol,-c-bak,-ovn,-ovn-controller,-ovn-northd,-q-ovn-metadata-agent,${{ matrix.additional_services }}' + + [[post-config|\$NEUTRON_CONF]] + [oslo_policy] + policy_dirs = /tmp/neutron-policies + enabled_services: 'q-svc,q-agt,q-dhcp,q-l3,q-meta,q-fwaas-v2,-cinder,-horizon,-tempest,-swift,-c-sch,-c-api,-c-vol,-c-bak,-ovn,-ovn-controller,-ovn-northd,-q-ovn-metadata-agent,openstack-cli-server' + - name: Checkout go - uses: actions/setup-go@v5 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: '^1.22' + go-version-file: 'go.mod' + cache: true + - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-fwaas + echo "TESTS_RUN=true" >> $GITHUB_ENV env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/networking/v2/extensions/fwaas_v2/..." OS_BRANCH: ${{ matrix.openstack_version }} + - name: Generate logs on failure run: ./script/collectlogs - if: failure() + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + - name: Upload logs artifacts on failure - if: failure() - uses: actions/upload-artifact@v4 + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: functional-fwaas_v2-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* + + - name: Set job status + run: | + if [[ "$TESTS_SKIPPED" == "true" || "$TESTS_RUN" == "true" ]]; then + echo "Job completed successfully (either ran tests or skipped appropriately)" + exit 0 + else + echo "Job failed - neither tests ran nor were properly skipped" + exit 1 + fi diff --git a/.github/workflows/functional-identity.yaml b/.github/workflows/functional-identity.yaml index f29da5323d..0cd48ea457 100644 --- a/.github/workflows/functional-identity.yaml +++ b/.github/workflows/functional-identity.yaml @@ -1,58 +1,96 @@ name: functional-identity on: + merge_group: pull_request: - paths: - - '**identity**' schedule: - cron: '0 0 */3 * *' jobs: functional-identity: + permissions: + contents: read strategy: fail-fast: false matrix: include: - name: "master" openstack_version: "master" - ubuntu_version: "22.04" - additional_services: "openstack-cli-server" - - name: "caracal" - openstack_version: "stable/2024.1" - ubuntu_version: "22.04" - additional_services: "" - - name: "bobcat" - openstack_version: "stable/2023.2" - ubuntu_version: "22.04" - additional_services: "" - - name: "antelope" - openstack_version: "stable/2023.1" - ubuntu_version: "22.04" - additional_services: "" + ubuntu_version: "24.04" + - name: "gazpacho" + openstack_version: "stable/2026.1" + ubuntu_version: "24.04" + - name: "epoxy" + openstack_version: "stable/2025.1" + ubuntu_version: "24.04" runs-on: ubuntu-${{ matrix.ubuntu_version }} - name: Deploy OpenStack ${{ matrix.name }} with Keystone and run identity acceptance tests + name: Keystone on OpenStack ${{ matrix.name }} steps: - name: Checkout Gophercloud - uses: actions/checkout@v4 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Check changed files + uses: ./.github/actions/file-filter + id: changed-files + with: + patterns: | + openstack/auth_env.go + openstack/client.go + openstack/endpoint.go + openstack/endpoint_location.go + openstack/config/provider_client.go + openstack/utils/choose_version.go + openstack/utils/discovery.go + **identity** + .github/workflows/functional-identity.yaml + + - name: Skip tests for unrelated changed-files + if: ${{ ! fromJSON(steps.changed-files.outputs.matches) }} + run: | + echo "No relevant files changed - skipping tests for ${{ matrix.name }}" + echo "TESTS_SKIPPED=true" >> $GITHUB_ENV + - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 # v0.19 with: branch: ${{ matrix.openstack_version }} - enabled_services: "${{ matrix.additional_services }}" + enabled_services: "openstack-cli-server" + - name: Checkout go - uses: actions/setup-go@v5 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: '^1.22' + go-version-file: 'go.mod' + cache: true + - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-identity + echo "TESTS_RUN=true" >> $GITHUB_ENV env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/identity/..." OS_BRANCH: ${{ matrix.openstack_version }} + - name: Generate logs on failure run: ./script/collectlogs - if: failure() + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + - name: Upload logs artifacts on failure - if: failure() - uses: actions/upload-artifact@v4 + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: functional-identity-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* + + - name: Set job status + run: | + if [[ "$TESTS_SKIPPED" == "true" || "$TESTS_RUN" == "true" ]]; then + echo "Job completed successfully (either ran tests or skipped appropriately)" + exit 0 + else + echo "Job failed - neither tests ran nor were properly skipped" + exit 1 + fi diff --git a/.github/workflows/functional-image.yaml b/.github/workflows/functional-image.yaml index 1dde9ddb16..213372d748 100644 --- a/.github/workflows/functional-image.yaml +++ b/.github/workflows/functional-image.yaml @@ -1,58 +1,96 @@ name: functional-image on: + merge_group: pull_request: - paths: - - '**image**' schedule: - cron: '0 0 */3 * *' jobs: functional-image: + permissions: + contents: read strategy: fail-fast: false matrix: include: - name: "master" openstack_version: "master" - ubuntu_version: "22.04" - additional_services: "openstack-cli-server" - - name: "caracal" - openstack_version: "stable/2024.1" - ubuntu_version: "22.04" - additional_services: "" - - name: "bobcat" - openstack_version: "stable/2023.2" - ubuntu_version: "22.04" - additional_services: "" - - name: "antelope" - openstack_version: "stable/2023.1" - ubuntu_version: "22.04" - additional_services: "" + ubuntu_version: "24.04" + - name: "gazpacho" + openstack_version: "stable/2026.1" + ubuntu_version: "24.04" + - name: "epoxy" + openstack_version: "stable/2025.1" + ubuntu_version: "24.04" runs-on: ubuntu-${{ matrix.ubuntu_version }} - name: Deploy OpenStack ${{ matrix.name }} with Glance and run image acceptance tests + name: Glance on OpenStack ${{ matrix.name }} steps: - name: Checkout Gophercloud - uses: actions/checkout@v4 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Check changed files + uses: ./.github/actions/file-filter + id: changed-files + with: + patterns: | + openstack/auth_env.go + openstack/client.go + openstack/endpoint.go + openstack/endpoint_location.go + openstack/config/provider_client.go + openstack/utils/choose_version.go + openstack/utils/discovery.go + **image** + .github/workflows/functional-image.yaml + + - name: Skip tests for unrelated changed-files + if: ${{ ! fromJSON(steps.changed-files.outputs.matches) }} + run: | + echo "No relevant files changed - skipping tests for ${{ matrix.name }}" + echo "TESTS_SKIPPED=true" >> $GITHUB_ENV + - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 # v0.19 with: branch: ${{ matrix.openstack_version }} - enabled_services: "${{ matrix.additional_services }}" + enabled_services: "openstack-cli-server" + - name: Checkout go - uses: actions/setup-go@v5 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: '^1.22' + go-version-file: 'go.mod' + cache: true + - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-image + echo "TESTS_RUN=true" >> $GITHUB_ENV env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/image/..." OS_BRANCH: ${{ matrix.openstack_version }} + - name: Generate logs on failure run: ./script/collectlogs - if: failure() + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + - name: Upload logs artifacts on failure - if: failure() - uses: actions/upload-artifact@v4 + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: functional-image-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* + + - name: Set job status + run: | + if [[ "$TESTS_SKIPPED" == "true" || "$TESTS_RUN" == "true" ]]; then + echo "Job completed successfully (either ran tests or skipped appropriately)" + exit 0 + else + echo "Job failed - neither tests ran nor were properly skipped" + exit 1 + fi diff --git a/.github/workflows/functional-keymanager.yaml b/.github/workflows/functional-keymanager.yaml index f20268060e..94c856b158 100644 --- a/.github/workflows/functional-keymanager.yaml +++ b/.github/workflows/functional-keymanager.yaml @@ -1,60 +1,100 @@ name: functional-keymanager on: + merge_group: pull_request: - paths: - - '**keymanager**' schedule: - cron: '0 0 */3 * *' jobs: functional-keymanager: + permissions: + contents: read strategy: fail-fast: false matrix: include: - name: "master" openstack_version: "master" - ubuntu_version: "22.04" - additional_services: "openstack-cli-server" - - name: "caracal" - openstack_version: "stable/2024.1" - ubuntu_version: "22.04" - additional_services: "" - - name: "bobcat" - openstack_version: "stable/2023.2" - ubuntu_version: "22.04" - additional_services: "" - - name: "antelope" - openstack_version: "stable/2023.1" - ubuntu_version: "22.04" - additional_services: "" + ubuntu_version: "24.04" + - name: "gazpacho" + openstack_version: "stable/2026.1" + ubuntu_version: "24.04" + - name: "epoxy" + openstack_version: "stable/2025.1" + ubuntu_version: "24.04" runs-on: ubuntu-${{ matrix.ubuntu_version }} - name: Deploy OpenStack ${{ matrix.name }} with Barbican and run keymanager acceptance tests + name: Barbican on OpenStack ${{ matrix.name }} steps: - name: Checkout Gophercloud - uses: actions/checkout@v4 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Check changed files + uses: ./.github/actions/file-filter + id: changed-files + with: + patterns: | + openstack/auth_env.go + openstack/client.go + openstack/endpoint.go + openstack/endpoint_location.go + openstack/config/provider_client.go + openstack/utils/choose_version.go + openstack/utils/discovery.go + **keymanager** + .github/workflows/functional-keymanager.yaml + + - name: Skip tests for unrelated changed-files + if: ${{ ! fromJSON(steps.changed-files.outputs.matches) }} + run: | + echo "No relevant files changed - skipping tests for ${{ matrix.name }}" + echo "TESTS_SKIPPED=true" >> $GITHUB_ENV + - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 # v0.19 with: branch: ${{ matrix.openstack_version }} conf_overrides: | enable_plugin barbican https://github.com/openstack/barbican ${{ matrix.openstack_version }} - enabled_services: "barbican-svc,barbican-retry,barbican-keystone-listener,${{ matrix.additional_services }}" + + ${{ matrix.devstack_conf_overrides }} + enabled_services: "barbican-svc,barbican-retry,barbican-keystone-listener,openstack-cli-server" + - name: Checkout go - uses: actions/setup-go@v5 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: '^1.22' + go-version-file: 'go.mod' + cache: true + - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-keymanager + echo "TESTS_RUN=true" >> $GITHUB_ENV env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/keymanager/..." OS_BRANCH: ${{ matrix.openstack_version }} + - name: Generate logs on failure run: ./script/collectlogs - if: failure() + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + - name: Upload logs artifacts on failure - if: failure() - uses: actions/upload-artifact@v4 + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: functional-keymanager-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* + + - name: Set job status + run: | + if [[ "$TESTS_SKIPPED" == "true" || "$TESTS_RUN" == "true" ]]; then + echo "Job completed successfully (either ran tests or skipped appropriately)" + exit 0 + else + echo "Job failed - neither tests ran nor were properly skipped" + exit 1 + fi diff --git a/.github/workflows/functional-loadbalancer.yaml b/.github/workflows/functional-loadbalancer.yaml index a0fb56f1d2..a3355516bb 100644 --- a/.github/workflows/functional-loadbalancer.yaml +++ b/.github/workflows/functional-loadbalancer.yaml @@ -1,61 +1,116 @@ name: functional-loadbalancer on: + merge_group: pull_request: - paths: - - '**loadbalancer**' schedule: - cron: '0 0 */3 * *' jobs: functional-loadbalancer: + permissions: + contents: read strategy: fail-fast: false matrix: include: - name: "master" openstack_version: "master" - ubuntu_version: "22.04" - additional_services: "openstack-cli-server" - - name: "caracal" - openstack_version: "stable/2024.1" - ubuntu_version: "22.04" - additional_services: "" - - name: "bobcat" - openstack_version: "stable/2023.2" - ubuntu_version: "22.04" - additional_services: "" - - name: "antelope" - openstack_version: "stable/2023.1" - ubuntu_version: "22.04" - additional_services: "" + ubuntu_version: "24.04" + amphora_image_url: "https://tarballs.opendev.org/openstack/octavia/test-images/test-only-amphora-x64-haproxy-ubuntu-noble.qcow2" + devstack_conf_overrides: | + LIBVIRT_CPU_MODE=host-passthrough + - name: "gazpacho" + openstack_version: "stable/2026.1" + ubuntu_version: "24.04" + amphora_image_url: "https://tarballs.opendev.org/openstack/octavia/test-images/test-only-amphora-x64-haproxy-ubuntu-noble.qcow2" + devstack_conf_overrides: | + LIBVIRT_CPU_MODE=host-passthrough + - name: "epoxy" + openstack_version: "stable/2025.1" + ubuntu_version: "24.04" + amphora_image_url: "https://tarballs.opendev.org/openstack/octavia/test-images/test-only-amphora-x64-haproxy-ubuntu-jammy.qcow2" + devstack_conf_overrides: | + LIBVIRT_CPU_MODE=host-passthrough runs-on: ubuntu-${{ matrix.ubuntu_version }} - name: Deploy OpenStack ${{ matrix.name }} with Octavia and run loadbalancer acceptance tests + name: Octavia on OpenStack ${{ matrix.name }} steps: - name: Checkout Gophercloud - uses: actions/checkout@v4 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Check changed files + uses: ./.github/actions/file-filter + id: changed-files + with: + patterns: | + openstack/auth_env.go + openstack/client.go + openstack/endpoint.go + openstack/endpoint_location.go + openstack/config/provider_client.go + openstack/utils/choose_version.go + openstack/utils/discovery.go + **loadbalancer** + .github/workflows/functional-loadbalancer.yaml + + - name: Skip tests for unrelated changed-files + if: ${{ ! fromJSON(steps.changed-files.outputs.matches) }} + run: | + echo "No relevant files changed - skipping tests for ${{ matrix.name }}" + echo "TESTS_SKIPPED=true" >> $GITHUB_ENV + + - name: Download pre-built amphora image + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + wget -q "${{ matrix.amphora_image_url }}" -O /tmp/amphora-x64-haproxy.qcow2 + - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 # v0.19 with: branch: ${{ matrix.openstack_version }} conf_overrides: | enable_plugin octavia https://github.com/openstack/octavia ${{ matrix.openstack_version }} enable_plugin neutron https://github.com/openstack/neutron ${{ matrix.openstack_version }} - enabled_services: "octavia,o-api,o-cw,o-hk,o-hm,o-da,neutron-qos,${{ matrix.additional_services }}" + OCTAVIA_AMP_IMAGE_FILE=/tmp/amphora-x64-haproxy.qcow2 + + ${{ matrix.devstack_conf_overrides }} + enabled_services: "octavia,o-api,o-cw,o-hk,o-hm,o-da,neutron-qos,openstack-cli-server" + - name: Checkout go - uses: actions/setup-go@v5 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: '^1.22' + go-version-file: 'go.mod' + cache: true + - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-loadbalancer + echo "TESTS_RUN=true" >> $GITHUB_ENV env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/loadbalancer/..." OS_BRANCH: ${{ matrix.openstack_version }} + - name: Generate logs on failure run: ./script/collectlogs - if: failure() + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + - name: Upload logs artifacts on failure - if: failure() - uses: actions/upload-artifact@v4 + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: functional-loadbalancer-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* + + - name: Set job status + run: | + if [[ "$TESTS_SKIPPED" == "true" || "$TESTS_RUN" == "true" ]]; then + echo "Job completed successfully (either ran tests or skipped appropriately)" + exit 0 + else + echo "Job failed - neither tests ran nor were properly skipped" + exit 1 + fi diff --git a/.github/workflows/functional-messaging.yaml b/.github/workflows/functional-messaging.yaml index ebf136bd59..3df50d278e 100644 --- a/.github/workflows/functional-messaging.yaml +++ b/.github/workflows/functional-messaging.yaml @@ -1,61 +1,99 @@ name: functional-messaging on: + merge_group: pull_request: - paths: - - '**messaging**' schedule: - cron: '0 0 */3 * *' jobs: functional-messaging: + permissions: + contents: read strategy: fail-fast: false matrix: include: - name: "master" openstack_version: "master" - ubuntu_version: "22.04" - additional_services: "openstack-cli-server" - - name: "caracal" - openstack_version: "stable/2024.1" - ubuntu_version: "22.04" - additional_services: "" - - name: "bobcat" - openstack_version: "stable/2023.2" - ubuntu_version: "22.04" - additional_services: "" - - name: "antelope" - openstack_version: "stable/2023.1" - ubuntu_version: "22.04" - additional_services: "" + ubuntu_version: "24.04" + - name: "gazpacho" + openstack_version: "stable/2026.1" + ubuntu_version: "24.04" + - name: "epoxy" + openstack_version: "stable/2025.1" + ubuntu_version: "24.04" runs-on: ubuntu-${{ matrix.ubuntu_version }} - name: Deploy OpenStack ${{ matrix.name }} with Zaqar and run messaging acceptance tests + name: Zaqar on OpenStack ${{ matrix.name }} steps: - name: Checkout Gophercloud - uses: actions/checkout@v4 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Check changed files + uses: ./.github/actions/file-filter + id: changed-files + with: + patterns: | + openstack/auth_env.go + openstack/client.go + openstack/endpoint.go + openstack/endpoint_location.go + openstack/config/provider_client.go + openstack/utils/choose_version.go + openstack/utils/discovery.go + **messaging** + .github/workflows/functional-messaging.yaml + + - name: Skip tests for unrelated changed-files + if: ${{ ! fromJSON(steps.changed-files.outputs.matches) }} + run: | + echo "No relevant files changed - skipping tests for ${{ matrix.name }}" + echo "TESTS_SKIPPED=true" >> $GITHUB_ENV + - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 # v0.19 with: branch: ${{ matrix.openstack_version }} conf_overrides: | enable_plugin zaqar https://github.com/openstack/zaqar ${{ matrix.openstack_version }} ZAQARCLIENT_BRANCH=${{ matrix.openstack_version }} - enabled_services: "${{ matrix.additional_services }}" + enabled_services: "openstack-cli-server" + - name: Checkout go - uses: actions/setup-go@v5 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: '^1.22' + go-version-file: 'go.mod' + cache: true + - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-messaging + echo "TESTS_RUN=true" >> $GITHUB_ENV env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/messaging/..." OS_BRANCH: ${{ matrix.openstack_version }} + - name: Generate logs on failure run: ./script/collectlogs - if: failure() + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + - name: Upload logs artifacts on failure - if: failure() - uses: actions/upload-artifact@v4 + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: functional-messaging-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* + + - name: Set job status + run: | + if [[ "$TESTS_SKIPPED" == "true" || "$TESTS_RUN" == "true" ]]; then + echo "Job completed successfully (either ran tests or skipped appropriately)" + exit 0 + else + echo "Job failed - neither tests ran nor were properly skipped" + exit 1 + fi diff --git a/.github/workflows/functional-metric.yaml b/.github/workflows/functional-metric.yaml new file mode 100644 index 0000000000..50b350c70e --- /dev/null +++ b/.github/workflows/functional-metric.yaml @@ -0,0 +1,99 @@ +name: functional-metric +on: + merge_group: + pull_request: + schedule: + - cron: '0 0 */3 * *' +jobs: + functional-metric: + permissions: + contents: read + strategy: + fail-fast: false + matrix: + include: + - name: "master" + openstack_version: "master" + ubuntu_version: "24.04" + runs-on: ubuntu-${{ matrix.ubuntu_version }} + name: Aetos on OpenStack ${{ matrix.name }} + steps: + - name: Checkout Gophercloud + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Check changed files + uses: ./.github/actions/file-filter + id: changed-files + with: + patterns: | + openstack/auth_env.go + openstack/client.go + openstack/endpoint.go + openstack/endpoint_location.go + openstack/config/provider_client.go + openstack/utils/choose_version.go + openstack/utils/discovery.go + **metric** + .github/workflows/functional-metric.yaml + + - name: Skip tests for unrelated changed-files + if: ${{ ! fromJSON(steps.changed-files.outputs.matches) }} + run: | + echo "No relevant files changed - skipping tests for ${{ matrix.name }}" + echo "TESTS_SKIPPED=true" >> $GITHUB_ENV + + - name: Deploy devstack + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 # v0.19 + with: + branch: ${{ matrix.openstack_version }} + conf_overrides: | + enable_plugin devstack-plugin-prometheus https://github.com/openstack/devstack-plugin-prometheus ${{ matrix.openstack_version }} + enable_plugin ceilometer https://github.com/openstack/ceilometer ${{ matrix.openstack_version }} + CEILOMETER_BACKEND=sg-core + PROMETHEUS_CUSTOM_SCRAPE_TARGETS="localhost:3000" + enable_plugin sg-core https://github.com/openstack-k8s-operators/sg-core main + enable_plugin aetos https://github.com/openstack/aetos ${{ matrix.openstack_version }} + + ${{ matrix.devstack_conf_overrides }} + enabled_services: "prometheus,node_exporter,openstack_exporter,ceilometer-acompute,ceilometer-acentral,ceilometer-anotification,sg-core,aetos,openstack-cli-server" + + - name: Checkout go + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version-file: 'go.mod' + cache: true + + - name: Run Gophercloud acceptance tests + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-metric + echo "TESTS_RUN=true" >> $GITHUB_ENV + env: + DEVSTACK_PATH: ${{ github.workspace }}/devstack + OS_BRANCH: ${{ matrix.openstack_version }} + + - name: Generate logs on failure + run: ./script/collectlogs + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + + - name: Upload logs artifacts on failure + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: functional-metric-${{ matrix.name }}-${{ github.run_id }} + path: /tmp/devstack-logs/* + + - name: Set job status + run: | + if [[ "$TESTS_SKIPPED" == "true" || "$TESTS_RUN" == "true" ]]; then + echo "Job completed successfully (either ran tests or skipped appropriately)" + exit 0 + else + echo "Job failed - neither tests ran nor were properly skipped" + exit 1 + fi diff --git a/.github/workflows/functional-networking.yaml b/.github/workflows/functional-networking.yaml index a5dc220076..a8fb047b57 100644 --- a/.github/workflows/functional-networking.yaml +++ b/.github/workflows/functional-networking.yaml @@ -1,38 +1,57 @@ name: functional-networking on: + merge_group: pull_request: - paths: - - '**networking**' schedule: - cron: '0 0 */3 * *' jobs: functional-networking: + permissions: + contents: read strategy: fail-fast: false matrix: include: - name: "master" openstack_version: "master" - ubuntu_version: "22.04" - additional_services: "openstack-cli-server" - - name: "caracal" - openstack_version: "stable/2024.1" - ubuntu_version: "22.04" - additional_services: "" - - name: "bobcat" - openstack_version: "stable/2023.2" - ubuntu_version: "22.04" - additional_services: "" - - name: "antelope" - openstack_version: "stable/2023.1" - ubuntu_version: "22.04" - additional_services: "" + ubuntu_version: "24.04" + - name: "gazpacho" + openstack_version: "stable/2026.1" + ubuntu_version: "24.04" + - name: "epoxy" + openstack_version: "stable/2025.1" + ubuntu_version: "24.04" runs-on: ubuntu-${{ matrix.ubuntu_version }} - name: Deploy OpenStack ${{ matrix.name }} with Neutron and run networking acceptance tests + name: Neutron on OpenStack ${{ matrix.name }} steps: - name: Checkout Gophercloud - uses: actions/checkout@v4 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Check changed files + uses: ./.github/actions/file-filter + id: changed-files + with: + patterns: | + openstack/auth_env.go + openstack/client.go + openstack/endpoint.go + openstack/endpoint_location.go + openstack/config/provider_client.go + openstack/utils/choose_version.go + openstack/utils/discovery.go + **networking** + .github/workflows/functional-networking.yaml + + - name: Skip tests for unrelated changed-files + if: ${{ ! fromJSON(steps.changed-files.outputs.matches) }} + run: | + echo "No relevant files changed - skipping tests for ${{ matrix.name }}" + echo "TESTS_SKIPPED=true" >> $GITHUB_ENV + - name: Create additional neutron policies + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} run: | mkdir /tmp/neutron-policies cat << EOF >> /tmp/neutron-policies/port_binding.yaml @@ -40,36 +59,58 @@ jobs: "create_port:binding:profile": "rule:admin_only or rule:service_api" "update_port:binding:profile": "rule:admin_only or rule:service_api" EOF + - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 # v0.19 with: branch: ${{ matrix.openstack_version }} conf_overrides: | + enable_plugin neutron https://opendev.org/openstack/neutron ${{ matrix.openstack_version }} enable_plugin neutron-dynamic-routing https://github.com/openstack/neutron-dynamic-routing ${{ matrix.openstack_version }} enable_plugin neutron-vpnaas https://github.com/openstack/neutron-vpnaas ${{ matrix.openstack_version }} enable_plugin networking-bgpvpn https://github.com/openstack/networking-bgpvpn.git ${{ matrix.openstack_version }} - Q_ML2_PLUGIN_EXT_DRIVERS=qos,port_security,dns_domain_keywords + BGP_SCHEDULER_DRIVER=neutron_dynamic_routing.services.bgp.scheduler.bgp_dragent_scheduler.StaticScheduler [[post-config|\$NEUTRON_CONF]] [oslo_policy] policy_dirs = /tmp/neutron-policies - enabled_services: "neutron-dns,neutron-qos,neutron-segments,neutron-trunk,neutron-uplink-status-propagation,neutron-network-segment-range,neutron-port-forwarding,${{ matrix.additional_services }}" + enabled_services: "neutron-dns,neutron-qos,neutron-segments,neutron-trunk,neutron-port-trusted-vif,neutron-uplink-status-propagation,neutron-network-segment-range,neutron-port-forwarding,openstack-cli-server" + - name: Checkout go - uses: actions/setup-go@v5 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: '^1.22' + go-version-file: 'go.mod' + cache: true + - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-networking + echo "TESTS_RUN=true" >> $GITHUB_ENV env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/networking/..." OS_BRANCH: ${{ matrix.openstack_version }} + - name: Generate logs on failure run: ./script/collectlogs - if: failure() + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + - name: Upload logs artifacts on failure - if: failure() - uses: actions/upload-artifact@v4 + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: functional-networking-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* + + - name: Set job status + run: | + if [[ "$TESTS_SKIPPED" == "true" || "$TESTS_RUN" == "true" ]]; then + echo "Job completed successfully (either ran tests or skipped appropriately)" + exit 0 + else + echo "Job failed - neither tests ran nor were properly skipped" + exit 1 + fi diff --git a/.github/workflows/functional-objectstorage.yaml b/.github/workflows/functional-objectstorage.yaml index 6f12439d72..3adddecf09 100644 --- a/.github/workflows/functional-objectstorage.yaml +++ b/.github/workflows/functional-objectstorage.yaml @@ -1,39 +1,58 @@ name: functional-objectstorage on: + merge_group: pull_request: - paths: - - '**objectstorage**' schedule: - cron: '0 0 */3 * *' jobs: functional-objectstorage: + permissions: + contents: read strategy: fail-fast: false matrix: include: - name: "master" openstack_version: "master" - ubuntu_version: "22.04" - additional_services: "openstack-cli-server" - - name: "caracal" - openstack_version: "stable/2024.1" - ubuntu_version: "22.04" - additional_services: "" - - name: "bobcat" - openstack_version: "stable/2023.2" - ubuntu_version: "22.04" - additional_services: "" - - name: "antelope" - openstack_version: "stable/2023.1" - ubuntu_version: "22.04" - additional_services: "" + ubuntu_version: "24.04" + - name: "gazpacho" + openstack_version: "stable/2026.1" + ubuntu_version: "24.04" + - name: "epoxy" + openstack_version: "stable/2025.1" + ubuntu_version: "24.04" runs-on: ubuntu-${{ matrix.ubuntu_version }} - name: Deploy OpenStack ${{ matrix.name }} with Swift and run objectstorage acceptance tests + name: Swift on OpenStack ${{ matrix.name }} steps: - name: Checkout Gophercloud - uses: actions/checkout@v4 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Check changed files + uses: ./.github/actions/file-filter + id: changed-files + with: + patterns: | + openstack/auth_env.go + openstack/client.go + openstack/endpoint.go + openstack/endpoint_location.go + openstack/config/provider_client.go + openstack/utils/choose_version.go + openstack/utils/discovery.go + **objectstorage** + .github/workflows/functional-objectstorage.yaml + + - name: Skip tests for unrelated changed-files + if: ${{ ! fromJSON(steps.changed-files.outputs.matches) }} + run: | + echo "No relevant files changed - skipping tests for ${{ matrix.name }}" + echo "TESTS_SKIPPED=true" >> $GITHUB_ENV + - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 # v0.19 with: branch: ${{ matrix.openstack_version }} conf_overrides: | @@ -42,23 +61,42 @@ jobs: [[post-config|\$SWIFT_CONFIG_PROXY_SERVER]] [filter:versioned_writes] allow_object_versioning = true - enabled_services: 's-account,s-container,s-object,s-proxy,${{ matrix.additional_services }}' + enabled_services: 's-account,s-container,s-object,s-proxy,openstack-cli-server' + - name: Checkout go - uses: actions/setup-go@v5 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: '^1.22' + go-version-file: 'go.mod' + cache: true + - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-objectstorage + echo "TESTS_RUN=true" >> $GITHUB_ENV env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/objectstorage/..." OS_BRANCH: ${{ matrix.openstack_version }} + - name: Generate logs on failure run: ./script/collectlogs - if: failure() + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + - name: Upload logs artifacts on failure - if: failure() - uses: actions/upload-artifact@v4 + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: functional-objectstorage-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* + + - name: Set job status + run: | + if [[ "$TESTS_SKIPPED" == "true" || "$TESTS_RUN" == "true" ]]; then + echo "Job completed successfully (either ran tests or skipped appropriately)" + exit 0 + else + echo "Job failed - neither tests ran nor were properly skipped" + exit 1 + fi diff --git a/.github/workflows/functional-orchestration.yaml b/.github/workflows/functional-orchestration.yaml index 0f397a7979..dddf874c0b 100644 --- a/.github/workflows/functional-orchestration.yaml +++ b/.github/workflows/functional-orchestration.yaml @@ -1,60 +1,98 @@ name: functional-orchestration on: + merge_group: pull_request: - paths: - - '**orchestration**' schedule: - cron: '0 0 */3 * *' jobs: functional-orchestration: + permissions: + contents: read strategy: fail-fast: false matrix: include: - name: "master" openstack_version: "master" - ubuntu_version: "22.04" - additional_services: "openstack-cli-server" - - name: "caracal" - openstack_version: "stable/2024.1" - ubuntu_version: "22.04" - additional_services: "" - - name: "bobcat" - openstack_version: "stable/2023.2" - ubuntu_version: "22.04" - additional_services: "" - - name: "antelope" - openstack_version: "stable/2023.1" - ubuntu_version: "22.04" - additional_services: "" + ubuntu_version: "24.04" + - name: "gazpacho" + openstack_version: "stable/2026.1" + ubuntu_version: "24.04" + - name: "epoxy" + openstack_version: "stable/2025.1" + ubuntu_version: "24.04" runs-on: ubuntu-${{ matrix.ubuntu_version }} - name: Deploy OpenStack ${{ matrix.name }} with Heat and run orchestration acceptance tests + name: Heat on OpenStack ${{ matrix.name }} steps: - name: Checkout Gophercloud - uses: actions/checkout@v4 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Check changed files + uses: ./.github/actions/file-filter + id: changed-files + with: + patterns: | + openstack/auth_env.go + openstack/client.go + openstack/endpoint.go + openstack/endpoint_location.go + openstack/config/provider_client.go + openstack/utils/choose_version.go + openstack/utils/discovery.go + **orchestration** + .github/workflows/functional-orchestration.yaml + + - name: Skip tests for unrelated changed-files + if: ${{ ! fromJSON(steps.changed-files.outputs.matches) }} + run: | + echo "No relevant files changed - skipping tests for ${{ matrix.name }}" + echo "TESTS_SKIPPED=true" >> $GITHUB_ENV + - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 # v0.19 with: branch: ${{ matrix.openstack_version }} conf_overrides: | enable_plugin heat https://github.com/openstack/heat ${{ matrix.openstack_version }} - enabled_services: 'h-eng,h-api,h-api-cfn,h-api-cw,${{ matrix.additional_services }}' + enabled_services: 'h-eng,h-api,h-api-cfn,h-api-cw,openstack-cli-server' + - name: Checkout go - uses: actions/setup-go@v5 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: '^1.22' + go-version-file: 'go.mod' + cache: true + - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-orchestration + echo "TESTS_RUN=true" >> $GITHUB_ENV env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/orchestration/..." OS_BRANCH: ${{ matrix.openstack_version }} + - name: Generate logs on failure run: ./script/collectlogs - if: failure() + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + - name: Upload logs artifacts on failure - if: failure() - uses: actions/upload-artifact@v4 + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: functional-orchestration-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* + + - name: Set job status + run: | + if [[ "$TESTS_SKIPPED" == "true" || "$TESTS_RUN" == "true" ]]; then + echo "Job completed successfully (either ran tests or skipped appropriately)" + exit 0 + else + echo "Job failed - neither tests ran nor were properly skipped" + exit 1 + fi diff --git a/.github/workflows/functional-placement.yaml b/.github/workflows/functional-placement.yaml index 0a4b1f2979..fd0082924a 100644 --- a/.github/workflows/functional-placement.yaml +++ b/.github/workflows/functional-placement.yaml @@ -1,58 +1,96 @@ name: functional-placement on: + merge_group: pull_request: - paths: - - '**placement**' schedule: - cron: '0 0 */3 * *' jobs: functional-placement: + permissions: + contents: read strategy: fail-fast: false matrix: include: - name: "master" openstack_version: "master" - ubuntu_version: "22.04" - additional_services: "openstack-cli-server" - - name: "caracal" - openstack_version: "stable/2024.1" - ubuntu_version: "22.04" - additional_services: "" - - name: "bobcat" - openstack_version: "stable/2023.2" - ubuntu_version: "22.04" - additional_services: "" - - name: "antelope" - openstack_version: "stable/2023.1" - ubuntu_version: "22.04" - additional_services: "" + ubuntu_version: "24.04" + - name: "gazpacho" + openstack_version: "stable/2026.1" + ubuntu_version: "24.04" + - name: "epoxy" + openstack_version: "stable/2025.1" + ubuntu_version: "24.04" runs-on: ubuntu-${{ matrix.ubuntu_version }} - name: Deploy OpenStack ${{ matrix.name }} with Placement and run placement acceptance tests + name: Placement on OpenStack ${{ matrix.name }} steps: - name: Checkout Gophercloud - uses: actions/checkout@v4 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Check changed files + uses: ./.github/actions/file-filter + id: changed-files + with: + patterns: | + openstack/auth_env.go + openstack/client.go + openstack/endpoint.go + openstack/endpoint_location.go + openstack/config/provider_client.go + openstack/utils/choose_version.go + openstack/utils/discovery.go + **placement** + .github/workflows/functional-placement.yaml + + - name: Skip tests for unrelated changed-files + if: ${{ ! fromJSON(steps.changed-files.outputs.matches) }} + run: | + echo "No relevant files changed - skipping tests for ${{ matrix.name }}" + echo "TESTS_SKIPPED=true" >> $GITHUB_ENV + - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 # v0.19 with: branch: ${{ matrix.openstack_version }} - enabled_services: "${{ matrix.additional_services }}" + enabled_services: "openstack-cli-server" + - name: Checkout go - uses: actions/setup-go@v5 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: '^1.22' + go-version-file: 'go.mod' + cache: true + - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-placement + echo "TESTS_RUN=true" >> $GITHUB_ENV env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/placement/..." OS_BRANCH: ${{ matrix.openstack_version }} + - name: Generate logs on failure run: ./script/collectlogs - if: failure() + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + - name: Upload logs artifacts on failure - if: failure() - uses: actions/upload-artifact@v4 + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: functional-placement-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* + + - name: Set job status + run: | + if [[ "$TESTS_SKIPPED" == "true" || "$TESTS_RUN" == "true" ]]; then + echo "Job completed successfully (either ran tests or skipped appropriately)" + exit 0 + else + echo "Job failed - neither tests ran nor were properly skipped" + exit 1 + fi diff --git a/.github/workflows/functional-sharedfilesystems.yaml b/.github/workflows/functional-sharedfilesystems.yaml index 92182f5219..1d6051df05 100644 --- a/.github/workflows/functional-sharedfilesystems.yaml +++ b/.github/workflows/functional-sharedfilesystems.yaml @@ -1,39 +1,58 @@ name: functional-sharedfilesystems on: + merge_group: pull_request: - paths: - - '**sharedfilesystems**' schedule: - cron: '0 0 */3 * *' jobs: functional-sharedfilesystems: + permissions: + contents: read strategy: fail-fast: false matrix: include: - name: "master" openstack_version: "master" - ubuntu_version: "22.04" - additional_services: "openstack-cli-server" - - name: "caracal" - openstack_version: "stable/2024.1" - ubuntu_version: "22.04" - additional_services: "" - - name: "bobcat" - openstack_version: "stable/2023.2" - ubuntu_version: "22.04" - additional_services: "" - - name: "antelope" - openstack_version: "stable/2023.1" - ubuntu_version: "22.04" - additional_services: "" + ubuntu_version: "24.04" + - name: "gazpacho" + openstack_version: "stable/2026.1" + ubuntu_version: "24.04" + - name: "epoxy" + openstack_version: "stable/2025.1" + ubuntu_version: "24.04" runs-on: ubuntu-${{ matrix.ubuntu_version }} - name: Deploy OpenStack ${{ matrix.name }} with Manila and run sharedfilesystems acceptance tests + name: Manila on OpenStack ${{ matrix.name }} steps: - name: Checkout Gophercloud - uses: actions/checkout@v4 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Check changed files + uses: ./.github/actions/file-filter + id: changed-files + with: + patterns: | + openstack/auth_env.go + openstack/client.go + openstack/endpoint.go + openstack/endpoint_location.go + openstack/config/provider_client.go + openstack/utils/choose_version.go + openstack/utils/discovery.go + **sharedfilesystems** + .github/workflows/functional-sharedfilesystems.yaml + + - name: Skip tests for unrelated changed-files + if: ${{ ! fromJSON(steps.changed-files.outputs.matches) }} + run: | + echo "No relevant files changed - skipping tests for ${{ matrix.name }}" + echo "TESTS_SKIPPED=true" >> $GITHUB_ENV + - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 # v0.19 with: branch: ${{ matrix.openstack_version }} conf_overrides: | @@ -52,23 +71,44 @@ jobs: MANILA_DEFAULT_SHARE_TYPE_EXTRA_SPECS='snapshot_support=True create_share_from_snapshot_support=True revert_to_snapshot_support=True mount_snapshot_support=True' MANILA_CONFIGURE_DEFAULT_TYPES=True MANILA_INSTALL_TEMPEST_PLUGIN_SYSTEMWIDE=false - enabled_services: "${{ matrix.additional_services }}" + + ${{ matrix.devstack_conf_overrides }} + enabled_services: "openstack-cli-server" + - name: Checkout go - uses: actions/setup-go@v5 + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: '^1.22' + go-version-file: 'go.mod' + cache: true + - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-sharedfilesystems + echo "TESTS_RUN=true" >> $GITHUB_ENV env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/sharedfilesystems/..." OS_BRANCH: ${{ matrix.openstack_version }} + - name: Generate logs on failure run: ./script/collectlogs - if: failure() + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + - name: Upload logs artifacts on failure - if: failure() - uses: actions/upload-artifact@v4 + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: functional-sharedfilesystems-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* + + - name: Set job status + run: | + if [[ "$TESTS_SKIPPED" == "true" || "$TESTS_RUN" == "true" ]]; then + echo "Job completed successfully (either ran tests or skipped appropriately)" + exit 0 + else + echo "Job failed - neither tests ran nor were properly skipped" + exit 1 + fi diff --git a/.github/workflows/functional-workflow.yaml b/.github/workflows/functional-workflow.yaml new file mode 100644 index 0000000000..267032b010 --- /dev/null +++ b/.github/workflows/functional-workflow.yaml @@ -0,0 +1,101 @@ +name: functional-workflow +on: + merge_group: + pull_request: + schedule: + - cron: '0 0 */3 * *' +jobs: + functional-workflow: + permissions: + contents: read + strategy: + fail-fast: false + matrix: + include: + - name: "master" + openstack_version: "master" + ubuntu_version: "24.04" + mistral_plugin_version: "master" + - name: "gazpacho" + openstack_version: "stable/2026.1" + ubuntu_version: "24.04" + mistral_plugin_version: "stable/2026.1" + - name: "epoxy" + openstack_version: "stable/2025.1" + ubuntu_version: "24.04" + mistral_plugin_version: "stable/2025.1" + runs-on: ubuntu-${{ matrix.ubuntu_version }} + name: Mistral on Deploy OpenStack ${{ matrix.name }} + steps: + - name: Checkout Gophercloud + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Check changed files + uses: ./.github/actions/file-filter + id: changed-files + with: + patterns: | + openstack/auth_env.go + openstack/client.go + openstack/endpoint.go + openstack/endpoint_location.go + openstack/config/provider_client.go + openstack/utils/choose_version.go + openstack/utils/discovery.go + **workflow** + .github/workflows/functional-workflow.yaml + + - name: Skip tests for unrelated changed-files + if: ${{ ! fromJSON(steps.changed-files.outputs.matches) }} + run: | + echo "No relevant files changed - skipping tests for ${{ matrix.name }}" + echo "TESTS_SKIPPED=true" >> $GITHUB_ENV + + - name: Deploy devstack + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 # v0.19 + with: + branch: ${{ matrix.openstack_version }} + conf_overrides: | + enable_plugin mistral https://github.com/openstack/mistral ${{ matrix.mistral_plugin_version }} + enabled_services: "mistral,mistral-api,mistral-engine,mistral-executor,mistral-event-engine,openstack-cli-server" + + - name: Checkout go + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version-file: 'go.mod' + cache: true + + - name: Run Gophercloud acceptance tests + if: ${{ fromJSON(steps.changed-files.outputs.matches) }} + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-workflow + echo "TESTS_RUN=true" >> $GITHUB_ENV + env: + DEVSTACK_PATH: ${{ github.workspace }}/devstack + OS_BRANCH: ${{ matrix.openstack_version }} + + - name: Generate logs on failure + run: ./script/collectlogs + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + + - name: Upload logs artifacts on failure + if: ${{ failure() && fromJSON(steps.changed-files.outputs.matches) }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: functional-workflow-${{ matrix.name }}-${{ github.run_id }} + path: /tmp/devstack-logs/* + + - name: Set job status + run: | + if [[ "$TESTS_SKIPPED" == "true" || "$TESTS_RUN" == "true" ]]; then + echo "Job completed successfully (either ran tests or skipped appropriately)" + exit 0 + else + echo "Job failed - neither tests ran nor were properly skipped" + exit 1 + fi diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml deleted file mode 100644 index 7bd51ee5e8..0000000000 --- a/.github/workflows/greetings.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Greetings - -on: [pull_request_target, issues] - -jobs: - greeting: - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - steps: - - uses: actions/first-interaction@v1 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - issue-message: | - Thank you for reporting your first issue! Be sure that we will be looking at it, but keep in mind - this sometimes takes a while. - Please let the maintainers know if your issue has not got enough attention after a few days. - If any doubt, please consult our issue [tutorial](https://github.com/gophercloud/gophercloud/blob/master/docs/contributor-tutorial/step-02-issues.md). - pr-message: | - Thank you for submitting your first PR! Be sure that we will be looking at it but keep in mind - this sometimes takes a while. - Please let the maintainers know if your PR has not got enough attention after a few days. - If any doubt, please consult our PR [tutorial](https://github.com/gophercloud/gophercloud/blob/master/docs/contributor-tutorial/step-05-pull-requests.md). diff --git a/.github/workflows/label-issue.yaml b/.github/workflows/label-issue.yaml new file mode 100644 index 0000000000..cb5e84e897 --- /dev/null +++ b/.github/workflows/label-issue.yaml @@ -0,0 +1,20 @@ +name: Label issue +on: + issue_comment: + types: + - created + +jobs: + clear_needinfo: + name: Clear needinfo + if: ${{ github.event.issue.user.login == github.event.comment.user.login }} + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - run: gh issue edit "$NUMBER" --remove-label "needinfo" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.issue.number }} diff --git a/.github/workflows/label-pr.yaml b/.github/workflows/label-pr.yaml new file mode 100644 index 0000000000..d1dee9da7b --- /dev/null +++ b/.github/workflows/label-pr.yaml @@ -0,0 +1,112 @@ +name: Label PR +on: + # Safe: the edits job does not checkout or execute any PR code. + # It only runs the pinned actions/labeler action which reads event + # metadata and the base branch .github/labeler.yml configuration. + pull_request_target: # zizmor: ignore[dangerous-triggers] + types: + - opened + - synchronize + - reopened + # Safe: the semver-label job never checks out or executes PR code. + # It only reads a data artifact produced by the unprivileged + # "Semver analysis" workflow and uses the GitHub API to apply labels. + workflow_run: # zizmor: ignore[dangerous-triggers] + workflows: ["Semver analysis"] + types: + - completed + +permissions: {} + +jobs: + semver-label: + if: github.event_name == 'workflow_run' + runs-on: ubuntu-latest + permissions: + actions: read + issues: write + pull-requests: write + steps: + - name: Download semver results + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: semver-results + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Read semver results + id: semver + run: | + echo "type=$(cat semver-type)" >> "$GITHUB_OUTPUT" + echo "pr-number=$(cat pr-number)" >> "$GITHUB_OUTPUT" + echo "base-ref=$(cat base-ref)" >> "$GITHUB_OUTPUT" + + - name: Report failure + if: github.event.workflow_run.conclusion == 'failure' + run: | + gh pr edit "$NUMBER" --remove-label "semver:major,semver:minor,semver:patch,backport-v2,backport-v1" + gh issue comment "$NUMBER" --body "$BODY" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ steps.semver.outputs.pr-number }} + BODY: > + Failed to assess the semver bump. See [logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}) for details. + + - name: Add label semver:patch + if: github.event.workflow_run.conclusion == 'success' && steps.semver.outputs.type == 'patch' + run: | + LABELS="semver:patch" + REMOVE_LABELS="semver:major,semver:minor" + + # Only add backport-v2 if not already targeting v2 or lower + if [[ "$BASE_REF" != "v2" && "$BASE_REF" != "v1" ]]; then + LABELS="$LABELS,backport-v2" + else + REMOVE_LABELS="$REMOVE_LABELS,backport-v2" + fi + + gh pr edit "$NUMBER" --add-label "$LABELS" --remove-label "$REMOVE_LABELS" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ steps.semver.outputs.pr-number }} + BASE_REF: ${{ steps.semver.outputs.base-ref }} + + - name: Add label semver:minor + if: github.event.workflow_run.conclusion == 'success' && steps.semver.outputs.type == 'minor' + run: | + LABELS="semver:minor" + REMOVE_LABELS="semver:major,semver:patch" + + # Only add backport-v2 if not already targeting v2 or lower + if [[ "$BASE_REF" != "v2" && "$BASE_REF" != "v1" ]]; then + LABELS="$LABELS,backport-v2" + REMOVE_LABELS="$REMOVE_LABELS,backport-v1" + else + REMOVE_LABELS="$REMOVE_LABELS,backport-v2,backport-v1" + fi + + gh pr edit "$NUMBER" --add-label "$LABELS" --remove-label "$REMOVE_LABELS" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ steps.semver.outputs.pr-number }} + BASE_REF: ${{ steps.semver.outputs.base-ref }} + + - name: Add label semver:major + if: github.event.workflow_run.conclusion == 'success' && steps.semver.outputs.type == 'major' + run: gh pr edit "$NUMBER" --add-label "semver:major" --remove-label "semver:minor,semver:patch,backport-v2,backport-v1" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ steps.semver.outputs.pr-number }} + + edits: + if: github.event_name == 'pull_request_target' + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0 diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 665b887187..0fe6b8cd48 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -1,27 +1,25 @@ -on: [push, pull_request] name: Linters +on: + - merge_group + - push + - pull_request permissions: contents: read - jobs: test: runs-on: ubuntu-latest - steps: - - uses: actions/setup-go@v5 + - name: Checkout Gophercloud + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: - go-version: '1' - - - uses: actions/checkout@v4 - - - name: Run go fmt - run: | - ./script/format - - - name: Run go vet + persist-credentials: false + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version-file: 'go.mod' + cache: true + - name: Run linters run: | - ./script/vet - + make lint - name: Ensure go.mod is up-to-date run: | - if [ $(go mod tidy && git diff | wc -l) -gt 0 ]; then git diff && exit 1; fi + if [ $(go mod tidy -diff | wc -l) -gt 0 ]; then git diff && exit 1; fi diff --git a/.github/workflows/semver-auto.yaml b/.github/workflows/semver-auto.yaml deleted file mode 100644 index c042be134a..0000000000 --- a/.github/workflows/semver-auto.yaml +++ /dev/null @@ -1,76 +0,0 @@ -name: Add PR semver labels -on: - pull_request_target: - types: [opened, synchronize, reopened] -jobs: - go-apidiff: - runs-on: ubuntu-latest - steps: - - name: Remove the semver labels - uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0 - with: - labels: | - semver:patch - semver:minor - semver:major - semver:unknown - github_token: ${{ secrets.GITHUB_TOKEN }} - - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.event.pull_request.head.sha }} - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Rebase the PR against origin/github.base_ref to ensure actual API compatibility - run: | - git config --global user.email "localrebase@gophercloud.io" - git config --global user.name "Local rebase" - git rebase -i origin/${{ github.base_ref }} - env: - GIT_SEQUENCE_EDITOR: '/usr/bin/true' - - - name: Add semver:unknown label - if: failure() - uses: actions-ecosystem/action-add-labels@18f1af5e3544586314bbe15c0273249c770b2daf - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - labels: semver:unknown - - - uses: actions/setup-go@v5 - with: - go-version: '1' - - - name: Checking Go API Compatibility - id: go-apidiff - # if semver=major, this will return RC=1, so let's ignore the failure so label - # can be set later. We check for actual errors in the next step. - continue-on-error: true - uses: joelanford/go-apidiff@002aa613b261e8d1547b516fb71793280f05bb78 - - # go-apidiff returns RC=1 when semver=major, which makes the workflow to return - # a failure. Instead let's just return a failure if go-apidiff failed to run. - - name: Return an error if Go API Compatibility couldn't be verified - if: steps.go-apidiff.outcome != 'success' && steps.go-apidiff.outputs.semver-type != 'major' - run: exit 1 - - - name: Add semver:patch label - if: steps.go-apidiff.outputs.semver-type == 'patch' - uses: actions-ecosystem/action-add-labels@18f1af5e3544586314bbe15c0273249c770b2daf - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - labels: semver:patch - - - name: Add semver:minor label - if: steps.go-apidiff.outputs.semver-type == 'minor' - uses: actions-ecosystem/action-add-labels@18f1af5e3544586314bbe15c0273249c770b2daf - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - labels: semver:minor - - - name: Add semver:major label - if: steps.go-apidiff.outputs.semver-type == 'major' - uses: actions-ecosystem/action-add-labels@18f1af5e3544586314bbe15c0273249c770b2daf - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - labels: semver:major diff --git a/.github/workflows/semver.yaml b/.github/workflows/semver.yaml new file mode 100644 index 0000000000..2ab445a758 --- /dev/null +++ b/.github/workflows/semver.yaml @@ -0,0 +1,66 @@ +name: Semver analysis +on: + pull_request: + types: + - opened + - synchronize + - reopened + +permissions: + contents: read + +jobs: + analyze: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + + - name: Rebase the PR against base ref to ensure actual API compatibility + run: | + git config --global user.email "localrebase@gophercloud.io" + git config --global user.name "Local rebase" + git rebase -i origin/$BASE_REF + env: + GIT_SEQUENCE_EDITOR: '/usr/bin/true' + BASE_REF: ${{ github.base_ref }} + + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version-file: 'go.mod' + cache: true + + - name: Checking Go API Compatibility + id: go-apidiff + # if semver=major, this will return RC=1, so let's ignore the failure so label + # can be set later. We check for actual errors in the next step. + continue-on-error: true + uses: joelanford/go-apidiff@60c4206be8f84348ebda2a3e0c3ac9cb54b8f685 # v0.8.3 + + # go-apidiff returns RC=1 when semver=major, which makes the workflow to return + # a failure. Instead let's just return a failure if go-apidiff failed to run. + - name: Return an error if Go API Compatibility couldn't be verified + if: steps.go-apidiff.outcome != 'success' && steps.go-apidiff.outputs.semver-type != 'major' + run: exit 1 + + - name: Save semver result + if: always() + run: | + mkdir -p semver-results + echo "$SEMVER_TYPE" > semver-results/semver-type + echo "$PR_NUMBER" > semver-results/pr-number + echo "$BASE_REF" > semver-results/base-ref + env: + SEMVER_TYPE: ${{ steps.go-apidiff.outputs.semver-type }} + PR_NUMBER: ${{ github.event.pull_request.number }} + BASE_REF: ${{ github.base_ref }} + + - name: Upload semver results + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: semver-results + path: semver-results/ diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yaml similarity index 58% rename from .github/workflows/unit.yml rename to .github/workflows/unit.yaml index 11fddb5f68..50e9f6c935 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yaml @@ -1,8 +1,10 @@ -on: [push, pull_request] name: Unit Testing +on: + - merge_group + - push + - pull_request permissions: contents: read - jobs: test: permissions: @@ -11,39 +13,31 @@ jobs: runs-on: ubuntu-latest strategy: fail-fast: false - matrix: - go-version: - - "1.22.3" - - "1" - steps: - - name: Setup Go ${{ matrix.go-version }} - uses: actions/setup-go@v5 + - name: Checkout Gophercloud + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: - go-version: ${{ matrix.go-version }} - - - uses: actions/checkout@v4 - + persist-credentials: false + - name: Setup Go + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version-file: 'go.mod' + cache: true - name: Setup environment run: | # Changing into a different directory to avoid polluting go.sum with "go get" cd "$(mktemp -d)" go mod init unit_tests - - go install github.com/wadey/gocovmerge@master - + go install github.com/alexfalkowski/gocovmerge@v1.4.0 - name: Run unit tests run: | - ./script/coverage - ./script/unittest - + make unit + make coverage - name: Check coverage - uses: coverallsapp/github-action@v2 + uses: coverallsapp/github-action@5cbfd81b66ca5d10c19b062c04de0199c215fb6e # v2.3.7 with: file: cover.out - flag-name: Go-${{ matrix.go-version }} parallel: true - finish: permissions: checks: write # for coverallsapp/github-action to create a new check based on the results @@ -52,7 +46,6 @@ jobs: runs-on: ubuntu-latest steps: - name: Store coverage results - uses: coverallsapp/github-action@v2 + uses: coverallsapp/github-action@5cbfd81b66ca5d10c19b062c04de0199c215fb6e # v2.3.7 with: parallel-finished: true - carryforward: Go-${{ join(matrix.go-version.*, '-') }} diff --git a/.github/workflows/zizmor.yaml b/.github/workflows/zizmor.yaml new file mode 100644 index 0000000000..ff0814e629 --- /dev/null +++ b/.github/workflows/zizmor.yaml @@ -0,0 +1,28 @@ +name: zizmor + +on: + push: + branches: + - master + paths: + - '.github/**' + pull_request: + paths: + - '.github/**' + +permissions: {} + +jobs: + zizmor: + runs-on: ubuntu-latest + permissions: + security-events: write + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Run zizmor + uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 diff --git a/.golangci.yaml b/.golangci.yaml index 828a099a40..1a197efa58 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,19 +1,34 @@ ---- +version: "2" linters: - disable-all: true + default: none enable: - errcheck - - gofmt - - goimports - govet - staticcheck - unparam - unused - -issues: - exclude: - - SA1006 # printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck) - exclude-rules: - - linters: - - staticcheck - text: 'SA1019: (x509.EncryptPEMBlock|strings.Title)' + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + - linters: + - staticcheck + text: "SA1019: (x509.EncryptPEMBlock|strings.Title|openstack.V2EndpointURL|openstack.V3EndpointURL)" + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..1b5a20580b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,215 @@ +# AGENTS.md + +Quick reference guide for AI coding agents working in the Gophercloud repository. + +**Project:** Gophercloud - Go SDK for OpenStack services +**Module:** `github.com/gophercloud/gophercloud/v2` +**Language:** Go (see version in [go.mod](go.mod)) +**Stable Branch:** v2 (main development on `main`) + +## Build, Test & Lint Commands + +### Running Tests + +**Unit tests (default):** +```bash +make unit +``` + +**Unit tests with verbose output:** +```bash +go test -v ./... +``` + +**Run single test by name:** +```bash +cd openstack/compute/v2/servers +go test -run TestCreateServer ./... +``` + +**Coverage:** +```bash +make coverage +``` + +**Acceptance tests (requires live OpenStack - may incur charges):** +```bash +make acceptance # All services +make acceptance-compute # Specific service +``` + +**Run single acceptance test:** +```bash +cd internal/acceptance/openstack/compute/v2 +go test -timeout 60m -tags "acceptance" -run TestServersList +``` + +### Linting & Formatting + +```bash +make lint # Run golangci-lint in container (Docker/Podman) +make format # Run gofmt with simplify flag +``` + +**Note:** If lint fails with SELinux errors, run: +```bash +chcon -Rt svirt_sandbox_file_t . +chcon -Rt svirt_sandbox_file_t ~/.cache/golangci-lint +``` + +## Code Style Guidelines + +### Import Organization + +Group imports in this order (separated by blank lines): +1. Standard library (alphabetically) +2. External dependencies (alphabetically) +3. Gophercloud internal packages (alphabetically) + +Example: +```go +import ( + "context" + "encoding/json" + "fmt" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/pagination" +) +``` + +### File Structure + +Standard package structure under `openstack////`: +- **`requests.go`** - HTTP request functions and OptsBuilder types +- **`results.go`** - Response structs and extraction methods +- **`urls.go`** - Endpoint URL construction helpers +- **`microversions.go`** - Microversion-specific types (when needed) +- **`testing/`** - Unit tests with HTTP mocking + +### Naming Conventions + +**Result receivers and variables:** +- Result method receiver: `r` +- Unmarshalled variable: `s` +- Request function return value: `r` + +**OptsBuilder pattern:** +- Interface name: `OptsBuilder` (e.g., `CreateOptsBuilder`, `ListOptsBuilder`) +- Method for request body: `ToMap` (e.g., `ToServerCreateMap`) +- Method for query string: `ToQuery` (e.g., `ToServerListQuery`) + +Example: +```go +type CreateOptsBuilder interface { + ToServerCreateMap() (map[string]interface{}, error) +} + +type CreateOpts struct { + Name string `json:"name"` +} + +func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "server") +} +``` + +### Types & Pointers + +- **New response fields (microversions):** Use pointer types to allow nil-checking +- **Optional request fields:** Always use `omitempty` JSON tag +- **Required fields:** No `omitempty` tag + +### Error Handling + +- Use `gophercloud.Result` and `gophercloud.ErrResult` types +- Extract errors with `.ExtractErr()` method +- Return errors directly, don't wrap unless adding context + +### Documentation + +- **All struct fields** must have GoDoc comments +- **Microversion-dependent fields** must document required version in GoDoc +- **Package documentation** goes in `doc.go` +- Follow existing comment style in similar packages + +Example: +```go +// This requires the client to be set to microversion 2.52 or later. +// Tags is the list of server tags. +Tags []string `json:"tags,omitempty"` +``` + +### Testing Requirements + +**Unit tests (in `testing/` subdirectory):** +- Use `testhelper` package to mock HTTP +- `fakeServer := th.SetupHTTP()` / `defer fakeServer.Teardown()` for setup/teardown +- `fakeServer.Mux.HandleFunc()` to register mock endpoints +- Test ALL options (every field in request/response structs) +- Use assertion helpers from `testhelper/convenience.go` (value assertions) and `testhelper/http_responses.go` (HTTP request assertions) +- `Assert*` variants are fatal (`t.Fatalf`), `Check*` variants are non-fatal (`t.Errorf`) +- Assertion argument order is **expected first, actual second**: `th.AssertEquals(t, "expected_value", actual.Field)` + +**Acceptance tests:** +- Located in `internal/acceptance/openstack//` +- Test against real OpenStack APIs +- Cover all operation variants + +## Microversions + +Set microversion on ServiceClient: +```go +client.Microversion = "2.52" +``` + +**Implementation rules:** +- **New request fields:** Must use `omitempty` + document microversion +- **New response fields:** Add as pointer types +- **Changed response types:** Create new structs in `microversions.go` + +See `docs/MICROVERSIONS.md` for details. + +## Pull Request Requirements + +**Before opening PR:** +1. **GitHub issue must exist** with core contributor approval +2. **PR description must include:** + - `For #` reference + - Link(s) to OpenStack source code (non-master branch) proving validity +3. **Keep PRs focused:** Group related operations together; avoid mixing unrelated changes +4. **Tests required:** Unit tests AND acceptance tests covering all options +5. **Work-in-progress:** Prefix title with `[wip]` until ready +6. **Dependencies:** Prefix with `[Pending #PRNUM]` if depends on another PR + +**During review:** +- Do NOT squash commits (only append) +- Follow existing patterns in codebase +- Address all reviewer feedback + +## Common Patterns + +**Context usage:** +Always pass `context.Context` to API operations: +```go +servers.List(client, opts).EachPage(ctx, func(ctx context.Context, page pagination.Page) (bool, error) { + // ... +}) +``` + +**Pagination:** +```go +pager := servers.List(client, servers.ListOpts{}) +err := pager.EachPage(ctx, func(ctx context.Context, page pagination.Page) (bool, error) { + servers, err := servers.ExtractServers(page) + // process... + return true, nil +}) +``` + +## Key Reminders + +- Module path: `github.com/gophercloud/gophercloud/v2` (note the `/v2`) +- Gophercloud does NOT validate microversion compatibility +- PRs target `main` branch, not `v2` +- Documentation auto-generated from GoDoc comments diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d7dfbc469..bae5109cfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## v2.1.0 (2024-07-24) + +* [GH-3078](https://github.com/gophercloud/gophercloud/pull/3078) [networking]: add BGP VPNs support +* [GH-3086](https://github.com/gophercloud/gophercloud/pull/3086) build(deps): bump golang.org/x/crypto from 0.24.0 to 0.25.0 +* [GH-3090](https://github.com/gophercloud/gophercloud/pull/3090) Adding support for field dns_publish_fixed_ip in a subnet +* [GH-3092](https://github.com/gophercloud/gophercloud/pull/3092) [neutron]: introduce Stateful argument for the security groups +* [GH-3094](https://github.com/gophercloud/gophercloud/pull/3094) [neutron]: introduce Description argument for the portforwarding +* [GH-3106](https://github.com/gophercloud/gophercloud/pull/3106) clouds: Parse trust_id from clouds.yaml +* [GH-3131](https://github.com/gophercloud/gophercloud/pull/3131) Align ServiceFail provisioning state value with Ironic +* [GH-3136](https://github.com/gophercloud/gophercloud/pull/3136) Added node.Retired + ## v2.0.0 (2024-05-27) MAIN BREAKING CHANGES: diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000000..47dc3e3d86 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/Makefile b/Makefile index 128beec005..cffb8e248b 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,10 @@ undefine GOFLAGS -GOLANGCI_LINT_VERSION?=v1.57.1 +GOLANGCI_LINT_VERSION?=v2.12.1 +GOTESTSUM_VERSION?=v1.13.0 +GO_TEST?=go run gotest.tools/gotestsum@$(GOTESTSUM_VERSION) --format testname -- +GOTESTFLAGS?= +TIMEOUT := "60m" ifeq ($(shell command -v podman 2> /dev/null),) RUNNER=docker @@ -8,102 +12,121 @@ else RUNNER=podman endif -# if the golangci-lint steps fails with the following error message: +# if the golangci-lint steps fails with one of the following error messages: # # directory prefix . does not contain main module or its selected dependencies # +# failed to initialize build cache at /root/.cache/golangci-lint: mkdir /root/.cache/golangci-lint: permission denied +# # you probably have to fix the SELinux security context for root directory plus your cache # # chcon -Rt svirt_sandbox_file_t . # chcon -Rt svirt_sandbox_file_t ~/.cache/golangci-lint lint: + mkdir -p ~/.cache/golangci-lint/$(GOLANGCI_LINT_VERSION) $(RUNNER) run -t --rm \ -v $(shell pwd):/app \ -v ~/.cache/golangci-lint/$(GOLANGCI_LINT_VERSION):/root/.cache \ -w /app \ -e GOFLAGS="-tags=acceptance" \ - golangci/golangci-lint:$(GOLANGCI_LINT_VERSION) golangci-lint run + golangci/golangci-lint:$(GOLANGCI_LINT_VERSION) golangci-lint run -v --max-same-issues 50 .PHONY: lint +format: + gofmt -w -s $(shell pwd) +.PHONY: format + unit: - go test ./... + $(GO_TEST) -shuffle on ./... .PHONY: unit coverage: - go test -covermode count -coverprofile cover.out -coverpkg=./... ./... + $(GO_TEST) -shuffle on -covermode count -coverprofile cover.out -coverpkg=./... ./... .PHONY: coverage -acceptance: acceptance-baremetal acceptance-blockstorage acceptance-compute acceptance-container acceptance-containerinfra acceptance-db acceptance-dns acceptance-identity acceptance-imageservice acceptance-keymanager acceptance-loadbalancer acceptance-messaging acceptance-networking acceptance-objectstorage acceptance-orchestration acceptance-placement acceptance-sharedfilesystems acceptance-workflow +acceptance: acceptance-basic acceptance-baremetal acceptance-blockstorage acceptance-compute acceptance-container acceptance-containerinfra acceptance-db acceptance-dns acceptance-identity acceptance-image acceptance-keymanager acceptance-loadbalancer acceptance-messaging acceptance-metric acceptance-networking acceptance-objectstorage acceptance-orchestration acceptance-placement acceptance-sharedfilesystems acceptance-workflow .PHONY: acceptance +acceptance-basic: + $(GO_TEST) -timeout $(TIMEOUT) -tags "fixtures acceptance" ./internal/acceptance/openstack +.PHONY: acceptance-basic + acceptance-baremetal: - go test -tags "fixtures acceptance" ./internal/acceptance/openstack/baremetal/... + $(GO_TEST) -timeout $(TIMEOUT) -tags "fixtures acceptance" ./internal/acceptance/openstack/baremetal/... .PHONY: acceptance-baremetal acceptance-blockstorage: - go test -tags "fixtures acceptance" ./internal/acceptance/openstack/blockstorage/... + $(GO_TEST) -timeout $(TIMEOUT) -tags "fixtures acceptance" ./internal/acceptance/openstack/blockstorage/... .PHONY: acceptance-blockstorage acceptance-compute: - go test -tags "fixtures acceptance" ./internal/acceptance/openstack/compute/... + $(GO_TEST) -timeout $(TIMEOUT) -tags "fixtures acceptance" ./internal/acceptance/openstack/compute/... .PHONY: acceptance-compute acceptance-container: - go test -tags "fixtures acceptance" ./internal/acceptance/openstack/container/... + $(GO_TEST) -timeout $(TIMEOUT) -tags "fixtures acceptance" ./internal/acceptance/openstack/container/... .PHONY: acceptance-container acceptance-containerinfra: - go test -tags "fixtures acceptance" ./internal/acceptance/openstack/containerinfra/... + $(GO_TEST) -timeout $(TIMEOUT) -tags "fixtures acceptance" ./internal/acceptance/openstack/containerinfra/... .PHONY: acceptance-containerinfra acceptance-db: - go test -tags "fixtures acceptance" ./internal/acceptance/openstack/db/... + $(GO_TEST) -timeout $(TIMEOUT) $(GOTESTFLAGS) -tags "fixtures acceptance" ./internal/acceptance/openstack/db/... .PHONY: acceptance-db acceptance-dns: - go test -tags "fixtures acceptance" ./internal/acceptance/openstack/dns/... + $(GO_TEST) -timeout $(TIMEOUT) -tags "fixtures acceptance" ./internal/acceptance/openstack/dns/... .PHONY: acceptance-dns +acceptance-fwaas: + $(GO_TEST) -timeout $(TIMEOUT) -tags "fixtures acceptance" ./internal/acceptance/openstack/networking/v2/extensions/fwaas_v2/... +.PHONY: acceptance-fwaas + acceptance-identity: - go test -tags "fixtures acceptance" ./internal/acceptance/openstack/identity/... + $(GO_TEST) -timeout $(TIMEOUT) -tags "fixtures acceptance" ./internal/acceptance/openstack/identity/... .PHONY: acceptance-identity acceptance-image: - go test -tags "fixtures acceptance" ./internal/acceptance/openstack/imageservice/... + $(GO_TEST) -timeout $(TIMEOUT) -tags "fixtures acceptance" ./internal/acceptance/openstack/image/... .PHONY: acceptance-image acceptance-keymanager: - go test -tags "fixtures acceptance" ./internal/acceptance/openstack/keymanager/... + $(GO_TEST) -timeout $(TIMEOUT) -tags "fixtures acceptance" ./internal/acceptance/openstack/keymanager/... .PHONY: acceptance-keymanager acceptance-loadbalancer: - go test -tags "fixtures acceptance" ./internal/acceptance/openstack/loadbalancer/... + $(GO_TEST) -timeout $(TIMEOUT) -tags "fixtures acceptance" ./internal/acceptance/openstack/loadbalancer/... .PHONY: acceptance-loadbalancer acceptance-messaging: - go test -tags "fixtures acceptance" ./internal/acceptance/openstack/messaging/... + $(GO_TEST) -timeout $(TIMEOUT) -tags "fixtures acceptance" ./internal/acceptance/openstack/messaging/... .PHONY: acceptance-messaging +acceptance-metric: + $(GO_TEST) -timeout $(TIMEOUT) -tags "fixtures acceptance" ./internal/acceptance/openstack/metric/... +.PHONY: acceptance-metric + acceptance-networking: - go test -tags "fixtures acceptance" ./internal/acceptance/openstack/networking/... + $(GO_TEST) -timeout $(TIMEOUT) -tags "fixtures acceptance" ./internal/acceptance/openstack/networking/... .PHONY: acceptance-networking acceptance-objectstorage: - go test -tags "fixtures acceptance" ./internal/acceptance/openstack/objectstorage/... + $(GO_TEST) -timeout $(TIMEOUT) -tags "fixtures acceptance" ./internal/acceptance/openstack/objectstorage/... .PHONY: acceptance-objectstorage acceptance-orchestration: - go test -tags "fixtures acceptance" ./internal/acceptance/openstack/orchestration/... + $(GO_TEST) -timeout $(TIMEOUT) -tags "fixtures acceptance" ./internal/acceptance/openstack/orchestration/... .PHONY: acceptance-orchestration acceptance-placement: - go test -tags "fixtures acceptance" ./internal/acceptance/openstack/placement/... + $(GO_TEST) -timeout $(TIMEOUT) -tags "fixtures acceptance" ./internal/acceptance/openstack/placement/... .PHONY: acceptance-placement acceptance-sharedfilesystems: - go test -tags "fixtures acceptance" ./internal/acceptance/openstack/sharedfilesystems/... + $(GO_TEST) -timeout $(TIMEOUT) -tags "fixtures acceptance" ./internal/acceptance/openstack/sharedfilesystems/... .PHONY: acceptance-sharefilesystems acceptance-workflow: - go test -tags "fixtures acceptance" ./internal/acceptance/openstack/workflow/... + $(GO_TEST) -timeout $(TIMEOUT) -tags "fixtures acceptance" ./internal/acceptance/openstack/workflow/... .PHONY: acceptance-workflow diff --git a/README.md b/README.md index e9ba39bb79..594e9af3fb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # Gophercloud: an OpenStack SDK for Go -[![Coverage Status](https://coveralls.io/repos/github/gophercloud/gophercloud/badge.svg?branch=master)](https://coveralls.io/github/gophercloud/gophercloud?branch=master) - -[Reference documentation](http://godoc.org/github.com/gophercloud/gophercloud/v2) +[![Coverage Status](https://coveralls.io/repos/github/gophercloud/gophercloud/badge.svg?branch=main)](https://coveralls.io/github/gophercloud/gophercloud?branch=main) Gophercloud is a Go SDK for OpenStack. @@ -98,9 +96,9 @@ func main() { // Use the ProviderClient and the endpoint options fetched from // `clouds.yaml` to build a service client: a compute client in this - // case. Note that the contructor does not accept a `context.Context`: - // no further call to the OpenStack API is needed at this stage. - computeClient, err := openstack.NewComputeV2(providerClient, endpointOptions) + // case. Note that the contructor does accept a `context.Context` + // to resolve supported API microversions. + computeClient, err := openstack.NewComputeV2(ctx, providerClient, endpointOptions) if err != nil { panic(err) } @@ -137,7 +135,7 @@ func main() { panic(err) } - computeClient, err := openstack.NewComputeV2(providerClient, gophercloud.EndpointOpts{ + computeClient, err := openstack.NewComputeV2(ctx, providerClient, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) if err != nil { @@ -175,7 +173,7 @@ func main() { panic(err) } - computeClient, err := openstack.NewComputeV2(providerClient, gophercloud.EndpointOpts{ + computeClient, err := openstack.NewComputeV2(ctx, providerClient, gophercloud.EndpointOpts{ Region: "RegionName", }) if err != nil { diff --git a/doc.go b/doc.go deleted file mode 100644 index a755ecb180..0000000000 --- a/doc.go +++ /dev/null @@ -1,148 +0,0 @@ -/* -Package gophercloud provides a multi-vendor interface to OpenStack-compatible -clouds. The library has a three-level hierarchy: providers, services, and -resources. - -# Authenticating with Providers - -Provider structs represent the cloud providers that offer and manage a -collection of services. You will generally want to create one Provider -client per OpenStack cloud. - - It is now recommended to use the `clientconfig` package found at - https://github.com/gophercloud/utils/tree/master/openstack/clientconfig - for all authentication purposes. - - The below documentation is still relevant. clientconfig simply implements - the below and presents it in an easier and more flexible way. - -Use your OpenStack credentials to create a Provider client. The -IdentityEndpoint is typically refered to as "auth_url" or "OS_AUTH_URL" in -information provided by the cloud operator. Additionally, the cloud may refer to -TenantID or TenantName as project_id and project_name. Credentials are -specified like so: - - opts := gophercloud.AuthOptions{ - IdentityEndpoint: "https://openstack.example.com:5000/v2.0", - Username: "{username}", - Password: "{password}", - TenantID: "{tenant_id}", - } - - provider, err := openstack.AuthenticatedClient(context.TODO(), opts) - -You can authenticate with a token by doing: - - opts := gophercloud.AuthOptions{ - IdentityEndpoint: "https://openstack.example.com:5000/v2.0", - TokenID: "{token_id}", - TenantID: "{tenant_id}", - } - - provider, err := openstack.AuthenticatedClient(context.TODO(), opts) - -You may also use the openstack.AuthOptionsFromEnv() helper function. This -function reads in standard environment variables frequently found in an -OpenStack `openrc` file. Again note that Gophercloud currently uses "tenant" -instead of "project". - - opts, err := openstack.AuthOptionsFromEnv() - provider, err := openstack.AuthenticatedClient(context.TODO(), opts) - -# Service Clients - -Service structs are specific to a provider and handle all of the logic and -operations for a particular OpenStack service. Examples of services include: -Compute, Object Storage, Block Storage. In order to define one, you need to -pass in the parent provider, like so: - - opts := gophercloud.EndpointOpts{Region: "RegionOne"} - - client, err := openstack.NewComputeV2(provider, opts) - -# Resources - -Resource structs are the domain models that services make use of in order -to work with and represent the state of API resources: - - server, err := servers.Get(context.TODO(), client, "{serverId}").Extract() - -Intermediate Result structs are returned for API operations, which allow -generic access to the HTTP headers, response body, and any errors associated -with the network transaction. To turn a result into a usable resource struct, -you must call the Extract method which is chained to the response, or an -Extract function from an applicable extension: - - result := servers.Get(context.TODO(), client, "{serverId}") - - // Attempt to extract the disk configuration from the OS-DCF disk config - // extension: - config, err := diskconfig.ExtractGet(result) - -All requests that enumerate a collection return a Pager struct that is used to -iterate through the results one page at a time. Use the EachPage method on that -Pager to handle each successive Page in a closure, then use the appropriate -extraction method from that request's package to interpret that Page as a slice -of results: - - err := servers.List(client, nil).EachPage(context.TODO(), func (_ context.Context, page pagination.Page) (bool, error) { - s, err := servers.ExtractServers(page) - if err != nil { - return false, err - } - - // Handle the []servers.Server slice. - - // Return "false" or an error to prematurely stop fetching new pages. - return true, nil - }) - -If you want to obtain the entire collection of pages without doing any -intermediary processing on each page, you can use the AllPages method: - - allPages, err := servers.List(client, nil).AllPages(context.TODO()) - allServers, err := servers.ExtractServers(allPages) - -This top-level package contains utility functions and data types that are used -throughout the provider and service packages. Of particular note for end users -are the AuthOptions and EndpointOpts structs. - -An example retry backoff function, which respects the 429 HTTP response code and a "Retry-After" header: - - endpoint := "http://localhost:5000" - provider, err := openstack.NewClient(endpoint) - if err != nil { - panic(err) - } - provider.MaxBackoffRetries = 3 // max three retries - provider.RetryBackoffFunc = func(ctx context.Context, respErr *ErrUnexpectedResponseCode, e error, retries uint) error { - retryAfter := respErr.ResponseHeader.Get("Retry-After") - if retryAfter == "" { - return e - } - - var sleep time.Duration - - // Parse delay seconds or HTTP date - if v, err := strconv.ParseUint(retryAfter, 10, 32); err == nil { - sleep = time.Duration(v) * time.Second - } else if v, err := time.Parse(http.TimeFormat, retryAfter); err == nil { - sleep = time.Until(v) - } else { - return e - } - - if ctx != nil { - select { - case <-time.After(sleep): - case <-ctx.Done(): - return e - } - } else { - time.Sleep(sleep) - } - - return nil - } -*/ -package gophercloud diff --git a/docs/FAQ.md b/docs/FAQ.md index bb37db8019..bfd0bc7bc8 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -8,7 +8,7 @@ Please see our dedicated document [here](MICROVERSIONS.md). You can implement custom logging and/or limit re-auth attempts by creating a custom HTTP client like the following and setting it as the provider client's HTTP Client (via the -`gophercloud.ProviderClient.HTTPClient` field): +`gophercloud.HTTPClient` field): ```go //... @@ -41,7 +41,7 @@ func (lrt *LogRoundTripper) RoundTrip(request *http.Request) (*http.Response, er if response.StatusCode == http.StatusUnauthorized { if lrt.numReauthAttempts == 3 { - return response, fmt.Errorf("Tried to re-authenticate 3 times with no success.") + return response, fmt.Errorf("tried to re-authenticate 3 times with no success") } lrt.numReauthAttempts++ } diff --git a/docs/MICROVERSIONS.md b/docs/MICROVERSIONS.md index 2054c9a6ef..6aa88e8a79 100644 --- a/docs/MICROVERSIONS.md +++ b/docs/MICROVERSIONS.md @@ -26,7 +26,7 @@ More information can be found here: You can set a specific microversion on a Service Client by doing the following: ```go -client, err := openstack.NewComputeV2(providerClient, nil) +client, err := openstack.NewComputeV2(context.TODO(), providerClient, nil) client.Microversion = "2.52" ``` diff --git a/docs/MIGRATING.md b/docs/MIGRATING.md index a507820bf5..7bf4ed9708 100644 --- a/docs/MIGRATING.md +++ b/docs/MIGRATING.md @@ -493,3 +493,148 @@ Users that still rely on theses old services should continue using Gophercloud v (`openstack/networking/v2/extensions/fwaas`) - Poppy (CDNaaS) service (`openstack/cdn`) - Senlin (Clustering) service (`openstack/clustering`) + +### Script-assisted migration + +#### Expected outcome + +After running the script, your code may not compile. The idea is that at this point, you're only left with a few changes that can't reasonably be automated. + +#### What it does + +* Add `/v2` to all Gophercloud imports, except to the packages that have been removed without replacement +* Adjust the import path of moved packages +* Adjust the package identifier in the code where possible +* Add `context.TODO()` where required + +#### Limitations + +* it doesn't fix the use of removed extensions. For example, if you used `openstack/blockstorage/extensions/availabilityzones`, you will have to manually put that back into e.g. `servers.CreateOpts` +* it will just put `context.TODO()` where a context is required to satisfy the function signature. It's up to you to actually replace that with a variable and provide proper cancellation +* it will add `context.TODO()` to `blockstorage/v1` calls, even though that package only exists in Gophercloud v1 + +```bash +# Adjust the blockstorage version appropriately +blockstorageversion=v3 + +openstack='github.com/gophercloud/gophercloud/openstack' +openstack_utils='github.com/gophercloud/utils/openstack' +find . -type f -name '*.go' -not -path "*/vendor/*" -exec sed -i ' + /^import ($/,/^)$/ { + + # 1: These packages have been removed and their functionality moved into the main module for the corresponding service. + /\(\/openstack\/blockstorage\/v1\|\/openstack\/networking\/v2\/extensions\/lbaas\|\/openstack\/networking\/v2\/extensions\/lbaas_v2\|\/openstack\/networking\/v2\/extensions\/fwaas\|\/openstack\/cdn\|\/openstack\/clustering\)/! { + /\/openstack\/blockstorage\/extensions\/volumehost/d + /\/openstack\/blockstorage\/extensions\/volumetenants/d + /\/openstack\/compute\/v2\/extensions\/bootfromvolume/d + /\/openstack\/compute\/v2\/extensions\/diskconfig/d + /\/openstack\/compute\/v2\/extensions\/extendedserverattributes/d + /\/openstack\/compute\/v2\/extensions\/extendedstatus/d + /\/openstack\/compute\/v2\/extensions\/schedulerhints/d + /\/openstack\/compute\/v2\/extensions\/serverusage/d + /\/openstack\/compute\/v2\/extensions\/availabilityzones/d + /\/openstack\/identity\/v3\/extensions\/trusts/d + } + + '" + # 2: Functions and supporting structs and interfaces of these packages have been moved to an existing package + s|${openstack}/blockstorage/extensions/schedulerhints|${openstack}/blockstorage/${blockstorageversion}/volumes|g + s|${openstack}/blockstorage/extensions/volumeactions|${openstack}/blockstorage/${blockstorageversion}/volumes|g + s|${openstack}/compute/v2/extensions/evacuate|${openstack}/compute/v2/servers|g + s|${openstack}/compute/v2/extensions/injectnetworkinfo|${openstack}/compute/v2/servers|g + s|${openstack}/compute/v2/extensions/lockunlock|${openstack}/compute/v2/servers|g + s|${openstack}/compute/v2/extensions/migrate|${openstack}/compute/v2/servers|g + s|${openstack}/compute/v2/extensions/pauseunpause|${openstack}/compute/v2/servers|g + s|${openstack}/compute/v2/extensions/rescueunrescue|${openstack}/compute/v2/servers|g + s|${openstack}/compute/v2/extensions/resetnetwork|${openstack}/compute/v2/servers|g + s|${openstack}/compute/v2/extensions/resetstate|${openstack}/compute/v2/servers|g + s|${openstack}/compute/v2/extensions/shelveunshelve|${openstack}/compute/v2/servers|g + s|${openstack}/compute/v2/extensions/startstop|${openstack}/compute/v2/servers|g + s|${openstack}/compute/v2/extensions/suspendresume|${openstack}/compute/v2/servers|g + + # 3: These packages have been renamed + s|${openstack}/imageservice|${openstack}/image|g + s|${openstack_utils}/imageservice|${openstack_utils}/image|g + s|${openstack}/blockstorage/extensions/availabilityzones|${openstack}/blockstorage/${blockstorageversion}/availabilityzones|g + s|${openstack}/blockstorage/extensions/backups|${openstack}/blockstorage/${blockstorageversion}/backups|g + s|${openstack}/blockstorage/extensions/limits|${openstack}/blockstorage/${blockstorageversion}/limits|g + s|${openstack}/blockstorage/extensions/quotasets|${openstack}/blockstorage/${blockstorageversion}/quotasets|g + s|${openstack}/blockstorage/extensions/schedulerstats|${openstack}/blockstorage/${blockstorageversion}/schedulerstats|g + s|${openstack}/blockstorage/extensions/services|${openstack}/blockstorage/${blockstorageversion}/services|g + s|${openstack}/blockstorage/extensions/volumetransfers|${openstack}/blockstorage/${blockstorageversion}/transfers|g + s|${openstack}/compute/v2/extensions/aggregates|${openstack}/compute/v2/aggregates|g + s|${openstack}/compute/v2/extensions/attachinterfaces|${openstack}/compute/v2/attachinterfaces|g + s|${openstack}/compute/v2/extensions/diagnostics|${openstack}/compute/v2/diagnostics|g + s|${openstack}/compute/v2/extensions/hypervisors|${openstack}/compute/v2/hypervisors|g + s|${openstack}/compute/v2/extensions/instanceactions|${openstack}/compute/v2/instanceactions|g + s|${openstack}/compute/v2/extensions/keypairs|${openstack}/compute/v2/keypairs|g + s|${openstack}/compute/v2/extensions/limits|${openstack}/compute/v2/limits|g + s|${openstack}/compute/v2/extensions/quotasets|${openstack}/compute/v2/quotasets|g + s|${openstack}/compute/v2/extensions/remoteconsoles|${openstack}/compute/v2/remoteconsoles|g + s|${openstack}/compute/v2/extensions/secgroups|${openstack}/compute/v2/secgroups|g + s|${openstack}/compute/v2/extensions/servergroups|${openstack}/compute/v2/servergroups|g + s|${openstack}/compute/v2/extensions/services|${openstack}/compute/v2/services|g + s|${openstack}/compute/v2/extensions/tags|${openstack}/compute/v2/tags|g + s|${openstack}/compute/v2/extensions/usage|${openstack}/compute/v2/usage|g + s|${openstack}/compute/v2/extensions/volumeattach|${openstack}/compute/v2/volumeattach|g + s|${openstack}/identity/v2/extensions/admin/roles|${openstack}/identity/v2/roles|g + s|${openstack}/identity/v3/extensions/ec2credentials|${openstack}/identity/v3/ec2credentials|g + s|${openstack}/identity/v3/extensions/ec2tokens|${openstack}/identity/v3/ec2tokens|g + s|${openstack}/identity/v3/extensions/federation|${openstack}/identity/v3/federation|g + s|${openstack}/identity/v3/extensions/oauth1|${openstack}/identity/v3/oauth1|g + s|${openstack}/identity/v3/extensions/projectendpoints|${openstack}/identity/v3/projectendpoints|g + + # 4: These removed packages existed as proxies of others + s|${openstack}/compute/v2/extensions/defsecrules|${openstack}/networking/v2/extensions/security/groups|g + s|${openstack}/compute/v2/extensions/floatingips|${openstack}/networking/v2/extensions/layer3/floatingips|g + s|${openstack}/compute/v2/extensions/images|${openstack}/image/v2/images|g + s|${openstack}/compute/v2/extensions/networks|${openstack}/networking/v2/networks|g + s|${openstack}/compute/v2/extensions/tenantnetworks|${openstack}/networking/v2/networks|g + "' + + # 5: Update to v2, except for packages that were removed without replacement + s|github.com/gophercloud/utils|github.com/gophercloud/utils/v2|g + /\(\/openstack\/blockstorage\/v1\|\/openstack\/networking\/v2\/extensions\/lbaas\|\/openstack\/networking\/v2\/extensions\/lbaas_v2\|\/openstack\/networking\/v2\/extensions\/fwaas\|\/openstack\/cdn\|\/openstack\/clustering\)/! s|github.com/gophercloud/gophercloud|github.com/gophercloud/gophercloud/v2|g + } + + /^)$/,$ { + + # 6: Rename identifiers of items of step 2 above + s#\(schedulerhints\|volumeactions\)\.\([A-Z][A-Z_a-z_0-9]*\)#volumes.\2#g + s#\(evacuate\|injectnetworkinfo\|lockunlock\|migrate\|pauseunpause\|rescueunrescue\|resetnetwork\|resetstate\|shelveunshelve\|startstop\|suspendresume\)\.\([A-Z][A-Z_a-z_0-9]*\)#servers.\2#g + + # 7: Add context.TODO() + s#\(accept\.Create\|accept\.Get\|accounts\.Get\|accounts\.Update\|acls\.DeleteContainerACL\|acls\.DeleteSecretACL\|acls\.GetContainerACL\|acls\.GetSecretACL\|acls\.SetContainerACL\|acls\.SetSecretACL\|acls\.UpdateContainerACL\|acls\.UpdateSecretACL\|addressscopes\.Create\|addressscopes\.Delete\|addressscopes\.Get\|addressscopes\.Update\|agents\.Delete\|agents\.Get\|agents\.ListDHCPNetworks\|agents\.ListL3Routers\|agents\.RemoveBGPSpeaker\|agents\.RemoveDHCPNetwork\|agents\.RemoveL3Router\|agents\.ScheduleBGPSpeaker\|agents\.ScheduleDHCPNetwork\|agents\.ScheduleL3Router\|agents\.Update\|aggregates\.AddHost\|aggregates\.Create\|aggregates\.Delete\|aggregates\.Get\|aggregates\.RemoveHost\|aggregates\.SetMetadata\|aggregates\.Update\|allocations\.Create\|allocations\.Delete\|allocations\.Get\|amphorae\.Failover\|amphorae\.Get\|apiversions\.Get\|apiversions\.List\|applicationcredentials\.Create\|applicationcredentials\.Delete\|applicationcredentials\.DeleteAccessRule\|applicationcredentials\.Get\|applicationcredentials\.GetAccessRule\|attachinterfaces\.Create\|attachinterfaces\.Delete\|attachinterfaces\.Get\|attachments\.Complete\|attachments\.Create\|attachments\.Delete\|attachments\.Get\|attachments\.Update\|attachments\.WaitForStatus\|backups\.Create\|backups\.Delete\|backups\.Export\|backups\.ForceDelete\|backups\.Get\|backups\.Import\|backups\.ResetStatus\|backups\.RestoreFromBackup\|backups\.Update\|bgpvpns\.Create\|bgpvpns\.CreateNetworkAssociation\|bgpvpns\.CreatePortAssociation\|bgpvpns\.CreateRouterAssociation\|bgpvpns\.Delete\|bgpvpns\.DeleteNetworkAssociation\|bgpvpns\.DeletePortAssociation\|bgpvpns\.DeleteRouterAssociation\|bgpvpns\.Get\|bgpvpns\.GetNetworkAssociation\|bgpvpns\.GetPortAssociation\|bgpvpns\.GetRouterAssociation\|bgpvpns\.Update\|bgpvpns\.UpdatePortAssociation\|bgpvpns\.UpdateRouterAssociation\|buildinfo\.Get\|capsules\.Create\|capsules\.Delete\|capsules\.Get\|certificates\.Create\|certificates\.Get\|certificates\.Update\|claims\.Create\|claims\.Delete\|claims\.Get\|claims\.Update\|clusters\.Create\|clusters\.Delete\|clusters\.Get\|clusters\.Resize\|clusters\.Update\|clusters\.Upgrade\|clustertemplates\.Create\|clustertemplates\.Delete\|clustertemplates\.Get\|clustertemplates\.Update\|conductors\.Get\|config\.NewProviderClient\|configurations\.Create\|configurations\.Delete\|configurations\.Get\|configurations\.GetDatastoreParam\|configurations\.GetGlobalParam\|configurations\.Replace\|configurations\.Update\|containers\.BulkDelete\|containers\.Create\|containers\.CreateConsumer\|containers\.CreateSecretRef\|containers\.Delete\|containers\.DeleteConsumer\|containers\.DeleteSecretRef\|containers\.Get\|containers\.Update\|credentials\.Create\|credentials\.Delete\|credentials\.Get\|credentials\.Update\|crontriggers\.Create\|crontriggers\.Delete\|crontriggers\.Get\|databases\.Create\|databases\.Delete\|datastores\.Get\|datastores\.GetVersion\|diagnostics\.Get\|domains\.Create\|domains\.Delete\|domains\.Get\|domains\.Update\|drivers\.GetDriverDetails\|drivers\.GetDriverDiskProperties\|drivers\.GetDriverProperties\|ec2credentials\.Create\|ec2credentials\.Delete\|ec2credentials\.Get\|ec2tokens\.Create\|ec2tokens\.ValidateS3Token\|endpointgroups\.Create\|endpointgroups\.Delete\|endpointgroups\.Get\|endpointgroups\.Update\|endpoints\.Create\|endpoints\.Delete\|endpoints\.Update\|executions\.Create\|executions\.Delete\|executions\.Get\|extensions\.Get\|extraroutes\.Add\|extraroutes\.Remove\|federation\.CreateMapping\|federation\.DeleteMapping\|federation\.GetMapping\|federation\.UpdateMapping\|flavorprofiles\.Create\|flavorprofiles\.Delete\|flavorprofiles\.Get\|flavorprofiles\.Update\|flavors\.AddAccess\|flavors\.Create\|flavors\.CreateExtraSpecs\|flavors\.Delete\|flavors\.DeleteExtraSpec\|flavors\.Get\|flavors\.GetExtraSpec\|flavors\.ListExtraSpecs\|flavors\.RemoveAccess\|flavors\.Update\|flavors\.UpdateExtraSpec\|floatingips\.Create\|floatingips\.Delete\|floatingips\.Get\|floatingips\.Update\|gophercloud\.WaitFor\|groups\.Create\|groups\.Delete\|groups\.Get\|groups\.RemoveEgressPolicy\|groups\.RemoveIngressPolicy\|groups\.Update\|hypervisors\.Get\|hypervisors\.GetStatistics\|hypervisors\.GetUptime\|ikepolicies\.Create\|ikepolicies\.Delete\|ikepolicies\.Get\|ikepolicies\.Update\|imagedata\.Download\|imagedata\.Stage\|imagedata\.Upload\|imageimport\.Create\|imageimport\.Get\|images\.Create\|images\.Delete\|images\.Get\|images\.Update\|instanceactions\.Get\|instances\.AttachConfigurationGroup\|instances\.Create\|instances\.Delete\|instances\.DetachConfigurationGroup\|instances\.EnableRootUser\|instances\.Get\|instances\.IsRootEnabled\|instances\.Resize\|instances\.ResizeVolume\|instances\.Restart\|introspection\.AbortIntrospection\|introspection\.GetIntrospectionData\|introspection\.GetIntrospectionStatus\|introspection\.ReApplyIntrospection\|introspection\.StartIntrospection\|ipsecpolicies\.Create\|ipsecpolicies\.Delete\|ipsecpolicies\.Get\|ipsecpolicies\.Update\|keypairs\.Create\|keypairs\.Delete\|keypairs\.Get\|l7policies\.Create\|l7policies\.CreateRule\|l7policies\.Delete\|l7policies\.DeleteRule\|l7policies\.Get\|l7policies\.GetRule\|l7policies\.Update\|l7policies\.UpdateRule\|limits\.BatchCreate\|limits\.Delete\|limits\.Get\|limits\.GetEnforcementModel\|limits\.Update\|listeners\.Create\|listeners\.Delete\|listeners\.Get\|listeners\.GetStats\|listeners\.Update\|loadbalancers\.Create\|loadbalancers\.Delete\|loadbalancers\.Failover\|loadbalancers\.Get\|loadbalancers\.GetStats\|loadbalancers\.GetStatuses\|loadbalancers\.Update\|members\.Create\|members\.Delete\|members\.Get\|members\.Update\|messages\.Create\|messages\.Delete\|messages\.DeleteMessages\|messages\.Get\|messages\.GetMessages\|messages\.PopMessages\|monitors\.Create\|monitors\.Delete\|monitors\.Get\|monitors\.Update\|networkipavailabilities\.Get\|networks\.Create\|networks\.Delete\|networks\.Get\|networks\.Update\|nodegroups\.Create\|nodegroups\.Delete\|nodegroups\.Get\|nodegroups\.Update\|nodes\.AttachVirtualMedia\|nodes\.ChangePowerState\|nodes\.ChangeProvisionState\|nodes\.Create\|nodes\.CreateSubscription\|nodes\.Delete\|nodes\.DeleteSubscription\|nodes\.DetachVirtualMedia\|nodes\.Get\|nodes\.GetAllSubscriptions\|nodes\.GetBIOSSetting\|nodes\.GetBootDevice\|nodes\.GetInventory\|nodes\.GetSubscription\|nodes\.GetSupportedBootDevices\|nodes\.GetVendorPassthruMethods\|nodes\.InjectNMI\|nodes\.ListBIOSSettings\|nodes\.ListFirmware\|nodes\.SetBootDevice\|nodes\.SetMaintenance\|nodes\.SetRAIDConfig\|nodes\.UnsetMaintenance\|nodes\.Update\|nodes\.Validate\|nodes\.WaitForProvisionState\|oauth1\.AuthorizeToken\|oauth1\.Create\|oauth1\.CreateAccessToken\|oauth1\.CreateConsumer\|oauth1\.DeleteConsumer\|oauth1\.GetAccessToken\|oauth1\.GetAccessTokenRole\|oauth1\.GetConsumer\|oauth1\.RequestToken\|oauth1\.RevokeAccessToken\|oauth1\.UpdateConsumer\|objects\.BulkDelete\|objects\.Copy\|objects\.Create\|objects\.CreateTempURL\|objects\.Delete\|objects\.Download\|objects\.Get\|objects\.Update\|openstack\.Authenticate\|openstack\.AuthenticatedClient\|openstack\.AuthenticateV2\|openstack\.AuthenticateV3\|orders\.Create\|orders\.Delete\|orders\.Get\|osinherit\.Assign\|osinherit\.Unassign\|osinherit\.Validate\|pagination\.Request\|peers\.Create\|peers\.Delete\|peers\.Get\|peers\.Update\|policies\.Create\|policies\.Delete\|policies\.Get\|policies\.InsertRule\|policies\.RemoveRule\|policies\.Update\|pools\.BatchUpdateMembers\|pools\.Create\|pools\.CreateMember\|pools\.Delete\|pools\.DeleteMember\|pools\.Get\|pools\.GetMember\|pools\.Update\|pools\.UpdateMember\|portforwarding\.Create\|portforwarding\.Delete\|portforwarding\.Get\|portforwarding\.Update\|ports\.Create\|ports\.Delete\|ports\.Get\|ports\.Update\|projectendpoints\.Create\|projectendpoints\.Delete\|projects\.Create\|projects\.Delete\|projects\.DeleteTags\|projects\.Get\|projects\.ListTags\|projects\.ModifyTags\|projects\.Update\|qos\.Associate\|qos\.Create\|qos\.Delete\|qos\.DeleteKeys\|qos\.Disassociate\|qos\.DisassociateAll\|qos\.Get\|qos\.Update\|queues\.Create\|queues\.Delete\|queues\.Get\|queues\.GetStats\|queues\.Purge\|queues\.Share\|queues\.Update\|quotas\.Create\|quotasets\.Delete\|quotasets\.Get\|quotasets\.GetDefaults\|quotasets\.GetDetail\|quotasets\.GetUsage\|quotasets\.Update\|quotas\.Get\|quotas\.GetDetail\|quotas\.Update\|rbacpolicies\.Create\|rbacpolicies\.Delete\|rbacpolicies\.Get\|rbacpolicies\.Update\|recordsets\.Create\|recordsets\.Delete\|recordsets\.Get\|recordsets\.Update\|regions\.Create\|regions\.Delete\|regions\.Get\|regions\.Update\|registeredlimits\.BatchCreate\|registeredlimits\.Delete\|registeredlimits\.Get\|registeredlimits\.Update\|remoteconsoles\.Create\|replicas\.Create\|replicas\.Delete\|replicas\.ForceDelete\|replicas\.Get\|replicas\.GetExportLocation\|replicas\.ListExportLocations\|replicas\.Promote\|replicas\.ResetState\|replicas\.ResetStatus\|replicas\.Resync\|request\.Create\|request\.Delete\|request\.Get\|request\.Update\|resourceproviders\.Create\|resourceproviders\.Delete\|resourceproviders\.Get\|resourceproviders\.GetAllocations\|resourceproviders\.GetInventories\|resourceproviders\.GetTraits\|resourceproviders\.GetUsages\|resourceproviders\.Update\|resourcetypes\.GenerateTemplate\|resourcetypes\.GetSchema\|resourcetypes\.List\|roles\.AddUser\|roles\.Assign\|roles\.Create\|roles\.CreateRoleInferenceRule\|roles\.Delete\|roles\.DeleteRoleInferenceRule\|roles\.DeleteUser\|roles\.Get\|roles\.GetRoleInferenceRule\|roles\.ListRoleInferenceRules\|roles\.Unassign\|roles\.Update\|routers\.AddInterface\|routers\.Create\|routers\.Delete\|routers\.Get\|routers\.RemoveInterface\|routers\.Update\|rules\.Create\|rules\.CreateBandwidthLimitRule\|rules\.CreateDSCPMarkingRule\|rules\.CreateMinimumBandwidthRule\|rules\.Delete\|rules\.DeleteBandwidthLimitRule\|rules\.DeleteDSCPMarkingRule\|rules\.DeleteMinimumBandwidthRule\|rules\.Get\|rules\.GetBandwidthLimitRule\|rules\.GetDSCPMarkingRule\|rules\.GetMinimumBandwidthRule\|rules\.Update\|rules\.UpdateBandwidthLimitRule\|rules\.UpdateDSCPMarkingRule\|rules\.UpdateMinimumBandwidthRule\|ruletypes\.GetRuleType\|secgroups\.AddServer\|secgroups\.Create\|secgroups\.CreateRule\|secgroups\.Delete\|secgroups\.DeleteRule\|secgroups\.Get\|secgroups\.RemoveServer\|secgroups\.Update\|secrets\.Create\|secrets\.CreateMetadata\|secrets\.CreateMetadatum\|secrets\.Delete\|secrets\.DeleteMetadatum\|secrets\.Get\|secrets\.GetMetadata\|secrets\.GetMetadatum\|secrets\.GetPayload\|secrets\.Update\|secrets\.UpdateMetadatum\|securityservices\.Create\|securityservices\.Delete\|securityservices\.Get\|securityservices\.Update\|servergroups\.Create\|servergroups\.Delete\|servergroups\.Get\|servers\.ChangeAdminPassword\|servers\.ConfirmResize\|servers\.Create\|servers\.CreateImage\|servers\.CreateMetadatum\|servers\.Delete\|servers\.DeleteMetadatum\|servers\.Evacuate\|servers\.ForceDelete\|servers\.Get\|servers\.GetPassword\|servers\.InjectNetworkInfo\|servers\.LiveMigrate\|servers\.Lock\|servers\.Metadata\|servers\.Metadatum\|servers\.Migrate\|servers\.Pause\|servers\.Reboot\|servers\.Rebuild\|servers\.Rescue\|servers\.ResetMetadata\|servers\.ResetNetwork\|servers\.ResetState\|servers\.Resize\|servers\.Resume\|servers\.RevertResize\|servers\.Shelve\|servers\.ShelveOffload\|servers\.ShowConsoleOutput\|servers\.Start\|servers\.Stop\|servers\.Suspend\|servers\.Unlock\|servers\.Unpause\|servers\.Unrescue\|servers\.Unshelve\|servers\.Update\|servers\.UpdateMetadata\|servers\.WaitForStatus\|services\.Create\|services\.Delete\|services\.Get\|services\.Update\|shareaccessrules\.Get\|shareaccessrules\.List\|sharenetworks\.AddSecurityService\|sharenetworks\.Create\|sharenetworks\.Delete\|sharenetworks\.Get\|sharenetworks\.RemoveSecurityService\|sharenetworks\.Update\|shares\.Create\|shares\.Delete\|shares\.DeleteMetadatum\|shares\.Extend\|shares\.ForceDelete\|shares\.Get\|shares\.GetExportLocation\|shares\.GetMetadata\|shares\.GetMetadatum\|shares\.GrantAccess\|shares\.ListAccessRights\|shares\.ListExportLocations\|shares\.ResetStatus\|shares\.Revert\|shares\.RevokeAccess\|shares\.SetMetadata\|shares\.Shrink\|shares\.Unmanage\|shares\.Update\|shares\.UpdateMetadata\|sharetransfers\.Accept\|sharetransfers\.Create\|sharetransfers\.Delete\|sharetransfers\.Get\|sharetypes\.AddAccess\|sharetypes\.Create\|sharetypes\.Delete\|sharetypes\.GetDefault\|sharetypes\.GetExtraSpecs\|sharetypes\.RemoveAccess\|sharetypes\.SetExtraSpecs\|sharetypes\.ShowAccess\|sharetypes\.UnsetExtraSpecs\|siteconnections\.Create\|siteconnections\.Delete\|siteconnections\.Get\|siteconnections\.Update\|snapshots\.Create\|snapshots\.Delete\|snapshots\.ForceDelete\|snapshots\.Get\|snapshots\.ResetStatus\|snapshots\.Update\|snapshots\.UpdateMetadata\|snapshots\.UpdateStatus\|snapshots\.WaitForStatus\|speakers\.AddBGPPeer\|speakers\.AddGatewayNetwork\|speakers\.Create\|speakers\.Delete\|speakers\.Get\|speakers\.RemoveBGPPeer\|speakers\.RemoveGatewayNetwork\|speakers\.Update\|stackevents\.Find\|stackevents\.Get\|stackresources\.Find\|stackresources\.Get\|stackresources\.MarkUnhealthy\|stackresources\.Metadata\|stackresources\.Schema\|stackresources\.Template\|stacks\.Abandon\|stacks\.Adopt\|stacks\.Create\|stacks\.Delete\|stacks\.Find\|stacks\.Get\|stacks\.Preview\|stacks\.Update\|stacks\.UpdatePatch\|stacktemplates\.Get\|stacktemplates\.Validate\|subnetpools\.Create\|subnetpools\.Delete\|subnetpools\.Get\|subnetpools\.Update\|subnets\.Create\|subnets\.Delete\|subnets\.Get\|subnets\.Update\|swauth\.Auth\|swauth\.NewObjectStorageV1\|tags\.Add\|tags\.Check\|tags\.Delete\|tags\.DeleteAll\|tags\.List\|tags\.ReplaceAll\|tasks\.Create\|tasks\.Get\|tenants\.Create\|tenants\.Delete\|tenants\.Get\|tenants\.Update\|tokens\.Create\|tokens\.Get\|tokens\.Revoke\|tokens\.Validate\|transfers\.Accept\|transfers\.Create\|transfers\.Delete\|transfers\.Get\|trunks\.AddSubports\|trunks\.Create\|trunks\.Delete\|trunks\.Get\|trunks\.GetSubports\|trunks\.RemoveSubports\|trunks\.Update\|trusts\.CheckRole\|trusts\.Create\|trusts\.Delete\|trusts\.Get\|trusts\.GetRole\|users\.AddToGroup\|users\.ChangePassword\|users\.Create\|users\.Delete\|users\.Get\|users\.IsMemberOfGroup\|users\.RemoveFromGroup\|users\.Update\|utils\.ChooseVersion\|utils\.GetSupportedMicroversions\|utils\.RequireMicroversion\|volumeattach\.Create\|volumeattach\.Delete\|volumeattach\.Get\|volumes\.Attach\|volumes\.BeginDetaching\|volumes\.ChangeType\|volumes\.Create\|volumes\.Delete\|volumes\.Detach\|volumes\.ExtendSize\|volumes\.ForceDelete\|volumes\.Get\|volumes\.InitializeConnection\|volumes\.ReImage\|volumes\.Reserve\|volumes\.ResetStatus\|volumes\.SetBootable\|volumes\.SetImageMetadata\|volumes\.TerminateConnection\|volumes\.Unreserve\|volumes\.Update\|volumes\.UploadImage\|volumes\.WaitForStatus\|volumetypes\.AddAccess\|volumetypes\.Create\|volumetypes\.CreateEncryption\|volumetypes\.CreateExtraSpecs\|volumetypes\.Delete\|volumetypes\.DeleteEncryption\|volumetypes\.DeleteExtraSpec\|volumetypes\.Get\|volumetypes\.GetEncryption\|volumetypes\.GetEncryptionSpec\|volumetypes\.GetExtraSpec\|volumetypes\.ListExtraSpecs\|volumetypes\.RemoveAccess\|volumetypes\.Update\|volumetypes\.UpdateEncryption\|volumetypes\.UpdateExtraSpec\|workflows\.Create\|workflows\.Delete\|workflows\.Get\|zones\.Create\|zones\.Delete\|zones\.Get\|zones\.Update\)(#\1(context.TODO(), #g + s#\(\.AllPages(\)#\1context.TODO(), #g + s#\(\.EachPage(\)\(func(\)#\1context.TODO(), \2ctx context.Context, #g + + # 8: Rename identifiers that were changed in v2 + s#\(\(volumes\|servers\)\.SchedulerHint\)s#\2.SchedulerHintOpts#g + + # 9: Tentatively replace error handling + s#\(\t\+\)if _, ok := err.(gophercloud.ErrDefault400); \(!\?\)ok {#\1if \2gophercloud.ResponseCodeIs(err, http.StatusBadRequest) {#g + s#\(\t\+\)if _, ok := err.(gophercloud.ErrDefault401); \(!\?\)ok {#\1if \2gophercloud.ResponseCodeIs(err, http.StatusUnauthorized) {#g + s#\(\t\+\)if _, ok := err.(gophercloud.ErrDefault403); \(!\?\)ok {#\1if \2gophercloud.ResponseCodeIs(err, http.StatusForbidden) {#g + s#\(\t\+\)if _, ok := err.(gophercloud.ErrDefault404); \(!\?\)ok {#\1if \2gophercloud.ResponseCodeIs(err, http.StatusNotFound) {#g + s#\(\t\+\)if _, ok := err.(gophercloud.ErrDefault405); \(!\?\)ok {#\1if \2gophercloud.ResponseCodeIs(err, http.StatusMethodNotAllowed) {#g + s#\(\t\+\)if _, ok := err.(gophercloud.ErrDefault408); \(!\?\)ok {#\1if \2gophercloud.ResponseCodeIs(err, http.StatusRequestTimeout) {#g + s#\(\t\+\)if _, ok := err.(gophercloud.ErrDefault409); \(!\?\)ok {#\1if \2gophercloud.ResponseCodeIs(err, http.StatusConflict) {#g + s#\(\t\+\)if _, ok := err.(gophercloud.ErrDefault429); \(!\?\)ok {#\1if \2gophercloud.ResponseCodeIs(err, http.StatusTooManyRequests) {#g + s#\(\t\+\)if _, ok := err.(gophercloud.ErrDefault500); \(!\?\)ok {#\1if \2gophercloud.ResponseCodeIs(err, http.StatusInternalServerError) {#g + s#\(\t\+\)if _, ok := err.(gophercloud.ErrDefault502); \(!\?\)ok {#\1if \2gophercloud.ResponseCodeIs(err, http.StatusBadGateway) {#g + s#\(\t\+\)if _, ok := err.(gophercloud.ErrDefault503); \(!\?\)ok {#\1if \2gophercloud.ResponseCodeIs(err, http.StatusServiceUnavailable) {#g + s#\(\t\+\)if _, ok := err.(gophercloud.ErrDefault504); \(!\?\)ok {#\1if \2gophercloud.ResponseCodeIs(err, http.StatusGatewayTimeout) {#g + } + ' {} \; + +grep -r -l 'context\.TODO' | xargs -r sed -i ' + /^import ($/ a "context" + ' + +grep -r -l 'http\.Status' | xargs -r sed -i ' + /^import ($/ a "net/http" + ' + +goimports -format-only -w . +go mod tidy +``` diff --git a/docs/STYLEGUIDE.md b/docs/STYLEGUIDE.md index fb09b72f6a..8d3b426d28 100644 --- a/docs/STYLEGUIDE.md +++ b/docs/STYLEGUIDE.md @@ -28,9 +28,8 @@ [Pending #PRNUM] tag once the PR has been updated with the merged, dependent PR. That will let reviewers know it is ready to review. -- A PR should be small. Even if you intend on implementing an entire - service, a PR should only be one route of that service - (e.g. create server or get server, but not both). +- A PR should be focused. Group related operations together, but avoid + mixing unrelated changes in a single PR. - Unless explicitly asked, do not squash commits in the middle of a review; only append. It makes it difficult for the reviewer to see what's changed from one diff --git a/docs/contributor-tutorial/.template/testing/requests_test.go b/docs/contributor-tutorial/.template/testing/requests_test.go index c1766d8624..9150678c7b 100644 --- a/docs/contributor-tutorial/.template/testing/requests_test.go +++ b/docs/contributor-tutorial/.template/testing/requests_test.go @@ -16,7 +16,7 @@ func TestListResources(t *testing.T) { HandleListResourcesSuccessfully(t) count := 0 - err := resources.List(client.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := resources.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := resources.ExtractResources(page) @@ -27,7 +27,7 @@ func TestListResources(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.AssertEquals(t, count, 1) + th.AssertEquals(t, 1, count) } func TestListResourcesAllPages(t *testing.T) { @@ -35,7 +35,7 @@ func TestListResourcesAllPages(t *testing.T) { defer th.TeardownHTTP() HandleListResourcesSuccessfully(t) - allPages, err := resources.List(client.ServiceClient(), nil).AllPages(context.TODO()) + allPages, err := resources.List(client.ServiceClient(fakeServer), nil).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := resources.ExtractResources(allPages) th.AssertNoErr(t, err) @@ -47,7 +47,7 @@ func TestGetResource(t *testing.T) { defer th.TeardownHTTP() HandleGetResourceSuccessfully(t) - actual, err := resources.Get(context.TODO(), client.ServiceClient(), "9fe1d3").Extract() + actual, err := resources.Get(context.TODO(), client.ServiceClient(fakeServer), "9fe1d3").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, SecondResource, *actual) } @@ -61,7 +61,7 @@ func TestCreateResource(t *testing.T) { Name: "resource two", } - actual, err := resources.Create(context.TODO(), client.ServiceClient(), createOpts).Extract() + actual, err := resources.Create(context.TODO(), client.ServiceClient(fakeServer), createOpts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, SecondResource, *actual) } @@ -71,7 +71,7 @@ func TestDeleteResource(t *testing.T) { defer th.TeardownHTTP() HandleDeleteResourceSuccessfully(t) - res := resources.Delete(context.TODO(), client.ServiceClient(), "9fe1d3") + res := resources.Delete(context.TODO(), client.ServiceClient(fakeServer), "9fe1d3") th.AssertNoErr(t, res.Err) } @@ -84,7 +84,7 @@ func TestUpdateResource(t *testing.T) { Description: "Staging Resource", } - actual, err := resources.Update(context.TODO(), client.ServiceClient(), "9fe1d3", updateOpts).Extract() + actual, err := resources.Update(context.TODO(), client.ServiceClient(fakeServer), "9fe1d3", updateOpts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, SecondResourceUpdated, *actual) } diff --git a/docs/contributor-tutorial/step-02-issues.md b/docs/contributor-tutorial/step-02-issues.md index 3c1937612b..62d8fc7094 100644 --- a/docs/contributor-tutorial/step-02-issues.md +++ b/docs/contributor-tutorial/step-02-issues.md @@ -87,8 +87,9 @@ supports the OpenStack projects proper. ### Adding a Missing API Suite -Adding support to a missing suite of API calls will require more than one Pull -Request. However, you can use a single issue for all PRs. +Adding support to a missing suite of API calls can be done in a single Pull +Request or broken into multiple PRs. Either way, you can use a single issue +to track the work. Examples of issues which track the addition of a missing API suite are: diff --git a/docs/contributor-tutorial/step-04-acceptance-testing.md b/docs/contributor-tutorial/step-04-acceptance-testing.md index fe82717439..a6708ee5fb 100644 --- a/docs/contributor-tutorial/step-04-acceptance-testing.md +++ b/docs/contributor-tutorial/step-04-acceptance-testing.md @@ -17,9 +17,13 @@ And, to be frank, submitting untested code will inevitably cause someone else to have to spend time fixing it. If you don't have an OpenStack environment to test with, we have lots of -documentation [here](/acceptance) to help you build your own small OpenStack +documentation [here](/internal/acceptance) to help you build your own small OpenStack environment for testing. +If you add new API calls/actions e.g. also make sure to include them into the +respective [acceptance tests](/internal/acceptance/openstack) for the code to be +validated against all the supported OpenStack versions. + --- Once you've confirmed you are able to test your code, proceed to diff --git a/docs/contributor-tutorial/step-05-pull-requests.md b/docs/contributor-tutorial/step-05-pull-requests.md index cb340a92ca..0d074b00e4 100644 --- a/docs/contributor-tutorial/step-05-pull-requests.md +++ b/docs/contributor-tutorial/step-05-pull-requests.md @@ -37,9 +37,6 @@ If you're adding a single field, then a single Pull Request is also fine. See [#662](https://github.com/gophercloud/gophercloud/pull/662) as an example of this. -If you plan to add more than one missing field, you will need to open a Pull -Request for _each_ field. - ### Adding a Single API Call Single API calls can also be submitted as a single Pull Request. See @@ -49,14 +46,13 @@ this. ### Adding a Suite of API Calls If you're adding support for a "suite" of API calls (meaning: Create, Update, -Delete, Get), then you will need to create one Pull Request for _each_ call. +Delete, Get), you can group related operations into a single Pull Request. +Different API suites (e.g. servers and flavors) should be submitted as separate +PRs. -The following Pull Requests are good examples of how to do this: +The following Pull Request is a good example of adding an entire API suite: -* https://github.com/gophercloud/gophercloud/pull/584 -* https://github.com/gophercloud/gophercloud/pull/586 -* https://github.com/gophercloud/gophercloud/pull/587 -* https://github.com/gophercloud/gophercloud/pull/594 +* https://github.com/gophercloud/gophercloud/pull/3701 You can also use the provided [template](/docs/contributor-tutorial/.template) as it contains a lot of the repeated boiler plate code seen in each resource. @@ -66,9 +62,9 @@ the work and will require further rounds of review to fix. ### Adding an Entire OpenStack Project -To add an entire OpenStack project, you must break each set of API calls into -individual Pull Requests. Implementing an entire project can be thought of as -implementing multiple API suites. +To add an entire OpenStack project, break it into Pull Requests by API suite. +Implementing an entire project can be thought of as implementing multiple API +suites. An example of this can be seen from the Pull Requests referenced in [#723](https://github.com/gophercloud/gophercloud/issues/723). @@ -99,7 +95,10 @@ generate your own fixtures using the OpenStack environment you're Since unit tests are not run against an actual OpenStack environment, acceptance tests can arguably be more important. The acceptance tests that you include in your Pull Request should confirm that your implemented code works -as intended with an actual OpenStack environment. +as intended with an actual OpenStack environment. We run our functional jobs +against all supported Skip Level Upgrade Release Process (SLURP) releases as +well as `master`. This information is sourced from +[releases.openstack.org](https://releases.openstack.org/). ### Documentation @@ -125,14 +124,14 @@ it's self contained. Use the following `git` workflow to implement this method: ```shell -$ git checkout master +$ git checkout main $ git pull $ git checkout -b identityv3-regions-create $ (write your code) $ git add . $ git commit -m "Implementing Regions Create" -$ git checkout master +$ git checkout main $ git checkout -b identityv3-regions-update $ (write your code) $ git add . @@ -159,7 +158,7 @@ defined relationship. Use the following `git` workflow to implement this method: ```shell -$ git checkout master +$ git checkout main $ git pull $ git checkout -b identityv3-regions-create $ (write your code) diff --git a/endpoint_search.go b/endpoint_search.go index 2fbc3c97f1..e0a900f0f8 100644 --- a/endpoint_search.go +++ b/endpoint_search.go @@ -1,5 +1,10 @@ package gophercloud +import ( + "context" + "slices" +) + // Availability indicates to whom a specific service endpoint is accessible: // the internet at large, internal networks only, or only to administrators. // Different identity services use different terminology for these. Identity v2 @@ -22,6 +27,31 @@ const ( AvailabilityInternal Availability = "internal" ) +// ServiceTypeAliases contains a mapping of service types to any aliases, as +// defined by the OpenStack Service Types Authority. Only service types that +// we support are included. +var ServiceTypeAliases = map[string][]string{ + "application-container": {"container"}, + "baremetal": {"bare-metal"}, + "baremetal-introspection": {}, + "block-storage": {"block-store", "volume", "volumev2", "volumev3"}, + "compute": {}, + "container-infrastructure-management": {"container-infrastructure", "container-infra"}, + "database": {}, + "dns": {}, + "identity": {}, + "image": {}, + "key-manager": {}, + "load-balancer": {}, + "message": {"messaging"}, + "networking": {}, + "object-store": {}, + "orchestration": {}, + "placement": {}, + "shared-file-system": {"sharev2", "share"}, + "workflow": {"workflowv2"}, +} + // EndpointOpts specifies search criteria used by queries against an // OpenStack service catalog. The options must contain enough information to // unambiguously identify one, and only one, endpoint within the catalog. @@ -30,8 +60,9 @@ const ( // package, like "openstack.NewComputeV2()". type EndpointOpts struct { // Type [required] is the service type for the client (e.g., "compute", - // "object-store"). Generally, this will be supplied by the service client - // function, but a user-given value will be honored if provided. + // "object-store"), as defined by the OpenStack Service Types Authority. + // This will generally be supplied by the service client function, but a + // user-given value will be honored if provided. Type string // Name [optional] is the service name for the client (e.g., "nova") as it @@ -39,11 +70,23 @@ type EndpointOpts struct { // different Name, which is why both Type and Name are sometimes needed. Name string + // Aliases [optional] is the set of aliases of the service type (e.g. + // "volumev2"/"volumev3", "volume" and "block-store" for the + // "block-storage" service type), as defined by the OpenStack Service Types + // Authority. As with Type, this will generally be supplied by the service + // client function, but a user-given value will be honored if provided. + Aliases []string + // Region [required] is the geographic region in which the endpoint resides, // generally specifying which datacenter should house your resources. // Required only for services that span multiple regions. Region string + // Version [optional] is the major version of the service required. It it not + // a microversion. Use this to ensure the correct endpoint is selected when + // multiple API versions are available. + Version int + // Availability [optional] is the visibility of the endpoint to be returned. // Valid types include the constants AvailabilityPublic, AvailabilityInternal, // or AvailabilityAdmin from this package. @@ -60,7 +103,7 @@ It provides an implementation that locates a single endpoint from a service catalog for a specific ProviderClient based on user-provided EndpointOpts. The provider then uses it to discover related ServiceClients. */ -type EndpointLocator func(EndpointOpts) (string, error) +type EndpointLocator func(context.Context, EndpointOpts) (string, error) // ApplyDefaults is an internal method to be used by provider implementations. // @@ -73,4 +116,26 @@ func (eo *EndpointOpts) ApplyDefaults(t string) { if eo.Availability == "" { eo.Availability = AvailabilityPublic } + if len(eo.Aliases) == 0 { + if aliases, ok := ServiceTypeAliases[eo.Type]; ok { + // happy path: user requested a service type by its official name + eo.Aliases = slices.Clone(aliases) + } else { + // unhappy path: user requested a service type by its alias or an + // invalid/unsupported service type + // TODO(stephenfin): This should probably be an error in v3 + for t, aliases := range ServiceTypeAliases { + if slices.Contains(aliases, eo.Type) { + // we intentionally override the service type, even if it + // was explicitly requested by the user + eo.Type = t + eo.Aliases = slices.Clone(aliases) + } + } + } + } +} + +func (eo *EndpointOpts) Types() []string { + return append([]string{eo.Type}, eo.Aliases...) } diff --git a/go.mod b/go.mod index d4ac50a838..e6cd1fb0a5 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module github.com/gophercloud/gophercloud/v2 -go 1.22 +go 1.25.0 require ( - golang.org/x/crypto v0.25.0 - gopkg.in/yaml.v2 v2.4.0 + go.yaml.in/yaml/v3 v3.0.4 + golang.org/x/crypto v0.53.0 ) -require golang.org/x/sys v0.22.0 // indirect +require golang.org/x/sys v0.46.0 // indirect diff --git a/go.sum b/go.sum index 1e1f48a722..0616a587ff 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,10 @@ -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/internal/acceptance/README.md b/internal/acceptance/README.md index cbbbac7f39..2010e368dc 100644 --- a/internal/acceptance/README.md +++ b/internal/acceptance/README.md @@ -18,16 +18,8 @@ environment. Additionally, you may incur bandwidth and service charges for the resources used, as mentioned in the note above. Therefore, it is usually best to first practice running acceptance tests in -an isolated test environment. Two options to easily create a testing -environment are [DevStack](https://docs.openstack.org/devstack/latest/) -and [PackStack](https://www.rdoproject.org/install/packstack/). - -The following blog posts detail how to create reusable PackStack environments. -These posts were written with Gophercloud in mind: - -* http://terrarum.net/blog/building-openstack-environments.html -* http://terrarum.net/blog/building-openstack-environments-2.html -* http://terrarum.net/blog/building-openstack-environments-3.html +an isolated test environment. The best option to easily create a testing +environment is [DevStack](https://docs.openstack.org/devstack/latest/). ### Step 2. Set environment variables @@ -91,7 +83,13 @@ to set them manually. From the root directory, run: ``` -./script/acceptancetest +make acceptance +``` + +You can also run tests for a specific service: + +``` +make acceptance-compute ``` Alternatively, add the following to your `.bashrc`: diff --git a/internal/acceptance/clients/clients.go b/internal/acceptance/clients/clients.go index 21be9dbea2..a2d0b0a69b 100644 --- a/internal/acceptance/clients/clients.go +++ b/internal/acceptance/clients/clients.go @@ -107,16 +107,12 @@ func AcceptanceTestChoicesFromEnv() (*AcceptanceTestChoices, error) { notDistinct = "OS_FLAVOR_ID and OS_FLAVOR_ID_RESIZE must be distinct." } - if len(missing) > 0 || notDistinct != "" { - text := "You're missing some important setup:\n" - if len(missing) > 0 { - text += " * These environment variables must be provided: " + strings.Join(missing, ", ") + "\n" - } - if notDistinct != "" { - text += " * " + notDistinct + "\n" - } + if len(missing) > 0 { + return nil, fmt.Errorf("you're missing some important setup:\n * These environment variables must be provided: %s", strings.Join(missing, ", ")) + } - return nil, fmt.Errorf(text) + if notDistinct != "" { + return nil, fmt.Errorf("you're missing some important setup:\n * %s", notDistinct) } return &AcceptanceTestChoices{ @@ -151,7 +147,7 @@ func NewBlockStorageV1Client() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewBlockStorageV1(client, gophercloud.EndpointOpts{ + return openstack.NewBlockStorageV1(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } @@ -172,7 +168,7 @@ func NewBlockStorageV2Client() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewBlockStorageV2(client, gophercloud.EndpointOpts{ + return openstack.NewBlockStorageV2(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } @@ -193,7 +189,7 @@ func NewBlockStorageV3Client() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewBlockStorageV3(client, gophercloud.EndpointOpts{ + return openstack.NewBlockStorageV3(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } @@ -252,7 +248,7 @@ func NewComputeV2Client() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewComputeV2(client, gophercloud.EndpointOpts{ + return openstack.NewComputeV2(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } @@ -273,7 +269,7 @@ func NewBareMetalV1Client() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewBareMetalV1(client, gophercloud.EndpointOpts{ + return openstack.NewBareMetalV1(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } @@ -314,7 +310,7 @@ func NewBareMetalIntrospectionV1Client() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewBareMetalIntrospectionV1(client, gophercloud.EndpointOpts{ + return openstack.NewBareMetalIntrospectionV1(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } @@ -335,7 +331,7 @@ func NewDBV1Client() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewDBV1(client, gophercloud.EndpointOpts{ + return openstack.NewDBV1(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } @@ -356,7 +352,7 @@ func NewDNSV2Client() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewDNSV2(client, gophercloud.EndpointOpts{ + return openstack.NewDNSV2(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } @@ -377,7 +373,7 @@ func NewIdentityV2Client() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewIdentityV2(client, gophercloud.EndpointOpts{ + return openstack.NewIdentityV2(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } @@ -398,7 +394,7 @@ func NewIdentityV2AdminClient() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewIdentityV2(client, gophercloud.EndpointOpts{ + return openstack.NewIdentityV2(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), Availability: gophercloud.AvailabilityAdmin, }) @@ -420,7 +416,7 @@ func NewIdentityV2UnauthenticatedClient() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewIdentityV2(client, gophercloud.EndpointOpts{}) + return openstack.NewIdentityV2(context.TODO(), client, gophercloud.EndpointOpts{}) } // NewIdentityV3Client returns a *ServiceClient for making calls @@ -439,7 +435,7 @@ func NewIdentityV3Client() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewIdentityV3(client, gophercloud.EndpointOpts{ + return openstack.NewIdentityV3(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } @@ -460,7 +456,7 @@ func NewIdentityV3UnauthenticatedClient() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewIdentityV3(client, gophercloud.EndpointOpts{}) + return openstack.NewIdentityV3(context.TODO(), client, gophercloud.EndpointOpts{}) } // NewImageV2Client returns a *ServiceClient for making calls to the @@ -479,7 +475,7 @@ func NewImageV2Client() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewImageV2(client, gophercloud.EndpointOpts{ + return openstack.NewImageV2(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } @@ -500,7 +496,7 @@ func NewNetworkV2Client() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewNetworkV2(client, gophercloud.EndpointOpts{ + return openstack.NewNetworkV2(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } @@ -521,7 +517,7 @@ func NewObjectStorageV1Client() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewObjectStorageV1(client, gophercloud.EndpointOpts{ + return openstack.NewObjectStorageV1(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } @@ -542,7 +538,7 @@ func NewSharedFileSystemV2Client() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewSharedFileSystemV2(client, gophercloud.EndpointOpts{ + return openstack.NewSharedFileSystemV2(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } @@ -563,7 +559,7 @@ func NewLoadBalancerV2Client() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewLoadBalancerV2(client, gophercloud.EndpointOpts{ + return openstack.NewLoadBalancerV2(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } @@ -584,7 +580,28 @@ func NewMessagingV2Client(clientID string) (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewMessagingV2(client, clientID, gophercloud.EndpointOpts{ + return openstack.NewMessagingV2(context.TODO(), client, clientID, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) +} + +// NewMetricV1Client returns a *ServiceClient for making calls +// to the OpenStack Metric v1 API. An error will be returned +// if authentication or client creation was not possible. +func NewMetricV1Client() (*gophercloud.ServiceClient, error) { + ao, err := openstack.AuthOptionsFromEnv() + if err != nil { + return nil, err + } + + client, err := openstack.AuthenticatedClient(context.TODO(), ao) + if err != nil { + return nil, err + } + + client = configureDebug(client) + + return openstack.NewMetricV1(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } @@ -605,7 +622,7 @@ func NewContainerV1Client() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewContainerV1(client, gophercloud.EndpointOpts{ + return openstack.NewContainerV1(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } @@ -626,7 +643,7 @@ func NewKeyManagerV1Client() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewKeyManagerV1(client, gophercloud.EndpointOpts{ + return openstack.NewKeyManagerV1(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } @@ -661,7 +678,7 @@ func NewContainerInfraV1Client() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewContainerInfraV1(client, gophercloud.EndpointOpts{ + return openstack.NewContainerInfraV1(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } @@ -682,7 +699,7 @@ func NewWorkflowV2Client() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewWorkflowV2(client, gophercloud.EndpointOpts{ + return openstack.NewWorkflowV2(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } @@ -703,7 +720,7 @@ func NewOrchestrationV1Client() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewOrchestrationV1(client, gophercloud.EndpointOpts{ + return openstack.NewOrchestrationV1(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } @@ -724,7 +741,7 @@ func NewPlacementV1Client() (*gophercloud.ServiceClient, error) { client = configureDebug(client) - return openstack.NewPlacementV1(client, gophercloud.EndpointOpts{ + return openstack.NewPlacementV1(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } diff --git a/internal/acceptance/clients/conditions.go b/internal/acceptance/clients/conditions.go index 619d31a94e..f5eaa578b3 100644 --- a/internal/acceptance/clients/conditions.go +++ b/internal/acceptance/clients/conditions.go @@ -14,14 +14,6 @@ func RequiredSystemScope(t *testing.T) { } } -// RequireManilaReplicas will restrict a test to only be run with enabled -// manila replicas. -func RequireManilaReplicas(t *testing.T) { - if os.Getenv("OS_MANILA_REPLICAS") != "true" { - t.Skip("manila replicas must be enabled to run this test") - } -} - // RequireAdmin will restrict a test to only be run by admin users. func RequireAdmin(t *testing.T) { if os.Getenv("OS_USERNAME") != "admin" { @@ -36,38 +28,6 @@ func RequireNonAdmin(t *testing.T) { } } -// RequirePortForwarding will restrict a test to only be run in environments -// that support port forwarding -func RequirePortForwarding(t *testing.T) { - if os.Getenv("OS_PORTFORWARDING_ENVIRONMENT") == "" { - t.Skip("this test requires support for port forwarding") - } -} - -// RequireGuestAgent will restrict a test to only be run in -// environments that support the QEMU guest agent. -func RequireGuestAgent(t *testing.T) { - if os.Getenv("OS_GUEST_AGENT") == "" { - t.Skip("this test requires support for qemu guest agent and to set OS_GUEST_AGENT to 1") - } -} - -// RequireIdentityV2 will restrict a test to only be run in -// environments that support the Identity V2 API. -func RequireIdentityV2(t *testing.T) { - if os.Getenv("OS_IDENTITY_API_VERSION") != "2.0" { - t.Skip("this test requires support for the identity v2 API") - } -} - -// RequireLiveMigration will restrict a test to only be run in -// environments that support live migration. -func RequireLiveMigration(t *testing.T) { - if os.Getenv("OS_LIVE_MIGRATE") == "" { - t.Skip("this test requires support for live migration and to set OS_LIVE_MIGRATE to 1") - } -} - // RequireLong will ensure long-running tests can run. func RequireLong(t *testing.T) { if testing.Short() { @@ -75,38 +35,6 @@ func RequireLong(t *testing.T) { } } -// RequireNovaNetwork will restrict a test to only be run in -// environments that support nova-network. -func RequireNovaNetwork(t *testing.T) { - if os.Getenv("OS_NOVANET") == "" { - t.Skip("this test requires nova-network and to set OS_NOVANET to 1") - } -} - -// RequireCinderNoAuth will restrict a test to be only run in environments that -// have Cinder using noauth. -func RequireCinderNoAuth(t *testing.T) { - if os.Getenv("CINDER_ENDPOINT") == "" || os.Getenv("OS_USERNAME") == "" { - t.Skip("this test requires Cinder using noauth, set OS_USERNAME and CINDER_ENDPOINT") - } -} - -// RequireIronicNoAuth will restrict a test to be only run in environments that -// have Ironic using noauth. -func RequireIronicNoAuth(t *testing.T) { - if os.Getenv("IRONIC_ENDPOINT") == "" || os.Getenv("OS_USERNAME") == "" { - t.Skip("this test requires IRONIC using noauth, set OS_USERNAME and IRONIC_ENDPOINT") - } -} - -// RequireIronicHTTPBasic will restrict a test to be only run in environments -// that have Ironic using http_basic. -func RequireIronicHTTPBasic(t *testing.T) { - if os.Getenv("IRONIC_ENDPOINT") == "" || os.Getenv("OS_USERNAME") == "" || os.Getenv("OS_PASSWORD") == "" { - t.Skip("this test requires Ironic using http_basic, set OS_USERNAME, OS_PASSWORD and IRONIC_ENDPOINT") - } -} - func getReleaseFromEnv(t *testing.T) string { current := strings.TrimPrefix(os.Getenv("OS_BRANCH"), "stable/") if current == "" { @@ -115,17 +43,17 @@ func getReleaseFromEnv(t *testing.T) string { return current } -// SkipRelease will have the test be skipped on a certain -// release. Releases are named such as 'stable/mitaka', master, etc. +// SkipRelease will have the test be skipped on a certain release. +// Releases are named such as 'stable/dalmatian', master, etc. func SkipRelease(t *testing.T, release string) { current := getReleaseFromEnv(t) - if current == release { + if current == strings.TrimPrefix(release, "stable/") { t.Skipf("this is not supported in %s", release) } } -// SkipReleasesBelow will have the test be skipped on releases below a certain -// one. Releases are named such as 'stable/mitaka', master, etc. +// SkipReleasesBelow will have the test be skipped on releases below a certain one. +// Releases are named such as 'stable/dalmatian', master, etc. func SkipReleasesBelow(t *testing.T, release string) { current := getReleaseFromEnv(t) @@ -134,9 +62,9 @@ func SkipReleasesBelow(t *testing.T, release string) { } } -// SkipReleasesAbove will have the test be skipped on releases above a certain -// one. The test is always skipped on master release. Releases are named such -// as 'stable/mitaka', master, etc. +// SkipReleasesAbove will have the test be skipped on releases above a certain one. +// The test is always skipped on master release. +// Releases are named such as 'stable/dalmatian', master, etc. func SkipReleasesAbove(t *testing.T, release string) { current := getReleaseFromEnv(t) @@ -150,9 +78,9 @@ func isReleaseNumeral(release string) bool { return err == nil } -// IsCurrentAbove will return true on releases above a certain -// one. The result is always true on master release. Releases are named such -// as 'stable/mitaka', master, etc. +// IsCurrentAbove will return true on releases above a certain one. +// The result is always true on master release. +// Releases are named such as 'stable/dalmatian', master, etc. func IsCurrentAbove(t *testing.T, release string) bool { current := getReleaseFromEnv(t) release = strings.TrimPrefix(release, "stable/") @@ -166,7 +94,7 @@ func IsCurrentAbove(t *testing.T, release string) bool { if isReleaseNumeral(current) && !isReleaseNumeral(release) { return true } - if current > release && !(!isReleaseNumeral(current) && isReleaseNumeral(release)) { + if current > release && (isReleaseNumeral(current) || !isReleaseNumeral(release)) { return true } } @@ -174,8 +102,9 @@ func IsCurrentAbove(t *testing.T, release string) bool { return false } -// IsCurrentBelow will return true on releases below a certain -// one. Releases are named such as 'stable/mitaka', master, etc. +// IsCurrentBelow will return true on releases below a certain one. +// The result is always false on master release. +// Releases are named such as 'stable/dalmatian', master, etc. func IsCurrentBelow(t *testing.T, release string) bool { current := getReleaseFromEnv(t) release = strings.TrimPrefix(release, "stable/") @@ -189,7 +118,7 @@ func IsCurrentBelow(t *testing.T, release string) bool { if isReleaseNumeral(release) && !isReleaseNumeral(current) { return true } - if release > current && !(!isReleaseNumeral(release) && isReleaseNumeral(current)) { + if release > current && (isReleaseNumeral(release) || !isReleaseNumeral(current)) { return true } } diff --git a/internal/acceptance/openstack/baremetal/httpbasic/allocations_test.go b/internal/acceptance/openstack/baremetal/httpbasic/allocations_test.go index 932c067ad0..09e2ff12a8 100644 --- a/internal/acceptance/openstack/baremetal/httpbasic/allocations_test.go +++ b/internal/acceptance/openstack/baremetal/httpbasic/allocations_test.go @@ -15,7 +15,7 @@ import ( func TestAllocationsCreateDestroy(t *testing.T) { clients.RequireLong(t) - clients.RequireIronicHTTPBasic(t) + RequireIronicHTTPBasic(t) client, err := clients.NewBareMetalV1HTTPBasic() th.AssertNoErr(t, err) @@ -43,5 +43,5 @@ func TestAllocationsCreateDestroy(t *testing.T) { return false, nil }) th.AssertNoErr(t, err) - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } diff --git a/internal/acceptance/openstack/baremetal/httpbasic/conditions.go b/internal/acceptance/openstack/baremetal/httpbasic/conditions.go new file mode 100644 index 0000000000..15d1c9f724 --- /dev/null +++ b/internal/acceptance/openstack/baremetal/httpbasic/conditions.go @@ -0,0 +1,14 @@ +package httpbasic + +import ( + "os" + "testing" +) + +// RequireIronicHTTPBasic will restrict a test to be only run in environments +// that have Ironic using http_basic. +func RequireIronicHTTPBasic(t *testing.T) { + if os.Getenv("IRONIC_ENDPOINT") == "" || os.Getenv("OS_USERNAME") == "" || os.Getenv("OS_PASSWORD") == "" { + t.Skip("this test requires Ironic using http_basic, set OS_USERNAME, OS_PASSWORD and IRONIC_ENDPOINT") + } +} diff --git a/internal/acceptance/openstack/baremetal/httpbasic/nodes_test.go b/internal/acceptance/openstack/baremetal/httpbasic/nodes_test.go index e35ae1e25a..af6180d24f 100644 --- a/internal/acceptance/openstack/baremetal/httpbasic/nodes_test.go +++ b/internal/acceptance/openstack/baremetal/httpbasic/nodes_test.go @@ -16,7 +16,7 @@ import ( func TestNodesCreateDestroy(t *testing.T) { clients.RequireLong(t) - clients.RequireIronicHTTPBasic(t) + RequireIronicHTTPBasic(t) client, err := clients.NewBareMetalV1HTTPBasic() th.AssertNoErr(t, err) @@ -44,12 +44,12 @@ func TestNodesCreateDestroy(t *testing.T) { }) th.AssertNoErr(t, err) - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestNodesUpdate(t *testing.T) { clients.RequireLong(t) - clients.RequireIronicHTTPBasic(t) + RequireIronicHTTPBasic(t) client, err := clients.NewBareMetalV1HTTPBasic() th.AssertNoErr(t, err) @@ -68,13 +68,13 @@ func TestNodesUpdate(t *testing.T) { }).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, updated.Maintenance, true) + th.AssertTrue(t, updated.Maintenance) } func TestNodesRAIDConfig(t *testing.T) { clients.SkipReleasesBelow(t, "stable/ussuri") clients.RequireLong(t) - clients.RequireIronicHTTPBasic(t) + RequireIronicHTTPBasic(t) client, err := clients.NewBareMetalV1HTTPBasic() th.AssertNoErr(t, err) @@ -104,7 +104,7 @@ func TestNodesRAIDConfig(t *testing.T) { func TestNodesFirmwareInterface(t *testing.T) { clients.SkipReleasesBelow(t, "stable/2023.2") clients.RequireLong(t) - clients.RequireIronicHTTPBasic(t) + RequireIronicHTTPBasic(t) client, err := clients.NewBareMetalV1HTTPBasic() th.AssertNoErr(t, err) @@ -114,9 +114,9 @@ func TestNodesFirmwareInterface(t *testing.T) { th.AssertNoErr(t, err) defer v1.DeleteNode(t, client, node) - th.AssertEquals(t, node.FirmwareInterface, "no-firmware") + th.AssertEquals(t, "no-firmware", node.FirmwareInterface) nodeFirmwareCmps, err := nodes.ListFirmware(context.TODO(), client, node.UUID).Extract() th.AssertNoErr(t, err) - th.AssertDeepEquals(t, nodeFirmwareCmps, []nodes.FirmwareComponent{}) + th.AssertDeepEquals(t, []nodes.FirmwareComponent{}, nodeFirmwareCmps) } diff --git a/internal/acceptance/openstack/baremetal/httpbasic/portgroups_test.go b/internal/acceptance/openstack/baremetal/httpbasic/portgroups_test.go new file mode 100644 index 0000000000..8db6da0075 --- /dev/null +++ b/internal/acceptance/openstack/baremetal/httpbasic/portgroups_test.go @@ -0,0 +1,52 @@ +//go:build acceptance || baremetal || portgroups + +package httpbasic + +import ( + "context" + "testing" + + "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" + v1 "github.com/gophercloud/gophercloud/v2/internal/acceptance/openstack/baremetal/v1" + "github.com/gophercloud/gophercloud/v2/openstack/baremetal/v1/portgroups" + "github.com/gophercloud/gophercloud/v2/pagination" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func TestPortGroupsCreateDestroy(t *testing.T) { + clients.RequireLong(t) + RequireIronicHTTPBasic(t) + + client, err := clients.NewBareMetalV1HTTPBasic() + th.AssertNoErr(t, err) + + // NOTE(sharpz7) - increased due to create fake node requiring it. + client.Microversion = "1.50" + + node, err := v1.CreateFakeNode(t, client) + th.AssertNoErr(t, err) + defer v1.DeleteNode(t, client, node) + + portgroup, err := v1.CreatePortGroup(t, client, node) + th.AssertNoErr(t, err) + defer v1.DeletePortGroup(t, client, portgroup) + + // Verify the portgroup exists by listing + err = portgroups.List(client, portgroups.ListOpts{ + Node: node.UUID, + }).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + pg, err := portgroups.ExtractPortGroups(page) + if err != nil { + return false, err + } + + for _, p := range pg { + if p.UUID == portgroup.UUID { + return true, nil + } + } + + return false, nil + }) + th.AssertNoErr(t, err) +} diff --git a/internal/acceptance/openstack/baremetal/httpbasic/ports_test.go b/internal/acceptance/openstack/baremetal/httpbasic/ports_test.go index 9912c49322..836b5a98e9 100644 --- a/internal/acceptance/openstack/baremetal/httpbasic/ports_test.go +++ b/internal/acceptance/openstack/baremetal/httpbasic/ports_test.go @@ -16,7 +16,7 @@ import ( func TestPortsCreateDestroy(t *testing.T) { clients.RequireLong(t) - clients.RequireIronicHTTPBasic(t) + RequireIronicHTTPBasic(t) client, err := clients.NewBareMetalV1HTTPBasic() th.AssertNoErr(t, err) @@ -47,12 +47,12 @@ func TestPortsCreateDestroy(t *testing.T) { }) th.AssertNoErr(t, err) - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestPortsUpdate(t *testing.T) { clients.RequireLong(t) - clients.RequireIronicHTTPBasic(t) + RequireIronicHTTPBasic(t) client, err := clients.NewBareMetalV1HTTPBasic() th.AssertNoErr(t, err) @@ -74,5 +74,5 @@ func TestPortsUpdate(t *testing.T) { }).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, updated.Address, "aa:bb:cc:dd:ee:ff") + th.AssertEquals(t, "aa:bb:cc:dd:ee:ff", updated.Address) } diff --git a/internal/acceptance/openstack/baremetal/noauth/allocations_test.go b/internal/acceptance/openstack/baremetal/noauth/allocations_test.go index e6cb33ac3d..5cc6eff65f 100644 --- a/internal/acceptance/openstack/baremetal/noauth/allocations_test.go +++ b/internal/acceptance/openstack/baremetal/noauth/allocations_test.go @@ -15,7 +15,7 @@ import ( func TestAllocationsCreateDestroy(t *testing.T) { clients.RequireLong(t) - clients.RequireIronicNoAuth(t) + RequireIronicNoAuth(t) client, err := clients.NewBareMetalV1NoAuthClient() th.AssertNoErr(t, err) @@ -43,5 +43,5 @@ func TestAllocationsCreateDestroy(t *testing.T) { return false, nil }) th.AssertNoErr(t, err) - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } diff --git a/internal/acceptance/openstack/baremetal/noauth/conditions.go b/internal/acceptance/openstack/baremetal/noauth/conditions.go new file mode 100644 index 0000000000..762f141f52 --- /dev/null +++ b/internal/acceptance/openstack/baremetal/noauth/conditions.go @@ -0,0 +1,15 @@ +package noauth + +import ( + "os" + "testing" +) + +// RequireIronicNoAuth will restrict a test to be only run in environments that + +// have Ironic using noauth. +func RequireIronicNoAuth(t *testing.T) { + if os.Getenv("IRONIC_ENDPOINT") == "" || os.Getenv("OS_USERNAME") == "" { + t.Skip("this test requires IRONIC using noauth, set OS_USERNAME and IRONIC_ENDPOINT") + } +} diff --git a/internal/acceptance/openstack/baremetal/noauth/nodes_test.go b/internal/acceptance/openstack/baremetal/noauth/nodes_test.go index bd05203b2e..82aca0a967 100644 --- a/internal/acceptance/openstack/baremetal/noauth/nodes_test.go +++ b/internal/acceptance/openstack/baremetal/noauth/nodes_test.go @@ -16,7 +16,7 @@ import ( func TestNodesCreateDestroy(t *testing.T) { clients.RequireLong(t) - clients.RequireIronicNoAuth(t) + RequireIronicNoAuth(t) client, err := clients.NewBareMetalV1NoAuthClient() th.AssertNoErr(t, err) @@ -44,12 +44,12 @@ func TestNodesCreateDestroy(t *testing.T) { }) th.AssertNoErr(t, err) - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestNodesUpdate(t *testing.T) { clients.RequireLong(t) - clients.RequireIronicNoAuth(t) + RequireIronicNoAuth(t) client, err := clients.NewBareMetalV1NoAuthClient() th.AssertNoErr(t, err) @@ -68,13 +68,13 @@ func TestNodesUpdate(t *testing.T) { }).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, updated.Maintenance, true) + th.AssertTrue(t, updated.Maintenance) } func TestNodesRAIDConfig(t *testing.T) { clients.SkipReleasesBelow(t, "stable/ussuri") clients.RequireLong(t) - clients.RequireIronicNoAuth(t) + RequireIronicNoAuth(t) client, err := clients.NewBareMetalV1NoAuthClient() th.AssertNoErr(t, err) diff --git a/internal/acceptance/openstack/baremetal/noauth/portgroups_test.go b/internal/acceptance/openstack/baremetal/noauth/portgroups_test.go new file mode 100644 index 0000000000..0ea05ed518 --- /dev/null +++ b/internal/acceptance/openstack/baremetal/noauth/portgroups_test.go @@ -0,0 +1,52 @@ +//go:build acceptance || baremetal || ports + +package noauth + +import ( + "context" + "testing" + + "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" + v1 "github.com/gophercloud/gophercloud/v2/internal/acceptance/openstack/baremetal/v1" + "github.com/gophercloud/gophercloud/v2/openstack/baremetal/v1/portgroups" + "github.com/gophercloud/gophercloud/v2/pagination" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func TestPortGroupsCreateDestroy(t *testing.T) { + clients.RequireLong(t) + RequireIronicNoAuth(t) + + client, err := clients.NewBareMetalV1NoAuthClient() + th.AssertNoErr(t, err) + + // NOTE(sharpz7) - increased due to create fake node requiring it. + client.Microversion = "1.50" + + node, err := v1.CreateFakeNode(t, client) + th.AssertNoErr(t, err) + defer v1.DeleteNode(t, client, node) + + portgroup, err := v1.CreatePortGroup(t, client, node) + th.AssertNoErr(t, err) + defer v1.DeletePortGroup(t, client, portgroup) + + // Verify the portgroup exists by listing + err = portgroups.List(client, portgroups.ListOpts{ + Node: node.UUID, + }).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + pg, err := portgroups.ExtractPortGroups(page) + if err != nil { + return false, err + } + + for _, p := range pg { + if p.UUID == portgroup.UUID { + return true, nil + } + } + + return false, nil + }) + th.AssertNoErr(t, err) +} diff --git a/internal/acceptance/openstack/baremetal/noauth/ports_test.go b/internal/acceptance/openstack/baremetal/noauth/ports_test.go index 980b2dec32..97fc5c52f8 100644 --- a/internal/acceptance/openstack/baremetal/noauth/ports_test.go +++ b/internal/acceptance/openstack/baremetal/noauth/ports_test.go @@ -16,7 +16,7 @@ import ( func TestPortsCreateDestroy(t *testing.T) { clients.RequireLong(t) - clients.RequireIronicNoAuth(t) + RequireIronicNoAuth(t) client, err := clients.NewBareMetalV1NoAuthClient() th.AssertNoErr(t, err) @@ -47,12 +47,12 @@ func TestPortsCreateDestroy(t *testing.T) { }) th.AssertNoErr(t, err) - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestPortsUpdate(t *testing.T) { clients.RequireLong(t) - clients.RequireIronicNoAuth(t) + RequireIronicNoAuth(t) client, err := clients.NewBareMetalV1NoAuthClient() th.AssertNoErr(t, err) @@ -74,5 +74,5 @@ func TestPortsUpdate(t *testing.T) { }).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, updated.Address, "aa:bb:cc:dd:ee:ff") + th.AssertEquals(t, "aa:bb:cc:dd:ee:ff", updated.Address) } diff --git a/internal/acceptance/openstack/baremetal/v1/allocations_test.go b/internal/acceptance/openstack/baremetal/v1/allocations_test.go index 6c2734f875..74405f7384 100644 --- a/internal/acceptance/openstack/baremetal/v1/allocations_test.go +++ b/internal/acceptance/openstack/baremetal/v1/allocations_test.go @@ -41,5 +41,5 @@ func TestAllocationsCreateDestroy(t *testing.T) { return false, nil }) th.AssertNoErr(t, err) - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } diff --git a/internal/acceptance/openstack/baremetal/v1/baremetal.go b/internal/acceptance/openstack/baremetal/v1/baremetal.go index a47f942dad..27dffd7ea3 100644 --- a/internal/acceptance/openstack/baremetal/v1/baremetal.go +++ b/internal/acceptance/openstack/baremetal/v1/baremetal.go @@ -9,7 +9,9 @@ import ( "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" "github.com/gophercloud/gophercloud/v2/openstack/baremetal/v1/allocations" "github.com/gophercloud/gophercloud/v2/openstack/baremetal/v1/nodes" + "github.com/gophercloud/gophercloud/v2/openstack/baremetal/v1/portgroups" "github.com/gophercloud/gophercloud/v2/openstack/baremetal/v1/ports" + th "github.com/gophercloud/gophercloud/v2/testhelper" ) // CreateNode creates a basic node with a randomly generated name. @@ -31,8 +33,16 @@ func CreateNode(t *testing.T, client *gophercloud.ServiceClient) (*nodes.Node, e "ipmi_password": "admin", }, }).Extract() + if err != nil { + return node, err + } + + th.AssertEquals(t, name, node.Name) + th.AssertEquals(t, "ipmi", node.Driver) + th.AssertEquals(t, "ipxe", node.BootInterface) + th.AssertEquals(t, "agent", node.RAIDInterface) - return node, err + return node, nil } // DeleteNode deletes a bare metal node via its UUID. @@ -62,8 +72,14 @@ func CreateAllocation(t *testing.T, client *gophercloud.ServiceClient) (*allocat Name: name, ResourceClass: "baremetal", }).Extract() + if err != nil { + return allocation, err + } - return allocation, err + th.AssertEquals(t, name, allocation.Name) + th.AssertEquals(t, "baremetal", allocation.ResourceClass) + + return allocation, nil } // DeleteAllocation deletes a bare metal allocation via its UUID. @@ -76,6 +92,35 @@ func DeleteAllocation(t *testing.T, client *gophercloud.ServiceClient, allocatio t.Logf("Deleted allocation: %s", allocation.UUID) } +// CreatePortGroup creates an allocation +func CreatePortGroup(t *testing.T, client *gophercloud.ServiceClient, node *nodes.Node) (*portgroups.PortGroup, error) { + name := tools.RandomString("ACPTTEST", 16) + t.Logf("Attempting to create bare metal allocation: %s", name) + + allocation, err := portgroups.Create(context.TODO(), client, portgroups.CreateOpts{ + Name: name, + NodeUUID: node.UUID, + }).Extract() + if err != nil { + return allocation, err + } + + th.AssertEquals(t, name, allocation.Name) + th.AssertEquals(t, node.UUID, allocation.NodeUUID) + + return allocation, nil +} + +// DeletePortGroup deletes a bare metal portgroup via its UUID. +func DeletePortGroup(t *testing.T, client *gophercloud.ServiceClient, portgroup *portgroups.PortGroup) { + err := portgroups.Delete(context.TODO(), client, portgroup.UUID).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete portgroup %s: %s", portgroup.UUID, err) + } + + t.Logf("Deleted portgroup: %s", portgroup.UUID) +} + // CreateFakeNode creates a node with fake-hardware. func CreateFakeNode(t *testing.T, client *gophercloud.ServiceClient) (*nodes.Node, error) { name := tools.RandomString("ACPTTEST", 16) @@ -95,8 +140,15 @@ func CreateFakeNode(t *testing.T, client *gophercloud.ServiceClient) (*nodes.Nod "ipmi_password": "admin", }, }).Extract() + if err != nil { + return node, err + } + + th.AssertEquals(t, name, node.Name) + th.AssertEquals(t, "fake-hardware", node.Driver) + th.AssertEquals(t, "fake", node.BootInterface) - return node, err + return node, nil } func ChangeProvisionStateAndWait(ctx context.Context, client *gophercloud.ServiceClient, node *nodes.Node, @@ -172,8 +224,14 @@ func CreatePort(t *testing.T, client *gophercloud.ServiceClient, node *nodes.Nod Address: mac, PXEEnabled: &iTrue, }).Extract() + if err != nil { + return port, err + } + + th.AssertEquals(t, node.UUID, port.NodeUUID) + th.AssertEquals(t, mac, port.Address) - return port, err + return port, nil } // DeletePort - deletes a port via its UUID diff --git a/internal/acceptance/openstack/baremetal/v1/nodes_test.go b/internal/acceptance/openstack/baremetal/v1/nodes_test.go index 669ea69cdc..a578c65490 100644 --- a/internal/acceptance/openstack/baremetal/v1/nodes_test.go +++ b/internal/acceptance/openstack/baremetal/v1/nodes_test.go @@ -42,7 +42,7 @@ func TestNodesCreateDestroy(t *testing.T) { return false, nil }) th.AssertNoErr(t, err) - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) th.AssertEquals(t, node.ProvisionState, string(nodes.Enroll)) @@ -110,7 +110,7 @@ func TestNodesUpdate(t *testing.T) { }).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, updated.Maintenance, true) + th.AssertTrue(t, updated.Maintenance) } func TestNodesMaintenance(t *testing.T) { @@ -132,8 +132,8 @@ func TestNodesMaintenance(t *testing.T) { updated, err := nodes.Get(context.TODO(), client, node.UUID).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, updated.Maintenance, true) - th.AssertEquals(t, updated.MaintenanceReason, "I'm tired") + th.AssertTrue(t, updated.Maintenance) + th.AssertEquals(t, "I'm tired", updated.MaintenanceReason) err = nodes.UnsetMaintenance(context.TODO(), client, node.UUID).ExtractErr() th.AssertNoErr(t, err) @@ -141,8 +141,8 @@ func TestNodesMaintenance(t *testing.T) { updated, err = nodes.Get(context.TODO(), client, node.UUID).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, updated.Maintenance, false) - th.AssertEquals(t, updated.MaintenanceReason, "") + th.AssertFalse(t, updated.Maintenance) + th.AssertEquals(t, "", updated.MaintenanceReason) } func TestNodesRAIDConfig(t *testing.T) { @@ -206,20 +206,20 @@ func TestNodesFirmwareInterface(t *testing.T) { th.AssertNoErr(t, err) defer DeleteNode(t, client, node) - th.AssertEquals(t, node.FirmwareInterface, "no-firmware") + th.AssertEquals(t, "no-firmware", node.FirmwareInterface) nodeFirmwareCmps, err := nodes.ListFirmware(context.TODO(), client, node.UUID).Extract() th.AssertNoErr(t, err) - th.AssertDeepEquals(t, nodeFirmwareCmps, []nodes.FirmwareComponent{}) + th.AssertDeepEquals(t, []nodes.FirmwareComponent{}, nodeFirmwareCmps) } func TestNodesVirtualMedia(t *testing.T) { - clients.SkipReleasesBelow(t, "master") // 2024.1 + clients.SkipReleasesBelow(t, "stable/2024.2") clients.RequireLong(t) client, err := clients.NewBareMetalV1Client() th.AssertNoErr(t, err) - client.Microversion = "1.89" + client.Microversion = "1.93" node, err := CreateNode(t, client) th.AssertNoErr(t, err) @@ -241,6 +241,15 @@ func TestNodesVirtualMedia(t *testing.T) { err = nodes.DetachVirtualMedia(context.TODO(), client, node.UUID, nodes.DetachVirtualMediaOpts{}).ExtractErr() th.AssertNoErr(t, err) + + err = nodes.GetVirtualMedia(context.TODO(), client, node.UUID).Err + // Since Virtual Media GET api call is synchronous, we get a HTTP 400 + // response as CreateNode has ipmi driver hardcoded, but the api is + // only supported by the redfish driver + // (TODO: hroyrh) fix this once redfish driver is used in the tests + if node.Driver == "redfish" { + th.AssertNoErr(t, err) + } } func TestNodesServicingHold(t *testing.T) { @@ -272,3 +281,51 @@ func TestNodesServicingHold(t *testing.T) { }, nodes.Active) th.AssertNoErr(t, err) } + +func TestNodesVirtualInterfaces(t *testing.T) { + clients.SkipReleasesBelow(t, "stable/2023.2") // Adjust based on when this feature was added + clients.RequireLong(t) + + client, err := clients.NewBareMetalV1Client() + th.AssertNoErr(t, err) + // VIFs were added in API version 1.28, but at least 1.38 is needed for tests to pass + client.Microversion = "1.38" + + node, err := CreateNode(t, client) + th.AssertNoErr(t, err) + defer DeleteNode(t, client, node) + + // First, list VIFs (should be empty initially) + vifs, err := nodes.ListVirtualInterfaces(context.TODO(), client, node.UUID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, 0, len(vifs)) + + // For a real test, we would need a valid VIF ID from the networking service + // Since this is difficult in a test environment, we can test the API call + // with a fake ID and expect it to fail with a specific error + fakeVifID := "1974dcfa-836f-41b2-b541-686c100900e5" + + // Try to attach a VIF (this will likely fail with a 404 Not Found since the VIF doesn't exist) + err = nodes.AttachVirtualInterface(context.TODO(), client, node.UUID, nodes.VirtualInterfaceOpts{ + ID: fakeVifID, + }).ExtractErr() + + // We expect this to fail, but we're testing the API call itself + // In a real environment with valid VIFs, you would check for success instead + if err == nil { + t.Logf("Warning: Expected error when attaching non-existent VIF, but got success. This might indicate the test environment has a VIF with ID %s", fakeVifID) + } + + // Try to detach a VIF (this will likely fail with a 404 Not Found) + err = nodes.DetachVirtualInterface(context.TODO(), client, node.UUID, fakeVifID).ExtractErr() + + // Again, we expect this to fail in most test environments + if err == nil { + t.Logf("Warning: Expected error when detaching non-existent VIF, but got success. This might indicate the test environment has a VIF with ID %s", fakeVifID) + } + + // List VIFs again to confirm state hasn't changed + vifs, err = nodes.ListVirtualInterfaces(context.TODO(), client, node.UUID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, 0, len(vifs)) +} diff --git a/internal/acceptance/openstack/baremetal/v1/portgroups_test.go b/internal/acceptance/openstack/baremetal/v1/portgroups_test.go new file mode 100644 index 0000000000..968244c459 --- /dev/null +++ b/internal/acceptance/openstack/baremetal/v1/portgroups_test.go @@ -0,0 +1,50 @@ +//go:build acceptance || baremetal || portgroups + +package v1 + +import ( + "context" + "testing" + + "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" + "github.com/gophercloud/gophercloud/v2/openstack/baremetal/v1/portgroups" + "github.com/gophercloud/gophercloud/v2/pagination" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func TestPortGroupsCreateDestroy(t *testing.T) { + clients.RequireLong(t) + + client, err := clients.NewBareMetalV1Client() + th.AssertNoErr(t, err) + + // NOTE(sharpz7) - increased due to create fake node requiring it. + client.Microversion = "1.50" + + node, err := CreateFakeNode(t, client) + th.AssertNoErr(t, err) + defer DeleteNode(t, client, node) + + portgroup, err := CreatePortGroup(t, client, node) + th.AssertNoErr(t, err) + defer DeletePortGroup(t, client, portgroup) + + // Verify the portgroup exists by listing + err = portgroups.List(client, portgroups.ListOpts{ + Node: node.UUID, + }).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + pg, err := portgroups.ExtractPortGroups(page) + if err != nil { + return false, err + } + + for _, p := range pg { + if p.UUID == portgroup.UUID { + return true, nil + } + } + + return false, nil + }) + th.AssertNoErr(t, err) +} diff --git a/internal/acceptance/openstack/baremetal/v1/ports_test.go b/internal/acceptance/openstack/baremetal/v1/ports_test.go index 783c0c556c..628a75cdae 100644 --- a/internal/acceptance/openstack/baremetal/v1/ports_test.go +++ b/internal/acceptance/openstack/baremetal/v1/ports_test.go @@ -45,7 +45,7 @@ func TestPortsCreateDestroy(t *testing.T) { }) th.AssertNoErr(t, err) - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestPortsUpdate(t *testing.T) { @@ -71,5 +71,5 @@ func TestPortsUpdate(t *testing.T) { }).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, updated.Address, "aa:bb:cc:dd:ee:ff") + th.AssertEquals(t, "aa:bb:cc:dd:ee:ff", updated.Address) } diff --git a/internal/acceptance/openstack/blockstorage/noauth/blockstorage.go b/internal/acceptance/openstack/blockstorage/noauth/blockstorage.go index cedccbd3cb..b1976dbd04 100644 --- a/internal/acceptance/openstack/blockstorage/noauth/blockstorage.go +++ b/internal/acceptance/openstack/blockstorage/noauth/blockstorage.go @@ -13,6 +13,7 @@ import ( "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/snapshots" "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/volumes" + th "github.com/gophercloud/gophercloud/v2/testhelper" ) // CreateVolume will create a volume with a random name and size of 1GB. An @@ -43,6 +44,9 @@ func CreateVolume(t *testing.T, client *gophercloud.ServiceClient) (*volumes.Vol return volume, err } + th.AssertEquals(t, volumeName, volume.Name) + th.AssertEquals(t, 1, volume.Size) + return volume, nil } @@ -80,6 +84,9 @@ func CreateVolumeFromImage(t *testing.T, client *gophercloud.ServiceClient) (*vo return volume, err } + th.AssertEquals(t, volumeName, volume.Name) + th.AssertEquals(t, 1, volume.Size) + return volume, nil } @@ -124,6 +131,10 @@ func CreateSnapshot(t *testing.T, client *gophercloud.ServiceClient, volume *vol return snapshot, err } + th.AssertEquals(t, snapshotName, snapshot.Name) + th.AssertEquals(t, snapshotDescription, snapshot.Description) + th.AssertEquals(t, volume.ID, snapshot.VolumeID) + return snapshot, nil } diff --git a/internal/acceptance/openstack/blockstorage/noauth/conditions.go b/internal/acceptance/openstack/blockstorage/noauth/conditions.go new file mode 100644 index 0000000000..b1fd42e3bb --- /dev/null +++ b/internal/acceptance/openstack/blockstorage/noauth/conditions.go @@ -0,0 +1,14 @@ +package noauth + +import ( + "os" + "testing" +) + +// RequireCinderNoAuth will restrict a test to be only run in environments that +// have Cinder using noauth. +func RequireCinderNoAuth(t *testing.T) { + if os.Getenv("CINDER_ENDPOINT") == "" || os.Getenv("OS_USERNAME") == "" { + t.Skip("this test requires Cinder using noauth, set OS_USERNAME and CINDER_ENDPOINT") + } +} diff --git a/internal/acceptance/openstack/blockstorage/noauth/snapshots_test.go b/internal/acceptance/openstack/blockstorage/noauth/snapshots_test.go index 5b8bb2a982..f1daa352eb 100644 --- a/internal/acceptance/openstack/blockstorage/noauth/snapshots_test.go +++ b/internal/acceptance/openstack/blockstorage/noauth/snapshots_test.go @@ -12,7 +12,7 @@ import ( ) func TestSnapshotsList(t *testing.T) { - clients.RequireCinderNoAuth(t) + RequireCinderNoAuth(t) client, err := clients.NewBlockStorageV3NoAuthClient() if err != nil { @@ -35,7 +35,7 @@ func TestSnapshotsList(t *testing.T) { } func TestSnapshotsCreateDelete(t *testing.T) { - clients.RequireCinderNoAuth(t) + RequireCinderNoAuth(t) client, err := clients.NewBlockStorageV3NoAuthClient() if err != nil { diff --git a/internal/acceptance/openstack/blockstorage/noauth/volumes_test.go b/internal/acceptance/openstack/blockstorage/noauth/volumes_test.go index 0c993addad..15d4815426 100644 --- a/internal/acceptance/openstack/blockstorage/noauth/volumes_test.go +++ b/internal/acceptance/openstack/blockstorage/noauth/volumes_test.go @@ -12,7 +12,7 @@ import ( ) func TestVolumesList(t *testing.T) { - clients.RequireCinderNoAuth(t) + RequireCinderNoAuth(t) client, err := clients.NewBlockStorageV3NoAuthClient() if err != nil { @@ -35,7 +35,7 @@ func TestVolumesList(t *testing.T) { } func TestVolumesCreateDestroy(t *testing.T) { - clients.RequireCinderNoAuth(t) + RequireCinderNoAuth(t) client, err := clients.NewBlockStorageV3NoAuthClient() if err != nil { diff --git a/internal/acceptance/openstack/blockstorage/v2/backups_test.go b/internal/acceptance/openstack/blockstorage/v2/backups_test.go index 00ade44bde..d406533a2a 100644 --- a/internal/acceptance/openstack/blockstorage/v2/backups_test.go +++ b/internal/acceptance/openstack/blockstorage/v2/backups_test.go @@ -39,7 +39,7 @@ func TestBackupsCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestBackupsResetStatus(t *testing.T) { diff --git a/internal/acceptance/openstack/blockstorage/v2/blockstorage.go b/internal/acceptance/openstack/blockstorage/v2/blockstorage.go index 9c22a2a4f8..fcdf5e31b6 100644 --- a/internal/acceptance/openstack/blockstorage/v2/blockstorage.go +++ b/internal/acceptance/openstack/blockstorage/v2/blockstorage.go @@ -50,6 +50,10 @@ func CreateSnapshot(t *testing.T, client *gophercloud.ServiceClient, volume *vol t.Logf("Successfully created snapshot: %s", snapshot.ID) + th.AssertEquals(t, snapshotName, snapshot.Name) + th.AssertEquals(t, snapshotDescription, snapshot.Description) + th.AssertEquals(t, volume.ID, snapshot.VolumeID) + return snapshot, nil } @@ -82,7 +86,7 @@ func CreateVolume(t *testing.T, client *gophercloud.ServiceClient) (*volumes.Vol tools.PrintResource(t, volume) th.AssertEquals(t, volume.Name, volumeName) th.AssertEquals(t, volume.Description, volumeDescription) - th.AssertEquals(t, volume.Size, 1) + th.AssertEquals(t, 1, volume.Size) t.Logf("Successfully created volume: %s", volume.ID) @@ -125,7 +129,7 @@ func CreateVolumeFromImage(t *testing.T, client *gophercloud.ServiceClient) (*vo } th.AssertEquals(t, newVolume.Name, volumeName) - th.AssertEquals(t, newVolume.Size, 1) + th.AssertEquals(t, 1, newVolume.Size) t.Logf("Successfully created volume from image: %s", newVolume.ID) @@ -455,7 +459,7 @@ func SetBootable(t *testing.T, client *gophercloud.ServiceClient, volume *volume } if strings.ToLower(vol.Bootable) != "true" { - return fmt.Errorf("Volume bootable status is %q, expected 'true'", vol.Bootable) + return fmt.Errorf("volume bootable status is %q, expected 'true'", vol.Bootable) } bootableOpts = volumes.BootableOpts{ @@ -473,7 +477,7 @@ func SetBootable(t *testing.T, client *gophercloud.ServiceClient, volume *volume } if strings.ToLower(vol.Bootable) == "true" { - return fmt.Errorf("Volume bootable status is %q, expected 'false'", vol.Bootable) + return fmt.Errorf("volume bootable status is %q, expected 'false'", vol.Bootable) } return nil diff --git a/internal/acceptance/openstack/blockstorage/v2/snapshots_test.go b/internal/acceptance/openstack/blockstorage/v2/snapshots_test.go index 50ab203700..dd112587f9 100644 --- a/internal/acceptance/openstack/blockstorage/v2/snapshots_test.go +++ b/internal/acceptance/openstack/blockstorage/v2/snapshots_test.go @@ -44,5 +44,5 @@ func TestSnapshots(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } diff --git a/internal/acceptance/openstack/blockstorage/v2/volumes_test.go b/internal/acceptance/openstack/blockstorage/v2/volumes_test.go index d54c6f8c5c..60a370457f 100644 --- a/internal/acceptance/openstack/blockstorage/v2/volumes_test.go +++ b/internal/acceptance/openstack/blockstorage/v2/volumes_test.go @@ -57,7 +57,7 @@ func TestVolumesCreateDestroy(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestVolumesCreateForceDestroy(t *testing.T) { diff --git a/internal/acceptance/openstack/blockstorage/v2/volumetenants_test.go b/internal/acceptance/openstack/blockstorage/v2/volumetenants_test.go index 45d9babbad..ffb7c24e78 100644 --- a/internal/acceptance/openstack/blockstorage/v2/volumetenants_test.go +++ b/internal/acceptance/openstack/blockstorage/v2/volumetenants_test.go @@ -38,5 +38,5 @@ func TestVolumeTenants(t *testing.T) { err = volumes.ExtractVolumesInto(allPages, &allVolumes) th.AssertNoErr(t, err) - th.AssertEquals(t, true, len(allVolumes) > 0) + th.AssertTrue(t, len(allVolumes) > 0) } diff --git a/internal/acceptance/openstack/blockstorage/v3/backups_test.go b/internal/acceptance/openstack/blockstorage/v3/backups_test.go index 0050c551ab..13f237a327 100644 --- a/internal/acceptance/openstack/blockstorage/v3/backups_test.go +++ b/internal/acceptance/openstack/blockstorage/v3/backups_test.go @@ -37,7 +37,7 @@ func TestBackupsCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestBackupsResetStatus(t *testing.T) { diff --git a/internal/acceptance/openstack/blockstorage/v3/blockstorage.go b/internal/acceptance/openstack/blockstorage/v3/blockstorage.go index 56cf612abf..94a3920e3e 100644 --- a/internal/acceptance/openstack/blockstorage/v3/blockstorage.go +++ b/internal/acceptance/openstack/blockstorage/v3/blockstorage.go @@ -14,6 +14,7 @@ import ( "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/backups" + "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/manageablevolumes" "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/qos" "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/snapshots" "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/volumes" @@ -55,8 +56,9 @@ func CreateSnapshot(t *testing.T, client *gophercloud.ServiceClient, volume *vol } tools.PrintResource(t, snapshot) - th.AssertEquals(t, snapshot.Name, snapshotName) - th.AssertEquals(t, snapshot.VolumeID, volume.ID) + th.AssertEquals(t, snapshotName, snapshot.Name) + th.AssertEquals(t, snapshotDescription, snapshot.Description) + th.AssertEquals(t, volume.ID, snapshot.VolumeID) t.Logf("Successfully created snapshot: %s", snapshot.ID) @@ -97,7 +99,7 @@ func CreateVolume(t *testing.T, client *gophercloud.ServiceClient) (*volumes.Vol tools.PrintResource(t, volume) th.AssertEquals(t, volume.Name, volumeName) th.AssertEquals(t, volume.Description, volumeDescription) - th.AssertEquals(t, volume.Size, 1) + th.AssertEquals(t, 1, volume.Size) t.Logf("Successfully created volume: %s", volume.ID) @@ -140,7 +142,7 @@ func CreateVolumeWithType(t *testing.T, client *gophercloud.ServiceClient, vt *v tools.PrintResource(t, volume) th.AssertEquals(t, volume.Name, volumeName) th.AssertEquals(t, volume.Description, volumeDescription) - th.AssertEquals(t, volume.Size, 1) + th.AssertEquals(t, 1, volume.Size) th.AssertEquals(t, volume.VolumeType, vt.Name) t.Logf("Successfully created volume: %s", volume.ID) @@ -157,7 +159,7 @@ func CreateVolumeType(t *testing.T, client *gophercloud.ServiceClient) (*volumet createOpts := volumetypes.CreateOpts{ Name: name, - ExtraSpecs: map[string]string{"volume_backend_name": "fake_backend_name"}, + ExtraSpecs: map[string]string{"volume_backend_name": "fake_backend_name", "RESKEY:availability_zones": "zone", "multiattach": " True"}, Description: description, } @@ -167,7 +169,7 @@ func CreateVolumeType(t *testing.T, client *gophercloud.ServiceClient) (*volumet } tools.PrintResource(t, vt) - th.AssertEquals(t, vt.IsPublic, true) + th.AssertTrue(t, vt.IsPublic) th.AssertEquals(t, vt.Name, name) th.AssertEquals(t, vt.Description, description) // TODO: For some reason returned extra_specs are empty even in API reference: https://developer.openstack.org/api-ref/block-storage/v3/?expanded=create-a-volume-type-detail#volume-types-types @@ -200,7 +202,7 @@ func CreateVolumeTypeNoExtraSpecs(t *testing.T, client *gophercloud.ServiceClien } tools.PrintResource(t, vt) - th.AssertEquals(t, vt.IsPublic, true) + th.AssertTrue(t, vt.IsPublic) th.AssertEquals(t, vt.Name, name) th.AssertEquals(t, vt.Description, description) @@ -229,10 +231,10 @@ func CreateVolumeTypeMultiAttach(t *testing.T, client *gophercloud.ServiceClient } tools.PrintResource(t, vt) - th.AssertEquals(t, vt.IsPublic, true) + th.AssertTrue(t, vt.IsPublic) th.AssertEquals(t, vt.Name, name) th.AssertEquals(t, vt.Description, description) - th.AssertEquals(t, vt.ExtraSpecs["multiattach"], " True") + th.AssertEquals(t, " True", vt.ExtraSpecs["multiattach"]) t.Logf("Successfully created volume type: %s", vt.ID) @@ -261,7 +263,7 @@ func CreatePrivateVolumeType(t *testing.T, client *gophercloud.ServiceClient) (* } tools.PrintResource(t, vt) - th.AssertEquals(t, vt.IsPublic, false) + th.AssertFalse(t, vt.IsPublic) th.AssertEquals(t, vt.Name, name) th.AssertEquals(t, vt.Description, description) @@ -364,7 +366,7 @@ func CreateQoS(t *testing.T, client *gophercloud.ServiceClient) (*qos.QoS, error } tools.PrintResource(t, qs) - th.AssertEquals(t, qs.Consumer, "front-end") + th.AssertEquals(t, "front-end", qs.Consumer) th.AssertEquals(t, qs.Name, name) th.AssertDeepEquals(t, qs.Specs, createOpts.Specs) @@ -419,7 +421,8 @@ func CreateBackup(t *testing.T, client *gophercloud.ServiceClient, volumeID stri t.Logf("Successfully created backup %s", backup.ID) tools.PrintResource(t, backup) - th.AssertEquals(t, backup.Name, backupName) + th.AssertEquals(t, backupName, backup.Name) + th.AssertEquals(t, volumeID, backup.VolumeID) return backup, nil } @@ -672,7 +675,7 @@ func SetBootable(t *testing.T, client *gophercloud.ServiceClient, volume *volume } if strings.ToLower(vol.Bootable) != "true" { - return fmt.Errorf("Volume bootable status is %q, expected 'true'", vol.Bootable) + return fmt.Errorf("volume bootable status is %q, expected 'true'", vol.Bootable) } bootableOpts = volumes.BootableOpts{ @@ -690,7 +693,7 @@ func SetBootable(t *testing.T, client *gophercloud.ServiceClient, volume *volume } if strings.ToLower(vol.Bootable) == "true" { - return fmt.Errorf("Volume bootable status is %q, expected 'false'", vol.Bootable) + return fmt.Errorf("volume bootable status is %q, expected 'false'", vol.Bootable) } return nil @@ -779,3 +782,78 @@ func ReImage(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Vo return nil } + +func Unmanage(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) error { + t.Logf("Attempting to unmanage volume %s", volume.ID) + + err := volumes.Unmanage(context.TODO(), client, volume.ID).ExtractErr() + if err != nil { + return err + } + + err = gophercloud.WaitFor(context.TODO(), func(ctx context.Context) (bool, error) { + if _, err := volumes.Get(ctx, client, volume.ID).Extract(); err != nil { + if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok { + if errCode.Actual == 404 { + return true, nil + } + } + return false, err + } + return false, nil + }) + if err != nil { + return err + } + + t.Logf("Successfully unmanaged volume %s", volume.ID) + + return nil +} + +func ManageExisting(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) (*volumes.Volume, error) { + t.Logf("Attempting to manage existing volume %s", volume.Name) + + manageOpts := manageablevolumes.ManageExistingOpts{ + Host: volume.Host, + Ref: map[string]string{ + "source-name": fmt.Sprintf("volume-%s", volume.ID), + }, + Name: volume.Name, + AvailabilityZone: volume.AvailabilityZone, + Description: volume.Description, + VolumeType: volume.VolumeType, + Bootable: volume.Bootable == "true", + Metadata: volume.Metadata, + } + + managed, err := manageablevolumes.ManageExisting(context.TODO(), client, manageOpts).Extract() + if err != nil { + return managed, err + } + + ctx, cancel := context.WithTimeout(context.TODO(), 60*time.Second) + defer cancel() + + if err := volumes.WaitForStatus(ctx, client, managed.ID, "available"); err != nil { + return managed, err + } + + managed, err = volumes.Get(context.TODO(), client, managed.ID).Extract() + if err != nil { + return managed, err + } + + tools.PrintResource(t, managed) + th.AssertEquals(t, managed.Host, volume.Host) + th.AssertEquals(t, managed.Name, volume.Name) + th.AssertEquals(t, managed.AvailabilityZone, volume.AvailabilityZone) + th.AssertEquals(t, managed.Description, volume.Description) + th.AssertEquals(t, managed.VolumeType, volume.VolumeType) + th.AssertEquals(t, managed.Bootable, volume.Bootable) + th.AssertDeepEquals(t, managed.Metadata, volume.Metadata) + + t.Logf("Successfully managed existing volume %s", managed.ID) + + return managed, nil +} diff --git a/internal/acceptance/openstack/blockstorage/v3/manageablevolumes_test.go b/internal/acceptance/openstack/blockstorage/v3/manageablevolumes_test.go new file mode 100644 index 0000000000..cee317df4a --- /dev/null +++ b/internal/acceptance/openstack/blockstorage/v3/manageablevolumes_test.go @@ -0,0 +1,57 @@ +//go:build acceptance || blockstorage || volumes + +package v3 + +import ( + "context" + "testing" + + "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" + "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/volumes" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func TestManageableVolumes(t *testing.T) { + clients.RequireLong(t) + + client, err := clients.NewBlockStorageV3Client() + th.AssertNoErr(t, err) + + client.Microversion = "3.8" + + volume1, err := CreateVolume(t, client) + th.AssertNoErr(t, err) + + err = Unmanage(t, client, volume1) + if err != nil { + DeleteVolume(t, client, volume1) + } + th.AssertNoErr(t, err) + + managed1, err := ManageExisting(t, client, volume1) + th.AssertNoErr(t, err) + defer DeleteVolume(t, client, managed1) + + th.CheckEquals(t, volume1.Host, managed1.Host) + th.AssertEquals(t, volume1.Name, managed1.Name) + th.AssertEquals(t, volume1.AvailabilityZone, managed1.AvailabilityZone) + th.AssertEquals(t, volume1.Description, managed1.Description) + th.AssertEquals(t, volume1.VolumeType, managed1.VolumeType) + th.AssertEquals(t, volume1.Bootable, managed1.Bootable) + th.AssertDeepEquals(t, volume1.Metadata, managed1.Metadata) + th.AssertEquals(t, volume1.Size, managed1.Size) + + allPages, err := volumes.List(client, volumes.ListOpts{}).AllPages(context.TODO()) + th.AssertNoErr(t, err) + allVolumes, err := volumes.ExtractVolumes(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, v := range allVolumes { + if v.ID == managed1.ID { + found = true + break + } + } + th.AssertTrue(t, found) +} diff --git a/internal/acceptance/openstack/blockstorage/v3/qos_test.go b/internal/acceptance/openstack/blockstorage/v3/qos_test.go index 9c3d341cf1..2dc5c84d67 100644 --- a/internal/acceptance/openstack/blockstorage/v3/qos_test.go +++ b/internal/acceptance/openstack/blockstorage/v3/qos_test.go @@ -68,7 +68,7 @@ func TestQoS(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) return true, nil }) diff --git a/internal/acceptance/openstack/blockstorage/v3/quotaset_test.go b/internal/acceptance/openstack/blockstorage/v3/quotaset_test.go index 327ea0d79d..c87fce7e2f 100644 --- a/internal/acceptance/openstack/blockstorage/v3/quotaset_test.go +++ b/internal/acceptance/openstack/blockstorage/v3/quotaset_test.go @@ -46,6 +46,7 @@ func TestQuotasetGetUsage(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, quotaSetUsage) + tools.PrintResource(t, quotaSetUsage.Extra) } var UpdateQuotaOpts = quotasets.UpdateOpts{ @@ -129,7 +130,7 @@ func TestQuotasetUpdate(t *testing.T) { } } - th.AssertEquals(t, count, 3) + th.AssertEquals(t, 3, count) // unpopulate resultQuotas.Extra as it is different per cloud and test // rest of the quotaSet diff --git a/internal/acceptance/openstack/blockstorage/v3/snapshots_test.go b/internal/acceptance/openstack/blockstorage/v3/snapshots_test.go index bdb4c8fed3..f78d76ea83 100644 --- a/internal/acceptance/openstack/blockstorage/v3/snapshots_test.go +++ b/internal/acceptance/openstack/blockstorage/v3/snapshots_test.go @@ -69,11 +69,28 @@ func TestSnapshots(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) return true, nil }) + th.AssertNoErr(t, err) + + err = snapshots.ListDetail(client, listOpts).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + actual, err := snapshots.ExtractSnapshots(page) + th.AssertNoErr(t, err) + th.AssertEquals(t, 1, len(actual)) + var found bool + for _, v := range actual { + if v.ID == snapshot1.ID || v.ID == snapshot2.ID { + found = true + } + } + + th.AssertTrue(t, found) + + return true, nil + }) th.AssertNoErr(t, err) } diff --git a/internal/acceptance/openstack/blockstorage/v3/volumeattachments.go b/internal/acceptance/openstack/blockstorage/v3/volumeattachments.go index 4bd531a856..ce47111e85 100644 --- a/internal/acceptance/openstack/blockstorage/v3/volumeattachments.go +++ b/internal/acceptance/openstack/blockstorage/v3/volumeattachments.go @@ -72,7 +72,7 @@ func CreateVolumeAttachment(t *testing.T, client *gophercloud.ServiceClient, vol } if allAttachments[0].ID != attachment.ID { - return fmt.Errorf("Attachment IDs from get and list are not equal: %q != %q", allAttachments[0].ID, attachment.ID) + return fmt.Errorf("attachment IDs from get and list are not equal: %q != %q", allAttachments[0].ID, attachment.ID) } t.Logf("Attached volume %s to server %s within %q attachment", volume.ID, server.ID, attachment.ID) diff --git a/internal/acceptance/openstack/blockstorage/v3/volumes_test.go b/internal/acceptance/openstack/blockstorage/v3/volumes_test.go index 5510a3cd92..38cd8ef339 100644 --- a/internal/acceptance/openstack/blockstorage/v3/volumes_test.go +++ b/internal/acceptance/openstack/blockstorage/v3/volumes_test.go @@ -60,7 +60,7 @@ func TestVolumes(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) return true, nil }) @@ -98,7 +98,7 @@ func TestVolumesMultiAttach(t *testing.T) { err = volumes.WaitForStatus(ctx, client, vol.ID, "available") th.AssertNoErr(t, err) - th.AssertEquals(t, vol.Multiattach, true) + th.AssertTrue(t, vol.Multiattach) } func TestVolumesCascadeDelete(t *testing.T) { diff --git a/internal/acceptance/openstack/blockstorage/v3/volumetenants_test.go b/internal/acceptance/openstack/blockstorage/v3/volumetenants_test.go index e03be8d3c7..63ba8d7ff1 100644 --- a/internal/acceptance/openstack/blockstorage/v3/volumetenants_test.go +++ b/internal/acceptance/openstack/blockstorage/v3/volumetenants_test.go @@ -36,5 +36,5 @@ func TestVolumeTenants(t *testing.T) { err = volumes.ExtractVolumesInto(allPages, &allVolumes) th.AssertNoErr(t, err) - th.AssertEquals(t, true, len(allVolumes) > 0) + th.AssertTrue(t, len(allVolumes) > 0) } diff --git a/internal/acceptance/openstack/blockstorage/v3/volumetypes_test.go b/internal/acceptance/openstack/blockstorage/v3/volumetypes_test.go index f19fd033de..223b7ff4c5 100644 --- a/internal/acceptance/openstack/blockstorage/v3/volumetypes_test.go +++ b/internal/acceptance/openstack/blockstorage/v3/volumetypes_test.go @@ -4,6 +4,7 @@ package v3 import ( "context" + "fmt" "testing" "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" @@ -13,7 +14,7 @@ import ( th "github.com/gophercloud/gophercloud/v2/testhelper" ) -func TestVolumeTypes(t *testing.T) { +func TestVolumeTypesCRUD(t *testing.T) { clients.RequireAdmin(t) client, err := clients.NewBlockStorageV3Client() @@ -37,11 +38,11 @@ func TestVolumeTypes(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) + name := vt.Name + "-updated" + description := vt.Description + "-updated" isPublic := false - name := vt.Name + "-UPDATED" - description := "" updateOpts := volumetypes.UpdateOpts{ Name: &name, Description: &description, @@ -55,6 +56,69 @@ func TestVolumeTypes(t *testing.T) { th.AssertEquals(t, name, newVT.Name) th.AssertEquals(t, description, newVT.Description) th.AssertEquals(t, isPublic, newVT.IsPublic) + newVisibility := volumetypes.VisibilityPrivate + + for _, tt := range []struct { + name string + opts volumetypes.ListOpts + expectedVolumeTypes int + }{ + { + name: "Volume Type Name", + opts: volumetypes.ListOpts{ + Name: newVT.Name, + }, + expectedVolumeTypes: 1, + }, + { + name: "Description", + opts: volumetypes.ListOpts{ + Description: "create_from_gophercloud-updated", + }, + expectedVolumeTypes: 1, + }, + { + name: "Is Public", + opts: volumetypes.ListOpts{ + IsPublic: newVisibility, + }, + expectedVolumeTypes: 1, + }, + } { + t.Run(fmt.Sprintf("List volumetypes by %s", tt.name), func(t *testing.T) { + allPages, err := volumetypes.List(client, tt.opts).AllPages(context.TODO()) + th.AssertNoErr(t, err) + + allVolumeTypes, err := volumetypes.ExtractVolumeTypes(allPages) + th.AssertNoErr(t, err) + + logVolumeTypes := func() { + for _, volumetype := range allVolumeTypes { + tools.PrintResource(t, volumetype) + } + } + + if len(allVolumeTypes) != tt.expectedVolumeTypes { + if len(allVolumeTypes) == 0 { + t.Fatalf("Volume type not found") + } + } else { + if len(allVolumeTypes) > 1 { + logVolumeTypes() + t.Fatalf("Expected %d volume type but got %d", tt.expectedVolumeTypes, len(allVolumeTypes)) + } + } + func() { + for _, volumetype := range allVolumeTypes { + if volumetype.ID == newVT.ID { + return + } + } + logVolumeTypes() + t.Fatalf("Returned volume types did not contain expected volume type") + }() + }) + } } func TestVolumeTypesExtraSpecs(t *testing.T) { @@ -77,9 +141,9 @@ func TestVolumeTypesExtraSpecs(t *testing.T) { tools.PrintResource(t, createdExtraSpecs) - th.AssertEquals(t, len(createdExtraSpecs), 2) - th.AssertEquals(t, createdExtraSpecs["capabilities"], "gpu") - th.AssertEquals(t, createdExtraSpecs["volume_backend_name"], "ssd") + th.AssertEquals(t, 2, len(createdExtraSpecs)) + th.AssertEquals(t, "gpu", createdExtraSpecs["capabilities"]) + th.AssertEquals(t, "ssd", createdExtraSpecs["volume_backend_name"]) err = volumetypes.DeleteExtraSpec(context.TODO(), client, vt.ID, "volume_backend_name").ExtractErr() th.AssertNoErr(t, err) @@ -92,22 +156,22 @@ func TestVolumeTypesExtraSpecs(t *testing.T) { tools.PrintResource(t, updatedExtraSpec) - th.AssertEquals(t, updatedExtraSpec["capabilities"], "gpu-2") + th.AssertEquals(t, "gpu-2", updatedExtraSpec["capabilities"]) allExtraSpecs, err := volumetypes.ListExtraSpecs(context.TODO(), client, vt.ID).Extract() th.AssertNoErr(t, err) tools.PrintResource(t, allExtraSpecs) - th.AssertEquals(t, len(allExtraSpecs), 1) - th.AssertEquals(t, allExtraSpecs["capabilities"], "gpu-2") + th.AssertEquals(t, 1, len(allExtraSpecs)) + th.AssertEquals(t, "gpu-2", allExtraSpecs["capabilities"]) singleSpec, err := volumetypes.GetExtraSpec(context.TODO(), client, vt.ID, "capabilities").Extract() th.AssertNoErr(t, err) tools.PrintResource(t, singleSpec) - th.AssertEquals(t, singleSpec["capabilities"], "gpu-2") + th.AssertEquals(t, "gpu-2", singleSpec["capabilities"]) } func TestVolumeTypesAccess(t *testing.T) { @@ -142,7 +206,7 @@ func TestVolumeTypesAccess(t *testing.T) { tools.PrintResource(t, accessList) - th.AssertEquals(t, len(accessList), 1) + th.AssertEquals(t, 1, len(accessList)) th.AssertEquals(t, accessList[0].ProjectID, project.ID) th.AssertEquals(t, accessList[0].VolumeTypeID, vt.ID) @@ -161,7 +225,7 @@ func TestVolumeTypesAccess(t *testing.T) { tools.PrintResource(t, accessList) - th.AssertEquals(t, len(accessList), 0) + th.AssertEquals(t, 0, len(accessList)) } func TestEncryptionVolumeTypes(t *testing.T) { diff --git a/internal/acceptance/openstack/client_test.go b/internal/acceptance/openstack/client_test.go index 7902961c55..acb0399b11 100644 --- a/internal/acceptance/openstack/client_test.go +++ b/internal/acceptance/openstack/client_test.go @@ -36,14 +36,14 @@ func TestAuthenticatedClient(t *testing.T) { t.Logf("Client successfully acquired a token: %v", client.TokenID) - // Find the storage service in the service catalog. - storage, err := openstack.NewObjectStorageV1(client, gophercloud.EndpointOpts{ + // Find the compute service in the service catalog. + compute, err := openstack.NewComputeV2(context.TODO(), client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) if err != nil { - t.Errorf("Unable to locate a storage service: %v", err) + t.Errorf("Unable to locate a compute service: %v", err) } else { - t.Logf("Located a storage service at endpoint: [%s]", storage.Endpoint) + t.Logf("Located a compute service at endpoint: [%s]", compute.Endpoint) } } @@ -56,10 +56,10 @@ func TestEC2AuthMethod(t *testing.T) { authOptions := tokens.AuthOptions{ Username: ao.Username, + UserID: ao.UserID, Password: ao.Password, DomainName: ao.DomainName, DomainID: ao.DomainID, - // We need a scope to get the token roles list Scope: tokens.Scope{ ProjectID: ao.TenantID, ProjectName: ao.TenantName, @@ -71,11 +71,11 @@ func TestEC2AuthMethod(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, token) - user, err := tokens.Get(context.TODO(), client, token.ID).ExtractUser() + user, err := tokens.Get(context.TODO(), client, token.ID, nil).ExtractUser() th.AssertNoErr(t, err) tools.PrintResource(t, user) - project, err := tokens.Get(context.TODO(), client, token.ID).ExtractProject() + project, err := tokens.Get(context.TODO(), client, token.ID, nil).ExtractProject() th.AssertNoErr(t, err) tools.PrintResource(t, project) @@ -94,7 +94,8 @@ func TestEC2AuthMethod(t *testing.T) { defer credentials.Delete(context.TODO(), client, credential.ID) tools.PrintResource(t, credential) - newClient, err := clients.NewIdentityV3UnauthenticatedClient() + // Create a new provider client for EC2 authentication using the existing token + newClient, err := clients.NewIdentityV3Client() th.AssertNoErr(t, err) ec2AuthOptions := &ec2tokens.AuthOptions{ @@ -128,7 +129,7 @@ func TestReauth(t *testing.T) { } t.Logf("Creating a compute client") - _, err = openstack.NewComputeV2(provider, gophercloud.EndpointOpts{ + _, err = openstack.NewComputeV2(context.TODO(), provider, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) if err != nil { @@ -145,7 +146,7 @@ func TestReauth(t *testing.T) { } t.Logf("Creating a compute client") - _, err = openstack.NewComputeV2(provider, gophercloud.EndpointOpts{ + _, err = openstack.NewComputeV2(context.TODO(), provider, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) if err != nil { diff --git a/internal/acceptance/openstack/compute/v2/aggregates_test.go b/internal/acceptance/openstack/compute/v2/aggregates_test.go index 88ac3a3365..082cadd13c 100644 --- a/internal/acceptance/openstack/compute/v2/aggregates_test.go +++ b/internal/acceptance/openstack/compute/v2/aggregates_test.go @@ -11,6 +11,7 @@ import ( "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" + "github.com/gophercloud/gophercloud/v2/internal/ptr" "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/aggregates" "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/hypervisors" th "github.com/gophercloud/gophercloud/v2/testhelper" @@ -47,8 +48,8 @@ func TestAggregatesCRUD(t *testing.T) { tools.PrintResource(t, aggregate) updateOpts := aggregates.UpdateOpts{ - Name: "new_aggregate_name", - AvailabilityZone: "new_azone", + Name: ptr.To("new_aggregate_name"), + AvailabilityZone: ptr.To("new_azone"), } updatedAggregate, err := aggregates.Update(context.TODO(), client, aggregate.ID, updateOpts).Extract() @@ -56,8 +57,8 @@ func TestAggregatesCRUD(t *testing.T) { tools.PrintResource(t, aggregate) - th.AssertEquals(t, updatedAggregate.Name, "new_aggregate_name") - th.AssertEquals(t, updatedAggregate.AvailabilityZone, "new_azone") + th.AssertEquals(t, "new_aggregate_name", updatedAggregate.Name) + th.AssertEquals(t, "new_azone", updatedAggregate.AvailabilityZone) } func TestAggregatesAddRemoveHost(t *testing.T) { @@ -93,7 +94,7 @@ func TestAggregatesAddRemoveHost(t *testing.T) { tools.PrintResource(t, aggregateWithRemovedHost) - th.AssertEquals(t, len(aggregateWithRemovedHost.Hosts), 0) + th.AssertEquals(t, 0, len(aggregateWithRemovedHost.Hosts)) } func TestAggregatesSetRemoveMetadata(t *testing.T) { @@ -146,5 +147,5 @@ func getHypervisor(t *testing.T, client *gophercloud.ServiceClient) (string, err return host, nil } - return "", fmt.Errorf("Unable to get hypervisor") + return "", fmt.Errorf("unable to get hypervisor") } diff --git a/internal/acceptance/openstack/compute/v2/attachinterfaces_test.go b/internal/acceptance/openstack/compute/v2/attachinterfaces_test.go index cf9b1a973e..d5be1c1b89 100644 --- a/internal/acceptance/openstack/compute/v2/attachinterfaces_test.go +++ b/internal/acceptance/openstack/compute/v2/attachinterfaces_test.go @@ -48,5 +48,5 @@ func TestAttachDetachInterface(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } diff --git a/internal/acceptance/openstack/compute/v2/availabilityzones_test.go b/internal/acceptance/openstack/compute/v2/availabilityzones_test.go index b3af24b0a1..6fda7f0a65 100644 --- a/internal/acceptance/openstack/compute/v2/availabilityzones_test.go +++ b/internal/acceptance/openstack/compute/v2/availabilityzones_test.go @@ -31,7 +31,7 @@ func TestAvailabilityZonesList(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestAvailabilityZonesListDetail(t *testing.T) { @@ -55,5 +55,5 @@ func TestAvailabilityZonesListDetail(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } diff --git a/internal/acceptance/openstack/compute/v2/bootfromvolume_test.go b/internal/acceptance/openstack/compute/v2/bootfromvolume_test.go index 5bf7f75e13..4b7082ab3a 100644 --- a/internal/acceptance/openstack/compute/v2/bootfromvolume_test.go +++ b/internal/acceptance/openstack/compute/v2/bootfromvolume_test.go @@ -39,7 +39,7 @@ func TestBootFromImage(t *testing.T) { tools.PrintResource(t, server) - th.AssertEquals(t, server.Image["id"], choices.ImageID) + th.AssertEquals(t, choices.ImageID, server.Image["id"]) } func TestBootFromNewVolume(t *testing.T) { @@ -80,13 +80,13 @@ func TestBootFromNewVolume(t *testing.T) { tools.PrintResource(t, server) tools.PrintResource(t, attachments) attachmentTag := *attachments[0].Tag - th.AssertEquals(t, attachmentTag, tagName) + th.AssertEquals(t, tagName, attachmentTag) if server.Image != nil { t.Fatalf("server image should be nil") } - th.AssertEquals(t, len(attachments), 1) + th.AssertEquals(t, 1, len(attachments)) // TODO: volumes_attached extension } @@ -131,8 +131,8 @@ func TestBootFromExistingVolume(t *testing.T) { t.Fatalf("server image should be nil") } - th.AssertEquals(t, len(attachments), 1) - th.AssertEquals(t, attachments[0].VolumeID, volume.ID) + th.AssertEquals(t, 1, len(attachments)) + th.AssertEquals(t, volume.ID, attachments[0].VolumeID) // TODO: volumes_attached extension } @@ -218,8 +218,8 @@ func TestAttachNewVolume(t *testing.T) { tools.PrintResource(t, server) tools.PrintResource(t, attachments) - th.AssertEquals(t, server.Image["id"], choices.ImageID) - th.AssertEquals(t, len(attachments), 1) + th.AssertEquals(t, choices.ImageID, server.Image["id"]) + th.AssertEquals(t, 1, len(attachments)) // TODO: volumes_attached extension } @@ -269,9 +269,9 @@ func TestAttachExistingVolume(t *testing.T) { tools.PrintResource(t, server) tools.PrintResource(t, attachments) - th.AssertEquals(t, server.Image["id"], choices.ImageID) - th.AssertEquals(t, len(attachments), 1) - th.AssertEquals(t, attachments[0].VolumeID, volume.ID) + th.AssertEquals(t, choices.ImageID, server.Image["id"]) + th.AssertEquals(t, 1, len(attachments)) + th.AssertEquals(t, volume.ID, attachments[0].VolumeID) // TODO: volumes_attached extension } diff --git a/internal/acceptance/openstack/compute/v2/compute.go b/internal/acceptance/openstack/compute/v2/compute.go index 513344082f..472f314d02 100644 --- a/internal/acceptance/openstack/compute/v2/compute.go +++ b/internal/acceptance/openstack/compute/v2/compute.go @@ -57,6 +57,8 @@ func AttachInterface(t *testing.T, client *gophercloud.ServiceClient, serverID s t.Logf("Successfully created interface %s on server %s", iface.PortID, serverID) + th.AssertEquals(t, networkID, iface.NetID) + return iface, nil } @@ -84,8 +86,8 @@ func CreateAggregate(t *testing.T, client *gophercloud.ServiceClient) (*aggregat return nil, err } - th.AssertEquals(t, aggregate.Name, aggregateName) - th.AssertEquals(t, aggregate.AvailabilityZone, availabilityZone) + th.AssertEquals(t, aggregateName, aggregate.Name) + th.AssertEquals(t, availabilityZone, aggregate.AvailabilityZone) return aggregate, nil } @@ -137,7 +139,7 @@ func CreateBootableVolumeServer(t *testing.T, client *gophercloud.ServiceClient, return nil, err } - th.AssertEquals(t, newServer.Name, name) + th.AssertEquals(t, name, newServer.Name) return newServer, nil } @@ -155,9 +157,9 @@ func CreateFlavor(t *testing.T, client *gophercloud.ServiceClient) (*flavors.Fla isPublic := true createOpts := flavors.CreateOpts{ Name: flavorName, - RAM: 1, + RAM: 64, VCPUs: 1, - Disk: gophercloud.IntToPointer(1), + Disk: gophercloud.IntToPointer(2), IsPublic: &isPublic, Description: flavorDescription, } @@ -169,12 +171,12 @@ func CreateFlavor(t *testing.T, client *gophercloud.ServiceClient) (*flavors.Fla t.Logf("Successfully created flavor %s", flavor.ID) - th.AssertEquals(t, flavor.Name, flavorName) - th.AssertEquals(t, flavor.RAM, 1) - th.AssertEquals(t, flavor.Disk, 1) - th.AssertEquals(t, flavor.VCPUs, 1) - th.AssertEquals(t, flavor.IsPublic, true) - th.AssertEquals(t, flavor.Description, flavorDescription) + th.AssertEquals(t, flavorName, flavor.Name) + th.AssertEquals(t, 64, flavor.RAM) + th.AssertEquals(t, 2, flavor.Disk) + th.AssertEquals(t, 1, flavor.VCPUs) + th.AssertTrue(t, flavor.IsPublic) + th.AssertEquals(t, flavorDescription, flavor.Description) return flavor, nil } @@ -213,7 +215,7 @@ func CreateKeyPair(t *testing.T, client *gophercloud.ServiceClient) (*keypairs.K t.Logf("Created keypair: %s", keyPairName) - th.AssertEquals(t, keyPair.Name, keyPairName) + th.AssertEquals(t, keyPairName, keyPair.Name) return keyPair, nil } @@ -263,9 +265,9 @@ func CreateMultiEphemeralServer(t *testing.T, client *gophercloud.ServiceClient, if err != nil { return server, err } - th.AssertEquals(t, newServer.Name, name) - th.AssertEquals(t, newServer.Flavor["id"], choices.FlavorID) - th.AssertEquals(t, newServer.Image["id"], choices.ImageID) + th.AssertEquals(t, name, newServer.Name) + th.AssertEquals(t, choices.FlavorID, newServer.Flavor["id"]) + th.AssertEquals(t, choices.ImageID, newServer.Image["id"]) return newServer, nil } @@ -279,9 +281,9 @@ func CreatePrivateFlavor(t *testing.T, client *gophercloud.ServiceClient) (*flav isPublic := false createOpts := flavors.CreateOpts{ Name: flavorName, - RAM: 1, + RAM: 64, VCPUs: 1, - Disk: gophercloud.IntToPointer(1), + Disk: gophercloud.IntToPointer(2), IsPublic: &isPublic, } @@ -292,11 +294,11 @@ func CreatePrivateFlavor(t *testing.T, client *gophercloud.ServiceClient) (*flav t.Logf("Successfully created flavor %s", flavor.ID) - th.AssertEquals(t, flavor.Name, flavorName) - th.AssertEquals(t, flavor.RAM, 1) - th.AssertEquals(t, flavor.Disk, 1) - th.AssertEquals(t, flavor.VCPUs, 1) - th.AssertEquals(t, flavor.IsPublic, false) + th.AssertEquals(t, flavorName, flavor.Name) + th.AssertEquals(t, 64, flavor.RAM) + th.AssertEquals(t, 2, flavor.Disk) + th.AssertEquals(t, 1, flavor.VCPUs) + th.AssertFalse(t, flavor.IsPublic) return flavor, nil } @@ -318,7 +320,8 @@ func CreateSecurityGroup(t *testing.T, client *gophercloud.ServiceClient) (*secg t.Logf("Created security group: %s", securityGroup.ID) - th.AssertEquals(t, securityGroup.Name, name) + th.AssertEquals(t, name, securityGroup.Name) + th.AssertEquals(t, "something", securityGroup.Description) return securityGroup, nil } @@ -344,9 +347,11 @@ func CreateSecurityGroupRule(t *testing.T, client *gophercloud.ServiceClient, se t.Logf("Created security group rule: %s", rule.ID) - th.AssertEquals(t, rule.FromPort, fromPort) - th.AssertEquals(t, rule.ToPort, toPort) - th.AssertEquals(t, rule.ParentGroupID, securityGroupID) + th.AssertEquals(t, fromPort, rule.FromPort) + th.AssertEquals(t, toPort, rule.ToPort) + th.AssertEquals(t, securityGroupID, rule.ParentGroupID) + th.AssertEqualsIgnoreCase(t, "TCP", rule.IPProtocol) + th.AssertEquals(t, "0.0.0.0/0", rule.IPRange.CIDR) return rule, nil } @@ -403,9 +408,10 @@ func CreateServer(t *testing.T, client *gophercloud.ServiceClient) (*servers.Ser return nil, err } - th.AssertEquals(t, newServer.Name, name) - th.AssertEquals(t, newServer.Flavor["id"], choices.FlavorID) - th.AssertEquals(t, newServer.Image["id"], choices.ImageID) + th.AssertEquals(t, name, newServer.Name) + th.AssertEquals(t, choices.FlavorID, newServer.Flavor["id"]) + th.AssertEquals(t, choices.ImageID, newServer.Image["id"]) + th.AssertDeepEquals(t, map[string]string{"abc": "def"}, newServer.Metadata) return newServer, nil } @@ -457,8 +463,9 @@ func CreateMicroversionServer(t *testing.T, client *gophercloud.ServiceClient) ( return nil, err } - th.AssertEquals(t, newServer.Name, name) - th.AssertEquals(t, newServer.Image["id"], choices.ImageID) + th.AssertEquals(t, name, newServer.Name) + th.AssertEquals(t, choices.ImageID, newServer.Image["id"]) + th.AssertDeepEquals(t, map[string]string{"abc": "def"}, newServer.Metadata) return newServer, nil } @@ -560,8 +567,8 @@ func CreateServerWithTags(t *testing.T, client *gophercloud.ServiceClient, netwo newServer, err := res.Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, newServer.Name, name) - th.AssertDeepEquals(t, *newServer.Tags, []string{"tag1", "tag2"}) + th.AssertEquals(t, name, newServer.Name) + th.AssertDeepEquals(t, []string{"tag1", "tag2"}, *newServer.Tags) return newServer, nil } @@ -584,7 +591,8 @@ func CreateServerGroup(t *testing.T, client *gophercloud.ServiceClient, policy s t.Logf("Successfully created server group %s", name) - th.AssertEquals(t, sg.Name, name) + th.AssertEquals(t, name, sg.Name) + th.AssertDeepEquals(t, []string{policy}, sg.Policies) return sg, nil } @@ -612,7 +620,15 @@ func CreateServerGroupMicroversion(t *testing.T, client *gophercloud.ServiceClie t.Logf("Successfully created server group %s", name) - th.AssertEquals(t, sg.Name, name) + th.AssertEquals(t, name, sg.Name) + if sg.Policy == nil { + t.Fatal("Expected Policy to be non-nil") + } + th.AssertEquals(t, policy, *sg.Policy) + if sg.Rules == nil { + t.Fatal("Expected Rules to be non-nil") + } + th.AssertEquals(t, maxServerPerHost, sg.Rules.MaxServerPerHost) return sg, nil } @@ -662,9 +678,9 @@ func CreateServerInServerGroup(t *testing.T, client *gophercloud.ServiceClient, return nil, err } - th.AssertEquals(t, newServer.Name, name) - th.AssertEquals(t, newServer.Flavor["id"], choices.FlavorID) - th.AssertEquals(t, newServer.Image["id"], choices.ImageID) + th.AssertEquals(t, name, newServer.Name) + th.AssertEquals(t, choices.FlavorID, newServer.Flavor["id"]) + th.AssertEquals(t, choices.ImageID, newServer.Image["id"]) return newServer, nil } @@ -685,19 +701,17 @@ func CreateServerWithPublicKey(t *testing.T, client *gophercloud.ServiceClient, name := tools.RandomString("ACPTTEST", 16) t.Logf("Attempting to create server: %s", name) - serverCreateOpts := servers.CreateOpts{ + createOpts := servers.CreateOpts{ Name: name, FlavorRef: choices.FlavorID, ImageRef: choices.ImageID, Networks: []servers.Network{ {UUID: networkID}, }, + KeyName: keyPairName, } - server, err := servers.Create(context.TODO(), client, keypairs.CreateOptsExt{ - CreateOptsBuilder: serverCreateOpts, - KeyName: keyPairName, - }, nil).Extract() + server, err := servers.Create(context.TODO(), client, createOpts, nil).Extract() if err != nil { return nil, err } @@ -711,9 +725,10 @@ func CreateServerWithPublicKey(t *testing.T, client *gophercloud.ServiceClient, return nil, err } - th.AssertEquals(t, newServer.Name, name) - th.AssertEquals(t, newServer.Flavor["id"], choices.FlavorID) - th.AssertEquals(t, newServer.Image["id"], choices.ImageID) + th.AssertEquals(t, name, newServer.Name) + th.AssertEquals(t, choices.FlavorID, newServer.Flavor["id"]) + th.AssertEquals(t, choices.ImageID, newServer.Image["id"]) + th.AssertEquals(t, keyPairName, newServer.KeyName) return newServer, nil } @@ -743,6 +758,15 @@ func CreateVolumeAttachment(t *testing.T, client *gophercloud.ServiceClient, blo return volumeAttachment, err } + th.AssertEquals(t, volume.ID, volumeAttachment.VolumeID) + th.AssertEquals(t, server.ID, volumeAttachment.ServerID) + if volumeAttachment.Tag != nil { + th.AssertEquals(t, tag, *volumeAttachment.Tag) + } + if volumeAttachment.DeleteOnTermination != nil { + th.AssertEquals(t, dot, *volumeAttachment.DeleteOnTermination) + } + return volumeAttachment, nil } @@ -890,7 +914,7 @@ func GetNetworkIDFromNetworks(t *testing.T, client *gophercloud.ServiceClient, n } } - return "", fmt.Errorf("Failed to obtain network ID for network %s", networkName) + return "", fmt.Errorf("failed to obtain network ID for network %s", networkName) } // ImportPublicKey will create a KeyPair with a random name and a specified @@ -910,8 +934,8 @@ func ImportPublicKey(t *testing.T, client *gophercloud.ServiceClient, publicKey t.Logf("Created keypair: %s", keyPairName) - th.AssertEquals(t, keyPair.Name, keyPairName) - th.AssertEquals(t, keyPair.PublicKey, publicKey) + th.AssertEquals(t, keyPairName, keyPair.Name) + th.AssertEquals(t, publicKey, keyPair.PublicKey) return keyPair, nil } @@ -954,7 +978,7 @@ func WaitForComputeStatus(client *gophercloud.ServiceClient, server *servers.Ser } if latest.Status == "ERROR" { - return false, fmt.Errorf("Instance in ERROR state") + return false, fmt.Errorf("instance in ERROR state") } return false, nil @@ -1022,6 +1046,10 @@ func CreateRemoteConsole(t *testing.T, client *gophercloud.ServiceClient, server } t.Logf("Successfully created console: %s", remoteConsole.URL) + + th.AssertEquals(t, string(remoteconsoles.ConsoleProtocolVNC), remoteConsole.Protocol) + th.AssertEquals(t, string(remoteconsoles.ConsoleTypeNoVNC), remoteConsole.Type) + return remoteConsole, nil } @@ -1070,9 +1098,10 @@ func CreateServerNoNetwork(t *testing.T, client *gophercloud.ServiceClient) (*se return nil, err } - th.AssertEquals(t, newServer.Name, name) - th.AssertEquals(t, newServer.Flavor["id"], choices.FlavorID) - th.AssertEquals(t, newServer.Image["id"], choices.ImageID) + th.AssertEquals(t, name, newServer.Name) + th.AssertEquals(t, choices.FlavorID, newServer.Flavor["id"]) + th.AssertEquals(t, choices.ImageID, newServer.Image["id"]) + th.AssertDeepEquals(t, map[string]string{"abc": "def"}, newServer.Metadata) return newServer, nil } diff --git a/internal/acceptance/openstack/compute/v2/conditions.go b/internal/acceptance/openstack/compute/v2/conditions.go new file mode 100644 index 0000000000..565a3e1cfe --- /dev/null +++ b/internal/acceptance/openstack/compute/v2/conditions.go @@ -0,0 +1,22 @@ +package v2 + +import ( + "os" + "testing" +) + +// RequireGuestAgent will restrict a test to only be run in +// environments that support the QEMU guest agent. +func RequireGuestAgent(t *testing.T) { + if os.Getenv("OS_GUEST_AGENT") == "" { + t.Skip("this test requires support for qemu guest agent and to set OS_GUEST_AGENT to 1") + } +} + +// RequireLiveMigration will restrict a test to only be run in +// environments that support live migration. +func RequireLiveMigration(t *testing.T) { + if os.Getenv("OS_LIVE_MIGRATE") == "" { + t.Skip("this test requires support for live migration and to set OS_LIVE_MIGRATE to 1") + } +} diff --git a/internal/acceptance/openstack/compute/v2/diagnostics_test.go b/internal/acceptance/openstack/compute/v2/diagnostics_test.go index e51984bd52..04ba0de043 100644 --- a/internal/acceptance/openstack/compute/v2/diagnostics_test.go +++ b/internal/acceptance/openstack/compute/v2/diagnostics_test.go @@ -28,5 +28,5 @@ func TestDiagnostics(t *testing.T) { tools.PrintResource(t, diag) _, ok := diag["memory"] - th.AssertEquals(t, true, ok) + th.AssertTrue(t, ok) } diff --git a/internal/acceptance/openstack/compute/v2/extension_test.go b/internal/acceptance/openstack/compute/v2/extension_test.go index 0339b251fb..6db15260dc 100644 --- a/internal/acceptance/openstack/compute/v2/extension_test.go +++ b/internal/acceptance/openstack/compute/v2/extension_test.go @@ -31,7 +31,7 @@ func TestExtensionsList(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestExtensionsGet(t *testing.T) { @@ -43,5 +43,5 @@ func TestExtensionsGet(t *testing.T) { tools.PrintResource(t, extension) - th.AssertEquals(t, extension.Name, "AdminActions") + th.AssertEquals(t, "AdminActions", extension.Name) } diff --git a/internal/acceptance/openstack/compute/v2/flavors_test.go b/internal/acceptance/openstack/compute/v2/flavors_test.go index cb6bd319a1..305b07ca47 100644 --- a/internal/acceptance/openstack/compute/v2/flavors_test.go +++ b/internal/acceptance/openstack/compute/v2/flavors_test.go @@ -35,7 +35,7 @@ func TestFlavorsList(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestFlavorsAccessTypeList(t *testing.T) { @@ -74,7 +74,7 @@ func TestFlavorsGet(t *testing.T) { tools.PrintResource(t, flavor) - th.AssertEquals(t, flavor.ID, choices.FlavorID) + th.AssertEquals(t, choices.FlavorID, flavor.ID) } func TestFlavorExtraSpecsGet(t *testing.T) { @@ -103,9 +103,9 @@ func TestFlavorExtraSpecsGet(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, flavor) - th.AssertEquals(t, len(flavor.ExtraSpecs), 2) - th.AssertEquals(t, flavor.ExtraSpecs["hw:cpu_policy"], "CPU-POLICY") - th.AssertEquals(t, flavor.ExtraSpecs["hw:cpu_thread_policy"], "CPU-THREAD-POLICY") + th.AssertEquals(t, 2, len(flavor.ExtraSpecs)) + th.AssertEquals(t, "CPU-POLICY", flavor.ExtraSpecs["hw:cpu_policy"]) + th.AssertEquals(t, "CPU-THREAD-POLICY", flavor.ExtraSpecs["hw:cpu_thread_policy"]) } func TestFlavorsCreateDelete(t *testing.T) { @@ -135,12 +135,12 @@ func TestFlavorsCreateUpdateDelete(t *testing.T) { newFlavorDescription := "This is the new description" updateOpts := flavors.UpdateOpts{ - Description: newFlavorDescription, + Description: &newFlavorDescription, } flavor, err = flavors.Update(context.TODO(), client, flavor.ID, updateOpts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, flavor.Description, newFlavorDescription) + th.AssertEquals(t, newFlavorDescription, flavor.Description) tools.PrintResource(t, flavor) } @@ -161,7 +161,7 @@ func TestFlavorsAccessesList(t *testing.T) { allAccesses, err := flavors.ExtractAccesses(allPages) th.AssertNoErr(t, err) - th.AssertEquals(t, len(allAccesses), 0) + th.AssertEquals(t, 0, len(allAccesses)) } func TestFlavorsAccessCRUD(t *testing.T) { @@ -188,9 +188,9 @@ func TestFlavorsAccessCRUD(t *testing.T) { accessList, err := flavors.AddAccess(context.TODO(), client, flavor.ID, addAccessOpts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, len(accessList), 1) - th.AssertEquals(t, accessList[0].TenantID, project.ID) - th.AssertEquals(t, accessList[0].FlavorID, flavor.ID) + th.AssertEquals(t, 1, len(accessList)) + th.AssertEquals(t, project.ID, accessList[0].TenantID) + th.AssertEquals(t, flavor.ID, accessList[0].FlavorID) for _, access := range accessList { tools.PrintResource(t, access) @@ -203,7 +203,7 @@ func TestFlavorsAccessCRUD(t *testing.T) { accessList, err = flavors.RemoveAccess(context.TODO(), client, flavor.ID, removeAccessOpts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, len(accessList), 0) + th.AssertEquals(t, 0, len(accessList)) } func TestFlavorsExtraSpecsCRUD(t *testing.T) { @@ -225,9 +225,9 @@ func TestFlavorsExtraSpecsCRUD(t *testing.T) { tools.PrintResource(t, createdExtraSpecs) - th.AssertEquals(t, len(createdExtraSpecs), 2) - th.AssertEquals(t, createdExtraSpecs["hw:cpu_policy"], "CPU-POLICY") - th.AssertEquals(t, createdExtraSpecs["hw:cpu_thread_policy"], "CPU-THREAD-POLICY") + th.AssertEquals(t, 2, len(createdExtraSpecs)) + th.AssertEquals(t, "CPU-POLICY", createdExtraSpecs["hw:cpu_policy"]) + th.AssertEquals(t, "CPU-THREAD-POLICY", createdExtraSpecs["hw:cpu_thread_policy"]) err = flavors.DeleteExtraSpec(context.TODO(), client, flavor.ID, "hw:cpu_policy").ExtractErr() th.AssertNoErr(t, err) @@ -245,13 +245,13 @@ func TestFlavorsExtraSpecsCRUD(t *testing.T) { tools.PrintResource(t, allExtraSpecs) - th.AssertEquals(t, len(allExtraSpecs), 1) - th.AssertEquals(t, allExtraSpecs["hw:cpu_thread_policy"], "CPU-THREAD-POLICY-BETTER") + th.AssertEquals(t, 1, len(allExtraSpecs)) + th.AssertEquals(t, "CPU-THREAD-POLICY-BETTER", allExtraSpecs["hw:cpu_thread_policy"]) spec, err := flavors.GetExtraSpec(context.TODO(), client, flavor.ID, "hw:cpu_thread_policy").Extract() th.AssertNoErr(t, err) tools.PrintResource(t, spec) - th.AssertEquals(t, spec["hw:cpu_thread_policy"], "CPU-THREAD-POLICY-BETTER") + th.AssertEquals(t, "CPU-THREAD-POLICY-BETTER", spec["hw:cpu_thread_policy"]) } diff --git a/internal/acceptance/openstack/compute/v2/hypervisors_test.go b/internal/acceptance/openstack/compute/v2/hypervisors_test.go index dae1bbcdbc..861fa7e02c 100644 --- a/internal/acceptance/openstack/compute/v2/hypervisors_test.go +++ b/internal/acceptance/openstack/compute/v2/hypervisors_test.go @@ -121,5 +121,5 @@ func getHypervisorID(t *testing.T, client *gophercloud.ServiceClient) (string, e return allHypervisors[0].ID, nil } - return "", fmt.Errorf("Unable to get hypervisor ID") + return "", fmt.Errorf("unable to get hypervisor ID") } diff --git a/internal/acceptance/openstack/compute/v2/instance_actions_test.go b/internal/acceptance/openstack/compute/v2/instance_actions_test.go index fb43cd72d6..9a83a732c0 100644 --- a/internal/acceptance/openstack/compute/v2/instance_actions_test.go +++ b/internal/acceptance/openstack/compute/v2/instance_actions_test.go @@ -39,7 +39,7 @@ func TestInstanceActions(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestInstanceActionsMicroversions(t *testing.T) { @@ -88,7 +88,7 @@ func TestInstanceActionsMicroversions(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) listOpts = instanceactions.ListOpts{ Limit: 1, @@ -101,5 +101,5 @@ func TestInstanceActionsMicroversions(t *testing.T) { allActions, err = instanceactions.ExtractInstanceActions(allPages) th.AssertNoErr(t, err) - th.AssertEquals(t, len(allActions), 0) + th.AssertEquals(t, 0, len(allActions)) } diff --git a/internal/acceptance/openstack/compute/v2/keypairs_test.go b/internal/acceptance/openstack/compute/v2/keypairs_test.go index f3a0ac7e32..6051dff117 100644 --- a/internal/acceptance/openstack/compute/v2/keypairs_test.go +++ b/internal/acceptance/openstack/compute/v2/keypairs_test.go @@ -57,7 +57,7 @@ func TestKeyPairsCreateDelete(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestKeyPairsImportPublicKey(t *testing.T) { @@ -94,7 +94,7 @@ func TestKeyPairsServerCreateWithKey(t *testing.T) { server, err = servers.Get(context.TODO(), client, server.ID).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, server.KeyName, keyPair.Name) + th.AssertEquals(t, keyPair.Name, server.KeyName) } func TestKeyPairsCreateDeleteByID(t *testing.T) { @@ -146,7 +146,7 @@ func TestKeyPairsCreateDeleteByID(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) deleteOpts := keypairs.DeleteOpts{ UserID: user.ID, diff --git a/internal/acceptance/openstack/compute/v2/limits_test.go b/internal/acceptance/openstack/compute/v2/limits_test.go index 55ef2854f2..a4240ee5e0 100644 --- a/internal/acceptance/openstack/compute/v2/limits_test.go +++ b/internal/acceptance/openstack/compute/v2/limits_test.go @@ -22,7 +22,7 @@ func TestLimits(t *testing.T) { tools.PrintResource(t, limits) - th.AssertEquals(t, limits.Absolute.MaxPersonalitySize, 10240) + th.AssertEquals(t, 10240, limits.Absolute.MaxPersonalitySize) } func TestLimitsForTenant(t *testing.T) { @@ -47,5 +47,5 @@ func TestLimitsForTenant(t *testing.T) { tools.PrintResource(t, limits) - th.AssertEquals(t, limits.Absolute.MaxPersonalitySize, 10240) + th.AssertEquals(t, 10240, limits.Absolute.MaxPersonalitySize) } diff --git a/internal/acceptance/openstack/compute/v2/migrate_test.go b/internal/acceptance/openstack/compute/v2/migrate_test.go index f22d6e67c6..6db845c0b3 100644 --- a/internal/acceptance/openstack/compute/v2/migrate_test.go +++ b/internal/acceptance/openstack/compute/v2/migrate_test.go @@ -31,7 +31,7 @@ func TestMigrate(t *testing.T) { func TestLiveMigrate(t *testing.T) { clients.RequireLong(t) clients.RequireAdmin(t) - clients.RequireLiveMigration(t) + RequireLiveMigration(t) client, err := clients.NewComputeV2Client() th.AssertNoErr(t, err) diff --git a/internal/acceptance/openstack/compute/v2/quotaset_test.go b/internal/acceptance/openstack/compute/v2/quotaset_test.go index 78ebbae2f0..d2da0c4193 100644 --- a/internal/acceptance/openstack/compute/v2/quotaset_test.go +++ b/internal/acceptance/openstack/compute/v2/quotaset_test.go @@ -31,7 +31,7 @@ func TestQuotasetGet(t *testing.T) { tools.PrintResource(t, quotaSet) - th.AssertEquals(t, quotaSet.FixedIPs, -1) + th.AssertEquals(t, -1, quotaSet.FixedIPs) } func getProjectID(t *testing.T, client *gophercloud.ServiceClient) (string, error) { @@ -45,7 +45,7 @@ func getProjectID(t *testing.T, client *gophercloud.ServiceClient) (string, erro return project.ID, nil } - return "", fmt.Errorf("Unable to get project ID") + return "", fmt.Errorf("unable to get project ID") } func getProjectIDByName(t *testing.T, client *gophercloud.ServiceClient, name string) (string, error) { @@ -61,7 +61,7 @@ func getProjectIDByName(t *testing.T, client *gophercloud.ServiceClient, name st } } - return "", fmt.Errorf("Unable to get project ID") + return "", fmt.Errorf("unable to get project ID") } // What will be sent as desired Quotas to the Server diff --git a/internal/acceptance/openstack/compute/v2/secgroup_test.go b/internal/acceptance/openstack/compute/v2/secgroup_test.go index 0809bb0dda..aa312a8239 100644 --- a/internal/acceptance/openstack/compute/v2/secgroup_test.go +++ b/internal/acceptance/openstack/compute/v2/secgroup_test.go @@ -32,7 +32,7 @@ func TestSecGroupsList(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestSecGroupsCRUD(t *testing.T) { @@ -48,7 +48,7 @@ func TestSecGroupsCRUD(t *testing.T) { newName := tools.RandomString("secgroup_", 4) description := "" updateOpts := secgroups.UpdateOpts{ - Name: newName, + Name: &newName, Description: &description, } updatedSecurityGroup, err := secgroups.Update(context.TODO(), client, securityGroup.ID, updateOpts).Extract() @@ -58,8 +58,8 @@ func TestSecGroupsCRUD(t *testing.T) { t.Logf("Updated %s's name to %s", updatedSecurityGroup.ID, updatedSecurityGroup.Name) - th.AssertEquals(t, updatedSecurityGroup.Name, newName) - th.AssertEquals(t, updatedSecurityGroup.Description, description) + th.AssertEquals(t, newName, updatedSecurityGroup.Name) + th.AssertEquals(t, description, updatedSecurityGroup.Description) } func TestSecGroupsRuleCreate(t *testing.T) { @@ -83,7 +83,7 @@ func TestSecGroupsRuleCreate(t *testing.T) { tools.PrintResource(t, newSecurityGroup) - th.AssertEquals(t, len(newSecurityGroup.Rules), 1) + th.AssertEquals(t, 1, len(newSecurityGroup.Rules)) } func TestSecGroupsAddGroupToServer(t *testing.T) { @@ -120,7 +120,7 @@ func TestSecGroupsAddGroupToServer(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) t.Logf("Removing group %s from server %s", securityGroup.ID, server.ID) err = secgroups.RemoveServer(context.TODO(), client, server.ID, securityGroup.Name).ExtractErr() @@ -139,5 +139,5 @@ func TestSecGroupsAddGroupToServer(t *testing.T) { } } - th.AssertEquals(t, found, false) + th.AssertFalse(t, found) } diff --git a/internal/acceptance/openstack/compute/v2/servergroup_test.go b/internal/acceptance/openstack/compute/v2/servergroup_test.go index 8d23ea8135..4f59ddb1ad 100644 --- a/internal/acceptance/openstack/compute/v2/servergroup_test.go +++ b/internal/acceptance/openstack/compute/v2/servergroup_test.go @@ -23,6 +23,7 @@ func TestServergroupsCreateDelete(t *testing.T) { serverGroup, err = servergroups.Get(context.TODO(), client, serverGroup.ID).Extract() th.AssertNoErr(t, err) + th.AssertDeepEquals(t, []string{"anti-affinity"}, serverGroup.Policies) tools.PrintResource(t, serverGroup) @@ -41,7 +42,7 @@ func TestServergroupsCreateDelete(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestServergroupsAffinityPolicy(t *testing.T) { @@ -82,6 +83,14 @@ func TestServergroupsMicroversionCreateDelete(t *testing.T) { serverGroup, err = servergroups.Get(context.TODO(), client, serverGroup.ID).Extract() th.AssertNoErr(t, err) + if serverGroup.Policy == nil { + t.Fatal("Expected Policy to be non-nil") + } + th.AssertEquals(t, "anti-affinity", *serverGroup.Policy) + if serverGroup.Rules == nil { + t.Fatal("Expected Rules to be non-nil") + } + th.AssertEquals(t, 3, serverGroup.Rules.MaxServerPerHost) tools.PrintResource(t, serverGroup) @@ -100,5 +109,5 @@ func TestServergroupsMicroversionCreateDelete(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } diff --git a/internal/acceptance/openstack/compute/v2/servers_test.go b/internal/acceptance/openstack/compute/v2/servers_test.go index 9aa8133de9..cfc36323b8 100644 --- a/internal/acceptance/openstack/compute/v2/servers_test.go +++ b/internal/acceptance/openstack/compute/v2/servers_test.go @@ -45,7 +45,7 @@ func TestServersCreateDestroy(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) allAddressPages, err := servers.ListAddresses(client, server.ID).AllPages(context.TODO()) th.AssertNoErr(t, err) @@ -93,12 +93,12 @@ func TestServersWithExtensionsCreateDestroy(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, created) - th.AssertEquals(t, created.AvailabilityZone, "nova") - th.AssertEquals(t, int(created.PowerState), servers.RUNNING) - th.AssertEquals(t, created.TaskState, "") - th.AssertEquals(t, created.VmState, "active") - th.AssertEquals(t, created.LaunchedAt.IsZero(), false) - th.AssertEquals(t, created.TerminatedAt.IsZero(), true) + th.AssertEquals(t, "nova", created.AvailabilityZone) + th.AssertEquals(t, servers.RUNNING, int(created.PowerState)) + th.AssertEquals(t, "", created.TaskState) + th.AssertEquals(t, "active", created.VmState) + th.AssertFalse(t, created.LaunchedAt.IsZero()) + th.AssertTrue(t, created.TerminatedAt.IsZero()) } func TestServersWithoutImageRef(t *testing.T) { @@ -133,13 +133,13 @@ func TestServersUpdate(t *testing.T) { t.Logf("Attempting to rename the server to %s.", alternateName) updateOpts := servers.UpdateOpts{ - Name: alternateName, + Name: &alternateName, } updated, err := servers.Update(context.TODO(), client, server.ID, updateOpts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, updated.ID, server.ID) + th.AssertEquals(t, server.ID, updated.ID) err = tools.WaitFor(func(ctx context.Context) (bool, error) { latest, err := servers.Get(ctx, client, updated.ID).Extract() @@ -234,7 +234,7 @@ func TestServersMetadata(t *testing.T) { func TestServersActionChangeAdminPassword(t *testing.T) { clients.RequireLong(t) - clients.RequireGuestAgent(t) + RequireGuestAgent(t) client, err := clients.NewComputeV2Client() th.AssertNoErr(t, err) @@ -307,7 +307,8 @@ func TestServersActionRebuild(t *testing.T) { rebuilt, err := servers.Rebuild(context.TODO(), client, server.ID, rebuildOpts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, rebuilt.ID, server.ID) + th.AssertEquals(t, server.ID, rebuilt.ID) + th.AssertEquals(t, rebuildOpts.Name, rebuilt.Name) if err = WaitForComputeStatus(client, rebuilt, "REBUILD"); err != nil { t.Fatal(err) @@ -347,7 +348,7 @@ func TestServersActionResizeConfirm(t *testing.T) { server, err = servers.Get(context.TODO(), client, server.ID).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, server.Flavor["id"], choices.FlavorIDResize) + th.AssertEquals(t, choices.FlavorIDResize, server.Flavor["id"]) } func TestServersActionResizeRevert(t *testing.T) { @@ -379,7 +380,7 @@ func TestServersActionResizeRevert(t *testing.T) { server, err = servers.Get(context.TODO(), client, server.ID).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, server.Flavor["id"], choices.FlavorID) + th.AssertEquals(t, choices.FlavorID, server.Flavor["id"]) } func TestServersActionPause(t *testing.T) { @@ -447,7 +448,7 @@ func TestServersActionLock(t *testing.T) { t.Logf("Attempting to delete locked server %s", server.ID) err = servers.Delete(context.TODO(), client, server.ID).ExtractErr() - th.AssertEquals(t, err != nil, true) + th.AssertTrue(t, err != nil) t.Logf("Attempting to unlock server %s", server.ID) err = servers.Unlock(context.TODO(), client, server.ID).ExtractErr() @@ -513,7 +514,7 @@ func TestServersTags(t *testing.T) { // Check single tag. exists, err := tags.Check(context.TODO(), client, server.ID, "tag2").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, true, exists) + th.AssertTrue(t, exists) // Add new tag. newTags, err := tags.ReplaceAll(context.TODO(), client, server.ID, tags.ReplaceAllOpts{Tags: []string{"tag3", "tag4"}}).Extract() @@ -536,7 +537,7 @@ func TestServersTags(t *testing.T) { // Check that tag doesn't exist anymore. exists, err = tags.Check(context.TODO(), client, server.ID, "tag4").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, false, exists) + th.AssertFalse(t, exists) // Remove all tags. err = tags.DeleteAll(context.TODO(), client, server.ID).ExtractErr() @@ -565,13 +566,13 @@ func TestServersWithExtendedAttributesCreateDestroy(t *testing.T) { t.Logf("Server With Extended Attributes: %#v", created) - th.AssertEquals(t, *created.ReservationID != "", true) - th.AssertEquals(t, *created.LaunchIndex, 0) - th.AssertEquals(t, *created.RAMDiskID == "", true) - th.AssertEquals(t, *created.KernelID == "", true) - th.AssertEquals(t, *created.Hostname != "", true) - th.AssertEquals(t, *created.RootDeviceName != "", true) - th.AssertEquals(t, created.Userdata == nil, true) + th.AssertTrue(t, *created.ReservationID != "") + th.AssertEquals(t, 0, *created.LaunchIndex) + th.AssertTrue(t, *created.RAMDiskID == "") + th.AssertTrue(t, *created.KernelID == "") + th.AssertTrue(t, *created.Hostname != "") + th.AssertTrue(t, *created.RootDeviceName != "") + th.AssertTrue(t, created.Userdata == nil) } func TestServerNoNetworkCreateDestroy(t *testing.T) { @@ -604,7 +605,7 @@ func TestServerNoNetworkCreateDestroy(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) allAddressPages, err := servers.ListAddresses(client, server.ID).AllPages(context.TODO()) th.AssertNoErr(t, err) @@ -631,3 +632,40 @@ func TestServerNoNetworkCreateDestroy(t *testing.T) { t.Fatalf("Instance must not be a member of specified network") } } + +func TestServersUpdateHostname(t *testing.T) { + clients.RequireLong(t) + + client, err := clients.NewComputeV2Client() + th.AssertNoErr(t, err) + client.Microversion = "2.90" + + server, err := CreateMicroversionServer(t, client) + th.AssertNoErr(t, err) + defer DeleteServer(t, client, server) + + alternateHostname := tools.RandomString("ACPTTEST", 16) + for alternateHostname == *server.Hostname { + alternateHostname = tools.RandomString("ACPTTEST", 16) + } + + t.Logf("Attempting to change the server's hostname to %s.", alternateHostname) + + updateOpts := servers.UpdateOpts{ + Hostname: &alternateHostname, + } + + updated, err := servers.Update(context.TODO(), client, server.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, server.ID, updated.ID) + + err = tools.WaitFor(func(ctx context.Context) (bool, error) { + latest, err := servers.Get(ctx, client, updated.ID).Extract() + if err != nil { + return false, err + } + return *latest.Hostname == alternateHostname, nil + }) + th.AssertNoErr(t, err) +} diff --git a/internal/acceptance/openstack/compute/v2/services_test.go b/internal/acceptance/openstack/compute/v2/services_test.go index 30b1613aa5..80f60de47b 100644 --- a/internal/acceptance/openstack/compute/v2/services_test.go +++ b/internal/acceptance/openstack/compute/v2/services_test.go @@ -33,7 +33,7 @@ func TestServicesList(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestServicesListWithOpts(t *testing.T) { @@ -55,12 +55,12 @@ func TestServicesListWithOpts(t *testing.T) { var found bool for _, service := range allServices { tools.PrintResource(t, service) - th.AssertEquals(t, service.Binary, "nova-scheduler") + th.AssertEquals(t, "nova-scheduler", service.Binary) if service.Binary == "nova-scheduler" { found = true } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } diff --git a/internal/acceptance/openstack/compute/v2/volumeattach_test.go b/internal/acceptance/openstack/compute/v2/volumeattach_test.go index d4cce8d700..157f97c4c0 100644 --- a/internal/acceptance/openstack/compute/v2/volumeattach_test.go +++ b/internal/acceptance/openstack/compute/v2/volumeattach_test.go @@ -35,5 +35,5 @@ func TestVolumeAttachAttachment(t *testing.T) { tools.PrintResource(t, volumeAttachment) - th.AssertEquals(t, volumeAttachment.ServerID, server.ID) + th.AssertEquals(t, server.ID, volumeAttachment.ServerID) } diff --git a/internal/acceptance/openstack/container/v1/capsules.go b/internal/acceptance/openstack/container/v1/capsules.go index b4f48fc903..6ad33c0c6d 100644 --- a/internal/acceptance/openstack/container/v1/capsules.go +++ b/internal/acceptance/openstack/container/v1/capsules.go @@ -36,11 +36,11 @@ func WaitForCapsuleStatus(client *gophercloud.ServiceClient, uuid, status string } if newStatus == "Failed" { - return false, fmt.Errorf("Capsule in FAILED state") + return false, fmt.Errorf("capsule in FAILED state") } if newStatus == "Error" { - return false, fmt.Errorf("Capsule in ERROR state") + return false, fmt.Errorf("capsule in ERROR state") } return false, nil diff --git a/internal/acceptance/openstack/container/v1/capsules_test.go b/internal/acceptance/openstack/container/v1/capsules_test.go index 5da7010948..57227ef5e1 100644 --- a/internal/acceptance/openstack/container/v1/capsules_test.go +++ b/internal/acceptance/openstack/container/v1/capsules_test.go @@ -13,8 +13,6 @@ import ( ) func TestCapsuleBase(t *testing.T) { - t.Skip("Currently failing in OpenLab") - clients.SkipRelease(t, "stable/mitaka") clients.SkipRelease(t, "stable/newton") clients.SkipRelease(t, "stable/ocata") @@ -52,7 +50,7 @@ func TestCapsuleBase(t *testing.T) { capsule, err := capsules.Get(context.TODO(), client, capsuleUUID).ExtractBase() th.AssertNoErr(t, err) - th.AssertEquals(t, capsule.MetaName, "template") + th.AssertEquals(t, "template", capsule.MetaName) err = capsules.Delete(context.TODO(), client, capsuleUUID).ExtractErr() th.AssertNoErr(t, err) @@ -64,8 +62,6 @@ func TestCapsuleBase(t *testing.T) { } func TestCapsuleV132(t *testing.T) { - t.Skip("Currently failing in OpenLab") - clients.SkipRelease(t, "stable/mitaka") clients.SkipRelease(t, "stable/newton") clients.SkipRelease(t, "stable/ocata") @@ -105,7 +101,7 @@ func TestCapsuleV132(t *testing.T) { capsule, err := capsules.Get(context.TODO(), client, capsuleUUID).ExtractV132() th.AssertNoErr(t, err) - th.AssertEquals(t, capsule.MetaName, "template") + th.AssertEquals(t, "template", capsule.MetaName) err = capsules.Delete(context.TODO(), client, capsuleUUID).ExtractErr() th.AssertNoErr(t, err) diff --git a/internal/acceptance/openstack/containerinfra/v1/clusters_test.go b/internal/acceptance/openstack/containerinfra/v1/clusters_test.go index 7a277b318a..c1ff6ef771 100644 --- a/internal/acceptance/openstack/containerinfra/v1/clusters_test.go +++ b/internal/acceptance/openstack/containerinfra/v1/clusters_test.go @@ -39,7 +39,7 @@ func TestClustersCRUD(t *testing.T) { found = true } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) updateOpts := []clusters.UpdateOptsBuilder{ clusters.UpdateOpts{ Op: clusters.ReplaceOp, @@ -63,6 +63,7 @@ func TestClustersCRUD(t *testing.T) { newCluster, err := clusters.Get(context.TODO(), client, clusterID).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, newCluster.UUID, clusterID) + th.AssertFalse(t, newCluster.MasterLBEnabled) allPagesDetail, err := clusters.ListDetail(client, nil).AllPages(context.TODO()) th.AssertNoErr(t, err) @@ -76,7 +77,7 @@ func TestClustersCRUD(t *testing.T) { foundDetail = true } } - th.AssertEquals(t, foundDetail, true) + th.AssertTrue(t, foundDetail) tools.PrintResource(t, newCluster) } diff --git a/internal/acceptance/openstack/containerinfra/v1/clustertemplates_test.go b/internal/acceptance/openstack/containerinfra/v1/clustertemplates_test.go index fec59e8229..1e42c3fcf4 100644 --- a/internal/acceptance/openstack/containerinfra/v1/clustertemplates_test.go +++ b/internal/acceptance/openstack/containerinfra/v1/clustertemplates_test.go @@ -36,7 +36,7 @@ func TestClusterTemplatesCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) template, err := clustertemplates.Get(context.TODO(), client, clusterTemplate.UUID).Extract() th.AssertNoErr(t, err) @@ -63,8 +63,8 @@ func TestClusterTemplatesCRUD(t *testing.T) { updateClusterTemplate, err := clustertemplates.Update(context.TODO(), client, clusterTemplate.UUID, updateOpts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, false, updateClusterTemplate.MasterLBEnabled) - th.AssertEquals(t, false, updateClusterTemplate.RegistryEnabled) + th.AssertFalse(t, updateClusterTemplate.MasterLBEnabled) + th.AssertFalse(t, updateClusterTemplate.RegistryEnabled) th.AssertEquals(t, "test", updateClusterTemplate.Labels["test"]) tools.PrintResource(t, updateClusterTemplate) diff --git a/internal/acceptance/openstack/containerinfra/v1/containerinfra.go b/internal/acceptance/openstack/containerinfra/v1/containerinfra.go index 9b26c0ad2d..2ebe98ee6c 100644 --- a/internal/acceptance/openstack/containerinfra/v1/containerinfra.go +++ b/internal/acceptance/openstack/containerinfra/v1/containerinfra.go @@ -31,6 +31,9 @@ func CreateClusterTemplateCOE(t *testing.T, client *gophercloud.ServiceClient, c t.Logf("Attempting to create %s cluster template: %s", coe, name) boolFalse := false + labels := map[string]string{ + "test": "test", + } createOpts := clustertemplates.CreateOpts{ COE: coe, DNSNameServer: "8.8.8.8", @@ -45,6 +48,8 @@ func CreateClusterTemplateCOE(t *testing.T, client *gophercloud.ServiceClient, c Public: &boolFalse, RegistryEnabled: &boolFalse, ServerType: "vm", + // workaround for https://bugs.launchpad.net/magnum/+bug/2109685 + Labels: labels, } res := clustertemplates.Create(context.TODO(), client, createOpts) @@ -53,7 +58,7 @@ func CreateClusterTemplateCOE(t *testing.T, client *gophercloud.ServiceClient, c } requestID := res.Header.Get("X-OpenStack-Request-Id") - th.AssertEquals(t, true, requestID != "") + th.AssertTrue(t, requestID != "") t.Logf("Cluster Template %s request ID: %s", name, requestID) @@ -68,8 +73,16 @@ func CreateClusterTemplateCOE(t *testing.T, client *gophercloud.ServiceClient, c tools.PrintResource(t, clusterTemplate.CreatedAt) th.AssertEquals(t, name, clusterTemplate.Name) + th.AssertDeepEquals(t, labels, clusterTemplate.Labels) th.AssertEquals(t, choices.ExternalNetworkID, clusterTemplate.ExternalNetworkID) th.AssertEquals(t, choices.MagnumImageID, clusterTemplate.ImageID) + th.AssertEquals(t, coe, clusterTemplate.COE) + th.AssertEquals(t, "8.8.8.8", clusterTemplate.DNSNameServer) + th.AssertEquals(t, "overlay2", clusterTemplate.DockerStorageDriver) + th.AssertFalse(t, clusterTemplate.FloatingIPEnabled) + th.AssertFalse(t, clusterTemplate.Public) + th.AssertFalse(t, clusterTemplate.RegistryEnabled) + th.AssertEquals(t, "vm", clusterTemplate.ServerType) return clusterTemplate, nil } @@ -197,7 +210,7 @@ func WaitForCluster(client *gophercloud.ServiceClient, clusterID string, status } if strings.Contains(cluster.Status, "FAILED") { - return false, fmt.Errorf("Cluster %s FAILED. Status=%s StatusReason=%s", clusterID, cluster.Status, cluster.StatusReason) + return false, fmt.Errorf("cluster %s FAILED. Status=%s StatusReason=%s", clusterID, cluster.Status, cluster.StatusReason) } return false, nil @@ -229,7 +242,7 @@ func CreateQuota(t *testing.T, client *gophercloud.ServiceClient) (*quotas.Quota } requestID := res.Header.Get("X-OpenStack-Request-Id") - th.AssertEquals(t, true, requestID != "") + th.AssertTrue(t, requestID != "") t.Logf("Quota %s request ID: %s", name, requestID) diff --git a/internal/acceptance/openstack/containerinfra/v1/nodegroups_test.go b/internal/acceptance/openstack/containerinfra/v1/nodegroups_test.go index d6493fa338..0cc10e6dcc 100644 --- a/internal/acceptance/openstack/containerinfra/v1/nodegroups_test.go +++ b/internal/acceptance/openstack/containerinfra/v1/nodegroups_test.go @@ -137,7 +137,7 @@ func testNodeGroupUpdate(t *testing.T, client *gophercloud.ServiceClient, cluste } ng, err = nodegroups.Update(context.TODO(), client, clusterID, nodeGroupID, updateOpts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, false, ng.MaxNodeCount == nil) + th.AssertFalse(t, ng.MaxNodeCount == nil) th.AssertEquals(t, 5, *ng.MaxNodeCount) updateOpts = []nodegroups.UpdateOptsBuilder{ @@ -154,7 +154,7 @@ func testNodeGroupUpdate(t *testing.T, client *gophercloud.ServiceClient, cluste } ng, err = nodegroups.Update(context.TODO(), client, clusterID, nodeGroupID, updateOpts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, false, ng.MaxNodeCount == nil) + th.AssertFalse(t, ng.MaxNodeCount == nil) th.AssertEquals(t, 1, ng.MinNodeCount) th.AssertEquals(t, 3, *ng.MaxNodeCount) } diff --git a/internal/acceptance/openstack/db/v1/configurations_test.go b/internal/acceptance/openstack/db/v1/configurations_test.go index 51058e16ba..4531bb13ba 100644 --- a/internal/acceptance/openstack/db/v1/configurations_test.go +++ b/internal/acceptance/openstack/db/v1/configurations_test.go @@ -49,19 +49,19 @@ func TestConfigurationsCRUD(t *testing.T) { } tools.PrintResource(t, readCgroup) - th.AssertEquals(t, readCgroup.Name, createOpts.Name) - th.AssertEquals(t, readCgroup.Description, createOpts.Description) + th.AssertEquals(t, createOpts.Name, readCgroup.Name) + th.AssertEquals(t, createOpts.Description, readCgroup.Description) // TODO: verify datastore //th.AssertDeepEquals(t, readCgroup.Datastore, datastore) // Update cgroup newCgroupName := "New configuration name" - newCgroupDescription := "" + newCgroupDescription := "Updated description" updateOpts := configurations.UpdateOpts{ Name: newCgroupName, Description: &newCgroupDescription, } - err = configurations.Update(context.TODO(), client, cgroup.ID, updateOpts).ExtractErr() + err = configurations.Replace(context.TODO(), client, cgroup.ID, updateOpts).ExtractErr() th.AssertNoErr(t, err) newCgroup, err := configurations.Get(context.TODO(), client, cgroup.ID).Extract() @@ -70,8 +70,8 @@ func TestConfigurationsCRUD(t *testing.T) { } tools.PrintResource(t, newCgroup) - th.AssertEquals(t, newCgroup.Name, newCgroupName) - th.AssertEquals(t, newCgroup.Description, newCgroupDescription) + th.AssertEquals(t, newCgroupName, newCgroup.Name) + th.AssertEquals(t, newCgroupDescription, newCgroup.Description) err = configurations.Delete(context.TODO(), client, cgroup.ID).ExtractErr() if err != nil { diff --git a/internal/acceptance/openstack/db/v1/db.go b/internal/acceptance/openstack/db/v1/db.go index abc92553a0..fc1fc60786 100644 --- a/internal/acceptance/openstack/db/v1/db.go +++ b/internal/acceptance/openstack/db/v1/db.go @@ -13,6 +13,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/db/v1/databases" "github.com/gophercloud/gophercloud/v2/openstack/db/v1/instances" "github.com/gophercloud/gophercloud/v2/openstack/db/v1/users" + th "github.com/gophercloud/gophercloud/v2/testhelper" ) // CreateDatabase will create a database with a randomly generated name. @@ -67,7 +68,14 @@ func CreateInstance(t *testing.T, client *gophercloud.ServiceClient) (*instances return instance, err } - return instances.Get(context.TODO(), client, instance.ID).Extract() + result, err := instances.Get(context.TODO(), client, instance.ID).Extract() + if err != nil { + return result, err + } + + th.AssertEquals(t, name, result.Name) + + return result, nil } // CreateUser will create a user with a randomly generated name. @@ -138,7 +146,7 @@ func WaitForInstanceStatus( } if latest.Status == "ERROR" { - return false, fmt.Errorf("Instance in ERROR state") + return false, fmt.Errorf("instance in ERROR state") } return false, nil diff --git a/internal/acceptance/openstack/dns/v2/dns.go b/internal/acceptance/openstack/dns/v2/dns.go index c31f238a6e..4140f601cf 100644 --- a/internal/acceptance/openstack/dns/v2/dns.go +++ b/internal/acceptance/openstack/dns/v2/dns.go @@ -9,6 +9,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/dns/v2/recordsets" transferAccepts "github.com/gophercloud/gophercloud/v2/openstack/dns/v2/transfer/accept" transferRequests "github.com/gophercloud/gophercloud/v2/openstack/dns/v2/transfer/request" + "github.com/gophercloud/gophercloud/v2/openstack/dns/v2/tsigkeys" "github.com/gophercloud/gophercloud/v2/openstack/dns/v2/zones" th "github.com/gophercloud/gophercloud/v2/testhelper" ) @@ -42,7 +43,10 @@ func CreateRecordSet(t *testing.T, client *gophercloud.ServiceClient, zone *zone t.Logf("Created record set: %s", newRS.Name) - th.AssertEquals(t, newRS.Name, zone.Name) + th.AssertEquals(t, zone.Name, newRS.Name) + th.AssertEquals(t, "A", newRS.Type) + th.AssertEquals(t, 3600, newRS.TTL) + th.AssertEquals(t, "Test recordset", newRS.Description) return rs, nil } @@ -77,8 +81,11 @@ func CreateZone(t *testing.T, client *gophercloud.ServiceClient) (*zones.Zone, e t.Logf("Created Zone: %s", zoneName) - th.AssertEquals(t, newZone.Name, zoneName) - th.AssertEquals(t, newZone.TTL, 7200) + th.AssertEquals(t, zoneName, newZone.Name) + th.AssertEquals(t, "root@example.com", newZone.Email) + th.AssertEquals(t, "PRIMARY", newZone.Type) + th.AssertEquals(t, "Test zone", newZone.Description) + th.AssertEquals(t, 7200, newZone.TTL) return newZone, nil } @@ -113,8 +120,9 @@ func CreateSecondaryZone(t *testing.T, client *gophercloud.ServiceClient) (*zone t.Logf("Created Zone: %s", zoneName) - th.AssertEquals(t, newZone.Name, zoneName) - th.AssertEquals(t, newZone.Masters[0], "10.0.0.1") + th.AssertEquals(t, zoneName, newZone.Name) + th.AssertEquals(t, "SECONDARY", newZone.Type) + th.AssertEquals(t, "10.0.0.1", newZone.Masters[0]) return newZone, nil } @@ -147,6 +155,8 @@ func CreateTransferRequest(t *testing.T, client *gophercloud.ServiceClient, zone th.AssertEquals(t, newTransferRequest.ZoneID, zone.ID) th.AssertEquals(t, newTransferRequest.ZoneName, zone.Name) + th.AssertEquals(t, targetProjectID, newTransferRequest.TargetProjectID) + th.AssertEquals(t, "Test transfer request", newTransferRequest.Description) return newTransferRequest, nil } @@ -186,6 +196,41 @@ func DeleteTransferRequest(t *testing.T, client *gophercloud.ServiceClient, tr * t.Logf("Deleted zone transfer request: %s", tr.ID) } +// CreateShare will create a zone share. An error will be returned if the +// zone share was unable to be created. +func CreateShare(t *testing.T, client *gophercloud.ServiceClient, zone *zones.Zone, targetProjectID string) (*zones.ZoneShare, error) { + t.Logf("Attempting to share zone %s with project %s", zone.ID, targetProjectID) + + createOpts := zones.ShareZoneOpts{ + TargetProjectID: targetProjectID, + } + + share, err := zones.Share(context.TODO(), client, zone.ID, createOpts).Extract() + if err != nil { + return share, err + } + + t.Logf("Created share for zone: %s", zone.ID) + + th.AssertEquals(t, share.ZoneID, zone.ID) + th.AssertEquals(t, share.TargetProjectID, targetProjectID) + + return share, nil +} + +// UnshareZone will unshare a zone. An error will be returned if the +// zone unshare was unable to be created. +func UnshareZone(t *testing.T, client *gophercloud.ServiceClient, share *zones.ZoneShare) { + t.Logf("Attempting to unshare zone %s with project %s", share.ZoneID, share.TargetProjectID) + + err := zones.Unshare(context.TODO(), client, share.ZoneID, share.ID).ExtractErr() + if err != nil { + t.Fatalf("Unable to unshare zone %s: %v", share.ZoneID, err) + } + + t.Logf("Unshared zone: %s", share.ZoneID) +} + // DeleteRecordSet will delete a specified record set. A fatal error will occur if // the record set failed to be deleted. This works best when used as a deferred // function. @@ -273,3 +318,48 @@ func WaitForZoneStatus(client *gophercloud.ServiceClient, zone *zones.Zone, stat return false, nil }) } + +// CreateTSIGKey will create a TSIG key with a random name. An error will +// be returned if the TSIG key was unable to be created. +func CreateTSIGKey(t *testing.T, client *gophercloud.ServiceClient) (*tsigkeys.TSIGKey, error) { + keyName := tools.RandomString("ACPTTEST", 8) + + t.Logf("Attempting to create TSIG key: %s", keyName) + createOpts := tsigkeys.CreateOpts{ + Name: keyName, + Algorithm: "hmac-sha256", + Secret: "example-test-secret-key==", + Scope: "POOL", + // Default pool ID from designate/conf/central.py + ResourceID: "794ccc2c-d751-44fe-b57f-8894c9f5c842", + } + + tsigkey, err := tsigkeys.Create(context.TODO(), client, createOpts).Extract() + if err != nil { + return tsigkey, err + } + + newTSIGKey, err := tsigkeys.Get(context.TODO(), client, tsigkey.ID).Extract() + if err != nil { + return tsigkey, err + } + + t.Logf("Created TSIG key: %s", keyName) + + th.AssertEquals(t, newTSIGKey.Name, keyName) + th.AssertEquals(t, "hmac-sha256", newTSIGKey.Algorithm) + + return newTSIGKey, nil +} + +// DeleteTSIGKey will delete a specified TSIG key. A fatal error will occur if +// the TSIG key failed to be deleted. This works best when used as a deferred +// function. +func DeleteTSIGKey(t *testing.T, client *gophercloud.ServiceClient, tsigkey *tsigkeys.TSIGKey) { + err := tsigkeys.Delete(context.TODO(), client, tsigkey.ID).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete TSIG key %s: %v", tsigkey.ID, err) + } + + t.Logf("Deleted TSIG key: %s", tsigkey.ID) +} diff --git a/internal/acceptance/openstack/dns/v2/quotas_test.go b/internal/acceptance/openstack/dns/v2/quotas_test.go new file mode 100644 index 0000000000..673d9c9761 --- /dev/null +++ b/internal/acceptance/openstack/dns/v2/quotas_test.go @@ -0,0 +1,52 @@ +//go:build acceptance || dns || quotas + +package v2 + +import ( + "context" + "testing" + + "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" + identity "github.com/gophercloud/gophercloud/v2/internal/acceptance/openstack/identity/v3" + "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" + "github.com/gophercloud/gophercloud/v2/openstack/dns/v2/quotas" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func TestQuotaGetUpdate(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewDNSV2Client() + th.AssertNoErr(t, err) + + identityClient, err := clients.NewIdentityV3Client() + th.AssertNoErr(t, err) + + project, err := identity.CreateProject(t, identityClient, nil) + th.AssertNoErr(t, err) + defer identity.DeleteProject(t, identityClient, project.ID) + + // use DNS specific header to set the project ID + client.MoreHeaders = map[string]string{ + "X-Auth-Sudo-Tenant-ID": project.ID, + } + + // test Get Quota + quota, err := quotas.Get(context.TODO(), client, project.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, quota) + + // test Update Quota + zones := 9 + updateOpts := quotas.UpdateOpts{ + Zones: &zones, + } + res, err := quotas.Update(context.TODO(), client, project.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, res) + + quota.Zones = zones + th.AssertDeepEquals(t, *quota, *res) +} diff --git a/internal/acceptance/openstack/dns/v2/recordsets_test.go b/internal/acceptance/openstack/dns/v2/recordsets_test.go index fd2e4d41bf..20d4408979 100644 --- a/internal/acceptance/openstack/dns/v2/recordsets_test.go +++ b/internal/acceptance/openstack/dns/v2/recordsets_test.go @@ -36,7 +36,7 @@ func TestRecordSetsListByZone(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) listOpts := recordsets.ListOpts{ Limit: 1, @@ -46,12 +46,55 @@ func TestRecordSetsListByZone(t *testing.T) { err = pager.EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { rr, err := recordsets.ExtractRecordSets(page) th.AssertNoErr(t, err) - th.AssertEquals(t, len(rr), 1) + th.AssertEquals(t, 1, len(rr)) return true, nil }) th.AssertNoErr(t, err) } +func TestRecordSetsListAll(t *testing.T) { + client, err := clients.NewDNSV2Client() + th.AssertNoErr(t, err) + + zone, err := CreateZone(t, client) + th.AssertNoErr(t, err) + defer DeleteZone(t, client, zone) + + rs, err := CreateRecordSet(t, client, zone) + th.AssertNoErr(t, err) + defer DeleteRecordSet(t, client, rs) + + allPages, err := recordsets.ListAll(client, nil).AllPages(context.TODO()) + th.AssertNoErr(t, err) + + allRecordSets, err := recordsets.ExtractRecordSets(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, recordset := range allRecordSets { + tools.PrintResource(t, &recordset) + + if recordset.ID == rs.ID { + found = true + } + } + + th.AssertTrue(t, found) + + listOpts := recordsets.ListOpts{ + Limit: 1, + } + + pager := recordsets.ListAll(client, listOpts) + err = pager.EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + rr, err := recordsets.ExtractRecordSets(page) + th.AssertNoErr(t, err) + th.AssertEquals(t, 1, len(rr)) + return false, nil + }) + th.AssertNoErr(t, err) +} + func TestRecordSetsCRUD(t *testing.T) { client, err := clients.NewDNSV2Client() th.AssertNoErr(t, err) @@ -91,7 +134,7 @@ func TestRecordSetsCRUD(t *testing.T) { tools.PrintResource(t, &newRS) th.AssertDeepEquals(t, newRS.Records, records) - th.AssertEquals(t, newRS.TTL, 3600) + th.AssertEquals(t, 3600, newRS.TTL) ttl := 0 updateOpts = recordsets.UpdateOpts{ diff --git a/internal/acceptance/openstack/dns/v2/shares_test.go b/internal/acceptance/openstack/dns/v2/shares_test.go new file mode 100644 index 0000000000..496ee8f1b4 --- /dev/null +++ b/internal/acceptance/openstack/dns/v2/shares_test.go @@ -0,0 +1,66 @@ +//go:build acceptance || dns || zone_shares + +package v2 + +import ( + "context" + "testing" + + "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" + identity "github.com/gophercloud/gophercloud/v2/internal/acceptance/openstack/identity/v3" + "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" + "github.com/gophercloud/gophercloud/v2/openstack/dns/v2/zones" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func TestShareCRD(t *testing.T) { + // Create new project + identityClient, err := clients.NewIdentityV3Client() + th.AssertNoErr(t, err) + + project, err := identity.CreateProject(t, identityClient, nil) + th.AssertNoErr(t, err) + defer identity.DeleteProject(t, identityClient, project.ID) + + // Create new Zone + client, err := clients.NewDNSV2Client() + th.AssertNoErr(t, err) + + zone, err := CreateZone(t, client) + th.AssertNoErr(t, err) + defer DeleteZone(t, client, zone) + + // Create a zone share to new tenant + share, err := CreateShare(t, client, zone, project.ID) + th.AssertNoErr(t, err) + tools.PrintResource(t, share) + defer UnshareZone(t, client, share) + + // Get the share + getShare, err := zones.GetShare(context.TODO(), client, share.ZoneID, share.ID).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, getShare) + th.AssertDeepEquals(t, *share, *getShare) + + // List shares + allPages, err := zones.ListShares(client, share.ZoneID, nil).AllPages(context.TODO()) + th.AssertNoErr(t, err) + + allShares, err := zones.ExtractZoneShares(allPages) + th.AssertNoErr(t, err) + tools.PrintResource(t, allShares) + + foundShare := -1 + for i, s := range allShares { + tools.PrintResource(t, &s) + if share.ID == s.ID { + foundShare = i + break + } + } + if foundShare == -1 { + t.Fatalf("Share %s not found in list", share.ID) + } + + th.AssertDeepEquals(t, *share, allShares[foundShare]) +} diff --git a/internal/acceptance/openstack/dns/v2/transfers_test.go b/internal/acceptance/openstack/dns/v2/transfers_test.go index 1149bfd0b3..f171ddf77e 100644 --- a/internal/acceptance/openstack/dns/v2/transfers_test.go +++ b/internal/acceptance/openstack/dns/v2/transfers_test.go @@ -41,7 +41,7 @@ func TestTransferRequestCRUD(t *testing.T) { foundRequest = true } } - th.AssertEquals(t, foundRequest, true) + th.AssertTrue(t, foundRequest) description := "new description" updateOpts := transferRequests.UpdateOpts{ @@ -93,5 +93,5 @@ func TestTransferRequestAccept(t *testing.T) { foundAccept = true } } - th.AssertEquals(t, foundAccept, true) + th.AssertTrue(t, foundAccept) } diff --git a/internal/acceptance/openstack/dns/v2/tsigkeys_test.go b/internal/acceptance/openstack/dns/v2/tsigkeys_test.go new file mode 100644 index 0000000000..118781c7a7 --- /dev/null +++ b/internal/acceptance/openstack/dns/v2/tsigkeys_test.go @@ -0,0 +1,56 @@ +//go:build acceptance || dns || tsigkeys + +package v2 + +import ( + "context" + "testing" + + "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" + "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" + "github.com/gophercloud/gophercloud/v2/openstack/dns/v2/tsigkeys" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func TestTSIGKeysCRUD(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewDNSV2Client() + th.AssertNoErr(t, err) + + tsigkey, err := CreateTSIGKey(t, client) + th.AssertNoErr(t, err) + defer DeleteTSIGKey(t, client, tsigkey) + + tools.PrintResource(t, &tsigkey) + + allPages, err := tsigkeys.List(client, nil).AllPages(context.TODO()) + th.AssertNoErr(t, err) + + allTSIGKeys, err := tsigkeys.ExtractTSIGKeys(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, k := range allTSIGKeys { + tools.PrintResource(t, &k) + + if tsigkey.Name == k.Name { + found = true + } + } + + th.AssertTrue(t, found) + + updateOpts := tsigkeys.UpdateOpts{ + Name: tsigkey.Name + "-updated", + Secret: "updated-test-secret-key==", + } + + newTSIGKey, err := tsigkeys.Update(context.TODO(), client, tsigkey.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, &newTSIGKey) + + th.AssertEquals(t, newTSIGKey.Name, tsigkey.Name+"-updated") + th.AssertEquals(t, "updated-test-secret-key==", newTSIGKey.Secret) +} diff --git a/internal/acceptance/openstack/dns/v2/zones_test.go b/internal/acceptance/openstack/dns/v2/zones_test.go index 2281094377..9984989bdd 100644 --- a/internal/acceptance/openstack/dns/v2/zones_test.go +++ b/internal/acceptance/openstack/dns/v2/zones_test.go @@ -37,12 +37,12 @@ func TestZonesCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) description := "" updateOpts := zones.UpdateOpts{ Description: &description, - TTL: 0, + TTL: 3600, } newZone, err := zones.Update(context.TODO(), client, zone.ID, updateOpts).Extract() @@ -51,4 +51,32 @@ func TestZonesCRUD(t *testing.T) { tools.PrintResource(t, &newZone) th.AssertEquals(t, newZone.Description, description) + th.AssertEquals(t, 3600, newZone.TTL) +} + +func TestZonesListWithAllProjects(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewDNSV2Client() + th.AssertNoErr(t, err) + + zone, err := CreateZone(t, client) + th.AssertNoErr(t, err) + defer DeleteZone(t, client, zone) + + listOpts := zones.ListOpts{AllProjects: true} + allPages, err := zones.List(client, listOpts).AllPages(context.TODO()) + th.AssertNoErr(t, err) + + allZones, err := zones.ExtractZones(allPages) + th.AssertNoErr(t, err) + + var found bool + for _, z := range allZones { + if zone.Name == z.Name { + found = true + } + } + + th.AssertTrue(t, found) } diff --git a/internal/acceptance/openstack/identity/v2/conditions.go b/internal/acceptance/openstack/identity/v2/conditions.go new file mode 100644 index 0000000000..9871afaf30 --- /dev/null +++ b/internal/acceptance/openstack/identity/v2/conditions.go @@ -0,0 +1,14 @@ +package v2 + +import ( + "os" + "testing" +) + +// RequireIdentityV2 will restrict a test to only be run in +// environments that support the Identity V2 API. +func RequireIdentityV2(t *testing.T) { + if os.Getenv("OS_IDENTITY_API_VERSION") != "2.0" { + t.Skip("this test requires support for the identity v2 API") + } +} diff --git a/internal/acceptance/openstack/identity/v2/extension_test.go b/internal/acceptance/openstack/identity/v2/extension_test.go index ada4ddef4f..264cdc008e 100644 --- a/internal/acceptance/openstack/identity/v2/extension_test.go +++ b/internal/acceptance/openstack/identity/v2/extension_test.go @@ -13,7 +13,7 @@ import ( ) func TestExtensionsList(t *testing.T) { - clients.RequireIdentityV2(t) + RequireIdentityV2(t) clients.RequireAdmin(t) client, err := clients.NewIdentityV2Client() @@ -33,11 +33,11 @@ func TestExtensionsList(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestExtensionsGet(t *testing.T) { - clients.RequireIdentityV2(t) + RequireIdentityV2(t) clients.RequireAdmin(t) client, err := clients.NewIdentityV2Client() diff --git a/internal/acceptance/openstack/identity/v2/identity.go b/internal/acceptance/openstack/identity/v2/identity.go index 032a01a72f..3c04cffa90 100644 --- a/internal/acceptance/openstack/identity/v2/identity.go +++ b/internal/acceptance/openstack/identity/v2/identity.go @@ -81,6 +81,7 @@ func CreateUser(t *testing.T, client *gophercloud.ServiceClient, tenant *tenants } th.AssertEquals(t, userName, user.Name) + th.AssertFalse(t, user.Enabled) return user, nil } @@ -191,6 +192,7 @@ func UpdateUser(t *testing.T, client *gophercloud.ServiceClient, user *users.Use } th.AssertEquals(t, userName, newUser.Name) + th.AssertEquals(t, updateOpts.Email, newUser.Email) return newUser, nil } diff --git a/internal/acceptance/openstack/identity/v2/role_test.go b/internal/acceptance/openstack/identity/v2/role_test.go index 2eed42a3c3..45eb067b95 100644 --- a/internal/acceptance/openstack/identity/v2/role_test.go +++ b/internal/acceptance/openstack/identity/v2/role_test.go @@ -14,7 +14,7 @@ import ( ) func TestRolesAddToUser(t *testing.T) { - clients.RequireIdentityV2(t) + RequireIdentityV2(t) clients.RequireAdmin(t) client, err := clients.NewIdentityV2AdminClient() @@ -49,11 +49,11 @@ func TestRolesAddToUser(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestRolesList(t *testing.T) { - clients.RequireIdentityV2(t) + RequireIdentityV2(t) clients.RequireAdmin(t) client, err := clients.NewIdentityV2AdminClient() @@ -73,5 +73,5 @@ func TestRolesList(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } diff --git a/internal/acceptance/openstack/identity/v2/tenant_test.go b/internal/acceptance/openstack/identity/v2/tenant_test.go index d390f14d35..116983c94c 100644 --- a/internal/acceptance/openstack/identity/v2/tenant_test.go +++ b/internal/acceptance/openstack/identity/v2/tenant_test.go @@ -13,7 +13,7 @@ import ( ) func TestTenantsList(t *testing.T) { - clients.RequireIdentityV2(t) + RequireIdentityV2(t) clients.RequireAdmin(t) client, err := clients.NewIdentityV2Client() @@ -34,11 +34,11 @@ func TestTenantsList(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestTenantsCRUD(t *testing.T) { - clients.RequireIdentityV2(t) + RequireIdentityV2(t) clients.RequireAdmin(t) client, err := clients.NewIdentityV2AdminClient() diff --git a/internal/acceptance/openstack/identity/v2/token_test.go b/internal/acceptance/openstack/identity/v2/token_test.go index fba2dbc0a5..61d93f2187 100644 --- a/internal/acceptance/openstack/identity/v2/token_test.go +++ b/internal/acceptance/openstack/identity/v2/token_test.go @@ -14,7 +14,7 @@ import ( ) func TestTokenAuthenticate(t *testing.T) { - clients.RequireIdentityV2(t) + RequireIdentityV2(t) clients.RequireAdmin(t) client, err := clients.NewIdentityV2UnauthenticatedClient() @@ -38,7 +38,7 @@ func TestTokenAuthenticate(t *testing.T) { } func TestTokenValidate(t *testing.T) { - clients.RequireIdentityV2(t) + RequireIdentityV2(t) clients.RequireAdmin(t) client, err := clients.NewIdentityV2Client() diff --git a/internal/acceptance/openstack/identity/v2/user_test.go b/internal/acceptance/openstack/identity/v2/user_test.go index f54e218d30..09f776a94c 100644 --- a/internal/acceptance/openstack/identity/v2/user_test.go +++ b/internal/acceptance/openstack/identity/v2/user_test.go @@ -13,7 +13,7 @@ import ( ) func TestUsersList(t *testing.T) { - clients.RequireIdentityV2(t) + RequireIdentityV2(t) clients.RequireAdmin(t) client, err := clients.NewIdentityV2AdminClient() @@ -34,11 +34,11 @@ func TestUsersList(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestUsersCreateUpdateDelete(t *testing.T) { - clients.RequireIdentityV2(t) + RequireIdentityV2(t) clients.RequireAdmin(t) client, err := clients.NewIdentityV2AdminClient() diff --git a/internal/acceptance/openstack/identity/v3/applicationcredentials_test.go b/internal/acceptance/openstack/identity/v3/applicationcredentials_test.go index 1390e284a9..8580310c40 100644 --- a/internal/acceptance/openstack/identity/v3/applicationcredentials_test.go +++ b/internal/acceptance/openstack/identity/v3/applicationcredentials_test.go @@ -34,10 +34,10 @@ func TestApplicationCredentialsCRD(t *testing.T) { authOptions := tokens.AuthOptions{ Username: ao.Username, + UserID: ao.UserID, Password: ao.Password, DomainName: ao.DomainName, DomainID: ao.DomainID, - // We need a scope to get the token roles list Scope: tokens.Scope{ ProjectID: ao.TenantID, ProjectName: ao.TenantName, @@ -50,15 +50,15 @@ func TestApplicationCredentialsCRD(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, token) - user, err := tokens.Get(context.TODO(), client, token.ID).ExtractUser() + user, err := tokens.Get(context.TODO(), client, token.ID, nil).ExtractUser() th.AssertNoErr(t, err) tools.PrintResource(t, user) - roles, err := tokens.Get(context.TODO(), client, token.ID).ExtractRoles() + roles, err := tokens.Get(context.TODO(), client, token.ID, nil).ExtractRoles() th.AssertNoErr(t, err) tools.PrintResource(t, roles) - project, err := tokens.Get(context.TODO(), client, token.ID).ExtractProject() + project, err := tokens.Get(context.TODO(), client, token.ID, nil).ExtractProject() th.AssertNoErr(t, err) tools.PrintResource(t, project) @@ -97,7 +97,7 @@ func TestApplicationCredentialsCRD(t *testing.T) { th.AssertEquals(t, applicationCredential.ExpiresAt, expiresAt) th.AssertEquals(t, applicationCredential.Name, createOpts.Name) th.AssertEquals(t, applicationCredential.Description, createOpts.Description) - th.AssertEquals(t, applicationCredential.Unrestricted, false) + th.AssertFalse(t, applicationCredential.Unrestricted) th.AssertEquals(t, applicationCredential.ProjectID, project.ID) checkACroles := rolesToMap(applicationCredential.Roles) @@ -124,7 +124,7 @@ func TestApplicationCredentialsCRD(t *testing.T) { th.AssertEquals(t, getApplicationCredential.ExpiresAt, expiresAt) th.AssertEquals(t, getApplicationCredential.Name, createOpts.Name) th.AssertEquals(t, getApplicationCredential.Description, createOpts.Description) - th.AssertEquals(t, getApplicationCredential.Unrestricted, false) + th.AssertFalse(t, getApplicationCredential.Unrestricted) th.AssertEquals(t, getApplicationCredential.ProjectID, project.ID) checkACroles = rolesToMap(getApplicationCredential.Roles) @@ -152,12 +152,12 @@ func TestApplicationCredentialsCRD(t *testing.T) { defer applicationcredentials.Delete(context.TODO(), client, user.ID, newApplicationCredential.ID) tools.PrintResource(t, newApplicationCredential) - th.AssertEquals(t, newApplicationCredential.ExpiresAt, time.Time{}) + th.AssertEquals(t, time.Time{}, newApplicationCredential.ExpiresAt) th.AssertEquals(t, newApplicationCredential.Name, createOpts.Name) th.AssertEquals(t, newApplicationCredential.Description, createOpts.Description) th.AssertEquals(t, newApplicationCredential.Secret, createOpts.Secret) - th.AssertEquals(t, newApplicationCredential.Unrestricted, true) - th.AssertEquals(t, newApplicationCredential.ExpiresAt, time.Time{}) + th.AssertTrue(t, newApplicationCredential.Unrestricted) + th.AssertEquals(t, time.Time{}, newApplicationCredential.ExpiresAt) th.AssertEquals(t, newApplicationCredential.ProjectID, project.ID) checkACroles = rolesToMap(newApplicationCredential.Roles) @@ -178,10 +178,10 @@ func TestApplicationCredentialsAccessRules(t *testing.T) { authOptions := tokens.AuthOptions{ Username: ao.Username, + UserID: ao.UserID, Password: ao.Password, DomainName: ao.DomainName, DomainID: ao.DomainID, - // We need a scope to get the token roles list Scope: tokens.Scope{ ProjectID: ao.TenantID, ProjectName: ao.TenantName, @@ -194,7 +194,7 @@ func TestApplicationCredentialsAccessRules(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, token) - user, err := tokens.Get(context.TODO(), client, token.ID).ExtractUser() + user, err := tokens.Get(context.TODO(), client, token.ID, nil).ExtractUser() th.AssertNoErr(t, err) tools.PrintResource(t, user) @@ -235,7 +235,7 @@ func TestApplicationCredentialsAccessRules(t *testing.T) { th.AssertEquals(t, applicationCredential.ExpiresAt, expiresAt) th.AssertEquals(t, applicationCredential.Name, createOpts.Name) th.AssertEquals(t, applicationCredential.Description, createOpts.Description) - th.AssertEquals(t, applicationCredential.Unrestricted, false) + th.AssertFalse(t, applicationCredential.Unrestricted) for i, rule := range applicationCredential.AccessRules { th.AssertEquals(t, rule.Path, apAccessRules[i].Path) @@ -255,7 +255,7 @@ func TestApplicationCredentialsAccessRules(t *testing.T) { th.AssertEquals(t, getApplicationCredential.ExpiresAt, expiresAt) th.AssertEquals(t, getApplicationCredential.Name, createOpts.Name) th.AssertEquals(t, getApplicationCredential.Description, createOpts.Description) - th.AssertEquals(t, getApplicationCredential.Unrestricted, false) + th.AssertFalse(t, getApplicationCredential.Unrestricted) for i, rule := range applicationCredential.AccessRules { th.AssertEquals(t, rule.Path, apAccessRules[i].Path) @@ -290,5 +290,5 @@ func TestApplicationCredentialsAccessRules(t *testing.T) { th.AssertNoErr(t, err) actual, err = applicationcredentials.ExtractAccessRules(allPages) th.AssertNoErr(t, err) - th.AssertEquals(t, len(actual), 0) + th.AssertEquals(t, 0, len(actual)) } diff --git a/internal/acceptance/openstack/identity/v3/credentials_test.go b/internal/acceptance/openstack/identity/v3/credentials_test.go index a1167ac647..51345fd4bc 100644 --- a/internal/acceptance/openstack/identity/v3/credentials_test.go +++ b/internal/acceptance/openstack/identity/v3/credentials_test.go @@ -24,10 +24,10 @@ func TestCredentialsCRUD(t *testing.T) { authOptions := tokens.AuthOptions{ Username: ao.Username, + UserID: ao.UserID, Password: ao.Password, DomainName: ao.DomainName, DomainID: ao.DomainID, - // We need a scope to get the token roles list Scope: tokens.Scope{ ProjectID: ao.TenantID, ProjectName: ao.TenantName, @@ -39,11 +39,11 @@ func TestCredentialsCRUD(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, token) - user, err := tokens.Get(context.TODO(), client, token.ID).ExtractUser() + user, err := tokens.Get(context.TODO(), client, token.ID, nil).ExtractUser() th.AssertNoErr(t, err) tools.PrintResource(t, user) - project, err := tokens.Get(context.TODO(), client, token.ID).ExtractProject() + project, err := tokens.Get(context.TODO(), client, token.ID, nil).ExtractProject() th.AssertNoErr(t, err) tools.PrintResource(t, project) @@ -89,7 +89,10 @@ func TestCredentialsCRUD(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, updateCredential) - th.AssertEquals(t, updateCredential.Blob, updateOpts.Blob) + th.AssertEquals(t, updateOpts.Blob, updateCredential.Blob) + th.AssertEquals(t, updateOpts.Type, updateCredential.Type) + th.AssertEquals(t, updateOpts.UserID, updateCredential.UserID) + th.AssertEquals(t, updateOpts.ProjectID, updateCredential.ProjectID) } func TestCredentialsValidateS3(t *testing.T) { @@ -101,10 +104,10 @@ func TestCredentialsValidateS3(t *testing.T) { authOptions := tokens.AuthOptions{ Username: ao.Username, + UserID: ao.UserID, Password: ao.Password, DomainName: ao.DomainName, DomainID: ao.DomainID, - // We need a scope to get the token roles list Scope: tokens.Scope{ ProjectID: ao.TenantID, ProjectName: ao.TenantName, @@ -116,11 +119,11 @@ func TestCredentialsValidateS3(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, token) - user, err := tokens.Get(context.TODO(), client, token.ID).ExtractUser() + user, err := tokens.Get(context.TODO(), client, token.ID, nil).ExtractUser() th.AssertNoErr(t, err) tools.PrintResource(t, user) - project, err := tokens.Get(context.TODO(), client, token.ID).ExtractProject() + project, err := tokens.Get(context.TODO(), client, token.ID, nil).ExtractProject() th.AssertNoErr(t, err) tools.PrintResource(t, project) diff --git a/internal/acceptance/openstack/identity/v3/domains_test.go b/internal/acceptance/openstack/identity/v3/domains_test.go index 3d1208ae9c..0031158702 100644 --- a/internal/acceptance/openstack/identity/v3/domains_test.go +++ b/internal/acceptance/openstack/identity/v3/domains_test.go @@ -35,7 +35,7 @@ func TestDomainsList(t *testing.T) { client, err := clients.NewIdentityV3Client() th.AssertNoErr(t, err) - var iTrue bool = true + var iTrue = true listOpts := domains.ListOpts{ Enabled: &iTrue, } @@ -55,7 +55,7 @@ func TestDomainsList(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestDomainsGet(t *testing.T) { @@ -69,7 +69,7 @@ func TestDomainsGet(t *testing.T) { tools.PrintResource(t, p) - th.AssertEquals(t, p.Name, "Default") + th.AssertEquals(t, "Default", p.Name) } func TestDomainsCRUD(t *testing.T) { @@ -78,7 +78,7 @@ func TestDomainsCRUD(t *testing.T) { client, err := clients.NewIdentityV3Client() th.AssertNoErr(t, err) - var iTrue bool = true + var iTrue = true var description = "Testing Domain" createOpts := domains.CreateOpts{ Description: description, @@ -92,8 +92,9 @@ func TestDomainsCRUD(t *testing.T) { tools.PrintResource(t, domain) th.AssertEquals(t, domain.Description, description) + th.AssertTrue(t, domain.Enabled) - var iFalse bool = false + var iFalse = false description = "" updateOpts := domains.UpdateOpts{ Description: &description, @@ -106,4 +107,5 @@ func TestDomainsCRUD(t *testing.T) { tools.PrintResource(t, newDomain) th.AssertEquals(t, newDomain.Description, description) + th.AssertFalse(t, newDomain.Enabled) } diff --git a/internal/acceptance/openstack/identity/v3/ec2credentials_test.go b/internal/acceptance/openstack/identity/v3/ec2credentials_test.go index 6500aa6f0c..7f27f51ed4 100644 --- a/internal/acceptance/openstack/identity/v3/ec2credentials_test.go +++ b/internal/acceptance/openstack/identity/v3/ec2credentials_test.go @@ -23,10 +23,10 @@ func TestEC2CredentialsCRD(t *testing.T) { authOptions := tokens.AuthOptions{ Username: ao.Username, + UserID: ao.UserID, Password: ao.Password, DomainName: ao.DomainName, DomainID: ao.DomainID, - // We need a scope to get the token roles list Scope: tokens.Scope{ ProjectID: ao.TenantID, ProjectName: ao.TenantName, diff --git a/internal/acceptance/openstack/identity/v3/endpoint_test.go b/internal/acceptance/openstack/identity/v3/endpoint_test.go index ae2254102a..814c364e35 100644 --- a/internal/acceptance/openstack/identity/v3/endpoint_test.go +++ b/internal/acceptance/openstack/identity/v3/endpoint_test.go @@ -36,7 +36,30 @@ func TestEndpointsList(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) +} + +func TestEndpointsGet(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewIdentityV3Client() + th.AssertNoErr(t, err) + + allPages, err := endpoints.List(client, nil).AllPages(context.TODO()) + th.AssertNoErr(t, err) + + allEndpoints, err := endpoints.ExtractEndpoints(allPages) + th.AssertNoErr(t, err) + + endpoint := allEndpoints[0] + e, err := endpoints.Get(context.TODO(), client, endpoint.ID).Extract() + if err != nil { + t.Fatalf("Unable to get endpoint: %v", err) + } + + tools.PrintResource(t, e) + + th.AssertEquals(t, endpoint.Name, e.Name) } func TestEndpointsNavigateCatalog(t *testing.T) { @@ -56,7 +79,7 @@ func TestEndpointsNavigateCatalog(t *testing.T) { allServices, err := services.ExtractServices(allPages) th.AssertNoErr(t, err) - th.AssertEquals(t, len(allServices), 1) + th.AssertEquals(t, 1, len(allServices)) computeService := allServices[0] tools.PrintResource(t, computeService) @@ -73,7 +96,49 @@ func TestEndpointsNavigateCatalog(t *testing.T) { allEndpoints, err := endpoints.ExtractEndpoints(allPages) th.AssertNoErr(t, err) - th.AssertEquals(t, len(allServices), 1) + th.AssertEquals(t, 1, len(allServices)) tools.PrintResource(t, allEndpoints[0]) } + +func TestEndpointCRUD(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewIdentityV3Client() + th.AssertNoErr(t, err) + + service, err := CreateService(t, client, &services.CreateOpts{ + Type: "endpoint-test", + Name: tools.RandomString("ACPTTEST", 8), + Extra: map[string]any{}, + }) + th.AssertNoErr(t, err) + defer DeleteService(t, client, service.ID) + + enabled := false + endpoint, err := CreateEndpoint(t, client, &endpoints.CreateOpts{ + Availability: gophercloud.Availability("internal"), + ServiceID: service.ID, + URL: "https://example.com", + Enabled: &enabled, + }) + th.AssertNoErr(t, err) + defer DeleteEndpoint(t, client, endpoint.ID) + + tools.PrintResource(t, endpoint) + tools.PrintResource(t, endpoint.URL) + + enabled = true + description := "" + newEndpoint, err := endpoints.Update(context.TODO(), client, endpoint.ID, &endpoints.UpdateOpts{ + Name: "new-endpoint", + URL: "https://example-updated.com", + Description: &description, + Enabled: &enabled, + }).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, "https://example-updated.com", newEndpoint.URL) + th.AssertEquals(t, newEndpoint.Description, description) + th.AssertEquals(t, "new-endpoint", newEndpoint.Name) +} diff --git a/internal/acceptance/openstack/identity/v3/federation_test.go b/internal/acceptance/openstack/identity/v3/federation_test.go index b52d54cc30..3b92073668 100644 --- a/internal/acceptance/openstack/identity/v3/federation_test.go +++ b/internal/acceptance/openstack/identity/v3/federation_test.go @@ -15,6 +15,8 @@ import ( ) func TestListMappings(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() th.AssertNoErr(t, err) @@ -28,10 +30,10 @@ func TestListMappings(t *testing.T) { } func TestMappingsCRUD(t *testing.T) { - mappingName := tools.RandomString("TESTMAPPING-", 8) - clients.RequireAdmin(t) + mappingName := tools.RandomString("TESTMAPPING-", 8) + client, err := clients.NewIdentityV3Client() th.AssertNoErr(t, err) @@ -116,5 +118,5 @@ func TestMappingsCRUD(t *testing.T) { th.AssertNoErr(t, err) resp := federation.GetMapping(context.TODO(), client, mappingName) - th.AssertEquals(t, true, gophercloud.ResponseCodeIs(resp.Err, http.StatusNotFound)) + th.AssertTrue(t, gophercloud.ResponseCodeIs(resp.Err, http.StatusNotFound)) } diff --git a/internal/acceptance/openstack/identity/v3/groups_test.go b/internal/acceptance/openstack/identity/v3/groups_test.go index b60fbd5a71..c482ae4fdb 100644 --- a/internal/acceptance/openstack/identity/v3/groups_test.go +++ b/internal/acceptance/openstack/identity/v3/groups_test.go @@ -83,7 +83,7 @@ func TestGroupCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) listOpts.Filters = map[string]string{ "name__contains": "TEST", @@ -105,7 +105,7 @@ func TestGroupCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) listOpts.Filters = map[string]string{ "name__contains": "foo", @@ -127,7 +127,7 @@ func TestGroupCRUD(t *testing.T) { } } - th.AssertEquals(t, found, false) + th.AssertFalse(t, found) // Get the recently created group by ID p, err := groups.Get(context.TODO(), client, group.ID).Extract() diff --git a/internal/acceptance/openstack/identity/v3/identity.go b/internal/acceptance/openstack/identity/v3/identity.go index ab34bb7530..f0c07a2af4 100644 --- a/internal/acceptance/openstack/identity/v3/identity.go +++ b/internal/acceptance/openstack/identity/v3/identity.go @@ -7,6 +7,7 @@ import ( "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/domains" + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/endpoints" "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/groups" "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/projects" "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/regions" @@ -17,6 +18,41 @@ import ( th "github.com/gophercloud/gophercloud/v2/testhelper" ) +// CreateEndpoint will create an endpoint with a random name. It only define +// endpoint name and description, and receive from CreateOpts others parameters, +// such as URL, Availability and ServiceID. An error will be returned if the +// endpoint was unabled to be created. +func CreateEndpoint(t *testing.T, client *gophercloud.ServiceClient, c *endpoints.CreateOpts) (*endpoints.Endpoint, error) { + name := tools.RandomString("ACPTTEST", 8) + description := tools.RandomString("ACPTTEST-DESC", 8) + t.Logf("Attempting to create endpoint: %s", name) + + var createOpts endpoints.CreateOpts + if c != nil { + createOpts = *c + } else { + createOpts = endpoints.CreateOpts{} + } + + createOpts.Name = name + createOpts.Description = description + + endpoint, err := endpoints.Create(context.TODO(), client, createOpts).Extract() + if err != nil { + return endpoint, err + } + + t.Logf("Successfully created endpoint %s with ID %s", name, endpoint.ID) + + th.AssertEquals(t, name, endpoint.Name) + th.AssertEquals(t, description, endpoint.Description) + th.AssertEquals(t, string(createOpts.Availability), string(endpoint.Availability)) + th.AssertEquals(t, createOpts.ServiceID, endpoint.ServiceID) + th.AssertEquals(t, createOpts.URL, endpoint.URL) + + return endpoint, nil +} + // CreateProject will create a project with a random name. // It takes an optional createOpts parameter since creating a project // has so many options. An error will be returned if the project was @@ -73,7 +109,8 @@ func CreateUser(t *testing.T, client *gophercloud.ServiceClient, c *users.Create t.Logf("Successfully created user %s with ID %s", name, user.ID) - th.AssertEquals(t, user.Name, name) + th.AssertEquals(t, name, user.Name) + th.AssertEquals(t, createOpts.Description, user.Description) return user, nil } @@ -102,7 +139,8 @@ func CreateGroup(t *testing.T, client *gophercloud.ServiceClient, c *groups.Crea t.Logf("Successfully created group %s with ID %s", name, group.ID) - th.AssertEquals(t, group.Name, name) + th.AssertEquals(t, name, group.Name) + th.AssertEquals(t, createOpts.Description, group.Description) return group, nil } @@ -131,7 +169,8 @@ func CreateDomain(t *testing.T, client *gophercloud.ServiceClient, c *domains.Cr t.Logf("Successfully created domain %s with ID %s", name, domain.ID) - th.AssertEquals(t, domain.Name, name) + th.AssertEquals(t, name, domain.Name) + th.AssertEquals(t, createOpts.Description, domain.Description) return domain, nil } @@ -165,7 +204,10 @@ func CreateRole(t *testing.T, client *gophercloud.ServiceClient, c *roles.Create t.Logf("Successfully created role %s with ID %s", name, role.ID) - th.AssertEquals(t, role.Name, name) + th.AssertEquals(t, name, role.Name) + if createOpts.Description != "" { + th.AssertEquals(t, createOpts.Description, role.Description) + } return role, nil } @@ -195,6 +237,7 @@ func CreateRegion(t *testing.T, client *gophercloud.ServiceClient, c *regions.Cr t.Logf("Successfully created region %s", id) th.AssertEquals(t, region.ID, id) + th.AssertEquals(t, createOpts.Description, region.Description) return region, nil } @@ -228,6 +271,18 @@ func CreateService(t *testing.T, client *gophercloud.ServiceClient, c *services. return service, nil } +// DeleteEndpoint will delete the specified Endpoint using its ID. A fatal error +// will occur if the endpoint failed to be deleted. This works best when using +// it as a deferred function. +func DeleteEndpoint(t *testing.T, client *gophercloud.ServiceClient, endpointID string) { + err := endpoints.Delete(context.TODO(), client, endpointID).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete endpoint %s: %v", endpointID, err) + } + + t.Logf("Deleted endpoint: %s", endpointID) +} + // DeleteProject will delete a project by ID. A fatal error will occur if // the project ID failed to be deleted. This works best when using it as // a deferred function. @@ -360,6 +415,10 @@ func CreateTrust(t *testing.T, client *gophercloud.ServiceClient, createOpts tru t.Logf("Successfully created trust %s", trust.ID) + th.AssertEquals(t, createOpts.TrusteeUserID, trust.TrusteeUserID) + th.AssertEquals(t, createOpts.TrustorUserID, trust.TrustorUserID) + th.AssertEquals(t, createOpts.ProjectID, trust.ProjectID) + return trust, nil } diff --git a/internal/acceptance/openstack/identity/v3/limits_test.go b/internal/acceptance/openstack/identity/v3/limits_test.go index 72dc12e4fb..348af5b8ab 100644 --- a/internal/acceptance/openstack/identity/v3/limits_test.go +++ b/internal/acceptance/openstack/identity/v3/limits_test.go @@ -111,6 +111,7 @@ func TestLimitsCRUD(t *testing.T) { th.AssertEquals(t, globalResourceName, createdLimits[0].ResourceName) th.AssertEquals(t, serviceID, createdLimits[0].ServiceID) th.AssertEquals(t, project.ID, createdLimits[0].ProjectID) + th.AssertEquals(t, "RegionOne", createdLimits[0].RegionID) limitID := createdLimits[0].ID diff --git a/internal/acceptance/openstack/identity/v3/oauth1_test.go b/internal/acceptance/openstack/identity/v3/oauth1_test.go index da6c8be148..782510fc18 100644 --- a/internal/acceptance/openstack/identity/v3/oauth1_test.go +++ b/internal/acceptance/openstack/identity/v3/oauth1_test.go @@ -16,6 +16,8 @@ import ( ) func TestOAuth1CRUD(t *testing.T) { + clients.RequireAdmin(t) + client, err := clients.NewIdentityV3Client() th.AssertNoErr(t, err) @@ -24,10 +26,10 @@ func TestOAuth1CRUD(t *testing.T) { authOptions := tokens.AuthOptions{ Username: ao.Username, + UserID: ao.UserID, Password: ao.Password, DomainName: ao.DomainName, DomainID: ao.DomainID, - // We need a scope to get the token roles list Scope: tokens.Scope{ ProjectID: ao.TenantID, ProjectName: ao.TenantName, @@ -177,7 +179,7 @@ func oauth1MethodTest(t *testing.T, client *gophercloud.ServiceClient, consumer found = true } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } // Get access token role @@ -191,7 +193,7 @@ func oauth1MethodTest(t *testing.T, client *gophercloud.ServiceClient, consumer found = true } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) // Test auth using OAuth1 newClient, err := clients.NewIdentityV3UnauthenticatedClient() @@ -213,7 +215,7 @@ func oauth1MethodTest(t *testing.T, client *gophercloud.ServiceClient, consumer tokens.Token oauth1.TokenExt } - tokenRes := tokens.Get(context.TODO(), newClient, newClient.ProviderClient.TokenID) + tokenRes := tokens.Get(context.TODO(), newClient, newClient.TokenID, nil) err = tokenRes.ExtractInto(&token) th.AssertNoErr(t, err) oauth1Roles, err := tokenRes.ExtractRoles() diff --git a/internal/acceptance/openstack/identity/v3/policies_test.go b/internal/acceptance/openstack/identity/v3/policies_test.go index 0b23bdd9f5..91d65db976 100644 --- a/internal/acceptance/openstack/identity/v3/policies_test.go +++ b/internal/acceptance/openstack/identity/v3/policies_test.go @@ -71,7 +71,7 @@ func TestPoliciesCRUD(t *testing.T) { } } - th.AssertEquals(t, true, found) + th.AssertTrue(t, found) listOpts.Filters = map[string]string{ "type__contains": "json", @@ -93,7 +93,7 @@ func TestPoliciesCRUD(t *testing.T) { } } - th.AssertEquals(t, true, found) + th.AssertTrue(t, found) listOpts.Filters = map[string]string{ "type__contains": "foobar", @@ -115,7 +115,7 @@ func TestPoliciesCRUD(t *testing.T) { } } - th.AssertEquals(t, false, found) + th.AssertFalse(t, found) gotPolicy, err := policies.Get(context.TODO(), client, policy.ID).Extract() th.AssertNoErr(t, err) diff --git a/internal/acceptance/openstack/identity/v3/projects_test.go b/internal/acceptance/openstack/identity/v3/projects_test.go index 528a30b2a7..2ce5cded9a 100644 --- a/internal/acceptance/openstack/identity/v3/projects_test.go +++ b/internal/acceptance/openstack/identity/v3/projects_test.go @@ -35,7 +35,7 @@ func TestProjectsList(t *testing.T) { client, err := clients.NewIdentityV3Client() th.AssertNoErr(t, err) - var iTrue bool = true + var iTrue = true listOpts := projects.ListOpts{ Enabled: &iTrue, } @@ -55,7 +55,7 @@ func TestProjectsList(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) listOpts.Filters = map[string]string{ "name__contains": "dmi", @@ -76,7 +76,7 @@ func TestProjectsList(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) listOpts.Filters = map[string]string{ "name__contains": "foo", @@ -97,7 +97,7 @@ func TestProjectsList(t *testing.T) { } } - th.AssertEquals(t, found, false) + th.AssertFalse(t, found) } func TestProjectsGet(t *testing.T) { @@ -247,7 +247,7 @@ func TestProjectsTags(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) // Search using all tags, including a not existing one listOpts = projects.ListOpts{ @@ -260,7 +260,7 @@ func TestProjectsTags(t *testing.T) { allProjects, err = projects.ExtractProjects(allPages) th.AssertNoErr(t, err) - th.AssertEquals(t, len(allProjects), 0) + th.AssertEquals(t, 0, len(allProjects)) // Search matching at least one tag listOpts = projects.ListOpts{ @@ -282,7 +282,7 @@ func TestProjectsTags(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) // Search not matching any single tag listOpts = projects.ListOpts{ @@ -304,7 +304,7 @@ func TestProjectsTags(t *testing.T) { } } - th.AssertEquals(t, found, false) + th.AssertFalse(t, found) // Search matching not all tags listOpts = projects.ListOpts{ @@ -326,7 +326,7 @@ func TestProjectsTags(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) // Update the tags updateOpts := projects.UpdateOpts{ @@ -337,8 +337,8 @@ func TestProjectsTags(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, updatedProject) - th.AssertEquals(t, len(updatedProject.Tags), 1) - th.AssertEquals(t, updatedProject.Tags[0], "Tag1") + th.AssertEquals(t, 1, len(updatedProject.Tags)) + th.AssertEquals(t, "Tag1", updatedProject.Tags[0]) // Update the project, but not its tags description := "Test description" @@ -350,8 +350,8 @@ func TestProjectsTags(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, updatedProject) - th.AssertEquals(t, len(updatedProject.Tags), 1) - th.AssertEquals(t, updatedProject.Tags[0], "Tag1") + th.AssertEquals(t, 1, len(updatedProject.Tags)) + th.AssertEquals(t, "Tag1", updatedProject.Tags[0]) // Remove all Tags updateOpts = projects.UpdateOpts{ @@ -362,7 +362,7 @@ func TestProjectsTags(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, updatedProject) - th.AssertEquals(t, len(updatedProject.Tags), 0) + th.AssertEquals(t, 0, len(updatedProject.Tags)) } func TestProjectsTagsCRUD(t *testing.T) { diff --git a/internal/acceptance/openstack/identity/v3/reauth_test.go b/internal/acceptance/openstack/identity/v3/reauth_test.go index 2936ffce19..f598f21a92 100644 --- a/internal/acceptance/openstack/identity/v3/reauth_test.go +++ b/internal/acceptance/openstack/identity/v3/reauth_test.go @@ -27,7 +27,7 @@ func TestReauthAuthResultDeadlock(t *testing.T) { provider.SetToken("this is not a valid token") - client, err := openstack.NewIdentityV3(provider, gophercloud.EndpointOpts{}) + client, err := openstack.NewIdentityV3(context.TODO(), provider, gophercloud.EndpointOpts{}) th.AssertNoErr(t, err) pages, err := projects.List(client, nil).AllPages(context.TODO()) th.AssertNoErr(t, err) diff --git a/internal/acceptance/openstack/identity/v3/regions_test.go b/internal/acceptance/openstack/identity/v3/regions_test.go index 4a4df55f9e..d8d2724a06 100644 --- a/internal/acceptance/openstack/identity/v3/regions_test.go +++ b/internal/acceptance/openstack/identity/v3/regions_test.go @@ -76,6 +76,8 @@ func TestRegionsCRUD(t *testing.T) { tools.PrintResource(t, region) tools.PrintResource(t, region.Extra) + th.AssertEquals(t, "Region for testing", region.Description) + var description = "" updateOpts := regions.UpdateOpts{ Description: &description, diff --git a/internal/acceptance/openstack/identity/v3/registeredlimits_test.go b/internal/acceptance/openstack/identity/v3/registeredlimits_test.go index ee0b61022b..a0458916ff 100644 --- a/internal/acceptance/openstack/identity/v3/registeredlimits_test.go +++ b/internal/acceptance/openstack/identity/v3/registeredlimits_test.go @@ -90,9 +90,10 @@ func TestRegisteredLimitsCRUD(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, updated_registered_limit) - th.AssertEquals(t, updated_registered_limit.Description, updatedDescription) - th.AssertEquals(t, updated_registered_limit.DefaultLimit, updatedDefaultLimit) - th.AssertEquals(t, updated_registered_limit.ResourceName, updatedResourceName) + th.AssertEquals(t, updatedDescription, updated_registered_limit.Description) + th.AssertEquals(t, updatedDefaultLimit, updated_registered_limit.DefaultLimit) + th.AssertEquals(t, updatedResourceName, updated_registered_limit.ResourceName) + th.AssertEquals(t, serviceID, updated_registered_limit.ServiceID) // Delete the registered limit del_err := registeredlimits.Delete(context.TODO(), client, createdRegisteredLimits[0].ID).ExtractErr() diff --git a/internal/acceptance/openstack/identity/v3/roles_test.go b/internal/acceptance/openstack/identity/v3/roles_test.go index ec69a9ba4e..fe94ada301 100644 --- a/internal/acceptance/openstack/identity/v3/roles_test.go +++ b/internal/acceptance/openstack/identity/v3/roles_test.go @@ -58,9 +58,14 @@ func TestRolesCRUD(t *testing.T) { th.AssertNoErr(t, err) createOpts := roles.CreateOpts{ - Name: "testrole", + Name: "role-test", + DomainID: "default", + Description: "test description", Extra: map[string]any{ - "description": "test role description", + "email": "testrole@example.com", + }, + Options: map[roles.Option]any{ + "immutable": false, }, } @@ -72,7 +77,11 @@ func TestRolesCRUD(t *testing.T) { tools.PrintResource(t, role) tools.PrintResource(t, role.Extra) - listOpts := roles.ListOpts{} + th.AssertEquals(t, "test description", role.Description) + + listOpts := roles.ListOpts{ + DomainID: "default", + } allPages, err := roles.List(client, listOpts).AllPages(context.TODO()) th.AssertNoErr(t, err) @@ -89,11 +98,15 @@ func TestRolesCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) - + th.AssertTrue(t, found) + description := "updated role test" updateOpts := roles.UpdateOpts{ + Description: &description, Extra: map[string]any{ - "description": "updated test role description", + "email": "updatedtestrole@example.com", + }, + Options: map[roles.Option]any{ + "immutable": nil, }, } @@ -103,7 +116,9 @@ func TestRolesCRUD(t *testing.T) { tools.PrintResource(t, newRole) tools.PrintResource(t, newRole.Extra) - th.AssertEquals(t, newRole.Extra["description"], "updated test role description") + th.AssertEquals(t, newRole.Description, description) + th.AssertEquals(t, "updatedtestrole@example.com", newRole.Extra["email"]) + } func TestRolesFilterList(t *testing.T) { @@ -145,7 +160,7 @@ func TestRolesFilterList(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) listOpts.Filters = map[string]string{ "name__contains": "reader", @@ -167,7 +182,7 @@ func TestRolesFilterList(t *testing.T) { } } - th.AssertEquals(t, found, false) + th.AssertFalse(t, found) } func TestRoleListAssignmentIncludeNamesAndSubtree(t *testing.T) { @@ -235,7 +250,7 @@ func TestRoleListAssignmentIncludeNamesAndSubtree(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestRoleListAssignmentForUserOnProject(t *testing.T) { @@ -297,7 +312,7 @@ func TestRoleListAssignmentForUserOnProject(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestRoleListAssignmentForUserOnDomain(t *testing.T) { @@ -362,7 +377,7 @@ func TestRoleListAssignmentForUserOnDomain(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestRoleListAssignmentForGroupOnProject(t *testing.T) { @@ -427,7 +442,7 @@ func TestRoleListAssignmentForGroupOnProject(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestRoleListAssignmentForGroupOnDomain(t *testing.T) { @@ -495,7 +510,7 @@ func TestRoleListAssignmentForGroupOnDomain(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestRolesAssignToUserOnProject(t *testing.T) { @@ -565,7 +580,7 @@ func TestRolesAssignToUserOnProject(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestRolesAssignToUserOnDomain(t *testing.T) { @@ -638,7 +653,7 @@ func TestRolesAssignToUserOnDomain(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestRolesAssignToGroupOnDomain(t *testing.T) { @@ -714,7 +729,7 @@ func TestRolesAssignToGroupOnDomain(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestRolesAssignToGroupOnProject(t *testing.T) { @@ -787,7 +802,7 @@ func TestRolesAssignToGroupOnProject(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestCRUDRoleInferenceRule(t *testing.T) { diff --git a/internal/acceptance/openstack/identity/v3/service_test.go b/internal/acceptance/openstack/identity/v3/service_test.go index 8686f2c890..496b7fb5b2 100644 --- a/internal/acceptance/openstack/identity/v3/service_test.go +++ b/internal/acceptance/openstack/identity/v3/service_test.go @@ -37,7 +37,7 @@ func TestServicesList(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestServicesCRUD(t *testing.T) { @@ -61,6 +61,8 @@ func TestServicesCRUD(t *testing.T) { tools.PrintResource(t, service) tools.PrintResource(t, service.Extra) + th.AssertEquals(t, "testing", service.Type) + updateOpts := services.UpdateOpts{ Type: "testing2", Extra: map[string]any{ @@ -75,5 +77,6 @@ func TestServicesCRUD(t *testing.T) { tools.PrintResource(t, newService) tools.PrintResource(t, newService.Extra) - th.AssertEquals(t, newService.Extra["description"], "Test Users") + th.AssertEquals(t, "Test Users", newService.Extra["description"]) + th.AssertEquals(t, "testing2", newService.Type) } diff --git a/internal/acceptance/openstack/identity/v3/token_test.go b/internal/acceptance/openstack/identity/v3/token_test.go index d5274ff6b1..290bce9474 100644 --- a/internal/acceptance/openstack/identity/v3/token_test.go +++ b/internal/acceptance/openstack/identity/v3/token_test.go @@ -24,27 +24,35 @@ func TestTokensGet(t *testing.T) { authOptions := tokens.AuthOptions{ Username: ao.Username, + UserID: ao.UserID, Password: ao.Password, - DomainName: "default", + DomainName: ao.DomainName, + DomainID: ao.DomainID, + Scope: tokens.Scope{ + ProjectID: ao.TenantID, + ProjectName: ao.TenantName, + DomainID: ao.DomainID, + DomainName: ao.DomainName, + }, } token, err := tokens.Create(context.TODO(), client, &authOptions).Extract() th.AssertNoErr(t, err) tools.PrintResource(t, token) - catalog, err := tokens.Get(context.TODO(), client, token.ID).ExtractServiceCatalog() + catalog, err := tokens.Get(context.TODO(), client, token.ID, nil).ExtractServiceCatalog() th.AssertNoErr(t, err) tools.PrintResource(t, catalog) - user, err := tokens.Get(context.TODO(), client, token.ID).ExtractUser() + user, err := tokens.Get(context.TODO(), client, token.ID, nil).ExtractUser() th.AssertNoErr(t, err) tools.PrintResource(t, user) - roles, err := tokens.Get(context.TODO(), client, token.ID).ExtractRoles() + roles, err := tokens.Get(context.TODO(), client, token.ID, nil).ExtractRoles() th.AssertNoErr(t, err) tools.PrintResource(t, roles) - project, err := tokens.Get(context.TODO(), client, token.ID).ExtractProject() + project, err := tokens.Get(context.TODO(), client, token.ID, nil).ExtractProject() th.AssertNoErr(t, err) tools.PrintResource(t, project) } diff --git a/internal/acceptance/openstack/identity/v3/trusts_test.go b/internal/acceptance/openstack/identity/v3/trusts_test.go index 39c7191eea..e6a894c7c3 100644 --- a/internal/acceptance/openstack/identity/v3/trusts_test.go +++ b/internal/acceptance/openstack/identity/v3/trusts_test.go @@ -29,14 +29,21 @@ func TestTrustCRUD(t *testing.T) { authOptions := tokens.AuthOptions{ Username: ao.Username, + UserID: ao.UserID, Password: ao.Password, DomainName: ao.DomainName, DomainID: ao.DomainID, + Scope: tokens.Scope{ + ProjectID: ao.TenantID, + ProjectName: ao.TenantName, + DomainID: ao.DomainID, + DomainName: ao.DomainName, + }, } token, err := tokens.Create(context.TODO(), client, &authOptions).Extract() th.AssertNoErr(t, err) - adminUser, err := tokens.Get(context.TODO(), client, token.ID).ExtractUser() + adminUser, err := tokens.Get(context.TODO(), client, token.ID, nil).ExtractUser() th.AssertNoErr(t, err) // Get the admin and member role IDs. @@ -105,7 +112,10 @@ func TestTrustCRUD(t *testing.T) { p, err := trusts.Get(context.TODO(), client, trust.ID).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, p.ExpiresAt, expiresAt) - th.AssertEquals(t, p.DeletedAt.IsZero(), true) + th.AssertTrue(t, p.DeletedAt.IsZero()) + th.AssertEquals(t, trusteeUser.ID, p.TrusteeUserID) + th.AssertEquals(t, adminUser.ID, p.TrustorUserID) + th.AssertEquals(t, trusteeProject.ID, p.ProjectID) tools.PrintResource(t, p) @@ -114,7 +124,7 @@ func TestTrustCRUD(t *testing.T) { th.AssertNoErr(t, err) allTrustRoles, err := trusts.ExtractRoles(rolesPages) th.AssertNoErr(t, err) - th.AssertEquals(t, len(allTrustRoles), 1) + th.AssertEquals(t, 1, len(allTrustRoles)) th.AssertEquals(t, allTrustRoles[0].ID, memberRoleID) // Get trust role diff --git a/internal/acceptance/openstack/identity/v3/users_test.go b/internal/acceptance/openstack/identity/v3/users_test.go index 9b2e63bfdf..1aac48fdc8 100644 --- a/internal/acceptance/openstack/identity/v3/users_test.go +++ b/internal/acceptance/openstack/identity/v3/users_test.go @@ -20,7 +20,7 @@ func TestUsersList(t *testing.T) { client, err := clients.NewIdentityV3Client() th.AssertNoErr(t, err) - var iTrue bool = true + var iTrue = true listOpts := users.ListOpts{ Enabled: &iTrue, } @@ -41,7 +41,7 @@ func TestUsersList(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) listOpts.Filters = map[string]string{ "name__contains": "dmi", @@ -63,7 +63,7 @@ func TestUsersList(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) listOpts.Filters = map[string]string{ "name__contains": "foo", @@ -85,7 +85,7 @@ func TestUsersList(t *testing.T) { } } - th.AssertEquals(t, found, false) + th.AssertFalse(t, found) } func TestUsersGet(t *testing.T) { @@ -172,7 +172,7 @@ func TestUserCRUD(t *testing.T) { th.AssertEquals(t, newUser.Name, name) th.AssertEquals(t, newUser.Description, description) th.AssertEquals(t, newUser.Enabled, iFalse) - th.AssertEquals(t, newUser.Extra["disabled_reason"], "DDOS") + th.AssertEquals(t, "DDOS", newUser.Extra["disabled_reason"]) } func TestUserChangePassword(t *testing.T) { @@ -251,7 +251,7 @@ func TestUsersGroups(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) found = false allUserPages, err := users.ListInGroup(client, group.ID, nil).AllPages(context.TODO()) @@ -269,7 +269,7 @@ func TestUsersGroups(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) ok, err := users.IsMemberOfGroup(context.TODO(), client, group.ID, user.ID).Extract() if err != nil { @@ -298,7 +298,7 @@ func TestUsersGroups(t *testing.T) { } } - th.AssertEquals(t, found, false) + th.AssertFalse(t, found) found = false allUserPages, err = users.ListInGroup(client, group.ID, nil).AllPages(context.TODO()) @@ -316,7 +316,7 @@ func TestUsersGroups(t *testing.T) { } } - th.AssertEquals(t, found, false) + th.AssertFalse(t, found) } diff --git a/internal/acceptance/openstack/image/v2/images_test.go b/internal/acceptance/openstack/image/v2/images_test.go index 2053a2047b..e8bb2ea1d2 100644 --- a/internal/acceptance/openstack/image/v2/images_test.go +++ b/internal/acceptance/openstack/image/v2/images_test.go @@ -66,7 +66,7 @@ func TestImagesListAllPages(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestImagesListByDate(t *testing.T) { @@ -175,7 +175,7 @@ func TestImagesUpdate(t *testing.T) { tools.PrintResource(t, newImage.Properties) th.AssertEquals(t, newImage.Name, image.Name+"foo") - th.AssertEquals(t, newImage.Protected, true) + th.AssertTrue(t, newImage.Protected) sort.Strings(newTags) sort.Strings(newImage.Tags) @@ -183,7 +183,7 @@ func TestImagesUpdate(t *testing.T) { // Because OpenStack is now adding additional properties automatically, // it's not possible to do an easy AssertDeepEquals. - th.AssertEquals(t, newImage.Properties["hw_disk_bus"], "scsi") + th.AssertEquals(t, "scsi", newImage.Properties["hw_disk_bus"]) if _, ok := newImage.Properties["architecture"]; ok { t.Fatal("architecture property still exists") diff --git a/internal/acceptance/openstack/image/v2/imageservice.go b/internal/acceptance/openstack/image/v2/imageservice.go index caa832ce6b..d9076ee9ce 100644 --- a/internal/acceptance/openstack/image/v2/imageservice.go +++ b/internal/acceptance/openstack/image/v2/imageservice.go @@ -7,6 +7,7 @@ import ( "io" "net/http" "os" + "sort" "testing" "github.com/gophercloud/gophercloud/v2" @@ -38,6 +39,7 @@ func CreateEmptyImage(t *testing.T, client *gophercloud.ServiceClient) (*images. Visibility: &visibility, Properties: map[string]string{ "architecture": "x86_64", + "properties": "{'hypervisor_type': 'qemu', 'architecture': 'x86_64'}", }, Tags: []string{"foo", "bar", "baz"}, } @@ -54,8 +56,17 @@ func CreateEmptyImage(t *testing.T, client *gophercloud.ServiceClient) (*images. t.Logf("Created image %s: %#v", name, newImage) - th.CheckEquals(t, newImage.Name, name) - th.CheckEquals(t, newImage.Properties["architecture"], "x86_64") + th.CheckEquals(t, name, newImage.Name) + th.CheckEquals(t, "bare", newImage.ContainerFormat) + th.CheckEquals(t, "qcow2", newImage.DiskFormat) + th.CheckEquals(t, images.ImageVisibilityPrivate, newImage.Visibility) + expectedTags := []string{"bar", "baz", "foo"} + actualTags := make([]string, len(newImage.Tags)) + copy(actualTags, newImage.Tags) + sort.Strings(actualTags) + th.AssertDeepEquals(t, expectedTags, actualTags) + th.CheckEquals(t, "x86_64", newImage.Properties["architecture"]) + th.CheckEquals(t, "{'hypervisor_type': 'qemu', 'architecture': 'x86_64'}", newImage.Properties["properties"]) return newImage, nil } diff --git a/internal/acceptance/openstack/keymanager/v1/acls_test.go b/internal/acceptance/openstack/keymanager/v1/acls_test.go index ab9cd61716..5e5b620022 100644 --- a/internal/acceptance/openstack/keymanager/v1/acls_test.go +++ b/internal/acceptance/openstack/keymanager/v1/acls_test.go @@ -49,8 +49,8 @@ func TestACLCRUD(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, acl) tools.PrintResource(t, (*acl)["read"].Created) - th.AssertEquals(t, len((*acl)["read"].Users), 1) - th.AssertEquals(t, (*acl)["read"].ProjectAccess, false) + th.AssertEquals(t, 1, len((*acl)["read"].Users)) + th.AssertFalse(t, (*acl)["read"].ProjectAccess) newUsers := []string{} updateOpts := acls.SetOpts{ @@ -68,8 +68,8 @@ func TestACLCRUD(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, acl) tools.PrintResource(t, (*acl)["read"].Created) - th.AssertEquals(t, len((*acl)["read"].Users), 0) - th.AssertEquals(t, (*acl)["read"].ProjectAccess, false) + th.AssertEquals(t, 0, len((*acl)["read"].Users)) + th.AssertFalse(t, (*acl)["read"].ProjectAccess) container, err := CreateGenericContainer(t, client, secret) th.AssertNoErr(t, err) @@ -92,8 +92,8 @@ func TestACLCRUD(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, acl) tools.PrintResource(t, (*acl)["read"].Created) - th.AssertEquals(t, len((*acl)["read"].Users), 1) - th.AssertEquals(t, (*acl)["read"].ProjectAccess, false) + th.AssertEquals(t, 1, len((*acl)["read"].Users)) + th.AssertFalse(t, (*acl)["read"].ProjectAccess) aclRef, err = acls.UpdateContainerACL(context.TODO(), client, containerID, updateOpts).Extract() th.AssertNoErr(t, err) @@ -103,6 +103,6 @@ func TestACLCRUD(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, acl) tools.PrintResource(t, (*acl)["read"].Created) - th.AssertEquals(t, len((*acl)["read"].Users), 0) - th.AssertEquals(t, (*acl)["read"].ProjectAccess, false) + th.AssertEquals(t, 0, len((*acl)["read"].Users)) + th.AssertFalse(t, (*acl)["read"].ProjectAccess) } diff --git a/internal/acceptance/openstack/keymanager/v1/containers_test.go b/internal/acceptance/openstack/keymanager/v1/containers_test.go index 9ebb5b3058..0e56b0971d 100644 --- a/internal/acceptance/openstack/keymanager/v1/containers_test.go +++ b/internal/acceptance/openstack/keymanager/v1/containers_test.go @@ -53,7 +53,7 @@ func TestGenericContainersCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestCertificateContainer(t *testing.T) { @@ -172,7 +172,7 @@ func TestContainerConsumersCRUD(t *testing.T) { container, err = containers.CreateConsumer(context.TODO(), client, containerID, consumerCreateOpts).Extract() th.AssertNoErr(t, err) tools.PrintResource(t, container.Consumers) - th.AssertEquals(t, len(container.Consumers), 1) + th.AssertEquals(t, 1, len(container.Consumers)) defer func() { deleteOpts := containers.DeleteConsumerOpts{ Name: consumerName, @@ -181,7 +181,7 @@ func TestContainerConsumersCRUD(t *testing.T) { container, err := containers.DeleteConsumer(context.TODO(), client, containerID, deleteOpts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, len(container.Consumers), 0) + th.AssertEquals(t, 0, len(container.Consumers)) }() allPages, err := containers.ListConsumers(client, containerID, nil).AllPages(context.TODO()) @@ -197,5 +197,5 @@ func TestContainerConsumersCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } diff --git a/internal/acceptance/openstack/keymanager/v1/keymanager.go b/internal/acceptance/openstack/keymanager/v1/keymanager.go index a44b57a4bf..295b71241d 100644 --- a/internal/acceptance/openstack/keymanager/v1/keymanager.go +++ b/internal/acceptance/openstack/keymanager/v1/keymanager.go @@ -63,7 +63,7 @@ func CreateAsymmetricOrder(t *testing.T, client *gophercloud.ServiceClient) (*or tools.PrintResource(t, order.Meta.Expiration) th.AssertEquals(t, order.Meta.Name, name) - th.AssertEquals(t, order.Type, "asymmetric") + th.AssertEquals(t, "asymmetric", order.Type) return order, nil } @@ -114,7 +114,7 @@ func CreateCertificateContainer(t *testing.T, client *gophercloud.ServiceClient, tools.PrintResource(t, container) th.AssertEquals(t, container.Name, containerName) - th.AssertEquals(t, container.Type, "certificate") + th.AssertEquals(t, "certificate", container.Type) return container, nil } @@ -156,7 +156,7 @@ func CreateKeyOrder(t *testing.T, client *gophercloud.ServiceClient) (*orders.Or tools.PrintResource(t, order.Meta.Expiration) th.AssertEquals(t, order.Meta.Name, name) - th.AssertEquals(t, order.Type, "key") + th.AssertEquals(t, "key", order.Type) return order, nil } @@ -207,7 +207,7 @@ func CreateRSAContainer(t *testing.T, client *gophercloud.ServiceClient, passphr tools.PrintResource(t, container) th.AssertEquals(t, container.Name, containerName) - th.AssertEquals(t, container.Type, "rsa") + th.AssertEquals(t, "rsa", container.Type) return container, nil } @@ -249,7 +249,8 @@ func CreateCertificateSecret(t *testing.T, client *gophercloud.ServiceClient, ce tools.PrintResource(t, secret) th.AssertEquals(t, secret.Name, name) - th.AssertEquals(t, secret.Algorithm, "rsa") + th.AssertEquals(t, "rsa", secret.Algorithm) + th.AssertEquals(t, "certificate", secret.SecretType) return secret, nil } @@ -289,7 +290,10 @@ func CreateEmptySecret(t *testing.T, client *gophercloud.ServiceClient) (*secret tools.PrintResource(t, secret) th.AssertEquals(t, secret.Name, secretName) - th.AssertEquals(t, secret.Algorithm, "aes") + th.AssertEquals(t, "aes", secret.Algorithm) + th.AssertEquals(t, 256, secret.BitLength) + th.AssertEquals(t, "cbc", secret.Mode) + th.AssertEquals(t, "opaque", secret.SecretType) return secret, nil } @@ -333,7 +337,7 @@ func CreateGenericContainer(t *testing.T, client *gophercloud.ServiceClient, sec tools.PrintResource(t, container) th.AssertEquals(t, container.Name, containerName) - th.AssertEquals(t, container.Type, "generic") + th.AssertEquals(t, "generic", container.Type) return container, nil } @@ -423,7 +427,10 @@ func CreatePassphraseSecret(t *testing.T, client *gophercloud.ServiceClient, pas tools.PrintResource(t, secret) th.AssertEquals(t, secret.Name, secretName) - th.AssertEquals(t, secret.Algorithm, "aes") + th.AssertEquals(t, "aes", secret.Algorithm) + th.AssertEquals(t, 256, secret.BitLength) + th.AssertEquals(t, "cbc", secret.Mode) + th.AssertEquals(t, "passphrase", secret.SecretType) return secret, nil } @@ -465,7 +472,8 @@ func CreatePublicSecret(t *testing.T, client *gophercloud.ServiceClient, pub []b tools.PrintResource(t, secret) th.AssertEquals(t, secret.Name, name) - th.AssertEquals(t, secret.Algorithm, "rsa") + th.AssertEquals(t, "rsa", secret.Algorithm) + th.AssertEquals(t, "public", secret.SecretType) return secret, nil } @@ -507,7 +515,8 @@ func CreatePrivateSecret(t *testing.T, client *gophercloud.ServiceClient, priv [ tools.PrintResource(t, secret) th.AssertEquals(t, secret.Name, name) - th.AssertEquals(t, secret.Algorithm, "rsa") + th.AssertEquals(t, "rsa", secret.Algorithm) + th.AssertEquals(t, "private", secret.SecretType) return secret, nil } @@ -551,7 +560,10 @@ func CreateSecretWithPayload(t *testing.T, client *gophercloud.ServiceClient, pa tools.PrintResource(t, secret) th.AssertEquals(t, secret.Name, secretName) - th.AssertEquals(t, secret.Algorithm, "aes") + th.AssertEquals(t, "aes", secret.Algorithm) + th.AssertEquals(t, 256, secret.BitLength) + th.AssertEquals(t, "cbc", secret.Mode) + th.AssertEquals(t, "opaque", secret.SecretType) th.AssertEquals(t, secret.Expiration, expiration) return secret, nil @@ -597,7 +609,10 @@ func CreateSymmetricSecret(t *testing.T, client *gophercloud.ServiceClient) (*se tools.PrintResource(t, secret) th.AssertEquals(t, secret.Name, name) - th.AssertEquals(t, secret.Algorithm, "aes") + th.AssertEquals(t, "aes", secret.Algorithm) + th.AssertEquals(t, 256, secret.BitLength) + th.AssertEquals(t, "cbc", secret.Mode) + th.AssertEquals(t, "symmetric", secret.SecretType) return secret, nil } @@ -646,7 +661,7 @@ func DeleteSecret(t *testing.T, client *gophercloud.ServiceClient, id string) { func ParseID(ref string) (string, error) { parts := strings.Split(ref, "/") if len(parts) < 2 { - return "", fmt.Errorf("Could not parse %s", ref) + return "", fmt.Errorf("could not parse %s", ref) } return parts[len(parts)-1], nil @@ -757,7 +772,7 @@ func WaitForOrder(client *gophercloud.ServiceClient, orderID string) error { } if order.Status == "ERROR" { - return false, fmt.Errorf("Order %s in ERROR state", orderID) + return false, fmt.Errorf("order %s in ERROR state", orderID) } return false, nil diff --git a/internal/acceptance/openstack/keymanager/v1/orders_test.go b/internal/acceptance/openstack/keymanager/v1/orders_test.go index b5235ff88f..496f2871a0 100644 --- a/internal/acceptance/openstack/keymanager/v1/orders_test.go +++ b/internal/acceptance/openstack/keymanager/v1/orders_test.go @@ -49,7 +49,7 @@ func TestOrdersCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestOrdersAsymmetric(t *testing.T) { diff --git a/internal/acceptance/openstack/keymanager/v1/secrets_test.go b/internal/acceptance/openstack/keymanager/v1/secrets_test.go index 078d190587..33fed359ba 100644 --- a/internal/acceptance/openstack/keymanager/v1/secrets_test.go +++ b/internal/acceptance/openstack/keymanager/v1/secrets_test.go @@ -52,7 +52,7 @@ func TestSecretsCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestSecretsDelayedPayload(t *testing.T) { @@ -105,8 +105,8 @@ func TestSecretsMetadataCRUD(t *testing.T) { metadata, err := secrets.GetMetadata(context.TODO(), client, secretID).Extract() th.AssertNoErr(t, err) tools.PrintResource(t, metadata) - th.AssertEquals(t, metadata["foo"], "bar") - th.AssertEquals(t, metadata["something"], "something else") + th.AssertEquals(t, "bar", metadata["foo"]) + th.AssertEquals(t, "something else", metadata["something"]) // Add a single metadatum metadatumOpts := secrets.MetadatumOpts{ @@ -120,10 +120,10 @@ func TestSecretsMetadataCRUD(t *testing.T) { metadata, err = secrets.GetMetadata(context.TODO(), client, secretID).Extract() th.AssertNoErr(t, err) tools.PrintResource(t, metadata) - th.AssertEquals(t, len(metadata), 3) - th.AssertEquals(t, metadata["foo"], "bar") - th.AssertEquals(t, metadata["something"], "something else") - th.AssertEquals(t, metadata["bar"], "baz") + th.AssertEquals(t, 3, len(metadata)) + th.AssertEquals(t, "bar", metadata["foo"]) + th.AssertEquals(t, "something else", metadata["something"]) + th.AssertEquals(t, "baz", metadata["bar"]) // Update a metadatum metadatumOpts.Key = "foo" @@ -132,16 +132,16 @@ func TestSecretsMetadataCRUD(t *testing.T) { metadatum, err := secrets.UpdateMetadatum(context.TODO(), client, secretID, metadatumOpts).Extract() th.AssertNoErr(t, err) tools.PrintResource(t, metadatum) - th.AssertDeepEquals(t, metadatum.Key, "foo") - th.AssertDeepEquals(t, metadatum.Value, "foo") + th.AssertDeepEquals(t, "foo", metadatum.Key) + th.AssertDeepEquals(t, "foo", metadatum.Value) metadata, err = secrets.GetMetadata(context.TODO(), client, secretID).Extract() th.AssertNoErr(t, err) tools.PrintResource(t, metadata) - th.AssertEquals(t, len(metadata), 3) - th.AssertEquals(t, metadata["foo"], "foo") - th.AssertEquals(t, metadata["something"], "something else") - th.AssertEquals(t, metadata["bar"], "baz") + th.AssertEquals(t, 3, len(metadata)) + th.AssertEquals(t, "foo", metadata["foo"]) + th.AssertEquals(t, "something else", metadata["something"]) + th.AssertEquals(t, "baz", metadata["bar"]) // Delete a metadatum err = secrets.DeleteMetadatum(context.TODO(), client, secretID, "foo").ExtractErr() @@ -150,9 +150,9 @@ func TestSecretsMetadataCRUD(t *testing.T) { metadata, err = secrets.GetMetadata(context.TODO(), client, secretID).Extract() th.AssertNoErr(t, err) tools.PrintResource(t, metadata) - th.AssertEquals(t, len(metadata), 2) - th.AssertEquals(t, metadata["something"], "something else") - th.AssertEquals(t, metadata["bar"], "baz") + th.AssertEquals(t, 2, len(metadata)) + th.AssertEquals(t, "something else", metadata["something"]) + th.AssertEquals(t, "baz", metadata["bar"]) } func TestSymmetricSecret(t *testing.T) { diff --git a/internal/acceptance/openstack/loadbalancer/v2/availabilityzoneprofiles_test.go b/internal/acceptance/openstack/loadbalancer/v2/availabilityzoneprofiles_test.go new file mode 100644 index 0000000000..238647a3b0 --- /dev/null +++ b/internal/acceptance/openstack/loadbalancer/v2/availabilityzoneprofiles_test.go @@ -0,0 +1,54 @@ +//go:build acceptance || networking || loadbalancer || availabilityzoneprofiles + +package v2 + +import ( + "context" + "testing" + + "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" + "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" + "github.com/gophercloud/gophercloud/v2/internal/ptr" + "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/availabilityzoneprofiles" + + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func TestAvailabilityZoneProfilesList(t *testing.T) { + client, err := clients.NewLoadBalancerV2Client() + th.AssertNoErr(t, err) + + allPages, err := availabilityzoneprofiles.List(client, nil).AllPages(context.TODO()) + th.AssertNoErr(t, err) + + allAvailabilityZoneProfiles, err := availabilityzoneprofiles.ExtractAvailabilityZoneProfiles(allPages) + th.AssertNoErr(t, err) + + for _, availabilityzoneprofile := range allAvailabilityZoneProfiles { + tools.PrintResource(t, availabilityzoneprofile) + } +} + +func TestAvailabilityZoneProfilesCRUD(t *testing.T) { + lbClient, err := clients.NewLoadBalancerV2Client() + th.AssertNoErr(t, err) + + availabilityZoneProfile, err := CreateAvailabilityZoneProfile(t, lbClient) + th.AssertNoErr(t, err) + defer DeleteAvailabilityZoneProfile(t, lbClient, availabilityZoneProfile) + + tools.PrintResource(t, availabilityZoneProfile) + + th.AssertEquals(t, "amphora", availabilityZoneProfile.ProviderName) + + availabilityZoneProfileUpdateOpts := availabilityzoneprofiles.UpdateOpts{ + Name: ptr.To(tools.RandomString("TESTACCTUP-", 8)), + } + + availabilityZoneProfileUpdated, err := availabilityzoneprofiles.Update(context.TODO(), lbClient, availabilityZoneProfile.ID, availabilityZoneProfileUpdateOpts).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, *availabilityZoneProfileUpdateOpts.Name, availabilityZoneProfileUpdated.Name) + + t.Logf("Successfully updated availabiltyzoneprofile %s", availabilityZoneProfileUpdated.Name) +} diff --git a/internal/acceptance/openstack/loadbalancer/v2/flavorprofiles_test.go b/internal/acceptance/openstack/loadbalancer/v2/flavorprofiles_test.go index 1a6ebadf28..7151a3458b 100644 --- a/internal/acceptance/openstack/loadbalancer/v2/flavorprofiles_test.go +++ b/internal/acceptance/openstack/loadbalancer/v2/flavorprofiles_test.go @@ -8,6 +8,7 @@ import ( "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" + "github.com/gophercloud/gophercloud/v2/internal/ptr" "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/flavorprofiles" th "github.com/gophercloud/gophercloud/v2/testhelper" @@ -41,13 +42,13 @@ func TestFlavorProfilesCRUD(t *testing.T) { th.AssertEquals(t, "amphora", flavorProfile.ProviderName) flavorProfileUpdateOpts := flavorprofiles.UpdateOpts{ - Name: tools.RandomString("TESTACCTUP-", 8), + Name: ptr.To(tools.RandomString("TESTACCTUP-", 8)), } flavorProfileUpdated, err := flavorprofiles.Update(context.TODO(), lbClient, flavorProfile.ID, flavorProfileUpdateOpts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, flavorProfileUpdateOpts.Name, flavorProfileUpdated.Name) + th.AssertEquals(t, *flavorProfileUpdateOpts.Name, flavorProfileUpdated.Name) t.Logf("Successfully updated flavorprofile %s", flavorProfileUpdated.Name) } diff --git a/internal/acceptance/openstack/loadbalancer/v2/flavors_test.go b/internal/acceptance/openstack/loadbalancer/v2/flavors_test.go index b714af7324..84aca91451 100644 --- a/internal/acceptance/openstack/loadbalancer/v2/flavors_test.go +++ b/internal/acceptance/openstack/loadbalancer/v2/flavors_test.go @@ -8,6 +8,7 @@ import ( "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" + "github.com/gophercloud/gophercloud/v2/internal/ptr" "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/flavors" th "github.com/gophercloud/gophercloud/v2/testhelper" ) @@ -51,16 +52,16 @@ func TestFlavorsCRUD(t *testing.T) { tools.PrintResource(t, flavor) - th.AssertEquals(t, flavor.FlavorProfileId, flavorProfile.ID) + th.AssertEquals(t, flavor.FlavorProfileID, flavorProfile.ID) flavorUpdateOpts := flavors.UpdateOpts{ - Name: tools.RandomString("TESTACCTUP-", 8), + Name: ptr.To(tools.RandomString("TESTACCTUP-", 8)), } flavorUpdated, err := flavors.Update(context.TODO(), lbClient, flavor.ID, flavorUpdateOpts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, flavorUpdateOpts.Name, flavorUpdated.Name) + th.AssertEquals(t, *flavorUpdateOpts.Name, flavorUpdated.Name) t.Logf("Successfully updated flavor %s", flavorUpdated.Name) } diff --git a/internal/acceptance/openstack/loadbalancer/v2/loadbalancer.go b/internal/acceptance/openstack/loadbalancer/v2/loadbalancer.go index e8596506ef..a086a27053 100644 --- a/internal/acceptance/openstack/loadbalancer/v2/loadbalancer.go +++ b/internal/acceptance/openstack/loadbalancer/v2/loadbalancer.go @@ -10,6 +10,8 @@ import ( "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" + "github.com/gophercloud/gophercloud/v2/internal/ptr" + "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/availabilityzoneprofiles" "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/flavorprofiles" "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/flavors" "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/l7policies" @@ -46,7 +48,7 @@ func CreateListener(t *testing.T, client *gophercloud.ServiceClient, lb *loadbal t.Logf("Successfully created listener %s", listenerName) if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE"); err != nil { - return listener, fmt.Errorf("Timed out waiting for loadbalancer to become active: %s", err) + return listener, fmt.Errorf("timed out waiting for loadbalancer to become active: %s", err) } th.AssertEquals(t, listener.Name, listenerName) @@ -98,7 +100,7 @@ func CreateListenerHTTP(t *testing.T, client *gophercloud.ServiceClient, lb *loa t.Logf("Successfully created listener %s", listenerName) if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE"); err != nil { - return listener, fmt.Errorf("Timed out waiting for loadbalancer to become active: %s", err) + return listener, fmt.Errorf("timed out waiting for loadbalancer to become active: %s", err) } th.AssertEquals(t, listener.Name, listenerName) @@ -152,7 +154,7 @@ func CreateLoadBalancer(t *testing.T, client *gophercloud.ServiceClient, subnetI th.AssertEquals(t, lb.Name, lbName) th.AssertEquals(t, lb.Description, lbDescription) th.AssertEquals(t, lb.VipSubnetID, subnetID) - th.AssertEquals(t, lb.AdminStateUp, true) + th.AssertTrue(t, lb.AdminStateUp) if len(tags) > 0 { th.AssertDeepEquals(t, lb.Tags, tags) @@ -213,6 +215,7 @@ func CreateLoadBalancerFullyPopulated(t *testing.T, client *gophercloud.ServiceC MaxRetries: 5, MaxRetriesDown: 4, Type: monitors.TypeHTTP, + HTTPVersion: "1.0", }, }, L7Policies: []l7policies.CreateOpts{{ @@ -249,28 +252,35 @@ func CreateLoadBalancerFullyPopulated(t *testing.T, client *gophercloud.ServiceC th.AssertEquals(t, lb.Name, lbName) th.AssertEquals(t, lb.Description, lbDescription) th.AssertEquals(t, lb.VipSubnetID, subnetID) - th.AssertEquals(t, lb.AdminStateUp, true) + th.AssertTrue(t, lb.AdminStateUp) - th.AssertEquals(t, len(lb.Listeners), 1) + th.AssertEquals(t, 1, len(lb.Listeners)) th.AssertEquals(t, lb.Listeners[0].Name, listenerName) th.AssertEquals(t, lb.Listeners[0].Description, listenerDescription) th.AssertEquals(t, lb.Listeners[0].ProtocolPort, listenerPort) - th.AssertEquals(t, len(lb.Listeners[0].L7Policies), 1) + th.AssertEquals(t, 1, len(lb.Listeners[0].L7Policies)) th.AssertEquals(t, lb.Listeners[0].L7Policies[0].Name, policyName) th.AssertEquals(t, lb.Listeners[0].L7Policies[0].Description, policyDescription) th.AssertEquals(t, lb.Listeners[0].L7Policies[0].Description, policyDescription) - th.AssertEquals(t, len(lb.Listeners[0].L7Policies[0].Rules), 1) + th.AssertEquals(t, 1, len(lb.Listeners[0].L7Policies[0].Rules)) - th.AssertEquals(t, len(lb.Pools), 1) + th.AssertEquals(t, 1, len(lb.Pools)) th.AssertEquals(t, lb.Pools[0].Name, poolName) th.AssertEquals(t, lb.Pools[0].Description, poolDescription) - th.AssertEquals(t, len(lb.Pools[0].Members), 1) + th.AssertEquals(t, 1, len(lb.Pools[0].Members)) th.AssertEquals(t, lb.Pools[0].Members[0].Name, memberName) th.AssertEquals(t, lb.Pools[0].Members[0].ProtocolPort, memberPort) th.AssertEquals(t, lb.Pools[0].Members[0].Weight, memberWeight) + th.AssertEquals(t, 10, lb.Pools[0].Monitor.Delay) + th.AssertEquals(t, 5, lb.Pools[0].Monitor.Timeout) + th.AssertEquals(t, 5, lb.Pools[0].Monitor.MaxRetries) + th.AssertEquals(t, 4, lb.Pools[0].Monitor.MaxRetriesDown) + th.AssertEquals(t, lb.Pools[0].Monitor.Type, string(monitors.TypeHTTP)) + th.AssertEquals(t, "1.0", lb.Pools[0].Monitor.HTTPVersion) + if len(tags) > 0 { th.AssertDeepEquals(t, lb.Tags, tags) } @@ -309,10 +319,14 @@ func CreateMember(t *testing.T, client *gophercloud.ServiceClient, lb *loadbalan t.Logf("Successfully created member %s", memberName) if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE"); err != nil { - return member, fmt.Errorf("Timed out waiting for loadbalancer to become active: %s", err) + return member, fmt.Errorf("timed out waiting for loadbalancer to become active: %s", err) } - th.AssertEquals(t, member.Name, memberName) + th.AssertEquals(t, memberName, member.Name) + th.AssertEquals(t, memberPort, member.ProtocolPort) + th.AssertEquals(t, memberWeight, member.Weight) + th.AssertEquals(t, memberAddress, member.Address) + th.AssertEquals(t, subnetID, member.SubnetID) return member, nil } @@ -332,6 +346,7 @@ func CreateMonitor(t *testing.T, client *gophercloud.ServiceClient, lb *loadbala MaxRetries: 5, MaxRetriesDown: 4, Type: monitors.TypePING, + HTTPVersion: "1.1", } monitor, err := monitors.Create(context.TODO(), client, createOpts).Extract() @@ -342,15 +357,16 @@ func CreateMonitor(t *testing.T, client *gophercloud.ServiceClient, lb *loadbala t.Logf("Successfully created monitor: %s", monitorName) if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE"); err != nil { - return monitor, fmt.Errorf("Timed out waiting for loadbalancer to become active: %s", err) + return monitor, fmt.Errorf("timed out waiting for loadbalancer to become active: %s", err) } th.AssertEquals(t, monitor.Name, monitorName) th.AssertEquals(t, monitor.Type, monitors.TypePING) - th.AssertEquals(t, monitor.Delay, 10) - th.AssertEquals(t, monitor.Timeout, 5) - th.AssertEquals(t, monitor.MaxRetries, 5) - th.AssertEquals(t, monitor.MaxRetriesDown, 4) + th.AssertEquals(t, 10, monitor.Delay) + th.AssertEquals(t, 5, monitor.Timeout) + th.AssertEquals(t, 5, monitor.MaxRetries) + th.AssertEquals(t, 4, monitor.MaxRetriesDown) + th.AssertEquals(t, "1.1", monitor.HTTPVersion) return monitor, nil } @@ -380,7 +396,7 @@ func CreatePool(t *testing.T, client *gophercloud.ServiceClient, lb *loadbalance t.Logf("Successfully created pool %s", poolName) if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE"); err != nil { - return pool, fmt.Errorf("Timed out waiting for loadbalancer to become active: %s", err) + return pool, fmt.Errorf("timed out waiting for loadbalancer to become active: %s", err) } th.AssertEquals(t, pool.Name, poolName) @@ -417,7 +433,7 @@ func CreatePoolHTTP(t *testing.T, client *gophercloud.ServiceClient, lb *loadbal t.Logf("Successfully created pool %s", poolName) if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE"); err != nil { - return pool, fmt.Errorf("Timed out waiting for loadbalancer to become active: %s", err) + return pool, fmt.Errorf("timed out waiting for loadbalancer to become active: %s", err) } th.AssertEquals(t, pool.Name, poolName) @@ -455,14 +471,14 @@ func CreateL7Policy(t *testing.T, client *gophercloud.ServiceClient, listener *l t.Logf("Successfully created l7 policy %s", policyName) if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE"); err != nil { - return policy, fmt.Errorf("Timed out waiting for loadbalancer to become active: %s", err) + return policy, fmt.Errorf("timed out waiting for loadbalancer to become active: %s", err) } th.AssertEquals(t, policy.Name, policyName) th.AssertEquals(t, policy.Description, policyDescription) th.AssertEquals(t, policy.ListenerID, listener.ID) th.AssertEquals(t, policy.Action, string(l7policies.ActionRedirectToURL)) - th.AssertEquals(t, policy.RedirectURL, "http://www.example.com") + th.AssertEquals(t, "http://www.example.com", policy.RedirectURL) th.AssertDeepEquals(t, policy.Tags, tags) return policy, nil @@ -487,12 +503,12 @@ func CreateL7Rule(t *testing.T, client *gophercloud.ServiceClient, policyID stri t.Logf("Successfully created l7 rule for policy %s", policyID) if err := WaitForLoadBalancerState(client, lb.ID, "ACTIVE"); err != nil { - return rule, fmt.Errorf("Timed out waiting for loadbalancer to become active: %s", err) + return rule, fmt.Errorf("timed out waiting for loadbalancer to become active: %s", err) } th.AssertEquals(t, rule.RuleType, string(l7policies.TypePath)) th.AssertEquals(t, rule.CompareType, string(l7policies.CompareTypeStartWith)) - th.AssertEquals(t, rule.Value, "/api") + th.AssertEquals(t, "/api", rule.Value) th.AssertDeepEquals(t, rule.Tags, tags) return rule, nil @@ -675,7 +691,7 @@ func WaitForLoadBalancerState(client *gophercloud.ServiceClient, lbID, status st } if current.ProvisioningStatus == "ERROR" { - return false, fmt.Errorf("Load balancer is in ERROR state") + return false, fmt.Errorf("load balancer is in ERROR state") } return false, nil @@ -723,8 +739,8 @@ func CreateFlavor(t *testing.T, client *gophercloud.ServiceClient, flavorProfile createOpts := flavors.CreateOpts{ Name: flavorName, Description: description, - FlavorProfileId: flavorProfile.ID, - Enabled: true, + FlavorProfileID: flavorProfile.ID, + Enabled: ptr.To(false), } flavor, err := flavors.Create(context.TODO(), client, createOpts).Extract() @@ -736,8 +752,8 @@ func CreateFlavor(t *testing.T, client *gophercloud.ServiceClient, flavorProfile th.AssertEquals(t, flavorName, flavor.Name) th.AssertEquals(t, description, flavor.Description) - th.AssertEquals(t, flavorProfile.ID, flavor.FlavorProfileId) - th.AssertEquals(t, true, flavor.Enabled) + th.AssertEquals(t, flavorProfile.ID, flavor.FlavorProfileID) + th.AssertFalse(t, flavor.Enabled) return flavor, nil } @@ -750,3 +766,37 @@ func DeleteFlavor(t *testing.T, client *gophercloud.ServiceClient, flavor *flavo t.Logf("Successfully deleted flavor %s", flavor.Name) } + +func CreateAvailabilityZoneProfile(t *testing.T, client *gophercloud.ServiceClient) (*availabilityzoneprofiles.AvailabilityZoneProfile, error) { + availabilityZoneProfileName := tools.RandomString("TESTACCT-", 8) + availabilityZoneProfileDriver := "amphora" + availabilityZoneData := "{\"compute_zone\": \"nova\"}" + + createOpts := availabilityzoneprofiles.CreateOpts{ + Name: availabilityZoneProfileName, + ProviderName: availabilityZoneProfileDriver, + AvailabilityZoneData: availabilityZoneData, + } + + availabilityZoneProfile, err := availabilityzoneprofiles.Create(context.TODO(), client, createOpts).Extract() + if err != nil { + return availabilityZoneProfile, err + } + + t.Logf("Successfully created availabilityzoneprofile %s", availabilityZoneProfileName) + + th.AssertEquals(t, availabilityZoneProfileName, availabilityZoneProfile.Name) + th.AssertEquals(t, availabilityZoneProfileDriver, availabilityZoneProfile.ProviderName) + th.AssertEquals(t, availabilityZoneData, availabilityZoneProfile.AvailabilityZoneData) + + return availabilityZoneProfile, nil +} + +func DeleteAvailabilityZoneProfile(t *testing.T, client *gophercloud.ServiceClient, availabilityZoneProfile *availabilityzoneprofiles.AvailabilityZoneProfile) { + err := availabilityzoneprofiles.Delete(context.TODO(), client, availabilityZoneProfile.ID).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete availabilityzoneprofile: %v", err) + } + + t.Logf("Successfully deleted availabilityzoneprofile %s", availabilityZoneProfile.Name) +} diff --git a/internal/acceptance/openstack/loadbalancer/v2/loadbalancers_test.go b/internal/acceptance/openstack/loadbalancer/v2/loadbalancers_test.go index 98556fbc25..c0942d4651 100644 --- a/internal/acceptance/openstack/loadbalancer/v2/loadbalancers_test.go +++ b/internal/acceptance/openstack/loadbalancer/v2/loadbalancers_test.go @@ -358,8 +358,8 @@ func TestLoadbalancersCRUD(t *testing.T) { tools.PrintResource(t, newListener) - th.AssertEquals(t, newListener.Name, listenerName) - th.AssertEquals(t, newListener.Description, listenerDescription) + th.AssertEquals(t, listenerName, newListener.Name) + th.AssertEquals(t, listenerDescription, newListener.Description) listenerStats, err := listeners.GetStats(context.TODO(), lbClient, listener.ID).Extract() th.AssertNoErr(t, err) @@ -406,7 +406,7 @@ func TestLoadbalancersCRUD(t *testing.T) { tools.PrintResource(t, newListener) - th.AssertEquals(t, newListener.DefaultPoolID, "") + th.AssertEquals(t, "", newListener.DefaultPoolID) // Member member, err := CreateMember(t, lbClient, lb, pool, subnet.ID, subnet.CIDR) @@ -430,7 +430,8 @@ func TestLoadbalancersCRUD(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, newMember) - th.AssertEquals(t, newMember.Name, memberName) + th.AssertEquals(t, memberName, newMember.Name) + th.AssertEquals(t, newWeight, newMember.Weight) newWeight = tools.RandomInt(11, 100) memberOpts := pools.BatchUpdateMemberOpts{ @@ -534,6 +535,7 @@ func TestLoadbalancersCascadeCRUD(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, newListener) + th.AssertEquals(t, listenerDescription, newListener.Description) // Pool pool, err := CreatePool(t, lbClient, lb) @@ -554,6 +556,7 @@ func TestLoadbalancersCascadeCRUD(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, newPool) + th.AssertEquals(t, poolDescription, newPool.Description) // Member member, err := CreateMember(t, lbClient, lb, newPool, subnet.ID, subnet.CIDR) @@ -574,6 +577,7 @@ func TestLoadbalancersCascadeCRUD(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, newMember) + th.AssertEquals(t, newWeight, newMember.Weight) // Monitor monitor, err := CreateMonitor(t, lbClient, lb, newPool) diff --git a/internal/acceptance/openstack/loadbalancer/v2/quotas_test.go b/internal/acceptance/openstack/loadbalancer/v2/quotas_test.go index cc778e5403..87f9d7621f 100644 --- a/internal/acceptance/openstack/loadbalancer/v2/quotas_test.go +++ b/internal/acceptance/openstack/loadbalancer/v2/quotas_test.go @@ -80,3 +80,13 @@ func TestQuotasUpdate(t *testing.T) { tools.PrintResource(t, restoredQuotas) } + +func TestQuotasDelete(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewLoadBalancerV2Client() + th.AssertNoErr(t, err) + + err = quotas.Delete(context.TODO(), client, os.Getenv("OS_PROJECT_NAME")).ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/internal/acceptance/openstack/messaging/v2/message_test.go b/internal/acceptance/openstack/messaging/v2/message_test.go index fed1dc6db8..650fa18415 100644 --- a/internal/acceptance/openstack/messaging/v2/message_test.go +++ b/internal/acceptance/openstack/messaging/v2/message_test.go @@ -38,7 +38,8 @@ func TestListMessages(t *testing.T) { client, err = clients.NewMessagingV2Client(clientID) th.AssertNoErr(t, err) - listOpts := messages.ListOpts{} + // We use limit=1 to regression test https://github.com/gophercloud/gophercloud/issues/3336 + listOpts := messages.ListOpts{Limit: 1} pager := messages.List(client, createdQueueName, listOpts) err = pager.EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { diff --git a/internal/acceptance/openstack/messaging/v2/queue_test.go b/internal/acceptance/openstack/messaging/v2/queue_test.go index a259fd5fb9..cbeb64ae41 100644 --- a/internal/acceptance/openstack/messaging/v2/queue_test.go +++ b/internal/acceptance/openstack/messaging/v2/queue_test.go @@ -74,10 +74,8 @@ func TestListQueues(t *testing.T) { th.AssertNoErr(t, err) defer DeleteQueue(t, client, secondQueueName) - listOpts := queues.ListOpts{ - Limit: 10, - Detailed: true, - } + // We use limit=1 to regression test https://github.com/gophercloud/gophercloud/issues/3336 + listOpts := queues.ListOpts{Limit: 1, Detailed: true} pager := queues.List(client, listOpts) err = pager.EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { diff --git a/internal/acceptance/openstack/metric/v1/metrics_test.go b/internal/acceptance/openstack/metric/v1/metrics_test.go new file mode 100644 index 0000000000..9aa021457e --- /dev/null +++ b/internal/acceptance/openstack/metric/v1/metrics_test.go @@ -0,0 +1,80 @@ +//go:build acceptance || metric || metrics + +package v1 + +import ( + "context" + "testing" + + "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" + "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" + "github.com/gophercloud/gophercloud/v2/openstack/metric/v1/metrics" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func TestMetricQuery(t *testing.T) { + client, err := clients.NewMetricV1Client() + th.AssertNoErr(t, err) + + result, err := metrics.Query(context.TODO(), client, metrics.QueryOpts{ + Query: "up", + }).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, result) + th.AssertEquals(t, "vector", result.ResultType) +} + +func TestMetricLabels(t *testing.T) { + client, err := clients.NewMetricV1Client() + th.AssertNoErr(t, err) + + result, err := metrics.Labels(context.TODO(), client, nil).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, result) + if len(result) == 0 { + t.Fatal("expected at least one label") + } +} + +func TestMetricLabelValues(t *testing.T) { + client, err := clients.NewMetricV1Client() + th.AssertNoErr(t, err) + + result, err := metrics.LabelValues(context.TODO(), client, "__name__", nil).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, result) + if len(result) == 0 { + t.Fatal("expected at least one label value") + } +} + +func TestMetricSeries(t *testing.T) { + client, err := clients.NewMetricV1Client() + th.AssertNoErr(t, err) + + result, err := metrics.Series(context.TODO(), client, metrics.SeriesOpts{ + Match: []string{`{__name__="up"}`}, + }).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, result) + if len(result) == 0 { + t.Fatal("expected at least one series") + } +} + +func TestMetricRuntimeInfo(t *testing.T) { + client, err := clients.NewMetricV1Client() + th.AssertNoErr(t, err) + + result, err := metrics.RuntimeInfo(context.TODO(), client).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, result) + if result.StorageRetention == "" { + t.Fatal("expected non-empty StorageRetention") + } +} diff --git a/internal/acceptance/openstack/metric/v1/pkg.go b/internal/acceptance/openstack/metric/v1/pkg.go new file mode 100644 index 0000000000..05e9b4a1ab --- /dev/null +++ b/internal/acceptance/openstack/metric/v1/pkg.go @@ -0,0 +1,4 @@ +//go:build acceptance || metric + +// Package v1 contains acceptance tests for the OpenStack metric-storage v1 service. +package v1 diff --git a/internal/acceptance/openstack/networking/v2/extensions/agents/agents_test.go b/internal/acceptance/openstack/networking/v2/extensions/agents/agents_test.go index 37b5e39c3b..ff6bc9b016 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/agents/agents_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/agents/agents_test.go @@ -16,8 +16,8 @@ import ( th "github.com/gophercloud/gophercloud/v2/testhelper" ) -func TestAgentsRUD(t *testing.T) { - t.Skip("TestAgentsRUD needs to be re-worked to work with both ML2/OVS and OVN") +func TestAgentsCRUD(t *testing.T) { + t.Skip("TestAgentsCRUD needs to be re-worked to work with both ML2/OVS and OVN") clients.RequireAdmin(t) client, err := clients.NewNetworkV2Client() @@ -96,13 +96,16 @@ func TestAgentsRUD(t *testing.T) { th.AssertNoErr(t, err) } -func TestBGPAgentRUD(t *testing.T) { - timeout := 15 * time.Minute +func TestBGPAgentCRUD(t *testing.T) { + timeout := 120 * time.Second clients.RequireAdmin(t) client, err := clients.NewNetworkV2Client() th.AssertNoErr(t, err) + // Skip these tests if we don't have the required extension + networking.RequireNeutronExtension(t, client, "bgp") + // List BGP Agents listOpts := &agents.ListOpts{ AgentType: "BGP Dynamic Routing Agent", @@ -119,86 +122,73 @@ func TestBGPAgentRUD(t *testing.T) { // Create a BGP Speaker bgpSpeaker, err := spk.CreateBGPSpeaker(t, client) th.AssertNoErr(t, err) + + // List BGP Speaker-Agent associations pages, err := agents.ListDRAgentHostingBGPSpeakers(client, bgpSpeaker.ID).AllPages(context.TODO()) th.AssertNoErr(t, err) - bgpAgents, err := agents.ExtractAgents(pages) - th.AssertNoErr(t, err) - th.AssertIntGreaterOrEqual(t, len(bgpAgents), 1) - - // List the BGP Agents that accommodate the BGP Speaker - err = tools.WaitForTimeout( - func(ctx context.Context) (bool, error) { - flag := true - for _, agt := range bgpAgents { - t.Logf("BGP Speaker %s has been scheduled to agent %s", bgpSpeaker.ID, agt.ID) - bgpAgent, err := agents.Get(ctx, client, agt.ID).Extract() - th.AssertNoErr(t, err) - numOfSpeakers := int(bgpAgent.Configurations["bgp_speakers"].(float64)) - flag = flag && (numOfSpeakers == 1) - } - return flag, nil - }, timeout) + bgpAgentsForSpeaker, err := agents.ExtractAgents(pages) th.AssertNoErr(t, err) - // List the BGP speakers on the first agent - bgpAgent, err := agents.Get(context.TODO(), client, bgpAgents[0].ID).Extract() - th.AssertNoErr(t, err) - agentConf := bgpAgent.Configurations - numOfSpeakers := int(agentConf["bgp_speakers"].(float64)) - t.Logf("Agent %s has %d speaker(s)", bgpAgents[0].ID, numOfSpeakers) + // If there are no associations, we can assume the static scheduler is in + // effect and we must manually associate/disassociate the speaker from the + // agent. + // + // https://docs.openstack.org/neutron-dynamic-routing/latest/admin/agent-scheduler.html + doManualAssignment := len(bgpAgentsForSpeaker) == 0 + var agentID string - pages, err = agents.ListBGPSpeakers(client, bgpAgents[0].ID).AllPages(context.TODO()) - th.AssertNoErr(t, err) - allSpeakers, err := agents.ExtractBGPSpeakers(pages) - th.AssertNoErr(t, err) - out := "Speakers:" - for _, speaker := range allSpeakers { - out += " " + speaker.ID + if doManualAssignment { + // If using manual assignment, schedule a BGP Speaker to an agent + agentID = allAgents[0].ID + opts := agents.ScheduleBGPSpeakerOpts{ + SpeakerID: bgpSpeaker.ID, + } + err = agents.ScheduleBGPSpeaker(context.TODO(), client, agentID, opts).ExtractErr() + th.AssertNoErr(t, err) + t.Logf("Successfully scheduled speaker %s to agent %s", bgpSpeaker.ID, agentID) + } else { + // If using automatic assignment, pick the first agent that the speaker + // was assigned to (it may be assigned to many, depending on how many + // nodes there are) + agentID = bgpAgentsForSpeaker[0].ID } - t.Log(out) - // Remove the BGP Speaker from the first agent - err = agents.RemoveBGPSpeaker(context.TODO(), client, bgpAgents[0].ID, bgpSpeaker.ID).ExtractErr() + // Wait for the association to complete. + pages, err = agents.ListDRAgentHostingBGPSpeakers(client, bgpSpeaker.ID).AllPages(context.TODO()) + th.AssertNoErr(t, err) + bgpAgentsForSpeaker, err = agents.ExtractAgents(pages) th.AssertNoErr(t, err) - t.Logf("BGP Speaker %s has been removed from agent %s", bgpSpeaker.ID, bgpAgents[0].ID) err = tools.WaitForTimeout( func(ctx context.Context) (bool, error) { - bgpAgent, err := agents.Get(ctx, client, bgpAgents[0].ID).Extract() + bgpAgent, err := agents.Get(ctx, client, agentID).Extract() th.AssertNoErr(t, err) agentConf := bgpAgent.Configurations numOfSpeakers := int(agentConf["bgp_speakers"].(float64)) - t.Logf("Agent %s has %d speaker(s)", bgpAgent.ID, numOfSpeakers) - return numOfSpeakers == 0, nil + t.Logf("Agent %s has %d speaker(s)", agentID, numOfSpeakers) + return numOfSpeakers == 1, nil }, timeout) th.AssertNoErr(t, err) - // Remove all BGP Speakers from the agent - pages, err = agents.ListBGPSpeakers(client, bgpAgents[0].ID).AllPages(context.TODO()) + // Disassociate the BGP Speaker from the agent. + err = agents.RemoveBGPSpeaker(context.TODO(), client, bgpAgentsForSpeaker[0].ID, bgpSpeaker.ID).ExtractErr() th.AssertNoErr(t, err) - allSpeakers, err = agents.ExtractBGPSpeakers(pages) - th.AssertNoErr(t, err) - for _, speaker := range allSpeakers { - th.AssertNoErr(t, agents.RemoveBGPSpeaker(context.TODO(), client, bgpAgents[0].ID, speaker.ID).ExtractErr()) - } + t.Logf("BGP Speaker %s has been removed from agent %s", bgpSpeaker.ID, bgpAgentsForSpeaker[0].ID) - // Schedule a BGP Speaker to an agent - opts := agents.ScheduleBGPSpeakerOpts{ - SpeakerID: bgpSpeaker.ID, + // Only validate the disassociation if we know the static scheduler is in + // effect as it'll simply be recreated if we're using the chance scheduler + // and running in a single node deployment. + if doManualAssignment { + err = tools.WaitForTimeout( + func(ctx context.Context) (bool, error) { + bgpAgent, err := agents.Get(ctx, client, bgpAgentsForSpeaker[0].ID).Extract() + th.AssertNoErr(t, err) + agentConf := bgpAgent.Configurations + numOfSpeakers := int(agentConf["bgp_speakers"].(float64)) + t.Logf("Agent %s has %d speaker(s)", bgpAgent.ID, numOfSpeakers) + return numOfSpeakers == 0, nil + }, timeout) + th.AssertNoErr(t, err) } - err = agents.ScheduleBGPSpeaker(context.TODO(), client, bgpAgents[0].ID, opts).ExtractErr() - th.AssertNoErr(t, err) - t.Logf("Successfully scheduled speaker %s to agent %s", bgpSpeaker.ID, bgpAgents[0].ID) - - err = tools.WaitForTimeout( - func(ctx context.Context) (bool, error) { - bgpAgent, err := agents.Get(ctx, client, bgpAgents[0].ID).Extract() - th.AssertNoErr(t, err) - agentConf := bgpAgent.Configurations - numOfSpeakers := int(agentConf["bgp_speakers"].(float64)) - t.Logf("Agent %s has %d speaker(s)", bgpAgent.ID, numOfSpeakers) - return 1 == numOfSpeakers, nil - }, timeout) - th.AssertNoErr(t, err) // Delete the BGP Speaker err = speakers.Delete(context.TODO(), client, bgpSpeaker.ID).ExtractErr() @@ -206,12 +196,12 @@ func TestBGPAgentRUD(t *testing.T) { t.Logf("Successfully deleted the BGP Speaker, %s", bgpSpeaker.ID) err = tools.WaitForTimeout( func(ctx context.Context) (bool, error) { - bgpAgent, err := agents.Get(ctx, client, bgpAgents[0].ID).Extract() + bgpAgent, err := agents.Get(ctx, client, agentID).Extract() th.AssertNoErr(t, err) agentConf := bgpAgent.Configurations numOfSpeakers := int(agentConf["bgp_speakers"].(float64)) t.Logf("Agent %s has %d speaker(s)", bgpAgent.ID, numOfSpeakers) - return 0 == numOfSpeakers, nil + return numOfSpeakers == 0, nil }, timeout) th.AssertNoErr(t, err) } diff --git a/internal/acceptance/openstack/networking/v2/extensions/attributestags_test.go b/internal/acceptance/openstack/networking/v2/extensions/attributestags_test.go index c691a575f3..6111fb2dc6 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/attributestags_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/attributestags_test.go @@ -65,10 +65,10 @@ func TestTags(t *testing.T) { // Confirm tags exist/don't exist exists, err := attributestags.Confirm(context.TODO(), client, "networks", network.ID, "d").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, true, exists) + th.AssertTrue(t, exists) noexists, err := attributestags.Confirm(context.TODO(), client, "networks", network.ID, "a").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, false, noexists) + th.AssertFalse(t, noexists) // Delete all tags err = attributestags.DeleteAll(context.TODO(), client, "networks", network.ID).ExtractErr() diff --git a/internal/acceptance/openstack/networking/v2/extensions/bgp/peers/bgppeers_test.go b/internal/acceptance/openstack/networking/v2/extensions/bgp/peers/bgppeers_test.go index 8fdee3c034..66bf7230b5 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/bgp/peers/bgppeers_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/bgp/peers/bgppeers_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" + networking "github.com/gophercloud/gophercloud/v2/internal/acceptance/openstack/networking/v2" "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/bgp/peers" th "github.com/gophercloud/gophercloud/v2/testhelper" @@ -18,6 +19,9 @@ func TestBGPPeerCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() th.AssertNoErr(t, err) + // Skip these tests if we don't have the required extension + networking.RequireNeutronExtension(t, client, "bgp") + // Create a BGP Peer bgpPeerCreated, err := CreateBGPPeer(t, client) th.AssertNoErr(t, err) @@ -30,9 +34,10 @@ func TestBGPPeerCRUD(t *testing.T) { // Update a BGP Peer newBGPPeerName := tools.RandomString("TESTACC-BGPPEER-", 10) + pass := tools.MakeNewPassword("") updateBGPOpts := peers.UpdateOpts{ - Name: newBGPPeerName, - Password: tools.MakeNewPassword(""), + Name: &newBGPPeerName, + Password: &pass, } bgpPeerUpdated, err := peers.Update(context.TODO(), client, bgpPeerGot.ID, updateBGPOpts).Extract() th.AssertNoErr(t, err) diff --git a/internal/acceptance/openstack/networking/v2/extensions/bgp/peers/peers.go b/internal/acceptance/openstack/networking/v2/extensions/bgp/peers/peers.go index 198673498b..e5d071b2b8 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/bgp/peers/peers.go +++ b/internal/acceptance/openstack/networking/v2/extensions/bgp/peers/peers.go @@ -27,6 +27,7 @@ func CreateBGPPeer(t *testing.T, client *gophercloud.ServiceClient) (*peers.BGPP th.AssertEquals(t, bgpPeer.Name, opts.Name) th.AssertEquals(t, bgpPeer.RemoteAS, opts.RemoteAS) th.AssertEquals(t, bgpPeer.PeerIP, opts.PeerIP) + th.AssertEquals(t, opts.AuthType, bgpPeer.AuthType) t.Logf("Successfully created BGP Peer") tools.PrintResource(t, bgpPeer) return bgpPeer, err diff --git a/internal/acceptance/openstack/networking/v2/extensions/bgp/speakers/bgpspeakers_test.go b/internal/acceptance/openstack/networking/v2/extensions/bgp/speakers/bgpspeakers_test.go index 5b50d6df59..d4e96afa19 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/bgp/speakers/bgpspeakers_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/bgp/speakers/bgpspeakers_test.go @@ -17,9 +17,13 @@ import ( func TestBGPSpeakerCRUD(t *testing.T) { clients.RequireAdmin(t) + client, err := clients.NewNetworkV2Client() th.AssertNoErr(t, err) + // Skip these tests if we don't have the required extension + networking.RequireNeutronExtension(t, client, "bgp") + // Create a BGP Speaker bgpSpeaker, err := CreateBGPSpeaker(t, client) th.AssertNoErr(t, err) @@ -44,21 +48,25 @@ func TestBGPSpeakerCRUD(t *testing.T) { defer networking.DeleteNetwork(t, client, network.ID) // Update BGP Speaker + name := tools.RandomString("TESTACC-BGPSPEAKER-", 10) + iTrue := true opts := speakers.UpdateOpts{ - Name: tools.RandomString("TESTACC-BGPSPEAKER-", 10), - AdvertiseTenantNetworks: false, - AdvertiseFloatingIPHostRoutes: true, + Name: &name, + AdvertiseTenantNetworks: new(bool), + AdvertiseFloatingIPHostRoutes: &iTrue, } speakerUpdated, err := speakers.Update(context.TODO(), client, bgpSpeaker.ID, opts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, speakerUpdated.Name, opts.Name) + th.AssertEquals(t, speakerUpdated.Name, *opts.Name) + th.AssertFalse(t, speakerUpdated.AdvertiseTenantNetworks) + th.AssertTrue(t, speakerUpdated.AdvertiseFloatingIPHostRoutes) t.Logf("Updated the BGP Speaker, name set from %s to %s", bgpSpeaker.Name, speakerUpdated.Name) // Get a BGP Speaker bgpSpeakerGot, err := speakers.Get(context.TODO(), client, bgpSpeaker.ID).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, bgpSpeaker.ID, bgpSpeakerGot.ID) - th.AssertEquals(t, opts.Name, bgpSpeakerGot.Name) + th.AssertEquals(t, *opts.Name, bgpSpeakerGot.Name) // AddBGPPeer addBGPPeerOpts := speakers.AddBGPPeerOpts{BGPPeerID: bgpPeer.ID} @@ -75,7 +83,7 @@ func TestBGPSpeakerCRUD(t *testing.T) { th.AssertNoErr(t, err) speakerGot, err = speakers.Get(context.TODO(), client, bgpSpeaker.ID).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, len(speakerGot.Networks), 0) + th.AssertEquals(t, 0, len(speakerGot.Networks)) t.Logf("Successfully removed BGP Peer %s to BGP Speaker %s", bgpPeer.Name, speakerUpdated.Name) // GetAdvertisedRoutes diff --git a/internal/acceptance/openstack/networking/v2/extensions/bgp/speakers/speakers.go b/internal/acceptance/openstack/networking/v2/extensions/bgp/speakers/speakers.go index d9e620144d..05ff71af2e 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/bgp/speakers/speakers.go +++ b/internal/acceptance/openstack/networking/v2/extensions/bgp/speakers/speakers.go @@ -2,7 +2,6 @@ package speakers import ( "context" - "strconv" "testing" "github.com/gophercloud/gophercloud/v2" @@ -12,13 +11,13 @@ import ( ) func CreateBGPSpeaker(t *testing.T, client *gophercloud.ServiceClient) (*speakers.BGPSpeaker, error) { + iTrue := true opts := speakers.CreateOpts{ IPVersion: 4, - AdvertiseFloatingIPHostRoutes: false, - AdvertiseTenantNetworks: true, + AdvertiseFloatingIPHostRoutes: new(bool), + AdvertiseTenantNetworks: &iTrue, Name: tools.RandomString("TESTACC-BGPSPEAKER-", 8), - LocalAS: "3000", - Networks: []string{}, + LocalAS: 3000, } t.Logf("Attempting to create BGP Speaker: %s", opts.Name) @@ -27,12 +26,11 @@ func CreateBGPSpeaker(t *testing.T, client *gophercloud.ServiceClient) (*speaker return bgpSpeaker, err } - localas, err := strconv.Atoi(opts.LocalAS) th.AssertEquals(t, bgpSpeaker.Name, opts.Name) - th.AssertEquals(t, bgpSpeaker.LocalAS, localas) + th.AssertEquals(t, bgpSpeaker.LocalAS, opts.LocalAS) th.AssertEquals(t, bgpSpeaker.IPVersion, opts.IPVersion) - th.AssertEquals(t, bgpSpeaker.AdvertiseTenantNetworks, opts.AdvertiseTenantNetworks) - th.AssertEquals(t, bgpSpeaker.AdvertiseFloatingIPHostRoutes, opts.AdvertiseFloatingIPHostRoutes) + th.AssertEquals(t, bgpSpeaker.AdvertiseTenantNetworks, *opts.AdvertiseTenantNetworks) + th.AssertEquals(t, bgpSpeaker.AdvertiseFloatingIPHostRoutes, *opts.AdvertiseFloatingIPHostRoutes) t.Logf("Successfully created BGP Speaker") tools.PrintResource(t, bgpSpeaker) return bgpSpeaker, err diff --git a/internal/acceptance/openstack/networking/v2/extensions/bgpvpns/bgpvpns_test.go b/internal/acceptance/openstack/networking/v2/extensions/bgpvpns/bgpvpns_test.go index 01542d6ec3..6880121e29 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/bgpvpns/bgpvpns_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/bgpvpns/bgpvpns_test.go @@ -20,6 +20,9 @@ func TestBGPVPNCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() th.AssertNoErr(t, err) + // Skip these tests if we don't have the required extension + networking.RequireNeutronExtension(t, client, "bgpvpn") + // Create a BGP VPN bgpVpnCreated, err := CreateBGPVPN(t, client) th.AssertNoErr(t, err) @@ -60,12 +63,15 @@ func TestBGPVPNCRUD(t *testing.T) { t.Logf("BGP VPN %s deleted", bgpVpnUpdated.Name) } -func TestBGPVPNNetworkAssociationCRD(t *testing.T) { +func TestBGPVPNNetworkAssociationCRUD(t *testing.T) { clients.RequireAdmin(t) client, err := clients.NewNetworkV2Client() th.AssertNoErr(t, err) + // Skip these tests if we don't have the required extension + networking.RequireNeutronExtension(t, client, "bgpvpn") + // Create a BGP VPN bgpVpnCreated, err := CreateBGPVPN(t, client) th.AssertNoErr(t, err) @@ -117,6 +123,9 @@ func TestBGPVPNRouterAssociationCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() th.AssertNoErr(t, err) + // Skip these tests if we don't have the required extension + networking.RequireNeutronExtension(t, client, "bgpvpn") + // Create a BGP VPN bgpVpnCreated, err := CreateBGPVPN(t, client) th.AssertNoErr(t, err) @@ -159,7 +168,7 @@ func TestBGPVPNRouterAssociationCRUD(t *testing.T) { assocUpdate, err := bgpvpns.UpdateRouterAssociation(context.TODO(), client, bgpVpnCreated.ID, assoc.ID, assocUpdOpts).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, routerCreated.ID, assocUpdate.RouterID) - th.AssertEquals(t, false, assocUpdate.AdvertiseExtraRoutes) + th.AssertFalse(t, assocUpdate.AdvertiseExtraRoutes) // List all Router Associations allPages, err := bgpvpns.ListRouterAssociations(client, bgpVpnCreated.ID, bgpvpns.ListRouterAssociationsOpts{}).AllPages(context.TODO()) @@ -182,6 +191,9 @@ func TestBGPVPNPortAssociationCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() th.AssertNoErr(t, err) + // Skip these tests if we don't have the required extension + networking.RequireNeutronExtension(t, client, "bgpvpn") + // Create a BGP VPN bgpVpnCreated, err := CreateBGPVPN(t, client) th.AssertNoErr(t, err) @@ -229,7 +241,7 @@ func TestBGPVPNPortAssociationCRUD(t *testing.T) { assocUpdate, err := bgpvpns.UpdatePortAssociation(context.TODO(), client, bgpVpnCreated.ID, assoc.ID, assocUpdOpts).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, port.ID, assocUpdate.PortID) - th.AssertEquals(t, false, assocUpdate.AdvertiseFixedIPs) + th.AssertFalse(t, assocUpdate.AdvertiseFixedIPs) // List all Port Associations allPages, err := bgpvpns.ListPortAssociations(client, bgpVpnCreated.ID, bgpvpns.ListPortAssociationsOpts{}).AllPages(context.TODO()) diff --git a/internal/acceptance/openstack/networking/v2/extensions/dns/dns_test.go b/internal/acceptance/openstack/networking/v2/extensions/dns/dns_test.go index d709a4252d..e9aee0dffb 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/dns/dns_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/dns/dns_test.go @@ -125,7 +125,7 @@ func TestDNSPortCRUDL(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, newPort) - th.AssertEquals(t, newPort.Description, newPortName) + th.AssertEquals(t, newPort.Name, newPortName) th.AssertEquals(t, newPort.Description, newPortDescription) th.AssertEquals(t, newPort.DNSName, newDNSName) @@ -141,8 +141,8 @@ func TestDNSPortCRUDL(t *testing.T) { th.AssertDeepEquals(t, newPort, getNewPort) } -func TestDNSFloatingIPCRDL(t *testing.T) { - t.Skip("Skipping TestDNSFloatingIPCRDL for now, as it doesn't work with ML2/OVN.") +func TestDNSFloatingIPCRUD(t *testing.T) { + t.Skip("Skipping TestDNSFloatingIPCRUD for now, as it doesn't work with ML2/OVN.") clients.RequireAdmin(t) client, err := clients.NewNetworkV2Client() @@ -242,7 +242,7 @@ func TestDNSNetwork(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, newNetwork) - th.AssertEquals(t, newNetwork.Description, newNetworkName) + th.AssertEquals(t, newNetwork.Name, newNetworkName) th.AssertEquals(t, newNetwork.Description, newNetworkDescription) th.AssertEquals(t, newNetwork.DNSDomain, newNetworkDNSDomain) diff --git a/internal/acceptance/openstack/networking/v2/extensions/extensions.go b/internal/acceptance/openstack/networking/v2/extensions/extensions.go index 4de1401f4d..3b21db64e4 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/extensions.go +++ b/internal/acceptance/openstack/networking/v2/extensions/extensions.go @@ -7,6 +7,7 @@ import ( "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/external" + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/security/addressgroups" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/security/groups" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/security/rules" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/networks" @@ -43,8 +44,9 @@ func CreateExternalNetwork(t *testing.T, client *gophercloud.ServiceClient) (*ne t.Logf("Created external network: %s", networkName) - th.AssertEquals(t, network.Name, networkName) - th.AssertEquals(t, network.Description, networkDescription) + th.AssertEquals(t, networkName, network.Name) + th.AssertEquals(t, networkDescription, network.Description) + th.AssertTrue(t, network.AdminStateUp) return network, nil } @@ -74,9 +76,9 @@ func CreatePortWithSecurityGroup(t *testing.T, client *gophercloud.ServiceClient t.Logf("Successfully created port: %s", portName) - th.AssertEquals(t, port.Name, portName) - th.AssertEquals(t, port.Description, portDescription) - th.AssertEquals(t, port.NetworkID, networkID) + th.AssertEquals(t, portName, port.Name) + th.AssertEquals(t, portDescription, port.Description) + th.AssertEquals(t, networkID, port.NetworkID) return port, nil } @@ -101,8 +103,8 @@ func CreateSecurityGroup(t *testing.T, client *gophercloud.ServiceClient) (*grou t.Logf("Created security group: %s", secGroup.ID) - th.AssertEquals(t, secGroup.Name, secGroupName) - th.AssertEquals(t, secGroup.Description, secGroupDescription) + th.AssertEquals(t, secGroupName, secGroup.Name) + th.AssertEquals(t, secGroupDescription, secGroup.Description) return secGroup, nil } @@ -136,10 +138,51 @@ func CreateSecurityGroupRule(t *testing.T, client *gophercloud.ServiceClient, se th.AssertEquals(t, rule.SecGroupID, secGroupID) th.AssertEquals(t, rule.Description, description) + th.AssertEquals(t, "ingress", rule.Direction) + th.AssertEquals(t, "IPv4", rule.EtherType) + th.AssertEquals(t, fromPort, rule.PortRangeMin) + th.AssertEquals(t, toPort, rule.PortRangeMax) + th.AssertEquals(t, string(rules.ProtocolTCP), rule.Protocol) return rule, nil } +// CreateSecurityGroupRulesBulk will create 3 security group rules with ports between 1080 and 1099. +// An error will be returned if one was failed to be created. +func CreateSecurityGroupRulesBulk(t *testing.T, client *gophercloud.ServiceClient, secGroupID string) ([]rules.SecGroupRule, error) { + t.Logf("Attempting to bulk create security group rules in group: %s", secGroupID) + + sgRulesCreateOpts := make([]rules.CreateOpts, 3) + for i := range sgRulesCreateOpts { + fromPort := 1080 + i + toPort := tools.RandomInt(fromPort, 1099) + + sgRulesCreateOpts[i] = rules.CreateOpts{ + Description: "Rule description", + Direction: "ingress", + EtherType: "IPv4", + SecGroupID: secGroupID, + PortRangeMin: fromPort, + PortRangeMax: toPort, + Protocol: rules.ProtocolTCP, + } + } + + rules, err := rules.CreateBulk(context.TODO(), client, sgRulesCreateOpts).Extract() + if err != nil { + return rules, err + } + + for i, rule := range rules { + t.Logf("Created security group rule: %s", rule.ID) + + th.AssertEquals(t, sgRulesCreateOpts[i].SecGroupID, rule.SecGroupID) + th.AssertEquals(t, sgRulesCreateOpts[i].Description, rule.Description) + } + + return rules, nil +} + // DeleteSecurityGroup will delete a security group of a specified ID. // A fatal error will occur if the deletion failed. This works best as a // deferred function @@ -163,3 +206,45 @@ func DeleteSecurityGroupRule(t *testing.T, client *gophercloud.ServiceClient, ru t.Fatalf("Unable to delete security group rule: %v", err) } } + +// CreateSecurityAddressGroup will create a security address group with a random name. +func CreateSecurityAddressGroup(t *testing.T, client *gophercloud.ServiceClient) (*addressgroups.AddressGroup, error) { + addressGroupName := tools.RandomString("TESTACC-", 8) + addressGroupDescription := tools.RandomString("TESTACC-DESC-", 8) + + t.Logf("Attempting to create security address group: %s", addressGroupName) + + addresses := []string{ + "192.168.1.1/32", + } + createOpts := addressgroups.CreateOpts{ + Name: addressGroupName, + Description: addressGroupDescription, + Addresses: addresses, + } + + addressGroup, err := addressgroups.Create(context.TODO(), client, createOpts).Extract() + if err != nil { + return addressGroup, err + } + + t.Logf("Created security address group: %s", addressGroup.ID) + + th.AssertEquals(t, addressGroupName, addressGroup.Name) + th.AssertEquals(t, addressGroupDescription, addressGroup.Description) + th.AssertDeepEquals(t, addresses, addressGroup.Addresses) + + return addressGroup, nil +} + +// DeleteSecurityAddressGroup will delete a security address group of a specified ID. +// A fatal error will occur if the deletion failed. This works best as a +// deferred function +func DeleteSecurityAddressGroup(t *testing.T, client *gophercloud.ServiceClient, addressGroupID string) { + t.Logf("Attempting to delete security address group: %s", addressGroupID) + + err := addressgroups.Delete(context.TODO(), client, addressGroupID).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete security address group: %v", err) + } +} diff --git a/internal/acceptance/openstack/networking/v2/extensions/fwaas_v2/fwaas_v2.go b/internal/acceptance/openstack/networking/v2/extensions/fwaas_v2/fwaas_v2.go index 805b2265be..2e0a4dabf6 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/fwaas_v2/fwaas_v2.go +++ b/internal/acceptance/openstack/networking/v2/extensions/fwaas_v2/fwaas_v2.go @@ -64,7 +64,7 @@ func CreatePolicy(t *testing.T, client *gophercloud.ServiceClient, ruleID string th.AssertEquals(t, policy.Name, policyName) th.AssertEquals(t, policy.Description, policyDescription) - th.AssertEquals(t, len(policy.Rules), 1) + th.AssertEquals(t, 1, len(policy.Rules)) return policy, nil } @@ -167,6 +167,8 @@ func CreateGroup(t *testing.T, client *gophercloud.ServiceClient) (*groups.Group t.Logf("firewall group %s successfully created", groupName) th.AssertEquals(t, group.Name, groupName) + th.AssertEquals(t, description, group.Description) + th.AssertTrue(t, group.AdminStateUp) return group, nil } diff --git a/internal/acceptance/openstack/networking/v2/extensions/fwaas_v2/groups_test.go b/internal/acceptance/openstack/networking/v2/extensions/fwaas_v2/groups_test.go index b8ee611c95..cadde9cc91 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/fwaas_v2/groups_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/fwaas_v2/groups_test.go @@ -73,7 +73,7 @@ func TestGroupCRUD(t *testing.T) { t.Fatalf("Unable to remove ingress firewall policy from firewall group %s: %v", removeIngressPolicy.ID, err) } - th.AssertEquals(t, removeIngressPolicy.IngressFirewallPolicyID, "") + th.AssertEquals(t, "", removeIngressPolicy.IngressFirewallPolicyID) th.AssertEquals(t, removeIngressPolicy.EgressFirewallPolicyID, firewall_policy_id) t.Logf("Ingress policy removed from firewall group %s", updatedGroup.ID) @@ -83,7 +83,7 @@ func TestGroupCRUD(t *testing.T) { t.Fatalf("Unable to remove egress firewall policy from firewall group %s: %v", removeEgressPolicy.ID, err) } - th.AssertEquals(t, removeEgressPolicy.EgressFirewallPolicyID, "") + th.AssertEquals(t, "", removeEgressPolicy.EgressFirewallPolicyID) t.Logf("Egress policy removed from firewall group %s", updatedGroup.ID) @@ -102,5 +102,5 @@ func TestGroupCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } diff --git a/internal/acceptance/openstack/networking/v2/extensions/fwaas_v2/policy_test.go b/internal/acceptance/openstack/networking/v2/extensions/fwaas_v2/policy_test.go index 881e9a87aa..5cc4862f04 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/fwaas_v2/policy_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/fwaas_v2/policy_test.go @@ -68,7 +68,7 @@ func TestPolicyCRUD(t *testing.T) { tools.PrintResource(t, newPolicy) th.AssertEquals(t, newPolicy.Name, name) th.AssertEquals(t, newPolicy.Description, description) - th.AssertEquals(t, len(newPolicy.Rules), 0) + th.AssertEquals(t, 0, len(newPolicy.Rules)) allPages, err := policies.List(client, nil).AllPages(context.TODO()) th.AssertNoErr(t, err) @@ -83,5 +83,5 @@ func TestPolicyCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } diff --git a/internal/acceptance/openstack/networking/v2/extensions/fwaas_v2/rule_test.go b/internal/acceptance/openstack/networking/v2/extensions/fwaas_v2/rule_test.go index 53de93fa84..329d80cf56 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/fwaas_v2/rule_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/fwaas_v2/rule_test.go @@ -68,5 +68,5 @@ func TestRuleCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } diff --git a/internal/acceptance/openstack/networking/v2/extensions/layer3/addressscopes_test.go b/internal/acceptance/openstack/networking/v2/extensions/layer3/addressscopes_test.go index bb4c8ce2d4..8e83725f07 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/layer3/addressscopes_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/layer3/addressscopes_test.go @@ -50,5 +50,5 @@ func TestAddressScopesCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } diff --git a/internal/acceptance/openstack/networking/v2/extensions/layer3/floatingips_test.go b/internal/acceptance/openstack/networking/v2/extensions/layer3/floatingips_test.go index fee8cacd38..beebce75f7 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/layer3/floatingips_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/layer3/floatingips_test.go @@ -4,6 +4,7 @@ package layer3 import ( "context" + "strings" "testing" "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" @@ -43,7 +44,7 @@ func TestLayer3FloatingIPsCreateDelete(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestLayer3FloatingIPsExternalCreateDelete(t *testing.T) { @@ -99,7 +100,7 @@ func TestLayer3FloatingIPsExternalCreateDelete(t *testing.T) { tools.PrintResource(t, newFip) - th.AssertEquals(t, newFip.PortID, "") + th.AssertEquals(t, "", newFip.PortID) } func TestLayer3FloatingIPsWithFixedIPsExternalCreateDelete(t *testing.T) { @@ -203,3 +204,62 @@ func TestLayer3FloatingIPsCreateDeleteBySubnetID(t *testing.T) { DeleteFloatingIP(t, client, fip.ID) } + +func TestLayer3FloatingIPsRevision(t *testing.T) { + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + choices, err := clients.AcceptanceTestChoicesFromEnv() + th.AssertNoErr(t, err) + + fip, err := CreateFloatingIP(t, client, choices.ExternalNetworkID, "") + th.AssertNoErr(t, err) + defer DeleteFloatingIP(t, client, fip.ID) + + tools.PrintResource(t, fip) + + // Store the current revision number. + oldRevisionNumber := fip.RevisionNumber + + // Update the fip without revision number. + // This should work. + newDescription := "" + updateOpts := &floatingips.UpdateOpts{ + Description: &newDescription, + } + fip, err = floatingips.Update(context.TODO(), client, fip.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, fip) + + // This should fail due to an old revision number. + newDescription = "new description" + updateOpts = &floatingips.UpdateOpts{ + Description: &newDescription, + RevisionNumber: &oldRevisionNumber, + } + _, err = floatingips.Update(context.TODO(), client, fip.ID, updateOpts).Extract() + th.AssertErr(t, err) + if !strings.Contains(err.Error(), "RevisionNumberConstraintFailed") { + t.Fatalf("expected to see an error of type RevisionNumberConstraintFailed, but got the following error instead: %v", err) + } + + // Reread the fip to show that it did not change. + fip, err = floatingips.Get(context.TODO(), client, fip.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, fip) + + // This should work because now we do provide a valid revision number. + newDescription = "new description" + updateOpts = &floatingips.UpdateOpts{ + Description: &newDescription, + RevisionNumber: &fip.RevisionNumber, + } + fip, err = floatingips.Update(context.TODO(), client, fip.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, fip) + + th.AssertEquals(t, fip.Description, newDescription) +} diff --git a/internal/acceptance/openstack/networking/v2/extensions/layer3/l3_scheduling_test.go b/internal/acceptance/openstack/networking/v2/extensions/layer3/l3_scheduling_test.go index f186118d44..cb57ee6b01 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/layer3/l3_scheduling_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/layer3/l3_scheduling_test.go @@ -64,7 +64,7 @@ func TestLayer3RouterScheduling(t *testing.T) { // List routers on hosting agent routersOnHostingAgent, err := agents.ListL3Routers(context.TODO(), client, hostingAgent.ID).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, containsRouterFunc(routersOnHostingAgent, router.ID), false) + th.AssertFalse(t, containsRouterFunc(routersOnHostingAgent, router.ID)) t.Logf("Router %s is not scheduled on %s", router.ID, hostingAgent.ID) // schedule back @@ -74,6 +74,6 @@ func TestLayer3RouterScheduling(t *testing.T) { // List hosting agent after readding routersOnHostingAgent, err = agents.ListL3Routers(context.TODO(), client, hostingAgent.ID).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, containsRouterFunc(routersOnHostingAgent, router.ID), true) + th.AssertTrue(t, containsRouterFunc(routersOnHostingAgent, router.ID)) t.Logf("Router %s is scheduled on %s", router.ID, hostingAgent.ID) } diff --git a/internal/acceptance/openstack/networking/v2/extensions/layer3/layer3.go b/internal/acceptance/openstack/networking/v2/extensions/layer3/layer3.go index c45bb08069..be2324267c 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/layer3/layer3.go +++ b/internal/acceptance/openstack/networking/v2/extensions/layer3/layer3.go @@ -37,7 +37,8 @@ func CreateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, networkID t.Logf("Created floating IP.") - th.AssertEquals(t, floatingIP.Description, fipDescription) + th.AssertEquals(t, fipDescription, floatingIP.Description) + th.AssertEquals(t, networkID, floatingIP.FloatingNetworkID) return floatingIP, err } @@ -62,8 +63,9 @@ func CreateFloatingIPWithFixedIP(t *testing.T, client *gophercloud.ServiceClient t.Logf("Created floating IP.") - th.AssertEquals(t, floatingIP.Description, fipDescription) - th.AssertEquals(t, floatingIP.FixedIP, fixedIP) + th.AssertEquals(t, fipDescription, floatingIP.Description) + th.AssertEquals(t, networkID, floatingIP.FloatingNetworkID) + th.AssertEquals(t, fixedIP, floatingIP.FixedIP) return floatingIP, err } @@ -92,7 +94,46 @@ func CreatePortForwarding(t *testing.T, client *gophercloud.ServiceClient, fipID t.Logf("Created Port Forwarding.") - th.AssertEquals(t, pf.Protocol, "tcp") + th.AssertEquals(t, pfDescription, pf.Description) + th.AssertEquals(t, "tcp", pf.Protocol) + th.AssertEquals(t, 25, pf.InternalPort) + th.AssertEquals(t, 2230, pf.ExternalPort) + th.AssertEquals(t, internalIP, pf.InternalIPAddress) + th.AssertEquals(t, portID, pf.InternalPortID) + + return pf, err +} + +// CreatePortRangeForwarding creates a port range forwarding for a given floating IP +// and port. An error will be returned if the creation failed. +func CreatePortRangeForwarding(t *testing.T, client *gophercloud.ServiceClient, fipID string, portID string, portFixedIPs []ports.IP) (*portforwarding.PortForwarding, error) { + t.Logf("Attempting to create Port Range forwarding for floating IP with ID: %s", fipID) + + fixedIP := portFixedIPs[0] + internalIP := fixedIP.IPAddress + pfDescription := "Test description range" + createOpts := &portforwarding.CreateOpts{ + Description: pfDescription, + Protocol: "tcp", + InternalPortRange: "1200:1299", + ExternalPortRange: "1300:1399", + InternalIPAddress: internalIP, + InternalPortID: portID, + } + + pf, err := portforwarding.Create(context.TODO(), client, fipID, createOpts).Extract() + if err != nil { + return pf, err + } + + t.Logf("Created Port Range Forwarding.") + + th.AssertEquals(t, pfDescription, pf.Description) + th.AssertEquals(t, "tcp", pf.Protocol) + th.AssertEquals(t, "1200:1299", pf.InternalPortRange) + th.AssertEquals(t, "1300:1399", pf.ExternalPortRange) + th.AssertEquals(t, internalIP, pf.InternalIPAddress) + th.AssertEquals(t, portID, pf.InternalPortID) return pf, err } @@ -148,8 +189,9 @@ func CreateExternalRouter(t *testing.T, client *gophercloud.ServiceClient) (*rou t.Logf("Created router: %s", routerName) - th.AssertEquals(t, router.Name, routerName) - th.AssertEquals(t, router.Description, routerDescription) + th.AssertEquals(t, routerName, router.Name) + th.AssertEquals(t, routerDescription, router.Description) + th.AssertTrue(t, router.AdminStateUp) return router, nil } @@ -180,8 +222,9 @@ func CreateRouter(t *testing.T, client *gophercloud.ServiceClient, networkID str t.Logf("Created router: %s", routerName) - th.AssertEquals(t, router.Name, routerName) - th.AssertEquals(t, router.Description, routerDescription) + th.AssertEquals(t, routerName, router.Name) + th.AssertEquals(t, routerDescription, router.Description) + th.AssertTrue(t, router.AdminStateUp) return router, nil } diff --git a/internal/acceptance/openstack/networking/v2/extensions/layer3/portforwardings_test.go b/internal/acceptance/openstack/networking/v2/extensions/layer3/portforwardings_test.go index 3d3f208529..ba5983cb77 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/layer3/portforwardings_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/layer3/portforwardings_test.go @@ -15,11 +15,11 @@ import ( ) func TestLayer3PortForwardingsCreateDelete(t *testing.T) { - clients.RequirePortForwarding(t) - client, err := clients.NewNetworkV2Client() th.AssertNoErr(t, err) + networking.RequireNeutronExtension(t, client, "floating-ip-port-forwarding") + choices, err := clients.AcceptanceTestChoicesFromEnv() th.AssertNoErr(t, err) @@ -56,10 +56,17 @@ func TestLayer3PortForwardingsCreateDelete(t *testing.T) { pf, err := CreatePortForwarding(t, client, fip.ID, port.ID, port.FixedIPs) th.AssertNoErr(t, err) - th.AssertEquals(t, pf.Description, "Test description") + th.AssertEquals(t, "Test description", pf.Description) defer DeletePortForwarding(t, client, fip.ID, pf.ID) tools.PrintResource(t, pf) + pfRange, err := CreatePortRangeForwarding(t, client, fip.ID, port.ID, port.FixedIPs) + th.AssertNoErr(t, err) + th.AssertEquals(t, "Test description range", pfRange.Description) + defer DeletePortForwarding(t, client, fip.ID, pfRange.ID) + tools.PrintResource(t, pfRange) + + // Test updating port newPf, err := portforwarding.Get(context.TODO(), client, fip.ID, pf.ID).Extract() th.AssertNoErr(t, err) @@ -75,7 +82,31 @@ func TestLayer3PortForwardingsCreateDelete(t *testing.T) { newPf, err = portforwarding.Get(context.TODO(), client, fip.ID, pf.ID).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, newPf.Description, "") + th.AssertEquals(t, "", newPf.Description) + th.AssertEquals(t, "udp", newPf.Protocol) + th.AssertEquals(t, 30, newPf.InternalPort) + th.AssertEquals(t, 678, newPf.ExternalPort) + + // Test updating port range + newRangePf, err := portforwarding.Get(context.TODO(), client, fip.ID, pfRange.ID).Extract() + th.AssertNoErr(t, err) + + updateOpts = portforwarding.UpdateOpts{ + Description: new(string), + Protocol: "udp", + InternalPortRange: "1400:1499", + ExternalPortRange: "1500:1599", + } + + _, err = portforwarding.Update(context.TODO(), client, fip.ID, newRangePf.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + newRangePf, err = portforwarding.Get(context.TODO(), client, fip.ID, pfRange.ID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, "", newRangePf.Description) + th.AssertEquals(t, "udp", newRangePf.Protocol) + th.AssertEquals(t, "1400:1499", newRangePf.InternalPortRange) + th.AssertEquals(t, "1500:1599", newRangePf.ExternalPortRange) allPages, err := portforwarding.List(client, portforwarding.ListOpts{}, fip.ID).AllPages(context.TODO()) th.AssertNoErr(t, err) @@ -90,6 +121,15 @@ func TestLayer3PortForwardingsCreateDelete(t *testing.T) { } } - th.AssertEquals(t, true, found) + th.AssertTrue(t, found) + + found = false + for _, pf := range allPFs { + if pf.ID == newRangePf.ID { + found = true + } + } + + th.AssertTrue(t, found) } diff --git a/internal/acceptance/openstack/networking/v2/extensions/layer3/routers_test.go b/internal/acceptance/openstack/networking/v2/extensions/layer3/routers_test.go index 8d1800f719..639da4c45e 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/layer3/routers_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/layer3/routers_test.go @@ -4,6 +4,7 @@ package layer3 import ( "context" + "strings" "testing" "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" @@ -58,7 +59,7 @@ func TestLayer3RouterCreateDelete(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestLayer3ExternalRouterCreateDelete(t *testing.T) { @@ -123,7 +124,7 @@ func TestLayer3ExternalRouterCreateDelete(t *testing.T) { newRouter, err = routers.Update(context.TODO(), client, router.ID, updateOpts).Extract() th.AssertNoErr(t, err) - th.AssertDeepEquals(t, newRouter.GatewayInfo, routers.GatewayInfo{}) + th.AssertDeepEquals(t, routers.GatewayInfo{}, newRouter.GatewayInfo) } @@ -211,5 +212,167 @@ func TestLayer3RouterAgents(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) +} + +func TestLayer3RouterRevision(t *testing.T) { + // https://bugs.launchpad.net/neutron/+bug/2101871 + clients.SkipRelease(t, "stable/2023.2") + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + network, err := networking.CreateNetwork(t, client) + th.AssertNoErr(t, err) + defer networking.DeleteNetwork(t, client, network.ID) + + router, err := CreateRouter(t, client, network.ID) + th.AssertNoErr(t, err) + defer DeleteRouter(t, client, router.ID) + + tools.PrintResource(t, router) + + // Store the current revision number. + oldRevisionNumber := router.RevisionNumber + + // Update the router without revision number. + // This should work. + newName := tools.RandomString("TESTACC-", 8) + newDescription := "" + updateOpts := &routers.UpdateOpts{ + Name: newName, + Description: &newDescription, + } + router, err = routers.Update(context.TODO(), client, router.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, router) + + // This should fail due to an old revision number. + newDescription = "new description" + updateOpts = &routers.UpdateOpts{ + Name: newName, + Description: &newDescription, + RevisionNumber: &oldRevisionNumber, + } + _, err = routers.Update(context.TODO(), client, router.ID, updateOpts).Extract() + th.AssertErr(t, err) + if !strings.Contains(err.Error(), "RevisionNumberConstraintFailed") { + t.Fatalf("expected to see an error of type RevisionNumberConstraintFailed, but got the following error instead: %v", err) + } + + // Reread the router to show that it did not change. + router, err = routers.Get(context.TODO(), client, router.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, router) + + // This should work because now we do provide a valid revision number. + newDescription = "new description" + updateOpts = &routers.UpdateOpts{ + Name: newName, + Description: &newDescription, + RevisionNumber: &router.RevisionNumber, + } + router, err = routers.Update(context.TODO(), client, router.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, router) + + th.AssertEquals(t, router.Name, newName) + th.AssertEquals(t, router.Description, newDescription) +} + +func TestLayer3RouterExternalGateways(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + // Skip if the external-gateway-multihoming extension is not available + networking.RequireNeutronExtension(t, client, "external-gateway-multihoming") + + // Create a router with external gateway + router, err := CreateExternalRouter(t, client) + th.AssertNoErr(t, err) + defer DeleteRouter(t, client, router.ID) + + tools.PrintResource(t, router) + + choices, err := clients.AcceptanceTestChoicesFromEnv() + th.AssertNoErr(t, err) + + // Test AddExternalGateways + t.Logf("Attempting to add external gateways to router %s", router.ID) + + addOpts := routers.AddExternalGatewaysOpts{ + ExternalGateways: []routers.GatewayInfo{ + { + NetworkID: choices.ExternalNetworkID, + }, + }, + } + + updatedRouter, err := routers.AddExternalGateways(context.TODO(), client, router.ID, addOpts).Extract() + th.AssertNoErr(t, err) + + t.Logf("Successfully added external gateways to router %s", router.ID) + tools.PrintResource(t, updatedRouter) + + // Test UpdateExternalGateways + // Note: UpdateExternalGateways requires external_fixed_ips to identify which gateway to update + t.Logf("Attempting to update external gateways of router %s", router.ID) + + // Get current external_fixed_ips from the router to identify the gateway + currentFixedIPs := make([]routers.ExternalFixedIP, len(updatedRouter.GatewayInfo.ExternalFixedIPs)) + for i, ip := range updatedRouter.GatewayInfo.ExternalFixedIPs { + currentFixedIPs[i] = routers.ExternalFixedIP{ + IPAddress: ip.IPAddress, + SubnetID: ip.SubnetID, + } + } + + enableSNAT := false + updateOpts := routers.UpdateExternalGatewaysOpts{ + ExternalGateways: []routers.GatewayInfo{ + { + NetworkID: updatedRouter.GatewayInfo.NetworkID, + EnableSNAT: &enableSNAT, + ExternalFixedIPs: currentFixedIPs, + }, + }, + } + + updatedRouter, err = routers.UpdateExternalGateways(context.TODO(), client, router.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + t.Logf("Successfully updated external gateways of router %s", router.ID) + tools.PrintResource(t, updatedRouter) + + // Test RemoveExternalGateways + // Note: RemoveExternalGateways requires external_fixed_ips to identify which gateway to remove + t.Logf("Attempting to remove external gateways from router %s", router.ID) + + // Get current external_fixed_ips from the updated router + currentFixedIPs = make([]routers.ExternalFixedIP, len(updatedRouter.GatewayInfo.ExternalFixedIPs)) + for i, ip := range updatedRouter.GatewayInfo.ExternalFixedIPs { + currentFixedIPs[i] = routers.ExternalFixedIP{ + IPAddress: ip.IPAddress, + SubnetID: ip.SubnetID, + } + } + + removeOpts := routers.RemoveExternalGatewaysOpts{ + ExternalGateways: []routers.GatewayInfo{ + { + NetworkID: updatedRouter.GatewayInfo.NetworkID, + ExternalFixedIPs: currentFixedIPs, + }, + }, + } + + updatedRouter, err = routers.RemoveExternalGateways(context.TODO(), client, router.ID, removeOpts).Extract() + th.AssertNoErr(t, err) + + t.Logf("Successfully removed external gateways from router %s", router.ID) + tools.PrintResource(t, updatedRouter) } diff --git a/internal/acceptance/openstack/networking/v2/extensions/mtu/mtu_test.go b/internal/acceptance/openstack/networking/v2/extensions/mtu/mtu_test.go index a71c96cc43..63e4f7790b 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/mtu/mtu_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/mtu/mtu_test.go @@ -16,7 +16,7 @@ import ( th "github.com/gophercloud/gophercloud/v2/testhelper" ) -func TestMTUNetworkCRUDL(t *testing.T) { +func TestMTUNetworkCRUD(t *testing.T) { clients.RequireAdmin(t) client, err := clients.NewNetworkV2Client() diff --git a/internal/acceptance/openstack/networking/v2/extensions/portsbinding/portsbinding_test.go b/internal/acceptance/openstack/networking/v2/extensions/portsbinding/portsbinding_test.go index 025217b710..f1b09494a1 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/portsbinding/portsbinding_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/portsbinding/portsbinding_test.go @@ -70,7 +70,7 @@ func TestPortsbindingCRUD(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, newPort) - th.AssertEquals(t, newPortName, newPort.Description) + th.AssertEquals(t, newPortName, newPort.Name) th.AssertEquals(t, newPortDescription, newPort.Description) th.AssertEquals(t, newHostID, newPort.HostID) th.AssertEquals(t, "normal", newPort.VNICType) diff --git a/internal/acceptance/openstack/networking/v2/extensions/qos/policies/policies_test.go b/internal/acceptance/openstack/networking/v2/extensions/qos/policies/policies_test.go index fc9563df38..c25934ea07 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/qos/policies/policies_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/qos/policies/policies_test.go @@ -4,6 +4,7 @@ package policies import ( "context" + "strings" "testing" "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" @@ -57,5 +58,70 @@ func TestPoliciesCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) +} + +func TestPoliciesRevision(t *testing.T) { + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + // Skip these tests if we don't have the required extension + v2.RequireNeutronExtension(t, client, "qos") + + // Create a policy + policy, err := CreateQoSPolicy(t, client) + th.AssertNoErr(t, err) + defer DeleteQoSPolicy(t, client, policy.ID) + + tools.PrintResource(t, policy) + + // Store the current revision number. + oldRevisionNumber := policy.RevisionNumber + + // Update the policy without revision number. + // This should work. + newName := tools.RandomString("TESTACC-", 8) + newDescription := "" + updateOpts := &policies.UpdateOpts{ + Name: newName, + Description: &newDescription, + } + policy, err = policies.Update(context.TODO(), client, policy.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, policy) + + // This should fail due to an old revision number. + newDescription = "new description" + updateOpts = &policies.UpdateOpts{ + Name: newName, + Description: &newDescription, + RevisionNumber: &oldRevisionNumber, + } + _, err = policies.Update(context.TODO(), client, policy.ID, updateOpts).Extract() + th.AssertErr(t, err) + if !strings.Contains(err.Error(), "RevisionNumberConstraintFailed") { + t.Fatalf("expected to see an error of type RevisionNumberConstraintFailed, but got the following error instead: %v", err) + } + + // Reread the policy to show that it did not change. + policy, err = policies.Get(context.TODO(), client, policy.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, policy) + + // This should work because now we do provide a valid revision number. + newDescription = "new description" + updateOpts = &policies.UpdateOpts{ + Name: newName, + Description: &newDescription, + RevisionNumber: &policy.RevisionNumber, + } + policy, err = policies.Update(context.TODO(), client, policy.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, policy) + + th.AssertEquals(t, policy.Name, newName) + th.AssertEquals(t, policy.Description, newDescription) } diff --git a/internal/acceptance/openstack/networking/v2/extensions/qos/rules/rules_test.go b/internal/acceptance/openstack/networking/v2/extensions/qos/rules/rules_test.go index 811e798862..eef5ca5136 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/qos/rules/rules_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/qos/rules/rules_test.go @@ -43,7 +43,7 @@ func TestBandwidthLimitRulesCRUD(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, newRule) - th.AssertEquals(t, newRule.MaxBurstKBps, 0) + th.AssertEquals(t, 0, newRule.MaxBurstKBps) allPages, err := rules.ListBandwidthLimitRules(client, policy.ID, rules.BandwidthLimitRulesListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) @@ -58,7 +58,7 @@ func TestBandwidthLimitRulesCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestDSCPMarkingRulesCRUD(t *testing.T) { @@ -89,7 +89,7 @@ func TestDSCPMarkingRulesCRUD(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, newRule) - th.AssertEquals(t, newRule.DSCPMark, 20) + th.AssertEquals(t, 20, newRule.DSCPMark) allPages, err := rules.ListDSCPMarkingRules(client, policy.ID, rules.DSCPMarkingRulesListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) @@ -104,7 +104,7 @@ func TestDSCPMarkingRulesCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestMinimumBandwidthRulesCRUD(t *testing.T) { @@ -135,7 +135,7 @@ func TestMinimumBandwidthRulesCRUD(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, newRule) - th.AssertEquals(t, newRule.MinKBps, 500) + th.AssertEquals(t, 500, newRule.MinKBps) allPages, err := rules.ListMinimumBandwidthRules(client, policy.ID, rules.MinimumBandwidthRulesListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) @@ -150,5 +150,5 @@ func TestMinimumBandwidthRulesCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } diff --git a/internal/acceptance/openstack/networking/v2/extensions/quotas/quotas_test.go b/internal/acceptance/openstack/networking/v2/extensions/quotas/quotas_test.go index 3eaf7d232b..06b2280791 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/quotas/quotas_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/quotas/quotas_test.go @@ -63,3 +63,13 @@ func TestQuotasUpdate(t *testing.T) { tools.PrintResource(t, restoredQuotas) } + +func TestQuotasDelete(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + err = quotas.Delete(context.TODO(), client, os.Getenv("OS_PROJECT_NAME")).ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/internal/acceptance/openstack/networking/v2/extensions/rbacpolicies/rbacpolicies.go b/internal/acceptance/openstack/networking/v2/extensions/rbacpolicies/rbacpolicies.go index 864e8fecb7..8f354c951f 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/rbacpolicies/rbacpolicies.go +++ b/internal/acceptance/openstack/networking/v2/extensions/rbacpolicies/rbacpolicies.go @@ -29,6 +29,9 @@ func CreateRBACPolicy(t *testing.T, client *gophercloud.ServiceClient, tenantID, t.Logf("Successfully created rbac_policy") th.AssertEquals(t, rbacPolicy.ObjectID, networkID) + th.AssertEquals(t, string(rbacpolicies.ActionAccessShared), string(rbacPolicy.Action)) + th.AssertEquals(t, "network", rbacPolicy.ObjectType) + th.AssertEquals(t, tenantID, rbacPolicy.TargetTenant) return rbacPolicy, nil } diff --git a/internal/acceptance/openstack/networking/v2/extensions/rbacpolicies/rbacpolicies_test.go b/internal/acceptance/openstack/networking/v2/extensions/rbacpolicies/rbacpolicies_test.go index 3bd33becd6..1c9efce343 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/rbacpolicies/rbacpolicies_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/rbacpolicies/rbacpolicies_test.go @@ -65,6 +65,7 @@ func TestRBACPolicyCRUD(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, newrbacPolicy) + th.AssertEquals(t, project2.ID, newrbacPolicy.TargetTenant) } func TestRBACPolicyList(t *testing.T) { diff --git a/internal/acceptance/openstack/networking/v2/extensions/security_test.go b/internal/acceptance/openstack/networking/v2/extensions/security_test.go index 670735ec5e..30e370c376 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/security_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/security_test.go @@ -4,11 +4,13 @@ package extensions import ( "context" + "strings" "testing" "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" networking "github.com/gophercloud/gophercloud/v2/internal/acceptance/openstack/networking/v2" "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/security/addressgroups" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/security/groups" th "github.com/gophercloud/gophercloud/v2/testhelper" ) @@ -20,18 +22,24 @@ func TestSecurityGroupsCreateUpdateDelete(t *testing.T) { group, err := CreateSecurityGroup(t, client) th.AssertNoErr(t, err) defer DeleteSecurityGroup(t, client, group.ID) - th.AssertEquals(t, group.Stateful, true) + th.AssertTrue(t, group.Stateful) rule, err := CreateSecurityGroupRule(t, client, group.ID) th.AssertNoErr(t, err) defer DeleteSecurityGroupRule(t, client, rule.ID) + rules, err := CreateSecurityGroupRulesBulk(t, client, group.ID) + th.AssertNoErr(t, err) + for _, r := range rules { + defer DeleteSecurityGroupRule(t, client, r.ID) + } + tools.PrintResource(t, group) var name = "Update group" var description = "" updateOpts := groups.UpdateOpts{ - Name: name, + Name: &name, Description: &description, Stateful: new(bool), } @@ -42,7 +50,7 @@ func TestSecurityGroupsCreateUpdateDelete(t *testing.T) { tools.PrintResource(t, newGroup) th.AssertEquals(t, newGroup.Name, name) th.AssertEquals(t, newGroup.Description, description) - th.AssertEquals(t, newGroup.Stateful, false) + th.AssertFalse(t, newGroup.Stateful) listOpts := groups.ListOpts{} allPages, err := groups.List(client, listOpts).AllPages(context.TODO()) @@ -58,7 +66,7 @@ func TestSecurityGroupsCreateUpdateDelete(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestSecurityGroupsPort(t *testing.T) { @@ -87,3 +95,140 @@ func TestSecurityGroupsPort(t *testing.T) { tools.PrintResource(t, port) } + +func TestSecurityGroupsRevision(t *testing.T) { + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + // Create a group + group, err := CreateSecurityGroup(t, client) + th.AssertNoErr(t, err) + defer DeleteSecurityGroup(t, client, group.ID) + + tools.PrintResource(t, group) + + // Store the current revision number. + oldRevisionNumber := group.RevisionNumber + + // Update the group without revision number. + // This should work. + newName := tools.RandomString("TESTACC-", 8) + newDescription := "" + updateOpts := &groups.UpdateOpts{ + Name: &newName, + Description: &newDescription, + } + group, err = groups.Update(context.TODO(), client, group.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, group) + + // This should fail due to an old revision number. + newDescription = "new description" + updateOpts = &groups.UpdateOpts{ + Description: &newDescription, + RevisionNumber: &oldRevisionNumber, + } + _, err = groups.Update(context.TODO(), client, group.ID, updateOpts).Extract() + th.AssertErr(t, err) + if !strings.Contains(err.Error(), "RevisionNumberConstraintFailed") { + t.Fatalf("expected to see an error of type RevisionNumberConstraintFailed, but got the following error instead: %v", err) + } + + // Reread the group to show that it did not change. + group, err = groups.Get(context.TODO(), client, group.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, group) + + // This should work because now we do provide a valid revision number. + newDescription = "new description" + updateOpts = &groups.UpdateOpts{ + Name: new(string), + Description: &newDescription, + RevisionNumber: &group.RevisionNumber, + } + group, err = groups.Update(context.TODO(), client, group.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, group) + + th.AssertEquals(t, "", group.Name) + th.AssertEquals(t, group.Description, newDescription) +} + +func TestSecurityAddressGroups(t *testing.T) { + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + group, err := CreateSecurityAddressGroup(t, client) + th.AssertNoErr(t, err) + defer DeleteSecurityAddressGroup(t, client, group.ID) + + tools.PrintResource(t, group) + + name := "Update group" + description := "" + updateOpts := addressgroups.UpdateOpts{ + Name: &name, + Description: &description, + } + newGroup, err := addressgroups.Update(context.TODO(), client, group.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, newGroup) + + th.AssertEquals(t, newGroup.Name, name) + th.AssertEquals(t, newGroup.Description, description) + + listOpts := addressgroups.ListOpts{} + allPages, err := addressgroups.List(client, listOpts).AllPages(context.TODO()) + th.AssertNoErr(t, err) + allGroups, err := addressgroups.ExtractGroups(allPages) + th.AssertNoErr(t, err) + + var found = -1 + for i, v := range allGroups { + if v.ID == group.ID { + found = i + break + } + } + if found == -1 { + t.Fatalf("Expected to find group %s in the list of groups", group.ID) + } + + th.AssertEquals(t, allGroups[found].Name, newGroup.Name) + th.AssertEquals(t, allGroups[found].Description, newGroup.Description) + th.AssertDeepEquals(t, allGroups[found].Addresses, newGroup.Addresses) + + // Test that we can add a new address to the group. + newAddresses := []string{ + "192.168.170.0/24", + } + addAddressOpts := addressgroups.UpdateAddressesOpts{ + Addresses: newAddresses, + } + updatedGroup, err := addressgroups.AddAddresses(context.TODO(), client, group.ID, addAddressOpts).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, updatedGroup) + + // Check that the new address was added. + expectedAddresses := append(group.Addresses, newAddresses...) + th.AssertDeepEquals(t, updatedGroup.Addresses, expectedAddresses) + + // Test that we can remove an address from the group. + removeAddressOpts := addressgroups.UpdateAddressesOpts{ + Addresses: newAddresses, + } + updatedGroup, err = addressgroups.RemoveAddresses(context.TODO(), client, group.ID, removeAddressOpts).Extract() + th.AssertNoErr(t, err) + tools.PrintResource(t, updatedGroup) + + // Check that the address was removed. + expectedAddresses = group.Addresses + th.AssertDeepEquals(t, updatedGroup.Addresses, expectedAddresses) + + // Verify that the group exists. + _, err = addressgroups.Get(context.TODO(), client, group.ID).Extract() + th.AssertNoErr(t, err) +} diff --git a/internal/acceptance/openstack/networking/v2/extensions/segments/segments.go b/internal/acceptance/openstack/networking/v2/extensions/segments/segments.go new file mode 100644 index 0000000000..68826ede10 --- /dev/null +++ b/internal/acceptance/openstack/networking/v2/extensions/segments/segments.go @@ -0,0 +1,48 @@ +package segments + +import ( + "context" + "testing" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/segments" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func CreateSegment(t *testing.T, client *gophercloud.ServiceClient, networkID string) (*segments.Segment, error) { + name := tools.RandomString("TESTACC-SEGMENT-", 8) + desc := "test segment description" + + opts := segments.CreateOpts{ + NetworkID: networkID, + NetworkType: "geneve", + Name: name, + Description: desc, + } + + segment, err := segments.Create(context.TODO(), client, opts).Extract() + if err != nil { + return nil, err + } + + tools.PrintResource(t, segment) + + th.AssertEquals(t, segment.Name, name) + th.AssertEquals(t, segment.Description, desc) + th.AssertEquals(t, "geneve", segment.NetworkType) + th.AssertEquals(t, segment.NetworkID, networkID) + + return segment, nil +} + +func DeleteSegment(t *testing.T, client *gophercloud.ServiceClient, segmentID string) { + t.Logf("Attempting to delete segment %s", segmentID) + + err := segments.Delete(context.TODO(), client, segmentID).ExtractErr() + if err != nil { + t.Fatalf("Failed to delete segment %s: %v", segmentID, err) + } + + t.Logf("Deleted segment %s", segmentID) +} diff --git a/internal/acceptance/openstack/networking/v2/extensions/segments/segments_test.go b/internal/acceptance/openstack/networking/v2/extensions/segments/segments_test.go new file mode 100644 index 0000000000..8caff205bb --- /dev/null +++ b/internal/acceptance/openstack/networking/v2/extensions/segments/segments_test.go @@ -0,0 +1,58 @@ +//go:build acceptance || networking || segments + +package segments + +import ( + "context" + "testing" + + "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" + networking "github.com/gophercloud/gophercloud/v2/internal/acceptance/openstack/networking/v2" + "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/segments" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func TestSegmentCRUD(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + // Skip these tests if we don't have the required extension + networking.RequireNeutronExtension(t, client, "segment") + + network, err := networking.CreateNetwork(t, client) + th.AssertNoErr(t, err) + defer networking.DeleteNetwork(t, client, network.ID) + + segment, err := CreateSegment(t, client, network.ID) + th.AssertNoErr(t, err) + defer DeleteSegment(t, client, segment.ID) + + // Get + segGet, err := segments.Get(context.TODO(), client, segment.ID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, segment.ID, segGet.ID) + + // Update + newName := tools.RandomString("UPDATED-SEGMENT-", 8) + newDesc := "updated description" + updateOpts := segments.UpdateOpts{ + Name: &newName, + Description: &newDesc, + } + segUpdated, err := segments.Update(context.TODO(), client, segment.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, newName, segUpdated.Name) + th.AssertEquals(t, newDesc, segUpdated.Description) + + // List + allPages, err := segments.List(client, nil).AllPages(context.TODO()) + th.AssertNoErr(t, err) + allSegments, err := segments.ExtractSegments(allPages) + th.AssertNoErr(t, err) + th.AssertIntGreaterOrEqual(t, len(allSegments), 1) + t.Logf("Found %d segments", len(allSegments)) + tools.PrintResource(t, allSegments) +} diff --git a/internal/acceptance/openstack/networking/v2/extensions/subnetpools/subnetpools.go b/internal/acceptance/openstack/networking/v2/extensions/subnetpools/subnetpools.go index 3594e94ad4..25de4f7ab5 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/subnetpools/subnetpools.go +++ b/internal/acceptance/openstack/networking/v2/extensions/subnetpools/subnetpools.go @@ -36,6 +36,8 @@ func CreateSubnetPool(t *testing.T, client *gophercloud.ServiceClient) (*subnetp th.AssertEquals(t, subnetPool.Name, subnetPoolName) th.AssertEquals(t, subnetPool.Description, subnetPoolDescription) + th.AssertDeepEquals(t, subnetPoolPrefixes, subnetPool.Prefixes) + th.AssertEquals(t, 24, subnetPool.DefaultPrefixLen) return subnetPool, nil } diff --git a/internal/acceptance/openstack/networking/v2/extensions/subnetpools/subnetpools_test.go b/internal/acceptance/openstack/networking/v2/extensions/subnetpools/subnetpools_test.go index 8a0575d8fe..3070ecd868 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/subnetpools/subnetpools_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/subnetpools/subnetpools_test.go @@ -4,6 +4,7 @@ package v2 import ( "context" + "strings" "testing" "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" @@ -53,5 +54,65 @@ func TestSubnetPoolsCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) +} + +func TestSubnetPoolsRevision(t *testing.T) { + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + // Create a subnetpool + subnetPool, err := CreateSubnetPool(t, client) + th.AssertNoErr(t, err) + defer DeleteSubnetPool(t, client, subnetPool.ID) + + // Store the current revision number. + oldRevisionNumber := subnetPool.RevisionNumber + + // Update the subnet pool without revision number. + // This should work. + newName := tools.RandomString("TESTACC-", 8) + newDescription := "" + updateOpts := &subnetpools.UpdateOpts{ + Name: newName, + Description: &newDescription, + } + subnetPool, err = subnetpools.Update(context.TODO(), client, subnetPool.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, subnetPool) + + // This should fail due to an old revision number. + newDescription = "new description" + updateOpts = &subnetpools.UpdateOpts{ + Name: newName, + Description: &newDescription, + RevisionNumber: &oldRevisionNumber, + } + _, err = subnetpools.Update(context.TODO(), client, subnetPool.ID, updateOpts).Extract() + th.AssertErr(t, err) + if !strings.Contains(err.Error(), "RevisionNumberConstraintFailed") { + t.Fatalf("expected to see an error of type RevisionNumberConstraintFailed, but got the following error instead: %v", err) + } + + // Reread the subnet pool to show that it did not change. + subnetPool, err = subnetpools.Get(context.TODO(), client, subnetPool.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, subnetPool) + + // This should work because now we do provide a valid revision number. + newDescription = "new description" + updateOpts = &subnetpools.UpdateOpts{ + Name: newName, + Description: &newDescription, + RevisionNumber: &subnetPool.RevisionNumber, + } + subnetPool, err = subnetpools.Update(context.TODO(), client, subnetPool.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, subnetPool) + + th.AssertEquals(t, subnetPool.Name, newName) + th.AssertEquals(t, subnetPool.Description, newDescription) } diff --git a/internal/acceptance/openstack/networking/v2/extensions/taas/taas.go b/internal/acceptance/openstack/networking/v2/extensions/taas/taas.go new file mode 100644 index 0000000000..74bc9355be --- /dev/null +++ b/internal/acceptance/openstack/networking/v2/extensions/taas/taas.go @@ -0,0 +1,60 @@ +package taas + +import ( + "context" + "testing" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/taas/tapmirrors" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +// CreateTapMirror will create a Tap Mirror with the specified portID and remoteIP. An error +// will be returned if the Tap Mirror could not be created. +func CreateTapMirror(t *testing.T, client *gophercloud.ServiceClient, portID string, remoteIP string) (*tapmirrors.TapMirror, error) { + mirrorName := tools.RandomString("TESTACC-", 8) + mirrorDescription := tools.RandomString("TESTACC-DESC-", 8) + mirrorDirectionIN := tools.RandomInt(1, 1000000) + t.Logf("Attempting to create tap mirror: %s", mirrorName) + + createopts := tapmirrors.CreateOpts{ + Name: mirrorName, + Description: mirrorDescription, + PortID: portID, + MirrorType: tapmirrors.MirrorTypeErspanv1, + RemoteIP: remoteIP, + Directions: tapmirrors.Directions{ + In: mirrorDirectionIN, + Out: mirrorDirectionIN + 1, + }, + } + + mirror, err := tapmirrors.Create(context.TODO(), client, createopts).Extract() + if err != nil { + return nil, err + } + + th.AssertEquals(t, mirrorName, mirror.Name) + th.AssertEquals(t, mirrorDescription, mirror.Description) + th.AssertEquals(t, portID, mirror.PortID) + th.AssertEquals(t, string(tapmirrors.MirrorTypeErspanv1), mirror.MirrorType) + th.AssertEquals(t, remoteIP, mirror.RemoteIP) + + t.Logf("Created Tap Mirror: %s", mirror.ID) + return mirror, nil +} + +// DeleteTapMirror will delete a Tap Mirror with a specified ID. A fatal error will +// occur if the delete was not successful. This works best when used as a +// deferred function. +func DeleteTapMirror(t *testing.T, client *gophercloud.ServiceClient, mirrorID string) { + t.Logf("Attempting to delete Tap Mirror: %s", mirrorID) + + err := tapmirrors.Delete(context.TODO(), client, mirrorID).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete Tap Mirror %s: %v", mirrorID, err) + } + + t.Logf("Deleted Tap Mirror: %s", mirrorID) +} diff --git a/internal/acceptance/openstack/networking/v2/extensions/taas/tapmirrors_test.go b/internal/acceptance/openstack/networking/v2/extensions/taas/tapmirrors_test.go new file mode 100644 index 0000000000..b46d5ae581 --- /dev/null +++ b/internal/acceptance/openstack/networking/v2/extensions/taas/tapmirrors_test.go @@ -0,0 +1,77 @@ +//go:build acceptance || networking || taas + +package taas + +import ( + "context" + "testing" + + "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" + networking "github.com/gophercloud/gophercloud/v2/internal/acceptance/openstack/networking/v2" + "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/taas/tapmirrors" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func TestTapMirrorList(t *testing.T) { + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + // Skip these tests if we don't have the required extension + networking.RequireNeutronExtension(t, client, "taas") + + allPages, err := tapmirrors.List(client, nil).AllPages(context.TODO()) + th.AssertNoErr(t, err) + + allMirrors, err := tapmirrors.ExtractTapMirrors(allPages) + th.AssertNoErr(t, err) + + for _, mirror := range allMirrors { + tools.PrintResource(t, mirror) + } +} + +func TestTapMirrorCRUD(t *testing.T) { + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + // Skip these tests if we don't have the required extension + networking.RequireNeutronExtension(t, client, "taas") + + // Create Port + network, err := networking.CreateNetwork(t, client) + th.AssertNoErr(t, err) + defer networking.DeleteNetwork(t, client, network.ID) + + subnet, err := networking.CreateSubnet(t, client, network.ID) + th.AssertNoErr(t, err) + defer networking.DeleteSubnet(t, client, subnet.ID) + + port, err := networking.CreatePort(t, client, network.ID, subnet.ID) + th.AssertNoErr(t, err) + defer networking.DeletePort(t, client, port.ID) + + // Create and defer Delete Tap Mirror + mirror, err := CreateTapMirror(t, client, port.ID, port.FixedIPs[0].IPAddress) + th.AssertNoErr(t, err) + defer DeleteTapMirror(t, client, mirror.ID) + + tools.PrintResource(t, mirror) + + // Get Tap Mirror + newmirror, err := tapmirrors.Get(context.TODO(), client, mirror.ID).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, mirror, newmirror) + + // Update Tap Mirror + updatedName := "TESTACC-updated name" + updatedDescription := "TESTACC-updated mirror description" + updateOpts := tapmirrors.UpdateOpts{ + Name: &updatedName, + Description: &updatedDescription, + } + updatedmirror, err := tapmirrors.Update(context.TODO(), client, mirror.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, updatedName, updatedmirror.Name) + th.AssertEquals(t, updatedDescription, updatedmirror.Description) +} diff --git a/internal/acceptance/openstack/networking/v2/extensions/trunk_details/trunks_test.go b/internal/acceptance/openstack/networking/v2/extensions/trunk_details/trunks_test.go index 3a9870d35b..e38404888f 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/trunk_details/trunks_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/trunk_details/trunks_test.go @@ -79,8 +79,8 @@ func TestListPortWithSubports(t *testing.T) { th.AssertEquals(t, 1, len(allPorts)) port := allPorts[0] - th.AssertEquals(t, trunk.ID, port.TrunkDetails.TrunkID) - th.AssertEquals(t, 2, len(port.TrunkDetails.SubPorts)) + th.AssertEquals(t, trunk.ID, port.TrunkID) + th.AssertEquals(t, 2, len(port.SubPorts)) // Note that MAC address is not (currently) returned in list queries. We // exclude it from the comparison here in case it's ever added. MAC @@ -92,18 +92,18 @@ func TestListPortWithSubports(t *testing.T) { SegmentationID: 1, SegmentationType: "vlan", PortID: subport1.ID, - }, port.TrunkDetails.SubPorts[0].Subport) + }, port.SubPorts[0].Subport) th.AssertDeepEquals(t, trunks.Subport{ SegmentationID: 2, SegmentationType: "vlan", PortID: subport2.ID, - }, port.TrunkDetails.SubPorts[1].Subport) + }, port.SubPorts[1].Subport) // Test GET port with trunk details err = ports.Get(context.TODO(), client, parentPort.ID).ExtractInto(&port) th.AssertNoErr(t, err) - th.AssertEquals(t, trunk.ID, port.TrunkDetails.TrunkID) - th.AssertEquals(t, 2, len(port.TrunkDetails.SubPorts)) + th.AssertEquals(t, trunk.ID, port.TrunkID) + th.AssertEquals(t, 2, len(port.SubPorts)) th.AssertDeepEquals(t, trunk_details.Subport{ Subport: trunks.Subport{ SegmentationID: 1, @@ -111,7 +111,7 @@ func TestListPortWithSubports(t *testing.T) { PortID: subport1.ID, }, MACAddress: subport1.MACAddress, - }, port.TrunkDetails.SubPorts[0]) + }, port.SubPorts[0]) th.AssertDeepEquals(t, trunk_details.Subport{ Subport: trunks.Subport{ SegmentationID: 2, @@ -119,5 +119,5 @@ func TestListPortWithSubports(t *testing.T) { PortID: subport2.ID, }, MACAddress: subport2.MACAddress, - }, port.TrunkDetails.SubPorts[1]) + }, port.SubPorts[1]) } diff --git a/internal/acceptance/openstack/networking/v2/extensions/trunks/trunks.go b/internal/acceptance/openstack/networking/v2/extensions/trunks/trunks.go index 29f18dab62..f664b27c46 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/trunks/trunks.go +++ b/internal/acceptance/openstack/networking/v2/extensions/trunks/trunks.go @@ -7,6 +7,7 @@ import ( "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/trunks" + th "github.com/gophercloud/gophercloud/v2/testhelper" ) func CreateTrunk(t *testing.T, client *gophercloud.ServiceClient, parentPortID string, subportIDs ...string) (trunk *trunks.Trunk, err error) { @@ -32,6 +33,11 @@ func CreateTrunk(t *testing.T, client *gophercloud.ServiceClient, parentPortID s trunk, err = trunks.Create(context.TODO(), client, opts).Extract() if err == nil { t.Logf("Successfully created trunk") + + th.AssertEquals(t, trunkName, trunk.Name) + th.AssertEquals(t, "Trunk created by gophercloud", trunk.Description) + th.AssertTrue(t, trunk.AdminStateUp) + th.AssertEquals(t, parentPortID, trunk.PortID) } return } diff --git a/internal/acceptance/openstack/networking/v2/extensions/trunks/trunks_test.go b/internal/acceptance/openstack/networking/v2/extensions/trunks/trunks_test.go index 65ebd90a78..3e0af63175 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/trunks/trunks_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/trunks/trunks_test.go @@ -5,6 +5,7 @@ package trunks import ( "context" "sort" + "strings" "testing" "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" @@ -17,56 +18,40 @@ import ( func TestTrunkCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) // Skip these tests if we don't have the required extension v2.RequireNeutronExtension(t, client, "trunk") // Create Network network, err := v2.CreateNetwork(t, client) - if err != nil { - t.Fatalf("Unable to create network: %v", err) - } + th.AssertNoErr(t, err) defer v2.DeleteNetwork(t, client, network.ID) // Create Subnet subnet, err := v2.CreateSubnet(t, client, network.ID) - if err != nil { - t.Fatalf("Unable to create subnet: %v", err) - } + th.AssertNoErr(t, err) defer v2.DeleteSubnet(t, client, subnet.ID) // Create port parentPort, err := v2.CreatePort(t, client, network.ID, subnet.ID) - if err != nil { - t.Fatalf("Unable to create port: %v", err) - } + th.AssertNoErr(t, err) defer v2.DeletePort(t, client, parentPort.ID) subport1, err := v2.CreatePort(t, client, network.ID, subnet.ID) - if err != nil { - t.Fatalf("Unable to create port: %v", err) - } + th.AssertNoErr(t, err) defer v2.DeletePort(t, client, subport1.ID) subport2, err := v2.CreatePort(t, client, network.ID, subnet.ID) - if err != nil { - t.Fatalf("Unable to create port: %v", err) - } + th.AssertNoErr(t, err) defer v2.DeletePort(t, client, subport2.ID) trunk, err := CreateTrunk(t, client, parentPort.ID, subport1.ID, subport2.ID) - if err != nil { - t.Fatalf("Unable to create trunk: %v", err) - } + th.AssertNoErr(t, err) defer DeleteTrunk(t, client, trunk.ID) _, err = trunks.Get(context.TODO(), client, trunk.ID).Extract() - if err != nil { - t.Fatalf("Unable to get trunk: %v", err) - } + th.AssertNoErr(t, err) // Update Trunk name := "" @@ -76,9 +61,7 @@ func TestTrunkCRUD(t *testing.T) { Description: &description, } updatedTrunk, err := trunks.Update(context.TODO(), client, trunk.ID, updateOpts).Extract() - if err != nil { - t.Fatalf("Unable to update trunk: %v", err) - } + th.AssertNoErr(t, err) if trunk.Name == updatedTrunk.Name { t.Fatalf("Trunk name was not updated correctly") @@ -93,9 +76,7 @@ func TestTrunkCRUD(t *testing.T) { // Get subports subports, err := trunks.GetSubports(context.TODO(), client, trunk.ID).Extract() - if err != nil { - t.Fatalf("Unable to get subports from the Trunk: %v", err) - } + th.AssertNoErr(t, err) th.AssertDeepEquals(t, trunk.Subports[0], subports[0]) th.AssertDeepEquals(t, trunk.Subports[1], subports[1]) @@ -104,22 +85,16 @@ func TestTrunkCRUD(t *testing.T) { func TestTrunkList(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) // Skip these tests if we don't have the required extension v2.RequireNeutronExtension(t, client, "trunk") allPages, err := trunks.List(client, nil).AllPages(context.TODO()) - if err != nil { - t.Fatalf("Unable to list trunks: %v", err) - } + th.AssertNoErr(t, err) allTrunks, err := trunks.ExtractTrunks(allPages) - if err != nil { - t.Fatalf("Unable to extract trunks: %v", err) - } + th.AssertNoErr(t, err) for _, trunk := range allTrunks { tools.PrintResource(t, trunk) @@ -128,50 +103,36 @@ func TestTrunkList(t *testing.T) { func TestTrunkSubportOperation(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) // Skip these tests if we don't have the required extension v2.RequireNeutronExtension(t, client, "trunk") // Create Network network, err := v2.CreateNetwork(t, client) - if err != nil { - t.Fatalf("Unable to create network: %v", err) - } + th.AssertNoErr(t, err) defer v2.DeleteNetwork(t, client, network.ID) // Create Subnet subnet, err := v2.CreateSubnet(t, client, network.ID) - if err != nil { - t.Fatalf("Unable to create subnet: %v", err) - } + th.AssertNoErr(t, err) defer v2.DeleteSubnet(t, client, subnet.ID) // Create port parentPort, err := v2.CreatePort(t, client, network.ID, subnet.ID) - if err != nil { - t.Fatalf("Unable to create port: %v", err) - } + th.AssertNoErr(t, err) defer v2.DeletePort(t, client, parentPort.ID) subport1, err := v2.CreatePort(t, client, network.ID, subnet.ID) - if err != nil { - t.Fatalf("Unable to create port: %v", err) - } + th.AssertNoErr(t, err) defer v2.DeletePort(t, client, subport1.ID) subport2, err := v2.CreatePort(t, client, network.ID, subnet.ID) - if err != nil { - t.Fatalf("Unable to create port: %v", err) - } + th.AssertNoErr(t, err) defer v2.DeletePort(t, client, subport2.ID) trunk, err := CreateTrunk(t, client, parentPort.ID) - if err != nil { - t.Fatalf("Unable to create trunk: %v", err) - } + th.AssertNoErr(t, err) defer DeleteTrunk(t, client, trunk.ID) // Add subports to the trunk @@ -190,9 +151,7 @@ func TestTrunkSubportOperation(t *testing.T) { }, } updatedTrunk, err := trunks.AddSubports(context.TODO(), client, trunk.ID, addSubportsOpts).Extract() - if err != nil { - t.Fatalf("Unable to add subports to the Trunk: %v", err) - } + th.AssertNoErr(t, err) th.AssertEquals(t, 2, len(updatedTrunk.Subports)) th.AssertDeepEquals(t, addSubportsOpts.Subports[0], updatedTrunk.Subports[0]) th.AssertDeepEquals(t, addSubportsOpts.Subports[1], updatedTrunk.Subports[1]) @@ -205,58 +164,42 @@ func TestTrunkSubportOperation(t *testing.T) { }, } updatedAgainTrunk, err := trunks.RemoveSubports(context.TODO(), client, trunk.ID, subRemoveOpts).Extract() - if err != nil { - t.Fatalf("Unable to remove subports from the Trunk: %v", err) - } + th.AssertNoErr(t, err) th.AssertDeepEquals(t, trunk.Subports, updatedAgainTrunk.Subports) } func TestTrunkTags(t *testing.T) { client, err := clients.NewNetworkV2Client() - if err != nil { - t.Fatalf("Unable to create a network client: %v", err) - } + th.AssertNoErr(t, err) // Skip these tests if we don't have the required extension v2.RequireNeutronExtension(t, client, "trunk") // Create Network network, err := v2.CreateNetwork(t, client) - if err != nil { - t.Fatalf("Unable to create network: %v", err) - } + th.AssertNoErr(t, err) defer v2.DeleteNetwork(t, client, network.ID) // Create Subnet subnet, err := v2.CreateSubnet(t, client, network.ID) - if err != nil { - t.Fatalf("Unable to create subnet: %v", err) - } + th.AssertNoErr(t, err) defer v2.DeleteSubnet(t, client, subnet.ID) // Create port parentPort, err := v2.CreatePort(t, client, network.ID, subnet.ID) - if err != nil { - t.Fatalf("Unable to create port: %v", err) - } + th.AssertNoErr(t, err) defer v2.DeletePort(t, client, parentPort.ID) subport1, err := v2.CreatePort(t, client, network.ID, subnet.ID) - if err != nil { - t.Fatalf("Unable to create port: %v", err) - } + th.AssertNoErr(t, err) defer v2.DeletePort(t, client, subport1.ID) subport2, err := v2.CreatePort(t, client, network.ID, subnet.ID) - if err != nil { - t.Fatalf("Unable to create port: %v", err) - } + th.AssertNoErr(t, err) defer v2.DeletePort(t, client, subport2.ID) trunk, err := CreateTrunk(t, client, parentPort.ID, subport1.ID, subport2.ID) - if err != nil { - t.Fatalf("Unable to create trunk: %v", err) - } + th.AssertNoErr(t, err) defer DeleteTrunk(t, client, trunk.ID) tagReplaceAllOpts := attributestags.ReplaceAllOpts{ @@ -264,14 +207,10 @@ func TestTrunkTags(t *testing.T) { Tags: []string{"a", "b", "c"}, } _, err = attributestags.ReplaceAll(context.TODO(), client, "trunks", trunk.ID, tagReplaceAllOpts).Extract() - if err != nil { - t.Fatalf("Unable to set trunk tags: %v", err) - } + th.AssertNoErr(t, err) gtrunk, err := trunks.Get(context.TODO(), client, trunk.ID).Extract() - if err != nil { - t.Fatalf("Unable to get trunk: %v", err) - } + th.AssertNoErr(t, err) tags := gtrunk.Tags sort.Strings(tags) // Ensure ordering, older OpenStack versions aren't sorted... th.AssertDeepEquals(t, []string{"a", "b", "c"}, tags) @@ -297,3 +236,90 @@ func TestTrunkTags(t *testing.T) { th.AssertNoErr(t, err) th.AssertEquals(t, 0, len(tags)) } + +func TestTrunkRevision(t *testing.T) { + client, err := clients.NewNetworkV2Client() + th.AssertNoErr(t, err) + + // Skip these tests if we don't have the required extension + v2.RequireNeutronExtension(t, client, "trunk") + + // Create Network + network, err := v2.CreateNetwork(t, client) + th.AssertNoErr(t, err) + defer v2.DeleteNetwork(t, client, network.ID) + + // Create Subnet + subnet, err := v2.CreateSubnet(t, client, network.ID) + th.AssertNoErr(t, err) + defer v2.DeleteSubnet(t, client, subnet.ID) + + // Create port + parentPort, err := v2.CreatePort(t, client, network.ID, subnet.ID) + th.AssertNoErr(t, err) + defer v2.DeletePort(t, client, parentPort.ID) + + subport1, err := v2.CreatePort(t, client, network.ID, subnet.ID) + th.AssertNoErr(t, err) + defer v2.DeletePort(t, client, subport1.ID) + + subport2, err := v2.CreatePort(t, client, network.ID, subnet.ID) + th.AssertNoErr(t, err) + defer v2.DeletePort(t, client, subport2.ID) + + trunk, err := CreateTrunk(t, client, parentPort.ID, subport1.ID, subport2.ID) + th.AssertNoErr(t, err) + defer DeleteTrunk(t, client, trunk.ID) + + tools.PrintResource(t, trunk) + + // Store the current revision number. + oldRevisionNumber := trunk.RevisionNumber + + // Update the trunk without revision number. + // This should work. + newName := tools.RandomString("TESTACC-", 8) + newDescription := "" + updateOpts := &trunks.UpdateOpts{ + Name: &newName, + Description: &newDescription, + } + trunk, err = trunks.Update(context.TODO(), client, trunk.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, trunk) + + // This should fail due to an old revision number. + newDescription = "new description" + updateOpts = &trunks.UpdateOpts{ + Name: &newName, + Description: &newDescription, + RevisionNumber: &oldRevisionNumber, + } + _, err = trunks.Update(context.TODO(), client, trunk.ID, updateOpts).Extract() + th.AssertErr(t, err) + if !strings.Contains(err.Error(), "RevisionNumberConstraintFailed") { + t.Fatalf("expected to see an error of type RevisionNumberConstraintFailed, but got the following error instead: %v", err) + } + + // Reread the trunk to show that it did not change. + trunk, err = trunks.Get(context.TODO(), client, trunk.ID).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, trunk) + + // This should work because now we do provide a valid revision number. + newDescription = "new description" + updateOpts = &trunks.UpdateOpts{ + Name: &newName, + Description: &newDescription, + RevisionNumber: &trunk.RevisionNumber, + } + trunk, err = trunks.Update(context.TODO(), client, trunk.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + + tools.PrintResource(t, trunk) + + th.AssertEquals(t, trunk.Name, newName) + th.AssertEquals(t, trunk.Description, newDescription) +} diff --git a/internal/acceptance/openstack/networking/v2/extensions/vlantransparent/vlantransparent.go b/internal/acceptance/openstack/networking/v2/extensions/vlantransparent/vlantransparent.go index 87aa2d4e8d..ec3dde26bd 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/vlantransparent/vlantransparent.go +++ b/internal/acceptance/openstack/networking/v2/extensions/vlantransparent/vlantransparent.go @@ -75,33 +75,3 @@ func CreateVLANTransparentNetwork(t *testing.T, client *gophercloud.ServiceClien return &network, nil } - -// UpdateVLANTransparentNetwork will update a network with the -// "vlan-transparent" extension. An error will be returned if the network could -// not be updated. -func UpdateVLANTransparentNetwork(t *testing.T, client *gophercloud.ServiceClient, networkID string) (*VLANTransparentNetwork, error) { - networkName := tools.RandomString("TESTACC-NEW-", 6) - networkUpdateOpts := networks.UpdateOpts{ - Name: &networkName, - } - - iFalse := false - updateOpts := vlantransparent.UpdateOptsExt{ - UpdateOptsBuilder: &networkUpdateOpts, - VLANTransparent: &iFalse, - } - - t.Logf("Attempting to update a VLAN-transparent network: %s", networkID) - - var network VLANTransparentNetwork - err := networks.Update(context.TODO(), client, networkID, updateOpts).ExtractInto(&network) - if err != nil { - return nil, err - } - - t.Logf("Successfully updated the network.") - - th.AssertEquals(t, networkName, network.Name) - - return &network, nil -} diff --git a/internal/acceptance/openstack/networking/v2/extensions/vlantransparent/vlantransparent_test.go b/internal/acceptance/openstack/networking/v2/extensions/vlantransparent/vlantransparent_test.go index b36d01c071..29ff0a061b 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/vlantransparent/vlantransparent_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/vlantransparent/vlantransparent_test.go @@ -25,11 +25,7 @@ func TestVLANTransparentCRUD(t *testing.T) { tools.PrintResource(t, network) - // Update the created VLAN transparent network. - newNetwork, err := UpdateVLANTransparentNetwork(t, client, network.ID) - th.AssertNoErr(t, err) - - tools.PrintResource(t, newNetwork) + // The vlan_transparent field is read-only so no update test // Check that the created VLAN transparent network exists. vlanTransparentNetworks, err := ListVLANTransparentNetworks(t, client) @@ -42,5 +38,5 @@ func TestVLANTransparentCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } diff --git a/internal/acceptance/openstack/networking/v2/extensions/vpnaas/group_test.go b/internal/acceptance/openstack/networking/v2/extensions/vpnaas/group_test.go index 8fe5e0638c..863a8ee229 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/vpnaas/group_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/vpnaas/group_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" + networking "github.com/gophercloud/gophercloud/v2/internal/acceptance/openstack/networking/v2" "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/vpnaas/endpointgroups" th "github.com/gophercloud/gophercloud/v2/testhelper" @@ -16,6 +17,9 @@ func TestGroupList(t *testing.T) { client, err := clients.NewNetworkV2Client() th.AssertNoErr(t, err) + // Skip these tests if we don't have the required extension + networking.RequireNeutronExtension(t, client, "vpnaas") + allPages, err := endpointgroups.List(client, nil).AllPages(context.TODO()) th.AssertNoErr(t, err) @@ -31,6 +35,9 @@ func TestGroupCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() th.AssertNoErr(t, err) + // Skip these tests if we don't have the required extension + networking.RequireNeutronExtension(t, client, "vpnaas") + group, err := CreateEndpointGroup(t, client) th.AssertNoErr(t, err) defer DeleteEndpointGroup(t, client, group.ID) @@ -49,4 +56,7 @@ func TestGroupCRUD(t *testing.T) { updatedGroup, err := endpointgroups.Update(context.TODO(), client, group.ID, updateOpts).Extract() th.AssertNoErr(t, err) tools.PrintResource(t, updatedGroup) + + th.AssertEquals(t, updatedName, updatedGroup.Name) + th.AssertEquals(t, updatedDescription, updatedGroup.Description) } diff --git a/internal/acceptance/openstack/networking/v2/extensions/vpnaas/ikepolicy_test.go b/internal/acceptance/openstack/networking/v2/extensions/vpnaas/ikepolicy_test.go index c0407c99a4..14336095d5 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/vpnaas/ikepolicy_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/vpnaas/ikepolicy_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" + networking "github.com/gophercloud/gophercloud/v2/internal/acceptance/openstack/networking/v2" "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/vpnaas/ikepolicies" th "github.com/gophercloud/gophercloud/v2/testhelper" @@ -16,6 +17,9 @@ func TestIKEPolicyList(t *testing.T) { client, err := clients.NewNetworkV2Client() th.AssertNoErr(t, err) + // Skip these tests if we don't have the required extension + networking.RequireNeutronExtension(t, client, "vpnaas") + allPages, err := ikepolicies.List(client, nil).AllPages(context.TODO()) th.AssertNoErr(t, err) @@ -31,6 +35,9 @@ func TestIKEPolicyCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() th.AssertNoErr(t, err) + // Skip these tests if we don't have the required extension + networking.RequireNeutronExtension(t, client, "vpnaas") + policy, err := CreateIKEPolicy(t, client) th.AssertNoErr(t, err) defer DeleteIKEPolicy(t, client, policy.ID) @@ -53,4 +60,8 @@ func TestIKEPolicyCRUD(t *testing.T) { updatedPolicy, err := ikepolicies.Update(context.TODO(), client, policy.ID, updateOpts).Extract() th.AssertNoErr(t, err) tools.PrintResource(t, updatedPolicy) + + th.AssertEquals(t, updatedName, updatedPolicy.Name) + th.AssertEquals(t, updatedDescription, updatedPolicy.Description) + th.AssertEquals(t, 7000, updatedPolicy.Lifetime.Value) } diff --git a/internal/acceptance/openstack/networking/v2/extensions/vpnaas/ipsecpolicy_test.go b/internal/acceptance/openstack/networking/v2/extensions/vpnaas/ipsecpolicy_test.go index f9aed9b2aa..f0384a6187 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/vpnaas/ipsecpolicy_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/vpnaas/ipsecpolicy_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" + networking "github.com/gophercloud/gophercloud/v2/internal/acceptance/openstack/networking/v2" "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/vpnaas/ipsecpolicies" th "github.com/gophercloud/gophercloud/v2/testhelper" @@ -16,6 +17,9 @@ func TestIPSecPolicyList(t *testing.T) { client, err := clients.NewNetworkV2Client() th.AssertNoErr(t, err) + // Skip these tests if we don't have the required extension + networking.RequireNeutronExtension(t, client, "vpnaas") + allPages, err := ipsecpolicies.List(client, nil).AllPages(context.TODO()) th.AssertNoErr(t, err) @@ -31,6 +35,9 @@ func TestIPSecPolicyCRUD(t *testing.T) { client, err := clients.NewNetworkV2Client() th.AssertNoErr(t, err) + // Skip these tests if we don't have the required extension + networking.RequireNeutronExtension(t, client, "vpnaas") + policy, err := CreateIPSecPolicy(t, client) th.AssertNoErr(t, err) defer DeleteIPSecPolicy(t, client, policy.ID) @@ -44,8 +51,10 @@ func TestIPSecPolicyCRUD(t *testing.T) { policy, err = ipsecpolicies.Update(context.TODO(), client, policy.ID, updateOpts).Extract() th.AssertNoErr(t, err) tools.PrintResource(t, policy) + th.AssertEquals(t, updatedDescription, policy.Description) newPolicy, err := ipsecpolicies.Get(context.TODO(), client, policy.ID).Extract() th.AssertNoErr(t, err) tools.PrintResource(t, newPolicy) + th.AssertEquals(t, updatedDescription, newPolicy.Description) } diff --git a/internal/acceptance/openstack/networking/v2/extensions/vpnaas/service_test.go b/internal/acceptance/openstack/networking/v2/extensions/vpnaas/service_test.go index 2ac1c7734c..b932dbdeb3 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/vpnaas/service_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/vpnaas/service_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" + networking "github.com/gophercloud/gophercloud/v2/internal/acceptance/openstack/networking/v2" layer3 "github.com/gophercloud/gophercloud/v2/internal/acceptance/openstack/networking/v2/extensions/layer3" "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/vpnaas/services" @@ -17,6 +18,9 @@ func TestServiceList(t *testing.T) { client, err := clients.NewNetworkV2Client() th.AssertNoErr(t, err) + // Skip these tests if we don't have the required extension + networking.RequireNeutronExtension(t, client, "vpnaas") + allPages, err := services.List(client, nil).AllPages(context.TODO()) th.AssertNoErr(t, err) @@ -29,10 +33,15 @@ func TestServiceList(t *testing.T) { } func TestServiceCRUD(t *testing.T) { + // TODO(stephenfin): Why are we skipping this? Can we unskip? If not, we should remove. clients.SkipReleasesAbove(t, "stable/wallaby") + client, err := clients.NewNetworkV2Client() th.AssertNoErr(t, err) + // Skip these tests if we don't have the required extension + networking.RequireNeutronExtension(t, client, "vpnaas") + router, err := layer3.CreateExternalRouter(t, client) th.AssertNoErr(t, err) defer layer3.DeleteRouter(t, client, router.ID) diff --git a/internal/acceptance/openstack/networking/v2/extensions/vpnaas/siteconnection_test.go b/internal/acceptance/openstack/networking/v2/extensions/vpnaas/siteconnection_test.go index 8aa8e49f57..68a7f45192 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/vpnaas/siteconnection_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/vpnaas/siteconnection_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" - networks "github.com/gophercloud/gophercloud/v2/internal/acceptance/openstack/networking/v2" + networking "github.com/gophercloud/gophercloud/v2/internal/acceptance/openstack/networking/v2" layer3 "github.com/gophercloud/gophercloud/v2/internal/acceptance/openstack/networking/v2/extensions/layer3" "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/layer3/routers" @@ -19,6 +19,9 @@ func TestConnectionList(t *testing.T) { client, err := clients.NewNetworkV2Client() th.AssertNoErr(t, err) + // Skip these tests if we don't have the required extension + networking.RequireNeutronExtension(t, client, "vpnaas") + allPages, err := siteconnections.List(client, nil).AllPages(context.TODO()) th.AssertNoErr(t, err) @@ -31,19 +34,24 @@ func TestConnectionList(t *testing.T) { } func TestConnectionCRUD(t *testing.T) { + // TODO(stephenfin): Why are we skipping this? Can we unskip? If not, we should remove. clients.SkipReleasesAbove(t, "stable/wallaby") + client, err := clients.NewNetworkV2Client() th.AssertNoErr(t, err) + // Skip these tests if we don't have the required extension + networking.RequireNeutronExtension(t, client, "vpnaas") + // Create Network - network, err := networks.CreateNetwork(t, client) + network, err := networking.CreateNetwork(t, client) th.AssertNoErr(t, err) - defer networks.DeleteNetwork(t, client, network.ID) + defer networking.DeleteNetwork(t, client, network.ID) // Create Subnet - subnet, err := networks.CreateSubnet(t, client, network.ID) + subnet, err := networking.CreateSubnet(t, client, network.ID) th.AssertNoErr(t, err) - defer networks.DeleteSubnet(t, client, subnet.ID) + defer networking.DeleteSubnet(t, client, subnet.ID) router, err := layer3.CreateExternalRouter(t, client) th.AssertNoErr(t, err) diff --git a/internal/acceptance/openstack/networking/v2/extensions/vpnaas/vpnaas.go b/internal/acceptance/openstack/networking/v2/extensions/vpnaas/vpnaas.go index 07b9e38839..2881f53752 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/vpnaas/vpnaas.go +++ b/internal/acceptance/openstack/networking/v2/extensions/vpnaas/vpnaas.go @@ -35,6 +35,8 @@ func CreateService(t *testing.T, client *gophercloud.ServiceClient, routerID str t.Logf("Successfully created service %s", serviceName) th.AssertEquals(t, service.Name, serviceName) + th.AssertTrue(t, service.AdminStateUp) + th.AssertEquals(t, routerID, service.RouterID) return service, nil } @@ -61,7 +63,9 @@ func CreateIPSecPolicy(t *testing.T, client *gophercloud.ServiceClient) (*ipsecp t.Logf("Attempting to create IPSec policy %s", policyName) createOpts := ipsecpolicies.CreateOpts{ - Name: policyName, + Name: policyName, + EncryptionAlgorithm: ipsecpolicies.EncryptionAlgorithmAES128, + AuthAlgorithm: ipsecpolicies.AuthAlgorithmAESCMAC, } policy, err := ipsecpolicies.Create(context.TODO(), client, createOpts).Extract() @@ -72,6 +76,8 @@ func CreateIPSecPolicy(t *testing.T, client *gophercloud.ServiceClient) (*ipsecp t.Logf("Successfully created IPSec policy %s", policyName) th.AssertEquals(t, policy.Name, policyName) + th.AssertEquals(t, string(ipsecpolicies.EncryptionAlgorithmAES128), string(policy.EncryptionAlgorithm)) + th.AssertEquals(t, string(ipsecpolicies.AuthAlgorithmAESCMAC), string(policy.AuthAlgorithm)) return policy, nil } @@ -85,8 +91,9 @@ func CreateIKEPolicy(t *testing.T, client *gophercloud.ServiceClient) (*ikepolic createOpts := ikepolicies.CreateOpts{ Name: policyName, - EncryptionAlgorithm: ikepolicies.EncryptionAlgorithm3DES, + EncryptionAlgorithm: ikepolicies.EncryptionAlgorithmAES128, PFS: ikepolicies.PFSGroup5, + AuthAlgorithm: ikepolicies.AuthAlgorithmSHA256, } policy, err := ikepolicies.Create(context.TODO(), client, createOpts).Extract() @@ -97,6 +104,9 @@ func CreateIKEPolicy(t *testing.T, client *gophercloud.ServiceClient) (*ikepolic t.Logf("Successfully created IKE policy %s", policyName) th.AssertEquals(t, policy.Name, policyName) + th.AssertEquals(t, string(ikepolicies.EncryptionAlgorithmAES128), string(policy.EncryptionAlgorithm)) + th.AssertEquals(t, string(ikepolicies.PFSGroup5), string(policy.PFS)) + th.AssertEquals(t, string(ikepolicies.AuthAlgorithmSHA256), string(policy.AuthAlgorithm)) return policy, nil } @@ -152,6 +162,8 @@ func CreateEndpointGroup(t *testing.T, client *gophercloud.ServiceClient) (*endp t.Logf("Successfully created group %s", groupName) th.AssertEquals(t, group.Name, groupName) + th.AssertEquals(t, string(endpointgroups.TypeCIDR), group.Type) + th.AssertDeepEquals(t, []string{"10.2.0.0/24", "10.3.0.0/24"}, group.Endpoints) return group, nil } @@ -179,6 +191,8 @@ func CreateEndpointGroupWithCIDR(t *testing.T, client *gophercloud.ServiceClient t.Logf("%v", group) th.AssertEquals(t, group.Name, groupName) + th.AssertEquals(t, string(endpointgroups.TypeCIDR), group.Type) + th.AssertDeepEquals(t, []string{cidr}, group.Endpoints) return group, nil } @@ -195,7 +209,6 @@ func DeleteEndpointGroup(t *testing.T, client *gophercloud.ServiceClient, epGrou } t.Logf("Deleted endpoint group: %s", epGroupID) - } // CreateEndpointGroupWithSubnet will create an endpoint group with a random name. @@ -220,6 +233,8 @@ func CreateEndpointGroupWithSubnet(t *testing.T, client *gophercloud.ServiceClie t.Logf("Successfully created group %s", groupName) th.AssertEquals(t, group.Name, groupName) + th.AssertEquals(t, string(endpointgroups.TypeSubnet), group.Type) + th.AssertDeepEquals(t, []string{subnetID}, group.Endpoints) return group, nil } @@ -254,6 +269,17 @@ func CreateSiteConnection(t *testing.T, client *gophercloud.ServiceClient, ikepo t.Logf("Successfully created IPSec Site Connection %s", connectionName) th.AssertEquals(t, connection.Name, connectionName) + th.AssertEquals(t, "secret", connection.PSK) + th.AssertEquals(t, string(siteconnections.InitiatorBiDirectional), connection.Initiator) + th.AssertTrue(t, connection.AdminStateUp) + th.AssertEquals(t, ipsecpolicyID, connection.IPSecPolicyID) + th.AssertEquals(t, peerEPGroupID, connection.PeerEPGroupID) + th.AssertEquals(t, ikepolicyID, connection.IKEPolicyID) + th.AssertEquals(t, serviceID, connection.VPNServiceID) + th.AssertEquals(t, localEPGroupID, connection.LocalEPGroupID) + th.AssertEquals(t, "172.24.4.233", connection.PeerAddress) + th.AssertEquals(t, "172.24.4.233", connection.PeerID) + th.AssertEquals(t, 1500, connection.MTU) return connection, nil } diff --git a/internal/acceptance/openstack/networking/v2/networking.go b/internal/acceptance/openstack/networking/v2/networking.go index 6b1d2ef2b7..c3874a980d 100644 --- a/internal/acceptance/openstack/networking/v2/networking.go +++ b/internal/acceptance/openstack/networking/v2/networking.go @@ -41,8 +41,9 @@ func CreateNetwork(t *testing.T, client *gophercloud.ServiceClient) (*networks.N t.Logf("Successfully created network.") - th.AssertEquals(t, network.Name, networkName) - th.AssertEquals(t, network.Description, networkDescription) + th.AssertEquals(t, networkName, network.Name) + th.AssertEquals(t, networkDescription, network.Description) + th.AssertTrue(t, network.AdminStateUp) return network, nil } @@ -108,8 +109,10 @@ func CreatePort(t *testing.T, client *gophercloud.ServiceClient, networkID, subn t.Logf("Successfully created port: %s", portName) - th.AssertEquals(t, port.Name, portName) - th.AssertEquals(t, port.Description, portDescription) + th.AssertEquals(t, portName, port.Name) + th.AssertEquals(t, portDescription, port.Description) + th.AssertTrue(t, port.AdminStateUp) + th.AssertEquals(t, networkID, port.NetworkID) return newPort, nil } @@ -146,7 +149,9 @@ func CreatePortWithNoSecurityGroup(t *testing.T, client *gophercloud.ServiceClie t.Logf("Successfully created port: %s", portName) - th.AssertEquals(t, port.Name, portName) + th.AssertEquals(t, portName, port.Name) + th.AssertFalse(t, port.AdminStateUp) + th.AssertEquals(t, networkID, port.NetworkID) return newPort, nil } @@ -187,7 +192,9 @@ func CreatePortWithoutPortSecurity(t *testing.T, client *gophercloud.ServiceClie t.Logf("Successfully created port: %s", portName) - th.AssertEquals(t, port.Name, portName) + th.AssertEquals(t, portName, port.Name) + th.AssertTrue(t, port.AdminStateUp) + th.AssertEquals(t, networkID, port.NetworkID) return newPort, nil } @@ -233,6 +240,10 @@ func CreatePortWithExtraDHCPOpts(t *testing.T, client *gophercloud.ServiceClient t.Logf("Successfully created port: %s", portName) + th.AssertEquals(t, portName, port.Name) + th.AssertTrue(t, port.AdminStateUp) + th.AssertEquals(t, networkID, port.NetworkID) + return port, nil } @@ -268,8 +279,10 @@ func CreatePortWithMultipleFixedIPs(t *testing.T, client *gophercloud.ServiceCli t.Logf("Successfully created port: %s", portName) - th.AssertEquals(t, port.Name, portName) - th.AssertEquals(t, port.Description, portDescription) + th.AssertEquals(t, portName, port.Name) + th.AssertEquals(t, portDescription, port.Description) + th.AssertTrue(t, port.AdminStateUp) + th.AssertEquals(t, networkID, port.NetworkID) if len(port.FixedIPs) != 2 { t.Fatalf("Failed to create a port with two fixed IPs: %s", portName) @@ -311,10 +324,13 @@ func CreateSubnetWithCIDR(t *testing.T, client *gophercloud.ServiceClient, netwo t.Logf("Successfully created subnet.") - th.AssertEquals(t, subnet.Name, subnetName) - th.AssertEquals(t, subnet.Description, subnetDescription) - th.AssertEquals(t, subnet.GatewayIP, subnetGateway) - th.AssertEquals(t, subnet.CIDR, subnetCIDR) + th.AssertEquals(t, subnetName, subnet.Name) + th.AssertEquals(t, subnetDescription, subnet.Description) + th.AssertEquals(t, subnetGateway, subnet.GatewayIP) + th.AssertEquals(t, subnetCIDR, subnet.CIDR) + th.AssertEquals(t, networkID, subnet.NetworkID) + th.AssertEquals(t, 4, subnet.IPVersion) + th.AssertFalse(t, subnet.EnableDHCP) return subnet, nil } @@ -349,11 +365,14 @@ func CreateSubnetWithServiceTypes(t *testing.T, client *gophercloud.ServiceClien t.Logf("Successfully created subnet.") - th.AssertEquals(t, subnet.Name, subnetName) - th.AssertEquals(t, subnet.Description, subnetDescription) - th.AssertEquals(t, subnet.GatewayIP, subnetGateway) - th.AssertEquals(t, subnet.CIDR, subnetCIDR) - th.AssertDeepEquals(t, subnet.ServiceTypes, serviceTypes) + th.AssertEquals(t, subnetName, subnet.Name) + th.AssertEquals(t, subnetDescription, subnet.Description) + th.AssertEquals(t, subnetGateway, subnet.GatewayIP) + th.AssertEquals(t, subnetCIDR, subnet.CIDR) + th.AssertEquals(t, networkID, subnet.NetworkID) + th.AssertEquals(t, 4, subnet.IPVersion) + th.AssertFalse(t, subnet.EnableDHCP) + th.AssertDeepEquals(t, serviceTypes, subnet.ServiceTypes) return subnet, nil } @@ -384,9 +403,12 @@ func CreateSubnetWithDefaultGateway(t *testing.T, client *gophercloud.ServiceCli t.Logf("Successfully created subnet.") - th.AssertEquals(t, subnet.Name, subnetName) - th.AssertEquals(t, subnet.GatewayIP, defaultGateway) - th.AssertEquals(t, subnet.CIDR, subnetCIDR) + th.AssertEquals(t, subnetName, subnet.Name) + th.AssertEquals(t, defaultGateway, subnet.GatewayIP) + th.AssertEquals(t, subnetCIDR, subnet.CIDR) + th.AssertEquals(t, networkID, subnet.NetworkID) + th.AssertEquals(t, 4, subnet.IPVersion) + th.AssertFalse(t, subnet.EnableDHCP) return subnet, nil } @@ -425,9 +447,12 @@ func CreateSubnetWithNoGateway(t *testing.T, client *gophercloud.ServiceClient, t.Logf("Successfully created subnet.") - th.AssertEquals(t, subnet.Name, subnetName) - th.AssertEquals(t, subnet.GatewayIP, "") - th.AssertEquals(t, subnet.CIDR, subnetCIDR) + th.AssertEquals(t, subnetName, subnet.Name) + th.AssertEquals(t, "", subnet.GatewayIP) + th.AssertEquals(t, subnetCIDR, subnet.CIDR) + th.AssertEquals(t, networkID, subnet.NetworkID) + th.AssertEquals(t, 4, subnet.IPVersion) + th.AssertFalse(t, subnet.EnableDHCP) return subnet, nil } @@ -456,8 +481,12 @@ func CreateSubnetWithSubnetPool(t *testing.T, client *gophercloud.ServiceClient, t.Logf("Successfully created subnet.") - th.AssertEquals(t, subnet.Name, subnetName) - th.AssertEquals(t, subnet.CIDR, subnetCIDR) + th.AssertEquals(t, subnetName, subnet.Name) + th.AssertEquals(t, subnetCIDR, subnet.CIDR) + th.AssertEquals(t, networkID, subnet.NetworkID) + th.AssertEquals(t, 4, subnet.IPVersion) + th.AssertFalse(t, subnet.EnableDHCP) + th.AssertEquals(t, subnetPoolID, subnet.SubnetPoolID) return subnet, nil } @@ -484,7 +513,11 @@ func CreateSubnetWithSubnetPoolNoCIDR(t *testing.T, client *gophercloud.ServiceC t.Logf("Successfully created subnet.") - th.AssertEquals(t, subnet.Name, subnetName) + th.AssertEquals(t, subnetName, subnet.Name) + th.AssertEquals(t, networkID, subnet.NetworkID) + th.AssertEquals(t, 4, subnet.IPVersion) + th.AssertFalse(t, subnet.EnableDHCP) + th.AssertEquals(t, subnetPoolID, subnet.SubnetPoolID) return subnet, nil } @@ -513,7 +546,11 @@ func CreateSubnetWithSubnetPoolPrefixlen(t *testing.T, client *gophercloud.Servi t.Logf("Successfully created subnet.") - th.AssertEquals(t, subnet.Name, subnetName) + th.AssertEquals(t, subnetName, subnet.Name) + th.AssertEquals(t, networkID, subnet.NetworkID) + th.AssertEquals(t, 4, subnet.IPVersion) + th.AssertFalse(t, subnet.EnableDHCP) + th.AssertEquals(t, subnetPoolID, subnet.SubnetPoolID) return subnet, nil } diff --git a/internal/acceptance/openstack/networking/v2/networks_test.go b/internal/acceptance/openstack/networking/v2/networks_test.go index a3e3928590..1c8b12f355 100644 --- a/internal/acceptance/openstack/networking/v2/networks_test.go +++ b/internal/acceptance/openstack/networking/v2/networks_test.go @@ -51,7 +51,7 @@ func TestNetworksExternalList(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) iFalse := false networkListOpts = networks.ListOpts{ @@ -67,7 +67,7 @@ func TestNetworksExternalList(t *testing.T) { v, err := networks.ExtractNetworks(allPages) th.AssertNoErr(t, err) - th.AssertEquals(t, len(v), 0) + th.AssertEquals(t, 0, len(v)) } func TestNetworksCRUD(t *testing.T) { @@ -118,7 +118,7 @@ func TestNetworksCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestNetworksPortSecurityCRUD(t *testing.T) { @@ -153,6 +153,7 @@ func TestNetworksPortSecurityCRUD(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, networkWithExtensions) + th.AssertTrue(t, networkWithExtensions.PortSecurityEnabled) } func TestNetworksRevision(t *testing.T) { diff --git a/internal/acceptance/openstack/networking/v2/ports_test.go b/internal/acceptance/openstack/networking/v2/ports_test.go index 569beeebd4..2667f4a20f 100644 --- a/internal/acceptance/openstack/networking/v2/ports_test.go +++ b/internal/acceptance/openstack/networking/v2/ports_test.go @@ -13,6 +13,7 @@ import ( "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/extradhcpopts" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/portsecurity" + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/portstrustedvif" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/ports" th "github.com/gophercloud/gophercloud/v2/testhelper" ) @@ -45,9 +46,11 @@ func TestPortsCRUD(t *testing.T) { // Update port newPortName := "" newPortDescription := "" + newMACAddress := "aa:bb:cc:dd:ee:ff" updateOpts := ports.UpdateOpts{ Name: &newPortName, Description: &newPortDescription, + MACAddress: &newMACAddress, } newPort, err := ports.Update(context.TODO(), client, port.ID, updateOpts).Extract() th.AssertNoErr(t, err) @@ -56,6 +59,7 @@ func TestPortsCRUD(t *testing.T) { th.AssertEquals(t, newPort.Name, newPortName) th.AssertEquals(t, newPort.Description, newPortDescription) + th.AssertEquals(t, newPort.MACAddress, newMACAddress) allPages, err := ports.List(client, nil).AllPages(context.TODO()) th.AssertNoErr(t, err) @@ -70,7 +74,7 @@ func TestPortsCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) ipAddress := port.FixedIPs[0].IPAddress t.Logf("Port has IP address: %s", ipAddress) @@ -411,6 +415,7 @@ func TestPortsPortSecurityCRUD(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, portWithExt) + th.AssertTrue(t, portWithExt.PortSecurityEnabled) } func TestPortsWithExtraDHCPOptsCRUD(t *testing.T) { @@ -462,6 +467,64 @@ func TestPortsWithExtraDHCPOptsCRUD(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, newPort) + th.AssertEquals(t, newPortName, newPort.Name) +} + +func TestPortsTrustedVIFCRUD(t *testing.T) { + client, err := clients.NewNetworkV2Client() + if err != nil { + t.Fatalf("Unable to create a network client: %v", err) + } + + // Create Network + network, err := CreateNetwork(t, client) + if err != nil { + t.Fatalf("Unable to create network: %v", err) + } + defer DeleteNetwork(t, client, network.ID) + + // Create Subnet + subnet, err := CreateSubnet(t, client, network.ID) + if err != nil { + t.Fatalf("Unable to create subnet: %v", err) + } + defer DeleteSubnet(t, client, subnet.ID) + + // Create port + port, err := CreatePort(t, client, network.ID, subnet.ID) + if err != nil { + t.Fatalf("Unable to create port: %v", err) + } + defer DeletePort(t, client, port.ID) + + var portWithExt struct { + ports.Port + portstrustedvif.PortTrustedVIFExt + } + + err = ports.Get(context.TODO(), client, port.ID).ExtractInto(&portWithExt) + if err != nil { + t.Fatalf("Unable to create port: %v", err) + } + + tools.PrintResource(t, portWithExt) + + // Update Port Trusted VIF status as true + iTrue := true + portUpdateOpts := ports.UpdateOpts{} + updateOpts := portstrustedvif.PortUpdateOptsExt{ + UpdateOptsBuilder: portUpdateOpts, + PortTrustedVIF: &iTrue, + } + + err = ports.Update(context.TODO(), client, port.ID, updateOpts).ExtractInto(&portWithExt) + if err != nil { + t.Fatalf("Unable to update port: %v", err) + } + if portWithExt.PortTrustedVIF == nil { + t.Fatal("Expected PortTrustedVIF to be non-nil after update") + } + th.AssertTrue(t, *portWithExt.PortTrustedVIF) } func TestPortsRevision(t *testing.T) { diff --git a/internal/acceptance/openstack/networking/v2/subnets_test.go b/internal/acceptance/openstack/networking/v2/subnets_test.go index de58dc73f0..7c84b910a4 100644 --- a/internal/acceptance/openstack/networking/v2/subnets_test.go +++ b/internal/acceptance/openstack/networking/v2/subnets_test.go @@ -62,7 +62,7 @@ func TestSubnetCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } func TestSubnetsServiceType(t *testing.T) { @@ -275,7 +275,7 @@ func TestSubnetDNSNameservers(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, newSubnet) - th.AssertEquals(t, len(newSubnet.DNSNameservers), 1) + th.AssertEquals(t, 1, len(newSubnet.DNSNameservers)) // Update Subnet again dnsNameservers = []string{} @@ -290,7 +290,7 @@ func TestSubnetDNSNameservers(t *testing.T) { th.AssertNoErr(t, err) tools.PrintResource(t, newSubnet) - th.AssertEquals(t, len(newSubnet.DNSNameservers), 0) + th.AssertEquals(t, 0, len(newSubnet.DNSNameservers)) } func TestSubnetsRevision(t *testing.T) { diff --git a/internal/acceptance/openstack/objectstorage/v1/objects_test.go b/internal/acceptance/openstack/objectstorage/v1/objects_test.go index 5fcf806936..fa2d80b579 100644 --- a/internal/acceptance/openstack/objectstorage/v1/objects_test.go +++ b/internal/acceptance/openstack/objectstorage/v1/objects_test.go @@ -100,7 +100,7 @@ func TestObjects(t *testing.T) { }) th.AssertNoErr(t, err) - resp, err := client.ProviderClient.HTTPClient.Get(objURLs[i]) + resp, err := client.HTTPClient.Get(objURLs[i]) th.AssertNoErr(t, err) if resp.StatusCode != http.StatusOK { resp.Body.Close() @@ -121,7 +121,7 @@ func TestObjects(t *testing.T) { }) th.AssertNoErr(t, err) - resp, err = client.ProviderClient.HTTPClient.Get(objURLs[i]) + resp, err = client.HTTPClient.Get(objURLs[i]) th.AssertNoErr(t, err) if resp.StatusCode != http.StatusOK { resp.Body.Close() @@ -428,5 +428,5 @@ func TestObjectsBulkDelete(t *testing.T) { t.Fatal(err) } - th.AssertEquals(t, len(allObjects), 0) + th.AssertEquals(t, 0, len(allObjects)) } diff --git a/internal/acceptance/openstack/objectstorage/v1/versioning_test.go b/internal/acceptance/openstack/objectstorage/v1/versioning_test.go index 7d52c8f1cc..884e784357 100644 --- a/internal/acceptance/openstack/objectstorage/v1/versioning_test.go +++ b/internal/acceptance/openstack/objectstorage/v1/versioning_test.go @@ -47,7 +47,7 @@ func TestObjectsVersioning(t *testing.T) { get, err := containers.Get(context.TODO(), client, cName, nil).Extract() th.AssertNoErr(t, err) t.Logf("Get container headers: %+v\n", get) - th.AssertEquals(t, true, get.VersionsEnabled) + th.AssertTrue(t, get.VersionsEnabled) // Create a slice of buffers to hold the test object content. oContents := make([]string, numObjects) @@ -85,7 +85,7 @@ func TestObjectsVersioning(t *testing.T) { get, err := containers.Get(context.TODO(), client, cName, nil).Extract() th.AssertNoErr(t, err) t.Logf("Get container headers: %+v\n", get) - th.AssertEquals(t, false, get.VersionsEnabled) + th.AssertFalse(t, get.VersionsEnabled) // delete all object versions before deleting the container currentVersionIDs := make([]string, numObjects) @@ -154,9 +154,9 @@ func TestObjectsVersioning(t *testing.T) { // ensure proper versioning attributes are set for i, obj := range ois { if i%2 == 0 { - th.AssertEquals(t, true, obj.IsLatest) + th.AssertTrue(t, obj.IsLatest) } else { - th.AssertEquals(t, false, obj.IsLatest) + th.AssertFalse(t, obj.IsLatest) } if obj.VersionID == "" { t.Fatalf("Unexpected empty version_id for the %s object", obj.Name) diff --git a/internal/acceptance/openstack/orchestration/v1/orchestration.go b/internal/acceptance/openstack/orchestration/v1/orchestration.go index 3d45311fef..c143dafbaf 100644 --- a/internal/acceptance/openstack/orchestration/v1/orchestration.go +++ b/internal/acceptance/openstack/orchestration/v1/orchestration.go @@ -78,7 +78,13 @@ func CreateStack(t *testing.T, client *gophercloud.ServiceClient) (*stacks.Retri } newStack, err := stacks.Get(context.TODO(), client, stackName, stack.ID).Extract() - return newStack, err + if err != nil { + return nil, err + } + + th.AssertEquals(t, stackName, newStack.Name) + + return newStack, nil } // DeleteStack deletes a stack via its ID. @@ -108,7 +114,7 @@ func WaitForStackStatus(client *gophercloud.ServiceClient, stackName, stackID, s } if latest.Status == "ERROR" { - return false, fmt.Errorf("Stack in ERROR state") + return false, fmt.Errorf("stack in ERROR state") } return false, nil diff --git a/internal/acceptance/openstack/orchestration/v1/stackevents_test.go b/internal/acceptance/openstack/orchestration/v1/stackevents_test.go index 66632321dc..61befec9ee 100644 --- a/internal/acceptance/openstack/orchestration/v1/stackevents_test.go +++ b/internal/acceptance/openstack/orchestration/v1/stackevents_test.go @@ -24,7 +24,7 @@ func TestStackEvents(t *testing.T) { allEvents, err := stackevents.ExtractEvents(allPages) th.AssertNoErr(t, err) - th.AssertEquals(t, len(allEvents), 4) + th.AssertEquals(t, 4, len(allEvents)) /* allPages is currently broke diff --git a/internal/acceptance/openstack/orchestration/v1/stackresources_test.go b/internal/acceptance/openstack/orchestration/v1/stackresources_test.go index 1e47f789b8..71b068f11d 100644 --- a/internal/acceptance/openstack/orchestration/v1/stackresources_test.go +++ b/internal/acceptance/openstack/orchestration/v1/stackresources_test.go @@ -53,5 +53,5 @@ func TestStackResources(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } diff --git a/internal/acceptance/openstack/orchestration/v1/stacks_test.go b/internal/acceptance/openstack/orchestration/v1/stacks_test.go index 56bb779f9b..e77a63dfdd 100644 --- a/internal/acceptance/openstack/orchestration/v1/stacks_test.go +++ b/internal/acceptance/openstack/orchestration/v1/stacks_test.go @@ -48,5 +48,5 @@ func TestStacksCRUD(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertTrue(t, found) } diff --git a/internal/acceptance/openstack/placement/v1/allocationcandidates_test.go b/internal/acceptance/openstack/placement/v1/allocationcandidates_test.go new file mode 100644 index 0000000000..9483ecb73a --- /dev/null +++ b/internal/acceptance/openstack/placement/v1/allocationcandidates_test.go @@ -0,0 +1,235 @@ +//go:build acceptance || placement || allocationcandidates + +package v1 + +import ( + "context" + "slices" + "testing" + + "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" + "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" + "github.com/gophercloud/gophercloud/v2/internal/ptr" + "github.com/gophercloud/gophercloud/v2/openstack/placement/v1/allocationcandidates" + "github.com/gophercloud/gophercloud/v2/openstack/placement/v1/resourceproviders" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +// createRPWithVCPUInventory creates a resource provider and seeds it with +// VCPU inventory, returning the provider UUID. The caller is responsible for +// deferring deletion of the provider. +func createRPWithVCPUInventory(t *testing.T, microversion string) (string, func()) { + t.Helper() + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + client.Microversion = microversion + + rp, err := CreateResourceProvider(t, client) + th.AssertNoErr(t, err) + + inventories, err := resourceproviders.GetInventories(context.TODO(), client, rp.UUID).Extract() + th.AssertNoErr(t, err) + + inventories, err = resourceproviders.UpdateInventories(context.TODO(), client, rp.UUID, resourceproviders.UpdateInventoriesOpts{ + ResourceProviderGeneration: inventories.ResourceProviderGeneration, + Inventories: map[string]resourceproviders.InventoryUpdateBase{ + "VCPU": { + AllocationRatio: ptr.To(float32(1.0)), + MaxUnit: ptr.To(8), + MinUnit: ptr.To(1), + Reserved: ptr.To(0), + StepSize: ptr.To(1), + Total: 8, + }, + }, + }).Extract() + th.AssertNoErr(t, err) + + _, err = resourceproviders.UpdateTraits(context.TODO(), client, rp.UUID, resourceproviders.UpdateTraitsOpts{ + ResourceProviderGeneration: inventories.ResourceProviderGeneration, + Traits: []string{"COMPUTE_NODE"}, + }).Extract() + th.AssertNoErr(t, err) + + cleanup := func() { DeleteResourceProvider(t, client, rp.UUID) } + return rp.UUID, cleanup +} + +func TestAllocationCandidatesList(t *testing.T) { + clients.SkipReleasesBelow(t, "stable/train") + clients.RequireAdmin(t) + + microversion := "1.34" + rpUUID, cleanup := createRPWithVCPUInventory(t, microversion) + defer cleanup() + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + client.Microversion = microversion + + page, err := allocationcandidates.List(client, allocationcandidates.ListOpts{ + Resources: "VCPU:1", + }).AllPages(context.TODO()) + th.AssertNoErr(t, err) + + result, err := allocationcandidates.ExtractAllocationCandidates(page) + th.AssertNoErr(t, err) + + th.AssertTrue(t, len(result.AllocationRequests) > 0) + + // Assert: The provider's summary contains the exact inventory we seeded: + // VCPU total=8, reserved=0 → capacity=8, used=0. + summary, present := result.ProviderSummaries[rpUUID] + th.AssertTrue(t, present) + vcpuSummary, present := summary.Resources["VCPU"] + th.AssertTrue(t, present) + th.AssertEquals(t, 8, vcpuSummary.Capacity) + th.AssertEquals(t, 0, vcpuSummary.Used) + th.AssertTrue(t, slices.Contains(*summary.Traits, "COMPUTE_NODE")) + // It is a root provider: root UUID equals its own UUID, parent is absent. + th.AssertEquals(t, rpUUID, *summary.RootProviderUUID) + th.AssertEquals(t, (*string)(nil), summary.ParentProviderUUID) + + // Assert: The allocation request contains the exact resource amount + // and the unsuffixed group maps to the newly created RP. + var req allocationcandidates.AllocationRequest + for _, r := range result.AllocationRequests { + if _, present := r.Allocations[rpUUID]; present { + req = r + break + } + } + th.AssertEquals(t, 1, req.Allocations[rpUUID].Resources["VCPU"]) + th.AssertDeepEquals(t, []string{rpUUID}, (*req.Mappings)[""]) + + tools.PrintResource(t, result) +} + +func TestAllocationCandidatesListPre129(t *testing.T) { + clients.SkipReleasesBelow(t, "stable/queens") + clients.RequireAdmin(t) + + microversion := "1.17" + rpUUID, cleanup := createRPWithVCPUInventory(t, microversion) + defer cleanup() + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + client.Microversion = microversion + + page, err := allocationcandidates.List(client, allocationcandidates.ListOpts{ + Resources: "VCPU:1", + }).AllPages(context.TODO()) + th.AssertNoErr(t, err) + + result, err := allocationcandidates.ExtractAllocationCandidates(page) + th.AssertNoErr(t, err) + + th.AssertTrue(t, len(result.AllocationRequests) > 0) + + summary, present := result.ProviderSummaries[rpUUID] + th.AssertTrue(t, present) + vcpuSummary, present := summary.Resources["VCPU"] + th.AssertTrue(t, present) + th.AssertEquals(t, 8, vcpuSummary.Capacity) + th.AssertEquals(t, 0, vcpuSummary.Used) + th.AssertTrue(t, slices.Contains(*summary.Traits, "COMPUTE_NODE")) + // Root/parent UUIDs are absent below 1.29. + th.AssertEquals(t, (*string)(nil), summary.RootProviderUUID) + th.AssertEquals(t, (*string)(nil), summary.ParentProviderUUID) + // Mappings are absent below 1.34. + th.AssertEquals(t, (*map[string][]string)(nil), result.AllocationRequests[0].Mappings) +} + +func TestAllocationCandidatesList110(t *testing.T) { + clients.SkipReleasesBelow(t, "stable/pike") + clients.RequireAdmin(t) + + microversion := "1.10" + rpUUID, cleanup := createRPWithVCPUInventory(t, microversion) + defer cleanup() + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + client.Microversion = microversion + + page, err := allocationcandidates.List(client, allocationcandidates.ListOpts{ + Resources: "VCPU:1", + }).AllPages(context.TODO()) + th.AssertNoErr(t, err) + + // Pre 1.12 uses an incompatible response format; use the separate function + // from microversions.go. + result, err := allocationcandidates.ExtractAllocationCandidates110(page) + th.AssertNoErr(t, err) + + th.AssertTrue(t, len(result.AllocationRequests) > 0) + th.AssertTrue(t, len(result.ProviderSummaries) > 0) + + // Assert: UUID of the created RP present and resource amount correct. + var foundAlloc allocationcandidates.AllocationRequest110Resource + for _, req := range result.AllocationRequests { + for _, alloc := range req.Allocations { + if alloc.ResourceProvider.UUID == rpUUID { + foundAlloc = alloc + } + } + } + th.AssertEquals(t, rpUUID, foundAlloc.ResourceProvider.UUID) + th.AssertEquals(t, 1, foundAlloc.Resources["VCPU"]) + + // Assert: The provider summary contains the expected inventory. + rpSummary, present := result.ProviderSummaries[rpUUID] + th.AssertTrue(t, present) + vcpuSummary, present := rpSummary.Resources["VCPU"] + th.AssertTrue(t, present) + th.AssertEquals(t, 8, vcpuSummary.Capacity) + th.AssertEquals(t, 0, vcpuSummary.Used) +} + +func TestAllocationCandidatesIsEmpty110(t *testing.T) { + clients.SkipReleasesBelow(t, "stable/pike") + clients.RequireAdmin(t) + + microversion := "1.10" + _, cleanup := createRPWithVCPUInventory(t, microversion) + defer cleanup() + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + client.Microversion = microversion + + page, err := allocationcandidates.List(client, allocationcandidates.ListOpts{ + Resources: "VCPU:1", + }).AllPages(context.TODO()) + th.AssertNoErr(t, err) + + isEmpty, err := page.IsEmpty() + th.AssertNoErr(t, err) + th.AssertFalse(t, isEmpty) +} + +func TestAllocationCandidatesListEmpty(t *testing.T) { + clients.SkipReleasesBelow(t, "stable/pike") + clients.RequireAdmin(t) + + microversion := "1.10" + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + client.Microversion = microversion + + page, err := allocationcandidates.List(client, allocationcandidates.ListOpts{ + Resources: "MEMORY_MB:999999", + }).AllPages(context.TODO()) + th.AssertNoErr(t, err) + + result, err := allocationcandidates.ExtractAllocationCandidates(page) + th.AssertNoErr(t, err) + th.AssertEquals(t, 0, len(result.AllocationRequests)) + th.AssertEquals(t, 0, len(result.ProviderSummaries)) + + isEmpty, err := page.IsEmpty() + th.AssertNoErr(t, err) + th.AssertTrue(t, isEmpty) +} diff --git a/internal/acceptance/openstack/placement/v1/allocations_test.go b/internal/acceptance/openstack/placement/v1/allocations_test.go new file mode 100644 index 0000000000..2c8d24ed05 --- /dev/null +++ b/internal/acceptance/openstack/placement/v1/allocations_test.go @@ -0,0 +1,267 @@ +//go:build acceptance || placement || allocations + +package v1 + +import ( + "context" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" + "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" + "github.com/gophercloud/gophercloud/v2/openstack/placement/v1/allocations" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func TestGetAllocationsSuccess(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + consumerUUID := tools.RandomUUID() + + // Assert: We don't have any allocations for this random UUID. + // We get an empty allocations map, not 404. + allocs, err := allocations.Get(context.TODO(), client, consumerUUID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, 0, len(allocs.Allocations)) +} + +func TestUpdateAllocationsNewConsumerSuccess(t *testing.T) { + clients.RequireAdmin(t) + clients.SkipReleasesBelow(t, "stable/rocky") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + resourceProvider, _, err := CreateResourceProviderWithVCPUInventory(t, client) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, resourceProvider.UUID) + + consumerUUID := tools.RandomUUID() + defer allocations.Delete(context.TODO(), client, consumerUUID) + + client.Microversion = "1.28" + + // Act: Update with nil ConsumerGeneration to signal a new consumer (serialized as JSON null, not omitted). + err = allocations.Update(context.TODO(), client, consumerUUID, allocations.UpdateOpts{ + Allocations: map[string]allocations.ProviderAllocationsOpts{ + resourceProvider.UUID: { + Resources: map[string]int{"VCPU": 2, "MEMORY_MB": 1024}, + }, + }, + ProjectID: "test-project", + UserID: "test-user", + ConsumerGeneration: nil, + }).ExtractErr() + th.AssertNoErr(t, err) + + allocs, err := allocations.Get(context.TODO(), client, consumerUUID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, 1, len(allocs.Allocations)) + th.AssertEquals(t, 2, allocs.Allocations[resourceProvider.UUID].Resources["VCPU"]) + th.AssertEquals(t, 1024, allocs.Allocations[resourceProvider.UUID].Resources["MEMORY_MB"]) +} + +func TestUpdateAllocationsSuccess(t *testing.T) { + clients.RequireAdmin(t) + clients.SkipReleasesBelow(t, "stable/rocky") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + resourceProvider, _, err := CreateResourceProviderWithVCPUInventory(t, client) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, resourceProvider.UUID) + + consumerUUID := tools.RandomUUID() + defer allocations.Delete(context.TODO(), client, consumerUUID) + + client.Microversion = "1.28" + + // Arrange: Create the consumer with nil ConsumerGeneration. + err = allocations.Update(context.TODO(), client, consumerUUID, allocations.UpdateOpts{ + Allocations: map[string]allocations.ProviderAllocationsOpts{ + resourceProvider.UUID: { + Resources: map[string]int{"VCPU": 1}, + }, + }, + ProjectID: "test-project", + UserID: "test-user", + ConsumerGeneration: nil, + }).ExtractErr() + th.AssertNoErr(t, err) + + existing, err := allocations.Get(context.TODO(), client, consumerUUID).Extract() + th.AssertNoErr(t, err) + + // Act: Update allocations using the consumer's current generation. + err = allocations.Update(context.TODO(), client, consumerUUID, allocations.UpdateOpts{ + Allocations: map[string]allocations.ProviderAllocationsOpts{ + resourceProvider.UUID: { + Resources: map[string]int{"VCPU": 2, "MEMORY_MB": 1024}, + }, + }, + ProjectID: *existing.ProjectID, + UserID: *existing.UserID, + ConsumerGeneration: existing.ConsumerGeneration, + }).ExtractErr() + th.AssertNoErr(t, err) + + updated, err := allocations.Get(context.TODO(), client, consumerUUID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, 2, updated.Allocations[resourceProvider.UUID].Resources["VCPU"]) + th.AssertEquals(t, 1024, updated.Allocations[resourceProvider.UUID].Resources["MEMORY_MB"]) + + tools.PrintResource(t, updated) +} + +func TestUpdateAllocationsConflict(t *testing.T) { + clients.RequireAdmin(t) + clients.SkipReleasesBelow(t, "stable/rocky") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + resourceProvider, _, err := CreateResourceProviderWithVCPUInventory(t, client) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, resourceProvider.UUID) + + consumerUUID := tools.RandomUUID() + defer allocations.Delete(context.TODO(), client, consumerUUID) + + client.Microversion = "1.28" + + // Arrange: Create the consumer to establish a valid generation. + err = allocations.Update(context.TODO(), client, consumerUUID, allocations.UpdateOpts{ + Allocations: map[string]allocations.ProviderAllocationsOpts{ + resourceProvider.UUID: { + Resources: map[string]int{"VCPU": 1}, + }, + }, + ProjectID: "test-project", + UserID: "test-user", + ConsumerGeneration: nil, + }).ExtractErr() + th.AssertNoErr(t, err) + + // Act: Update with a stale generation to trigger a 409 conflict. + staleGeneration := -1 + err = allocations.Update(context.TODO(), client, consumerUUID, allocations.UpdateOpts{ + Allocations: map[string]allocations.ProviderAllocationsOpts{ + resourceProvider.UUID: { + Resources: map[string]int{"VCPU": 2}, + }, + }, + ProjectID: "test-project", + UserID: "test-user", + ConsumerGeneration: &staleGeneration, + }).ExtractErr() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusConflict)) +} + +func TestDeleteAllocationsSuccess(t *testing.T) { + clients.RequireAdmin(t) + clients.SkipReleasesBelow(t, "stable/rocky") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + resourceProvider, _, err := CreateResourceProviderWithVCPUInventory(t, client) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, resourceProvider.UUID) + + consumerUUID := tools.RandomUUID() + + client.Microversion = "1.28" + + // Arrange: Create allocations for the consumer. + err = allocations.Update(context.TODO(), client, consumerUUID, allocations.UpdateOpts{ + Allocations: map[string]allocations.ProviderAllocationsOpts{ + resourceProvider.UUID: { + Resources: map[string]int{"VCPU": 1}, + }, + }, + ProjectID: "test-project", + UserID: "test-user", + ConsumerGeneration: nil, + }).ExtractErr() + th.AssertNoErr(t, err) + + // Act: Delete all allocations for the consumer. + err = allocations.Delete(context.TODO(), client, consumerUUID).ExtractErr() + th.AssertNoErr(t, err) + + // Assert: Consumer now returns an empty allocations map. + allocs, err := allocations.Get(context.TODO(), client, consumerUUID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, 0, len(allocs.Allocations)) +} + +func TestDeleteAllocationsNotFound(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + resourceProvider, err := CreateResourceProvider(t, client) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, resourceProvider.UUID) + + // Assert: An RP that was never a consumer returns 404 on DELETE. + err = allocations.Delete(context.TODO(), client, resourceProvider.UUID).ExtractErr() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) +} + +func TestManageAllocationsSuccess(t *testing.T) { + clients.RequireAdmin(t) + clients.SkipReleasesBelow(t, "stable/rocky") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + resourceProvider, _, err := CreateResourceProviderWithVCPUInventory(t, client) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, resourceProvider.UUID) + + consumer1UUID := tools.RandomUUID() + consumer2UUID := tools.RandomUUID() + defer allocations.Delete(context.TODO(), client, consumer1UUID) + defer allocations.Delete(context.TODO(), client, consumer2UUID) + + client.Microversion = "1.28" + + // Act: Atomically set allocations for two consumers. + err = allocations.Manage(context.TODO(), client, allocations.ManageOpts{ + consumer1UUID: { + Allocations: map[string]allocations.ProviderAllocationsOpts{ + resourceProvider.UUID: { + Resources: map[string]int{"VCPU": 1}, + }, + }, + ProjectID: "test-project", + UserID: "test-user", + ConsumerGeneration: nil, + }, + consumer2UUID: { + Allocations: map[string]allocations.ProviderAllocationsOpts{ + resourceProvider.UUID: { + Resources: map[string]int{"VCPU": 1}, + }, + }, + ProjectID: "test-project", + UserID: "test-user", + ConsumerGeneration: nil, + }, + }).ExtractErr() + th.AssertNoErr(t, err) + + for _, consumerUUID := range []string{consumer1UUID, consumer2UUID} { + allocs, err := allocations.Get(context.TODO(), client, consumerUUID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, 1, len(allocs.Allocations)) + th.AssertEquals(t, 1, allocs.Allocations[resourceProvider.UUID].Resources["VCPU"]) + } +} diff --git a/internal/acceptance/openstack/placement/v1/placement.go b/internal/acceptance/openstack/placement/v1/placement.go index 4149d5ff8b..5b4be86e0d 100644 --- a/internal/acceptance/openstack/placement/v1/placement.go +++ b/internal/acceptance/openstack/placement/v1/placement.go @@ -6,10 +6,18 @@ import ( "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" + "github.com/gophercloud/gophercloud/v2/internal/ptr" "github.com/gophercloud/gophercloud/v2/openstack/placement/v1/resourceproviders" th "github.com/gophercloud/gophercloud/v2/testhelper" ) +func restoreClientMicroversion(client *gophercloud.ServiceClient) func() { + originalMicroversion := client.Microversion + return func() { + client.Microversion = originalMicroversion + } +} + func CreateResourceProvider(t *testing.T, client *gophercloud.ServiceClient) (*resourceproviders.ResourceProvider, error) { name := tools.RandomString("TESTACC-", 8) t.Logf("Attempting to create resource provider: %s", name) @@ -18,6 +26,8 @@ func CreateResourceProvider(t *testing.T, client *gophercloud.ServiceClient) (*r Name: name, } + defer restoreClientMicroversion(client)() + client.Microversion = "1.20" resourceProvider, err := resourceproviders.Create(context.TODO(), client, createOpts).Extract() if err != nil { @@ -41,6 +51,8 @@ func CreateResourceProviderWithParent(t *testing.T, client *gophercloud.ServiceC ParentProviderUUID: parentUUID, } + defer restoreClientMicroversion(client)() + client.Microversion = "1.20" resourceProvider, err := resourceproviders.Create(context.TODO(), client, createOpts).Extract() if err != nil { @@ -69,3 +81,46 @@ func DeleteResourceProvider(t *testing.T, client *gophercloud.ServiceClient, res t.Logf("Deleted resourceProvider: %s.", resourceProviderID) } + +// CreateResourceProviderWithVCPUInventory creates a resource provider and seeds it +// with a VCPU inventory, returning the provider and the inventory generation. +// This is used by acceptance tests that need a resource provider with available +// capacity before setting allocations against it. +func CreateResourceProviderWithVCPUInventory(t *testing.T, client *gophercloud.ServiceClient) (*resourceproviders.ResourceProvider, int, error) { + resourceProvider, err := CreateResourceProvider(t, client) + if err != nil { + return nil, 0, err + } + + inventories, err := resourceproviders.GetInventories(context.TODO(), client, resourceProvider.UUID).Extract() + if err != nil { + return nil, 0, err + } + + updatedInventories, err := resourceproviders.UpdateInventories(context.TODO(), client, resourceProvider.UUID, resourceproviders.UpdateInventoriesOpts{ + ResourceProviderGeneration: inventories.ResourceProviderGeneration, + Inventories: map[string]resourceproviders.InventoryUpdateBase{ + "VCPU": { + AllocationRatio: ptr.To(float32(1.0)), + MaxUnit: ptr.To(8), + MinUnit: ptr.To(1), + Reserved: ptr.To(0), + StepSize: ptr.To(1), + Total: 8, + }, + "MEMORY_MB": { + AllocationRatio: ptr.To(float32(1.0)), + MaxUnit: ptr.To(8192), + MinUnit: ptr.To(1), + Reserved: ptr.To(0), + StepSize: ptr.To(1), + Total: 8192, + }, + }, + }).Extract() + if err != nil { + return nil, 0, err + } + + return resourceProvider, updatedInventories.ResourceProviderGeneration, nil +} diff --git a/internal/acceptance/openstack/placement/v1/resourceclasses_test.go b/internal/acceptance/openstack/placement/v1/resourceclasses_test.go new file mode 100644 index 0000000000..c4e78cad9d --- /dev/null +++ b/internal/acceptance/openstack/placement/v1/resourceclasses_test.go @@ -0,0 +1,221 @@ +//go:build acceptance || placement || resourceclasses + +package v1 + +import ( + "context" + "net/http" + "slices" + "strings" + "testing" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" + "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" + "github.com/gophercloud/gophercloud/v2/openstack/placement/v1/resourceclasses" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func TestResourceClassesList(t *testing.T) { + // Resource classes were introduced in 1.2 + clients.SkipReleasesBelow(t, "stable/ocata") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.2" + + allPages, err := resourceclasses.List(client).AllPages(context.TODO()) + th.AssertNoErr(t, err) + + allResourceClasses, err := resourceclasses.ExtractResourceClasses(allPages) + th.AssertNoErr(t, err) + + // Ensure VCPU is in the list + th.AssertTrue(t, slices.ContainsFunc(allResourceClasses, func(rc resourceclasses.ResourceClass) bool { + return rc.Name == "VCPU" + })) +} + +func TestResourceClassGetSuccess(t *testing.T) { + // Resource classes were introduced in 1.2 + clients.SkipReleasesBelow(t, "stable/ocata") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.2" + + // VCPU is a standard resource class + rc, err := resourceclasses.Get(context.TODO(), client, "VCPU").Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, "VCPU", rc.Name) +} + +func TestResourceClassGetNegative(t *testing.T) { + // Resource classes were introduced in 1.2 + clients.SkipReleasesBelow(t, "stable/ocata") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.2" + + _, err = resourceclasses.Get(context.TODO(), client, "NON_EXISTENT_RC").Extract() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) +} + +func TestResourceClassCreateByPostSuccess(t *testing.T) { + // Resource classes were introduced in 1.2 + clients.SkipReleasesBelow(t, "stable/ocata") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.2" + + name := strings.ToUpper(tools.RandomString("CUSTOM_", 8)) + createOpts := resourceclasses.CreateOpts{ + Name: name, + } + + // Act: Create a resource class using POST + err = resourceclasses.Create(context.TODO(), client, createOpts).ExtractErr() + th.AssertNoErr(t, err) + + // Assert: The resource class exists + rc, err := resourceclasses.Get(context.TODO(), client, name).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, name, rc.Name) +} + +func TestResourceClassCreateByPostDuplicate(t *testing.T) { + // Resource classes were introduced in 1.2 + clients.SkipReleasesBelow(t, "stable/ocata") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.2" + + name := strings.ToUpper(tools.RandomString("CUSTOM_", 8)) + createOpts := resourceclasses.CreateOpts{ + Name: name, + } + + // Act: Create a resource class using POST + err = resourceclasses.Create(context.TODO(), client, createOpts).ExtractErr() + th.AssertNoErr(t, err) + + // Act: Try to create the same resource class again + err = resourceclasses.Create(context.TODO(), client, createOpts).ExtractErr() + // Assert: The error is a conflict + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusConflict)) +} + +func TestResourceClassCreateByUpdateSuccess(t *testing.T) { + // Creating by Update (PUT) requires microversion 1.7 or later + clients.SkipReleasesBelow(t, "stable/pike") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.7" + + name := strings.ToUpper(tools.RandomString("CUSTOM_", 8)) + + // Act: Create a resource class using PUT (Update) + err = resourceclasses.Update(context.TODO(), client, name).ExtractErr() + // No error, with 201 returned + th.AssertNoErr(t, err) + + // Act: Try to create the same resource class again + err = resourceclasses.Update(context.TODO(), client, name).ExtractErr() + // No error, with 204 returned + th.AssertNoErr(t, err) + + // Assert: The resource class exists + rc, err := resourceclasses.Get(context.TODO(), client, name).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, name, rc.Name) +} + +func TestResourceClassCreateByUpdateNonCustomName(t *testing.T) { + // Creating by Update (PUT) requires microversion 1.7 or later + clients.SkipReleasesBelow(t, "stable/pike") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.7" + + name := "CANNOT_CREATE_THIS" + + // Act: Try to create a resource class with a non-custom name using PUT (Update) + err = resourceclasses.Update(context.TODO(), client, name).ExtractErr() + // Assert: We get 400 + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusBadRequest)) +} + +func TestResourceClassDeleteSuccess(t *testing.T) { + // Resource classes were introduced in 1.2 + clients.SkipReleasesBelow(t, "stable/ocata") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.2" + + name := strings.ToUpper(tools.RandomString("CUSTOM_", 8)) + createOpts := resourceclasses.CreateOpts{ + Name: name, + } + + // Arrange: Create a resource class to delete + err = resourceclasses.Create(context.TODO(), client, createOpts).ExtractErr() + th.AssertNoErr(t, err) + + // Arrange: Sanity check, the newly created resource class exists + rc, err := resourceclasses.Get(context.TODO(), client, name).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, name, rc.Name) + + // Act: Delete the resource class + err = resourceclasses.Delete(context.TODO(), client, name).ExtractErr() + th.AssertNoErr(t, err) + + // Assert: The resource class no longer exists + _, err = resourceclasses.Get(context.TODO(), client, name).Extract() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) +} + +func TestResourceClassDeleteNotFound(t *testing.T) { + // Resource classes were introduced in 1.2 + clients.SkipReleasesBelow(t, "stable/ocata") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.2" + + // Act: Try to delete a non-existent resource class + err = resourceclasses.Delete(context.TODO(), client, "CUSTOM_NON_EXISTENT_RC").ExtractErr() + // Assert: We get 404 + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) +} + +func TestResourceClassDeleteStandardClass(t *testing.T) { + // Resource classes were introduced in 1.2 + clients.SkipReleasesBelow(t, "stable/ocata") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.2" + + // Act: Try to delete a standard resource class + err = resourceclasses.Delete(context.TODO(), client, "VCPU").ExtractErr() + // Assert: We get 400 + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusBadRequest)) +} diff --git a/internal/acceptance/openstack/placement/v1/resourceproviders_test.go b/internal/acceptance/openstack/placement/v1/resourceproviders_test.go index bd6c3f1fa4..424e202e09 100644 --- a/internal/acceptance/openstack/placement/v1/resourceproviders_test.go +++ b/internal/acceptance/openstack/placement/v1/resourceproviders_test.go @@ -4,14 +4,23 @@ package v1 import ( "context" + "net/http" + "slices" + "strings" "testing" + "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" + "github.com/gophercloud/gophercloud/v2/internal/ptr" "github.com/gophercloud/gophercloud/v2/openstack/placement/v1/resourceproviders" + "github.com/gophercloud/gophercloud/v2/openstack/placement/v1/traits" th "github.com/gophercloud/gophercloud/v2/testhelper" ) +const InventoryResourceClass = "VCPU" +const MissingInventoryResourceClass = "NO_SUCH_CLASS" + func TestResourceProviderList(t *testing.T) { clients.RequireAdmin(t) @@ -29,6 +38,76 @@ func TestResourceProviderList(t *testing.T) { } } +func TestResourceProviderList139(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.39" + + // Arrange: Create a resource provider, traits, and aggregates. + // Assign them to the created RP. + resourceProvider, err := CreateResourceProvider(t, client) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, resourceProvider.UUID) + + trait1 := strings.ToUpper(tools.RandomString("CUSTOM_", 8)) + trait2 := strings.ToUpper(tools.RandomString("CUSTOM_", 8)) + err = traits.Create(context.TODO(), client, trait1).ExtractErr() + th.AssertNoErr(t, err) + defer traits.Delete(context.TODO(), client, trait1) + err = traits.Create(context.TODO(), client, trait2).ExtractErr() + th.AssertNoErr(t, err) + defer traits.Delete(context.TODO(), client, trait2) + + currentTraits, err := resourceproviders.GetTraits(context.TODO(), client, resourceProvider.UUID).Extract() + th.AssertNoErr(t, err) + + _, err = resourceproviders.UpdateTraits(context.TODO(), client, resourceProvider.UUID, resourceproviders.UpdateTraitsOpts{ + ResourceProviderGeneration: currentTraits.ResourceProviderGeneration, + Traits: []string{trait1, trait2}, + }).Extract() + th.AssertNoErr(t, err) + + currentAggregates, err := resourceproviders.GetAggregates(context.TODO(), client, resourceProvider.UUID).Extract() + th.AssertNoErr(t, err) + + aggregate1 := tools.RandomUUID() + aggregate2 := tools.RandomUUID() + aggregate3 := tools.RandomUUID() + _, err = resourceproviders.UpdateAggregates(context.TODO(), client, resourceProvider.UUID, resourceproviders.UpdateAggregatesOpts{ + ResourceProviderGeneration: currentAggregates.ResourceProviderGeneration, + Aggregates: []string{aggregate1, aggregate2}, + }).Extract() + th.AssertNoErr(t, err) + + listOpts := resourceproviders.ListOpts139{ + // Repeating member_of means AND: provider must be in aggregate1 and in any of (aggregate2, aggregate3). + // We'll expect list to return our provider. + MemberOf: []string{aggregate1, "in:" + aggregate2 + "," + aggregate3}, + Required: []string{trait1, trait2}, + } + + // Act: List resource providers with the above traits and aggregates as filters. + allPages, err := resourceproviders.List(client, listOpts).AllPages(context.TODO()) + th.AssertNoErr(t, err) + + // Assert: Our resource provider is in the results and has the traits and aggregates we set. + allResourceProviders, err := resourceproviders.ExtractResourceProviders(allPages) + th.AssertNoErr(t, err) + th.AssertTrue(t, len(allResourceProviders) > 0) + + found := false + for _, rp := range allResourceProviders { + if rp.UUID == resourceProvider.UUID { + found = true + break + } + } + th.AssertTrue(t, found) +} + func TestResourceProvider(t *testing.T) { clients.SkipRelease(t, "stable/mitaka") clients.SkipRelease(t, "stable/newton") @@ -100,12 +179,341 @@ func TestResourceProviderInventories(t *testing.T) { tools.PrintResource(t, usage) } +func TestResourceProviderInventory(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + resourceProvider, err := CreateResourceProvider(t, client) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, resourceProvider.UUID) + + inventories, err := resourceproviders.GetInventories(context.TODO(), client, resourceProvider.UUID).Extract() + th.AssertNoErr(t, err) + + seededInventories, err := resourceproviders.UpdateInventories(context.TODO(), client, resourceProvider.UUID, resourceproviders.UpdateInventoriesOpts{ + ResourceProviderGeneration: inventories.ResourceProviderGeneration, + Inventories: map[string]resourceproviders.InventoryUpdateBase{ + InventoryResourceClass: { + AllocationRatio: ptr.To(float32(1.0)), + MaxUnit: ptr.To(4), + MinUnit: ptr.To(1), + Reserved: ptr.To(0), + StepSize: ptr.To(1), + Total: 4, + }, + }, + }).Extract() + th.AssertNoErr(t, err) + + inventory, err := resourceproviders.GetInventory(context.TODO(), client, resourceProvider.UUID, InventoryResourceClass).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, seededInventories.ResourceProviderGeneration, inventory.ResourceProviderGeneration) + th.AssertDeepEquals(t, seededInventories.Inventories[InventoryResourceClass], inventory.Inventory) +} + +func TestResourceProviderInventoryNotFound(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + resourceProvider, err := CreateResourceProvider(t, client) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, resourceProvider.UUID) + + inventories, err := resourceproviders.GetInventories(context.TODO(), client, resourceProvider.UUID).Extract() + th.AssertNoErr(t, err) + + _, err = resourceproviders.UpdateInventories(context.TODO(), client, resourceProvider.UUID, resourceproviders.UpdateInventoriesOpts{ + ResourceProviderGeneration: inventories.ResourceProviderGeneration, + Inventories: map[string]resourceproviders.InventoryUpdateBase{ + InventoryResourceClass: { + AllocationRatio: ptr.To(float32(1.0)), + MaxUnit: ptr.To(4), + MinUnit: ptr.To(1), + Reserved: ptr.To(0), + StepSize: ptr.To(1), + Total: 4, + }, + }, + }).Extract() + th.AssertNoErr(t, err) + + _, err = resourceproviders.GetInventory(context.TODO(), client, resourceProvider.UUID, MissingInventoryResourceClass).Extract() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) +} + +func TestResourceProviderUpdateInventory(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + resourceProvider, err := CreateResourceProvider(t, client) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, resourceProvider.UUID) + + // Arrange: Get the current inventory to retrieve the generation + inventories, err := resourceproviders.GetInventories(context.TODO(), client, resourceProvider.UUID).Extract() + th.AssertNoErr(t, err) + + // Arrange: The resource class on this provider must exist first + seedOpts := resourceproviders.UpdateInventoriesOpts{ + ResourceProviderGeneration: inventories.ResourceProviderGeneration, + Inventories: map[string]resourceproviders.InventoryUpdateBase{ + InventoryResourceClass: { + AllocationRatio: ptr.To(float32(1.0)), + MaxUnit: ptr.To(4), + MinUnit: ptr.To(1), + // Skipping Reserved on purpose + StepSize: ptr.To(1), + Total: 4, + }, + }, + } + + seededInventories, err := resourceproviders.UpdateInventories(context.TODO(), client, resourceProvider.UUID, seedOpts).Extract() + th.AssertNoErr(t, err) + + expectedInventory := resourceproviders.Inventory{ + AllocationRatio: 1.0, + MaxUnit: 8, + MinUnit: 1, + Reserved: 0, + StepSize: 1, + Total: 8, + } + + updateOpts := resourceproviders.UpdateInventoryOpts{ + ResourceProviderGeneration: seededInventories.ResourceProviderGeneration, + InventoryUpdateBase: resourceproviders.InventoryUpdateBase{ + AllocationRatio: ptr.To(expectedInventory.AllocationRatio), + MaxUnit: ptr.To(expectedInventory.MaxUnit), + MinUnit: ptr.To(expectedInventory.MinUnit), + StepSize: ptr.To(expectedInventory.StepSize), + Total: expectedInventory.Total, + }, + } + + _, err = resourceproviders.UpdateInventory(context.TODO(), client, resourceProvider.UUID, InventoryResourceClass, updateOpts).Extract() + th.AssertNoErr(t, err) + + updatedInventories, err := resourceproviders.GetInventories(context.TODO(), client, resourceProvider.UUID).Extract() + th.AssertNoErr(t, err) + + actualInventory, ok := updatedInventories.Inventories[InventoryResourceClass] + th.AssertTrue(t, ok) + th.AssertDeepEquals(t, expectedInventory, actualInventory) +} + +func TestResourceProviderUpdateInventoryNotFound(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + updateOpts := resourceproviders.UpdateInventoryOpts{ + ResourceProviderGeneration: 0, + InventoryUpdateBase: resourceproviders.InventoryUpdateBase{ + AllocationRatio: ptr.To(float32(1.0)), + MaxUnit: ptr.To(1), + MinUnit: ptr.To(1), + Reserved: ptr.To(0), + StepSize: ptr.To(1), + Total: 1, + }, + } + + _, err = resourceproviders.UpdateInventory(context.TODO(), client, tools.RandomUUID(), InventoryResourceClass, updateOpts).Extract() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) +} + +func TestResourceProviderDeleteInventorySuccess(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + resourceProvider, err := CreateResourceProvider(t, client) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, resourceProvider.UUID) + + // Arrange: Get the current inventory to retrieve the generation + inventories, err := resourceproviders.GetInventories(context.TODO(), client, resourceProvider.UUID).Extract() + th.AssertNoErr(t, err) + + _, err = resourceproviders.UpdateInventories(context.TODO(), client, resourceProvider.UUID, resourceproviders.UpdateInventoriesOpts{ + ResourceProviderGeneration: inventories.ResourceProviderGeneration, + Inventories: map[string]resourceproviders.InventoryUpdateBase{ + InventoryResourceClass: { + AllocationRatio: ptr.To(float32(1.0)), + MaxUnit: ptr.To(4), + MinUnit: ptr.To(1), + Reserved: ptr.To(0), + StepSize: ptr.To(1), + Total: 4, + }, + }, + }).Extract() + th.AssertNoErr(t, err) + + err = resourceproviders.DeleteInventory(context.TODO(), client, resourceProvider.UUID, InventoryResourceClass).ExtractErr() + th.AssertNoErr(t, err) + + // Assert: The inventory should no longer be found + updatedInventories, err := resourceproviders.GetInventories(context.TODO(), client, resourceProvider.UUID).Extract() + th.AssertNoErr(t, err) + + _, found := updatedInventories.Inventories[InventoryResourceClass] + th.AssertFalse(t, found) +} + +func TestResourceProviderDeleteInventoryNotFound(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + resourceProvider, err := CreateResourceProvider(t, client) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, resourceProvider.UUID) + + err = resourceproviders.DeleteInventory(context.TODO(), client, resourceProvider.UUID, MissingInventoryResourceClass).ExtractErr() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) +} + +func TestResourceProviderDeleteInventoriesSuccess(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.20" + + resourceProvider, err := CreateResourceProvider(t, client) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, resourceProvider.UUID) + + inventories, err := resourceproviders.GetInventories(context.TODO(), client, resourceProvider.UUID).Extract() + th.AssertNoErr(t, err) + + _, err = resourceproviders.UpdateInventories(context.TODO(), client, resourceProvider.UUID, resourceproviders.UpdateInventoriesOpts{ + ResourceProviderGeneration: inventories.ResourceProviderGeneration, + Inventories: map[string]resourceproviders.InventoryUpdateBase{ + InventoryResourceClass: { + AllocationRatio: ptr.To(float32(1.0)), + MaxUnit: ptr.To(4), + MinUnit: ptr.To(1), + Reserved: ptr.To(0), + StepSize: ptr.To(1), + Total: 4, + }, + "MEMORY_MB": { + AllocationRatio: ptr.To(float32(1.0)), + MaxUnit: ptr.To(1024), + MinUnit: ptr.To(1), + Reserved: ptr.To(0), + StepSize: ptr.To(1), + Total: 1024, + }, + }, + }).Extract() + th.AssertNoErr(t, err) + + seededInventories, err := resourceproviders.GetInventories(context.TODO(), client, resourceProvider.UUID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, 2, len(seededInventories.Inventories)) + + err = resourceproviders.DeleteInventories(context.TODO(), client, resourceProvider.UUID).ExtractErr() + th.AssertNoErr(t, err) + + updatedInventories, err := resourceproviders.GetInventories(context.TODO(), client, resourceProvider.UUID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, 0, len(updatedInventories.Inventories)) +} + +func TestResourceProviderUpdateInventories(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + resourceProvider, err := CreateResourceProvider(t, client) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, resourceProvider.UUID) + + // Arrange: Get the current inventory to retrieve the generation + inventories, err := resourceproviders.GetInventories(context.TODO(), client, resourceProvider.UUID).Extract() + th.AssertNoErr(t, err) + + expectedInventories := map[string]resourceproviders.Inventory{ + "DISK_GB": { + AllocationRatio: 1.0, + MaxUnit: 100, + MinUnit: 1, + Reserved: 0, + StepSize: 1, + Total: 100, + }, + } + + updateOpts := resourceproviders.UpdateInventoriesOpts{ + ResourceProviderGeneration: inventories.ResourceProviderGeneration, + Inventories: map[string]resourceproviders.InventoryUpdateBase{ + "DISK_GB": { + AllocationRatio: ptr.To(float32(1.0)), + MaxUnit: ptr.To(100), + MinUnit: ptr.To(1), + Reserved: ptr.To(0), + StepSize: ptr.To(1), + Total: 100, + }, + }, + } + + _, err = resourceproviders.UpdateInventories(context.TODO(), client, resourceProvider.UUID, updateOpts).Extract() + th.AssertNoErr(t, err) + + updatedInventories, err := resourceproviders.GetInventories(context.TODO(), client, resourceProvider.UUID).Extract() + th.AssertNoErr(t, err) + + th.AssertDeepEquals(t, expectedInventories, updatedInventories.Inventories) +} + +func TestResourceProviderUpdateInventoriesNotFound(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + updateOpts := resourceproviders.UpdateInventoriesOpts{ + ResourceProviderGeneration: 0, + Inventories: map[string]resourceproviders.InventoryUpdateBase{ + InventoryResourceClass: { + AllocationRatio: ptr.To(float32(1.0)), + MaxUnit: ptr.To(1), + MinUnit: ptr.To(1), + Reserved: ptr.To(0), + StepSize: ptr.To(1), + Total: 1, + }, + }, + } + + _, err = resourceproviders.UpdateInventories(context.TODO(), client, tools.RandomUUID(), updateOpts).Extract() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) +} + func TestResourceProviderTraits(t *testing.T) { clients.RequireAdmin(t) client, err := clients.NewPlacementV1Client() th.AssertNoErr(t, err) + client.Microversion = "1.20" + // first create new resource provider resourceProvider, err := CreateResourceProvider(t, client) th.AssertNoErr(t, err) @@ -135,3 +543,217 @@ func TestResourceProviderAllocations(t *testing.T) { tools.PrintResource(t, usage) } + +func TestResourceProviderAggregates(t *testing.T) { + clients.SkipReleasesBelow(t, "stable/ocata") + clients.RequireAdmin(t) + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + // first create new resource provider + resourceProvider, err := CreateResourceProvider(t, client) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, resourceProvider.UUID) + + // now get the aggregates for same + client.Microversion = "1.19" + aggregates, err := resourceproviders.GetAggregates(context.TODO(), client, resourceProvider.UUID).Extract() + th.AssertNoErr(t, err) + th.AssertTrue(t, aggregates.ResourceProviderGeneration != nil) + + // ensure that we handle older microversions where generation is missing + client.Microversion = "1.1" + aggregates, err = resourceproviders.GetAggregates(context.TODO(), client, resourceProvider.UUID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, 0, len(aggregates.Aggregates)) + th.AssertDeepEquals(t, (*int)(nil), aggregates.ResourceProviderGeneration) +} + +func TestResourceProviderAggregatesNotFound(t *testing.T) { + clients.SkipReleasesBelow(t, "stable/ocata") + clients.RequireAdmin(t) + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.19" + _, err = resourceproviders.GetAggregates(context.TODO(), client, tools.RandomUUID()).Extract() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) + + client.Microversion = "1.1" + _, err = resourceproviders.GetAggregates(context.TODO(), client, tools.RandomUUID()).Extract() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) +} + +func TestResourceProviderUpdateAggregates(t *testing.T) { + clients.SkipReleasesBelow(t, "stable/ocata") + clients.RequireAdmin(t) + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + resourceProvider, err := CreateResourceProvider(t, client) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, resourceProvider.UUID) + + client.Microversion = "1.19" + + before, err := resourceproviders.GetAggregates(context.TODO(), client, resourceProvider.UUID).Extract() + th.AssertNoErr(t, err) + th.AssertTrue(t, before.ResourceProviderGeneration != nil) + + updateOpts := resourceproviders.UpdateAggregatesOpts{ + ResourceProviderGeneration: before.ResourceProviderGeneration, + Aggregates: []string{ + tools.RandomUUID(), + tools.RandomUUID(), + }, + } + + _, err = resourceproviders.UpdateAggregates(context.TODO(), client, resourceProvider.UUID, updateOpts).Extract() + th.AssertNoErr(t, err) + + after, err := resourceproviders.GetAggregates(context.TODO(), client, resourceProvider.UUID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, len(updateOpts.Aggregates), len(after.Aggregates)) + + for _, aggregate := range updateOpts.Aggregates { + th.AssertTrue(t, slices.Contains(after.Aggregates, aggregate)) + } +} + +func TestResourceProviderUpdateAggregateMismatch(t *testing.T) { + clients.SkipReleasesBelow(t, "stable/ocata") + clients.RequireAdmin(t) + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + resourceProvider, err := CreateResourceProvider(t, client) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, resourceProvider.UUID) + + client.Microversion = "1.19" + + current, err := resourceproviders.GetAggregates(context.TODO(), client, resourceProvider.UUID).Extract() + th.AssertNoErr(t, err) + th.AssertTrue(t, current.ResourceProviderGeneration != nil) + wrongGeneration := *current.ResourceProviderGeneration + 100 + + updateOpts := resourceproviders.UpdateAggregatesOpts{ + ResourceProviderGeneration: &wrongGeneration, + Aggregates: []string{ + tools.RandomUUID(), + }, + } + + _, err = resourceproviders.UpdateAggregates(context.TODO(), client, resourceProvider.UUID, updateOpts).Extract() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusConflict)) +} + +func TestResourceProviderUpdateAggregatesPreGeneration(t *testing.T) { + clients.SkipReleasesBelow(t, "stable/ocata") + clients.RequireAdmin(t) + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + resourceProvider, err := CreateResourceProvider(t, client) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, resourceProvider.UUID) + + client.Microversion = "1.1" + + updateOpts := resourceproviders.UpdateAggregatesOpts{ + Aggregates: []string{ + tools.RandomUUID(), + tools.RandomUUID(), + }, + } + + _, err = resourceproviders.UpdateAggregates(context.TODO(), client, resourceProvider.UUID, updateOpts).Extract() + th.AssertNoErr(t, err) + + after, err := resourceproviders.GetAggregates(context.TODO(), client, resourceProvider.UUID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, len(updateOpts.Aggregates), len(after.Aggregates)) + + for _, aggregate := range updateOpts.Aggregates { + th.AssertTrue(t, slices.Contains(after.Aggregates, aggregate)) + } +} + +func TestResourceProviderUpdateAggregatesPreGenerationWithGenerationSuccess(t *testing.T) { + // Before microversion 1.19, ResourceProviderGeneration in opts is silently stripped from + // the request body, so the operation must succeed even when the caller supplies it. + clients.SkipReleasesBelow(t, "stable/ocata") + clients.RequireAdmin(t) + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + resourceProvider, err := CreateResourceProvider(t, client) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, resourceProvider.UUID) + + client.Microversion = "1.1" + + gen := 1 + updateOpts := resourceproviders.UpdateAggregatesOpts{ + ResourceProviderGeneration: &gen, + Aggregates: []string{ + tools.RandomUUID(), + tools.RandomUUID(), + }, + } + + _, err = resourceproviders.UpdateAggregates(context.TODO(), client, resourceProvider.UUID, updateOpts).Extract() + th.AssertNoErr(t, err) + + after, err := resourceproviders.GetAggregates(context.TODO(), client, resourceProvider.UUID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, len(updateOpts.Aggregates), len(after.Aggregates)) + + for _, aggregate := range updateOpts.Aggregates { + th.AssertTrue(t, slices.Contains(after.Aggregates, aggregate)) + } +} + +func TestResourceProviderParentDetach(t *testing.T) { + clients.RequireAdmin(t) + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + // Arrange: Create a parent resource provider + parentProvider, err := CreateResourceProvider(t, client) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, parentProvider.UUID) + + // Arrange: Create a child resource provider with that parent + childProvider, err := CreateResourceProviderWithParent(t, client, parentProvider.UUID) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, childProvider.UUID) + + // Sanity check: Verify that the child provider has the correct parent before the update + th.AssertEquals(t, parentProvider.UUID, childProvider.ParentProviderUUID) + + // Act: Update the child resource provider to remove the parent (transform to root) + client.Microversion = "1.37" + empty := "" + updateOpts := resourceproviders.UpdateOpts{ + Name: &childProvider.Name, + ParentProviderUUID: &empty, + } + updatedChild, err := resourceproviders.Update(context.TODO(), client, childProvider.UUID, updateOpts).Extract() + th.AssertNoErr(t, err) + + // Assert: Verify that the ParentProviderUUID is now null (empty string in Gophercloud result struct) + th.AssertEquals(t, "", updatedChild.ParentProviderUUID) + + // Assert: Double check with a Get request + childGet, err := resourceproviders.Get(context.TODO(), client, childProvider.UUID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, "", childGet.ParentProviderUUID) +} diff --git a/internal/acceptance/openstack/placement/v1/traits_test.go b/internal/acceptance/openstack/placement/v1/traits_test.go new file mode 100644 index 0000000000..5b56fafaa1 --- /dev/null +++ b/internal/acceptance/openstack/placement/v1/traits_test.go @@ -0,0 +1,202 @@ +//go:build acceptance || placement || traits + +package v1 + +import ( + "context" + "net/http" + "slices" + "strings" + "testing" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" + "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" + "github.com/gophercloud/gophercloud/v2/openstack/placement/v1/traits" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func TestTraitsList(t *testing.T) { + // The Traits API requires microversion 1.6 or later + clients.SkipReleasesBelow(t, "stable/pike") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.6" + + allPages, err := traits.List(client, traits.ListOpts{}).AllPages(context.TODO()) + th.AssertNoErr(t, err) + + allTraits, err := traits.ExtractTraits(allPages) + th.AssertNoErr(t, err) + + // Ensure COMPUTE_NODE is in the list + // os-traits never removes traits, so this should always pass + th.AssertTrue(t, slices.Contains(allTraits, "COMPUTE_NODE")) +} + +func TestTraitGet(t *testing.T) { + // The Traits API requires microversion 1.6 or later + clients.SkipReleasesBelow(t, "stable/pike") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.6" + + // Verify that Get confirms the existence of the COMPUTE_NODE trait + // os-traits never removes traits, so this should always pass + err = traits.Get(context.TODO(), client, "COMPUTE_NODE").ExtractErr() + th.AssertNoErr(t, err) +} + +func TestTraitGetNegative(t *testing.T) { + // The Traits API requires microversion 1.6 or later + clients.SkipReleasesBelow(t, "stable/pike") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.6" + + // Verify that Get returns an error for a non-existent trait + err = traits.Get(context.TODO(), client, "CUSTOM_NON_EXISTENT_TRAIT").ExtractErr() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) +} + +func TestTraitsListFiltering(t *testing.T) { + // The Traits API requires microversion 1.6 or later + clients.SkipReleasesBelow(t, "stable/pike") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.6" + + // os-traits never removes traits, so this should always pass + listOpts := traits.ListOpts{ + Name: "startswith:HW_", + } + + allPages, err := traits.List(client, listOpts).AllPages(context.TODO()) + th.AssertNoErr(t, err) + + filteredTraits, err := traits.ExtractTraits(allPages) + th.AssertNoErr(t, err) + + for _, trait := range filteredTraits { + th.AssertTrue(t, strings.HasPrefix(trait, "HW_")) + } +} + +func TestTraitsCreateSuccess(t *testing.T) { + // The Traits API requires microversion 1.6 or later + clients.SkipReleasesBelow(t, "stable/pike") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.6" + + traitName := strings.ToUpper(tools.RandomString("CUSTOM_", 8)) + + err = traits.Create(context.TODO(), client, traitName).ExtractErr() + th.AssertNoErr(t, err) + + // Assert that the trait now exists + err = traits.Get(context.TODO(), client, traitName).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestTraitsCreateDuplicate(t *testing.T) { + // The Traits API requires microversion 1.6 or later + clients.SkipReleasesBelow(t, "stable/pike") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.6" + + traitName := strings.ToUpper(tools.RandomString("CUSTOM_", 8)) + + // Create the trait for the first time + err = traits.Create(context.TODO(), client, traitName).ExtractErr() + th.AssertNoErr(t, err) + + // Creating the same trait again results in 204 (no error) + err = traits.Create(context.TODO(), client, traitName).ExtractErr() + th.AssertNoErr(t, err) +} + +// Test of creating a trait name that cannot be created in an API +func TestTraitsCreateInvalidName(t *testing.T) { + // The Traits API requires microversion 1.6 or later + clients.SkipReleasesBelow(t, "stable/pike") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.6" + + traitName := "HW_WE_CANNOT_CREATE_THIS_TRAIT" + + err = traits.Create(context.TODO(), client, traitName).ExtractErr() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusBadRequest)) +} + +func TestTraitsDeleteSuccess(t *testing.T) { + // The Traits API requires microversion 1.6 or later + clients.SkipReleasesBelow(t, "stable/pike") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.6" + + traitName := strings.ToUpper(tools.RandomString("CUSTOM_", 8)) + + // Prepare: Create the trait + err = traits.Create(context.TODO(), client, traitName).ExtractErr() + th.AssertNoErr(t, err) + + // Act: Delete the trait + err = traits.Delete(context.TODO(), client, traitName).ExtractErr() + th.AssertNoErr(t, err) + + // Assert: The trait no longer exists + err = traits.Get(context.TODO(), client, traitName).ExtractErr() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) +} + +func TestTraitsDeleteNotFound(t *testing.T) { + // The Traits API requires microversion 1.6 or later + clients.SkipReleasesBelow(t, "stable/pike") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.6" + + traitName := strings.ToUpper(tools.RandomString("CUSTOM_", 8)) + + err = traits.Delete(context.TODO(), client, traitName).ExtractErr() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) +} + +// API does allow manipulation solely of custom traits, +// so trying to delete a standard trait should fail. +func TestTraitsDeleteStandardTraitFailure(t *testing.T) { + // The Traits API requires microversion 1.6 or later + clients.SkipReleasesBelow(t, "stable/pike") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.6" + + traitName := "COMPUTE_NODE" + + err = traits.Delete(context.TODO(), client, traitName).ExtractErr() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusBadRequest)) +} diff --git a/internal/acceptance/openstack/placement/v1/usages_test.go b/internal/acceptance/openstack/placement/v1/usages_test.go new file mode 100644 index 0000000000..20fa216a71 --- /dev/null +++ b/internal/acceptance/openstack/placement/v1/usages_test.go @@ -0,0 +1,195 @@ +//go:build acceptance || placement || usages + +package v1 + +import ( + "context" + "testing" + + "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" + "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" + "github.com/gophercloud/gophercloud/v2/openstack/placement/v1/allocations" + "github.com/gophercloud/gophercloud/v2/openstack/placement/v1/usages" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func TestGetUsagesSuccess(t *testing.T) { + clients.RequireAdmin(t) + clients.SkipReleasesBelow(t, "stable/rocky") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + resourceProvider, _, err := CreateResourceProviderWithVCPUInventory(t, client) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, resourceProvider.UUID) + + consumerUUID := tools.RandomUUID() + defer allocations.Delete(context.TODO(), client, consumerUUID) + + projectID := "test-project" + userID := "test-user" + + client.Microversion = "1.38" + + // Arrange: Create allocations for a consumer under the test project. + err = allocations.Update(context.TODO(), client, consumerUUID, allocations.UpdateOpts{ + Allocations: map[string]allocations.ProviderAllocationsOpts{ + resourceProvider.UUID: { + Resources: map[string]int{"VCPU": 2, "MEMORY_MB": 1024}, + }, + }, + ProjectID: projectID, + UserID: userID, + ConsumerGeneration: nil, + ConsumerType: "INSTANCE", + }).ExtractErr() + th.AssertNoErr(t, err) + + // Act: Retrieve total usages for the project (1.38+, grouped by consumer type). + totalUsages, err := usages.Get(context.TODO(), client, usages.GetOpts{ + ProjectID: projectID, + }).Extract() + th.AssertNoErr(t, err) + + // Assert: Exactly one consumer type "INSTANCE" with our exact resource usage. + th.AssertEquals(t, 1, len(totalUsages.Usages)) + ctUsage := totalUsages.Usages["INSTANCE"] + th.AssertEquals(t, 2, ctUsage["VCPU"]) + th.AssertEquals(t, 1024, ctUsage["MEMORY_MB"]) + th.AssertEquals(t, 1, ctUsage["consumer_count"]) + + tools.PrintResource(t, totalUsages) +} + +func TestGetUsagesWithUserSuccess(t *testing.T) { + clients.RequireAdmin(t) + clients.SkipReleasesBelow(t, "stable/rocky") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + resourceProvider, _, err := CreateResourceProviderWithVCPUInventory(t, client) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, resourceProvider.UUID) + + consumerUUID := tools.RandomUUID() + defer allocations.Delete(context.TODO(), client, consumerUUID) + + projectID := "test-project" + userID := "test-user" + + client.Microversion = "1.38" + + // Arrange: Create allocations. + err = allocations.Update(context.TODO(), client, consumerUUID, allocations.UpdateOpts{ + Allocations: map[string]allocations.ProviderAllocationsOpts{ + resourceProvider.UUID: { + Resources: map[string]int{"VCPU": 1}, + }, + }, + ProjectID: projectID, + UserID: userID, + ConsumerGeneration: nil, + ConsumerType: "INSTANCE", + }).ExtractErr() + th.AssertNoErr(t, err) + + // Act: Retrieve usages filtered by project and user (1.38+). + totalUsages, err := usages.Get(context.TODO(), client, usages.GetOpts{ + ProjectID: projectID, + UserID: userID, + }).Extract() + th.AssertNoErr(t, err) + + // Assert: Exactly one consumer type "INSTANCE" with our exact VCPU usage. + th.AssertEquals(t, 1, len(totalUsages.Usages)) + ctUsage := totalUsages.Usages["INSTANCE"] + th.AssertEquals(t, 1, ctUsage["VCPU"]) + th.AssertEquals(t, 1, ctUsage["consumer_count"]) + + tools.PrintResource(t, totalUsages) +} + +func TestGetUsagesEmptySuccess(t *testing.T) { + clients.RequireAdmin(t) + clients.SkipReleasesBelow(t, "stable/rocky") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.38" + + // Act: Query usages for a project with no allocations. + totalUsages, err := usages.Get(context.TODO(), client, usages.GetOpts{ + ProjectID: "nonexistent-project-with-no-allocations", + }).Extract() + th.AssertNoErr(t, err) + + // Assert: Empty usages map. + th.AssertEquals(t, 0, len(totalUsages.Usages)) +} + +func TestGetUsagesPre138Success(t *testing.T) { + clients.RequireAdmin(t) + clients.SkipReleasesBelow(t, "stable/rocky") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + resourceProvider, _, err := CreateResourceProviderWithVCPUInventory(t, client) + th.AssertNoErr(t, err) + defer DeleteResourceProvider(t, client, resourceProvider.UUID) + + consumerUUID := tools.RandomUUID() + defer allocations.Delete(context.TODO(), client, consumerUUID) + + projectID := "test-project" + userID := "test-user" + + client.Microversion = "1.28" + + // Arrange: Create allocations for a consumer. + err = allocations.Update(context.TODO(), client, consumerUUID, allocations.UpdateOpts{ + Allocations: map[string]allocations.ProviderAllocationsOpts{ + resourceProvider.UUID: { + Resources: map[string]int{"VCPU": 2, "MEMORY_MB": 1024}, + }, + }, + ProjectID: projectID, + UserID: userID, + ConsumerGeneration: nil, + }).ExtractErr() + th.AssertNoErr(t, err) + + // Act: Retrieve total usages for the project (pre-1.38, flat map). + totalUsages, err := usages.Get(context.TODO(), client, usages.GetOpts{ + ProjectID: projectID, + }).ExtractPre138() + th.AssertNoErr(t, err) + + // Assert: Usages reflect the allocations we just created. + th.AssertEquals(t, 2, totalUsages.Usages["VCPU"]) + th.AssertEquals(t, 1024, totalUsages.Usages["MEMORY_MB"]) + + tools.PrintResource(t, totalUsages) +} + +func TestGetUsagesPre138EmptySuccess(t *testing.T) { + clients.RequireAdmin(t) + clients.SkipReleasesBelow(t, "stable/rocky") + + client, err := clients.NewPlacementV1Client() + th.AssertNoErr(t, err) + + client.Microversion = "1.9" + + // Act: Query usages for a project with no allocations (pre-1.38). + totalUsages, err := usages.Get(context.TODO(), client, usages.GetOpts{ + ProjectID: "nonexistent-project-with-no-allocations", + }).ExtractPre138() + th.AssertNoErr(t, err) + + // Assert: Empty usages map. + th.AssertEquals(t, 0, len(totalUsages.Usages)) +} diff --git a/internal/acceptance/openstack/sharedfilesystems/v2/conditions.go b/internal/acceptance/openstack/sharedfilesystems/v2/conditions.go new file mode 100644 index 0000000000..de48b6939a --- /dev/null +++ b/internal/acceptance/openstack/sharedfilesystems/v2/conditions.go @@ -0,0 +1,14 @@ +package v2 + +import ( + "os" + "testing" +) + +// RequireManilaReplicas will restrict a test to only be run with enabled +// manila replicas. +func RequireManilaReplicas(t *testing.T) { + if os.Getenv("OS_MANILA_REPLICAS") != "true" { + t.Skip("manila replicas must be enabled to run this test") + } +} diff --git a/internal/acceptance/openstack/sharedfilesystems/v2/replicas.go b/internal/acceptance/openstack/sharedfilesystems/v2/replicas.go index 6a2d31d08a..8a3b88815f 100644 --- a/internal/acceptance/openstack/sharedfilesystems/v2/replicas.go +++ b/internal/acceptance/openstack/sharedfilesystems/v2/replicas.go @@ -95,7 +95,7 @@ func waitForReplicaStatus(t *testing.T, c *gophercloud.ServiceClient, id, status } if strings.Contains(current.Status, "error") { - return true, fmt.Errorf("An error occurred, wrong status: %s", current.Status) + return true, fmt.Errorf("an error occurred, wrong status: %s", current.Status) } return false, nil @@ -104,7 +104,7 @@ func waitForReplicaStatus(t *testing.T, c *gophercloud.ServiceClient, id, status if err != nil { mErr := PrintMessages(t, c, id) if mErr != nil { - return fmt.Errorf("Replica status is '%s' and unable to get manila messages: %s", err, mErr) + return fmt.Errorf("replica status is '%s' and unable to get manila messages: %s", err, mErr) } } @@ -127,7 +127,7 @@ func waitForReplicaState(t *testing.T, c *gophercloud.ServiceClient, id, state s } if strings.Contains(current.State, "error") { - return true, fmt.Errorf("An error occurred, wrong state: %s", current.State) + return true, fmt.Errorf("an error occurred, wrong state: %s", current.State) } return false, nil @@ -136,7 +136,7 @@ func waitForReplicaState(t *testing.T, c *gophercloud.ServiceClient, id, state s if err != nil { mErr := PrintMessages(t, c, id) if mErr != nil { - return fmt.Errorf("Replica state is '%s' and unable to get manila messages: %s", err, mErr) + return fmt.Errorf("replica state is '%s' and unable to get manila messages: %s", err, mErr) } } diff --git a/internal/acceptance/openstack/sharedfilesystems/v2/replicas_test.go b/internal/acceptance/openstack/sharedfilesystems/v2/replicas_test.go index 41913ac266..e662781360 100644 --- a/internal/acceptance/openstack/sharedfilesystems/v2/replicas_test.go +++ b/internal/acceptance/openstack/sharedfilesystems/v2/replicas_test.go @@ -18,7 +18,7 @@ import ( const replicasPathMicroversion = "2.56" func TestReplicaCreate(t *testing.T) { - clients.RequireManilaReplicas(t) + RequireManilaReplicas(t) client, err := clients.NewSharedFileSystemV2Client() if err != nil { @@ -55,7 +55,7 @@ func TestReplicaCreate(t *testing.T) { } func TestReplicaPromote(t *testing.T) { - clients.RequireManilaReplicas(t) + RequireManilaReplicas(t) client, err := clients.NewSharedFileSystemV2Client() if err != nil { @@ -131,7 +131,7 @@ func TestReplicaPromote(t *testing.T) { } func TestReplicaExportLocations(t *testing.T) { - clients.RequireManilaReplicas(t) + RequireManilaReplicas(t) client, err := clients.NewSharedFileSystemV2Client() if err != nil { @@ -181,15 +181,11 @@ func TestReplicaExportLocations(t *testing.T) { } exportLocations, err = replicas.ListExportLocations(context.TODO(), client, activeReplicaID).Extract() - if err != nil { - t.Errorf("Unable to list replica export locations: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, exportLocations) exportLocation, err := replicas.GetExportLocation(context.TODO(), client, activeReplicaID, exportLocations[0].ID).Extract() - if err != nil { - t.Errorf("Unable to get replica export location: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, exportLocation) // unset CreatedAt and UpdatedAt exportLocation.CreatedAt = time.Time{} @@ -198,7 +194,7 @@ func TestReplicaExportLocations(t *testing.T) { } func TestReplicaListDetail(t *testing.T) { - clients.RequireManilaReplicas(t) + RequireManilaReplicas(t) client, err := clients.NewSharedFileSystemV2Client() if err != nil { @@ -231,7 +227,7 @@ func TestReplicaListDetail(t *testing.T) { } func TestReplicaResetStatus(t *testing.T) { - clients.RequireManilaReplicas(t) + RequireManilaReplicas(t) client, err := clients.NewSharedFileSystemV2Client() if err != nil { @@ -272,7 +268,7 @@ func TestReplicaResetStatus(t *testing.T) { // This test available only for cloud admins func TestReplicaForceDelete(t *testing.T) { - clients.RequireManilaReplicas(t) + RequireManilaReplicas(t) clients.RequireAdmin(t) client, err := clients.NewSharedFileSystemV2Client() diff --git a/internal/acceptance/openstack/sharedfilesystems/v2/securityservices.go b/internal/acceptance/openstack/sharedfilesystems/v2/securityservices.go index 413095a6e2..e781f332cf 100644 --- a/internal/acceptance/openstack/sharedfilesystems/v2/securityservices.go +++ b/internal/acceptance/openstack/sharedfilesystems/v2/securityservices.go @@ -7,6 +7,7 @@ import ( "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" "github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/securityservices" + th "github.com/gophercloud/gophercloud/v2/testhelper" ) // CreateSecurityService will create a security service with a random name. An @@ -31,6 +32,10 @@ func CreateSecurityService(t *testing.T, client *gophercloud.ServiceClient) (*se return securityService, err } + th.AssertEquals(t, securityServiceName, securityService.Name) + th.AssertEquals(t, securityServiceDescription, securityService.Description) + th.AssertEquals(t, "kerberos", securityService.Type) + return securityService, nil } diff --git a/internal/acceptance/openstack/sharedfilesystems/v2/securityservices_test.go b/internal/acceptance/openstack/sharedfilesystems/v2/securityservices_test.go index a149a83324..abbb0b557c 100644 --- a/internal/acceptance/openstack/sharedfilesystems/v2/securityservices_test.go +++ b/internal/acceptance/openstack/sharedfilesystems/v2/securityservices_test.go @@ -9,6 +9,7 @@ import ( "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" "github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/securityservices" + th "github.com/gophercloud/gophercloud/v2/testhelper" ) func TestSecurityServiceCreateDelete(t *testing.T) { @@ -23,9 +24,7 @@ func TestSecurityServiceCreateDelete(t *testing.T) { } newSecurityService, err := securityservices.Get(context.TODO(), client, securityService.ID).Extract() - if err != nil { - t.Errorf("Unable to retrieve the security service: %v", err) - } + th.AssertNoErr(t, err) if newSecurityService.Name != securityService.Name { t.Fatalf("Security service name was expeted to be: %s", securityService.Name) @@ -125,14 +124,10 @@ func TestSecurityServiceUpdate(t *testing.T) { } _, err = securityservices.Update(context.TODO(), client, securityService.ID, options).Extract() - if err != nil { - t.Errorf("Unable to update the security service: %v", err) - } + th.AssertNoErr(t, err) newSecurityService, err := securityservices.Get(context.TODO(), client, securityService.ID).Extract() - if err != nil { - t.Errorf("Unable to retrieve the security service: %v", err) - } + th.AssertNoErr(t, err) if newSecurityService.Name != name { t.Fatalf("Security service name was expeted to be: %s", name) diff --git a/internal/acceptance/openstack/sharedfilesystems/v2/services_test.go b/internal/acceptance/openstack/sharedfilesystems/v2/services_test.go index e9a80f8675..8908c81a06 100644 --- a/internal/acceptance/openstack/sharedfilesystems/v2/services_test.go +++ b/internal/acceptance/openstack/sharedfilesystems/v2/services_test.go @@ -27,6 +27,6 @@ func TestServicesList(t *testing.T) { for _, s := range allServices { tools.PrintResource(t, &s) - th.AssertEquals(t, s.Status, "enabled") + th.AssertEquals(t, "enabled", s.Status) } } diff --git a/internal/acceptance/openstack/sharedfilesystems/v2/sharenetworks.go b/internal/acceptance/openstack/sharedfilesystems/v2/sharenetworks.go index 86d074058c..ab81ea28ac 100644 --- a/internal/acceptance/openstack/sharedfilesystems/v2/sharenetworks.go +++ b/internal/acceptance/openstack/sharedfilesystems/v2/sharenetworks.go @@ -8,6 +8,7 @@ import ( "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" "github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/sharenetworks" + th "github.com/gophercloud/gophercloud/v2/testhelper" ) // CreateShareNetwork will create a share network with a random name. An @@ -37,6 +38,11 @@ func CreateShareNetwork(t *testing.T, client *gophercloud.ServiceClient) (*share return shareNetwork, err } + th.AssertEquals(t, shareNetworkName, shareNetwork.Name) + th.AssertEquals(t, "This is a shared network", shareNetwork.Description) + th.AssertEquals(t, choices.NetworkID, shareNetwork.NeutronNetID) + th.AssertEquals(t, choices.SubnetID, shareNetwork.NeutronSubnetID) + return shareNetwork, nil } diff --git a/internal/acceptance/openstack/sharedfilesystems/v2/sharenetworks_test.go b/internal/acceptance/openstack/sharedfilesystems/v2/sharenetworks_test.go index c6a9bf1530..c75c07a26b 100644 --- a/internal/acceptance/openstack/sharedfilesystems/v2/sharenetworks_test.go +++ b/internal/acceptance/openstack/sharedfilesystems/v2/sharenetworks_test.go @@ -25,9 +25,7 @@ func TestShareNetworkCreateDestroy(t *testing.T) { } newShareNetwork, err := sharenetworks.Get(context.TODO(), client, shareNetwork.ID).Extract() - if err != nil { - t.Errorf("Unable to retrieve shareNetwork: %v", err) - } + th.AssertNoErr(t, err) if newShareNetwork.Name != shareNetwork.Name { t.Fatalf("Share network name was expeted to be: %s", shareNetwork.Name) @@ -56,9 +54,7 @@ func TestShareNetworkUpdate(t *testing.T) { } expectedShareNetwork, err := sharenetworks.Get(context.TODO(), client, shareNetwork.ID).Extract() - if err != nil { - t.Errorf("Unable to retrieve shareNetwork: %v", err) - } + th.AssertNoErr(t, err) name := "NewName" description := "" @@ -76,9 +72,7 @@ func TestShareNetworkUpdate(t *testing.T) { } updatedShareNetwork, err := sharenetworks.Get(context.TODO(), client, shareNetwork.ID).Extract() - if err != nil { - t.Errorf("Unable to retrieve shareNetwork: %v", err) - } + th.AssertNoErr(t, err) // Update time has to be set in order to get the assert equal to pass expectedShareNetwork.UpdatedAt = updatedShareNetwork.UpdatedAt diff --git a/internal/acceptance/openstack/sharedfilesystems/v2/shares.go b/internal/acceptance/openstack/sharedfilesystems/v2/shares.go index afb699627c..e5b9a78e64 100644 --- a/internal/acceptance/openstack/sharedfilesystems/v2/shares.go +++ b/internal/acceptance/openstack/sharedfilesystems/v2/shares.go @@ -12,6 +12,7 @@ import ( "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/messages" "github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/shares" + th "github.com/gophercloud/gophercloud/v2/testhelper" ) // CreateShare will create a share with a name, and a size of 1Gb. An @@ -48,6 +49,11 @@ func CreateShare(t *testing.T, client *gophercloud.ServiceClient, optShareType . return share, err } + th.AssertEquals(t, "My Test Share", share.Name) + th.AssertEquals(t, "My Test Description", share.Description) + th.AssertEquals(t, "NFS", share.ShareProto) + th.AssertEquals(t, 1, share.Size) + return share, nil } @@ -120,12 +126,12 @@ func PrintMessages(t *testing.T, c *gophercloud.ServiceClient, id string) error allPages, err := messages.List(c, messages.ListOpts{ResourceID: id}).AllPages(context.TODO()) if err != nil { - return fmt.Errorf("Unable to retrieve messages: %v", err) + return fmt.Errorf("unable to retrieve messages: %v", err) } allMessages, err := messages.ExtractMessages(allPages) if err != nil { - return fmt.Errorf("Unable to extract messages: %v", err) + return fmt.Errorf("unable to extract messages: %v", err) } for _, message := range allMessages { @@ -159,7 +165,7 @@ func waitForStatus(t *testing.T, c *gophercloud.ServiceClient, id, status string } if strings.Contains(current.Status, "error") { - return true, fmt.Errorf("An error occurred, wrong status: %s", current.Status) + return true, fmt.Errorf("an error occurred, wrong status: %s", current.Status) } return false, nil @@ -168,7 +174,7 @@ func waitForStatus(t *testing.T, c *gophercloud.ServiceClient, id, status string if err != nil { mErr := PrintMessages(t, c, id) if mErr != nil { - return current, fmt.Errorf("Share status is '%s' and unable to get manila messages: %s", err, mErr) + return current, fmt.Errorf("share status is '%s' and unable to get manila messages: %s", err, mErr) } } diff --git a/internal/acceptance/openstack/sharedfilesystems/v2/shares_test.go b/internal/acceptance/openstack/sharedfilesystems/v2/shares_test.go index 3d8bb0d32e..7247aa8984 100644 --- a/internal/acceptance/openstack/sharedfilesystems/v2/shares_test.go +++ b/internal/acceptance/openstack/sharedfilesystems/v2/shares_test.go @@ -53,15 +53,11 @@ func TestShareExportLocations(t *testing.T) { client.Microversion = "2.9" exportLocations, err := shares.ListExportLocations(context.TODO(), client, share.ID).Extract() - if err != nil { - t.Errorf("Unable to list share export locations: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, exportLocations) exportLocation, err := shares.GetExportLocation(context.TODO(), client, share.ID, exportLocations[0].ID).Extract() - if err != nil { - t.Errorf("Unable to get share export location: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, exportLocation) th.AssertEquals(t, exportLocations[0], *exportLocation) } @@ -80,9 +76,7 @@ func TestShareUpdate(t *testing.T) { defer DeleteShare(t, client, share) expectedShare, err := shares.Get(context.TODO(), client, share.ID).Extract() - if err != nil { - t.Errorf("Unable to retrieve share: %v", err) - } + th.AssertNoErr(t, err) name := "NewName" description := "" @@ -98,14 +92,10 @@ func TestShareUpdate(t *testing.T) { expectedShare.IsPublic = iFalse _, err = shares.Update(context.TODO(), client, share.ID, options).Extract() - if err != nil { - t.Errorf("Unable to update share: %v", err) - } + th.AssertNoErr(t, err) updatedShare, err := shares.Get(context.TODO(), client, share.ID).Extract() - if err != nil { - t.Errorf("Unable to retrieve share: %v", err) - } + th.AssertNoErr(t, err) // Update time has to be set in order to get the assert equal to pass expectedShare.UpdatedAt = updatedShare.UpdatedAt diff --git a/internal/acceptance/openstack/sharedfilesystems/v2/sharetransfers_test.go b/internal/acceptance/openstack/sharedfilesystems/v2/sharetransfers_test.go index 296b7ca807..6deac825fb 100644 --- a/internal/acceptance/openstack/sharedfilesystems/v2/sharetransfers_test.go +++ b/internal/acceptance/openstack/sharedfilesystems/v2/sharetransfers_test.go @@ -53,13 +53,13 @@ func TestTransferRequestCRUD(t *testing.T) { foundRequest = true } } - th.AssertEquals(t, foundRequest, true) + th.AssertTrue(t, foundRequest) // checking get tr, err := sharetransfers.Get(context.TODO(), client, transferRequest.ID).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, transferRequest.ID == tr.ID, true) + th.AssertTrue(t, transferRequest.ID == tr.ID) // Accept Share Transfer Request err = AcceptTransfer(t, client, transferRequest) diff --git a/internal/acceptance/openstack/sharedfilesystems/v2/sharetypes.go b/internal/acceptance/openstack/sharedfilesystems/v2/sharetypes.go index 69e0a4ea3c..69b46d444c 100644 --- a/internal/acceptance/openstack/sharedfilesystems/v2/sharetypes.go +++ b/internal/acceptance/openstack/sharedfilesystems/v2/sharetypes.go @@ -7,6 +7,7 @@ import ( "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" "github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/sharetypes" + th "github.com/gophercloud/gophercloud/v2/testhelper" ) // CreateShareType will create a share type with a random name. An @@ -34,6 +35,9 @@ func CreateShareType(t *testing.T, client *gophercloud.ServiceClient) (*sharetyp return shareType, err } + th.AssertEquals(t, shareTypeName, shareType.Name) + th.AssertFalse(t, shareType.IsPublic) + return shareType, nil } diff --git a/internal/acceptance/openstack/sharedfilesystems/v2/snapshots.go b/internal/acceptance/openstack/sharedfilesystems/v2/snapshots.go index 4fb3128f29..4a34ac7111 100644 --- a/internal/acceptance/openstack/sharedfilesystems/v2/snapshots.go +++ b/internal/acceptance/openstack/sharedfilesystems/v2/snapshots.go @@ -10,6 +10,7 @@ import ( "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/internal/acceptance/tools" "github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/snapshots" + th "github.com/gophercloud/gophercloud/v2/testhelper" ) // CreateSnapshot will create a snapshot from the share ID with a name. An error will @@ -37,6 +38,10 @@ func CreateSnapshot(t *testing.T, client *gophercloud.ServiceClient, shareID str return snapshot, err } + th.AssertEquals(t, "My Test Snapshot", snapshot.Name) + th.AssertEquals(t, "My Test Description", snapshot.Description) + th.AssertEquals(t, shareID, snapshot.ShareID) + return snapshot, nil } @@ -90,7 +95,7 @@ func waitForSnapshotStatus(t *testing.T, c *gophercloud.ServiceClient, id, statu } if strings.Contains(current.Status, "error") { - return true, fmt.Errorf("An error occurred, wrong status: %s", current.Status) + return true, fmt.Errorf("an error occurred, wrong status: %s", current.Status) } return false, nil @@ -99,7 +104,7 @@ func waitForSnapshotStatus(t *testing.T, c *gophercloud.ServiceClient, id, statu if err != nil { mErr := PrintMessages(t, c, id) if mErr != nil { - return fmt.Errorf("Snapshot status is '%s' and unable to get manila messages: %s", err, mErr) + return fmt.Errorf("snapshot status is '%s' and unable to get manila messages: %s", err, mErr) } } diff --git a/internal/acceptance/openstack/sharedfilesystems/v2/snapshots_test.go b/internal/acceptance/openstack/sharedfilesystems/v2/snapshots_test.go index 46b7c6d488..3c997d3c49 100644 --- a/internal/acceptance/openstack/sharedfilesystems/v2/snapshots_test.go +++ b/internal/acceptance/openstack/sharedfilesystems/v2/snapshots_test.go @@ -65,9 +65,7 @@ func TestSnapshotUpdate(t *testing.T) { defer DeleteSnapshot(t, client, snapshot) expectedSnapshot, err := snapshots.Get(context.TODO(), client, snapshot.ID).Extract() - if err != nil { - t.Errorf("Unable to retrieve snapshot: %v", err) - } + th.AssertNoErr(t, err) name := "NewName" description := "" @@ -80,14 +78,10 @@ func TestSnapshotUpdate(t *testing.T) { expectedSnapshot.Description = description _, err = snapshots.Update(context.TODO(), client, snapshot.ID, options).Extract() - if err != nil { - t.Errorf("Unable to update snapshot: %v", err) - } + th.AssertNoErr(t, err) updatedSnapshot, err := snapshots.Get(context.TODO(), client, snapshot.ID).Extract() - if err != nil { - t.Errorf("Unable to retrieve snapshot: %v", err) - } + th.AssertNoErr(t, err) tools.PrintResource(t, snapshot) diff --git a/internal/acceptance/openstack/workflow/v2/crontrigger.go b/internal/acceptance/openstack/workflow/v2/crontrigger.go index 109807c81c..289cd4ee44 100644 --- a/internal/acceptance/openstack/workflow/v2/crontrigger.go +++ b/internal/acceptance/openstack/workflow/v2/crontrigger.go @@ -32,7 +32,9 @@ func CreateCronTrigger(t *testing.T, client *gophercloud.ServiceClient, workflow return crontrigger, err } t.Logf("Cron trigger created: %s", crontriggerName) - th.AssertEquals(t, crontrigger.Name, crontriggerName) + th.AssertEquals(t, crontriggerName, crontrigger.Name) + th.AssertEquals(t, workflow.ID, crontrigger.WorkflowID) + th.AssertEquals(t, "0 0 1 1 *", crontrigger.Pattern) return crontrigger, nil } diff --git a/internal/acceptance/openstack/workflow/v2/execution.go b/internal/acceptance/openstack/workflow/v2/execution.go index d2c2a71970..672222fe4d 100644 --- a/internal/acceptance/openstack/workflow/v2/execution.go +++ b/internal/acceptance/openstack/workflow/v2/execution.go @@ -14,11 +14,12 @@ import ( // CreateExecution creates an execution for the given workflow. func CreateExecution(t *testing.T, client *gophercloud.ServiceClient, workflow *workflows.Workflow) (*executions.Execution, error) { + executionID := tools.RandomString("execution_", 5) executionDescription := tools.RandomString("execution_", 5) t.Logf("Attempting to create execution: %s", executionDescription) createOpts := executions.CreateOpts{ - ID: executionDescription, + ID: executionID, WorkflowID: workflow.ID, WorkflowNamespace: workflow.Namespace, Description: executionDescription, @@ -33,7 +34,9 @@ func CreateExecution(t *testing.T, client *gophercloud.ServiceClient, workflow * t.Logf("Execution created: %s", executionDescription) - th.AssertEquals(t, execution.Description, executionDescription) + th.AssertEquals(t, executionDescription, execution.Description) + th.AssertEquals(t, workflow.ID, execution.WorkflowID) + th.AssertEquals(t, workflow.Namespace, execution.WorkflowNamespace) t.Logf("Wait for execution status SUCCESS: %s", executionDescription) th.AssertNoErr(t, tools.WaitFor(func(ctx context.Context) (bool, error) { @@ -48,7 +51,7 @@ func CreateExecution(t *testing.T, client *gophercloud.ServiceClient, workflow * } if latest.State == "ERROR" { - return false, fmt.Errorf("Execution in ERROR state") + return false, fmt.Errorf("execution in ERROR state") } return false, nil diff --git a/internal/acceptance/openstack/workflow/v2/workflow.go b/internal/acceptance/openstack/workflow/v2/workflow.go index ea67739eac..3d9ce81134 100644 --- a/internal/acceptance/openstack/workflow/v2/workflow.go +++ b/internal/acceptance/openstack/workflow/v2/workflow.go @@ -57,6 +57,8 @@ func CreateWorkflow(t *testing.T, client *gophercloud.ServiceClient) (*workflows t.Logf("Workflow created: %s", workflowName) th.AssertEquals(t, workflowName, workflow.Name) + th.AssertEquals(t, "some-namespace", workflow.Namespace) + th.AssertEquals(t, "private", workflow.Scope) return &workflow, nil } diff --git a/internal/acceptance/tools/tools.go b/internal/acceptance/tools/tools.go index 8b0fad2ea6..2f0238226a 100644 --- a/internal/acceptance/tools/tools.go +++ b/internal/acceptance/tools/tools.go @@ -3,6 +3,7 @@ package tools import ( "context" "encoding/json" + "fmt" "math/rand" "strings" "testing" @@ -69,6 +70,10 @@ func RandomInt(min, max int) int { return rand.Intn(max-min) + min } +func RandomUUID() string { + return fmt.Sprintf("%08x-0000-0000-0000-000000000000", rand.Int31()) +} + // Elide returns the first bit of its input string with a suffix of "..." if it's longer than // a comfortable 40 characters. func Elide(value string) string { @@ -81,5 +86,5 @@ func Elide(value string) string { // PrintResource returns a resource as a readable structure func PrintResource(t *testing.T, resource any) { b, _ := json.MarshalIndent(resource, "", " ") - t.Logf(string(b)) + t.Log(string(b)) } diff --git a/internal/ptr/ptr.go b/internal/ptr/ptr.go new file mode 100644 index 0000000000..6c3ee9bd51 --- /dev/null +++ b/internal/ptr/ptr.go @@ -0,0 +1,5 @@ +package ptr + +func To[T any](v T) *T { + return &v +} diff --git a/openstack/auth_env.go b/openstack/auth_env.go index 893787b787..9ecc5b4efe 100644 --- a/openstack/auth_env.go +++ b/openstack/auth_env.go @@ -24,8 +24,8 @@ OS_PROJECT_NAME and the latter are expected against a v3 auth api. If OS_PROJECT_ID and OS_PROJECT_NAME are set, they will still be referred as "tenant" in Gophercloud. -If OS_PROJECT_NAME is set, it requires OS_PROJECT_ID to be set as well to -handle projects not on the default domain. +If OS_PROJECT_NAME is set, it requires OS_DOMAIN_ID or OS_DOMAIN_NAME to be +set as well to handle projects not on the default domain. To use this function, first set the OS_* environment variables (for example, by sourcing an `openrc` file), then: diff --git a/openstack/baremetal/apiversions/testing/fixtures_test.go b/openstack/baremetal/apiversions/testing/fixtures_test.go index 5e169d36fc..2d799a07c1 100644 --- a/openstack/baremetal/apiversions/testing/fixtures_test.go +++ b/openstack/baremetal/apiversions/testing/fixtures_test.go @@ -81,26 +81,26 @@ var IronicAllAPIVersionResults = apiversions.APIVersions{ }, } -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { +func MockListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, IronicAPIAllVersionResponse) + fmt.Fprint(w, IronicAPIAllVersionResponse) }) } -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc("/v1/", func(w http.ResponseWriter, r *http.Request) { +func MockGetResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, IronicAPIVersionResponse) + fmt.Fprint(w, IronicAPIVersionResponse) }) } diff --git a/openstack/baremetal/apiversions/testing/requests_test.go b/openstack/baremetal/apiversions/testing/requests_test.go index 27d9fa5e22..2a9de065cb 100644 --- a/openstack/baremetal/apiversions/testing/requests_test.go +++ b/openstack/baremetal/apiversions/testing/requests_test.go @@ -10,24 +10,24 @@ import ( ) func TestListAPIVersions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) - actual, err := apiversions.List(context.TODO(), client.ServiceClient()).Extract() + actual, err := apiversions.List(context.TODO(), client.ServiceClient(fakeServer)).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, IronicAllAPIVersionResults, *actual) } func TestGetAPIVersion(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetResponse(t) + MockGetResponse(t, fakeServer) - actual, err := apiversions.Get(context.TODO(), client.ServiceClient(), "v1").Extract() + actual, err := apiversions.Get(context.TODO(), client.ServiceClient(fakeServer), "v1").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, IronicAPIVersion1Result, *actual) diff --git a/openstack/baremetal/httpbasic/requests.go b/openstack/baremetal/httpbasic/requests.go index 3b13dd4637..6eb65eff14 100644 --- a/openstack/baremetal/httpbasic/requests.go +++ b/openstack/baremetal/httpbasic/requests.go @@ -20,7 +20,7 @@ func initClientOpts(client *gophercloud.ProviderClient, eo EndpointOpts) (*gophe return nil, fmt.Errorf("IronicEndpoint is required") } if eo.IronicUser == "" || eo.IronicUserPassword == "" { - return nil, fmt.Errorf("User and Password are required") + return nil, fmt.Errorf("IronicUser and IronicUserPassword are required") } token := []byte(eo.IronicUser + ":" + eo.IronicUserPassword) diff --git a/openstack/baremetal/httpbasic/testing/requests_test.go b/openstack/baremetal/httpbasic/testing/requests_test.go index d8c9faf5ba..9618a1b1df 100644 --- a/openstack/baremetal/httpbasic/testing/requests_test.go +++ b/openstack/baremetal/httpbasic/testing/requests_test.go @@ -24,7 +24,7 @@ func TestHttpBasic(t *testing.T) { IronicEndpoint: "http://ironic:6385/v1", }) _ = errTest1 - th.AssertEquals(t, "User and Password are required", err.Error()) + th.AssertEquals(t, "IronicUser and IronicUserPassword are required", err.Error()) errTest2, err := httpbasic.NewBareMetalHTTPBasic(httpbasic.EndpointOpts{ IronicUser: "myUser", diff --git a/openstack/baremetal/inventory/plugindata.go b/openstack/baremetal/inventory/plugindata.go index 3c4666eb34..4cbd8214e7 100644 --- a/openstack/baremetal/inventory/plugindata.go +++ b/openstack/baremetal/inventory/plugindata.go @@ -54,7 +54,7 @@ func (r *LLDPTLVType) UnmarshalJSON(data []byte) error { } if len(list) != 2 { - return fmt.Errorf("Invalid LLDP TLV key-value pair") + return fmt.Errorf("invalid LLDP TLV key-value pair") } fieldtype, ok := list[0].(float64) diff --git a/openstack/baremetal/inventory/types.go b/openstack/baremetal/inventory/types.go index 468f32a74f..98498d530d 100644 --- a/openstack/baremetal/inventory/types.go +++ b/openstack/baremetal/inventory/types.go @@ -24,6 +24,7 @@ type InterfaceType struct { Product string `json:"product"` SpeedMbps int `json:"speed_mbps"` Vendor string `json:"vendor"` + PCIAddress string `json:"pci_address"` } type MemoryType struct { diff --git a/openstack/baremetal/v1/allocations/results.go b/openstack/baremetal/v1/allocations/results.go index 932862e283..0b6be1f6cf 100644 --- a/openstack/baremetal/v1/allocations/results.go +++ b/openstack/baremetal/v1/allocations/results.go @@ -56,11 +56,11 @@ func (r allocationResult) Extract() (*Allocation, error) { } func (r allocationResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "") + return r.ExtractIntoStructPtr(v, "") } func ExtractAllocationsInto(r pagination.Page, v any) error { - return r.(AllocationPage).Result.ExtractIntoSlicePtr(v, "allocations") + return r.(AllocationPage).ExtractIntoSlicePtr(v, "allocations") } // AllocationPage abstracts the raw results of making a List() request against @@ -81,7 +81,7 @@ func (r AllocationPage) IsEmpty() (bool, error) { // NextPageURL uses the response's embedded link reference to navigate to the // next page of results. -func (r AllocationPage) NextPageURL() (string, error) { +func (r AllocationPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"allocations_links"` } diff --git a/openstack/baremetal/v1/allocations/testing/fixtures_test.go b/openstack/baremetal/v1/allocations/testing/fixtures_test.go index 6b8655169b..9350437592 100644 --- a/openstack/baremetal/v1/allocations/testing/fixtures_test.go +++ b/openstack/baremetal/v1/allocations/testing/fixtures_test.go @@ -108,8 +108,8 @@ var ( ) // HandleAllocationListSuccessfully sets up the test server to respond to a allocation List request. -func HandleAllocationListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/allocations", func(w http.ResponseWriter, r *http.Request) { +func HandleAllocationListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/allocations", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") @@ -120,10 +120,10 @@ func HandleAllocationListSuccessfully(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, AllocationListBody) + fmt.Fprint(w, AllocationListBody) case "eff80f47-75f0-4d41-b1aa-cf07c201adac": - fmt.Fprintf(w, `{ "allocations": [] }`) + fmt.Fprint(w, `{ "allocations": [] }`) default: t.Fatalf("/allocations invoked with unexpected marker=[%s]", marker) } @@ -132,8 +132,8 @@ func HandleAllocationListSuccessfully(t *testing.T) { // HandleAllocationCreationSuccessfully sets up the test server to respond to a allocation creation request // with a given response. -func HandleAllocationCreationSuccessfully(t *testing.T, response string) { - th.Mux.HandleFunc("/allocations", func(w http.ResponseWriter, r *http.Request) { +func HandleAllocationCreationSuccessfully(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/allocations", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -145,13 +145,13 @@ func HandleAllocationCreationSuccessfully(t *testing.T, response string) { w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) } // HandleAllocationDeletionSuccessfully sets up the test server to respond to a allocation deletion request. -func HandleAllocationDeletionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/allocations/344a3e2-978a-444e-990a-cbf47c62ef88", func(w http.ResponseWriter, r *http.Request) { +func HandleAllocationDeletionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/allocations/344a3e2-978a-444e-990a-cbf47c62ef88", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -159,12 +159,12 @@ func HandleAllocationDeletionSuccessfully(t *testing.T) { }) } -func HandleAllocationGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/allocations/344a3e2-978a-444e-990a-cbf47c62ef88", func(w http.ResponseWriter, r *http.Request) { +func HandleAllocationGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/allocations/344a3e2-978a-444e-990a-cbf47c62ef88", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleAllocationBody) + fmt.Fprint(w, SingleAllocationBody) }) } diff --git a/openstack/baremetal/v1/allocations/testing/requests_test.go b/openstack/baremetal/v1/allocations/testing/requests_test.go index 2eec2aa51b..c30334eafa 100644 --- a/openstack/baremetal/v1/allocations/testing/requests_test.go +++ b/openstack/baremetal/v1/allocations/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestListAllocations(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAllocationListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAllocationListSuccessfully(t, fakeServer) pages := 0 - err := allocations.List(client.ServiceClient(), allocations.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := allocations.List(client.ServiceClient(fakeServer), allocations.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := allocations.ExtractAllocations(page) @@ -41,11 +41,11 @@ func TestListAllocations(t *testing.T) { } func TestCreateAllocation(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAllocationCreationSuccessfully(t, SingleAllocationBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAllocationCreationSuccessfully(t, fakeServer, SingleAllocationBody) - actual, err := allocations.Create(context.TODO(), client.ServiceClient(), allocations.CreateOpts{ + actual, err := allocations.Create(context.TODO(), client.ServiceClient(fakeServer), allocations.CreateOpts{ Name: "allocation-1", ResourceClass: "baremetal", CandidateNodes: []string{"344a3e2-978a-444e-990a-cbf47c62ef88"}, @@ -57,20 +57,20 @@ func TestCreateAllocation(t *testing.T) { } func TestDeleteAllocation(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAllocationDeletionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAllocationDeletionSuccessfully(t, fakeServer) - res := allocations.Delete(context.TODO(), client.ServiceClient(), "344a3e2-978a-444e-990a-cbf47c62ef88") + res := allocations.Delete(context.TODO(), client.ServiceClient(fakeServer), "344a3e2-978a-444e-990a-cbf47c62ef88") th.AssertNoErr(t, res.Err) } func TestGetAllocation(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAllocationGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAllocationGetSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) actual, err := allocations.Get(context.TODO(), c, "344a3e2-978a-444e-990a-cbf47c62ef88").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) diff --git a/openstack/baremetal/v1/conductors/results.go b/openstack/baremetal/v1/conductors/results.go index 5431106108..e1bcd6d75b 100644 --- a/openstack/baremetal/v1/conductors/results.go +++ b/openstack/baremetal/v1/conductors/results.go @@ -19,11 +19,11 @@ func (r conductorResult) Extract() (*Conductor, error) { } func (r conductorResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "") + return r.ExtractIntoStructPtr(v, "") } func ExtractConductorInto(r pagination.Page, v any) error { - return r.(ConductorPage).Result.ExtractIntoSlicePtr(v, "conductors") + return r.(ConductorPage).ExtractIntoSlicePtr(v, "conductors") } // Conductor represents a conductor in the OpenStack Bare Metal API. @@ -67,7 +67,7 @@ func (r ConductorPage) IsEmpty() (bool, error) { // NextPageURL uses the response's embedded link reference to navigate to the // next page of results. -func (r ConductorPage) NextPageURL() (string, error) { +func (r ConductorPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"conductor_links"` } diff --git a/openstack/baremetal/v1/conductors/testing/fixtures_test.go b/openstack/baremetal/v1/conductors/testing/fixtures_test.go index 8e59ddaab4..4b264fd7e8 100644 --- a/openstack/baremetal/v1/conductors/testing/fixtures_test.go +++ b/openstack/baremetal/v1/conductors/testing/fixtures_test.go @@ -138,8 +138,8 @@ var ( ) // HandleConductorListSuccessfully sets up the test server to respond to a server List request. -func HandleConductorListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/conductors", func(w http.ResponseWriter, r *http.Request) { +func HandleConductorListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/conductors", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") @@ -150,10 +150,10 @@ func HandleConductorListSuccessfully(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, ConductorListBody) + fmt.Fprint(w, ConductorListBody) case "9e5476bd-a4ec-4653-93d6-72c93aa682ba": - fmt.Fprintf(w, `{ "servers": [] }`) + fmt.Fprint(w, `{ "servers": [] }`) default: t.Fatalf("/conductors invoked with unexpected marker=[%s]", marker) } @@ -161,8 +161,8 @@ func HandleConductorListSuccessfully(t *testing.T) { } // HandleConductorListDetailSuccessfully sets up the test server to respond to a server List request. -func HandleConductorListDetailSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/conductors", func(w http.ResponseWriter, r *http.Request) { +func HandleConductorListDetailSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/conductors", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") @@ -170,16 +170,16 @@ func HandleConductorListDetailSuccessfully(t *testing.T) { t.Errorf("Failed to parse request form %v", err) } - fmt.Fprintf(w, ConductorListDetailBody) + fmt.Fprint(w, ConductorListDetailBody) }) } -func HandleConductorGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/conductors/1234asdf", func(w http.ResponseWriter, r *http.Request) { +func HandleConductorGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/conductors/1234asdf", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleConductorBody) + fmt.Fprint(w, SingleConductorBody) }) } diff --git a/openstack/baremetal/v1/conductors/testing/requests_test.go b/openstack/baremetal/v1/conductors/testing/requests_test.go index 6f6aa518ad..d0463fd79d 100644 --- a/openstack/baremetal/v1/conductors/testing/requests_test.go +++ b/openstack/baremetal/v1/conductors/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestListConductors(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleConductorListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleConductorListSuccessfully(t, fakeServer) pages := 0 - err := conductors.List(client.ServiceClient(), conductors.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := conductors.List(client.ServiceClient(fakeServer), conductors.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := conductors.ExtractConductors(page) @@ -41,12 +41,12 @@ func TestListConductors(t *testing.T) { } func TestListDetailConductors(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleConductorListDetailSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleConductorListDetailSuccessfully(t, fakeServer) pages := 0 - err := conductors.List(client.ServiceClient(), conductors.ListOpts{Detail: true}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := conductors.List(client.ServiceClient(fakeServer), conductors.ListOpts{Detail: true}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := conductors.ExtractConductors(page) @@ -58,9 +58,9 @@ func TestListDetailConductors(t *testing.T) { t.Fatalf("Expected 2 conductors, got %d", len(actual)) } th.AssertEquals(t, "compute1.localdomain", actual[0].Hostname) - th.AssertEquals(t, false, actual[0].Alive) + th.AssertFalse(t, actual[0].Alive) th.AssertEquals(t, "compute2.localdomain", actual[1].Hostname) - th.AssertEquals(t, true, actual[1].Alive) + th.AssertTrue(t, actual[1].Alive) return true, nil }) @@ -84,7 +84,7 @@ func TestListOpts(t *testing.T) { } _, err := optsDetail.ToConductorListQuery() - th.AssertEquals(t, err.Error(), "cannot have both fields and detail options for conductors") + th.AssertEquals(t, "cannot have both fields and detail options for conductors", err.Error()) // Regular ListOpts can query, err := opts.ToConductorListQuery() @@ -93,11 +93,11 @@ func TestListOpts(t *testing.T) { } func TestGetConductor(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleConductorGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleConductorGetSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) actual, err := conductors.Get(context.TODO(), c, "1234asdf").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) diff --git a/openstack/baremetal/v1/drivers/results.go b/openstack/baremetal/v1/drivers/results.go index 4905fa902b..d6ed459640 100644 --- a/openstack/baremetal/v1/drivers/results.go +++ b/openstack/baremetal/v1/drivers/results.go @@ -17,11 +17,11 @@ func (r driverResult) Extract() (*Driver, error) { } func (r driverResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "") + return r.ExtractIntoStructPtr(v, "") } func ExtractDriversInto(r pagination.Page, v any) error { - return r.(DriverPage).Result.ExtractIntoSlicePtr(v, "drivers") + return r.(DriverPage).ExtractIntoSlicePtr(v, "drivers") } // Driver represents a driver in the OpenStack Bare Metal API. @@ -151,7 +151,7 @@ func (r DriverPage) IsEmpty() (bool, error) { // NextPageURL uses the response's embedded link reference to navigate to the // next page of results. -func (r DriverPage) NextPageURL() (string, error) { +func (r DriverPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"drivers_links"` } diff --git a/openstack/baremetal/v1/drivers/testing/fixtures_test.go b/openstack/baremetal/v1/drivers/testing/fixtures_test.go index a06bcee928..7fb607fd1f 100644 --- a/openstack/baremetal/v1/drivers/testing/fixtures_test.go +++ b/openstack/baremetal/v1/drivers/testing/fixtures_test.go @@ -368,8 +368,8 @@ var ( ) // HandleListDriversSuccessfully sets up the test server to respond to a drivers ListDrivers request. -func HandleListDriversSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/drivers", func(w http.ResponseWriter, r *http.Request) { +func HandleListDriversSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/drivers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") @@ -377,39 +377,39 @@ func HandleListDriversSuccessfully(t *testing.T) { t.Errorf("Failed to parse request form %v", err) } - fmt.Fprintf(w, ListDriversBody) + fmt.Fprint(w, ListDriversBody) }) } // HandleGetDriverDetailsSuccessfully sets up the test server to respond to a drivers GetDriverDetails request. -func HandleGetDriverDetailsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/drivers/ipmi", func(w http.ResponseWriter, r *http.Request) { +func HandleGetDriverDetailsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/drivers/ipmi", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleDriverDetails) + fmt.Fprint(w, SingleDriverDetails) }) } // HandleGetDriverPropertiesSuccessfully sets up the test server to respond to a drivers GetDriverProperties request. -func HandleGetDriverPropertiesSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/drivers/agent_ipmitool/properties", func(w http.ResponseWriter, r *http.Request) { +func HandleGetDriverPropertiesSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/drivers/agent_ipmitool/properties", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleDriverProperties) + fmt.Fprint(w, SingleDriverProperties) }) } // HandleGetDriverDiskPropertiesSuccessfully sets up the test server to respond to a drivers GetDriverDiskProperties request. -func HandleGetDriverDiskPropertiesSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/drivers/agent_ipmitool/raid/logical_disk_properties", func(w http.ResponseWriter, r *http.Request) { +func HandleGetDriverDiskPropertiesSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/drivers/agent_ipmitool/raid/logical_disk_properties", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleDriverDiskProperties) + fmt.Fprint(w, SingleDriverDiskProperties) }) } diff --git a/openstack/baremetal/v1/drivers/testing/requests_test.go b/openstack/baremetal/v1/drivers/testing/requests_test.go index f99e3198c0..58d83f27a6 100644 --- a/openstack/baremetal/v1/drivers/testing/requests_test.go +++ b/openstack/baremetal/v1/drivers/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestListDrivers(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListDriversSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListDriversSuccessfully(t, fakeServer) pages := 0 - err := drivers.ListDrivers(client.ServiceClient(), drivers.ListDriversOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := drivers.ListDrivers(client.ServiceClient(fakeServer), drivers.ListDriversOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := drivers.ExtractDrivers(page) @@ -43,11 +43,11 @@ func TestListDrivers(t *testing.T) { } func TestGetDriverDetails(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetDriverDetailsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetDriverDetailsSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) actual, err := drivers.GetDriverDetails(context.TODO(), c, "ipmi").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) @@ -57,11 +57,11 @@ func TestGetDriverDetails(t *testing.T) { } func TestGetDriverProperties(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetDriverPropertiesSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetDriverPropertiesSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) actual, err := drivers.GetDriverProperties(context.TODO(), c, "agent_ipmitool").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) @@ -71,11 +71,11 @@ func TestGetDriverProperties(t *testing.T) { } func TestGetDriverDiskProperties(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetDriverDiskPropertiesSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetDriverDiskPropertiesSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) actual, err := drivers.GetDriverDiskProperties(context.TODO(), c, "agent_ipmitool").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) diff --git a/openstack/baremetal/v1/nodes/requests.go b/openstack/baremetal/v1/nodes/requests.go index f79721b4e7..89ba6a5b16 100644 --- a/openstack/baremetal/v1/nodes/requests.go +++ b/openstack/baremetal/v1/nodes/requests.go @@ -50,7 +50,7 @@ const ( Unrescuing ProvisionState = "unrescuing" Servicing ProvisionState = "servicing" ServiceWait ProvisionState = "service wait" - ServiceFail ProvisionState = "service fail" + ServiceFail ProvisionState = "service failed" ServiceHold ProvisionState = "service hold" ) @@ -113,6 +113,10 @@ type ListOpts struct { // Filter the list with the specified fault. Fault string `q:"fault"` + // Filter the list with the specified health status. + // Requires microversion 1.109 or later. + Health string `q:"health"` + // One or more fields to be returned in the response. Fields []string `q:"fields" format:"comma-separated"` @@ -274,6 +278,10 @@ type CreateOpts struct { // Static network configuration to use during deployment and cleaning. NetworkData map[string]any `json:"network_data,omitempty"` + + // Whether disable_power_off is enabled or disabled on this node. + // Requires microversion 1.95 or later. + DisablePowerOff *bool `json:"disable_power_off,omitempty"` } // ToNodeCreateMap assembles a request body based on the contents of a CreateOpts. @@ -998,3 +1006,73 @@ func DetachVirtualMedia(ctx context.Context, client *gophercloud.ServiceClient, _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } + +// Request the list of virtual media devices attached to the Node. +// Requires microversion 1.93 or later. +func GetVirtualMedia(ctx context.Context, client *gophercloud.ServiceClient, id string) (r VirtualMediaGetResult) { + + resp, err := client.Get(ctx, virtualMediaURL(client, id), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// VirtualInterfaceOpts defines options for attaching a VIF to a node +type VirtualInterfaceOpts struct { + // The UUID or name of the VIF + ID string `json:"id" required:"true"` + // The UUID of a port to attach the VIF to. Cannot be specified with PortgroupUUID + PortUUID string `json:"port_uuid,omitempty"` + // The UUID of a portgroup to attach the VIF to. Cannot be specified with PortUUID + PortgroupUUID string `json:"portgroup_uuid,omitempty"` +} + +// VirtualInterfaceOptsBuilder allows extensions to add additional parameters to the +// AttachVirtualInterface request. +type VirtualInterfaceOptsBuilder interface { + ToVirtualInterfaceMap() (map[string]any, error) +} + +// ToVirtualInterfaceMap assembles a request body based on the contents of a VirtualInterfaceOpts. +func (opts VirtualInterfaceOpts) ToVirtualInterfaceMap() (map[string]any, error) { + if opts.PortUUID != "" && opts.PortgroupUUID != "" { + return nil, fmt.Errorf("cannot specify both port_uuid and portgroup_uuid") + } + + return gophercloud.BuildRequestBody(opts, "") +} + +// ListVirtualInterfaces returns a list of VIFs that are attached to the node. +func ListVirtualInterfaces(ctx context.Context, client *gophercloud.ServiceClient, id string) (r ListVirtualInterfacesResult) { + resp, err := client.Get(ctx, virtualInterfaceURL(client, id), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// AttachVirtualInterface attaches a VIF to a node. +func AttachVirtualInterface(ctx context.Context, client *gophercloud.ServiceClient, id string, opts VirtualInterfaceOptsBuilder) (r VirtualInterfaceAttachResult) { + reqBody, err := opts.ToVirtualInterfaceMap() + if err != nil { + r.Err = err + return + } + + resp, err := client.Post(ctx, virtualInterfaceURL(client, id), reqBody, nil, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// DetachVirtualInterface detaches a VIF from a node. +func DetachVirtualInterface(ctx context.Context, client *gophercloud.ServiceClient, id string, vifID string) (r VirtualInterfaceDetachResult) { + resp, err := client.Delete(ctx, virtualInterfaceDeleteURL(client, id, vifID), &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/baremetal/v1/nodes/results.go b/openstack/baremetal/v1/nodes/results.go index 0602add768..e2c5947b7b 100644 --- a/openstack/baremetal/v1/nodes/results.go +++ b/openstack/baremetal/v1/nodes/results.go @@ -47,11 +47,11 @@ func (r ValidateResult) Extract() (*NodeValidation, error) { } func (r nodeResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "") + return r.ExtractIntoStructPtr(v, "") } func ExtractNodesInto(r pagination.Page, v any) error { - return r.(NodePage).Result.ExtractIntoSlicePtr(v, "nodes") + return r.(NodePage).ExtractIntoSlicePtr(v, "nodes") } // Extract interprets a BIOSSettingsResult as an array of BIOSSetting structs, if possible. @@ -90,12 +90,19 @@ func (r SubscriptionVendorPassthruResult) Extract() (*SubscriptionVendorPassthru return &s, err } +// Link represents a hyperlink and its relationship to the current resource. +type Link struct { + // Href is the URL of the related resource. + Href string `json:"href"` + + // Rel describes the relationship of the resource to the current + // context (e.g., "self", "bookmark"). + Rel string `json:"rel"` +} + // Node represents a node in the OpenStack Bare Metal API. +// https://docs.openstack.org/api-ref/baremetal/#list-nodes-detailed type Node struct { - // Whether automated cleaning is enabled or disabled on this node. - // Requires microversion 1.47 or later. - AutomatedClean *bool `json:"automated_clean"` - // UUID for the resource. UUID string `json:"uuid"` @@ -132,6 +139,11 @@ type Node struct { // node. There are other possible types, e.g., “clean failure” and “rescue abort failure”. Fault string `json:"fault"` + // Health indicates the hardware health status reported by the BMC. + // Possible values are "OK", "Warning", "Critical", or empty if not available. + // This field is read-only and requires microversion 1.109 or later. + Health string `json:"health"` + // Error from the most recent (last) transaction that started but failed to finish. LastError string `json:"last_error"` @@ -183,8 +195,17 @@ type Node struct { // Current deploy step. DeployStep map[string]any `json:"deploy_step"` - // Current service step. - ServiceStep map[string]any `json:"service_step"` + // A list of relative links. Includes the self and bookmark links. + Links []Link `json:"links"` + + // Links to the collection of ports on this node + Ports []Link `json:"ports"` + + // Links to the collection of portgroups on this node. + PortGroups []Link `json:"portgroups"` + + // Links to the collection of states. Note that this resource is also used to request state transitions. + States []Link `json:"states"` // String which can be used by external schedulers to identify this Node as a unit of a specific type of resource. // For more details, see: https://docs.openstack.org/ironic/latest/install/configure-nova-flavors.html @@ -202,9 +223,6 @@ type Node struct { // Deploy interface for a node, e.g. “iscsi”. DeployInterface string `json:"deploy_interface"` - // Firmware interface for a node, e.g. “redfish”. - FirmwareInterface string `json:"firmware_interface"` - // Interface used for node inspection, e.g. “no-inspect”. InspectInterface string `json:"inspect_interface"` @@ -232,9 +250,15 @@ type Node struct { // For vendor-specific functionality on this node, e.g. “no-vendor”. VendorInterface string `json:"vendor_interface"` + // Links to the volume resources. + Volume []Link `json:"volume"` + // Conductor group for a node. Case-insensitive string up to 255 characters, containing a-z, 0-9, _, -, and .. ConductorGroup string `json:"conductor_group"` + // An optional UUID which can be used to denote the “parent” baremetal node. + ParentNode string `json:"parent_node"` + // The node is protected from undeploying, rebuilding and deletion. Protected bool `json:"protected"` @@ -244,14 +268,42 @@ type Node struct { // A string or UUID of the tenant who owns the baremetal node. Owner string `json:"owner"` + // A string or UUID of the tenant who is leasing the object. + Lessee string `json:"lessee"` + + // A string indicating the shard this node belongs to. + Shard string `json:"shard"` + + // Informational text about this node. + Description string `json:"description"` + + // The conductor currently servicing a node. This field is read-only. + Conductor string `json:"conductor"` + + // The UUID of the allocation associated with the node. If not null, will be the same as instance_uuid + // (the opposite is not always true). Unlike instance_uuid, this field is read-only. Please use the + // Allocation API to remove allocations. + AllocationUUID string `json:"allocation_uuid"` + + // Whether the node is retired. A Node tagged as retired will prevent any further + // scheduling of instances, but will still allow for other operations, such as cleaning, to happen + Retired bool `json:"retired"` + + // Reason the node is marked as retired. + RetiredReason string `json:"retired_reason"` + // Static network configuration to use during deployment and cleaning. NetworkData map[string]any `json:"network_data"` - // The UTC date and time when the resource was created, ISO 8601 format. - CreatedAt time.Time `json:"created_at"` + // Whether automated cleaning is enabled or disabled on this node. + // Requires microversion 1.47 or later. + AutomatedClean *bool `json:"automated_clean"` - // The UTC date and time when the resource was updated, ISO 8601 format. May be “null”. - UpdatedAt time.Time `json:"updated_at"` + // Current service step. + ServiceStep map[string]any `json:"service_step"` + + // Firmware interface for a node, e.g. “redfish”. + FirmwareInterface string `json:"firmware_interface"` // The UTC date and time when the provision state was updated, ISO 8601 format. May be “null”. ProvisionUpdatedAt time.Time `json:"provision_updated_at"` @@ -261,6 +313,16 @@ type Node struct { // The UTC date and time when the last inspection was finished, ISO 8601 format. May be “null” if inspection hasn't been finished yet. InspectionFinishedAt *time.Time `json:"inspection_finished_at"` + + // The UTC date and time when the resource was created, ISO 8601 format. + CreatedAt time.Time `json:"created_at"` + + // The UTC date and time when the resource was updated, ISO 8601 format. May be “null”. + UpdatedAt time.Time `json:"updated_at"` + + // Whether disable_power_off is enabled or disabled on this node. + // Requires microversion 1.95 or later. + DisablePowerOff bool `json:"disable_power_off"` } // NodePage abstracts the raw results of making a List() request against @@ -283,7 +345,7 @@ func (r NodePage) IsEmpty() (bool, error) { // NextPageURL uses the response's embedded link reference to navigate to the // next page of results. -func (r NodePage) NextPageURL() (string, error) { +func (r NodePage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"nodes_links"` } @@ -653,3 +715,45 @@ type VirtualMediaAttachResult struct { type VirtualMediaDetachResult struct { gophercloud.ErrResult } + +// Requires microversion 1.93 or later. +type VirtualMediaGetResult struct { + gophercloud.Result +} + +// VirtualInterfaceAttachResult is the response from an AttachVirtualInterface operation. +type VirtualInterfaceAttachResult struct { + gophercloud.ErrResult +} + +// VirtualInterfaceDetachResult is the response from a DetachVirtualInterface operation. +type VirtualInterfaceDetachResult struct { + gophercloud.ErrResult +} + +// VIF represents a virtual interface attached to a node. +type VIF struct { + // The UUID or name of the VIF + ID string `json:"id"` +} + +// ListVirtualInterfacesResult is the response from a ListVirtualInterfaces operation. +type ListVirtualInterfacesResult struct { + gophercloud.Result + gophercloud.HeaderResult +} + +// Extract interprets any ListVirtualInterfacesResult as a list of VIFs. +func (r ListVirtualInterfacesResult) Extract() ([]VIF, error) { + var s struct { + VIFs []VIF `json:"vifs"` + } + + err := r.Result.ExtractInto(&s) + return s.VIFs, err +} + +// ExtractHeader interprets any ListVirtualInterfacesResult as a HeaderResult. +func (r ListVirtualInterfacesResult) ExtractHeader() (gophercloud.HeaderResult, error) { + return r.HeaderResult, nil +} diff --git a/openstack/baremetal/v1/nodes/testing/fixtures_test.go b/openstack/baremetal/v1/nodes/testing/fixtures_test.go index 8ab570b54d..84a6cccb96 100644 --- a/openstack/baremetal/v1/nodes/testing/fixtures_test.go +++ b/openstack/baremetal/v1/nodes/testing/fixtures_test.go @@ -87,6 +87,7 @@ const NodeListDetailBody = ` "created_at": "2019-01-31T19:59:28+00:00", "deploy_interface": "iscsi", "deploy_step": {}, + "disable_power_off": false, "driver": "ipmi", "driver_info": { "ipmi_port": "6230", @@ -101,6 +102,7 @@ const NodeListDetailBody = ` "extra": {}, "fault": null, "firmware_interface": "no-firmware", + "health": "OK", "inspect_interface": "no-inspect", "inspection_finished_at": null, "inspection_started_at": null, @@ -149,6 +151,8 @@ const NodeListDetailBody = ` "provision_updated_at": "2019-02-15T17:21:29+00:00", "raid_config": {}, "raid_interface": "no-raid", + "retired": false, + "retired_reason": "No longer needed", "rescue_interface": "no-rescue", "reservation": null, "resource_class": null, @@ -193,6 +197,7 @@ const NodeListDetailBody = ` "created_at": "2019-01-31T19:59:29+00:00", "deploy_interface": "iscsi", "deploy_step": {}, + "disable_power_off": false, "driver": "ipmi", "driver_info": {}, "driver_internal_info": {}, @@ -247,6 +252,8 @@ const NodeListDetailBody = ` "provision_updated_at": null, "raid_config": {}, "raid_interface": "no-raid", + "retired": false, + "retired_reason": "No longer needed", "rescue_interface": "no-rescue", "reservation": null, "resource_class": null, @@ -291,6 +298,7 @@ const NodeListDetailBody = ` "created_at": "2019-01-31T19:59:30+00:00", "deploy_interface": "iscsi", "deploy_step": {}, + "disable_power_off": true, "driver": "ipmi", "driver_info": {}, "driver_internal_info": {}, @@ -345,6 +353,8 @@ const NodeListDetailBody = ` "provision_updated_at": null, "raid_config": {}, "raid_interface": "no-raid", + "retired": false, + "retired_reason": "No longer needed", "rescue_interface": "no-rescue", "reservation": null, "resource_class": null, @@ -408,6 +418,7 @@ const SingleNodeBody = ` "extra": {}, "fault": null, "firmware_interface": "no-firmware", + "health": "OK", "inspect_interface": "no-inspect", "inspection_finished_at": null, "inspection_started_at": null, @@ -456,6 +467,8 @@ const SingleNodeBody = ` "provision_updated_at": "2019-02-15T17:21:29+00:00", "raid_config": {}, "raid_interface": "no-raid", + "retired": false, + "retired_reason": "No longer needed", "rescue_interface": "no-rescue", "reservation": null, "resource_class": null, @@ -855,7 +868,7 @@ const NodeFirmwareListBody = ` { "firmware": [ { - "created_at": "2023-10-03T18:30:00+00:00", + "created_at": "2023-10-03T18:30:00+00:00", "updated_at": null, "component": "bios", "initial_version": "U30 v2.36 (07/16/2020)", @@ -864,10 +877,10 @@ const NodeFirmwareListBody = ` }, { "created_at": "2023-10-03T18:30:00+00:00", - "updated_at": "2023-10-03T18:45:54+00:00", - "component": "bmc", - "initial_version": "iLO 5 v2.78", - "current_version": "iLO 5 v2.81", + "updated_at": "2023-10-03T18:45:54+00:00", + "component": "bmc", + "initial_version": "iLO 5 v2.78", + "current_version": "iLO 5 v2.81", "last_version_flashed": "iLO 5 v2.81" } ] @@ -889,6 +902,28 @@ const NodeVirtualMediaAttachBodyWithSource = ` } ` +const NodeVirtualMediaGetBodyAttached = ` +{ + "image": "https://example.com/image", + "inserted": true, + "media_types": [ + "CD", + "DVD" + ] +} +` + +const NodeVirtualMediaGetBodyNotAttached = ` +{ + "image": "", + "inserted": false, + "media_types": [ + "CD", + "DVD" + ] +} +` + var ( createdAtFoo, _ = time.Parse(time.RFC3339, "2019-01-31T19:59:28+00:00") createdAtBar, _ = time.Parse(time.RFC3339, "2019-01-31T19:59:29+00:00") @@ -906,6 +941,7 @@ var ( Maintenance: false, MaintenanceReason: "", Fault: "", + Health: "OK", LastError: "", Reservation: "", Driver: "ipmi", @@ -917,23 +953,57 @@ var ( "deploy_ramdisk": "http://172.22.0.1/images/tinyipa-stable-rocky.gz", "ipmi_password": "admin", }, - DriverInternalInfo: map[string]any{}, - Properties: map[string]any{}, - InstanceInfo: map[string]any{}, - InstanceUUID: "", - ChassisUUID: "", - Extra: map[string]any{}, - ConsoleEnabled: false, - RAIDConfig: map[string]any{}, - TargetRAIDConfig: map[string]any{}, - CleanStep: map[string]any{}, - DeployStep: map[string]any{}, + DriverInternalInfo: map[string]any{}, + Properties: map[string]any{}, + InstanceInfo: map[string]any{}, + InstanceUUID: "", + ChassisUUID: "", + Extra: map[string]any{}, + ConsoleEnabled: false, + RAIDConfig: map[string]any{}, + TargetRAIDConfig: map[string]any{}, + CleanStep: map[string]any{}, + DeployStep: map[string]any{}, + Links: []nodes.Link{ + { + Href: "http://ironic.example.com:6385/v1/nodes/d2630783-6ec8-4836-b556-ab427c4b581e", + Rel: "self", + }, + { + Href: "http://ironic.example.com:6385/nodes/d2630783-6ec8-4836-b556-ab427c4b581e", + Rel: "bookmark", + }, + }, + Ports: []nodes.Link{ + { + Href: "http://ironic.example.com:6385/v1/nodes/d2630783-6ec8-4836-b556-ab427c4b581e/ports", + Rel: "self", + }, + { + Href: "http://ironic.example.com:6385/nodes/d2630783-6ec8-4836-b556-ab427c4b581e/ports", + Rel: "bookmark", + }, + }, + PortGroups: []nodes.Link{ + { + Href: "http://ironic.example.com:6385/v1/nodes/d2630783-6ec8-4836-b556-ab427c4b581e/portgroups", + Rel: "self", + }, + { + Href: "http://ironic.example.com:6385/nodes/d2630783-6ec8-4836-b556-ab427c4b581e/portgroups", + Rel: "bookmark"}, + }, + States: []nodes.Link{ + { + Href: "http://ironic.example.com:6385/v1/nodes/d2630783-6ec8-4836-b556-ab427c4b581e/states", + Rel: "self", + }, + }, ResourceClass: "", BIOSInterface: "no-bios", BootInterface: "pxe", ConsoleInterface: "no-console", DeployInterface: "iscsi", - FirmwareInterface: "no-firmware", InspectInterface: "no-inspect", ManagementInterface: "ipmitool", NetworkInterface: "flat", @@ -943,12 +1013,33 @@ var ( StorageInterface: "noop", Traits: []string{}, VendorInterface: "ipmitool", - ConductorGroup: "", - Protected: false, - ProtectedReason: "", - CreatedAt: createdAtFoo, - UpdatedAt: updatedAt, - ProvisionUpdatedAt: provisonUpdatedAt, + Volume: []nodes.Link{ + { + Href: "http://ironic.example.com:6385/v1/nodes/d2630783-6ec8-4836-b556-ab427c4b581e/volume", + Rel: "self", + }, + }, + ConductorGroup: "", + ParentNode: "", + Protected: false, + ProtectedReason: "", + Owner: "", + Lessee: "", + Shard: "", + Description: "", + Conductor: "", + AllocationUUID: "", + Retired: false, + RetiredReason: "No longer needed", + NetworkData: map[string]interface{}(nil), + AutomatedClean: nil, + ServiceStep: map[string]interface{}(nil), + FirmwareInterface: "no-firmware", + ProvisionUpdatedAt: provisonUpdatedAt, + InspectionStartedAt: nil, + InspectionFinishedAt: nil, + CreatedAt: createdAtFoo, + UpdatedAt: updatedAt, } NodeFooValidation = nodes.NodeValidation{ @@ -1058,6 +1149,29 @@ var ( UpdatedAt: updatedAt, InspectionStartedAt: &InspectionStartedAt, InspectionFinishedAt: &InspectionFinishedAt, + Retired: false, + RetiredReason: "No longer needed", + DisablePowerOff: false, + Links: []nodes.Link{ + {Href: "http://ironic.example.com:6385/v1/nodes/08c84581-58f5-4ea2-a0c6-dd2e5d2b3662", Rel: "self"}, + {Href: "http://ironic.example.com:6385/nodes/08c84581-58f5-4ea2-a0c6-dd2e5d2b3662", Rel: "bookmark"}, + }, + Ports: []nodes.Link{ + {Href: "http://ironic.example.com:6385/v1/nodes/08c84581-58f5-4ea2-a0c6-dd2e5d2b3662/ports", Rel: "self"}, + {Href: "http://ironic.example.com:6385/nodes/08c84581-58f5-4ea2-a0c6-dd2e5d2b3662/ports", Rel: "bookmark"}, + }, + PortGroups: []nodes.Link{ + {Href: "http://ironic.example.com:6385/v1/nodes/08c84581-58f5-4ea2-a0c6-dd2e5d2b3662/portgroups", Rel: "self"}, + {Href: "http://ironic.example.com:6385/nodes/08c84581-58f5-4ea2-a0c6-dd2e5d2b3662/portgroups", Rel: "bookmark"}, + }, + States: []nodes.Link{ + {Href: "http://ironic.example.com:6385/v1/nodes/08c84581-58f5-4ea2-a0c6-dd2e5d2b3662/states", Rel: "self"}, + {Href: "http://ironic.example.com:6385/nodes/08c84581-58f5-4ea2-a0c6-dd2e5d2b3662/states", Rel: "bookmark"}, + }, + Volume: []nodes.Link{ + {Href: "http://ironic.example.com:6385/v1/nodes/08c84581-58f5-4ea2-a0c6-dd2e5d2b3662/volume", Rel: "self"}, + {Href: "http://ironic.example.com:6385/nodes/08c84581-58f5-4ea2-a0c6-dd2e5d2b3662/volume", Rel: "bookmark"}, + }, } NodeBaz = nodes.Node{ @@ -1105,6 +1219,29 @@ var ( ProtectedReason: "", CreatedAt: createdAtBaz, UpdatedAt: updatedAt, + Retired: false, + RetiredReason: "No longer needed", + DisablePowerOff: true, + Links: []nodes.Link{ + {Href: "http://ironic.example.com:6385/v1/nodes/c9afd385-5d89-4ecb-9e1c-68194da6b474", Rel: "self"}, + {Href: "http://ironic.example.com:6385/nodes/c9afd385-5d89-4ecb-9e1c-68194da6b474", Rel: "bookmark"}, + }, + Ports: []nodes.Link{ + {Href: "http://ironic.example.com:6385/v1/nodes/c9afd385-5d89-4ecb-9e1c-68194da6b474/ports", Rel: "self"}, + {Href: "http://ironic.example.com:6385/nodes/c9afd385-5d89-4ecb-9e1c-68194da6b474/ports", Rel: "bookmark"}, + }, + PortGroups: []nodes.Link{ + {Href: "http://ironic.example.com:6385/v1/nodes/c9afd385-5d89-4ecb-9e1c-68194da6b474/portgroups", Rel: "self"}, + {Href: "http://ironic.example.com:6385/nodes/c9afd385-5d89-4ecb-9e1c-68194da6b474/portgroups", Rel: "bookmark"}, + }, + States: []nodes.Link{ + {Href: "http://ironic.example.com:6385/v1/nodes/c9afd385-5d89-4ecb-9e1c-68194da6b474/states", Rel: "self"}, + {Href: "http://ironic.example.com:6385/nodes/c9afd385-5d89-4ecb-9e1c-68194da6b474/states", Rel: "bookmark"}, + }, + Volume: []nodes.Link{ + {Href: "http://ironic.example.com:6385/v1/nodes/c9afd385-5d89-4ecb-9e1c-68194da6b474/volume", Rel: "self"}, + {Href: "http://ironic.example.com:6385/nodes/c9afd385-5d89-4ecb-9e1c-68194da6b474/volume", Rel: "bookmark"}, + }, } ConfigDriveMap = nodes.ConfigDrive{ @@ -1285,8 +1422,8 @@ var ( ) // HandleNodeListSuccessfully sets up the test server to respond to a server List request. -func HandleNodeListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/nodes", func(w http.ResponseWriter, r *http.Request) { +func HandleNodeListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") @@ -1297,10 +1434,10 @@ func HandleNodeListSuccessfully(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, NodeListBody) + fmt.Fprint(w, NodeListBody) case "9e5476bd-a4ec-4653-93d6-72c93aa682ba": - fmt.Fprintf(w, `{ "servers": [] }`) + fmt.Fprint(w, `{ "servers": [] }`) default: t.Fatalf("/nodes invoked with unexpected marker=[%s]", marker) } @@ -1308,8 +1445,8 @@ func HandleNodeListSuccessfully(t *testing.T) { } // HandleNodeListSuccessfully sets up the test server to respond to a server List request. -func HandleNodeListDetailSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/nodes/detail", func(w http.ResponseWriter, r *http.Request) { +func HandleNodeListDetailSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") @@ -1317,14 +1454,14 @@ func HandleNodeListDetailSuccessfully(t *testing.T) { t.Errorf("Failed to parse request form %v", err) } - fmt.Fprintf(w, NodeListDetailBody) + fmt.Fprint(w, NodeListDetailBody) }) } // HandleServerCreationSuccessfully sets up the test server to respond to a server creation request // with a given response. -func HandleNodeCreationSuccessfully(t *testing.T, response string) { - th.Mux.HandleFunc("/nodes", func(w http.ResponseWriter, r *http.Request) { +func HandleNodeCreationSuccessfully(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/nodes", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -1344,13 +1481,13 @@ func HandleNodeCreationSuccessfully(t *testing.T, response string) { w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) } // HandleNodeDeletionSuccessfully sets up the test server to respond to a server deletion request. -func HandleNodeDeletionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/nodes/asdfasdfasdf", func(w http.ResponseWriter, r *http.Request) { +func HandleNodeDeletionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/asdfasdfasdf", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -1358,41 +1495,41 @@ func HandleNodeDeletionSuccessfully(t *testing.T) { }) } -func HandleNodeGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf", func(w http.ResponseWriter, r *http.Request) { +func HandleNodeGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleNodeBody) + fmt.Fprint(w, SingleNodeBody) }) } -func HandleNodeUpdateSuccessfully(t *testing.T, response string) { - th.Mux.HandleFunc("/nodes/1234asdf", func(w http.ResponseWriter, r *http.Request) { +func HandleNodeUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "Content-Type", "application/json") th.TestJSONRequest(t, r, `[{"op": "replace", "path": "/properties", "value": {"root_gb": 25}}]`) - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) } -func HandleNodeValidateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/validate", func(w http.ResponseWriter, r *http.Request) { +func HandleNodeValidateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/validate", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, NodeValidationBody) + fmt.Fprint(w, NodeValidationBody) }) } // HandleInjectNMISuccessfully sets up the test server to respond to a node InjectNMI request -func HandleInjectNMISuccessfully(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/management/inject_nmi", func(w http.ResponseWriter, r *http.Request) { +func HandleInjectNMISuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/management/inject_nmi", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, "{}") @@ -1402,8 +1539,8 @@ func HandleInjectNMISuccessfully(t *testing.T) { } // HandleSetBootDeviceSuccessfully sets up the test server to respond to a set boot device request for a node -func HandleSetBootDeviceSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/management/boot_device", func(w http.ResponseWriter, r *http.Request) { +func HandleSetBootDeviceSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/management/boot_device", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, NodeBootDeviceBody) @@ -1413,27 +1550,27 @@ func HandleSetBootDeviceSuccessfully(t *testing.T) { } // HandleGetBootDeviceSuccessfully sets up the test server to respond to a get boot device request for a node -func HandleGetBootDeviceSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/management/boot_device", func(w http.ResponseWriter, r *http.Request) { +func HandleGetBootDeviceSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/management/boot_device", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, NodeBootDeviceBody) + fmt.Fprint(w, NodeBootDeviceBody) }) } // HandleGetBootDeviceSuccessfully sets up the test server to respond to a get boot device request for a node -func HandleGetSupportedBootDeviceSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/management/boot_device/supported", func(w http.ResponseWriter, r *http.Request) { +func HandleGetSupportedBootDeviceSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/management/boot_device/supported", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, NodeSupportedBootDeviceBody) + fmt.Fprint(w, NodeSupportedBootDeviceBody) }) } -func HandleNodeChangeProvisionStateActive(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/states/provision", func(w http.ResponseWriter, r *http.Request) { +func HandleNodeChangeProvisionStateActive(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/states/provision", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, NodeProvisionStateActiveBody) @@ -1441,8 +1578,8 @@ func HandleNodeChangeProvisionStateActive(t *testing.T) { }) } -func HandleNodeChangeProvisionStateActiveWithSteps(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/states/provision", func(w http.ResponseWriter, r *http.Request) { +func HandleNodeChangeProvisionStateActiveWithSteps(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/states/provision", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, NodeProvisionStateActiveBodyWithSteps) @@ -1450,8 +1587,8 @@ func HandleNodeChangeProvisionStateActiveWithSteps(t *testing.T) { }) } -func HandleNodeChangeProvisionStateClean(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/states/provision", func(w http.ResponseWriter, r *http.Request) { +func HandleNodeChangeProvisionStateClean(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/states/provision", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, NodeProvisionStateCleanBody) @@ -1459,8 +1596,8 @@ func HandleNodeChangeProvisionStateClean(t *testing.T) { }) } -func HandleNodeChangeProvisionStateCleanWithConflict(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/states/provision", func(w http.ResponseWriter, r *http.Request) { +func HandleNodeChangeProvisionStateCleanWithConflict(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/states/provision", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, NodeProvisionStateCleanBody) @@ -1468,8 +1605,8 @@ func HandleNodeChangeProvisionStateCleanWithConflict(t *testing.T) { }) } -func HandleNodeChangeProvisionStateConfigDrive(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/states/provision", func(w http.ResponseWriter, r *http.Request) { +func HandleNodeChangeProvisionStateConfigDrive(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/states/provision", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, NodeProvisionStateConfigDriveBody) @@ -1477,8 +1614,8 @@ func HandleNodeChangeProvisionStateConfigDrive(t *testing.T) { }) } -func HandleNodeChangeProvisionStateService(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/states/provision", func(w http.ResponseWriter, r *http.Request) { +func HandleNodeChangeProvisionStateService(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/states/provision", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, NodeProvisionStateServiceBody) @@ -1487,8 +1624,8 @@ func HandleNodeChangeProvisionStateService(t *testing.T) { } // HandleChangePowerStateSuccessfully sets up the test server to respond to a change power state request for a node -func HandleChangePowerStateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/states/power", func(w http.ResponseWriter, r *http.Request) { +func HandleChangePowerStateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/states/power", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -1501,8 +1638,8 @@ func HandleChangePowerStateSuccessfully(t *testing.T) { } // HandleChangePowerStateWithConflict sets up the test server to respond to a change power state request for a node with a 409 error -func HandleChangePowerStateWithConflict(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/states/power", func(w http.ResponseWriter, r *http.Request) { +func HandleChangePowerStateWithConflict(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/states/power", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -1514,8 +1651,8 @@ func HandleChangePowerStateWithConflict(t *testing.T) { }) } -func HandleSetRAIDConfig(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/states/raid", func(w http.ResponseWriter, r *http.Request) { +func HandleSetRAIDConfig(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/states/raid", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, ` @@ -1534,8 +1671,8 @@ func HandleSetRAIDConfig(t *testing.T) { }) } -func HandleSetRAIDConfigMaxSize(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/states/raid", func(w http.ResponseWriter, r *http.Request) { +func HandleSetRAIDConfigMaxSize(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/states/raid", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, ` @@ -1554,59 +1691,59 @@ func HandleSetRAIDConfigMaxSize(t *testing.T) { }) } -func HandleListBIOSSettingsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/bios", func(w http.ResponseWriter, r *http.Request) { +func HandleListBIOSSettingsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/bios", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, NodeBIOSSettingsBody) + fmt.Fprint(w, NodeBIOSSettingsBody) }) } -func HandleListDetailBIOSSettingsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/bios", func(w http.ResponseWriter, r *http.Request) { +func HandleListDetailBIOSSettingsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/bios", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, NodeDetailBIOSSettingsBody) + fmt.Fprint(w, NodeDetailBIOSSettingsBody) }) } -func HandleGetBIOSSettingSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/bios/ProcVirtualization", func(w http.ResponseWriter, r *http.Request) { +func HandleGetBIOSSettingSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/bios/ProcVirtualization", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, NodeSingleBIOSSettingBody) + fmt.Fprint(w, NodeSingleBIOSSettingBody) }) } -func HandleGetVendorPassthruMethodsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/vendor_passthru/methods", func(w http.ResponseWriter, r *http.Request) { +func HandleGetVendorPassthruMethodsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/vendor_passthru/methods", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, NodeVendorPassthruMethodsBody) + fmt.Fprint(w, NodeVendorPassthruMethodsBody) }) } -func HandleGetAllSubscriptionsVendorPassthruSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/vendor_passthru", func(w http.ResponseWriter, r *http.Request) { +func HandleGetAllSubscriptionsVendorPassthruSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/vendor_passthru", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") th.TestFormValues(t, r, map[string]string{"method": "get_all_subscriptions"}) - fmt.Fprintf(w, NodeGetAllSubscriptionsVnedorPassthruBody) + fmt.Fprint(w, NodeGetAllSubscriptionsVnedorPassthruBody) }) } -func HandleGetSubscriptionVendorPassthruSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/vendor_passthru", func(w http.ResponseWriter, r *http.Request) { +func HandleGetSubscriptionVendorPassthruSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/vendor_passthru", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") @@ -1617,12 +1754,12 @@ func HandleGetSubscriptionVendorPassthruSuccessfully(t *testing.T) { } `) - fmt.Fprintf(w, NodeGetSubscriptionVendorPassthruBody) + fmt.Fprint(w, NodeGetSubscriptionVendorPassthruBody) }) } -func HandleCreateSubscriptionVendorPassthruAllParametersSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/vendor_passthru", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateSubscriptionVendorPassthruAllParametersSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/vendor_passthru", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") @@ -1637,12 +1774,12 @@ func HandleCreateSubscriptionVendorPassthruAllParametersSuccessfully(t *testing. } `) - fmt.Fprintf(w, NodeCreateSubscriptionVendorPassthruAllParametersBody) + fmt.Fprint(w, NodeCreateSubscriptionVendorPassthruAllParametersBody) }) } -func HandleCreateSubscriptionVendorPassthruRequiredParametersSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/vendor_passthru", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateSubscriptionVendorPassthruRequiredParametersSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/vendor_passthru", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") @@ -1653,12 +1790,12 @@ func HandleCreateSubscriptionVendorPassthruRequiredParametersSuccessfully(t *tes } `) - fmt.Fprintf(w, NodeCreateSubscriptionVendorPassthruRequiredParametersBody) + fmt.Fprint(w, NodeCreateSubscriptionVendorPassthruRequiredParametersBody) }) } -func HandleDeleteSubscriptionVendorPassthruSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/vendor_passthru", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteSubscriptionVendorPassthruSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/vendor_passthru", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") @@ -1673,8 +1810,8 @@ func HandleDeleteSubscriptionVendorPassthruSuccessfully(t *testing.T) { }) } -func HandleSetNodeMaintenanceSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/maintenance", func(w http.ResponseWriter, r *http.Request) { +func HandleSetNodeMaintenanceSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/maintenance", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, NodeSetMaintenanceBody) @@ -1683,8 +1820,8 @@ func HandleSetNodeMaintenanceSuccessfully(t *testing.T) { }) } -func HandleUnsetNodeMaintenanceSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/maintenance", func(w http.ResponseWriter, r *http.Request) { +func HandleUnsetNodeMaintenanceSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/maintenance", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -1693,27 +1830,27 @@ func HandleUnsetNodeMaintenanceSuccessfully(t *testing.T) { } // HandleGetInventorySuccessfully sets up the test server to respond to a get inventory request for a node -func HandleGetInventorySuccessfully(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/inventory", func(w http.ResponseWriter, r *http.Request) { +func HandleGetInventorySuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/inventory", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, NodeInventoryBody) + fmt.Fprint(w, NodeInventoryBody) }) } // HandleListFirmware -func HandleListFirmwareSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/nodes/1234asdf/firmware", func(w http.ResponseWriter, r *http.Request) { +func HandleListFirmwareSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/firmware", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, NodeFirmwareListBody) + fmt.Fprint(w, NodeFirmwareListBody) }) } -func HandleAttachVirtualMediaSuccessfully(t *testing.T, withSource bool) { - th.Mux.HandleFunc("/nodes/1234asdf/vmedia", func(w http.ResponseWriter, r *http.Request) { +func HandleAttachVirtualMediaSuccessfully(t *testing.T, fakeServer th.FakeServer, withSource bool) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/vmedia", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) if withSource { @@ -1725,8 +1862,8 @@ func HandleAttachVirtualMediaSuccessfully(t *testing.T, withSource bool) { }) } -func HandleDetachVirtualMediaSuccessfully(t *testing.T, withType bool) { - th.Mux.HandleFunc("/nodes/1234asdf/vmedia", func(w http.ResponseWriter, r *http.Request) { +func HandleDetachVirtualMediaSuccessfully(t *testing.T, fakeServer th.FakeServer, withType bool) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/vmedia", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) if withType { @@ -1737,3 +1874,91 @@ func HandleDetachVirtualMediaSuccessfully(t *testing.T, withType bool) { w.WriteHeader(http.StatusNoContent) }) } + +func HandleGetVirtualMediaSuccessfully(t *testing.T, fakeServer th.FakeServer, attached bool) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/vmedia", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + w.WriteHeader(http.StatusOK) + if attached { + fmt.Fprint(w, NodeVirtualMediaGetBodyAttached) + } else { + fmt.Fprint(w, NodeVirtualMediaGetBodyNotAttached) + } + }) +} + +// HandleListVirtualInterfacesSuccessfully sets up the test server to respond to a ListVirtualInterfaces request +func HandleListVirtualInterfacesSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/vifs", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ` +{ + "vifs": [ + { + "id": "1974dcfa-836f-41b2-b541-686c100900e5" + } + ] +}`) + }) +} + +// HandleAttachVirtualInterfaceSuccessfully sets up the test server to respond to an AttachVirtualInterface request +func HandleAttachVirtualInterfaceSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/vifs", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, `{"id":"1974dcfa-836f-41b2-b541-686c100900e5"}`) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleAttachVirtualInterfaceWithPortSuccessfully sets up the test server to respond to an AttachVirtualInterface request with port +func HandleAttachVirtualInterfaceWithPortSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/vifs", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, `{"id":"1974dcfa-836f-41b2-b541-686c100900e5","port_uuid":"b2f96298-5172-45e9-b174-8d1ba936ab47"}`) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleAttachVirtualInterfaceWithPortgroupSuccessfully sets up the test server to respond to an AttachVirtualInterface request with portgroup +func HandleAttachVirtualInterfaceWithPortgroupSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/vifs", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, `{"id":"1974dcfa-836f-41b2-b541-686c100900e5","portgroup_uuid":"c24944b5-a52e-4c5c-9c0a-52a0235a08a2"}`) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleDetachVirtualInterfaceSuccessfully sets up the test server to respond to a DetachVirtualInterface request +func HandleDetachVirtualInterfaceSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/nodes/1234asdf/vifs/1974dcfa-836f-41b2-b541-686c100900e5", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + w.WriteHeader(http.StatusNoContent) + }) +} diff --git a/openstack/baremetal/v1/nodes/testing/requests_test.go b/openstack/baremetal/v1/nodes/testing/requests_test.go index 5529e795ad..be1ce0ed64 100644 --- a/openstack/baremetal/v1/nodes/testing/requests_test.go +++ b/openstack/baremetal/v1/nodes/testing/requests_test.go @@ -13,12 +13,12 @@ import ( ) func TestListDetailNodes(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleNodeListDetailSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleNodeListDetailSuccessfully(t, fakeServer) pages := 0 - err := nodes.ListDetail(client.ServiceClient(), nodes.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := nodes.ListDetail(client.ServiceClient(fakeServer), nodes.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := nodes.ExtractNodes(page) @@ -44,12 +44,12 @@ func TestListDetailNodes(t *testing.T) { } func TestListNodes(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleNodeListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleNodeListSuccessfully(t, fakeServer) pages := 0 - err := nodes.List(client.ServiceClient(), nodes.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := nodes.List(client.ServiceClient(fakeServer), nodes.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := nodes.ExtractNodes(page) @@ -81,7 +81,7 @@ func TestListOpts(t *testing.T) { } _, err := opts.ToNodeListDetailQuery() - th.AssertEquals(t, err.Error(), "fields is not a valid option when getting a detailed listing of nodes") + th.AssertEquals(t, "fields is not a valid option when getting a detailed listing of nodes", err.Error()) // Regular ListOpts can query, err := opts.ToNodeListQuery() @@ -90,11 +90,11 @@ func TestListOpts(t *testing.T) { } func TestCreateNode(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleNodeCreationSuccessfully(t, SingleNodeBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleNodeCreationSuccessfully(t, fakeServer, SingleNodeBody) - actual, err := nodes.Create(context.TODO(), client.ServiceClient(), nodes.CreateOpts{ + actual, err := nodes.Create(context.TODO(), client.ServiceClient(fakeServer), nodes.CreateOpts{ Name: "foo", Driver: "ipmi", BootInterface: "pxe", @@ -114,20 +114,20 @@ func TestCreateNode(t *testing.T) { } func TestDeleteNode(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleNodeDeletionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleNodeDeletionSuccessfully(t, fakeServer) - res := nodes.Delete(context.TODO(), client.ServiceClient(), "asdfasdfasdf") + res := nodes.Delete(context.TODO(), client.ServiceClient(fakeServer), "asdfasdfasdf") th.AssertNoErr(t, res.Err) } func TestGetNode(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleNodeGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleNodeGetSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) actual, err := nodes.Get(context.TODO(), c, "1234asdf").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) @@ -137,11 +137,11 @@ func TestGetNode(t *testing.T) { } func TestUpdateNode(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleNodeUpdateSuccessfully(t, SingleNodeBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleNodeUpdateSuccessfully(t, fakeServer, SingleNodeBody) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) actual, err := nodes.Update(context.TODO(), c, "1234asdf", nodes.UpdateOpts{ nodes.UpdateOperation{ Op: nodes.ReplaceOp, @@ -159,7 +159,10 @@ func TestUpdateNode(t *testing.T) { } func TestUpdateRequiredOp(t *testing.T) { - c := client.ServiceClient() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + c := client.ServiceClient(fakeServer) _, err := nodes.Update(context.TODO(), c, "1234asdf", nodes.UpdateOpts{ nodes.UpdateOperation{ Path: "/driver", @@ -174,7 +177,10 @@ func TestUpdateRequiredOp(t *testing.T) { } func TestUpdateRequiredPath(t *testing.T) { - c := client.ServiceClient() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + c := client.ServiceClient(fakeServer) _, err := nodes.Update(context.TODO(), c, "1234asdf", nodes.UpdateOpts{ nodes.UpdateOperation{ Op: nodes.ReplaceOp, @@ -188,32 +194,32 @@ func TestUpdateRequiredPath(t *testing.T) { } func TestValidateNode(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleNodeValidateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleNodeValidateSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) actual, err := nodes.Validate(context.TODO(), c, "1234asdf").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, NodeFooValidation, *actual) } func TestInjectNMI(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleInjectNMISuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleInjectNMISuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) err := nodes.InjectNMI(context.TODO(), c, "1234asdf").ExtractErr() th.AssertNoErr(t, err) } func TestSetBootDevice(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleSetBootDeviceSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleSetBootDeviceSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) err := nodes.SetBootDevice(context.TODO(), c, "1234asdf", nodes.BootDeviceOpts{ BootDevice: "pxe", Persistent: false, @@ -222,33 +228,33 @@ func TestSetBootDevice(t *testing.T) { } func TestGetBootDevice(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetBootDeviceSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetBootDeviceSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) bootDevice, err := nodes.GetBootDevice(context.TODO(), c, "1234asdf").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, NodeBootDevice, *bootDevice) } func TestGetSupportedBootDevices(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSupportedBootDeviceSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSupportedBootDeviceSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) bootDevices, err := nodes.GetSupportedBootDevices(context.TODO(), c, "1234asdf").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, NodeSupportedBootDevice, bootDevices) } func TestNodeChangeProvisionStateActive(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleNodeChangeProvisionStateActive(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleNodeChangeProvisionStateActive(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) err := nodes.ChangeProvisionState(context.TODO(), c, "1234asdf", nodes.ProvisionStateOpts{ Target: nodes.TargetActive, ConfigDrive: "http://127.0.0.1/images/test-node-config-drive.iso.gz", @@ -258,11 +264,11 @@ func TestNodeChangeProvisionStateActive(t *testing.T) { } func TestNodeChangeProvisionStateActiveWithSteps(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleNodeChangeProvisionStateActiveWithSteps(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleNodeChangeProvisionStateActiveWithSteps(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) err := nodes.ChangeProvisionState(context.TODO(), c, "1234asdf", nodes.ProvisionStateOpts{ Target: nodes.TargetActive, DeploySteps: []nodes.DeployStep{ @@ -281,12 +287,11 @@ func TestNodeChangeProvisionStateActiveWithSteps(t *testing.T) { } func TestHandleNodeChangeProvisionStateConfigDrive(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - HandleNodeChangeProvisionStateConfigDrive(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleNodeChangeProvisionStateConfigDrive(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) err := nodes.ChangeProvisionState(context.TODO(), c, "1234asdf", nodes.ProvisionStateOpts{ Target: nodes.TargetActive, @@ -297,11 +302,11 @@ func TestHandleNodeChangeProvisionStateConfigDrive(t *testing.T) { } func TestNodeChangeProvisionStateClean(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleNodeChangeProvisionStateClean(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleNodeChangeProvisionStateClean(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) err := nodes.ChangeProvisionState(context.TODO(), c, "1234asdf", nodes.ProvisionStateOpts{ Target: nodes.TargetClean, CleanSteps: []nodes.CleanStep{ @@ -319,11 +324,11 @@ func TestNodeChangeProvisionStateClean(t *testing.T) { } func TestNodeChangeProvisionStateCleanWithConflict(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleNodeChangeProvisionStateCleanWithConflict(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleNodeChangeProvisionStateCleanWithConflict(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) err := nodes.ChangeProvisionState(context.TODO(), c, "1234asdf", nodes.ProvisionStateOpts{ Target: nodes.TargetClean, CleanSteps: []nodes.CleanStep{ @@ -343,7 +348,10 @@ func TestNodeChangeProvisionStateCleanWithConflict(t *testing.T) { } func TestCleanStepRequiresInterface(t *testing.T) { - c := client.ServiceClient() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + c := client.ServiceClient(fakeServer) err := nodes.ChangeProvisionState(context.TODO(), c, "1234asdf", nodes.ProvisionStateOpts{ Target: nodes.TargetClean, CleanSteps: []nodes.CleanStep{ @@ -362,7 +370,10 @@ func TestCleanStepRequiresInterface(t *testing.T) { } func TestCleanStepRequiresStep(t *testing.T) { - c := client.ServiceClient() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + c := client.ServiceClient(fakeServer) err := nodes.ChangeProvisionState(context.TODO(), c, "1234asdf", nodes.ProvisionStateOpts{ Target: nodes.TargetClean, CleanSteps: []nodes.CleanStep{ @@ -381,11 +392,11 @@ func TestCleanStepRequiresStep(t *testing.T) { } func TestNodeChangeProvisionStateService(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleNodeChangeProvisionStateService(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleNodeChangeProvisionStateService(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) err := nodes.ChangeProvisionState(context.TODO(), c, "1234asdf", nodes.ProvisionStateOpts{ Target: nodes.TargetService, ServiceSteps: []nodes.ServiceStep{ @@ -403,31 +414,31 @@ func TestNodeChangeProvisionStateService(t *testing.T) { } func TestChangePowerState(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleChangePowerStateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleChangePowerStateSuccessfully(t, fakeServer) opts := nodes.PowerStateOpts{ Target: nodes.PowerOn, Timeout: 100, } - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) err := nodes.ChangePowerState(context.TODO(), c, "1234asdf", opts).ExtractErr() th.AssertNoErr(t, err) } func TestChangePowerStateWithConflict(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleChangePowerStateWithConflict(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleChangePowerStateWithConflict(t, fakeServer) opts := nodes.PowerStateOpts{ Target: nodes.PowerOn, Timeout: 100, } - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) err := nodes.ChangePowerState(context.TODO(), c, "1234asdf", opts).ExtractErr() if !gophercloud.ResponseCodeIs(err, http.StatusConflict) { t.Fatalf("expected 409 response, but got %s", err.Error()) @@ -435,9 +446,9 @@ func TestChangePowerStateWithConflict(t *testing.T) { } func TestSetRAIDConfig(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleSetRAIDConfig(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleSetRAIDConfig(t, fakeServer) sizeGB := 100 isRootVolume := true @@ -452,16 +463,16 @@ func TestSetRAIDConfig(t *testing.T) { }, } - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) err := nodes.SetRAIDConfig(context.TODO(), c, "1234asdf", config).ExtractErr() th.AssertNoErr(t, err) } // Without specifying a size, we need to send a string: "MAX" func TestSetRAIDConfigMaxSize(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleSetRAIDConfigMaxSize(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleSetRAIDConfigMaxSize(t, fakeServer) isRootVolume := true @@ -474,7 +485,7 @@ func TestSetRAIDConfigMaxSize(t *testing.T) { }, } - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) err := nodes.SetRAIDConfig(context.TODO(), c, "1234asdf", config).ExtractErr() th.AssertNoErr(t, err) } @@ -572,37 +583,37 @@ func TestToRAIDConfigMap(t *testing.T) { } func TestListBIOSSettings(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListBIOSSettingsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListBIOSSettingsSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) actual, err := nodes.ListBIOSSettings(context.TODO(), c, "1234asdf", nil).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, NodeBIOSSettings, actual) } func TestListDetailBIOSSettings(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListDetailBIOSSettingsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListDetailBIOSSettingsSuccessfully(t, fakeServer) opts := nodes.ListBIOSSettingsOpts{ Detail: true, } - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) actual, err := nodes.ListBIOSSettings(context.TODO(), c, "1234asdf", opts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, NodeDetailBIOSSettings, actual) } func TestGetBIOSSetting(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetBIOSSettingSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetBIOSSettingSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) actual, err := nodes.GetBIOSSetting(context.TODO(), c, "1234asdf", "ProcVirtualization").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, NodeSingleBIOSSetting, *actual) @@ -616,26 +627,26 @@ func TestListBIOSSettingsOpts(t *testing.T) { } _, err := opts.ToListBIOSSettingsOptsQuery() - th.AssertEquals(t, err.Error(), "cannot have both fields and detail options for BIOS settings") + th.AssertEquals(t, "cannot have both fields and detail options for BIOS settings", err.Error()) } func TestGetVendorPassthruMethods(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetVendorPassthruMethodsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetVendorPassthruMethodsSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) actual, err := nodes.GetVendorPassthruMethods(context.TODO(), c, "1234asdf").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, NodeVendorPassthruMethods, *actual) } func TestGetAllSubscriptions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetAllSubscriptionsVendorPassthruSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetAllSubscriptionsVendorPassthruSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) method := nodes.CallVendorPassthruOpts{ Method: "get_all_subscriptions", } @@ -645,11 +656,11 @@ func TestGetAllSubscriptions(t *testing.T) { } func TestGetSubscription(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSubscriptionVendorPassthruSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSubscriptionVendorPassthruSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) method := nodes.CallVendorPassthruOpts{ Method: "get_subscription", } @@ -662,11 +673,11 @@ func TestGetSubscription(t *testing.T) { } func TestCreateSubscriptionAllParameters(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateSubscriptionVendorPassthruAllParametersSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateSubscriptionVendorPassthruAllParametersSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) method := nodes.CallVendorPassthruOpts{ Method: "create_subscription", } @@ -683,11 +694,11 @@ func TestCreateSubscriptionAllParameters(t *testing.T) { } func TestCreateSubscriptionWithRequiredParameters(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateSubscriptionVendorPassthruRequiredParametersSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateSubscriptionVendorPassthruRequiredParametersSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) method := nodes.CallVendorPassthruOpts{ Method: "create_subscription", } @@ -700,11 +711,11 @@ func TestCreateSubscriptionWithRequiredParameters(t *testing.T) { } func TestDeleteSubscription(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteSubscriptionVendorPassthruSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteSubscriptionVendorPassthruSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) method := nodes.CallVendorPassthruOpts{ Method: "delete_subscription", } @@ -716,11 +727,11 @@ func TestDeleteSubscription(t *testing.T) { } func TestSetMaintenance(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleSetNodeMaintenanceSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleSetNodeMaintenanceSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) err := nodes.SetMaintenance(context.TODO(), c, "1234asdf", nodes.MaintenanceOpts{ Reason: "I'm tired", }).ExtractErr() @@ -728,21 +739,21 @@ func TestSetMaintenance(t *testing.T) { } func TestUnsetMaintenance(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUnsetNodeMaintenanceSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUnsetNodeMaintenanceSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) err := nodes.UnsetMaintenance(context.TODO(), c, "1234asdf").ExtractErr() th.AssertNoErr(t, err) } func TestGetInventory(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetInventorySuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetInventorySuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) actual, err := nodes.GetInventory(context.TODO(), c, "1234asdf").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, NodeInventoryData.Inventory, actual.Inventory) @@ -757,11 +768,11 @@ func TestGetInventory(t *testing.T) { } func TestListFirmware(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListFirmwareSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListFirmwareSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) actual, err := nodes.ListFirmware(context.TODO(), c, "1234asdf").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, NodeFirmwareList, actual) @@ -779,11 +790,11 @@ func TestVirtualMediaOpts(t *testing.T) { } func TestVirtualMediaAttach(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAttachVirtualMediaSuccessfully(t, false) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAttachVirtualMediaSuccessfully(t, fakeServer, false) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) opts := nodes.AttachVirtualMediaOpts{ ImageURL: "https://example.com/image", DeviceType: nodes.VirtualMediaCD, @@ -793,11 +804,11 @@ func TestVirtualMediaAttach(t *testing.T) { } func TestVirtualMediaAttachWithSource(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAttachVirtualMediaSuccessfully(t, true) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAttachVirtualMediaSuccessfully(t, fakeServer, true) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) opts := nodes.AttachVirtualMediaOpts{ ImageURL: "https://example.com/image", DeviceType: nodes.VirtualMediaCD, @@ -808,24 +819,124 @@ func TestVirtualMediaAttachWithSource(t *testing.T) { } func TestVirtualMediaDetach(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDetachVirtualMediaSuccessfully(t, false) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDetachVirtualMediaSuccessfully(t, fakeServer, false) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) err := nodes.DetachVirtualMedia(context.TODO(), c, "1234asdf", nodes.DetachVirtualMediaOpts{}).ExtractErr() th.AssertNoErr(t, err) } func TestVirtualMediaDetachWithTypes(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDetachVirtualMediaSuccessfully(t, true) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDetachVirtualMediaSuccessfully(t, fakeServer, true) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) opts := nodes.DetachVirtualMediaOpts{ DeviceTypes: []nodes.VirtualMediaDeviceType{nodes.VirtualMediaCD}, } err := nodes.DetachVirtualMedia(context.TODO(), c, "1234asdf", opts).ExtractErr() th.AssertNoErr(t, err) } + +func TestVirtualMediaGetAttached(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetVirtualMediaSuccessfully(t, fakeServer, true) + + c := client.ServiceClient(fakeServer) + err := nodes.GetVirtualMedia(context.TODO(), c, "1234asdf").Err + th.AssertNoErr(t, err) +} + +func TestVirtualMediaGetNotAttached(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetVirtualMediaSuccessfully(t, fakeServer, false) + + c := client.ServiceClient(fakeServer) + err := nodes.GetVirtualMedia(context.TODO(), c, "1234asdf").Err + th.AssertNoErr(t, err) +} + +func TestListVirtualInterfaces(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListVirtualInterfacesSuccessfully(t, fakeServer) + + c := client.ServiceClient(fakeServer) + actual, err := nodes.ListVirtualInterfaces(context.TODO(), c, "1234asdf").Extract() + th.AssertNoErr(t, err) + + expected := []nodes.VIF{ + { + ID: "1974dcfa-836f-41b2-b541-686c100900e5", + }, + } + + th.CheckDeepEquals(t, expected, actual) +} + +func TestAttachVirtualInterface(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAttachVirtualInterfaceSuccessfully(t, fakeServer) + + c := client.ServiceClient(fakeServer) + opts := nodes.VirtualInterfaceOpts{ + ID: "1974dcfa-836f-41b2-b541-686c100900e5", + } + err := nodes.AttachVirtualInterface(context.TODO(), c, "1234asdf", opts).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestAttachVirtualInterfaceWithPort(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAttachVirtualInterfaceWithPortSuccessfully(t, fakeServer) + + c := client.ServiceClient(fakeServer) + opts := nodes.VirtualInterfaceOpts{ + ID: "1974dcfa-836f-41b2-b541-686c100900e5", + PortUUID: "b2f96298-5172-45e9-b174-8d1ba936ab47", + } + err := nodes.AttachVirtualInterface(context.TODO(), c, "1234asdf", opts).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestAttachVirtualInterfaceWithPortgroup(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAttachVirtualInterfaceWithPortgroupSuccessfully(t, fakeServer) + + c := client.ServiceClient(fakeServer) + opts := nodes.VirtualInterfaceOpts{ + ID: "1974dcfa-836f-41b2-b541-686c100900e5", + PortgroupUUID: "c24944b5-a52e-4c5c-9c0a-52a0235a08a2", + } + err := nodes.AttachVirtualInterface(context.TODO(), c, "1234asdf", opts).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestDetachVirtualInterface(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDetachVirtualInterfaceSuccessfully(t, fakeServer) + + c := client.ServiceClient(fakeServer) + err := nodes.DetachVirtualInterface(context.TODO(), c, "1234asdf", "1974dcfa-836f-41b2-b541-686c100900e5").ExtractErr() + th.AssertNoErr(t, err) +} + +func TestVirtualInterfaceOptsValidation(t *testing.T) { + opts := nodes.VirtualInterfaceOpts{ + ID: "1974dcfa-836f-41b2-b541-686c100900e5", + PortUUID: "b2f96298-5172-45e9-b174-8d1ba936ab47", + PortgroupUUID: "c24944b5-a52e-4c5c-9c0a-52a0235a08a2", + } + + _, err := opts.ToVirtualInterfaceMap() + th.AssertEquals(t, "cannot specify both port_uuid and portgroup_uuid", err.Error()) +} diff --git a/openstack/baremetal/v1/nodes/testing/results_test.go b/openstack/baremetal/v1/nodes/testing/results_test.go index 7c683ebecb..f7b99d827f 100644 --- a/openstack/baremetal/v1/nodes/testing/results_test.go +++ b/openstack/baremetal/v1/nodes/testing/results_test.go @@ -8,68 +8,68 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/baremetal/v1/nodes" "github.com/gophercloud/gophercloud/v2/openstack/baremetalintrospection/v1/introspection" insptest "github.com/gophercloud/gophercloud/v2/openstack/baremetalintrospection/v1/introspection/testing" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" ) func TestStandardPluginData(t *testing.T) { var pluginData nodes.PluginData - err := pluginData.RawMessage.UnmarshalJSON([]byte(invtest.StandardPluginDataSample)) - testhelper.AssertNoErr(t, err) + err := pluginData.UnmarshalJSON([]byte(invtest.StandardPluginDataSample)) + th.AssertNoErr(t, err) parsedData, err := pluginData.AsStandardData() - testhelper.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, invtest.StandardPluginData, parsedData) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, invtest.StandardPluginData, parsedData) irData, inspData, err := pluginData.GuessFormat() - testhelper.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, invtest.StandardPluginData, *irData) - testhelper.CheckEquals(t, (*introspection.Data)(nil), inspData) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, invtest.StandardPluginData, *irData) + th.CheckEquals(t, (*introspection.Data)(nil), inspData) } func TestInspectorPluginData(t *testing.T) { var pluginData nodes.PluginData - err := pluginData.RawMessage.UnmarshalJSON([]byte(insptest.IntrospectionDataJSONSample)) - testhelper.AssertNoErr(t, err) + err := pluginData.UnmarshalJSON([]byte(insptest.IntrospectionDataJSONSample)) + th.AssertNoErr(t, err) parsedData, err := pluginData.AsInspectorData() - testhelper.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, insptest.IntrospectionDataRes, parsedData) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, insptest.IntrospectionDataRes, parsedData) irData, inspData, err := pluginData.GuessFormat() - testhelper.AssertNoErr(t, err) - testhelper.CheckEquals(t, (*inventory.StandardPluginData)(nil), irData) - testhelper.CheckDeepEquals(t, insptest.IntrospectionDataRes, *inspData) + th.AssertNoErr(t, err) + th.CheckEquals(t, (*inventory.StandardPluginData)(nil), irData) + th.CheckDeepEquals(t, insptest.IntrospectionDataRes, *inspData) } func TestGuessFormatUnknownDefaultsToIronic(t *testing.T) { var pluginData nodes.PluginData - err := pluginData.RawMessage.UnmarshalJSON([]byte("{}")) - testhelper.AssertNoErr(t, err) + err := pluginData.UnmarshalJSON([]byte("{}")) + th.AssertNoErr(t, err) irData, inspData, err := pluginData.GuessFormat() - testhelper.CheckDeepEquals(t, inventory.StandardPluginData{}, *irData) - testhelper.CheckEquals(t, (*introspection.Data)(nil), inspData) - testhelper.AssertNoErr(t, err) + th.CheckDeepEquals(t, inventory.StandardPluginData{}, *irData) + th.CheckEquals(t, (*introspection.Data)(nil), inspData) + th.AssertNoErr(t, err) } func TestGuessFormatErrors(t *testing.T) { var pluginData nodes.PluginData - err := pluginData.RawMessage.UnmarshalJSON([]byte("\"banana\"")) - testhelper.AssertNoErr(t, err) + err := pluginData.UnmarshalJSON([]byte("\"banana\"")) + th.AssertNoErr(t, err) irData, inspData, err := pluginData.GuessFormat() - testhelper.CheckEquals(t, (*inventory.StandardPluginData)(nil), irData) - testhelper.CheckEquals(t, (*introspection.Data)(nil), inspData) - testhelper.AssertErr(t, err) + th.CheckEquals(t, (*inventory.StandardPluginData)(nil), irData) + th.CheckEquals(t, (*introspection.Data)(nil), inspData) + th.AssertErr(t, err) failsInspectorConversion := `{ "interfaces": "banana" }` - err = pluginData.RawMessage.UnmarshalJSON([]byte(failsInspectorConversion)) - testhelper.AssertNoErr(t, err) + err = pluginData.UnmarshalJSON([]byte(failsInspectorConversion)) + th.AssertNoErr(t, err) irData, inspData, err = pluginData.GuessFormat() - testhelper.CheckEquals(t, (*inventory.StandardPluginData)(nil), irData) - testhelper.CheckEquals(t, (*introspection.Data)(nil), inspData) - testhelper.AssertErr(t, err) + th.CheckEquals(t, (*inventory.StandardPluginData)(nil), irData) + th.CheckEquals(t, (*introspection.Data)(nil), inspData) + th.AssertErr(t, err) } diff --git a/openstack/baremetal/v1/nodes/urls.go b/openstack/baremetal/v1/nodes/urls.go index 2948bb659e..b86e4820e5 100644 --- a/openstack/baremetal/v1/nodes/urls.go +++ b/openstack/baremetal/v1/nodes/urls.go @@ -89,3 +89,11 @@ func firmwareListURL(client *gophercloud.ServiceClient, id string) string { func virtualMediaURL(client *gophercloud.ServiceClient, id string) string { return client.ServiceURL("nodes", id, "vmedia") } + +func virtualInterfaceURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("nodes", id, "vifs") +} + +func virtualInterfaceDeleteURL(client *gophercloud.ServiceClient, id string, vifID string) string { + return client.ServiceURL("nodes", id, "vifs", vifID) +} diff --git a/openstack/baremetal/v1/portgroups/requests.go b/openstack/baremetal/v1/portgroups/requests.go new file mode 100644 index 0000000000..9a06755323 --- /dev/null +++ b/openstack/baremetal/v1/portgroups/requests.go @@ -0,0 +1,149 @@ +package portgroups + +import ( + "context" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/pagination" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToPortGroupCreateMap() (map[string]any, error) +} + +// CreateOpts specifies port group creation parameters +type CreateOpts struct { + // NodeUUID is the UUID of the Node this resource belongs to + NodeUUID string `json:"node_uuid" required:"true"` + + // Address is the physical hardware address of this Portgroup, + // typically the hardware MAC address + Address string `json:"address,omitempty"` + + // Name is a human-readable identifier for the Portgroup resource + Name string `json:"name,omitempty"` + + // Mode is the mode of the port group. For possible values, refer to + // https://www.kernel.org/doc/Documentation/networking/bonding.txt + // If not specified, it will be set to the value of the + // [DEFAULT]default_portgroup_mode configuration option. + // When set, cannot be removed from the port group. + Mode string `json:"mode,omitempty"` + + // StandalonePortsSupported indicates whether ports that are members + // of this portgroup can be used as stand-alone ports + StandalonePortsSupported bool `json:"standalone_ports_supported,omitempty"` + + // Properties contains key/value properties related to the port + // group's configuration + Properties map[string]interface{} `json:"properties,omitempty"` + + // Extra is a set of one or more arbitrary metadata key and value pairs + Extra map[string]string `json:"extra,omitempty"` + + // UUID is the UUID for the resource + UUID string `json:"uuid,omitempty"` +} + +// ToPortGroupCreateMap assembles a request body based on the contents of a CreateOpts. +func (opts CreateOpts) ToPortGroupCreateMap() (map[string]any, error) { + body, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + return body, nil +} + +// Create requests a node to be created +func Create(ctx context.Context, client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + reqBody, err := opts.ToPortGroupCreateMap() + if err != nil { + r.Err = err + return + } + + resp, err := client.Post(ctx, createURL(client), reqBody, &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// ListOptsBuilder allows extensions to add additional parameters to the List request. +type ListOptsBuilder interface { + ToPortGroupListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing specific query parameters to the API. +type ListOpts struct { + // Node filters the list to return only Portgroups associated with this + // specific node (name or UUID) + Node string `q:"node,omitempty"` + + // Address filters the list to return only Portgroups with the specified + // physical hardware address (typically MAC) + Address string `q:"address,omitempty"` + + // Fields specifies which fields to return in the response + // For example: "uuid,name" will return only those fields + Fields []string `q:"fields,omitempty"` + + // Limit requests a page size of items. Returns a number of items up to a limit value. + // Use with marker to implement pagination. Cannot exceed max_limit set in configuration. + Limit int `q:"limit,omitempty"` + + // Marker is the ID of the last-seen item. Use with limit to implement pagination. + // Use the ID from the response as marker in subsequent limited requests. + Marker string `q:"marker,omitempty"` + + // SortDir sorts the response by the requested direction. + // Valid values are "asc" or "desc". Default is "asc". + SortDir string `q:"sort_dir,omitempty"` + + // SortKey sorts the response by this attribute value. + // Default is "id". Multiple sort key/direction pairs can be specified. + SortKey string `q:"sort_key,omitempty"` + + // Detail indicates whether to show detailed information about the resource. + // Cannot be true if Fields parameter is specified. + Detail bool `q:"detail,omitempty"` +} + +// ToPortGroupListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToPortGroupListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List makes a request against the API to list portgroups accessible to you. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToPortGroupListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return PortGroupsPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get requests the details of an portgroup by ID. +func Get(ctx context.Context, client *gophercloud.ServiceClient, id string) (r GetResult) { + resp, err := client.Get(ctx, getURL(client, id), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// Delete requests the deletion of an portgroup +func Delete(ctx context.Context, client *gophercloud.ServiceClient, id string) (r DeleteResult) { + resp, err := client.Delete(ctx, deleteURL(client, id), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/baremetal/v1/portgroups/results.go b/openstack/baremetal/v1/portgroups/results.go new file mode 100644 index 0000000000..0618a21d3a --- /dev/null +++ b/openstack/baremetal/v1/portgroups/results.go @@ -0,0 +1,132 @@ +package portgroups + +import ( + "time" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/pagination" +) + +// ResourceLink represents a link with href and rel attributes +type ResourceLink struct { + Href string `json:"href"` + Rel string `json:"rel"` +} + +// PortGroup represents a port group in the baremetal service +// https://docs.openstack.org/api-ref/baremetal/#portgroups-portgroups +type PortGroup struct { + // Human-readable identifier for the Portgroup resource. May be undefined. + Name string `json:"name"` + + // The UUID for the resource. + UUID string `json:"uuid"` + + // Physical hardware address of this Portgroup, typically the hardware MAC address. + Address string `json:"address,omitempty"` + + // UUID of the Node this resource belongs to. + NodeUUID string `json:"node_uuid"` + + // Indicates whether ports that are members of this portgroup can be used as + // stand-alone ports. + StandalonePortsSupported bool `json:"standalone_ports_supported"` + + // Internal metadata set and stored by the Portgroup. This field is read-only. + InternalInfo map[string]any `json:"internal_info"` + + // A set of one or more arbitrary metadata key and value pairs. + Extra map[string]any `json:"extra"` + + // Mode of the port group. For possible values, refer to + // https://www.kernel.org/doc/Documentation/networking/bonding.txt + Mode string `json:"mode"` + + // Key/value properties related to the port group's configuration. + Properties map[string]any `json:"properties"` + + // The UTC date and time when the resource was created, ISO 8601 format. + CreatedAt time.Time `json:"created_at"` + + // The UTC date and time when the resource was updated, ISO 8601 format. + // May be "null". + UpdatedAt time.Time `json:"updated_at"` + + // A list of relative links. Includes the self and bookmark links. + Links []ResourceLink `json:"links"` + + // Links to the collection of ports belonging to this portgroup. + Ports []ResourceLink `json:"ports"` +} + +type portgroupsResult struct { + gophercloud.Result +} + +func (r portgroupsResult) Extract() (*PortGroup, error) { + var s PortGroup + err := r.ExtractInto(&s) + return &s, err +} + +func (r portgroupsResult) ExtractInto(v any) error { + return r.ExtractIntoStructPtr(v, "") +} + +func ExtractPortGroupsInto(r pagination.Page, v any) error { + return r.(PortGroupsPage).ExtractIntoSlicePtr(v, "portgroups") +} + +// PortGroupsPage abstracts the raw results of making a List() request against +// the API. +type PortGroupsPage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if a page contains no PortGroup results. +func (r PortGroupsPage) IsEmpty() (bool, error) { + if r.StatusCode == 204 { + return true, nil + } + + s, err := ExtractPortGroups(r) + return len(s) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the +// next page of results. +func (r PortGroupsPage) NextPageURL(endpointURL string) (string, error) { + var s struct { + Links []gophercloud.Link `json:"portgroups_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// ExtractPortGroups interprets the results of a single page from a List() call, +// producing a slice of PortGroup entities. +func ExtractPortGroups(r pagination.Page) ([]PortGroup, error) { + var s []PortGroup + err := ExtractPortGroupsInto(r, &s) + return s, err +} + +// GetResult is the response from a Get operation. Call its Extract +// method to interpret it as a PortGroup. +type GetResult struct { + portgroupsResult +} + +// CreateResult is the response from a Create operation. +type CreateResult struct { + portgroupsResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr +// method to determine if the call succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/openstack/baremetal/v1/portgroups/testing/fixtures.go b/openstack/baremetal/v1/portgroups/testing/fixtures.go new file mode 100644 index 0000000000..52039ca7de --- /dev/null +++ b/openstack/baremetal/v1/portgroups/testing/fixtures.go @@ -0,0 +1,271 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gophercloud/gophercloud/v2/openstack/baremetal/v1/portgroups" + th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/client" +) + +// PortGroupsListBody is the JSON response for listing all portgroups. +var PortGroupsListBody = ` +{ + "portgroups": [ + { + "address": "00:1a:2b:3c:4d:5e", + "created_at": "2019-02-20T09:43:58Z", + "extra": { + "description": "Primary network bond", + "location": "rack-3-unit-12" + }, + "internal_info": { + "fault_count": 0, + "last_check": "2024-03-15T10:30:00Z" + }, + "links": [ + { + "href": "http://ironic.example.com/v1/portgroups/d2b42f0d-c7e6-4f08-b9bc-e8b23a6ee796", + "rel": "self" + }, + { + "href": "http://ironic.example.com/portgroups/d2b42f0d-c7e6-4f08-b9bc-e8b23a6ee796", + "rel": "bookmark" + } + ], + "mode": "active-backup", + "name": "bond0", + "node_uuid": "f9c9a846-c53f-4b17-9f0c-dd9f459d35c8", + "ports": [ + { + "href": "http://ironic.example.com/v1/portgroups/d2b42f0d-c7e6-4f08-b9bc-e8b23a6ee796/ports", + "rel": "self" + } + ], + "properties": { + "miimon": "100", + "updelay": "1000", + "downdelay": "1000", + "xmit_hash_policy": "layer2" + }, + "standalone_ports_supported": true, + "updated_at": "2019-02-20T09:43:58Z", + "uuid": "d2b42f0d-c7e6-4f08-b9bc-e8b23a6ee796" + }, + { + "address": "11:22:33:44:55:66", + "created_at": "2019-02-20T09:43:58Z", + "extra": { + "description": "Secondary bond", + "location": "rack-1-unit-4" + }, + "internal_info": { + "fault_count": 1, + "last_check": "2024-04-01T09:00:00Z" + }, + "links": [ + { + "href": "http://ironic.example.com/v1/portgroups/a1b2c3d4-e5f6-7890-1234-56789abcdef0", + "rel": "self" + }, + { + "href": "http://ironic.example.com/portgroups/a1b2c3d4-e5f6-7890-1234-56789abcdef0", + "rel": "bookmark" + } + ], + "mode": "active-backup", + "name": "bond1", + "node_uuid": "aabbcc00-1122-3344-5566-778899aabbcc", + "ports": [ + { + "href": "http://ironic.example.com/v1/portgroups/a1b2c3d4-e5f6-7890-1234-56789abcdef0/ports", + "rel": "self" + } + ], + "properties": { + "miimon": "200", + "updelay": "500", + "downdelay": "500", + "xmit_hash_policy": "layer3+4" + }, + "standalone_ports_supported": true, + "updated_at": "2019-02-20T09:43:58Z", + "uuid": "a1b2c3d4-e5f6-7890-1234-56789abcdef0" + } + ] +} +` + +// SinglePortGroupBody returns JSON for a single portgroup. +// Here we use PortGroup1 as the example. +var SinglePortGroupBody = ` +{ + "address": "00:1a:2b:3c:4d:5e", + "created_at": "2019-02-20T09:43:58Z", + "extra": { + "description": "Primary network bond", + "location": "rack-3-unit-12" + }, + "internal_info": { + "fault_count": 0, + "last_check": "2024-03-15T10:30:00Z" + }, + "links": [ + { + "href": "http://ironic.example.com/v1/portgroups/d2b42f0d-c7e6-4f08-b9bc-e8b23a6ee796", + "rel": "self" + }, + { + "href": "http://ironic.example.com/portgroups/d2b42f0d-c7e6-4f08-b9bc-e8b23a6ee796", + "rel": "bookmark" + } + ], + "mode": "active-backup", + "name": "bond0", + "node_uuid": "f9c9a846-c53f-4b17-9f0c-dd9f459d35c8", + "ports": [ + { + "href": "http://ironic.example.com/v1/portgroups/d2b42f0d-c7e6-4f08-b9bc-e8b23a6ee796/ports", + "rel": "self" + } + ], + "properties": { + "miimon": "100", + "updelay": "1000", + "downdelay": "1000", + "xmit_hash_policy": "layer2" + }, + "standalone_ports_supported": true, + "updated_at": "2019-02-20T09:43:58Z", + "uuid": "d2b42f0d-c7e6-4f08-b9bc-e8b23a6ee796" +} +` + +var ( + createdAt, _ = time.Parse(time.RFC3339, "2019-02-20T09:43:58Z") + + // PortGroup1 is the first portgroup. + PortGroup1 = portgroups.PortGroup{ + Name: "bond0", + UUID: "d2b42f0d-c7e6-4f08-b9bc-e8b23a6ee796", + Address: "00:1a:2b:3c:4d:5e", + NodeUUID: "f9c9a846-c53f-4b17-9f0c-dd9f459d35c8", + StandalonePortsSupported: true, + InternalInfo: map[string]any{ + "fault_count": float64(0), + "last_check": "2024-03-15T10:30:00Z", + }, + Extra: map[string]any{ + "description": "Primary network bond", + "location": "rack-3-unit-12", + }, + Mode: "active-backup", + Properties: map[string]any{ + "miimon": "100", + "updelay": "1000", + "downdelay": "1000", + "xmit_hash_policy": "layer2", + }, + CreatedAt: createdAt, + UpdatedAt: createdAt, + Links: []portgroups.ResourceLink{ + { + Href: "http://ironic.example.com/v1/portgroups/d2b42f0d-c7e6-4f08-b9bc-e8b23a6ee796", + Rel: "self", + }, + { + Href: "http://ironic.example.com/portgroups/d2b42f0d-c7e6-4f08-b9bc-e8b23a6ee796", + Rel: "bookmark", + }, + }, + Ports: []portgroups.ResourceLink{ + { + Href: "http://ironic.example.com/v1/portgroups/d2b42f0d-c7e6-4f08-b9bc-e8b23a6ee796/ports", + Rel: "self", + }, + }, + } +) + +// HandlePortGroupListSuccessfully sets up the test server to respond to a +// portgroup List request. +func HandlePortGroupListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/portgroups", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + w.Header().Add("Content-Type", "application/json") + if err := r.ParseForm(); err != nil { + t.Errorf("Failed to parse request form: %v", err) + } + + marker := r.Form.Get("marker") + switch marker { + case "": + // Return both portgroups. + fmt.Fprint(w, PortGroupsListBody) + case "d2b42f0d-c7e6-4f08-b9bc-e8b23a6ee796": + // No portgroups remain. + fmt.Fprintf(w, `{ "portgroups": [] }`) + default: + t.Fatalf("/portgroups invoked with unexpected marker=[%s]", marker) + } + }) +} + +// HandlePortGroupCreationSuccessfully sets up the test server to respond to a PortGroup creation request +// with a given response. +func HandlePortGroupCreationSuccessfully(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/portgroups", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "node_uuid": "f9c9a846-c53f-4b17-9f0c-dd9f459d35c8", + "address": "00:1a:2b:3c:4d:5e", + "name": "bond0", + "mode": "active-backup", + "standalone_ports_supported": true, + "properties": { + "miimon": "100", + "updelay": "1000", + "downdelay": "1000", + "xmit_hash_policy": "layer2" + }, + "extra": { + "description": "Primary network bond", + "location": "rack-3-unit-12" + } + }`) + + w.WriteHeader(http.StatusAccepted) + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, response) + }) +} + +// HandlePortGroupDeletionSuccessfully sets up the test server to respond to a +// portgroup Deletion (DELETE) request for PortGroup2. +func HandlePortGroupDeletionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/portgroups/d2b42f0d-c7e6-4f08-b9bc-e8b23a6ee796", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandlePortGroupGetSuccessfully sets up the test server to respond to a +// portgroup Get request for PortGroup1. +func HandlePortGroupGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/portgroups/d2b42f0d-c7e6-4f08-b9bc-e8b23a6ee796", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, SinglePortGroupBody) + }) +} diff --git a/openstack/baremetal/v1/portgroups/testing/requests_test.go b/openstack/baremetal/v1/portgroups/testing/requests_test.go new file mode 100644 index 0000000000..b8dc0b479a --- /dev/null +++ b/openstack/baremetal/v1/portgroups/testing/requests_test.go @@ -0,0 +1,94 @@ +package testing + +import ( + "context" + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/baremetal/v1/portgroups" + "github.com/gophercloud/gophercloud/v2/pagination" + "github.com/gophercloud/gophercloud/v2/testhelper/client" + + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func TestListPortGroups(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePortGroupListSuccessfully(t, fakeServer) + + pages := 0 + err := portgroups.List(client.ServiceClient(fakeServer), portgroups.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + pages++ + + actual, err := portgroups.ExtractPortGroups(page) + if err != nil { + return false, err + } + + if len(actual) != 2 { + t.Fatalf("Expected 2 portgroups, got %d", len(actual)) + } + th.AssertEquals(t, "d2b42f0d-c7e6-4f08-b9bc-e8b23a6ee796", actual[0].UUID) + th.AssertEquals(t, "a1b2c3d4-e5f6-7890-1234-56789abcdef0", actual[1].UUID) + th.AssertEquals(t, "bond0", actual[0].Name) + th.AssertEquals(t, "bond1", actual[1].Name) + + return true, nil + }) + + th.AssertNoErr(t, err) + + if pages != 1 { + t.Errorf("Expected 1 page, saw %d", pages) + } +} + +func TestCreatePortGroup(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePortGroupCreationSuccessfully(t, fakeServer, SinglePortGroupBody) + + actual, err := portgroups.Create(context.TODO(), client.ServiceClient(fakeServer), portgroups.CreateOpts{ + Name: "bond0", + NodeUUID: "f9c9a846-c53f-4b17-9f0c-dd9f459d35c8", + Address: "00:1a:2b:3c:4d:5e", + Mode: "active-backup", + StandalonePortsSupported: true, + Properties: map[string]interface{}{ + "miimon": "100", + "updelay": "1000", + "downdelay": "1000", + "xmit_hash_policy": "layer2", + }, + Extra: map[string]string{ + "description": "Primary network bond", + "location": "rack-3-unit-12", + }, + }).Extract() + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, PortGroup1, *actual) +} + +func TestDeletePortGroup(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePortGroupDeletionSuccessfully(t, fakeServer) + + res := portgroups.Delete(context.TODO(), client.ServiceClient(fakeServer), "d2b42f0d-c7e6-4f08-b9bc-e8b23a6ee796") + th.AssertNoErr(t, res.Err) +} + +func TestGetPortGroup(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePortGroupGetSuccessfully(t, fakeServer) + + c := client.ServiceClient(fakeServer) + actual, err := portgroups.Get(context.TODO(), c, "d2b42f0d-c7e6-4f08-b9bc-e8b23a6ee796").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, PortGroup1, *actual) +} diff --git a/openstack/baremetal/v1/portgroups/urls.go b/openstack/baremetal/v1/portgroups/urls.go new file mode 100644 index 0000000000..3320a8e173 --- /dev/null +++ b/openstack/baremetal/v1/portgroups/urls.go @@ -0,0 +1,23 @@ +package portgroups + +import "github.com/gophercloud/gophercloud/v2" + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("portgroups") +} + +func listURL(client *gophercloud.ServiceClient) string { + return createURL(client) +} + +func resourceURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("portgroups", id) +} + +func deleteURL(client *gophercloud.ServiceClient, id string) string { + return resourceURL(client, id) +} + +func getURL(client *gophercloud.ServiceClient, id string) string { + return resourceURL(client, id) +} diff --git a/openstack/baremetal/v1/ports/results.go b/openstack/baremetal/v1/ports/results.go index b1ec5cca81..afc01a2016 100644 --- a/openstack/baremetal/v1/ports/results.go +++ b/openstack/baremetal/v1/ports/results.go @@ -18,11 +18,11 @@ func (r portResult) Extract() (*Port, error) { } func (r portResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "") + return r.ExtractIntoStructPtr(v, "") } func ExtractPortsInto(r pagination.Page, v any) error { - return r.(PortPage).Result.ExtractIntoSlicePtr(v, "ports") + return r.(PortPage).ExtractIntoSlicePtr(v, "ports") } // Port represents a port in the OpenStack Bare Metal API. @@ -92,7 +92,7 @@ func (r PortPage) IsEmpty() (bool, error) { // NextPageURL uses the response's embedded link reference to navigate to the // next page of results. -func (r PortPage) NextPageURL() (string, error) { +func (r PortPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"ports_links"` } diff --git a/openstack/baremetal/v1/ports/testing/fixtures_test.go b/openstack/baremetal/v1/ports/testing/fixtures_test.go index 492aa42aa6..0d1ec9cdfa 100644 --- a/openstack/baremetal/v1/ports/testing/fixtures_test.go +++ b/openstack/baremetal/v1/ports/testing/fixtures_test.go @@ -169,8 +169,8 @@ var ( ) // HandlePortListSuccessfully sets up the test server to respond to a port List request. -func HandlePortListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/ports", func(w http.ResponseWriter, r *http.Request) { +func HandlePortListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/ports", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") @@ -181,10 +181,10 @@ func HandlePortListSuccessfully(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, PortListBody) + fmt.Fprint(w, PortListBody) case "f2845e11-dbd4-4728-a8c0-30d19f48924a": - fmt.Fprintf(w, `{ "ports": [] }`) + fmt.Fprint(w, `{ "ports": [] }`) default: t.Fatalf("/ports invoked with unexpected marker=[%s]", marker) } @@ -192,8 +192,8 @@ func HandlePortListSuccessfully(t *testing.T) { } // HandlePortListSuccessfully sets up the test server to respond to a port List request. -func HandlePortListDetailSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/ports/detail", func(w http.ResponseWriter, r *http.Request) { +func HandlePortListDetailSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/ports/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") @@ -201,14 +201,14 @@ func HandlePortListDetailSuccessfully(t *testing.T) { t.Errorf("Failed to parse request form %v", err) } - fmt.Fprintf(w, PortListDetailBody) + fmt.Fprint(w, PortListDetailBody) }) } // HandleSPortCreationSuccessfully sets up the test server to respond to a port creation request // with a given response. -func HandlePortCreationSuccessfully(t *testing.T, response string) { - th.Mux.HandleFunc("/ports", func(w http.ResponseWriter, r *http.Request) { +func HandlePortCreationSuccessfully(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/ports", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -219,13 +219,13 @@ func HandlePortCreationSuccessfully(t *testing.T, response string) { w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) } // HandlePortDeletionSuccessfully sets up the test server to respond to a port deletion request. -func HandlePortDeletionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/ports/3abe3f36-9708-4e9f-b07e-0f898061d3a7", func(w http.ResponseWriter, r *http.Request) { +func HandlePortDeletionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/ports/3abe3f36-9708-4e9f-b07e-0f898061d3a7", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -233,24 +233,24 @@ func HandlePortDeletionSuccessfully(t *testing.T) { }) } -func HandlePortGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/ports/f2845e11-dbd4-4728-a8c0-30d19f48924a", func(w http.ResponseWriter, r *http.Request) { +func HandlePortGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/ports/f2845e11-dbd4-4728-a8c0-30d19f48924a", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SinglePortBody) + fmt.Fprint(w, SinglePortBody) }) } -func HandlePortUpdateSuccessfully(t *testing.T, response string) { - th.Mux.HandleFunc("/ports/f2845e11-dbd4-4728-a8c0-30d19f48924a", func(w http.ResponseWriter, r *http.Request) { +func HandlePortUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/ports/f2845e11-dbd4-4728-a8c0-30d19f48924a", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "Content-Type", "application/json") th.TestJSONRequest(t, r, `[{"op": "replace", "path": "/address", "value": "22:22:22:22:22:22"}]`) - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) } diff --git a/openstack/baremetal/v1/ports/testing/requests_test.go b/openstack/baremetal/v1/ports/testing/requests_test.go index b52d3c521c..befb02e41c 100644 --- a/openstack/baremetal/v1/ports/testing/requests_test.go +++ b/openstack/baremetal/v1/ports/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestListDetailPorts(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandlePortListDetailSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePortListDetailSuccessfully(t, fakeServer) pages := 0 - err := ports.ListDetail(client.ServiceClient(), ports.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := ports.ListDetail(client.ServiceClient(fakeServer), ports.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := ports.ExtractPorts(page) @@ -41,12 +41,12 @@ func TestListDetailPorts(t *testing.T) { } func TestListPorts(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandlePortListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePortListSuccessfully(t, fakeServer) pages := 0 - err := ports.List(client.ServiceClient(), ports.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := ports.List(client.ServiceClient(fakeServer), ports.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := ports.ExtractPorts(page) @@ -77,7 +77,7 @@ func TestListOpts(t *testing.T) { } _, err := opts.ToPortListDetailQuery() - th.AssertEquals(t, err.Error(), "fields is not a valid option when getting a detailed listing of ports") + th.AssertEquals(t, "fields is not a valid option when getting a detailed listing of ports", err.Error()) // Regular ListOpts can query, err := opts.ToPortListQuery() @@ -86,12 +86,12 @@ func TestListOpts(t *testing.T) { } func TestCreatePort(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandlePortCreationSuccessfully(t, SinglePortBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePortCreationSuccessfully(t, fakeServer, SinglePortBody) iTrue := true - actual, err := ports.Create(context.TODO(), client.ServiceClient(), ports.CreateOpts{ + actual, err := ports.Create(context.TODO(), client.ServiceClient(fakeServer), ports.CreateOpts{ NodeUUID: "ddd06a60-b91e-4ab4-a6e7-56c0b25b6086", Address: "52:54:00:4d:87:e6", PXEEnabled: &iTrue, @@ -102,20 +102,20 @@ func TestCreatePort(t *testing.T) { } func TestDeletePort(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandlePortDeletionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePortDeletionSuccessfully(t, fakeServer) - res := ports.Delete(context.TODO(), client.ServiceClient(), "3abe3f36-9708-4e9f-b07e-0f898061d3a7") + res := ports.Delete(context.TODO(), client.ServiceClient(fakeServer), "3abe3f36-9708-4e9f-b07e-0f898061d3a7") th.AssertNoErr(t, res.Err) } func TestGetPort(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandlePortGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePortGetSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) actual, err := ports.Get(context.TODO(), c, "f2845e11-dbd4-4728-a8c0-30d19f48924a").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) @@ -125,11 +125,11 @@ func TestGetPort(t *testing.T) { } func TestUpdatePort(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandlePortUpdateSuccessfully(t, SinglePortBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePortUpdateSuccessfully(t, fakeServer, SinglePortBody) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) actual, err := ports.Update(context.TODO(), c, "f2845e11-dbd4-4728-a8c0-30d19f48924a", ports.UpdateOpts{ ports.UpdateOperation{ Op: ports.ReplaceOp, diff --git a/openstack/baremetalintrospection/httpbasic/requests.go b/openstack/baremetalintrospection/httpbasic/requests.go index 473e92c372..023ef67046 100644 --- a/openstack/baremetalintrospection/httpbasic/requests.go +++ b/openstack/baremetalintrospection/httpbasic/requests.go @@ -20,7 +20,7 @@ func initClientOpts(client *gophercloud.ProviderClient, eo EndpointOpts) (*gophe return nil, fmt.Errorf("IronicInspectorEndpoint is required") } if eo.IronicInspectorUser == "" || eo.IronicInspectorUserPassword == "" { - return nil, fmt.Errorf("User and Password are required") + return nil, fmt.Errorf("IronicInspectorUser and IronicInspectorUserPassword are required") } token := []byte(eo.IronicInspectorUser + ":" + eo.IronicInspectorUserPassword) diff --git a/openstack/baremetalintrospection/httpbasic/testing/requests_test.go b/openstack/baremetalintrospection/httpbasic/testing/requests_test.go index df091e0cc9..bceb8c5d8c 100644 --- a/openstack/baremetalintrospection/httpbasic/testing/requests_test.go +++ b/openstack/baremetalintrospection/httpbasic/testing/requests_test.go @@ -23,7 +23,7 @@ func TestNoAuth(t *testing.T) { IronicInspectorEndpoint: "http://ironic:5050/v1", }) _ = errTest1 - th.AssertEquals(t, "User and Password are required", err.Error()) + th.AssertEquals(t, "IronicInspectorUser and IronicInspectorUserPassword are required", err.Error()) errTest2, err := httpbasic.NewBareMetalIntrospectionHTTPBasic(httpbasic.EndpointOpts{ IronicInspectorUser: "myUser", diff --git a/openstack/baremetalintrospection/v1/introspection/results.go b/openstack/baremetalintrospection/v1/introspection/results.go index 00e0287bc4..74b7808182 100644 --- a/openstack/baremetalintrospection/v1/introspection/results.go +++ b/openstack/baremetalintrospection/v1/introspection/results.go @@ -22,13 +22,13 @@ func (r introspectionResult) Extract() (*Introspection, error) { // ExtractInto will extract a response body into an Introspection struct. func (r introspectionResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "") + return r.ExtractIntoStructPtr(v, "") } // ExtractIntrospectionsInto will extract a collection of introspectResult pages into a // slice of Introspection entities. func ExtractIntrospectionsInto(r pagination.Page, v any) error { - return r.(IntrospectionPage).Result.ExtractIntoSlicePtr(v, "introspection") + return r.(IntrospectionPage).ExtractIntoSlicePtr(v, "introspection") } // ExtractIntrospections interprets the results of a single page from a @@ -83,7 +83,7 @@ func (r IntrospectionPage) IsEmpty() (bool, error) { // NextPageURL uses the response's embedded link reference to navigate to the // next page of results. -func (r IntrospectionPage) NextPageURL() (string, error) { +func (r IntrospectionPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"introspection_links"` } diff --git a/openstack/baremetalintrospection/v1/introspection/testing/fixtures.go b/openstack/baremetalintrospection/v1/introspection/testing/fixtures.go index af2ad9e7f5..6170e53036 100644 --- a/openstack/baremetalintrospection/v1/introspection/testing/fixtures.go +++ b/openstack/baremetalintrospection/v1/introspection/testing/fixtures.go @@ -454,8 +454,8 @@ var ( ) // HandleListIntrospectionsSuccessfully sets up the test server to respond to a server ListIntrospections request. -func HandleListIntrospectionsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/introspection", func(w http.ResponseWriter, r *http.Request) { +func HandleListIntrospectionsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/introspection", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") @@ -467,10 +467,10 @@ func HandleListIntrospectionsSuccessfully(t *testing.T) { switch marker { case "": - fmt.Fprintf(w, IntrospectionListBody) + fmt.Fprint(w, IntrospectionListBody) case "c244557e-899f-46fa-a1ff-5b2c6718616b": - fmt.Fprintf(w, `{ "introspection": [] }`) + fmt.Fprint(w, `{ "introspection": [] }`) default: t.Fatalf("/introspection invoked with unexpected marker=[%s]", marker) @@ -479,18 +479,18 @@ func HandleListIntrospectionsSuccessfully(t *testing.T) { } // HandleGetIntrospectionStatusSuccessfully sets up the test server to respond to a GetIntrospectionStatus request. -func HandleGetIntrospectionStatusSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/introspection/c244557e-899f-46fa-a1ff-5b2c6718616b", func(w http.ResponseWriter, r *http.Request) { +func HandleGetIntrospectionStatusSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/introspection/c244557e-899f-46fa-a1ff-5b2c6718616b", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, IntrospectionStatus) + fmt.Fprint(w, IntrospectionStatus) }) } // HandleStartIntrospectionSuccessfully sets up the test server to respond to a StartIntrospection request. -func HandleStartIntrospectionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/introspection/c244557e-899f-46fa-a1ff-5b2c6718616b", func(w http.ResponseWriter, r *http.Request) { +func HandleStartIntrospectionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/introspection/c244557e-899f-46fa-a1ff-5b2c6718616b", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) @@ -498,8 +498,8 @@ func HandleStartIntrospectionSuccessfully(t *testing.T) { } // HandleAbortIntrospectionSuccessfully sets up the test server to respond to an AbortIntrospection request. -func HandleAbortIntrospectionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/introspection/c244557e-899f-46fa-a1ff-5b2c6718616b/abort", func(w http.ResponseWriter, r *http.Request) { +func HandleAbortIntrospectionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/introspection/c244557e-899f-46fa-a1ff-5b2c6718616b/abort", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) @@ -507,19 +507,19 @@ func HandleAbortIntrospectionSuccessfully(t *testing.T) { } // HandleGetIntrospectionDataSuccessfully sets up the test server to respond to a GetIntrospectionData request. -func HandleGetIntrospectionDataSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/introspection/c244557e-899f-46fa-a1ff-5b2c6718616b/data", func(w http.ResponseWriter, r *http.Request) { +func HandleGetIntrospectionDataSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/introspection/c244557e-899f-46fa-a1ff-5b2c6718616b/data", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, IntrospectionDataJSONSample) + fmt.Fprint(w, IntrospectionDataJSONSample) }) } // HandleReApplyIntrospectionSuccessfully sets up the test server to respond to a ReApplyIntrospection request. -func HandleReApplyIntrospectionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/introspection/c244557e-899f-46fa-a1ff-5b2c6718616b/data/unprocessed", func(w http.ResponseWriter, r *http.Request) { +func HandleReApplyIntrospectionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/introspection/c244557e-899f-46fa-a1ff-5b2c6718616b/data/unprocessed", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) diff --git a/openstack/baremetalintrospection/v1/introspection/testing/requests_test.go b/openstack/baremetalintrospection/v1/introspection/testing/requests_test.go index b36318ce76..97d788c56d 100644 --- a/openstack/baremetalintrospection/v1/introspection/testing/requests_test.go +++ b/openstack/baremetalintrospection/v1/introspection/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestListIntrospections(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListIntrospectionsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListIntrospectionsSuccessfully(t, fakeServer) pages := 0 - err := introspection.ListIntrospections(client.ServiceClient(), introspection.ListIntrospectionsOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := introspection.ListIntrospections(client.ServiceClient(fakeServer), introspection.ListIntrospectionsOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := introspection.ExtractIntrospections(page) @@ -41,11 +41,11 @@ func TestListIntrospections(t *testing.T) { } func TestGetIntrospectionStatus(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetIntrospectionStatusSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetIntrospectionStatusSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) actual, err := introspection.GetIntrospectionStatus(context.TODO(), c, "c244557e-899f-46fa-a1ff-5b2c6718616b").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) @@ -55,31 +55,31 @@ func TestGetIntrospectionStatus(t *testing.T) { } func TestStartIntrospection(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleStartIntrospectionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleStartIntrospectionSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) err := introspection.StartIntrospection(context.TODO(), c, "c244557e-899f-46fa-a1ff-5b2c6718616b", introspection.StartOpts{}).ExtractErr() th.AssertNoErr(t, err) } func TestAbortIntrospection(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAbortIntrospectionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAbortIntrospectionSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) err := introspection.AbortIntrospection(context.TODO(), c, "c244557e-899f-46fa-a1ff-5b2c6718616b").ExtractErr() th.AssertNoErr(t, err) } func TestGetIntrospectionData(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetIntrospectionDataSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetIntrospectionDataSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) actual, err := introspection.GetIntrospectionData(context.TODO(), c, "c244557e-899f-46fa-a1ff-5b2c6718616b").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) @@ -89,11 +89,11 @@ func TestGetIntrospectionData(t *testing.T) { } func TestReApplyIntrospection(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleReApplyIntrospectionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleReApplyIntrospectionSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) err := introspection.ReApplyIntrospection(context.TODO(), c, "c244557e-899f-46fa-a1ff-5b2c6718616b").ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/baremetalintrospection/v1/introspection/testing/results_test.go b/openstack/baremetalintrospection/v1/introspection/testing/results_test.go index da75e49190..9463ddb6f0 100644 --- a/openstack/baremetalintrospection/v1/introspection/testing/results_test.go +++ b/openstack/baremetalintrospection/v1/introspection/testing/results_test.go @@ -15,5 +15,5 @@ func TestHostnameInInventory(t *testing.T) { t.Fatalf("Failed to unmarshal Inventory data: %s", err) } - th.CheckDeepEquals(t, IntrospectionDataRes.Inventory.Hostname, "myawesomehost") + th.CheckDeepEquals(t, "myawesomehost", IntrospectionDataRes.Inventory.Hostname) } diff --git a/openstack/blockstorage/apiversions/testing/fixtures_test.go b/openstack/blockstorage/apiversions/testing/fixtures_test.go index 4a368e9da9..d4a34b9bf4 100644 --- a/openstack/blockstorage/apiversions/testing/fixtures_test.go +++ b/openstack/blockstorage/apiversions/testing/fixtures_test.go @@ -116,26 +116,26 @@ const APIListOldResponse = ` ] }` -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { +func MockListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, APIListResponse) + fmt.Fprint(w, APIListResponse) }) } -func MockListOldResponse(t *testing.T) { - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { +func MockListOldResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, APIListOldResponse) + fmt.Fprint(w, APIListOldResponse) }) } diff --git a/openstack/blockstorage/apiversions/testing/requests_test.go b/openstack/blockstorage/apiversions/testing/requests_test.go index 1cfcb72e29..ea48e3581c 100644 --- a/openstack/blockstorage/apiversions/testing/requests_test.go +++ b/openstack/blockstorage/apiversions/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestListVersions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) - allVersions, err := apiversions.List(client.ServiceClient()).AllPages(context.TODO()) + allVersions, err := apiversions.List(client.ServiceClient(fakeServer)).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := apiversions.ExtractAPIVersions(allVersions) th.AssertNoErr(t, err) @@ -45,12 +45,12 @@ func TestListVersions(t *testing.T) { } func TestListOldVersions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListOldResponse(t) + MockListOldResponse(t, fakeServer) - allVersions, err := apiversions.List(client.ServiceClient()).AllPages(context.TODO()) + allVersions, err := apiversions.List(client.ServiceClient(fakeServer)).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := apiversions.ExtractAPIVersions(allVersions) th.AssertNoErr(t, err) @@ -72,12 +72,12 @@ func TestListOldVersions(t *testing.T) { } func TestGetVersion(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) - allVersions, err := apiversions.List(client.ServiceClient()).AllPages(context.TODO()) + allVersions, err := apiversions.List(client.ServiceClient(fakeServer)).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := apiversions.ExtractAPIVersion(allVersions, "v3.0") th.AssertNoErr(t, err) @@ -96,12 +96,12 @@ func TestGetVersion(t *testing.T) { } func TestGetOldVersion(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListOldResponse(t) + MockListOldResponse(t, fakeServer) - allVersions, err := apiversions.List(client.ServiceClient()).AllPages(context.TODO()) + allVersions, err := apiversions.List(client.ServiceClient(fakeServer)).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := apiversions.ExtractAPIVersion(allVersions, "v2.0") th.AssertNoErr(t, err) diff --git a/openstack/blockstorage/noauth/requests.go b/openstack/blockstorage/noauth/requests.go index 87f2d9b7f9..e9d1a156b1 100644 --- a/openstack/blockstorage/noauth/requests.go +++ b/openstack/blockstorage/noauth/requests.go @@ -39,7 +39,7 @@ func initClientOpts(client *gophercloud.ProviderClient, eo EndpointOpts, clientT token := strings.Split(client.TokenID, ":") if len(token) != 2 { - return nil, fmt.Errorf("Malformed noauth token") + return nil, fmt.Errorf("malformed noauth token") } endpoint := fmt.Sprintf("%s%s", gophercloud.NormalizeURL(eo.CinderEndpoint), token[1]) @@ -51,10 +51,10 @@ func initClientOpts(client *gophercloud.ProviderClient, eo EndpointOpts, clientT // NewBlockStorageNoAuthV2 creates a ServiceClient that may be used to access "noauth" v2 block storage service. func NewBlockStorageNoAuthV2(client *gophercloud.ProviderClient, eo EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "volumev2") + return initClientOpts(client, eo, "block-storage") } // NewBlockStorageNoAuthV3 creates a ServiceClient that may be used to access "noauth" v3 block storage service. func NewBlockStorageNoAuthV3(client *gophercloud.ProviderClient, eo EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "volumev3") + return initClientOpts(client, eo, "block-storage") } diff --git a/openstack/blockstorage/v2/availabilityzones/testing/fixtures_test.go b/openstack/blockstorage/v2/availabilityzones/testing/fixtures_test.go index b13e464abb..9c29adac94 100644 --- a/openstack/blockstorage/v2/availabilityzones/testing/fixtures_test.go +++ b/openstack/blockstorage/v2/availabilityzones/testing/fixtures_test.go @@ -41,12 +41,12 @@ var AZResult = []az.AvailabilityZone{ // HandleGetSuccessfully configures the test server to respond to a Get request // for availability zone information. -func HandleGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-availability-zone", func(w http.ResponseWriter, r *http.Request) { +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-availability-zone", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } diff --git a/openstack/blockstorage/v2/availabilityzones/testing/requests_test.go b/openstack/blockstorage/v2/availabilityzones/testing/requests_test.go index 4e8e751f45..0873ab6d2b 100644 --- a/openstack/blockstorage/v2/availabilityzones/testing/requests_test.go +++ b/openstack/blockstorage/v2/availabilityzones/testing/requests_test.go @@ -11,12 +11,12 @@ import ( // Verifies that availability zones can be listed correctly func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleGetSuccessfully(t) + HandleGetSuccessfully(t, fakeServer) - allPages, err := az.List(client.ServiceClient()).AllPages(context.TODO()) + allPages, err := az.List(client.ServiceClient(fakeServer)).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := az.ExtractAvailabilityZones(allPages) diff --git a/openstack/blockstorage/v2/backups/requests.go b/openstack/blockstorage/v2/backups/requests.go index 573946ab25..a791191d96 100644 --- a/openstack/blockstorage/v2/backups/requests.go +++ b/openstack/blockstorage/v2/backups/requests.go @@ -238,6 +238,12 @@ func Update(ctx context.Context, client *gophercloud.ServiceClient, id string, o return } +// RestoreOptsBuilder allows extensions to add additional parameters to the +// Restore request. +type RestoreOptsBuilder interface { + ToRestoreMap() (map[string]any, error) +} + // RestoreOpts contains options for restoring a Backup. This object is passed to // the backups.RestoreFromBackup function. type RestoreOpts struct { @@ -257,7 +263,7 @@ func (opts RestoreOpts) ToRestoreMap() (map[string]any, error) { // RestoreFromBackup will restore a Backup to a volume based on the values in // RestoreOpts. To extract the Restore object from the response, call the // Extract method on the RestoreResult. -func RestoreFromBackup(ctx context.Context, client *gophercloud.ServiceClient, id string, opts RestoreOpts) (r RestoreResult) { +func RestoreFromBackup(ctx context.Context, client *gophercloud.ServiceClient, id string, opts RestoreOptsBuilder) (r RestoreResult) { b, err := opts.ToRestoreMap() if err != nil { r.Err = err @@ -278,6 +284,12 @@ func Export(ctx context.Context, client *gophercloud.ServiceClient, id string) ( return } +// ImportOptsBuilder allows extensions to add additional parameters to the +// Import request. +type ImportOptsBuilder interface { + ToBackupImportMap() (map[string]any, error) +} + // ImportOpts contains options for importing a Backup. This object is passed to // the backups.ImportBackup function. type ImportOpts BackupRecord @@ -291,7 +303,7 @@ func (opts ImportOpts) ToBackupImportMap() (map[string]any, error) { // Import will import a Backup data to a backup based on the values in // ImportOpts. To extract the Backup object from the response, call the // Extract method on the ImportResult. -func Import(ctx context.Context, client *gophercloud.ServiceClient, opts ImportOpts) (r ImportResult) { +func Import(ctx context.Context, client *gophercloud.ServiceClient, opts ImportOptsBuilder) (r ImportResult) { b, err := opts.ToBackupImportMap() if err != nil { r.Err = err diff --git a/openstack/blockstorage/v2/backups/results.go b/openstack/blockstorage/v2/backups/results.go index 77620de917..e5eb72a705 100644 --- a/openstack/blockstorage/v2/backups/results.go +++ b/openstack/blockstorage/v2/backups/results.go @@ -121,7 +121,7 @@ func (r BackupPage) IsEmpty() (bool, error) { return len(volumes) == 0, err } -func (page BackupPage) NextPageURL() (string, error) { +func (page BackupPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"backups_links"` } @@ -156,11 +156,11 @@ func (r commonResult) Extract() (*Backup, error) { } func (r commonResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "backup") + return r.ExtractIntoStructPtr(v, "backup") } func ExtractBackupsInto(r pagination.Page, v any) error { - return r.(BackupPage).Result.ExtractIntoSlicePtr(v, "backups") + return r.(BackupPage).ExtractIntoSlicePtr(v, "backups") } // RestoreResult contains the response body and error from a restore request. @@ -189,7 +189,7 @@ func (r RestoreResult) Extract() (*Restore, error) { } func (r RestoreResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "restore") + return r.ExtractIntoStructPtr(v, "restore") } // ExportResult contains the response body and error from an export request. @@ -214,7 +214,7 @@ func (r ExportResult) Extract() (*BackupRecord, error) { } func (r ExportResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "backup-record") + return r.ExtractIntoStructPtr(v, "backup-record") } // ImportResponse struct contains the response of the Backup Import action. @@ -236,7 +236,7 @@ func (r ImportResult) Extract() (*ImportResponse, error) { } func (r ImportResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "backup") + return r.ExtractIntoStructPtr(v, "backup") } // ImportBackup contains all the information to import a Cinder Backup. diff --git a/openstack/blockstorage/v2/backups/testing/fixtures_test.go b/openstack/blockstorage/v2/backups/testing/fixtures_test.go index 2f24173418..f09e16c955 100644 --- a/openstack/blockstorage/v2/backups/testing/fixtures_test.go +++ b/openstack/blockstorage/v2/backups/testing/fixtures_test.go @@ -9,7 +9,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v2/backups" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) const ListResponse = ` @@ -194,10 +194,10 @@ var ( backupURL, _ = json.Marshal(backupImport) ) -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/backups", func(w http.ResponseWriter, r *http.Request) { +func MockListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/backups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -208,19 +208,19 @@ func MockListResponse(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, ListResponse, th.Server.URL) + fmt.Fprintf(w, ListResponse, fakeServer.Server.URL) case "1": - fmt.Fprintf(w, `{"backups": []}`) + fmt.Fprint(w, `{"backups": []}`) default: t.Fatalf("Unexpected marker: [%s]", marker) } }) } -func MockListDetailResponse(t *testing.T) { - th.Mux.HandleFunc("/backups/detail", func(w http.ResponseWriter, r *http.Request) { +func MockListDetailResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/backups/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -231,30 +231,30 @@ func MockListDetailResponse(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, ListDetailResponse, th.Server.URL) + fmt.Fprintf(w, ListDetailResponse, fakeServer.Server.URL) case "1": - fmt.Fprintf(w, `{"backups": []}`) + fmt.Fprint(w, `{"backups": []}`) default: t.Fatalf("Unexpected marker: [%s]", marker) } }) } -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { +func MockGetResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) } -func MockCreateResponse(t *testing.T) { - th.Mux.HandleFunc("/backups", func(w http.ResponseWriter, r *http.Request) { +func MockCreateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/backups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, CreateRequest) @@ -262,14 +262,14 @@ func MockCreateResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) } -func MockRestoreResponse(t *testing.T) { - th.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22/restore", func(w http.ResponseWriter, r *http.Request) { +func MockRestoreResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22/restore", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, RestoreRequest) @@ -277,35 +277,35 @@ func MockRestoreResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, RestoreResponse) + fmt.Fprint(w, RestoreResponse) }) } -func MockDeleteResponse(t *testing.T) { - th.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { +func MockDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) } -func MockExportResponse(t *testing.T) { - th.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22/export_record", func(w http.ResponseWriter, r *http.Request) { +func MockExportResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22/export_record", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ExportResponse) + fmt.Fprint(w, ExportResponse) }) } -func MockImportResponse(t *testing.T) { - th.Mux.HandleFunc("/backups/import_record", func(w http.ResponseWriter, r *http.Request) { +func MockImportResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/backups/import_record", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ImportRequest) @@ -313,15 +313,15 @@ func MockImportResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ImportResponse) + fmt.Fprint(w, ImportResponse) }) } // MockResetStatusResponse provides mock response for reset backup status API call -func MockResetStatusResponse(t *testing.T) { - th.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22/action", func(w http.ResponseWriter, r *http.Request) { +func MockResetStatusResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestJSONRequest(t, r, ResetRequest) @@ -330,10 +330,10 @@ func MockResetStatusResponse(t *testing.T) { } // MockForceDeleteResponse provides mock response for force delete backup API call -func MockForceDeleteResponse(t *testing.T) { - th.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22/action", func(w http.ResponseWriter, r *http.Request) { +func MockForceDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestJSONRequest(t, r, ForceDeleteRequest) diff --git a/openstack/blockstorage/v2/backups/testing/requests_test.go b/openstack/blockstorage/v2/backups/testing/requests_test.go index f34bb5f775..fc6c2b2445 100644 --- a/openstack/blockstorage/v2/backups/testing/requests_test.go +++ b/openstack/blockstorage/v2/backups/testing/requests_test.go @@ -13,14 +13,14 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) count := 0 - err := backups.List(client.ServiceClient(), &backups.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := backups.List(client.ServiceClient(fakeServer), &backups.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := backups.ExtractBackups(page) if err != nil { @@ -53,14 +53,14 @@ func TestList(t *testing.T) { } func TestListDetail(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListDetailResponse(t) + MockListDetailResponse(t, fakeServer) count := 0 - err := backups.ListDetail(client.ServiceClient(), &backups.ListDetailOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := backups.ListDetail(client.ServiceClient(fakeServer), &backups.ListDetailOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := backups.ExtractBackups(page) if err != nil { @@ -103,68 +103,68 @@ func TestListDetail(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetResponse(t) + MockGetResponse(t, fakeServer) - v, err := backups.Get(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() + v, err := backups.Get(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, v.Name, "backup-001") - th.AssertEquals(t, v.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") + th.AssertEquals(t, "backup-001", v.Name) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", v.ID) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockCreateResponse(t) + MockCreateResponse(t, fakeServer) options := backups.CreateOpts{VolumeID: "1234", Name: "backup-001"} - n, err := backups.Create(context.TODO(), client.ServiceClient(), options).Extract() + n, err := backups.Create(context.TODO(), client.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.VolumeID, "1234") - th.AssertEquals(t, n.Name, "backup-001") - th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") + th.AssertEquals(t, "1234", n.VolumeID) + th.AssertEquals(t, "backup-001", n.Name) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", n.ID) } func TestRestore(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockRestoreResponse(t) + MockRestoreResponse(t, fakeServer) options := backups.RestoreOpts{VolumeID: "1234", Name: "vol-001"} - n, err := backups.RestoreFromBackup(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", options).Extract() + n, err := backups.RestoreFromBackup(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22", options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.VolumeID, "1234") - th.AssertEquals(t, n.VolumeName, "vol-001") - th.AssertEquals(t, n.BackupID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") + th.AssertEquals(t, "1234", n.VolumeID) + th.AssertEquals(t, "vol-001", n.VolumeName) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", n.BackupID) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDeleteResponse(t) + MockDeleteResponse(t, fakeServer) - res := backups.Delete(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22") + res := backups.Delete(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22") th.AssertNoErr(t, res.Err) } func TestExport(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockExportResponse(t) + MockExportResponse(t, fakeServer) - n, err := backups.Export(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() + n, err := backups.Export(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.BackupService, "cinder.backup.drivers.swift.SwiftBackupDriver") + th.AssertEquals(t, "cinder.backup.drivers.swift.SwiftBackupDriver", n.BackupService) th.AssertDeepEquals(t, n.BackupURL, backupURL) tmp := backups.ImportBackup{} @@ -174,40 +174,40 @@ func TestExport(t *testing.T) { } func TestImport(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockImportResponse(t) + MockImportResponse(t, fakeServer) options := backups.ImportOpts{ BackupService: "cinder.backup.drivers.swift.SwiftBackupDriver", BackupURL: backupURL, } - n, err := backups.Import(context.TODO(), client.ServiceClient(), options).Extract() + n, err := backups.Import(context.TODO(), client.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", n.ID) } func TestResetStatus(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockResetStatusResponse(t) + MockResetStatusResponse(t, fakeServer) opts := &backups.ResetStatusOpts{ Status: "error", } - res := backups.ResetStatus(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", opts) + res := backups.ResetStatus(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22", opts) th.AssertNoErr(t, res.Err) } func TestForceDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockForceDeleteResponse(t) + MockForceDeleteResponse(t, fakeServer) - res := backups.ForceDelete(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22") + res := backups.ForceDelete(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22") th.AssertNoErr(t, res.Err) } diff --git a/openstack/blockstorage/v2/limits/testing/fixtures_test.go b/openstack/blockstorage/v2/limits/testing/fixtures_test.go index c3f8bf803d..44d9166d74 100644 --- a/openstack/blockstorage/v2/limits/testing/fixtures_test.go +++ b/openstack/blockstorage/v2/limits/testing/fixtures_test.go @@ -118,12 +118,12 @@ var LimitsResult = limits.Limits{ // HandleGetSuccessfully configures the test server to respond to a Get request // for a limit. -func HandleGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/limits", func(w http.ResponseWriter, r *http.Request) { +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/limits", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } diff --git a/openstack/blockstorage/v2/limits/testing/requests_test.go b/openstack/blockstorage/v2/limits/testing/requests_test.go index a7b6c0a503..660c84a02e 100644 --- a/openstack/blockstorage/v2/limits/testing/requests_test.go +++ b/openstack/blockstorage/v2/limits/testing/requests_test.go @@ -10,11 +10,11 @@ import ( ) func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSuccessfully(t, fakeServer) - actual, err := limits.Get(context.TODO(), client.ServiceClient()).Extract() + actual, err := limits.Get(context.TODO(), client.ServiceClient(fakeServer)).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &LimitsResult, actual) } diff --git a/openstack/blockstorage/v2/quotasets/results.go b/openstack/blockstorage/v2/quotasets/results.go index e75aea5b51..179427f4b2 100644 --- a/openstack/blockstorage/v2/quotasets/results.go +++ b/openstack/blockstorage/v2/quotasets/results.go @@ -111,6 +111,47 @@ type QuotaUsageSet struct { // Note: allocated attribute is available only when nested quota is // enabled. Groups QuotaUsage `json:"groups"` + + // Extra is a collection of key/values that has the size (GB) usage information + // per volume_type. Note: allocated attribute is available only when nested + // quota is enabled. + Extra map[string]QuotaUsage `json:"-"` +} + +// UnmarshalJSON is used on QuotaUsageSet to unmarshal extra keys that are +// used to represent QuotaUsage per volume_type. +func (r *QuotaUsageSet) UnmarshalJSON(b []byte) error { + type tmp QuotaUsageSet + var s struct { + tmp + Extra map[string]QuotaUsage `json:"extra"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = QuotaUsageSet(s.tmp) + + var result any + err = json.Unmarshal(b, &result) + if err != nil { + return err + } + + // process remaining items as separate QuotaUsage objects. + if resultMap, ok := result.(map[string]any); ok { + tmpb, err := json.Marshal(gophercloud.RemainingKeys(QuotaUsageSet{}, resultMap)) + if err != nil { + return err + } + + err = json.Unmarshal(tmpb, &r.Extra) + if err != nil { + return err + } + } + + return err } // QuotaUsage is a set of details about a single operational limit that allows diff --git a/openstack/blockstorage/v2/quotasets/testing/fixtures_test.go b/openstack/blockstorage/v2/quotasets/testing/fixtures_test.go index 066e1729c8..ad59147988 100644 --- a/openstack/blockstorage/v2/quotasets/testing/fixtures_test.go +++ b/openstack/blockstorage/v2/quotasets/testing/fixtures_test.go @@ -75,8 +75,23 @@ var getUsageExpectedJSONBody = ` "in_use": 40, "limit": 41, "reserved": 42 + }, + "gigabytes_hdd" : { + "in_use": 50, + "limit": 51, + "reserved": 52 + }, + "volumes_hdd" : { + "in_use": 53, + "limit": 54, + "reserved": 55 + }, + "snapshots_hdd": { + "in_use": 56, + "limit": 57, + "reserved": 58 } - } + } }` var getUsageExpectedQuotaSet = quotasets.QuotaUsageSet{ @@ -88,6 +103,11 @@ var getUsageExpectedQuotaSet = quotasets.QuotaUsageSet{ Backups: quotasets.QuotaUsage{InUse: 27, Limit: 28, Reserved: 29}, BackupGigabytes: quotasets.QuotaUsage{InUse: 30, Limit: 31, Reserved: 32}, Groups: quotasets.QuotaUsage{InUse: 40, Limit: 41, Reserved: 42}, + Extra: map[string]quotasets.QuotaUsage{ + "gigabytes_hdd": {InUse: 50, Limit: 51, Reserved: 52}, + "volumes_hdd": {InUse: 53, Limit: 54, Reserved: 55}, + "snapshots_hdd": {InUse: 56, Limit: 57, Reserved: 58}, + }, } var fullUpdateExpectedJSONBody = ` @@ -147,15 +167,15 @@ var partialUpdateOpts = quotasets.UpdateOpts{ Extra: make(map[string]any), } -var partiualUpdateExpectedQuotaSet = quotasets.QuotaSet{ +var partialUpdateExpectedQuotaSet = quotasets.QuotaSet{ Volumes: 200, Extra: make(map[string]any), } // HandleSuccessfulRequest configures the test server to respond to an HTTP request. -func HandleSuccessfulRequest(t *testing.T, httpMethod, uriPath, jsonOutput string, uriQueryParams map[string]string) { +func HandleSuccessfulRequest(t *testing.T, fakeServer th.FakeServer, httpMethod, uriPath, jsonOutput string, uriQueryParams map[string]string) { - th.Mux.HandleFunc(uriPath, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(uriPath, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, httpMethod) th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") @@ -164,13 +184,13 @@ func HandleSuccessfulRequest(t *testing.T, httpMethod, uriPath, jsonOutput strin th.TestFormValues(t, r, uriQueryParams) } - fmt.Fprintf(w, jsonOutput) + fmt.Fprint(w, jsonOutput) }) } // HandleDeleteSuccessfully tests quotaset deletion. -func HandleDeleteSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-quota-sets/"+FirstTenantID, func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-quota-sets/"+FirstTenantID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) diff --git a/openstack/blockstorage/v2/quotasets/testing/requests_test.go b/openstack/blockstorage/v2/quotasets/testing/requests_test.go index 563b2d1d3d..5e40907675 100644 --- a/openstack/blockstorage/v2/quotasets/testing/requests_test.go +++ b/openstack/blockstorage/v2/quotasets/testing/requests_test.go @@ -11,71 +11,71 @@ import ( ) func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() uriQueryParms := map[string]string{} - HandleSuccessfulRequest(t, "GET", "/os-quota-sets/"+FirstTenantID, getExpectedJSONBody, uriQueryParms) - actual, err := quotasets.Get(context.TODO(), client.ServiceClient(), FirstTenantID).Extract() + HandleSuccessfulRequest(t, fakeServer, "GET", "/os-quota-sets/"+FirstTenantID, getExpectedJSONBody, uriQueryParms) + actual, err := quotasets.Get(context.TODO(), client.ServiceClient(fakeServer), FirstTenantID).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &getExpectedQuotaSet, actual) } func TestGetUsage(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() uriQueryParms := map[string]string{"usage": "true"} - HandleSuccessfulRequest(t, "GET", "/os-quota-sets/"+FirstTenantID, getUsageExpectedJSONBody, uriQueryParms) - actual, err := quotasets.GetUsage(context.TODO(), client.ServiceClient(), FirstTenantID).Extract() + HandleSuccessfulRequest(t, fakeServer, "GET", "/os-quota-sets/"+FirstTenantID, getUsageExpectedJSONBody, uriQueryParms) + actual, err := quotasets.GetUsage(context.TODO(), client.ServiceClient(fakeServer), FirstTenantID).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, getUsageExpectedQuotaSet, actual) } func TestFullUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() uriQueryParms := map[string]string{} - HandleSuccessfulRequest(t, "PUT", "/os-quota-sets/"+FirstTenantID, fullUpdateExpectedJSONBody, uriQueryParms) - actual, err := quotasets.Update(context.TODO(), client.ServiceClient(), FirstTenantID, fullUpdateOpts).Extract() + HandleSuccessfulRequest(t, fakeServer, "PUT", "/os-quota-sets/"+FirstTenantID, fullUpdateExpectedJSONBody, uriQueryParms) + actual, err := quotasets.Update(context.TODO(), client.ServiceClient(fakeServer), FirstTenantID, fullUpdateOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &fullUpdateExpectedQuotaSet, actual) } func TestPartialUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() uriQueryParms := map[string]string{} - HandleSuccessfulRequest(t, "PUT", "/os-quota-sets/"+FirstTenantID, partialUpdateExpectedJSONBody, uriQueryParms) - actual, err := quotasets.Update(context.TODO(), client.ServiceClient(), FirstTenantID, partialUpdateOpts).Extract() + HandleSuccessfulRequest(t, fakeServer, "PUT", "/os-quota-sets/"+FirstTenantID, partialUpdateExpectedJSONBody, uriQueryParms) + actual, err := quotasets.Update(context.TODO(), client.ServiceClient(fakeServer), FirstTenantID, partialUpdateOpts).Extract() th.AssertNoErr(t, err) - th.CheckDeepEquals(t, &partiualUpdateExpectedQuotaSet, actual) + th.CheckDeepEquals(t, &partialUpdateExpectedQuotaSet, actual) } type ErrorUpdateOpts quotasets.UpdateOpts func (opts ErrorUpdateOpts) ToBlockStorageQuotaUpdateMap() (map[string]any, error) { - return nil, errors.New("This is an error") + return nil, errors.New("this is an error") } func TestErrorInToBlockStorageQuotaUpdateMap(t *testing.T) { opts := &ErrorUpdateOpts{} - th.SetupHTTP() - defer th.TeardownHTTP() - HandleSuccessfulRequest(t, "PUT", "/os-quota-sets/"+FirstTenantID, "", nil) - _, err := quotasets.Update(context.TODO(), client.ServiceClient(), FirstTenantID, opts).Extract() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleSuccessfulRequest(t, fakeServer, "PUT", "/os-quota-sets/"+FirstTenantID, "", nil) + _, err := quotasets.Update(context.TODO(), client.ServiceClient(fakeServer), FirstTenantID, opts).Extract() if err == nil { t.Fatal("Error handling failed") } } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteSuccessfully(t, fakeServer) - err := quotasets.Delete(context.TODO(), client.ServiceClient(), FirstTenantID).ExtractErr() + err := quotasets.Delete(context.TODO(), client.ServiceClient(fakeServer), FirstTenantID).ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/blockstorage/v2/schedulerstats/testing/fixtures_test.go b/openstack/blockstorage/v2/schedulerstats/testing/fixtures_test.go index 6f0317ef60..b372811312 100644 --- a/openstack/blockstorage/v2/schedulerstats/testing/fixtures_test.go +++ b/openstack/blockstorage/v2/schedulerstats/testing/fixtures_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v2/schedulerstats" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" "github.com/gophercloud/gophercloud/v2/testhelper/client" ) @@ -93,10 +93,10 @@ var ( } ) -func HandleStoragePoolsListSuccessfully(t *testing.T) { - testhelper.Mux.HandleFunc("/scheduler-stats/get_pools", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "GET") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandleStoragePoolsListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/scheduler-stats/get_pools", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") @@ -104,9 +104,9 @@ func HandleStoragePoolsListSuccessfully(t *testing.T) { t.Errorf("Failed to parse request form %v", err) } if r.FormValue("detail") == "true" { - fmt.Fprintf(w, StoragePoolsListBodyDetail) + fmt.Fprint(w, StoragePoolsListBodyDetail) } else { - fmt.Fprintf(w, StoragePoolsListBody) + fmt.Fprint(w, StoragePoolsListBody) } }) } diff --git a/openstack/blockstorage/v2/schedulerstats/testing/requests_test.go b/openstack/blockstorage/v2/schedulerstats/testing/requests_test.go index 709ddeac58..35bdb7ea68 100644 --- a/openstack/blockstorage/v2/schedulerstats/testing/requests_test.go +++ b/openstack/blockstorage/v2/schedulerstats/testing/requests_test.go @@ -6,32 +6,32 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v2/schedulerstats" "github.com/gophercloud/gophercloud/v2/pagination" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestListStoragePoolsDetail(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - HandleStoragePoolsListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleStoragePoolsListSuccessfully(t, fakeServer) pages := 0 - err := schedulerstats.List(client.ServiceClient(), schedulerstats.ListOpts{Detail: true}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := schedulerstats.List(client.ServiceClient(fakeServer), schedulerstats.ListOpts{Detail: true}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := schedulerstats.ExtractStoragePools(page) - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) if len(actual) != 2 { t.Fatalf("Expected 2 backends, got %d", len(actual)) } - testhelper.CheckDeepEquals(t, StoragePoolFake1, actual[0]) - testhelper.CheckDeepEquals(t, StoragePoolFake2, actual[1]) + th.CheckDeepEquals(t, StoragePoolFake1, actual[0]) + th.CheckDeepEquals(t, StoragePoolFake2, actual[1]) return true, nil }) - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) if pages != 1 { t.Errorf("Expected 1 page, saw %d", pages) diff --git a/openstack/blockstorage/v2/services/testing/fixtures_test.go b/openstack/blockstorage/v2/services/testing/fixtures_test.go index a396abc39e..398ad96c44 100644 --- a/openstack/blockstorage/v2/services/testing/fixtures_test.go +++ b/openstack/blockstorage/v2/services/testing/fixtures_test.go @@ -86,12 +86,12 @@ var ThirdFakeService = services.Service{ } // HandleListSuccessfully configures the test server to respond to a List request. -func HandleListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-services", func(w http.ResponseWriter, r *http.Request) { +func HandleListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-services", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ServiceListBody) + fmt.Fprint(w, ServiceListBody) }) } diff --git a/openstack/blockstorage/v2/services/testing/requests_test.go b/openstack/blockstorage/v2/services/testing/requests_test.go index 8ce1266d7e..9fbbe61201 100644 --- a/openstack/blockstorage/v2/services/testing/requests_test.go +++ b/openstack/blockstorage/v2/services/testing/requests_test.go @@ -6,17 +6,17 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v2/services" "github.com/gophercloud/gophercloud/v2/pagination" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestListServices(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - HandleListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer) pages := 0 - err := services.List(client.ServiceClient(), services.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := services.List(client.ServiceClient(fakeServer), services.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := services.ExtractServices(page) @@ -27,14 +27,14 @@ func TestListServices(t *testing.T) { if len(actual) != 3 { t.Fatalf("Expected 3 services, got %d", len(actual)) } - testhelper.CheckDeepEquals(t, FirstFakeService, actual[0]) - testhelper.CheckDeepEquals(t, SecondFakeService, actual[1]) - testhelper.CheckDeepEquals(t, ThirdFakeService, actual[2]) + th.CheckDeepEquals(t, FirstFakeService, actual[0]) + th.CheckDeepEquals(t, SecondFakeService, actual[1]) + th.CheckDeepEquals(t, ThirdFakeService, actual[2]) return true, nil }) - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) if pages != 1 { t.Errorf("Expected 1 page, saw %d", pages) diff --git a/openstack/blockstorage/v2/snapshots/testing/fixtures_test.go b/openstack/blockstorage/v2/snapshots/testing/fixtures_test.go index 2c00c8a05f..964dfff2e6 100644 --- a/openstack/blockstorage/v2/snapshots/testing/fixtures_test.go +++ b/openstack/blockstorage/v2/snapshots/testing/fixtures_test.go @@ -6,18 +6,18 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots", func(w http.ResponseWriter, r *http.Request) { +func MockListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/snapshots", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "snapshots": [ { @@ -44,14 +44,14 @@ func MockListResponse(t *testing.T) { }) } -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { +func MockGetResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/snapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "snapshot": { "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", @@ -67,10 +67,10 @@ func MockGetResponse(t *testing.T) { }) } -func MockCreateResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots", func(w http.ResponseWriter, r *http.Request) { +func MockCreateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/snapshots", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -85,7 +85,7 @@ func MockCreateResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "snapshot": { "volume_id": "1234", @@ -102,10 +102,10 @@ func MockCreateResponse(t *testing.T) { }) } -func MockUpdateMetadataResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots/123/metadata", func(w http.ResponseWriter, r *http.Request) { +func MockUpdateMetadataResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/snapshots/123/metadata", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestJSONRequest(t, r, ` { @@ -115,7 +115,7 @@ func MockUpdateMetadataResponse(t *testing.T) { } `) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "metadata": { "key": "v1" @@ -125,10 +125,10 @@ func MockUpdateMetadataResponse(t *testing.T) { }) } -func MockDeleteResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { +func MockDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/snapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) } diff --git a/openstack/blockstorage/v2/snapshots/testing/requests_test.go b/openstack/blockstorage/v2/snapshots/testing/requests_test.go index 07d74a14a7..7409e1f560 100644 --- a/openstack/blockstorage/v2/snapshots/testing/requests_test.go +++ b/openstack/blockstorage/v2/snapshots/testing/requests_test.go @@ -12,14 +12,14 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) count := 0 - err := snapshots.List(client.ServiceClient(), &snapshots.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := snapshots.List(client.ServiceClient(fakeServer), &snapshots.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := snapshots.ExtractSnapshots(page) if err != nil { @@ -60,38 +60,38 @@ func TestList(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetResponse(t) + MockGetResponse(t, fakeServer) - v, err := snapshots.Get(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() + v, err := snapshots.Get(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, v.Name, "snapshot-001") - th.AssertEquals(t, v.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") + th.AssertEquals(t, "snapshot-001", v.Name) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", v.ID) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockCreateResponse(t) + MockCreateResponse(t, fakeServer) options := snapshots.CreateOpts{VolumeID: "1234", Name: "snapshot-001"} - n, err := snapshots.Create(context.TODO(), client.ServiceClient(), options).Extract() + n, err := snapshots.Create(context.TODO(), client.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.VolumeID, "1234") - th.AssertEquals(t, n.Name, "snapshot-001") - th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") + th.AssertEquals(t, "1234", n.VolumeID) + th.AssertEquals(t, "snapshot-001", n.Name) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", n.ID) } func TestUpdateMetadata(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockUpdateMetadataResponse(t) + MockUpdateMetadataResponse(t, fakeServer) expected := map[string]any{"key": "v1"} @@ -101,18 +101,18 @@ func TestUpdateMetadata(t *testing.T) { }, } - actual, err := snapshots.UpdateMetadata(context.TODO(), client.ServiceClient(), "123", options).ExtractMetadata() + actual, err := snapshots.UpdateMetadata(context.TODO(), client.ServiceClient(fakeServer), "123", options).ExtractMetadata() th.AssertNoErr(t, err) th.AssertDeepEquals(t, actual, expected) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDeleteResponse(t) + MockDeleteResponse(t, fakeServer) - res := snapshots.Delete(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22") + res := snapshots.Delete(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22") th.AssertNoErr(t, res.Err) } diff --git a/openstack/blockstorage/v2/transfers/requests.go b/openstack/blockstorage/v2/transfers/requests.go index 9c762cfe1e..4d146ff235 100644 --- a/openstack/blockstorage/v2/transfers/requests.go +++ b/openstack/blockstorage/v2/transfers/requests.go @@ -7,6 +7,12 @@ import ( "github.com/gophercloud/gophercloud/v2/pagination" ) +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToCreateMap() (map[string]any, error) +} + // CreateOpts contains options for a Volume transfer. type CreateOpts struct { // The ID of the volume to transfer. @@ -23,7 +29,7 @@ func (opts CreateOpts) ToCreateMap() (map[string]any, error) { } // Create will create a volume tranfer request based on the values in CreateOpts. -func Create(ctx context.Context, client *gophercloud.ServiceClient, opts CreateOpts) (r CreateResult) { +func Create(ctx context.Context, client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { b, err := opts.ToCreateMap() if err != nil { r.Err = err @@ -36,6 +42,12 @@ func Create(ctx context.Context, client *gophercloud.ServiceClient, opts CreateO return } +// AcceptOptsBuilder allows extensions to add additional parameters to the +// Accept request. +type AcceptOptsBuilder interface { + ToAcceptMap() (map[string]any, error) +} + // AcceptOpts contains options for a Volume transfer accept reqeust. type AcceptOpts struct { // The auth key of the volume transfer to accept. @@ -49,7 +61,7 @@ func (opts AcceptOpts) ToAcceptMap() (map[string]any, error) { } // Accept will accept a volume tranfer request based on the values in AcceptOpts. -func Accept(ctx context.Context, client *gophercloud.ServiceClient, id string, opts AcceptOpts) (r CreateResult) { +func Accept(ctx context.Context, client *gophercloud.ServiceClient, id string, opts AcceptOptsBuilder) (r CreateResult) { b, err := opts.ToAcceptMap() if err != nil { r.Err = err diff --git a/openstack/blockstorage/v2/transfers/results.go b/openstack/blockstorage/v2/transfers/results.go index 42885c27ef..8b8894dd86 100644 --- a/openstack/blockstorage/v2/transfers/results.go +++ b/openstack/blockstorage/v2/transfers/results.go @@ -49,7 +49,7 @@ func (r commonResult) Extract() (*Transfer, error) { // ExtractInto converts our response data into a transfer struct func (r commonResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "transfer") + return r.ExtractIntoStructPtr(v, "transfer") } // CreateResult contains the response body and error from a Create request. @@ -76,7 +76,7 @@ func ExtractTransfers(r pagination.Page) ([]Transfer, error) { // ExtractTransfersInto similar to ExtractInto but operates on a `list` of transfers func ExtractTransfersInto(r pagination.Page, v any) error { - return r.(TransferPage).Result.ExtractIntoSlicePtr(v, "transfers") + return r.(TransferPage).ExtractIntoSlicePtr(v, "transfers") } // TransferPage is a pagination.pager that is returned from a call to the List function. @@ -94,7 +94,7 @@ func (r TransferPage) IsEmpty() (bool, error) { return len(transfers) == 0, err } -func (page TransferPage) NextPageURL() (string, error) { +func (page TransferPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"transfers_links"` } diff --git a/openstack/blockstorage/v2/transfers/testing/fixtures_test.go b/openstack/blockstorage/v2/transfers/testing/fixtures_test.go index 6a150508eb..f24b7fa82c 100644 --- a/openstack/blockstorage/v2/transfers/testing/fixtures_test.go +++ b/openstack/blockstorage/v2/transfers/testing/fixtures_test.go @@ -159,32 +159,32 @@ var AcceptResponse = transfers.Transfer{ }, } -func HandleCreateTransfer(t *testing.T) { - th.Mux.HandleFunc("/os-volume-transfer", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateTransfer(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-volume-transfer", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) } -func HandleAcceptTransfer(t *testing.T) { - th.Mux.HandleFunc("/os-volume-transfer/b8913bfd-a4d3-4ec5-bd8b-fe2dbeef9f4f/accept", func(w http.ResponseWriter, r *http.Request) { +func HandleAcceptTransfer(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-volume-transfer/b8913bfd-a4d3-4ec5-bd8b-fe2dbeef9f4f/accept", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") th.TestJSONRequest(t, r, AcceptTransferRequest) w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, AcceptTransferResponse) + fmt.Fprint(w, AcceptTransferResponse) }) } -func HandleDeleteTransfer(t *testing.T) { - th.Mux.HandleFunc("/os-volume-transfer/b8913bfd-a4d3-4ec5-bd8b-fe2dbeef9f4f", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteTransfer(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-volume-transfer/b8913bfd-a4d3-4ec5-bd8b-fe2dbeef9f4f", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -192,25 +192,25 @@ func HandleDeleteTransfer(t *testing.T) { }) } -func HandleListTransfers(t *testing.T) { - th.Mux.HandleFunc("/os-volume-transfer/detail", func(w http.ResponseWriter, r *http.Request) { +func HandleListTransfers(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-volume-transfer/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") th.TestFormValues(t, r, map[string]string{"all_tenants": "true"}) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } -func HandleGetTransfer(t *testing.T) { - th.Mux.HandleFunc("/os-volume-transfer/b8913bfd-a4d3-4ec5-bd8b-fe2dbeef9f4f", func(w http.ResponseWriter, r *http.Request) { +func HandleGetTransfer(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-volume-transfer/b8913bfd-a4d3-4ec5-bd8b-fe2dbeef9f4f", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } diff --git a/openstack/blockstorage/v2/transfers/testing/requests_test.go b/openstack/blockstorage/v2/transfers/testing/requests_test.go index 32501eb095..f768e5b7d5 100644 --- a/openstack/blockstorage/v2/transfers/testing/requests_test.go +++ b/openstack/blockstorage/v2/transfers/testing/requests_test.go @@ -11,44 +11,44 @@ import ( ) func TestCreateTransfer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateTransfer(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateTransfer(t, fakeServer) - actual, err := transfers.Create(context.TODO(), client.ServiceClient(), TransferRequest).Extract() + actual, err := transfers.Create(context.TODO(), client.ServiceClient(fakeServer), TransferRequest).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, TransferResponse, *actual) } func TestAcceptTransfer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAcceptTransfer(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAcceptTransfer(t, fakeServer) - actual, err := transfers.Accept(context.TODO(), client.ServiceClient(), TransferResponse.ID, AcceptRequest).Extract() + actual, err := transfers.Accept(context.TODO(), client.ServiceClient(fakeServer), TransferResponse.ID, AcceptRequest).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, AcceptResponse, *actual) } func TestDeleteTransfer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteTransfer(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteTransfer(t, fakeServer) - err := transfers.Delete(context.TODO(), client.ServiceClient(), TransferResponse.ID).ExtractErr() + err := transfers.Delete(context.TODO(), client.ServiceClient(fakeServer), TransferResponse.ID).ExtractErr() th.AssertNoErr(t, err) } func TestListTransfers(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListTransfers(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListTransfers(t, fakeServer) expectedResponse := TransferListResponse expectedResponse[0].AuthKey = "" count := 0 - err := transfers.List(client.ServiceClient(), &transfers.ListOpts{AllTenants: true}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := transfers.List(client.ServiceClient(fakeServer), &transfers.ListOpts{AllTenants: true}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := transfers.ExtractTransfers(page) @@ -59,18 +59,18 @@ func TestListTransfers(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListTransfersAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListTransfers(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListTransfers(t, fakeServer) expectedResponse := TransferListResponse expectedResponse[0].AuthKey = "" - allPages, err := transfers.List(client.ServiceClient(), &transfers.ListOpts{AllTenants: true}).AllPages(context.TODO()) + allPages, err := transfers.List(client.ServiceClient(fakeServer), &transfers.ListOpts{AllTenants: true}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := transfers.ExtractTransfers(allPages) th.AssertNoErr(t, err) @@ -78,14 +78,14 @@ func TestListTransfersAllPages(t *testing.T) { } func TestGetTransfer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetTransfer(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetTransfer(t, fakeServer) expectedResponse := TransferResponse expectedResponse.AuthKey = "" - actual, err := transfers.Get(context.TODO(), client.ServiceClient(), TransferResponse.ID).Extract() + actual, err := transfers.Get(context.TODO(), client.ServiceClient(fakeServer), TransferResponse.ID).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, expectedResponse, *actual) } diff --git a/openstack/blockstorage/v2/volumes/requests.go b/openstack/blockstorage/v2/volumes/requests.go index 9d1e797fc9..62b36d2143 100644 --- a/openstack/blockstorage/v2/volumes/requests.go +++ b/openstack/blockstorage/v2/volumes/requests.go @@ -617,6 +617,12 @@ func SetImageMetadata(ctx context.Context, client *gophercloud.ServiceClient, id return } +// BootableOptsBuilder allows extensions to add additional parameters to the +// SetBootable request. +type BootableOptsBuilder interface { + ToBootableMap() (map[string]any, error) +} + // BootableOpts contains options for setting bootable status to a volume. type BootableOpts struct { // Enables or disables the bootable attribute. You can boot an instance from a bootable volume. @@ -630,7 +636,7 @@ func (opts BootableOpts) ToBootableMap() (map[string]any, error) { } // SetBootable will set bootable status on a volume based on the values in BootableOpts -func SetBootable(ctx context.Context, client *gophercloud.ServiceClient, id string, opts BootableOpts) (r SetBootableResult) { +func SetBootable(ctx context.Context, client *gophercloud.ServiceClient, id string, opts BootableOptsBuilder) (r SetBootableResult) { b, err := opts.ToBootableMap() if err != nil { r.Err = err @@ -691,6 +697,12 @@ func ChangeType(ctx context.Context, client *gophercloud.ServiceClient, id strin return } +// ReImageOptsBuilder allows extensions to add additional parameters to the +// ReImage request. +type ReImageOptsBuilder interface { + ToReImageMap() (map[string]any, error) +} + // ReImageOpts contains options for Re-image a volume. type ReImageOpts struct { // New image id @@ -705,7 +717,7 @@ func (opts ReImageOpts) ToReImageMap() (map[string]any, error) { } // ReImage will re-image a volume based on the values in ReImageOpts -func ReImage(ctx context.Context, client *gophercloud.ServiceClient, id string, opts ReImageOpts) (r ReImageResult) { +func ReImage(ctx context.Context, client *gophercloud.ServiceClient, id string, opts ReImageOptsBuilder) (r ReImageResult) { b, err := opts.ToReImageMap() if err != nil { r.Err = err diff --git a/openstack/blockstorage/v2/volumes/results.go b/openstack/blockstorage/v2/volumes/results.go index 061309e991..bffff2a2a2 100644 --- a/openstack/blockstorage/v2/volumes/results.go +++ b/openstack/blockstorage/v2/volumes/results.go @@ -117,7 +117,7 @@ func (r VolumePage) IsEmpty() (bool, error) { // NextPageURL uses the response's embedded link reference to navigate to the // next page of results. -func (r VolumePage) NextPageURL() (string, error) { +func (r VolumePage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"volumes_links"` } @@ -147,11 +147,11 @@ func (r commonResult) Extract() (*Volume, error) { } func (r commonResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "volume") + return r.ExtractIntoStructPtr(v, "volume") } func ExtractVolumesInto(r pagination.Page, v any) error { - return r.(VolumePage).Result.ExtractIntoSlicePtr(v, "volumes") + return r.(VolumePage).ExtractIntoSlicePtr(v, "volumes") } // CreateResult contains the response body and error from a Create request. diff --git a/openstack/blockstorage/v2/volumes/testing/fixtures_test.go b/openstack/blockstorage/v2/volumes/testing/fixtures_test.go index 80677d2e40..5b73310285 100644 --- a/openstack/blockstorage/v2/volumes/testing/fixtures_test.go +++ b/openstack/blockstorage/v2/volumes/testing/fixtures_test.go @@ -6,18 +6,18 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/detail", func(w http.ResponseWriter, r *http.Request) { +func MockListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volumes": [ { @@ -86,14 +86,14 @@ func MockListResponse(t *testing.T) { }) } -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { +func MockGetResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volume": { "volume_type": "lvmdriver-1", @@ -134,10 +134,10 @@ func MockGetResponse(t *testing.T) { }) } -func MockCreateResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes", func(w http.ResponseWriter, r *http.Request) { +func MockCreateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -152,7 +152,7 @@ func MockCreateResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volume": { "size": 75, @@ -179,20 +179,20 @@ func MockCreateResponse(t *testing.T) { }) } -func MockDeleteResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { +func MockDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) }) } -func MockUpdateResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { +func MockUpdateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volume": { "name": "vol-002" @@ -202,11 +202,11 @@ func MockUpdateResponse(t *testing.T) { }) } -func MockAttachResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", +func MockAttachResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -223,15 +223,15 @@ func MockAttachResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } -func MockBeginDetachingResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", +func MockBeginDetachingResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -243,15 +243,15 @@ func MockBeginDetachingResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } -func MockDetachResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", +func MockDetachResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -263,15 +263,15 @@ func MockDetachResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } -func MockUploadImageResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", +func MockUploadImageResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -288,7 +288,7 @@ func MockUploadImageResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "os-volume_upload_image": { "container_format": "bare", @@ -319,11 +319,11 @@ func MockUploadImageResponse(t *testing.T) { }) } -func MockReserveResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", +func MockReserveResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -335,15 +335,15 @@ func MockReserveResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } -func MockUnreserveResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", +func MockUnreserveResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -355,15 +355,15 @@ func MockUnreserveResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } -func MockInitializeConnectionResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", +func MockInitializeConnectionResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -386,7 +386,7 @@ func MockInitializeConnectionResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "connection_info": { "data": { "target_portals": [ @@ -416,11 +416,11 @@ func MockInitializeConnectionResponse(t *testing.T) { }) } -func MockTerminateConnectionResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", +func MockTerminateConnectionResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -443,15 +443,15 @@ func MockTerminateConnectionResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } -func MockExtendSizeResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", +func MockExtendSizeResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -466,23 +466,23 @@ func MockExtendSizeResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } -func MockForceDeleteResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22/action", func(w http.ResponseWriter, r *http.Request) { +func MockForceDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestBody(t, r, `{"os-force_delete":""}`) w.WriteHeader(http.StatusAccepted) }) } -func MockSetImageMetadataResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { +func MockSetImageMetadataResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -497,14 +497,14 @@ func MockSetImageMetadataResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } -func MockSetBootableResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { +func MockSetBootableResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -520,10 +520,10 @@ func MockSetBootableResponse(t *testing.T) { }) } -func MockReImageResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { +func MockReImageResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -540,11 +540,11 @@ func MockReImageResponse(t *testing.T) { }) } -func MockChangeTypeResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", +func MockChangeTypeResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -560,15 +560,15 @@ func MockChangeTypeResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } -func MockResetStatusResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", +func MockResetStatusResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestJSONRequest(t, r, ` { diff --git a/openstack/blockstorage/v2/volumes/testing/requests_test.go b/openstack/blockstorage/v2/volumes/testing/requests_test.go index 3d829a56ae..4ffccfbbfc 100644 --- a/openstack/blockstorage/v2/volumes/testing/requests_test.go +++ b/openstack/blockstorage/v2/volumes/testing/requests_test.go @@ -13,14 +13,14 @@ import ( ) func TestListWithExtensions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) count := 0 - err := volumes.List(client.ServiceClient(), &volumes.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := volumes.List(client.ServiceClient(fakeServer), &volumes.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := volumes.ExtractVolumes(page) if err != nil { @@ -97,12 +97,12 @@ func TestListWithExtensions(t *testing.T) { } func TestListAllWithExtensions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) - allPages, err := volumes.List(client.ServiceClient(), &volumes.ListOpts{}).AllPages(context.TODO()) + allPages, err := volumes.List(client.ServiceClient(fakeServer), &volumes.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) var actual []volumes.Volume @@ -113,12 +113,12 @@ func TestListAllWithExtensions(t *testing.T) { } func TestListAll(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) - allPages, err := volumes.List(client.ServiceClient(), &volumes.ListOpts{}).AllPages(context.TODO()) + allPages, err := volumes.List(client.ServiceClient(fakeServer), &volumes.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := volumes.ExtractVolumes(allPages) th.AssertNoErr(t, err) @@ -185,30 +185,30 @@ func TestListAll(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetResponse(t) + MockGetResponse(t, fakeServer) - v, err := volumes.Get(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() + v, err := volumes.Get(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, v.Name, "vol-001") - th.AssertEquals(t, v.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") + th.AssertEquals(t, "vol-001", v.Name) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", v.ID) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockCreateResponse(t) + MockCreateResponse(t, fakeServer) options := &volumes.CreateOpts{Size: 75, Name: "vol-001"} - n, err := volumes.Create(context.TODO(), client.ServiceClient(), options, nil).Extract() + n, err := volumes.Create(context.TODO(), client.ServiceClient(fakeServer), options, nil).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.Size, 75) - th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") + th.AssertEquals(t, 75, n.Size) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", n.ID) } func TestCreateSchedulerHints(t *testing.T) { @@ -246,84 +246,84 @@ func TestCreateSchedulerHints(t *testing.T) { } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDeleteResponse(t) + MockDeleteResponse(t, fakeServer) - res := volumes.Delete(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", volumes.DeleteOpts{}) + res := volumes.Delete(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22", volumes.DeleteOpts{}) th.AssertNoErr(t, res.Err) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockUpdateResponse(t) + MockUpdateResponse(t, fakeServer) var name = "vol-002" options := volumes.UpdateOpts{Name: &name} - v, err := volumes.Update(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", options).Extract() + v, err := volumes.Update(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22", options).Extract() th.AssertNoErr(t, err) th.CheckEquals(t, "vol-002", v.Name) } func TestGetWithExtensions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetResponse(t) + MockGetResponse(t, fakeServer) var v volumes.Volume - err := volumes.Get(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(&v) + err := volumes.Get(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(&v) th.AssertNoErr(t, err) th.AssertEquals(t, "304dc00909ac4d0da6c62d816bcb3459", v.TenantID) - err = volumes.Get(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(v) + err = volumes.Get(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(v) if err == nil { t.Errorf("Expected error when providing non-pointer struct") } } func TestAttach(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockAttachResponse(t) + MockAttachResponse(t, fakeServer) options := &volumes.AttachOpts{ MountPoint: "/mnt", Mode: "rw", InstanceUUID: "50902f4f-a974-46a0-85e9-7efc5e22dfdd", } - err := volumes.Attach(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() + err := volumes.Attach(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() th.AssertNoErr(t, err) } func TestBeginDetaching(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockBeginDetachingResponse(t) + MockBeginDetachingResponse(t, fakeServer) - err := volumes.BeginDetaching(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c").ExtractErr() + err := volumes.BeginDetaching(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c").ExtractErr() th.AssertNoErr(t, err) } func TestDetach(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDetachResponse(t) + MockDetachResponse(t, fakeServer) - err := volumes.Detach(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", &volumes.DetachOpts{}).ExtractErr() + err := volumes.Detach(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c", &volumes.DetachOpts{}).ExtractErr() th.AssertNoErr(t, err) } func TestUploadImage(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - MockUploadImageResponse(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + MockUploadImageResponse(t, fakeServer) options := &volumes.UploadImageOpts{ ContainerFormat: "bare", DiskFormat: "raw", @@ -331,7 +331,7 @@ func TestUploadImage(t *testing.T) { Force: true, } - actual, err := volumes.UploadImage(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).Extract() + actual, err := volumes.UploadImage(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c", options).Extract() th.AssertNoErr(t, err) expected := volumes.VolumeImage{ @@ -361,30 +361,30 @@ func TestUploadImage(t *testing.T) { } func TestReserve(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockReserveResponse(t) + MockReserveResponse(t, fakeServer) - err := volumes.Reserve(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c").ExtractErr() + err := volumes.Reserve(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c").ExtractErr() th.AssertNoErr(t, err) } func TestUnreserve(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockUnreserveResponse(t) + MockUnreserveResponse(t, fakeServer) - err := volumes.Unreserve(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c").ExtractErr() + err := volumes.Unreserve(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c").ExtractErr() th.AssertNoErr(t, err) } func TestInitializeConnection(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockInitializeConnectionResponse(t) + MockInitializeConnectionResponse(t, fakeServer) options := &volumes.InitializeConnectionOpts{ IP: "127.0.0.1", @@ -394,15 +394,15 @@ func TestInitializeConnection(t *testing.T) { Platform: "x86_64", OSType: "linux2", } - _, err := volumes.InitializeConnection(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).Extract() + _, err := volumes.InitializeConnection(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c", options).Extract() th.AssertNoErr(t, err) } func TestTerminateConnection(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockTerminateConnectionResponse(t) + MockTerminateConnectionResponse(t, fakeServer) options := &volumes.TerminateConnectionOpts{ IP: "127.0.0.1", @@ -412,39 +412,39 @@ func TestTerminateConnection(t *testing.T) { Platform: "x86_64", OSType: "linux2", } - err := volumes.TerminateConnection(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() + err := volumes.TerminateConnection(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() th.AssertNoErr(t, err) } func TestExtendSize(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockExtendSizeResponse(t) + MockExtendSizeResponse(t, fakeServer) options := &volumes.ExtendSizeOpts{ NewSize: 3, } - err := volumes.ExtendSize(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() + err := volumes.ExtendSize(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() th.AssertNoErr(t, err) } func TestForceDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockForceDeleteResponse(t) + MockForceDeleteResponse(t, fakeServer) - res := volumes.ForceDelete(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22") + res := volumes.ForceDelete(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22") th.AssertNoErr(t, res.Err) } func TestSetImageMetadata(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockSetImageMetadataResponse(t) + MockSetImageMetadataResponse(t, fakeServer) options := &volumes.ImageMetadataOpts{ Metadata: map[string]string{ @@ -452,59 +452,59 @@ func TestSetImageMetadata(t *testing.T) { }, } - err := volumes.SetImageMetadata(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() + err := volumes.SetImageMetadata(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() th.AssertNoErr(t, err) } func TestSetBootable(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockSetBootableResponse(t) + MockSetBootableResponse(t, fakeServer) options := volumes.BootableOpts{ Bootable: true, } - err := volumes.SetBootable(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() + err := volumes.SetBootable(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() th.AssertNoErr(t, err) } func TestReImage(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockReImageResponse(t) + MockReImageResponse(t, fakeServer) options := volumes.ReImageOpts{ ImageID: "71543ced-a8af-45b6-a5c4-a46282108a90", ReImageReserved: false, } - err := volumes.ReImage(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() + err := volumes.ReImage(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() th.AssertNoErr(t, err) } func TestChangeType(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockChangeTypeResponse(t) + MockChangeTypeResponse(t, fakeServer) options := &volumes.ChangeTypeOpts{ NewType: "ssd", MigrationPolicy: "on-demand", } - err := volumes.ChangeType(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() + err := volumes.ChangeType(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() th.AssertNoErr(t, err) } func TestResetStatus(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockResetStatusResponse(t) + MockResetStatusResponse(t, fakeServer) options := &volumes.ResetStatusOpts{ Status: "error", @@ -512,6 +512,6 @@ func TestResetStatus(t *testing.T) { MigrationStatus: "migrating", } - err := volumes.ResetStatus(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() + err := volumes.ResetStatus(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/blockstorage/v3/attachments/results.go b/openstack/blockstorage/v3/attachments/results.go index 8c572047ae..0ef8a66074 100644 --- a/openstack/blockstorage/v3/attachments/results.go +++ b/openstack/blockstorage/v3/attachments/results.go @@ -89,13 +89,13 @@ func (r commonResult) Extract() (*Attachment, error) { // ExtractInto converts our response data into a attachment struct. func (r commonResult) ExtractInto(a any) error { - return r.Result.ExtractIntoStructPtr(a, "attachment") + return r.ExtractIntoStructPtr(a, "attachment") } // ExtractAttachmentsInto similar to ExtractInto but operates on a List of // attachments. func ExtractAttachmentsInto(r pagination.Page, a any) error { - return r.(AttachmentPage).Result.ExtractIntoSlicePtr(a, "attachments") + return r.(AttachmentPage).ExtractIntoSlicePtr(a, "attachments") } // CreateResult contains the response body and error from a Create request. diff --git a/openstack/blockstorage/v3/attachments/testing/fixtures_test.go b/openstack/blockstorage/v3/attachments/testing/fixtures_test.go index d489ce16ef..6d35b5f304 100644 --- a/openstack/blockstorage/v3/attachments/testing/fixtures_test.go +++ b/openstack/blockstorage/v3/attachments/testing/fixtures_test.go @@ -9,7 +9,7 @@ import ( "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/attachments" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) var ( @@ -27,10 +27,10 @@ var ( } ) -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/attachments/detail", func(w http.ResponseWriter, r *http.Request) { +func MockListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/attachments/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -62,23 +62,23 @@ func MockListResponse(t *testing.T) { } ] } - `, th.Server.URL) + `, fakeServer.Server.URL) case "1": - fmt.Fprintf(w, `{"volumes": []}`) + fmt.Fprint(w, `{"volumes": []}`) default: t.Fatalf("Unexpected marker: [%s]", marker) } }) } -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc("/attachments/05551600-a936-4d4a-ba42-79a037c1-c91a", func(w http.ResponseWriter, r *http.Request) { +func MockGetResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/attachments/05551600-a936-4d4a-ba42-79a037c1-c91a", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "attachment": { "status": "attaching", @@ -95,10 +95,10 @@ func MockGetResponse(t *testing.T) { }) } -func MockCreateResponse(t *testing.T) { - th.Mux.HandleFunc("/attachments", func(w http.ResponseWriter, r *http.Request) { +func MockCreateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/attachments", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -123,7 +123,7 @@ func MockCreateResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "attachment": { "status": "attaching", @@ -140,18 +140,18 @@ func MockCreateResponse(t *testing.T) { }) } -func MockDeleteResponse(t *testing.T) { - th.Mux.HandleFunc("/attachments/05551600-a936-4d4a-ba42-79a037c1-c91a", func(w http.ResponseWriter, r *http.Request) { +func MockDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/attachments/05551600-a936-4d4a-ba42-79a037c1-c91a", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) }) } -func MockUpdateResponse(t *testing.T) { - th.Mux.HandleFunc("/attachments/05551600-a936-4d4a-ba42-79a037c1-c91a", func(w http.ResponseWriter, r *http.Request) { +func MockUpdateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/attachments/05551600-a936-4d4a-ba42-79a037c1-c91a", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, ` { @@ -171,7 +171,7 @@ func MockUpdateResponse(t *testing.T) { `) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "attachment": { "status": "attaching", @@ -188,10 +188,10 @@ func MockUpdateResponse(t *testing.T) { }) } -func MockUpdateEmptyResponse(t *testing.T) { - th.Mux.HandleFunc("/attachments/05551600-a936-4d4a-ba42-79a037c1-c91a", func(w http.ResponseWriter, r *http.Request) { +func MockUpdateEmptyResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/attachments/05551600-a936-4d4a-ba42-79a037c1-c91a", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, ` { @@ -202,7 +202,7 @@ func MockUpdateEmptyResponse(t *testing.T) { `) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "attachment": { "status": "attaching", @@ -225,10 +225,10 @@ var completeRequest = ` } ` -func MockCompleteResponse(t *testing.T) { - th.Mux.HandleFunc("/attachments/05551600-a936-4d4a-ba42-79a037c1-c91a/action", func(w http.ResponseWriter, r *http.Request) { +func MockCompleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/attachments/05551600-a936-4d4a-ba42-79a037c1-c91a/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, completeRequest) w.WriteHeader(http.StatusNoContent) }) diff --git a/openstack/blockstorage/v3/attachments/testing/requests_test.go b/openstack/blockstorage/v3/attachments/testing/requests_test.go index 4f9f91dcb4..5f1591950b 100644 --- a/openstack/blockstorage/v3/attachments/testing/requests_test.go +++ b/openstack/blockstorage/v3/attachments/testing/requests_test.go @@ -10,12 +10,12 @@ import ( ) func TestListAll(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) - allPages, err := attachments.List(client.ServiceClient(), &attachments.ListOpts{}).AllPages(context.TODO()) + allPages, err := attachments.List(client.ServiceClient(fakeServer), &attachments.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := attachments.ExtractAttachments(allPages) th.AssertNoErr(t, err) @@ -27,22 +27,22 @@ func TestListAll(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetResponse(t) + MockGetResponse(t, fakeServer) - attachment, err := attachments.Get(context.TODO(), client.ServiceClient(), "05551600-a936-4d4a-ba42-79a037c1-c91a").Extract() + attachment, err := attachments.Get(context.TODO(), client.ServiceClient(fakeServer), "05551600-a936-4d4a-ba42-79a037c1-c91a").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, expectedAttachment, attachment) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockCreateResponse(t) + MockCreateResponse(t, fakeServer) options := &attachments.CreateOpts{ InstanceUUID: "83ec2e3b-4321-422b-8706-a84185f52a0a", @@ -58,27 +58,27 @@ func TestCreate(t *testing.T) { }, VolumeUUID: "289da7f8-6440-407c-9fb4-7db01ec49164", } - attachment, err := attachments.Create(context.TODO(), client.ServiceClient(), options).Extract() + attachment, err := attachments.Create(context.TODO(), client.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, expectedAttachment, attachment) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDeleteResponse(t) + MockDeleteResponse(t, fakeServer) - res := attachments.Delete(context.TODO(), client.ServiceClient(), "05551600-a936-4d4a-ba42-79a037c1-c91a") + res := attachments.Delete(context.TODO(), client.ServiceClient(fakeServer), "05551600-a936-4d4a-ba42-79a037c1-c91a") th.AssertNoErr(t, res.Err) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockUpdateResponse(t) + MockUpdateResponse(t, fakeServer) options := &attachments.UpdateOpts{ Connector: map[string]any{ @@ -92,29 +92,29 @@ func TestUpdate(t *testing.T) { "mode": "rw", }, } - attachment, err := attachments.Update(context.TODO(), client.ServiceClient(), "05551600-a936-4d4a-ba42-79a037c1-c91a", options).Extract() + attachment, err := attachments.Update(context.TODO(), client.ServiceClient(fakeServer), "05551600-a936-4d4a-ba42-79a037c1-c91a", options).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, expectedAttachment, attachment) } func TestUpdateEmpty(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockUpdateEmptyResponse(t) + MockUpdateEmptyResponse(t, fakeServer) options := attachments.UpdateOpts{} - attachment, err := attachments.Update(context.TODO(), client.ServiceClient(), "05551600-a936-4d4a-ba42-79a037c1-c91a", options).Extract() + attachment, err := attachments.Update(context.TODO(), client.ServiceClient(fakeServer), "05551600-a936-4d4a-ba42-79a037c1-c91a", options).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, expectedAttachment, attachment) } func TestComplete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockCompleteResponse(t) + MockCompleteResponse(t, fakeServer) - err := attachments.Complete(context.TODO(), client.ServiceClient(), "05551600-a936-4d4a-ba42-79a037c1-c91a").ExtractErr() + err := attachments.Complete(context.TODO(), client.ServiceClient(fakeServer), "05551600-a936-4d4a-ba42-79a037c1-c91a").ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/blockstorage/v3/availabilityzones/testing/fixtures_test.go b/openstack/blockstorage/v3/availabilityzones/testing/fixtures_test.go index 98f93db1d3..067ba9fa12 100644 --- a/openstack/blockstorage/v3/availabilityzones/testing/fixtures_test.go +++ b/openstack/blockstorage/v3/availabilityzones/testing/fixtures_test.go @@ -41,12 +41,12 @@ var AZResult = []az.AvailabilityZone{ // HandleGetSuccessfully configures the test server to respond to a Get request // for availability zone information. -func HandleGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-availability-zone", func(w http.ResponseWriter, r *http.Request) { +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-availability-zone", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } diff --git a/openstack/blockstorage/v3/availabilityzones/testing/requests_test.go b/openstack/blockstorage/v3/availabilityzones/testing/requests_test.go index 3d1ed1bd0f..fce05f2c05 100644 --- a/openstack/blockstorage/v3/availabilityzones/testing/requests_test.go +++ b/openstack/blockstorage/v3/availabilityzones/testing/requests_test.go @@ -11,12 +11,12 @@ import ( // Verifies that availability zones can be listed correctly func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleGetSuccessfully(t) + HandleGetSuccessfully(t, fakeServer) - allPages, err := az.List(client.ServiceClient()).AllPages(context.TODO()) + allPages, err := az.List(client.ServiceClient(fakeServer)).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := az.ExtractAvailabilityZones(allPages) diff --git a/openstack/blockstorage/v3/backups/requests.go b/openstack/blockstorage/v3/backups/requests.go index a1f3cc8eef..d75da4c254 100644 --- a/openstack/blockstorage/v3/backups/requests.go +++ b/openstack/blockstorage/v3/backups/requests.go @@ -238,6 +238,12 @@ func Update(ctx context.Context, client *gophercloud.ServiceClient, id string, o return } +// RestoreOptsBuilder allows extensions to add additional parameters to the +// Restore request. +type RestoreOptsBuilder interface { + ToRestoreMap() (map[string]any, error) +} + // RestoreOpts contains options for restoring a Backup. This object is passed to // the backups.RestoreFromBackup function. type RestoreOpts struct { @@ -257,7 +263,7 @@ func (opts RestoreOpts) ToRestoreMap() (map[string]any, error) { // RestoreFromBackup will restore a Backup to a volume based on the values in // RestoreOpts. To extract the Restore object from the response, call the // Extract method on the RestoreResult. -func RestoreFromBackup(ctx context.Context, client *gophercloud.ServiceClient, id string, opts RestoreOpts) (r RestoreResult) { +func RestoreFromBackup(ctx context.Context, client *gophercloud.ServiceClient, id string, opts RestoreOptsBuilder) (r RestoreResult) { b, err := opts.ToRestoreMap() if err != nil { r.Err = err @@ -278,6 +284,12 @@ func Export(ctx context.Context, client *gophercloud.ServiceClient, id string) ( return } +// ImportOptsBuilder allows extensions to add additional parameters to the +// Import request. +type ImportOptsBuilder interface { + ToBackupImportMap() (map[string]any, error) +} + // ImportOpts contains options for importing a Backup. This object is passed to // the backups.ImportBackup function. type ImportOpts BackupRecord @@ -291,7 +303,7 @@ func (opts ImportOpts) ToBackupImportMap() (map[string]any, error) { // Import will import a Backup data to a backup based on the values in // ImportOpts. To extract the Backup object from the response, call the // Extract method on the ImportResult. -func Import(ctx context.Context, client *gophercloud.ServiceClient, opts ImportOpts) (r ImportResult) { +func Import(ctx context.Context, client *gophercloud.ServiceClient, opts ImportOptsBuilder) (r ImportResult) { b, err := opts.ToBackupImportMap() if err != nil { r.Err = err diff --git a/openstack/blockstorage/v3/backups/results.go b/openstack/blockstorage/v3/backups/results.go index 77620de917..e5eb72a705 100644 --- a/openstack/blockstorage/v3/backups/results.go +++ b/openstack/blockstorage/v3/backups/results.go @@ -121,7 +121,7 @@ func (r BackupPage) IsEmpty() (bool, error) { return len(volumes) == 0, err } -func (page BackupPage) NextPageURL() (string, error) { +func (page BackupPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"backups_links"` } @@ -156,11 +156,11 @@ func (r commonResult) Extract() (*Backup, error) { } func (r commonResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "backup") + return r.ExtractIntoStructPtr(v, "backup") } func ExtractBackupsInto(r pagination.Page, v any) error { - return r.(BackupPage).Result.ExtractIntoSlicePtr(v, "backups") + return r.(BackupPage).ExtractIntoSlicePtr(v, "backups") } // RestoreResult contains the response body and error from a restore request. @@ -189,7 +189,7 @@ func (r RestoreResult) Extract() (*Restore, error) { } func (r RestoreResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "restore") + return r.ExtractIntoStructPtr(v, "restore") } // ExportResult contains the response body and error from an export request. @@ -214,7 +214,7 @@ func (r ExportResult) Extract() (*BackupRecord, error) { } func (r ExportResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "backup-record") + return r.ExtractIntoStructPtr(v, "backup-record") } // ImportResponse struct contains the response of the Backup Import action. @@ -236,7 +236,7 @@ func (r ImportResult) Extract() (*ImportResponse, error) { } func (r ImportResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "backup") + return r.ExtractIntoStructPtr(v, "backup") } // ImportBackup contains all the information to import a Cinder Backup. diff --git a/openstack/blockstorage/v3/backups/testing/fixtures_test.go b/openstack/blockstorage/v3/backups/testing/fixtures_test.go index 13fc1d61d9..51b0f953aa 100644 --- a/openstack/blockstorage/v3/backups/testing/fixtures_test.go +++ b/openstack/blockstorage/v3/backups/testing/fixtures_test.go @@ -9,7 +9,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/backups" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) const ListResponse = ` @@ -194,10 +194,10 @@ var ( backupURL, _ = json.Marshal(backupImport) ) -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/backups", func(w http.ResponseWriter, r *http.Request) { +func MockListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/backups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -208,19 +208,19 @@ func MockListResponse(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, ListResponse, th.Server.URL) + fmt.Fprintf(w, ListResponse, fakeServer.Server.URL) case "1": - fmt.Fprintf(w, `{"backups": []}`) + fmt.Fprint(w, `{"backups": []}`) default: t.Fatalf("Unexpected marker: [%s]", marker) } }) } -func MockListDetailResponse(t *testing.T) { - th.Mux.HandleFunc("/backups/detail", func(w http.ResponseWriter, r *http.Request) { +func MockListDetailResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/backups/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -231,30 +231,30 @@ func MockListDetailResponse(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, ListDetailResponse, th.Server.URL) + fmt.Fprintf(w, ListDetailResponse, fakeServer.Server.URL) case "1": - fmt.Fprintf(w, `{"backups": []}`) + fmt.Fprint(w, `{"backups": []}`) default: t.Fatalf("Unexpected marker: [%s]", marker) } }) } -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { +func MockGetResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) } -func MockCreateResponse(t *testing.T) { - th.Mux.HandleFunc("/backups", func(w http.ResponseWriter, r *http.Request) { +func MockCreateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/backups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, CreateRequest) @@ -262,14 +262,14 @@ func MockCreateResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) } -func MockRestoreResponse(t *testing.T) { - th.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22/restore", func(w http.ResponseWriter, r *http.Request) { +func MockRestoreResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22/restore", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, RestoreRequest) @@ -277,35 +277,35 @@ func MockRestoreResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, RestoreResponse) + fmt.Fprint(w, RestoreResponse) }) } -func MockDeleteResponse(t *testing.T) { - th.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { +func MockDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) } -func MockExportResponse(t *testing.T) { - th.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22/export_record", func(w http.ResponseWriter, r *http.Request) { +func MockExportResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22/export_record", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ExportResponse) + fmt.Fprint(w, ExportResponse) }) } -func MockImportResponse(t *testing.T) { - th.Mux.HandleFunc("/backups/import_record", func(w http.ResponseWriter, r *http.Request) { +func MockImportResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/backups/import_record", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ImportRequest) @@ -313,15 +313,15 @@ func MockImportResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ImportResponse) + fmt.Fprint(w, ImportResponse) }) } // MockResetStatusResponse provides mock response for reset backup status API call -func MockResetStatusResponse(t *testing.T) { - th.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22/action", func(w http.ResponseWriter, r *http.Request) { +func MockResetStatusResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestJSONRequest(t, r, ResetRequest) @@ -330,10 +330,10 @@ func MockResetStatusResponse(t *testing.T) { } // MockForceDeleteResponse provides mock response for force delete backup API call -func MockForceDeleteResponse(t *testing.T) { - th.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22/action", func(w http.ResponseWriter, r *http.Request) { +func MockForceDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/backups/d32019d3-bc6e-4319-9c1d-6722fc136a22/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestJSONRequest(t, r, ForceDeleteRequest) diff --git a/openstack/blockstorage/v3/backups/testing/requests_test.go b/openstack/blockstorage/v3/backups/testing/requests_test.go index b9c302896d..7eeff49d92 100644 --- a/openstack/blockstorage/v3/backups/testing/requests_test.go +++ b/openstack/blockstorage/v3/backups/testing/requests_test.go @@ -13,14 +13,14 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) count := 0 - err := backups.List(client.ServiceClient(), &backups.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := backups.List(client.ServiceClient(fakeServer), &backups.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := backups.ExtractBackups(page) if err != nil { @@ -53,14 +53,14 @@ func TestList(t *testing.T) { } func TestListDetail(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListDetailResponse(t) + MockListDetailResponse(t, fakeServer) count := 0 - err := backups.ListDetail(client.ServiceClient(), &backups.ListDetailOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := backups.ListDetail(client.ServiceClient(fakeServer), &backups.ListDetailOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := backups.ExtractBackups(page) if err != nil { @@ -103,68 +103,68 @@ func TestListDetail(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetResponse(t) + MockGetResponse(t, fakeServer) - v, err := backups.Get(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() + v, err := backups.Get(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, v.Name, "backup-001") - th.AssertEquals(t, v.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") + th.AssertEquals(t, "backup-001", v.Name) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", v.ID) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockCreateResponse(t) + MockCreateResponse(t, fakeServer) options := backups.CreateOpts{VolumeID: "1234", Name: "backup-001"} - n, err := backups.Create(context.TODO(), client.ServiceClient(), options).Extract() + n, err := backups.Create(context.TODO(), client.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.VolumeID, "1234") - th.AssertEquals(t, n.Name, "backup-001") - th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") + th.AssertEquals(t, "1234", n.VolumeID) + th.AssertEquals(t, "backup-001", n.Name) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", n.ID) } func TestRestore(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockRestoreResponse(t) + MockRestoreResponse(t, fakeServer) options := backups.RestoreOpts{VolumeID: "1234", Name: "vol-001"} - n, err := backups.RestoreFromBackup(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", options).Extract() + n, err := backups.RestoreFromBackup(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22", options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.VolumeID, "1234") - th.AssertEquals(t, n.VolumeName, "vol-001") - th.AssertEquals(t, n.BackupID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") + th.AssertEquals(t, "1234", n.VolumeID) + th.AssertEquals(t, "vol-001", n.VolumeName) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", n.BackupID) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDeleteResponse(t) + MockDeleteResponse(t, fakeServer) - res := backups.Delete(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22") + res := backups.Delete(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22") th.AssertNoErr(t, res.Err) } func TestExport(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockExportResponse(t) + MockExportResponse(t, fakeServer) - n, err := backups.Export(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() + n, err := backups.Export(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.BackupService, "cinder.backup.drivers.swift.SwiftBackupDriver") + th.AssertEquals(t, "cinder.backup.drivers.swift.SwiftBackupDriver", n.BackupService) th.AssertDeepEquals(t, n.BackupURL, backupURL) tmp := backups.ImportBackup{} @@ -174,40 +174,40 @@ func TestExport(t *testing.T) { } func TestImport(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockImportResponse(t) + MockImportResponse(t, fakeServer) options := backups.ImportOpts{ BackupService: "cinder.backup.drivers.swift.SwiftBackupDriver", BackupURL: backupURL, } - n, err := backups.Import(context.TODO(), client.ServiceClient(), options).Extract() + n, err := backups.Import(context.TODO(), client.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", n.ID) } func TestResetStatus(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockResetStatusResponse(t) + MockResetStatusResponse(t, fakeServer) opts := &backups.ResetStatusOpts{ Status: "error", } - res := backups.ResetStatus(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", opts) + res := backups.ResetStatus(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22", opts) th.AssertNoErr(t, res.Err) } func TestForceDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockForceDeleteResponse(t) + MockForceDeleteResponse(t, fakeServer) - res := backups.ForceDelete(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22") + res := backups.ForceDelete(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22") th.AssertNoErr(t, res.Err) } diff --git a/openstack/blockstorage/v3/limits/testing/fixtures_test.go b/openstack/blockstorage/v3/limits/testing/fixtures_test.go index 5d3b647427..318ef7779c 100644 --- a/openstack/blockstorage/v3/limits/testing/fixtures_test.go +++ b/openstack/blockstorage/v3/limits/testing/fixtures_test.go @@ -118,12 +118,12 @@ var LimitsResult = limits.Limits{ // HandleGetSuccessfully configures the test server to respond to a Get request // for a limit. -func HandleGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/limits", func(w http.ResponseWriter, r *http.Request) { +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/limits", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } diff --git a/openstack/blockstorage/v3/limits/testing/requests_test.go b/openstack/blockstorage/v3/limits/testing/requests_test.go index bd2a2cbfd2..8296ef751b 100644 --- a/openstack/blockstorage/v3/limits/testing/requests_test.go +++ b/openstack/blockstorage/v3/limits/testing/requests_test.go @@ -10,11 +10,11 @@ import ( ) func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSuccessfully(t, fakeServer) - actual, err := limits.Get(context.TODO(), client.ServiceClient()).Extract() + actual, err := limits.Get(context.TODO(), client.ServiceClient(fakeServer)).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &LimitsResult, actual) } diff --git a/openstack/blockstorage/v3/manageablevolumes/doc.go b/openstack/blockstorage/v3/manageablevolumes/doc.go new file mode 100644 index 0000000000..a2217fc0fb --- /dev/null +++ b/openstack/blockstorage/v3/manageablevolumes/doc.go @@ -0,0 +1,32 @@ +/* +Package manageablevolumes information and interaction with manageable volumes +for the OpenStack Block Storage service. + +NOTE: Requires at least microversion 3.8 + +Example to manage an existing volume + + manageOpts := manageablevolumes.ManageExistingOpts{ + Host: "host@lvm#LVM", + Ref: map[string]string{ + "source-name": "volume-73796b96-169f-4675-a5bc-73fc0f8f9a17", + }, + Name: "New Volume", + AvailabilityZone: "nova", + Description: "Volume imported from existingLV", + VolumeType: "lvm", + Bootable: true, + Metadata: map[string]string{ + "key1": "value1", + "key2": "value2" + }, + } + + managedVolume, err := manageablevolumes.ManageExisting(context.TODO(), client, manageOpts).Extract() + if err != nil { + log.Fatal(err) + } + + fmt.Printf("Managed volume: %+v\n", managedVolume) +*/ +package manageablevolumes diff --git a/openstack/blockstorage/v3/manageablevolumes/requests.go b/openstack/blockstorage/v3/manageablevolumes/requests.go new file mode 100644 index 0000000000..42c5593cda --- /dev/null +++ b/openstack/blockstorage/v3/manageablevolumes/requests.go @@ -0,0 +1,61 @@ +package manageablevolumes + +import ( + "context" + + "github.com/gophercloud/gophercloud/v2" +) + +// ManageExistingOptsBuilder allows extentions to add additional parameters to the ManageExisting request. +type ManageExistingOptsBuilder interface { + ToManageExistingMap() (map[string]any, error) +} + +// ManageExistingOpts contains options for managing a existing volume. +// This object is passed to the volumes.ManageExisting function. +// For more information about the parameters, see the Volume object and OpenStack BlockStorage API Guide. +type ManageExistingOpts struct { + // The OpenStack Block Storage host where the existing resource resides. + // Optional only if cluster field is provided. + Host string `json:"host,omitempty"` + // The OpenStack Block Storage cluster where the resource resides. + // Optional only if host field is provided. + Cluster string `json:"cluster,omitempty"` + // A reference to the existing volume. + // The internal structure of this reference depends on the volume driver implementation. + // For details about the required elements in the structure, see the documentation for the volume driver. + Ref map[string]string `json:"ref,omitempty"` + // Human-readable display name for the volume. + Name string `json:"name,omitempty"` + // The availability zone. + AvailabilityZone string `json:"availability_zone,omitempty"` + // Human-readable description for the volume. + Description string `json:"description,omitempty"` + // The associated volume type + VolumeType string `json:"volume_type,omitempty"` + // Indicates whether this is a bootable volume. + Bootable bool `json:"bootable,omitempty"` + // One or more metadata key and value pairs to associate with the volume. + Metadata map[string]string `json:"metadata,omitempty"` +} + +// ToManageExistingMap assembles a request body based on the contents of a ManageExistingOpts. +func (opts ManageExistingOpts) ToManageExistingMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "volume") +} + +// ManageExisting will manage an existing volume based on the values in ManageExistingOpts. +// To extract the Volume object from response, call the Extract method on the ManageExistingResult. +func ManageExisting(ctx context.Context, client *gophercloud.ServiceClient, opts ManageExistingOptsBuilder) (r ManageExistingResult) { + b, err := opts.ToManageExistingMap() + if err != nil { + r.Err = err + return + } + + resp, err := client.Post(ctx, createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/blockstorage/v3/manageablevolumes/results.go b/openstack/blockstorage/v3/manageablevolumes/results.go new file mode 100644 index 0000000000..bb8ec9f7e3 --- /dev/null +++ b/openstack/blockstorage/v3/manageablevolumes/results.go @@ -0,0 +1,22 @@ +package manageablevolumes + +import ( + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/volumes" +) + +type ManageExistingResult struct { + gophercloud.Result +} + +// Extract will get the Volume object out of the ManageExistingResult object. +func (r ManageExistingResult) Extract() (*volumes.Volume, error) { + var s volumes.Volume + err := r.ExtractInto(&s) + return &s, err +} + +// ExtractInto converts our response data into a volume struct +func (r ManageExistingResult) ExtractInto(v any) error { + return r.ExtractIntoStructPtr(v, "volume") +} diff --git a/openstack/blockstorage/v3/manageablevolumes/testing/doc.go b/openstack/blockstorage/v3/manageablevolumes/testing/doc.go new file mode 100644 index 0000000000..4acd665887 --- /dev/null +++ b/openstack/blockstorage/v3/manageablevolumes/testing/doc.go @@ -0,0 +1,2 @@ +// manageablevolumes unit tests +package testing diff --git a/openstack/blockstorage/v3/manageablevolumes/testing/fixtures_test.go b/openstack/blockstorage/v3/manageablevolumes/testing/fixtures_test.go new file mode 100644 index 0000000000..cc124c02e3 --- /dev/null +++ b/openstack/blockstorage/v3/manageablevolumes/testing/fixtures_test.go @@ -0,0 +1,93 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/gophercloud/gophercloud/v2/testhelper" + fake "github.com/gophercloud/gophercloud/v2/testhelper/client" +) + +func MockManageExistingResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/manageable_volumes", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "volume": { + "host": "host@lvm#LVM", + "ref": { + "source-name": "volume-73796b96-169f-4675-a5bc-73fc0f8f9a17" + }, + "name": "New Volume", + "availability_zone": "nova", + "description": "Volume imported from existingLV", + "volume_type": "lvm", + "bootable": true, + "metadata": { + "key1": "value1", + "key2": "value2" + } + } +} + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusAccepted) + + fmt.Fprint(w, ` +{ + "volume": { + "id": "23cf872b-c781-4cd4-847d-5f2ec8cbd91c", + "status": "creating", + "size": 0, + "availability_zone": "nova", + "created_at": "2025-03-20T11:58:05.000000", + "updated_at": "2025-03-20T11:58:05.000000", + "name": "New Volume", + "description": "Volume imported from existingLV", + "volume_type": "lvm", + "snapshot_id": null, + "source_volid": null, + "metadata": { + "key1": "value1", + "key2": "value2" + }, + "links": [ + { + "href": "http://10.0.2.15:8776/v3/87c8522052ca4eed98bc672b4c1a3ddb/volumes/23cf872b-c781-4cd4-847d-5f2ec8cbd91c", + "rel": "self" + }, + { + "href": "http://10.0.2.15:8776/87c8522052ca4eed98bc672b4c1a3ddb/volumes/23cf872b-c781-4cd4-847d-5f2ec8cbd91c", + "rel": "bookmark" + } + ], + "user_id": "eae1472b5fc5496998a3d06550929e7e", + "bootable": "true", + "encrypted": false, + "replication_status": null, + "consistencygroup_id": null, + "multiattach": false, + "attachments": [], + "created_at": "2014-07-18T00:12:54.000000", + "migration_status": null, + "group_id": null, + "provider_id": null, + "shared_targets": true, + "service_uuid": null, + "cluster_name": null, + "volume_type_id": "a218796e-605b-4b6f-9dfc-8be95a0d7d03", + "consumes_quota": true, + "os-vol-mig-status-attr:migstat": null, + "os-vol-mig-status-attr:name_id": null, + "os-vol-tenant-attr:tenant_id": "87c8522052ca4eed98bc672b4c1a3ddb", + "os-vol-host-attr:host": "host@lvm#LVM" + } +} + `) + }) +} diff --git a/openstack/blockstorage/v3/manageablevolumes/testing/requests_test.go b/openstack/blockstorage/v3/manageablevolumes/testing/requests_test.go new file mode 100644 index 0000000000..5c02c1a5ef --- /dev/null +++ b/openstack/blockstorage/v3/manageablevolumes/testing/requests_test.go @@ -0,0 +1,44 @@ +package testing + +import ( + "context" + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/manageablevolumes" + th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/client" +) + +func TestManageExisting(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + MockManageExistingResponse(t, fakeServer) + + options := &manageablevolumes.ManageExistingOpts{ + Host: "host@lvm#LVM", + Ref: map[string]string{"source-name": "volume-73796b96-169f-4675-a5bc-73fc0f8f9a17"}, + Name: "New Volume", + AvailabilityZone: "nova", + Description: "Volume imported from existingLV", + VolumeType: "lvm", + Bootable: true, + Metadata: map[string]string{ + "key1": "value1", + "key2": "value2", + }, + } + n, err := manageablevolumes.ManageExisting(context.TODO(), client.ServiceClient(fakeServer), options).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, "host@lvm#LVM", n.Host) + th.AssertEquals(t, "New Volume", n.Name) + th.AssertEquals(t, "nova", n.AvailabilityZone) + th.AssertEquals(t, "Volume imported from existingLV", n.Description) + th.AssertEquals(t, "true", n.Bootable) + th.AssertDeepEquals(t, map[string]string{ + "key1": "value1", + "key2": "value2", + }, n.Metadata) + th.AssertEquals(t, "23cf872b-c781-4cd4-847d-5f2ec8cbd91c", n.ID) +} diff --git a/openstack/blockstorage/v3/manageablevolumes/urls.go b/openstack/blockstorage/v3/manageablevolumes/urls.go new file mode 100644 index 0000000000..c58c94a396 --- /dev/null +++ b/openstack/blockstorage/v3/manageablevolumes/urls.go @@ -0,0 +1,7 @@ +package manageablevolumes + +import "github.com/gophercloud/gophercloud/v2" + +func createURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("manageable_volumes") +} diff --git a/openstack/blockstorage/v3/qos/results.go b/openstack/blockstorage/v3/qos/results.go index 314bf878ca..3a77228da7 100644 --- a/openstack/blockstorage/v3/qos/results.go +++ b/openstack/blockstorage/v3/qos/results.go @@ -30,7 +30,7 @@ func (r commonResult) Extract() (*QoS, error) { // ExtractInto converts our response data into a QoS struct func (r commonResult) ExtractInto(qos any) error { - return r.Result.ExtractIntoStructPtr(qos, "qos_specs") + return r.ExtractIntoStructPtr(qos, "qos_specs") } // CreateResult contains the response body and error from a Create request. @@ -59,7 +59,7 @@ func (page QoSPage) IsEmpty() (bool, error) { // NextPageURL uses the response's embedded link reference to navigate to the // next page of results. -func (page QoSPage) NextPageURL() (string, error) { +func (page QoSPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"qos_specs_links"` } diff --git a/openstack/blockstorage/v3/qos/testing/fixtures_test.go b/openstack/blockstorage/v3/qos/testing/fixtures_test.go index 8f9d2e2d51..0e52ba2048 100644 --- a/openstack/blockstorage/v3/qos/testing/fixtures_test.go +++ b/openstack/blockstorage/v3/qos/testing/fixtures_test.go @@ -7,7 +7,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/qos" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) var createQoSExpected = qos.QoS{ @@ -28,10 +28,10 @@ var getQoSExpected = qos.QoS{ }, } -func MockCreateResponse(t *testing.T) { - th.Mux.HandleFunc("/qos-specs", func(w http.ResponseWriter, r *http.Request) { +func MockCreateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/qos-specs", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -47,7 +47,7 @@ func MockCreateResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "qos_specs": { "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", @@ -62,18 +62,18 @@ func MockCreateResponse(t *testing.T) { }) } -func MockDeleteResponse(t *testing.T) { - th.Mux.HandleFunc("/qos-specs/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { +func MockDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/qos-specs/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) }) } -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/qos-specs", func(w http.ResponseWriter, r *http.Request) { +func MockListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/qos-specs", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") if err := r.ParseForm(); err != nil { @@ -108,23 +108,23 @@ func MockListResponse(t *testing.T) { } ] } - `, th.Server.URL) + `, fakeServer.Server.URL) case "2": - fmt.Fprintf(w, `{ "qos_specs": [] }`) + fmt.Fprint(w, `{ "qos_specs": [] }`) default: t.Fatalf("Unexpected marker: [%s]", marker) } }) } -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc("/qos-specs/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { +func MockGetResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/qos-specs/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "qos_specs": { "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", @@ -157,10 +157,10 @@ var UpdateQos = map[string]string{ "write_iops_sec": "40000", } -func MockUpdateResponse(t *testing.T) { - th.Mux.HandleFunc("/qos-specs/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { +func MockUpdateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/qos-specs/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, `{ "qos_specs": { @@ -172,14 +172,14 @@ func MockUpdateResponse(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateBody) + fmt.Fprint(w, UpdateBody) }) } -func MockDeleteKeysResponse(t *testing.T) { - th.Mux.HandleFunc("/qos-specs/d32019d3-bc6e-4319-9c1d-6722fc136a22/delete_keys", func(w http.ResponseWriter, r *http.Request) { +func MockDeleteKeysResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/qos-specs/d32019d3-bc6e-4319-9c1d-6722fc136a22/delete_keys", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ "keys": [ "read_iops_sec" @@ -190,36 +190,36 @@ func MockDeleteKeysResponse(t *testing.T) { }) } -func MockAssociateResponse(t *testing.T) { - th.Mux.HandleFunc("/qos-specs/d32019d3-bc6e-4319-9c1d-6722fc136a22/associate", func(w http.ResponseWriter, r *http.Request) { +func MockAssociateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/qos-specs/d32019d3-bc6e-4319-9c1d-6722fc136a22/associate", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) }) } -func MockDisassociateResponse(t *testing.T) { - th.Mux.HandleFunc("/qos-specs/d32019d3-bc6e-4319-9c1d-6722fc136a22/disassociate", func(w http.ResponseWriter, r *http.Request) { +func MockDisassociateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/qos-specs/d32019d3-bc6e-4319-9c1d-6722fc136a22/disassociate", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) }) } -func MockDisassociateAllResponse(t *testing.T) { - th.Mux.HandleFunc("/qos-specs/d32019d3-bc6e-4319-9c1d-6722fc136a22/disassociate_all", func(w http.ResponseWriter, r *http.Request) { +func MockDisassociateAllResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/qos-specs/d32019d3-bc6e-4319-9c1d-6722fc136a22/disassociate_all", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) }) } -func MockListAssociationsResponse(t *testing.T) { - th.Mux.HandleFunc("/qos-specs/d32019d3-bc6e-4319-9c1d-6722fc136a22/associations", func(w http.ResponseWriter, r *http.Request) { +func MockListAssociationsResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/qos-specs/d32019d3-bc6e-4319-9c1d-6722fc136a22/associations", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "qos_associations": [ { diff --git a/openstack/blockstorage/v3/qos/testing/requests_test.go b/openstack/blockstorage/v3/qos/testing/requests_test.go index be88fbd302..7ccfa4a424 100644 --- a/openstack/blockstorage/v3/qos/testing/requests_test.go +++ b/openstack/blockstorage/v3/qos/testing/requests_test.go @@ -12,10 +12,10 @@ import ( ) func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockCreateResponse(t) + MockCreateResponse(t, fakeServer) options := qos.CreateOpts{ Name: "qos-001", @@ -24,29 +24,29 @@ func TestCreate(t *testing.T) { "read_iops_sec": "20000", }, } - actual, err := qos.Create(context.TODO(), client.ServiceClient(), options).Extract() + actual, err := qos.Create(context.TODO(), client.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &createQoSExpected, actual) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDeleteResponse(t) + MockDeleteResponse(t, fakeServer) - res := qos.Delete(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", qos.DeleteOpts{}) + res := qos.Delete(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22", qos.DeleteOpts{}) th.AssertNoErr(t, res.Err) } func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) pages := 0 - err := qos.List(client.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := qos.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := qos.ExtractQoS(page) if err != nil { @@ -76,20 +76,20 @@ func TestList(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetResponse(t) + MockGetResponse(t, fakeServer) - actual, err := qos.Get(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() + actual, err := qos.Get(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &getQoSExpected, actual) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - MockUpdateResponse(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + MockUpdateResponse(t, fakeServer) updateOpts := qos.UpdateOpts{ Consumer: qos.ConsumerBack, @@ -100,64 +100,64 @@ func TestUpdate(t *testing.T) { } expected := UpdateQos - actual, err := qos.Update(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", updateOpts).Extract() + actual, err := qos.Update(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22", updateOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, expected, actual) } func TestDeleteKeys(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDeleteKeysResponse(t) + MockDeleteKeysResponse(t, fakeServer) - res := qos.DeleteKeys(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", qos.DeleteKeysOpts{"read_iops_sec"}) + res := qos.DeleteKeys(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22", qos.DeleteKeysOpts{"read_iops_sec"}) th.AssertNoErr(t, res.Err) } func TestAssociate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockAssociateResponse(t) + MockAssociateResponse(t, fakeServer) associateOpts := qos.AssociateOpts{ VolumeTypeID: "b596be6a-0ce9-43fa-804a-5c5e181ede76", } - res := qos.Associate(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", associateOpts) + res := qos.Associate(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22", associateOpts) th.AssertNoErr(t, res.Err) } func TestDisssociate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDisassociateResponse(t) + MockDisassociateResponse(t, fakeServer) disassociateOpts := qos.DisassociateOpts{ VolumeTypeID: "b596be6a-0ce9-43fa-804a-5c5e181ede76", } - res := qos.Disassociate(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", disassociateOpts) + res := qos.Disassociate(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22", disassociateOpts) th.AssertNoErr(t, res.Err) } func TestDissasociateAll(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDisassociateAllResponse(t) + MockDisassociateAllResponse(t, fakeServer) - res := qos.DisassociateAll(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22") + res := qos.DisassociateAll(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22") th.AssertNoErr(t, res.Err) } func TestQosAssociationsList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListAssociationsResponse(t) + MockListAssociationsResponse(t, fakeServer) expected := []qos.QosAssociation{ { @@ -167,7 +167,7 @@ func TestQosAssociationsList(t *testing.T) { }, } - allPages, err := qos.ListAssociations(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").AllPages(context.TODO()) + allPages, err := qos.ListAssociations(client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22").AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := qos.ExtractAssociations(allPages) diff --git a/openstack/blockstorage/v3/quotasets/results.go b/openstack/blockstorage/v3/quotasets/results.go index e75aea5b51..179427f4b2 100644 --- a/openstack/blockstorage/v3/quotasets/results.go +++ b/openstack/blockstorage/v3/quotasets/results.go @@ -111,6 +111,47 @@ type QuotaUsageSet struct { // Note: allocated attribute is available only when nested quota is // enabled. Groups QuotaUsage `json:"groups"` + + // Extra is a collection of key/values that has the size (GB) usage information + // per volume_type. Note: allocated attribute is available only when nested + // quota is enabled. + Extra map[string]QuotaUsage `json:"-"` +} + +// UnmarshalJSON is used on QuotaUsageSet to unmarshal extra keys that are +// used to represent QuotaUsage per volume_type. +func (r *QuotaUsageSet) UnmarshalJSON(b []byte) error { + type tmp QuotaUsageSet + var s struct { + tmp + Extra map[string]QuotaUsage `json:"extra"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = QuotaUsageSet(s.tmp) + + var result any + err = json.Unmarshal(b, &result) + if err != nil { + return err + } + + // process remaining items as separate QuotaUsage objects. + if resultMap, ok := result.(map[string]any); ok { + tmpb, err := json.Marshal(gophercloud.RemainingKeys(QuotaUsageSet{}, resultMap)) + if err != nil { + return err + } + + err = json.Unmarshal(tmpb, &r.Extra) + if err != nil { + return err + } + } + + return err } // QuotaUsage is a set of details about a single operational limit that allows diff --git a/openstack/blockstorage/v3/quotasets/testing/fixtures_test.go b/openstack/blockstorage/v3/quotasets/testing/fixtures_test.go index df029ff8e1..a64b68ec0e 100644 --- a/openstack/blockstorage/v3/quotasets/testing/fixtures_test.go +++ b/openstack/blockstorage/v3/quotasets/testing/fixtures_test.go @@ -75,8 +75,23 @@ var getUsageExpectedJSONBody = ` "in_use": 40, "limit": 41, "reserved": 42 + }, + "gigabytes_hdd" : { + "in_use": 50, + "limit": 51, + "reserved": 52 + }, + "volumes_hdd" : { + "in_use": 53, + "limit": 54, + "reserved": 55 + }, + "snapshots_hdd": { + "in_use": 56, + "limit": 57, + "reserved": 58 } - } + } }` var getUsageExpectedQuotaSet = quotasets.QuotaUsageSet{ @@ -88,6 +103,11 @@ var getUsageExpectedQuotaSet = quotasets.QuotaUsageSet{ Backups: quotasets.QuotaUsage{InUse: 27, Limit: 28, Reserved: 29}, BackupGigabytes: quotasets.QuotaUsage{InUse: 30, Limit: 31, Reserved: 32}, Groups: quotasets.QuotaUsage{InUse: 40, Limit: 41, Reserved: 42}, + Extra: map[string]quotasets.QuotaUsage{ + "gigabytes_hdd": {InUse: 50, Limit: 51, Reserved: 52}, + "volumes_hdd": {InUse: 53, Limit: 54, Reserved: 55}, + "snapshots_hdd": {InUse: 56, Limit: 57, Reserved: 58}, + }, } var fullUpdateExpectedJSONBody = ` @@ -147,15 +167,15 @@ var partialUpdateOpts = quotasets.UpdateOpts{ Extra: make(map[string]any), } -var partiualUpdateExpectedQuotaSet = quotasets.QuotaSet{ +var partialUpdateExpectedQuotaSet = quotasets.QuotaSet{ Volumes: 200, Extra: make(map[string]any), } // HandleSuccessfulRequest configures the test server to respond to an HTTP request. -func HandleSuccessfulRequest(t *testing.T, httpMethod, uriPath, jsonOutput string, uriQueryParams map[string]string) { +func HandleSuccessfulRequest(t *testing.T, fakeServer th.FakeServer, httpMethod, uriPath, jsonOutput string, uriQueryParams map[string]string) { - th.Mux.HandleFunc(uriPath, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(uriPath, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, httpMethod) th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") @@ -164,13 +184,13 @@ func HandleSuccessfulRequest(t *testing.T, httpMethod, uriPath, jsonOutput strin th.TestFormValues(t, r, uriQueryParams) } - fmt.Fprintf(w, jsonOutput) + fmt.Fprint(w, jsonOutput) }) } // HandleDeleteSuccessfully tests quotaset deletion. -func HandleDeleteSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-quota-sets/"+FirstTenantID, func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-quota-sets/"+FirstTenantID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) diff --git a/openstack/blockstorage/v3/quotasets/testing/requests_test.go b/openstack/blockstorage/v3/quotasets/testing/requests_test.go index ca17ef10b4..9cf76b2a3f 100644 --- a/openstack/blockstorage/v3/quotasets/testing/requests_test.go +++ b/openstack/blockstorage/v3/quotasets/testing/requests_test.go @@ -11,71 +11,71 @@ import ( ) func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() uriQueryParms := map[string]string{} - HandleSuccessfulRequest(t, "GET", "/os-quota-sets/"+FirstTenantID, getExpectedJSONBody, uriQueryParms) - actual, err := quotasets.Get(context.TODO(), client.ServiceClient(), FirstTenantID).Extract() + HandleSuccessfulRequest(t, fakeServer, "GET", "/os-quota-sets/"+FirstTenantID, getExpectedJSONBody, uriQueryParms) + actual, err := quotasets.Get(context.TODO(), client.ServiceClient(fakeServer), FirstTenantID).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &getExpectedQuotaSet, actual) } func TestGetUsage(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() uriQueryParms := map[string]string{"usage": "true"} - HandleSuccessfulRequest(t, "GET", "/os-quota-sets/"+FirstTenantID, getUsageExpectedJSONBody, uriQueryParms) - actual, err := quotasets.GetUsage(context.TODO(), client.ServiceClient(), FirstTenantID).Extract() + HandleSuccessfulRequest(t, fakeServer, "GET", "/os-quota-sets/"+FirstTenantID, getUsageExpectedJSONBody, uriQueryParms) + actual, err := quotasets.GetUsage(context.TODO(), client.ServiceClient(fakeServer), FirstTenantID).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, getUsageExpectedQuotaSet, actual) } func TestFullUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() uriQueryParms := map[string]string{} - HandleSuccessfulRequest(t, "PUT", "/os-quota-sets/"+FirstTenantID, fullUpdateExpectedJSONBody, uriQueryParms) - actual, err := quotasets.Update(context.TODO(), client.ServiceClient(), FirstTenantID, fullUpdateOpts).Extract() + HandleSuccessfulRequest(t, fakeServer, "PUT", "/os-quota-sets/"+FirstTenantID, fullUpdateExpectedJSONBody, uriQueryParms) + actual, err := quotasets.Update(context.TODO(), client.ServiceClient(fakeServer), FirstTenantID, fullUpdateOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &fullUpdateExpectedQuotaSet, actual) } func TestPartialUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() uriQueryParms := map[string]string{} - HandleSuccessfulRequest(t, "PUT", "/os-quota-sets/"+FirstTenantID, partialUpdateExpectedJSONBody, uriQueryParms) - actual, err := quotasets.Update(context.TODO(), client.ServiceClient(), FirstTenantID, partialUpdateOpts).Extract() + HandleSuccessfulRequest(t, fakeServer, "PUT", "/os-quota-sets/"+FirstTenantID, partialUpdateExpectedJSONBody, uriQueryParms) + actual, err := quotasets.Update(context.TODO(), client.ServiceClient(fakeServer), FirstTenantID, partialUpdateOpts).Extract() th.AssertNoErr(t, err) - th.CheckDeepEquals(t, &partiualUpdateExpectedQuotaSet, actual) + th.CheckDeepEquals(t, &partialUpdateExpectedQuotaSet, actual) } type ErrorUpdateOpts quotasets.UpdateOpts func (opts ErrorUpdateOpts) ToBlockStorageQuotaUpdateMap() (map[string]any, error) { - return nil, errors.New("This is an error") + return nil, errors.New("this is an error") } func TestErrorInToBlockStorageQuotaUpdateMap(t *testing.T) { opts := &ErrorUpdateOpts{} - th.SetupHTTP() - defer th.TeardownHTTP() - HandleSuccessfulRequest(t, "PUT", "/os-quota-sets/"+FirstTenantID, "", nil) - _, err := quotasets.Update(context.TODO(), client.ServiceClient(), FirstTenantID, opts).Extract() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleSuccessfulRequest(t, fakeServer, "PUT", "/os-quota-sets/"+FirstTenantID, "", nil) + _, err := quotasets.Update(context.TODO(), client.ServiceClient(fakeServer), FirstTenantID, opts).Extract() if err == nil { t.Fatal("Error handling failed") } } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteSuccessfully(t, fakeServer) - err := quotasets.Delete(context.TODO(), client.ServiceClient(), FirstTenantID).ExtractErr() + err := quotasets.Delete(context.TODO(), client.ServiceClient(fakeServer), FirstTenantID).ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/blockstorage/v3/schedulerstats/testing/fixtures_test.go b/openstack/blockstorage/v3/schedulerstats/testing/fixtures_test.go index df0ccaa7f5..d84f647cb2 100644 --- a/openstack/blockstorage/v3/schedulerstats/testing/fixtures_test.go +++ b/openstack/blockstorage/v3/schedulerstats/testing/fixtures_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/schedulerstats" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" "github.com/gophercloud/gophercloud/v2/testhelper/client" ) @@ -93,10 +93,10 @@ var ( } ) -func HandleStoragePoolsListSuccessfully(t *testing.T) { - testhelper.Mux.HandleFunc("/scheduler-stats/get_pools", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "GET") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandleStoragePoolsListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/scheduler-stats/get_pools", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") @@ -104,9 +104,9 @@ func HandleStoragePoolsListSuccessfully(t *testing.T) { t.Errorf("Failed to parse request form %v", err) } if r.FormValue("detail") == "true" { - fmt.Fprintf(w, StoragePoolsListBodyDetail) + fmt.Fprint(w, StoragePoolsListBodyDetail) } else { - fmt.Fprintf(w, StoragePoolsListBody) + fmt.Fprint(w, StoragePoolsListBody) } }) } diff --git a/openstack/blockstorage/v3/schedulerstats/testing/requests_test.go b/openstack/blockstorage/v3/schedulerstats/testing/requests_test.go index b5e3f31ef6..d42cf69b6b 100644 --- a/openstack/blockstorage/v3/schedulerstats/testing/requests_test.go +++ b/openstack/blockstorage/v3/schedulerstats/testing/requests_test.go @@ -6,32 +6,32 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/schedulerstats" "github.com/gophercloud/gophercloud/v2/pagination" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestListStoragePoolsDetail(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - HandleStoragePoolsListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleStoragePoolsListSuccessfully(t, fakeServer) pages := 0 - err := schedulerstats.List(client.ServiceClient(), schedulerstats.ListOpts{Detail: true}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := schedulerstats.List(client.ServiceClient(fakeServer), schedulerstats.ListOpts{Detail: true}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := schedulerstats.ExtractStoragePools(page) - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) if len(actual) != 2 { t.Fatalf("Expected 2 backends, got %d", len(actual)) } - testhelper.CheckDeepEquals(t, StoragePoolFake1, actual[0]) - testhelper.CheckDeepEquals(t, StoragePoolFake2, actual[1]) + th.CheckDeepEquals(t, StoragePoolFake1, actual[0]) + th.CheckDeepEquals(t, StoragePoolFake2, actual[1]) return true, nil }) - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) if pages != 1 { t.Errorf("Expected 1 page, saw %d", pages) diff --git a/openstack/blockstorage/v3/services/testing/fixtures_test.go b/openstack/blockstorage/v3/services/testing/fixtures_test.go index adf7993df7..f1acd556d3 100644 --- a/openstack/blockstorage/v3/services/testing/fixtures_test.go +++ b/openstack/blockstorage/v3/services/testing/fixtures_test.go @@ -86,12 +86,12 @@ var ThirdFakeService = services.Service{ } // HandleListSuccessfully configures the test server to respond to a List request. -func HandleListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-services", func(w http.ResponseWriter, r *http.Request) { +func HandleListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-services", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ServiceListBody) + fmt.Fprint(w, ServiceListBody) }) } diff --git a/openstack/blockstorage/v3/services/testing/requests_test.go b/openstack/blockstorage/v3/services/testing/requests_test.go index 1fd5bcdd1e..12fa3bc33f 100644 --- a/openstack/blockstorage/v3/services/testing/requests_test.go +++ b/openstack/blockstorage/v3/services/testing/requests_test.go @@ -6,17 +6,17 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/services" "github.com/gophercloud/gophercloud/v2/pagination" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestListServices(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - HandleListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer) pages := 0 - err := services.List(client.ServiceClient(), services.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := services.List(client.ServiceClient(fakeServer), services.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := services.ExtractServices(page) @@ -27,14 +27,14 @@ func TestListServices(t *testing.T) { if len(actual) != 3 { t.Fatalf("Expected 3 services, got %d", len(actual)) } - testhelper.CheckDeepEquals(t, FirstFakeService, actual[0]) - testhelper.CheckDeepEquals(t, SecondFakeService, actual[1]) - testhelper.CheckDeepEquals(t, ThirdFakeService, actual[2]) + th.CheckDeepEquals(t, FirstFakeService, actual[0]) + th.CheckDeepEquals(t, SecondFakeService, actual[1]) + th.CheckDeepEquals(t, ThirdFakeService, actual[2]) return true, nil }) - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) if pages != 1 { t.Errorf("Expected 1 page, saw %d", pages) diff --git a/openstack/blockstorage/v3/snapshots/requests.go b/openstack/blockstorage/v3/snapshots/requests.go index 644e9cb1d3..85b67df879 100644 --- a/openstack/blockstorage/v3/snapshots/requests.go +++ b/openstack/blockstorage/v3/snapshots/requests.go @@ -122,6 +122,21 @@ func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pa }) } +// ListDetail returns Snapshots with additional details optionally limited by the conditions provided in ListOpts. +func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listDetailsURL(client) + if opts != nil { + query, err := opts.ToSnapshotListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return SnapshotPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + // UpdateOptsBuilder allows extensions to add additional parameters to the // Update request. type UpdateOptsBuilder interface { diff --git a/openstack/blockstorage/v3/snapshots/results.go b/openstack/blockstorage/v3/snapshots/results.go index 8a1440da42..636530a280 100644 --- a/openstack/blockstorage/v3/snapshots/results.go +++ b/openstack/blockstorage/v3/snapshots/results.go @@ -36,6 +36,21 @@ type Snapshot struct { // User-defined key-value pairs. Metadata map[string]string `json:"metadata"` + + // Progress of the snapshot creation. + Progress string `json:"os-extended-snapshot-attributes:progress"` + + // Project ID that owns the snapshot. + ProjectID string `json:"os-extended-snapshot-attributes:project_id"` + + // ID of the group snapshot, if applicable. + GroupSnapshotID string `json:"group_snapshot_id"` + + // User ID that created the snapshot. + UserID string `json:"user_id"` + + // Indicates whether the snapshot consumes quota. + ConsumesQuota bool `json:"consumes_quota"` } // CreateResult contains the response body and error from a Create request. @@ -95,7 +110,7 @@ func (r SnapshotPage) IsEmpty() (bool, error) { // NextPageURL uses the response's embedded link reference to navigate to the // next page of results. -func (r SnapshotPage) NextPageURL() (string, error) { +func (r SnapshotPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"snapshots_links"` } diff --git a/openstack/blockstorage/v3/snapshots/testing/fixtures_test.go b/openstack/blockstorage/v3/snapshots/testing/fixtures_test.go index 8bbc45e77d..a5ce93c38b 100644 --- a/openstack/blockstorage/v3/snapshots/testing/fixtures_test.go +++ b/openstack/blockstorage/v3/snapshots/testing/fixtures_test.go @@ -6,14 +6,14 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) // MockListResponse provides mock response for list snapshot API call -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots", func(w http.ResponseWriter, r *http.Request) { +func MockListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/snapshots", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -52,9 +52,65 @@ func MockListResponse(t *testing.T) { "rel": "next" }] } - `, th.Server.URL) + `, fakeServer.Server.URL) case "1": - fmt.Fprintf(w, `{"snapshots": []}`) + fmt.Fprint(w, `{"snapshots": []}`) + default: + t.Fatalf("Unexpected marker: [%s]", marker) + } + }) +} + +func MockListDetailsResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/snapshots/detail", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + if err := r.ParseForm(); err != nil { + t.Errorf("Failed to parse request form %v", err) + } + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprint(w, ` + { + "snapshots": [ + { + "id": "289da7f8-6440-407c-9fb4-7db01ec49164", + "name": "snapshot-001", + "volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c", + "description": "Daily Backup", + "status": "available", + "size": 30, + "created_at": "2017-05-30T03:35:03.000000", + "os-extended-snapshot-attributes:progress": "100%", + "os-extended-snapshot-attributes:project_id": "84b8950a-8594-4e5b-8dce-0dfa9c696357", + "group_snapshot_id": null, + "user_id": "075da7f8-6440-407c-9fb4-7db01ec49531", + "consumes_quota": true + }, + { + "id": "96c3bda7-c82a-4f50-be73-ca7621794835", + "name": "snapshot-002", + "volume_id": "76b8950a-8594-4e5b-8dce-0dfa9c696358", + "description": "Weekly Backup", + "status": "available", + "size": 25, + "created_at": "2017-05-30T03:35:03.000000", + "os-extended-snapshot-attributes:progress": "50%", + "os-extended-snapshot-attributes:project_id": "84b8950a-8594-4e5b-8dce-0dfa9c696357", + "group_snapshot_id": "865da7f8-6440-407c-9fb4-7db01ec40876", + "user_id": "075da7f8-6440-407c-9fb4-7db01ec49531", + "consumes_quota": false + } + ] + } + `) + case "1": + fmt.Fprint(w, `{"snapshots": []}`) default: t.Fatalf("Unexpected marker: [%s]", marker) } @@ -62,14 +118,14 @@ func MockListResponse(t *testing.T) { } // MockGetResponse provides mock response for get snapshot API call -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { +func MockGetResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/snapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "snapshot": { "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", @@ -86,10 +142,10 @@ func MockGetResponse(t *testing.T) { } // MockCreateResponse provides mock response for create snapshot API call -func MockCreateResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots", func(w http.ResponseWriter, r *http.Request) { +func MockCreateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/snapshots", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -104,7 +160,7 @@ func MockCreateResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "snapshot": { "volume_id": "1234", @@ -122,10 +178,10 @@ func MockCreateResponse(t *testing.T) { } // MockUpdateMetadataResponse provides mock response for update metadata snapshot API call -func MockUpdateMetadataResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots/123/metadata", func(w http.ResponseWriter, r *http.Request) { +func MockUpdateMetadataResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/snapshots/123/metadata", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestJSONRequest(t, r, ` { @@ -135,7 +191,7 @@ func MockUpdateMetadataResponse(t *testing.T) { } `) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "metadata": { "key": "v1" @@ -146,23 +202,23 @@ func MockUpdateMetadataResponse(t *testing.T) { } // MockDeleteResponse provides mock response for delete snapshot API call -func MockDeleteResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { +func MockDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/snapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) } // MockUpdateResponse provides mock response for update snapshot API call -func MockUpdateResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { +func MockUpdateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/snapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "snapshot": { "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", @@ -180,10 +236,10 @@ func MockUpdateResponse(t *testing.T) { } // MockResetStatusResponse provides mock response for reset snapshot status API call -func MockResetStatusResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22/action", func(w http.ResponseWriter, r *http.Request) { +func MockResetStatusResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/snapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestJSONRequest(t, r, ` { @@ -197,10 +253,10 @@ func MockResetStatusResponse(t *testing.T) { } // MockUpdateStatusResponse provides mock response for update snapshot status API call -func MockUpdateStatusResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22/action", func(w http.ResponseWriter, r *http.Request) { +func MockUpdateStatusResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/snapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestJSONRequest(t, r, ` { @@ -215,10 +271,10 @@ func MockUpdateStatusResponse(t *testing.T) { } // MockForceDeleteResponse provides mock response for force delete snapshot API call -func MockForceDeleteResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22/action", func(w http.ResponseWriter, r *http.Request) { +func MockForceDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/snapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestJSONRequest(t, r, ` { diff --git a/openstack/blockstorage/v3/snapshots/testing/requests_test.go b/openstack/blockstorage/v3/snapshots/testing/requests_test.go index e0ca0b8e0f..64e61b608b 100644 --- a/openstack/blockstorage/v3/snapshots/testing/requests_test.go +++ b/openstack/blockstorage/v3/snapshots/testing/requests_test.go @@ -12,14 +12,14 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) count := 0 - err := snapshots.List(client.ServiceClient(), &snapshots.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := snapshots.List(client.ServiceClient(fakeServer), &snapshots.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := snapshots.ExtractSnapshots(page) if err != nil { @@ -59,39 +59,95 @@ func TestList(t *testing.T) { } } +func TestDetailList(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + MockListDetailsResponse(t, fakeServer) + + count := 0 + + err := snapshots.ListDetail(client.ServiceClient(fakeServer), &snapshots.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + count++ + actual, err := snapshots.ExtractSnapshots(page) + if err != nil { + t.Errorf("Failed to extract snapshots: %v", err) + return false, err + } + expected := []snapshots.Snapshot{ + { + ID: "289da7f8-6440-407c-9fb4-7db01ec49164", + Name: "snapshot-001", + VolumeID: "521752a6-acf6-4b2d-bc7a-119f9148cd8c", + Status: "available", + Size: 30, + CreatedAt: time.Date(2017, 5, 30, 3, 35, 3, 0, time.UTC), + Description: "Daily Backup", + Progress: "100%", + ProjectID: "84b8950a-8594-4e5b-8dce-0dfa9c696357", + GroupSnapshotID: "", + UserID: "075da7f8-6440-407c-9fb4-7db01ec49531", + ConsumesQuota: true, + }, + { + ID: "96c3bda7-c82a-4f50-be73-ca7621794835", + Name: "snapshot-002", + VolumeID: "76b8950a-8594-4e5b-8dce-0dfa9c696358", + Status: "available", + Size: 25, + CreatedAt: time.Date(2017, 5, 30, 3, 35, 3, 0, time.UTC), + Description: "Weekly Backup", + Progress: "50%", + ProjectID: "84b8950a-8594-4e5b-8dce-0dfa9c696357", + GroupSnapshotID: "865da7f8-6440-407c-9fb4-7db01ec40876", + UserID: "075da7f8-6440-407c-9fb4-7db01ec49531", + ConsumesQuota: false, + }, + } + th.CheckDeepEquals(t, expected, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + + if count != 1 { + t.Errorf("Expected 1 page, got %d", count) + } +} + func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetResponse(t) + MockGetResponse(t, fakeServer) - v, err := snapshots.Get(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() + v, err := snapshots.Get(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, v.Name, "snapshot-001") - th.AssertEquals(t, v.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") + th.AssertEquals(t, "snapshot-001", v.Name) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", v.ID) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockCreateResponse(t) + MockCreateResponse(t, fakeServer) options := snapshots.CreateOpts{VolumeID: "1234", Name: "snapshot-001"} - n, err := snapshots.Create(context.TODO(), client.ServiceClient(), options).Extract() + n, err := snapshots.Create(context.TODO(), client.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.VolumeID, "1234") - th.AssertEquals(t, n.Name, "snapshot-001") - th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") + th.AssertEquals(t, "1234", n.VolumeID) + th.AssertEquals(t, "snapshot-001", n.Name) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", n.ID) } func TestUpdateMetadata(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockUpdateMetadataResponse(t) + MockUpdateMetadataResponse(t, fakeServer) expected := map[string]any{"key": "v1"} @@ -101,70 +157,70 @@ func TestUpdateMetadata(t *testing.T) { }, } - actual, err := snapshots.UpdateMetadata(context.TODO(), client.ServiceClient(), "123", options).ExtractMetadata() + actual, err := snapshots.UpdateMetadata(context.TODO(), client.ServiceClient(fakeServer), "123", options).ExtractMetadata() th.AssertNoErr(t, err) th.AssertDeepEquals(t, actual, expected) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDeleteResponse(t) + MockDeleteResponse(t, fakeServer) - res := snapshots.Delete(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22") + res := snapshots.Delete(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22") th.AssertNoErr(t, res.Err) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockUpdateResponse(t) + MockUpdateResponse(t, fakeServer) var name = "snapshot-002" var description = "Daily backup 002" options := snapshots.UpdateOpts{Name: &name, Description: &description} - v, err := snapshots.Update(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", options).Extract() + v, err := snapshots.Update(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22", options).Extract() th.AssertNoErr(t, err) th.CheckEquals(t, "snapshot-002", v.Name) th.CheckEquals(t, "Daily backup 002", v.Description) } func TestResetStatus(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockResetStatusResponse(t) + MockResetStatusResponse(t, fakeServer) opts := &snapshots.ResetStatusOpts{ Status: "error", } - res := snapshots.ResetStatus(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", opts) + res := snapshots.ResetStatus(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22", opts) th.AssertNoErr(t, res.Err) } func TestUpdateStatus(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockUpdateStatusResponse(t) + MockUpdateStatusResponse(t, fakeServer) opts := &snapshots.UpdateStatusOpts{ Status: "error", Progress: "80%", } - res := snapshots.UpdateStatus(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", opts) + res := snapshots.UpdateStatus(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22", opts) th.AssertNoErr(t, res.Err) } func TestForceDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockForceDeleteResponse(t) + MockForceDeleteResponse(t, fakeServer) - res := snapshots.ForceDelete(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22") + res := snapshots.ForceDelete(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22") th.AssertNoErr(t, res.Err) } diff --git a/openstack/blockstorage/v3/snapshots/urls.go b/openstack/blockstorage/v3/snapshots/urls.go index b68cee9ccf..de6a83cc32 100644 --- a/openstack/blockstorage/v3/snapshots/urls.go +++ b/openstack/blockstorage/v3/snapshots/urls.go @@ -18,6 +18,10 @@ func listURL(c *gophercloud.ServiceClient) string { return createURL(c) } +func listDetailsURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("snapshots", "detail") +} + func updateURL(c *gophercloud.ServiceClient, id string) string { return deleteURL(c, id) } diff --git a/openstack/blockstorage/v3/transfers/requests.go b/openstack/blockstorage/v3/transfers/requests.go index 9c762cfe1e..4d146ff235 100644 --- a/openstack/blockstorage/v3/transfers/requests.go +++ b/openstack/blockstorage/v3/transfers/requests.go @@ -7,6 +7,12 @@ import ( "github.com/gophercloud/gophercloud/v2/pagination" ) +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToCreateMap() (map[string]any, error) +} + // CreateOpts contains options for a Volume transfer. type CreateOpts struct { // The ID of the volume to transfer. @@ -23,7 +29,7 @@ func (opts CreateOpts) ToCreateMap() (map[string]any, error) { } // Create will create a volume tranfer request based on the values in CreateOpts. -func Create(ctx context.Context, client *gophercloud.ServiceClient, opts CreateOpts) (r CreateResult) { +func Create(ctx context.Context, client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { b, err := opts.ToCreateMap() if err != nil { r.Err = err @@ -36,6 +42,12 @@ func Create(ctx context.Context, client *gophercloud.ServiceClient, opts CreateO return } +// AcceptOptsBuilder allows extensions to add additional parameters to the +// Accept request. +type AcceptOptsBuilder interface { + ToAcceptMap() (map[string]any, error) +} + // AcceptOpts contains options for a Volume transfer accept reqeust. type AcceptOpts struct { // The auth key of the volume transfer to accept. @@ -49,7 +61,7 @@ func (opts AcceptOpts) ToAcceptMap() (map[string]any, error) { } // Accept will accept a volume tranfer request based on the values in AcceptOpts. -func Accept(ctx context.Context, client *gophercloud.ServiceClient, id string, opts AcceptOpts) (r CreateResult) { +func Accept(ctx context.Context, client *gophercloud.ServiceClient, id string, opts AcceptOptsBuilder) (r CreateResult) { b, err := opts.ToAcceptMap() if err != nil { r.Err = err diff --git a/openstack/blockstorage/v3/transfers/results.go b/openstack/blockstorage/v3/transfers/results.go index 42885c27ef..8b8894dd86 100644 --- a/openstack/blockstorage/v3/transfers/results.go +++ b/openstack/blockstorage/v3/transfers/results.go @@ -49,7 +49,7 @@ func (r commonResult) Extract() (*Transfer, error) { // ExtractInto converts our response data into a transfer struct func (r commonResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "transfer") + return r.ExtractIntoStructPtr(v, "transfer") } // CreateResult contains the response body and error from a Create request. @@ -76,7 +76,7 @@ func ExtractTransfers(r pagination.Page) ([]Transfer, error) { // ExtractTransfersInto similar to ExtractInto but operates on a `list` of transfers func ExtractTransfersInto(r pagination.Page, v any) error { - return r.(TransferPage).Result.ExtractIntoSlicePtr(v, "transfers") + return r.(TransferPage).ExtractIntoSlicePtr(v, "transfers") } // TransferPage is a pagination.pager that is returned from a call to the List function. @@ -94,7 +94,7 @@ func (r TransferPage) IsEmpty() (bool, error) { return len(transfers) == 0, err } -func (page TransferPage) NextPageURL() (string, error) { +func (page TransferPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"transfers_links"` } diff --git a/openstack/blockstorage/v3/transfers/testing/fixtures_test.go b/openstack/blockstorage/v3/transfers/testing/fixtures_test.go index b2c11f60c7..2e1d50b667 100644 --- a/openstack/blockstorage/v3/transfers/testing/fixtures_test.go +++ b/openstack/blockstorage/v3/transfers/testing/fixtures_test.go @@ -159,32 +159,32 @@ var AcceptResponse = transfers.Transfer{ }, } -func HandleCreateTransfer(t *testing.T) { - th.Mux.HandleFunc("/os-volume-transfer", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateTransfer(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-volume-transfer", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) } -func HandleAcceptTransfer(t *testing.T) { - th.Mux.HandleFunc("/os-volume-transfer/b8913bfd-a4d3-4ec5-bd8b-fe2dbeef9f4f/accept", func(w http.ResponseWriter, r *http.Request) { +func HandleAcceptTransfer(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-volume-transfer/b8913bfd-a4d3-4ec5-bd8b-fe2dbeef9f4f/accept", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") th.TestJSONRequest(t, r, AcceptTransferRequest) w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, AcceptTransferResponse) + fmt.Fprint(w, AcceptTransferResponse) }) } -func HandleDeleteTransfer(t *testing.T) { - th.Mux.HandleFunc("/os-volume-transfer/b8913bfd-a4d3-4ec5-bd8b-fe2dbeef9f4f", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteTransfer(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-volume-transfer/b8913bfd-a4d3-4ec5-bd8b-fe2dbeef9f4f", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -192,25 +192,25 @@ func HandleDeleteTransfer(t *testing.T) { }) } -func HandleListTransfers(t *testing.T) { - th.Mux.HandleFunc("/os-volume-transfer/detail", func(w http.ResponseWriter, r *http.Request) { +func HandleListTransfers(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-volume-transfer/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") th.TestFormValues(t, r, map[string]string{"all_tenants": "true"}) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } -func HandleGetTransfer(t *testing.T) { - th.Mux.HandleFunc("/os-volume-transfer/b8913bfd-a4d3-4ec5-bd8b-fe2dbeef9f4f", func(w http.ResponseWriter, r *http.Request) { +func HandleGetTransfer(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-volume-transfer/b8913bfd-a4d3-4ec5-bd8b-fe2dbeef9f4f", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } diff --git a/openstack/blockstorage/v3/transfers/testing/requests_test.go b/openstack/blockstorage/v3/transfers/testing/requests_test.go index 43013a71ec..d1065bdef0 100644 --- a/openstack/blockstorage/v3/transfers/testing/requests_test.go +++ b/openstack/blockstorage/v3/transfers/testing/requests_test.go @@ -11,44 +11,44 @@ import ( ) func TestCreateTransfer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateTransfer(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateTransfer(t, fakeServer) - actual, err := transfers.Create(context.TODO(), client.ServiceClient(), TransferRequest).Extract() + actual, err := transfers.Create(context.TODO(), client.ServiceClient(fakeServer), TransferRequest).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, TransferResponse, *actual) } func TestAcceptTransfer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAcceptTransfer(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAcceptTransfer(t, fakeServer) - actual, err := transfers.Accept(context.TODO(), client.ServiceClient(), TransferResponse.ID, AcceptRequest).Extract() + actual, err := transfers.Accept(context.TODO(), client.ServiceClient(fakeServer), TransferResponse.ID, AcceptRequest).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, AcceptResponse, *actual) } func TestDeleteTransfer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteTransfer(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteTransfer(t, fakeServer) - err := transfers.Delete(context.TODO(), client.ServiceClient(), TransferResponse.ID).ExtractErr() + err := transfers.Delete(context.TODO(), client.ServiceClient(fakeServer), TransferResponse.ID).ExtractErr() th.AssertNoErr(t, err) } func TestListTransfers(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListTransfers(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListTransfers(t, fakeServer) expectedResponse := TransferListResponse expectedResponse[0].AuthKey = "" count := 0 - err := transfers.List(client.ServiceClient(), &transfers.ListOpts{AllTenants: true}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := transfers.List(client.ServiceClient(fakeServer), &transfers.ListOpts{AllTenants: true}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := transfers.ExtractTransfers(page) @@ -59,18 +59,18 @@ func TestListTransfers(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListTransfersAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListTransfers(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListTransfers(t, fakeServer) expectedResponse := TransferListResponse expectedResponse[0].AuthKey = "" - allPages, err := transfers.List(client.ServiceClient(), &transfers.ListOpts{AllTenants: true}).AllPages(context.TODO()) + allPages, err := transfers.List(client.ServiceClient(fakeServer), &transfers.ListOpts{AllTenants: true}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := transfers.ExtractTransfers(allPages) th.AssertNoErr(t, err) @@ -78,14 +78,14 @@ func TestListTransfersAllPages(t *testing.T) { } func TestGetTransfer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetTransfer(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetTransfer(t, fakeServer) expectedResponse := TransferResponse expectedResponse.AuthKey = "" - actual, err := transfers.Get(context.TODO(), client.ServiceClient(), TransferResponse.ID).Extract() + actual, err := transfers.Get(context.TODO(), client.ServiceClient(fakeServer), TransferResponse.ID).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, expectedResponse, *actual) } diff --git a/openstack/blockstorage/v3/volumes/doc.go b/openstack/blockstorage/v3/volumes/doc.go index 2ab4af93ee..e018b57a8d 100644 --- a/openstack/blockstorage/v3/volumes/doc.go +++ b/openstack/blockstorage/v3/volumes/doc.go @@ -157,5 +157,12 @@ Example of Attaching a Volume to an Instance if err != nil { panic(err) } + +Example of Unmanaging a Volume + + err := volumes.Unmanage(context.TODO(), client, volume.ID).ExtractErr() + if err != nil { + panic(err) + } */ package volumes diff --git a/openstack/blockstorage/v3/volumes/requests.go b/openstack/blockstorage/v3/volumes/requests.go index 77210943b5..1026d1ecaa 100644 --- a/openstack/blockstorage/v3/volumes/requests.go +++ b/openstack/blockstorage/v3/volumes/requests.go @@ -623,6 +623,12 @@ func SetImageMetadata(ctx context.Context, client *gophercloud.ServiceClient, id return } +// BootableOptsBuilder allows extensions to add additional parameters to the +// SetBootable request. +type BootableOptsBuilder interface { + ToBootableMap() (map[string]any, error) +} + // BootableOpts contains options for setting bootable status to a volume. type BootableOpts struct { // Enables or disables the bootable attribute. You can boot an instance from a bootable volume. @@ -636,7 +642,7 @@ func (opts BootableOpts) ToBootableMap() (map[string]any, error) { } // SetBootable will set bootable status on a volume based on the values in BootableOpts -func SetBootable(ctx context.Context, client *gophercloud.ServiceClient, id string, opts BootableOpts) (r SetBootableResult) { +func SetBootable(ctx context.Context, client *gophercloud.ServiceClient, id string, opts BootableOptsBuilder) (r SetBootableResult) { b, err := opts.ToBootableMap() if err != nil { r.Err = err @@ -697,6 +703,12 @@ func ChangeType(ctx context.Context, client *gophercloud.ServiceClient, id strin return } +// ReImageOptsBuilder allows extensions to add additional parameters to the +// ReImage request. +type ReImageOptsBuilder interface { + ToReImageMap() (map[string]any, error) +} + // ReImageOpts contains options for Re-image a volume. type ReImageOpts struct { // New image id @@ -711,7 +723,7 @@ func (opts ReImageOpts) ToReImageMap() (map[string]any, error) { } // ReImage will re-image a volume based on the values in ReImageOpts -func ReImage(ctx context.Context, client *gophercloud.ServiceClient, id string, opts ReImageOpts) (r ReImageResult) { +func ReImage(ctx context.Context, client *gophercloud.ServiceClient, id string, opts ReImageOptsBuilder) (r ReImageResult) { b, err := opts.ToReImageMap() if err != nil { r.Err = err @@ -763,3 +775,14 @@ func ResetStatus(ctx context.Context, client *gophercloud.ServiceClient, id stri _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } + +// Unmanage removes a volume from Block Storage management without +// removing the back-end storage object that is associated with it. +func Unmanage(ctx context.Context, client *gophercloud.ServiceClient, id string) (r UnmanageResult) { + body := map[string]any{"os-unmanage": make(map[string]any)} + resp, err := client.Post(ctx, actionURL(client, id), body, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/blockstorage/v3/volumes/results.go b/openstack/blockstorage/v3/volumes/results.go index 3f184b398e..34923ba3d7 100644 --- a/openstack/blockstorage/v3/volumes/results.go +++ b/openstack/blockstorage/v3/volumes/results.go @@ -123,7 +123,7 @@ func (r VolumePage) IsEmpty() (bool, error) { return len(volumes) == 0, err } -func (page VolumePage) NextPageURL() (string, error) { +func (page VolumePage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"volumes_links"` } @@ -154,12 +154,12 @@ func (r commonResult) Extract() (*Volume, error) { // ExtractInto converts our response data into a volume struct func (r commonResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "volume") + return r.ExtractIntoStructPtr(v, "volume") } // ExtractVolumesInto similar to ExtractInto but operates on a `list` of volumes func ExtractVolumesInto(r pagination.Page, v any) error { - return r.(VolumePage).Result.ExtractIntoSlicePtr(v, "volumes") + return r.(VolumePage).ExtractIntoSlicePtr(v, "volumes") } // CreateResult contains the response body and error from a Create request. @@ -399,3 +399,8 @@ type ReImageResult struct { type ResetStatusResult struct { gophercloud.ErrResult } + +// UnmanageResult contains the response error from a Unmanage request. +type UnmanageResult struct { + gophercloud.ErrResult +} diff --git a/openstack/blockstorage/v3/volumes/testing/fixtures_test.go b/openstack/blockstorage/v3/volumes/testing/fixtures_test.go index 4efb9e08f4..2cabeb7ceb 100644 --- a/openstack/blockstorage/v3/volumes/testing/fixtures_test.go +++ b/openstack/blockstorage/v3/volumes/testing/fixtures_test.go @@ -6,13 +6,13 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/detail", func(w http.ResponseWriter, r *http.Request) { +func MockListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -93,23 +93,23 @@ func MockListResponse(t *testing.T) { "rel": "next" }] } - `, th.Server.URL) + `, fakeServer.Server.URL) case "1": - fmt.Fprintf(w, `{"volumes": []}`) + fmt.Fprint(w, `{"volumes": []}`) default: t.Fatalf("Unexpected marker: [%s]", marker) } }) } -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { +func MockGetResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volume": { "volume_type": "lvmdriver-1", @@ -153,10 +153,10 @@ func MockGetResponse(t *testing.T) { }) } -func MockCreateResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes", func(w http.ResponseWriter, r *http.Request) { +func MockCreateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -171,7 +171,7 @@ func MockCreateResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volume": { "size": 75, @@ -198,20 +198,20 @@ func MockCreateResponse(t *testing.T) { }) } -func MockDeleteResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { +func MockDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) }) } -func MockUpdateResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { +func MockUpdateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volume": { "name": "vol-002" @@ -221,10 +221,10 @@ func MockUpdateResponse(t *testing.T) { }) } -func MockCreateVolumeFromBackupResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes", func(w http.ResponseWriter, r *http.Request) { +func MockCreateVolumeFromBackupResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -239,7 +239,7 @@ func MockCreateVolumeFromBackupResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volume": { "size": 30, @@ -266,11 +266,11 @@ func MockCreateVolumeFromBackupResponse(t *testing.T) { }) } -func MockAttachResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", +func MockAttachResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -287,15 +287,15 @@ func MockAttachResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } -func MockBeginDetachingResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", +func MockBeginDetachingResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -307,15 +307,15 @@ func MockBeginDetachingResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } -func MockDetachResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", +func MockDetachResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -327,15 +327,15 @@ func MockDetachResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } -func MockUploadImageResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", +func MockUploadImageResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -352,7 +352,7 @@ func MockUploadImageResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "os-volume_upload_image": { "container_format": "bare", @@ -383,11 +383,11 @@ func MockUploadImageResponse(t *testing.T) { }) } -func MockReserveResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", +func MockReserveResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -399,15 +399,15 @@ func MockReserveResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } -func MockUnreserveResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", +func MockUnreserveResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -419,15 +419,15 @@ func MockUnreserveResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } -func MockInitializeConnectionResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", +func MockInitializeConnectionResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -450,7 +450,7 @@ func MockInitializeConnectionResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "connection_info": { "data": { "target_portals": [ @@ -480,11 +480,11 @@ func MockInitializeConnectionResponse(t *testing.T) { }) } -func MockTerminateConnectionResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", +func MockTerminateConnectionResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -507,15 +507,15 @@ func MockTerminateConnectionResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } -func MockExtendSizeResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", +func MockExtendSizeResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -530,23 +530,23 @@ func MockExtendSizeResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } -func MockForceDeleteResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22/action", func(w http.ResponseWriter, r *http.Request) { +func MockForceDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestBody(t, r, `{"os-force_delete":""}`) w.WriteHeader(http.StatusAccepted) }) } -func MockSetImageMetadataResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { +func MockSetImageMetadataResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -561,14 +561,14 @@ func MockSetImageMetadataResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } -func MockSetBootableResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { +func MockSetBootableResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -584,10 +584,10 @@ func MockSetBootableResponse(t *testing.T) { }) } -func MockReImageResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { +func MockReImageResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -604,11 +604,11 @@ func MockReImageResponse(t *testing.T) { }) } -func MockChangeTypeResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", +func MockChangeTypeResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -624,15 +624,15 @@ func MockChangeTypeResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } -func MockResetStatusResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", +func MockResetStatusResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestJSONRequest(t, r, ` { @@ -648,3 +648,19 @@ func MockResetStatusResponse(t *testing.T) { w.WriteHeader(http.StatusAccepted) }) } + +func MockUnmanageResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, ` +{ + "os-unmanage": {} +} + `) + + w.WriteHeader(http.StatusAccepted) + }) +} diff --git a/openstack/blockstorage/v3/volumes/testing/requests_test.go b/openstack/blockstorage/v3/volumes/testing/requests_test.go index 5e32346747..145fb0ed29 100644 --- a/openstack/blockstorage/v3/volumes/testing/requests_test.go +++ b/openstack/blockstorage/v3/volumes/testing/requests_test.go @@ -13,14 +13,14 @@ import ( ) func TestListWithExtensions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) count := 0 - err := volumes.List(client.ServiceClient(), &volumes.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := volumes.List(client.ServiceClient(fakeServer), &volumes.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := volumes.ExtractVolumes(page) if err != nil { @@ -98,12 +98,12 @@ func TestListWithExtensions(t *testing.T) { } func TestListAllWithExtensions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) - allPages, err := volumes.List(client.ServiceClient(), &volumes.ListOpts{}).AllPages(context.TODO()) + allPages, err := volumes.List(client.ServiceClient(fakeServer), &volumes.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) var actual []volumes.Volume @@ -116,12 +116,12 @@ func TestListAllWithExtensions(t *testing.T) { } func TestListAll(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) - allPages, err := volumes.List(client.ServiceClient(), &volumes.ListOpts{}).AllPages(context.TODO()) + allPages, err := volumes.List(client.ServiceClient(fakeServer), &volumes.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := volumes.ExtractVolumes(allPages) th.AssertNoErr(t, err) @@ -189,30 +189,30 @@ func TestListAll(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetResponse(t) + MockGetResponse(t, fakeServer) - v, err := volumes.Get(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() + v, err := volumes.Get(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, v.Name, "vol-001") - th.AssertEquals(t, v.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") + th.AssertEquals(t, "vol-001", v.Name) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", v.ID) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockCreateResponse(t) + MockCreateResponse(t, fakeServer) options := &volumes.CreateOpts{Size: 75, Name: "vol-001"} - n, err := volumes.Create(context.TODO(), client.ServiceClient(), options, nil).Extract() + n, err := volumes.Create(context.TODO(), client.ServiceClient(fakeServer), options, nil).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.Size, 75) - th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") + th.AssertEquals(t, 75, n.Size) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", n.ID) } func TestCreateSchedulerHints(t *testing.T) { @@ -250,103 +250,103 @@ func TestCreateSchedulerHints(t *testing.T) { } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDeleteResponse(t) + MockDeleteResponse(t, fakeServer) - res := volumes.Delete(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", volumes.DeleteOpts{}) + res := volumes.Delete(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22", volumes.DeleteOpts{}) th.AssertNoErr(t, res.Err) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockUpdateResponse(t) + MockUpdateResponse(t, fakeServer) var name = "vol-002" options := volumes.UpdateOpts{Name: &name} - v, err := volumes.Update(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", options).Extract() + v, err := volumes.Update(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22", options).Extract() th.AssertNoErr(t, err) th.CheckEquals(t, "vol-002", v.Name) } func TestGetWithExtensions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetResponse(t) + MockGetResponse(t, fakeServer) var v volumes.Volume - err := volumes.Get(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(&v) + err := volumes.Get(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(&v) th.AssertNoErr(t, err) th.AssertEquals(t, "304dc00909ac4d0da6c62d816bcb3459", v.TenantID) th.AssertEquals(t, "centos", v.VolumeImageMetadata["image_name"]) - err = volumes.Get(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(v) + err = volumes.Get(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(v) if err == nil { t.Errorf("Expected error when providing non-pointer struct") } } func TestCreateFromBackup(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockCreateVolumeFromBackupResponse(t) + MockCreateVolumeFromBackupResponse(t, fakeServer) options := volumes.CreateOpts{ Name: "vol-001", BackupID: "20c792f0-bb03-434f-b653-06ef238e337e", } - v, err := volumes.Create(context.TODO(), client.ServiceClient(), options, nil).Extract() + v, err := volumes.Create(context.TODO(), client.ServiceClient(fakeServer), options, nil).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, v.Size, 30) - th.AssertEquals(t, v.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") - th.AssertEquals(t, *v.BackupID, "20c792f0-bb03-434f-b653-06ef238e337e") + th.AssertEquals(t, 30, v.Size) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", v.ID) + th.AssertEquals(t, "20c792f0-bb03-434f-b653-06ef238e337e", *v.BackupID) } func TestAttach(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockAttachResponse(t) + MockAttachResponse(t, fakeServer) options := &volumes.AttachOpts{ MountPoint: "/mnt", Mode: "rw", InstanceUUID: "50902f4f-a974-46a0-85e9-7efc5e22dfdd", } - err := volumes.Attach(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() + err := volumes.Attach(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() th.AssertNoErr(t, err) } func TestBeginDetaching(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockBeginDetachingResponse(t) + MockBeginDetachingResponse(t, fakeServer) - err := volumes.BeginDetaching(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c").ExtractErr() + err := volumes.BeginDetaching(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c").ExtractErr() th.AssertNoErr(t, err) } func TestDetach(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDetachResponse(t) + MockDetachResponse(t, fakeServer) - err := volumes.Detach(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", &volumes.DetachOpts{}).ExtractErr() + err := volumes.Detach(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c", &volumes.DetachOpts{}).ExtractErr() th.AssertNoErr(t, err) } func TestUploadImage(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - MockUploadImageResponse(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + MockUploadImageResponse(t, fakeServer) options := &volumes.UploadImageOpts{ ContainerFormat: "bare", DiskFormat: "raw", @@ -354,7 +354,7 @@ func TestUploadImage(t *testing.T) { Force: true, } - actual, err := volumes.UploadImage(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).Extract() + actual, err := volumes.UploadImage(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c", options).Extract() th.AssertNoErr(t, err) expected := volumes.VolumeImage{ @@ -384,30 +384,30 @@ func TestUploadImage(t *testing.T) { } func TestReserve(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockReserveResponse(t) + MockReserveResponse(t, fakeServer) - err := volumes.Reserve(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c").ExtractErr() + err := volumes.Reserve(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c").ExtractErr() th.AssertNoErr(t, err) } func TestUnreserve(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockUnreserveResponse(t) + MockUnreserveResponse(t, fakeServer) - err := volumes.Unreserve(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c").ExtractErr() + err := volumes.Unreserve(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c").ExtractErr() th.AssertNoErr(t, err) } func TestInitializeConnection(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockInitializeConnectionResponse(t) + MockInitializeConnectionResponse(t, fakeServer) options := &volumes.InitializeConnectionOpts{ IP: "127.0.0.1", @@ -417,15 +417,15 @@ func TestInitializeConnection(t *testing.T) { Platform: "x86_64", OSType: "linux2", } - _, err := volumes.InitializeConnection(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).Extract() + _, err := volumes.InitializeConnection(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c", options).Extract() th.AssertNoErr(t, err) } func TestTerminateConnection(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockTerminateConnectionResponse(t) + MockTerminateConnectionResponse(t, fakeServer) options := &volumes.TerminateConnectionOpts{ IP: "127.0.0.1", @@ -435,39 +435,39 @@ func TestTerminateConnection(t *testing.T) { Platform: "x86_64", OSType: "linux2", } - err := volumes.TerminateConnection(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() + err := volumes.TerminateConnection(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() th.AssertNoErr(t, err) } func TestExtendSize(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockExtendSizeResponse(t) + MockExtendSizeResponse(t, fakeServer) options := &volumes.ExtendSizeOpts{ NewSize: 3, } - err := volumes.ExtendSize(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() + err := volumes.ExtendSize(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() th.AssertNoErr(t, err) } func TestForceDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockForceDeleteResponse(t) + MockForceDeleteResponse(t, fakeServer) - res := volumes.ForceDelete(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22") + res := volumes.ForceDelete(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22") th.AssertNoErr(t, res.Err) } func TestSetImageMetadata(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockSetImageMetadataResponse(t) + MockSetImageMetadataResponse(t, fakeServer) options := &volumes.ImageMetadataOpts{ Metadata: map[string]string{ @@ -475,59 +475,59 @@ func TestSetImageMetadata(t *testing.T) { }, } - err := volumes.SetImageMetadata(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() + err := volumes.SetImageMetadata(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() th.AssertNoErr(t, err) } func TestSetBootable(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockSetBootableResponse(t) + MockSetBootableResponse(t, fakeServer) options := volumes.BootableOpts{ Bootable: true, } - err := volumes.SetBootable(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() + err := volumes.SetBootable(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() th.AssertNoErr(t, err) } func TestReImage(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockReImageResponse(t) + MockReImageResponse(t, fakeServer) options := volumes.ReImageOpts{ ImageID: "71543ced-a8af-45b6-a5c4-a46282108a90", ReImageReserved: false, } - err := volumes.ReImage(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() + err := volumes.ReImage(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() th.AssertNoErr(t, err) } func TestChangeType(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockChangeTypeResponse(t) + MockChangeTypeResponse(t, fakeServer) options := &volumes.ChangeTypeOpts{ NewType: "ssd", MigrationPolicy: "on-demand", } - err := volumes.ChangeType(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() + err := volumes.ChangeType(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() th.AssertNoErr(t, err) } func TestResetStatus(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockResetStatusResponse(t) + MockResetStatusResponse(t, fakeServer) options := &volumes.ResetStatusOpts{ Status: "error", @@ -535,6 +535,16 @@ func TestResetStatus(t *testing.T) { MigrationStatus: "migrating", } - err := volumes.ResetStatus(context.TODO(), client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() + err := volumes.ResetStatus(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestUnmanage(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + MockUnmanageResponse(t, fakeServer) + + err := volumes.Unmanage(context.TODO(), client.ServiceClient(fakeServer), "cd281d77-8217-4830-be95-9528227c105c").ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/blockstorage/v3/volumetypes/requests.go b/openstack/blockstorage/v3/volumetypes/requests.go index d419d75d9b..d62db1c8b6 100644 --- a/openstack/blockstorage/v3/volumetypes/requests.go +++ b/openstack/blockstorage/v3/volumetypes/requests.go @@ -73,6 +73,13 @@ type ListOptsBuilder interface { // ListOpts holds options for listing Volume Types. It is passed to the volumetypes.List // function. type ListOpts struct { + // Name will filter by the specified volume type name. + Name string `q:"name"` + // Description will filter by the specified volume type description. + Description string `q:"description"` + // Specifies whether the query should include public or private Volume Types. + // By default, it queries both types. + IsPublic visibility `q:"is_public"` // Comma-separated list of sort keys and optional sort directions in the // form of [:]. Sort string `q:"sort"` @@ -84,8 +91,22 @@ type ListOpts struct { Marker string `q:"marker"` } +type visibility string + +const ( + // VisibilityDefault enables querying both public and private Volume Types. + VisibilityDefault visibility = "None" + // VisibilityPublic restricts the query to only public Volume Types. + VisibilityPublic visibility = "true" + // VisibilityPrivate restricts the query to only private Volume Types. + VisibilityPrivate visibility = "false" +) + // ToVolumeTypeListQuery formats a ListOpts into a query string. func (opts ListOpts) ToVolumeTypeListQuery() (string, error) { + if opts.IsPublic == "" { + opts.IsPublic = VisibilityDefault + } q, err := gophercloud.BuildQueryString(opts) return q.String(), err } @@ -93,14 +114,14 @@ func (opts ListOpts) ToVolumeTypeListQuery() (string, error) { // List returns Volume types. func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { url := listURL(client) - - if opts != nil { - query, err := opts.ToVolumeTypeListQuery() - if err != nil { - return pagination.Pager{Err: err} - } - url += query + if opts == nil { + opts = ListOpts{} + } + query, err := opts.ToVolumeTypeListQuery() + if err != nil { + return pagination.Pager{Err: err} } + url += query return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { return VolumeTypePage{pagination.LinkedPageBase{PageResult: r}} diff --git a/openstack/blockstorage/v3/volumetypes/results.go b/openstack/blockstorage/v3/volumetypes/results.go index 9d5dce56ee..23f2834a34 100644 --- a/openstack/blockstorage/v3/volumetypes/results.go +++ b/openstack/blockstorage/v3/volumetypes/results.go @@ -38,7 +38,7 @@ func (r VolumeTypePage) IsEmpty() (bool, error) { return len(volumetypes) == 0, err } -func (page VolumeTypePage) NextPageURL() (string, error) { +func (page VolumeTypePage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"volume_type_links"` } @@ -69,12 +69,12 @@ func (r commonResult) Extract() (*VolumeType, error) { // ExtractInto converts our response data into a volume type struct func (r commonResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "volume_type") + return r.ExtractIntoStructPtr(v, "volume_type") } // ExtractVolumeTypesInto similar to ExtractInto but operates on a `list` of volume types func ExtractVolumeTypesInto(r pagination.Page, v any) error { - return r.(VolumeTypePage).Result.ExtractIntoSlicePtr(v, "volume_types") + return r.(VolumeTypePage).ExtractIntoSlicePtr(v, "volume_types") } // GetResult contains the response body and error from a Get request. @@ -228,7 +228,7 @@ func (r encryptionResult) Extract() (*EncryptionType, error) { // ExtractInto converts our response data into a volume type struct func (r encryptionResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "encryption") + return r.ExtractIntoStructPtr(v, "encryption") } type CreateEncryptionResult struct { diff --git a/openstack/blockstorage/v3/volumetypes/testing/fixtures_test.go b/openstack/blockstorage/v3/volumetypes/testing/fixtures_test.go index e1db5dfbd4..d305f22fbb 100644 --- a/openstack/blockstorage/v3/volumetypes/testing/fixtures_test.go +++ b/openstack/blockstorage/v3/volumetypes/testing/fixtures_test.go @@ -6,13 +6,13 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/types", func(w http.ResponseWriter, r *http.Request) { +func MockListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -56,23 +56,23 @@ func MockListResponse(t *testing.T) { } ] } - `, th.Server.URL) + `, fakeServer.Server.URL) case "1": - fmt.Fprintf(w, `{"volume_types": []}`) + fmt.Fprint(w, `{"volume_types": []}`) default: t.Fatalf("Unexpected marker: [%s]", marker) } }) } -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc("/types/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { +func MockGetResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volume_type": { "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", @@ -90,10 +90,10 @@ func MockGetResponse(t *testing.T) { }) } -func MockCreateResponse(t *testing.T) { - th.Mux.HandleFunc("/types", func(w http.ResponseWriter, r *http.Request) { +func MockCreateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -112,7 +112,7 @@ func MockCreateResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volume_type": { "name": "test_type", @@ -130,20 +130,20 @@ func MockCreateResponse(t *testing.T) { }) } -func MockDeleteResponse(t *testing.T) { - th.Mux.HandleFunc("/types/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { +func MockDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) }) } -func MockUpdateResponse(t *testing.T) { - th.Mux.HandleFunc("/types/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { +func MockUpdateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volume_type": { "name": "vol-type-002", @@ -195,34 +195,149 @@ var UpdatedExtraSpec = map[string]string{ "capabilities": "gpu-2", } -func HandleExtraSpecsListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/types/1/extra_specs", func(w http.ResponseWriter, r *http.Request) { +func HandleListIsPublicParam(t *testing.T, fakeServer th.FakeServer, values map[string]string) { + fakeServer.Mux.HandleFunc("/types", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestFormValues(t, r, values) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"volume_types": []}`) + }) +} + +func HandleListWithNameFilter(t *testing.T, fakeServer th.FakeServer, values map[string]string) { + fakeServer.Mux.HandleFunc("/types", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestFormValues(t, r, values) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, ` +{ + "volume_types": [ + { + "name": "test-type", + "qos_specs_id": null, + "os-volume-type-access:is_public": true, + "extra_specs": { + "storage_protocol": "nfs" + }, + "is_public": true, + "id": "996af3df-92fd-4814-a0ee-ba5f899aa1ec", + "description": "test" + } + ] +} +`) + }) +} + +func HandleListWithDescriptionFilter(t *testing.T, fakeServer th.FakeServer, values map[string]string) { + fakeServer.Mux.HandleFunc("/types", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestFormValues(t, r, values) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, ` +{ + "volume_types": [ + { + "name": "test-type", + "qos_specs_id": null, + "os-volume-type-access:is_public": true, + "extra_specs": { + "multiattach": " True" + }, + "is_public": true, + "id": "ab948f0a-13ed-47c8-b9be-cade0beb0706", + "description": "test" + } + ] +} +`) + }) +} + +func HandleListWithExtraSpecsFilter(t *testing.T, fakeServer th.FakeServer, values map[string]string) { + fakeServer.Mux.HandleFunc("/types", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestFormValues(t, r, values) + + if err := r.ParseForm(); err != nil { + t.Errorf("Failed to parse request form %v", err) + } + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + extraSpecsFilter := r.Form.Get("extra_specs") + + // Determine which volume type to return based on extra_specs filter. + // Note: We only test with a single extra_spec value because testing multiple + // values is not feasible due to the non-deterministic order of map keys in Go. + // The order of keys in the serialized string can vary between runs, making + // it impossible to reliably match the expected string without adding complex + // normalization code that would clutter the test. + if extraSpecsFilter == "{'storage_protocol':'nfs'}" { + // Return only nfs-type + fmt.Fprint(w, ` +{ + "volume_types": [ + { + "name": "nfs-type", + "qos_specs_id": null, + "os-volume-type-access:is_public": true, + "extra_specs": { + "storage_protocol": "nfs" + }, + "is_public": true, + "id": "6b0cfee7-48b6-41b7-9d68-0d74cbdc08de", + "description": "NFS storage type" + } + ] +} +`) + } else { + // Default: return empty list + fmt.Fprint(w, `{"volume_types": []}`) + } + }) +} + +func HandleExtraSpecsListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types/1/extra_specs", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ExtraSpecsGetBody) + fmt.Fprint(w, ExtraSpecsGetBody) }) } -func HandleExtraSpecGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/types/1/extra_specs/capabilities", func(w http.ResponseWriter, r *http.Request) { +func HandleExtraSpecGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types/1/extra_specs/capabilities", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetExtraSpecBody) + fmt.Fprint(w, GetExtraSpecBody) }) } -func HandleExtraSpecsCreateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/types/1/extra_specs", func(w http.ResponseWriter, r *http.Request) { +func HandleExtraSpecsCreateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types/1/extra_specs", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, `{ "extra_specs": { @@ -233,14 +348,14 @@ func HandleExtraSpecsCreateSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ExtraSpecsGetBody) + fmt.Fprint(w, ExtraSpecsGetBody) }) } -func HandleExtraSpecUpdateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/types/1/extra_specs/capabilities", func(w http.ResponseWriter, r *http.Request) { +func HandleExtraSpecUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types/1/extra_specs/capabilities", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, `{ "capabilities": "gpu-2" @@ -248,23 +363,23 @@ func HandleExtraSpecUpdateSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdatedExtraSpecBody) + fmt.Fprint(w, UpdatedExtraSpecBody) }) } -func HandleExtraSpecDeleteSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/types/1/extra_specs/capabilities", func(w http.ResponseWriter, r *http.Request) { +func HandleExtraSpecDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types/1/extra_specs/capabilities", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) }) } -func MockEncryptionCreateResponse(t *testing.T) { - th.Mux.HandleFunc("/types/a5082c24-2a27-43a4-b48e-fcec1240e36b/encryption", func(w http.ResponseWriter, r *http.Request) { +func MockEncryptionCreateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types/a5082c24-2a27-43a4-b48e-fcec1240e36b/encryption", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -281,7 +396,7 @@ func MockEncryptionCreateResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "encryption": { "volume_type_id": "a5082c24-2a27-43a4-b48e-fcec1240e36b", @@ -296,18 +411,18 @@ func MockEncryptionCreateResponse(t *testing.T) { }) } -func MockDeleteEncryptionResponse(t *testing.T) { - th.Mux.HandleFunc("/types/a5082c24-2a27-43a4-b48e-fcec1240e36b/encryption/81e069c6-7394-4856-8df7-3b237ca61f74", func(w http.ResponseWriter, r *http.Request) { +func MockDeleteEncryptionResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types/a5082c24-2a27-43a4-b48e-fcec1240e36b/encryption/81e069c6-7394-4856-8df7-3b237ca61f74", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) }) } -func MockEncryptionUpdateResponse(t *testing.T) { - th.Mux.HandleFunc("/types/a5082c24-2a27-43a4-b48e-fcec1240e36b/encryption/81e069c6-7394-4856-8df7-3b237ca61f74", func(w http.ResponseWriter, r *http.Request) { +func MockEncryptionUpdateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types/a5082c24-2a27-43a4-b48e-fcec1240e36b/encryption/81e069c6-7394-4856-8df7-3b237ca61f74", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -324,7 +439,7 @@ func MockEncryptionUpdateResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "encryption": { "control_location": "front-end", @@ -337,15 +452,15 @@ func MockEncryptionUpdateResponse(t *testing.T) { }) } -func MockEncryptionGetResponse(t *testing.T) { - th.Mux.HandleFunc("/types/a5082c24-2a27-43a4-b48e-fcec1240e36b/encryption", func(w http.ResponseWriter, r *http.Request) { +func MockEncryptionGetResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types/a5082c24-2a27-43a4-b48e-fcec1240e36b/encryption", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volume_type_id": "a5082c24-2a27-43a4-b48e-fcec1240e36b", "control_location": "front-end", @@ -362,15 +477,15 @@ func MockEncryptionGetResponse(t *testing.T) { }) } -func MockEncryptionGetSpecResponse(t *testing.T) { - th.Mux.HandleFunc("/types/a5082c24-2a27-43a4-b48e-fcec1240e36b/encryption/cipher", func(w http.ResponseWriter, r *http.Request) { +func MockEncryptionGetSpecResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types/a5082c24-2a27-43a4-b48e-fcec1240e36b/encryption/cipher", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "cipher": "aes-xts-plain64" } diff --git a/openstack/blockstorage/v3/volumetypes/testing/requests_test.go b/openstack/blockstorage/v3/volumetypes/testing/requests_test.go index 56f22632f9..aa19f08e2e 100644 --- a/openstack/blockstorage/v3/volumetypes/testing/requests_test.go +++ b/openstack/blockstorage/v3/volumetypes/testing/requests_test.go @@ -14,12 +14,12 @@ import ( ) func TestListAll(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) pages := 0 - err := volumetypes.List(client.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := volumetypes.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := volumetypes.ExtractVolumeTypes(page) if err != nil { @@ -48,30 +48,30 @@ func TestListAll(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.AssertEquals(t, pages, 1) + th.AssertEquals(t, 1, pages) } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetResponse(t) + MockGetResponse(t, fakeServer) - v, err := volumetypes.Get(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() + v, err := volumetypes.Get(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, v.Name, "vol-type-001") - th.AssertEquals(t, v.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") - th.AssertEquals(t, v.ExtraSpecs["capabilities"], "gpu") - th.AssertEquals(t, v.QosSpecID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") - th.AssertEquals(t, v.PublicAccess, true) + th.AssertEquals(t, "vol-type-001", v.Name) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", v.ID) + th.AssertEquals(t, "gpu", v.ExtraSpecs["capabilities"]) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", v.QosSpecID) + th.AssertTrue(t, v.PublicAccess) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockCreateResponse(t) + MockCreateResponse(t, fakeServer) var isPublic = true @@ -82,32 +82,32 @@ func TestCreate(t *testing.T) { ExtraSpecs: map[string]string{"capabilities": "gpu"}, } - n, err := volumetypes.Create(context.TODO(), client.ServiceClient(), options).Extract() + n, err := volumetypes.Create(context.TODO(), client.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.Name, "test_type") - th.AssertEquals(t, n.Description, "test_type_desc") - th.AssertEquals(t, n.IsPublic, true) - th.AssertEquals(t, n.PublicAccess, true) - th.AssertEquals(t, n.ID, "6d0ff92a-0007-4780-9ece-acfe5876966a") - th.AssertEquals(t, n.ExtraSpecs["capabilities"], "gpu") + th.AssertEquals(t, "test_type", n.Name) + th.AssertEquals(t, "test_type_desc", n.Description) + th.AssertTrue(t, n.IsPublic) + th.AssertTrue(t, n.PublicAccess) + th.AssertEquals(t, "6d0ff92a-0007-4780-9ece-acfe5876966a", n.ID) + th.AssertEquals(t, "gpu", n.ExtraSpecs["capabilities"]) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDeleteResponse(t) + MockDeleteResponse(t, fakeServer) - res := volumetypes.Delete(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22") + res := volumetypes.Delete(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22") th.AssertNoErr(t, res.Err) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockUpdateResponse(t) + MockUpdateResponse(t, fakeServer) var isPublic = true var name = "vol-type-002" @@ -116,81 +116,167 @@ func TestUpdate(t *testing.T) { IsPublic: &isPublic, } - v, err := volumetypes.Update(context.TODO(), client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", options).Extract() + v, err := volumetypes.Update(context.TODO(), client.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22", options).Extract() th.AssertNoErr(t, err) th.CheckEquals(t, "vol-type-002", v.Name) - th.CheckEquals(t, true, v.IsPublic) + th.CheckTrue(t, v.IsPublic) +} + +func TestListIsPublicParam(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + result := make(map[string]string) + HandleListIsPublicParam(t, fakeServer, result) + + // An empty ListOpts should query both public and private volume types by default. + result["is_public"] = "None" + err := volumetypes.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + return true, nil + }) + th.AssertNoErr(t, err) + + err = volumetypes.List(client.ServiceClient(fakeServer), volumetypes.ListOpts{IsPublic: volumetypes.VisibilityDefault}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + return true, nil + }) + th.AssertNoErr(t, err) + + // Specific visibility queries + result["is_public"] = "true" + err = volumetypes.List(client.ServiceClient(fakeServer), volumetypes.ListOpts{IsPublic: volumetypes.VisibilityPublic}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + return true, nil + }) + th.AssertNoErr(t, err) + + result["is_public"] = "false" + err = volumetypes.List(client.ServiceClient(fakeServer), volumetypes.ListOpts{IsPublic: volumetypes.VisibilityPrivate}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + return true, nil + }) + th.AssertNoErr(t, err) + + // Specific visibility query while ensuring other options are still considered. + result["is_public"] = "None" + result["sort"] = "asc" + err = volumetypes.List(client.ServiceClient(fakeServer), volumetypes.ListOpts{IsPublic: volumetypes.VisibilityDefault, Sort: "asc"}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + return true, nil + }) + th.AssertNoErr(t, err) +} + +func TestListNameParam(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + result := make(map[string]string) + HandleListWithNameFilter(t, fakeServer, result) + + result["is_public"] = "None" + result["name"] = "test-type" + allPages, err := volumetypes.List(client.ServiceClient(fakeServer), volumetypes.ListOpts{ + Name: "test-type", + }).AllPages(context.TODO()) + th.AssertNoErr(t, err) + actual, err := volumetypes.ExtractVolumeTypes(allPages) + th.AssertNoErr(t, err) + th.AssertEquals(t, 1, len(actual)) + th.AssertEquals(t, "test-type", actual[0].Name) + th.AssertEquals(t, "996af3df-92fd-4814-a0ee-ba5f899aa1ec", actual[0].ID) + th.AssertEquals(t, "test", actual[0].Description) + th.AssertTrue(t, actual[0].IsPublic) + th.AssertTrue(t, actual[0].PublicAccess) + th.AssertEquals(t, "nfs", actual[0].ExtraSpecs["storage_protocol"]) +} + +func TestListDescriptionParam(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + result := make(map[string]string) + HandleListWithDescriptionFilter(t, fakeServer, result) + + result["is_public"] = "None" + result["description"] = "test" + allPages, err := volumetypes.List(client.ServiceClient(fakeServer), volumetypes.ListOpts{ + Description: "test", + }).AllPages(context.TODO()) + th.AssertNoErr(t, err) + actual, err := volumetypes.ExtractVolumeTypes(allPages) + th.AssertNoErr(t, err) + th.AssertEquals(t, 1, len(actual)) + th.AssertEquals(t, "test", actual[0].Description) + th.AssertEquals(t, "test-type", actual[0].Name) + th.AssertEquals(t, "ab948f0a-13ed-47c8-b9be-cade0beb0706", actual[0].ID) + th.AssertTrue(t, actual[0].IsPublic) + th.AssertTrue(t, actual[0].PublicAccess) + th.AssertEquals(t, " True", actual[0].ExtraSpecs["multiattach"]) } func TestVolumeTypeExtraSpecsList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleExtraSpecsListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleExtraSpecsListSuccessfully(t, fakeServer) expected := ExtraSpecs - actual, err := volumetypes.ListExtraSpecs(context.TODO(), client.ServiceClient(), "1").Extract() + actual, err := volumetypes.ListExtraSpecs(context.TODO(), client.ServiceClient(fakeServer), "1").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, expected, actual) } func TestVolumeTypeExtraSpecGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleExtraSpecGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleExtraSpecGetSuccessfully(t, fakeServer) expected := ExtraSpec - actual, err := volumetypes.GetExtraSpec(context.TODO(), client.ServiceClient(), "1", "capabilities").Extract() + actual, err := volumetypes.GetExtraSpec(context.TODO(), client.ServiceClient(fakeServer), "1", "capabilities").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, expected, actual) } func TestVolumeTypeExtraSpecsCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleExtraSpecsCreateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleExtraSpecsCreateSuccessfully(t, fakeServer) createOpts := volumetypes.ExtraSpecsOpts{ "capabilities": "gpu", "volume_backend_name": "ssd", } expected := ExtraSpecs - actual, err := volumetypes.CreateExtraSpecs(context.TODO(), client.ServiceClient(), "1", createOpts).Extract() + actual, err := volumetypes.CreateExtraSpecs(context.TODO(), client.ServiceClient(fakeServer), "1", createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, expected, actual) } func TestVolumeTypeExtraSpecUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleExtraSpecUpdateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleExtraSpecUpdateSuccessfully(t, fakeServer) updateOpts := volumetypes.ExtraSpecsOpts{ "capabilities": "gpu-2", } expected := UpdatedExtraSpec - actual, err := volumetypes.UpdateExtraSpec(context.TODO(), client.ServiceClient(), "1", updateOpts).Extract() + actual, err := volumetypes.UpdateExtraSpec(context.TODO(), client.ServiceClient(fakeServer), "1", updateOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, expected, actual) } func TestVolumeTypeExtraSpecDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleExtraSpecDeleteSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleExtraSpecDeleteSuccessfully(t, fakeServer) - res := volumetypes.DeleteExtraSpec(context.TODO(), client.ServiceClient(), "1", "capabilities") + res := volumetypes.DeleteExtraSpec(context.TODO(), client.ServiceClient(fakeServer), "1", "capabilities") th.AssertNoErr(t, res.Err) } func TestVolumeTypeListAccesses(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/types/a5082c24-2a27-43a4-b48e-fcec1240e36b/os-volume-type-access", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/types/a5082c24-2a27-43a4-b48e-fcec1240e36b/os-volume-type-access", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volume_type_access": [ { @@ -209,7 +295,7 @@ func TestVolumeTypeListAccesses(t *testing.T) { }, } - allPages, err := volumetypes.ListAccesses(client.ServiceClient(), "a5082c24-2a27-43a4-b48e-fcec1240e36b").AllPages(context.TODO()) + allPages, err := volumetypes.ListAccesses(client.ServiceClient(fakeServer), "a5082c24-2a27-43a4-b48e-fcec1240e36b").AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := volumetypes.ExtractAccesses(allPages) @@ -221,10 +307,10 @@ func TestVolumeTypeListAccesses(t *testing.T) { } func TestVolumeTypeAddAccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/types/a5082c24-2a27-43a4-b48e-fcec1240e36b/action", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/types/a5082c24-2a27-43a4-b48e-fcec1240e36b/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "accept", "application/json") @@ -244,16 +330,16 @@ func TestVolumeTypeAddAccess(t *testing.T) { Project: "6f70656e737461636b20342065766572", } - err := volumetypes.AddAccess(context.TODO(), client.ServiceClient(), "a5082c24-2a27-43a4-b48e-fcec1240e36b", addAccessOpts).ExtractErr() + err := volumetypes.AddAccess(context.TODO(), client.ServiceClient(fakeServer), "a5082c24-2a27-43a4-b48e-fcec1240e36b", addAccessOpts).ExtractErr() th.AssertNoErr(t, err) } func TestVolumeTypeRemoveAccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/types/a5082c24-2a27-43a4-b48e-fcec1240e36b/action", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/types/a5082c24-2a27-43a4-b48e-fcec1240e36b/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "accept", "application/json") @@ -273,16 +359,16 @@ func TestVolumeTypeRemoveAccess(t *testing.T) { Project: "6f70656e737461636b20342065766572", } - err := volumetypes.RemoveAccess(context.TODO(), client.ServiceClient(), "a5082c24-2a27-43a4-b48e-fcec1240e36b", removeAccessOpts).ExtractErr() + err := volumetypes.RemoveAccess(context.TODO(), client.ServiceClient(fakeServer), "a5082c24-2a27-43a4-b48e-fcec1240e36b", removeAccessOpts).ExtractErr() th.AssertNoErr(t, err) } func TestCreateEncryption(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockEncryptionCreateResponse(t) + MockEncryptionCreateResponse(t, fakeServer) options := &volumetypes.CreateEncryptionOpts{ KeySize: 256, @@ -291,7 +377,7 @@ func TestCreateEncryption(t *testing.T) { Cipher: "aes-xts-plain64", } id := "a5082c24-2a27-43a4-b48e-fcec1240e36b" - n, err := volumetypes.CreateEncryption(context.TODO(), client.ServiceClient(), id, options).Extract() + n, err := volumetypes.CreateEncryption(context.TODO(), client.ServiceClient(fakeServer), id, options).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "a5082c24-2a27-43a4-b48e-fcec1240e36b", n.VolumeTypeID) @@ -303,20 +389,20 @@ func TestCreateEncryption(t *testing.T) { } func TestDeleteEncryption(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDeleteEncryptionResponse(t) + MockDeleteEncryptionResponse(t, fakeServer) - res := volumetypes.DeleteEncryption(context.TODO(), client.ServiceClient(), "a5082c24-2a27-43a4-b48e-fcec1240e36b", "81e069c6-7394-4856-8df7-3b237ca61f74") + res := volumetypes.DeleteEncryption(context.TODO(), client.ServiceClient(fakeServer), "a5082c24-2a27-43a4-b48e-fcec1240e36b", "81e069c6-7394-4856-8df7-3b237ca61f74") th.AssertNoErr(t, res.Err) } func TestUpdateEncryption(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockEncryptionUpdateResponse(t) + MockEncryptionUpdateResponse(t, fakeServer) options := &volumetypes.UpdateEncryptionOpts{ KeySize: 256, @@ -326,7 +412,7 @@ func TestUpdateEncryption(t *testing.T) { } id := "a5082c24-2a27-43a4-b48e-fcec1240e36b" encryptionID := "81e069c6-7394-4856-8df7-3b237ca61f74" - n, err := volumetypes.UpdateEncryption(context.TODO(), client.ServiceClient(), id, encryptionID, options).Extract() + n, err := volumetypes.UpdateEncryption(context.TODO(), client.ServiceClient(fakeServer), id, encryptionID, options).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "front-end", n.ControlLocation) @@ -336,17 +422,17 @@ func TestUpdateEncryption(t *testing.T) { } func TestGetEncryption(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockEncryptionGetResponse(t) + MockEncryptionGetResponse(t, fakeServer) id := "a5082c24-2a27-43a4-b48e-fcec1240e36b" - n, err := volumetypes.GetEncryption(context.TODO(), client.ServiceClient(), id).Extract() + n, err := volumetypes.GetEncryption(context.TODO(), client.ServiceClient(fakeServer), id).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "a5082c24-2a27-43a4-b48e-fcec1240e36b", n.VolumeTypeID) th.AssertEquals(t, "front-end", n.ControlLocation) - th.AssertEquals(t, false, n.Deleted) + th.AssertFalse(t, n.Deleted) th.AssertEquals(t, "2016-12-28T02:32:25.000000", n.CreatedAt) th.AssertEquals(t, "", n.UpdatedAt) th.AssertEquals(t, "81e069c6-7394-4856-8df7-3b237ca61f74", n.EncryptionID) @@ -357,12 +443,12 @@ func TestGetEncryption(t *testing.T) { } func TestGetEncryptionSpec(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockEncryptionGetSpecResponse(t) + MockEncryptionGetSpecResponse(t, fakeServer) id := "a5082c24-2a27-43a4-b48e-fcec1240e36b" - n, err := volumetypes.GetEncryptionSpec(context.TODO(), client.ServiceClient(), id, "cipher").Extract() + n, err := volumetypes.GetEncryptionSpec(context.TODO(), client.ServiceClient(fakeServer), id, "cipher").Extract() th.AssertNoErr(t, err) key := "cipher" diff --git a/openstack/client.go b/openstack/client.go index 43b569d3b4..4de89ab4ea 100644 --- a/openstack/client.go +++ b/openstack/client.go @@ -2,6 +2,7 @@ package openstack import ( "context" + "errors" "fmt" "reflect" "strings" @@ -35,7 +36,7 @@ const ( // // ao, err := openstack.AuthOptionsFromEnv() // provider, err := openstack.NewClient(ao.IdentityEndpoint) -// client, err := openstack.NewIdentityV3(provider, gophercloud.EndpointOpts{}) +// client, err := openstack.NewIdentityV3(ctx, provider, gophercloud.EndpointOpts{}) func NewClient(endpoint string) (*gophercloud.ProviderClient, error) { base, err := utils.BaseEndpoint(endpoint) if err != nil { @@ -68,7 +69,7 @@ func NewClient(endpoint string) (*gophercloud.ProviderClient, error) { // // ao, err := openstack.AuthOptionsFromEnv() // provider, err := openstack.AuthenticatedClient(ctx, ao) -// client, err := openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{ +// client, err := openstack.NewNetworkV2(ctx, provider, gophercloud.EndpointOpts{ // Region: os.Getenv("OS_REGION_NAME"), // }) func AuthenticatedClient(ctx context.Context, options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) { @@ -104,7 +105,7 @@ func Authenticate(ctx context.Context, client *gophercloud.ProviderClient, optio return v3auth(ctx, client, endpoint, &options, gophercloud.EndpointOpts{}) default: // The switch statement must be out of date from the versions list. - return fmt.Errorf("Unrecognized identity version: %s", chosen.ID) + return fmt.Errorf("unrecognized identity version: %s", chosen.ID) } } @@ -120,7 +121,7 @@ type v2TokenNoReauth struct { func (v2TokenNoReauth) CanReauth() bool { return false } func v2auth(ctx context.Context, client *gophercloud.ProviderClient, endpoint string, options tokens2.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error { - v2Client, err := NewIdentityV2(client, eo) + v2Client, err := NewIdentityV2(ctx, client, eo) if err != nil { return err } @@ -161,8 +162,8 @@ func v2auth(ctx context.Context, client *gophercloud.ProviderClient, endpoint st return nil } } - client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) { - return V2EndpointURL(catalog, opts) + client.EndpointLocator = func(ctx context.Context, opts gophercloud.EndpointOpts) (string, error) { + return V2Endpoint(ctx, client, catalog, opts) } return nil @@ -175,7 +176,7 @@ func AuthenticateV3(ctx context.Context, client *gophercloud.ProviderClient, opt func v3auth(ctx context.Context, client *gophercloud.ProviderClient, endpoint string, opts tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error { // Override the generated service endpoint with the one returned by the version endpoint. - v3Client, err := NewIdentityV3(client, eo) + v3Client, err := NewIdentityV3(ctx, client, eo) if err != nil { return err } @@ -205,7 +206,7 @@ func v3auth(ctx context.Context, client *gophercloud.ProviderClient, endpoint st } v3Client.SetToken(tokenID) - result := tokens3.Get(ctx, v3Client, tokenID) + result := tokens3.Get(ctx, v3Client, tokenID, nil) if result.Err != nil { return result.Err } @@ -282,8 +283,8 @@ func v3auth(ctx context.Context, client *gophercloud.ProviderClient, endpoint st return nil } } - client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) { - return V3EndpointURL(catalog, opts) + client.EndpointLocator = func(ctx context.Context, opts gophercloud.EndpointOpts) (string, error) { + return V3Endpoint(ctx, client, catalog, opts) } return nil @@ -291,13 +292,13 @@ func v3auth(ctx context.Context, client *gophercloud.ProviderClient, endpoint st // NewIdentityV2 creates a ServiceClient that may be used to interact with the // v2 identity service. -func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { +func NewIdentityV2(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { endpoint := client.IdentityBase + "v2.0/" clientType := "identity" var err error if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) { eo.ApplyDefaults(clientType) - endpoint, err = client.EndpointLocator(eo) + endpoint, err = client.EndpointLocator(ctx, eo) if err != nil { return nil, err } @@ -312,13 +313,13 @@ func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOp // NewIdentityV3 creates a ServiceClient that may be used to access the v3 // identity service. -func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { +func NewIdentityV3(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { endpoint := client.IdentityBase + "v3/" clientType := "identity" var err error if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) { eo.ApplyDefaults(clientType) - endpoint, err = client.EndpointLocator(eo) + endpoint, err = client.EndpointLocator(ctx, eo) if err != nil { return nil, err } @@ -344,13 +345,21 @@ func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOp }, nil } -func initClientOpts(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts, clientType string) (*gophercloud.ServiceClient, error) { +// TODO(stephenfin): Allow passing aliases to all New${SERVICE}V${VERSION} methods in v3 +func initClientOpts(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts, clientType string, version int) (*gophercloud.ServiceClient, error) { sc := new(gophercloud.ServiceClient) + eo.ApplyDefaults(clientType) - url, err := client.EndpointLocator(eo) + if eo.Version != 0 && eo.Version != version { + return sc, errors.New("conflict between requested service major version and manually set version") + } + eo.Version = version + + url, err := client.EndpointLocator(ctx, eo) if err != nil { return sc, err } + sc.ProviderClient = client sc.Endpoint = url sc.Type = clientType @@ -359,8 +368,8 @@ func initClientOpts(client *gophercloud.ProviderClient, eo gophercloud.EndpointO // NewBareMetalV1 creates a ServiceClient that may be used with the v1 // bare metal package. -func NewBareMetalV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - sc, err := initClientOpts(client, eo, "baremetal") +func NewBareMetalV1(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(ctx, client, eo, "baremetal", 1) if !strings.HasSuffix(strings.TrimSuffix(sc.Endpoint, "/"), "v1") { sc.ResourceBase = sc.Endpoint + "v1/" } @@ -369,124 +378,133 @@ func NewBareMetalV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointO // NewBareMetalIntrospectionV1 creates a ServiceClient that may be used with the v1 // bare metal introspection package. -func NewBareMetalIntrospectionV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "baremetal-introspection") +func NewBareMetalIntrospectionV1(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(ctx, client, eo, "baremetal-introspection", 1) } // NewObjectStorageV1 creates a ServiceClient that may be used with the v1 // object storage package. -func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "object-store") +func NewObjectStorageV1(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(ctx, client, eo, "object-store", 1) } // NewComputeV2 creates a ServiceClient that may be used with the v2 compute // package. -func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "compute") +func NewComputeV2(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(ctx, client, eo, "compute", 2) } // NewNetworkV2 creates a ServiceClient that may be used with the v2 network // package. -func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - sc, err := initClientOpts(client, eo, "network") +func NewNetworkV2(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(ctx, client, eo, "network", 2) sc.ResourceBase = sc.Endpoint + "v2.0/" return sc, err } +// TODO(stephenfin): Remove this in v3. We no longer support the V1 Block Storage service. // NewBlockStorageV1 creates a ServiceClient that may be used to access the v1 // block storage service. -func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "volume") +func NewBlockStorageV1(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(ctx, client, eo, "volume", 1) } // NewBlockStorageV2 creates a ServiceClient that may be used to access the v2 // block storage service. -func NewBlockStorageV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "volumev2") +func NewBlockStorageV2(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(ctx, client, eo, "block-storage", 2) } // NewBlockStorageV3 creates a ServiceClient that may be used to access the v3 block storage service. -func NewBlockStorageV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "volumev3") +func NewBlockStorageV3(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(ctx, client, eo, "block-storage", 3) } // NewSharedFileSystemV2 creates a ServiceClient that may be used to access the v2 shared file system service. -func NewSharedFileSystemV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "sharev2") +func NewSharedFileSystemV2(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(ctx, client, eo, "shared-file-system", 2) } // NewOrchestrationV1 creates a ServiceClient that may be used to access the v1 // orchestration service. -func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "orchestration") +func NewOrchestrationV1(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(ctx, client, eo, "orchestration", 1) } // NewDBV1 creates a ServiceClient that may be used to access the v1 DB service. -func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "database") +func NewDBV1(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(ctx, client, eo, "database", 1) } // NewDNSV2 creates a ServiceClient that may be used to access the v2 DNS // service. -func NewDNSV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - sc, err := initClientOpts(client, eo, "dns") +func NewDNSV2(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(ctx, client, eo, "dns", 2) sc.ResourceBase = sc.Endpoint + "v2/" return sc, err } // NewImageV2 creates a ServiceClient that may be used to access the v2 image // service. -func NewImageV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - sc, err := initClientOpts(client, eo, "image") +func NewImageV2(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(ctx, client, eo, "image", 2) sc.ResourceBase = sc.Endpoint + "v2/" return sc, err } // NewLoadBalancerV2 creates a ServiceClient that may be used to access the v2 // load balancer service. -func NewLoadBalancerV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - sc, err := initClientOpts(client, eo, "load-balancer") +func NewLoadBalancerV2(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(ctx, client, eo, "load-balancer", 2) // Fixes edge case having an OpenStack lb endpoint with trailing version number. - endpoint := strings.Replace(sc.Endpoint, "v2.0/", "", -1) + endpoint := strings.ReplaceAll(sc.Endpoint, "v2.0/", "") sc.ResourceBase = endpoint + "v2.0/" return sc, err } +// NewMetricV1 creates a ServiceClient that may be used with the v1 metric-storage +// service (Aetos). +func NewMetricV1(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(ctx, client, eo, "metric-storage", 1) + sc.ResourceBase = sc.Endpoint + "api/v1/" + return sc, err +} + // NewMessagingV2 creates a ServiceClient that may be used with the v2 messaging // service. -func NewMessagingV2(client *gophercloud.ProviderClient, clientID string, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - sc, err := initClientOpts(client, eo, "messaging") +func NewMessagingV2(ctx context.Context, client *gophercloud.ProviderClient, clientID string, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(ctx, client, eo, "message", 2) sc.MoreHeaders = map[string]string{"Client-ID": clientID} return sc, err } // NewContainerV1 creates a ServiceClient that may be used with v1 container package -func NewContainerV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "container") +func NewContainerV1(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(ctx, client, eo, "application-container", 1) } // NewKeyManagerV1 creates a ServiceClient that may be used with the v1 key // manager service. -func NewKeyManagerV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - sc, err := initClientOpts(client, eo, "key-manager") +func NewKeyManagerV1(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(ctx, client, eo, "key-manager", 1) sc.ResourceBase = sc.Endpoint + "v1/" return sc, err } // NewContainerInfraV1 creates a ServiceClient that may be used with the v1 container infra management // package. -func NewContainerInfraV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "container-infra") +func NewContainerInfraV1(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(ctx, client, eo, "container-infrastructure-management", 1) } // NewWorkflowV2 creates a ServiceClient that may be used with the v2 workflow management package. -func NewWorkflowV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "workflowv2") +func NewWorkflowV2(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(ctx, client, eo, "workflow", 2) } // NewPlacementV1 creates a ServiceClient that may be used with the placement package. -func NewPlacementV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "placement") +func NewPlacementV1(ctx context.Context, client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(ctx, client, eo, "placement", 1) } diff --git a/openstack/common/extensions/doc.go b/openstack/common/extensions/doc.go index e07dd0f8f0..ca402ee47a 100644 --- a/openstack/common/extensions/doc.go +++ b/openstack/common/extensions/doc.go @@ -22,7 +22,7 @@ Example of Retrieving Compute Extensions ao, err := openstack.AuthOptionsFromEnv() provider, err := openstack.AuthenticatedClient(context.TODO(), ao) - computeClient, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{ + computeClient, err := openstack.NewComputeV2(context.TODO(), provider, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) @@ -37,7 +37,7 @@ Example of Retrieving Network Extensions ao, err := openstack.AuthOptionsFromEnv() provider, err := openstack.AuthenticatedClient(context.TODO(), ao) - networkClient, err := openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{ + networkClient, err := openstack.NewNetworkV2(context.TODO(), provider, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) diff --git a/openstack/common/extensions/testing/fixtures.go b/openstack/common/extensions/testing/fixtures.go index cfcdbe90e9..092763818c 100644 --- a/openstack/common/extensions/testing/fixtures.go +++ b/openstack/common/extensions/testing/fixtures.go @@ -64,27 +64,27 @@ var SingleExtension = &extensions.Extension{ // HandleListExtensionsSuccessfully creates an HTTP handler at `/extensions` on the test handler // mux that response with a list containing a single tenant. -func HandleListExtensionsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/extensions", func(w http.ResponseWriter, r *http.Request) { +func HandleListExtensionsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/extensions", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } // HandleGetExtensionSuccessfully creates an HTTP handler at `/extensions/agent` that responds with // a JSON payload corresponding to SingleExtension. -func HandleGetExtensionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/extensions/agent", func(w http.ResponseWriter, r *http.Request) { +func HandleGetExtensionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/extensions/agent", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } diff --git a/openstack/common/extensions/testing/requests_test.go b/openstack/common/extensions/testing/requests_test.go index 37a7a5092d..5713bf2d9a 100644 --- a/openstack/common/extensions/testing/requests_test.go +++ b/openstack/common/extensions/testing/requests_test.go @@ -11,13 +11,13 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListExtensionsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListExtensionsSuccessfully(t, fakeServer) count := 0 - err := extensions.List(client.ServiceClient()).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := extensions.List(client.ServiceClient(fakeServer)).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := extensions.ExtractExtensions(page) th.AssertNoErr(t, err) @@ -31,11 +31,11 @@ func TestList(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetExtensionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetExtensionSuccessfully(t, fakeServer) - actual, err := extensions.Get(context.TODO(), client.ServiceClient(), "agent").Extract() + actual, err := extensions.Get(context.TODO(), client.ServiceClient(fakeServer), "agent").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SingleExtension, actual) } diff --git a/openstack/compute/apiversions/testing/fixtures_test.go b/openstack/compute/apiversions/testing/fixtures_test.go index c7f4b1a977..ac122e2d2c 100644 --- a/openstack/compute/apiversions/testing/fixtures_test.go +++ b/openstack/compute/apiversions/testing/fixtures_test.go @@ -161,38 +161,38 @@ var NovaAllAPIVersionResults = []apiversions.APIVersion{ NovaAPIVersion21Result, } -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { +func MockListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, NovaAllAPIVersionsResponse) + fmt.Fprint(w, NovaAllAPIVersionsResponse) }) } -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc("/v2.1/", func(w http.ResponseWriter, r *http.Request) { +func MockGetResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.1/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, NovaAPIVersionResponse_21) + fmt.Fprint(w, NovaAPIVersionResponse_21) }) } -func MockGetMultipleResponses(t *testing.T) { - th.Mux.HandleFunc("/v3/", func(w http.ResponseWriter, r *http.Request) { +func MockGetMultipleResponses(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v3/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, NovaAPIInvalidVersionResponse) + fmt.Fprint(w, NovaAPIInvalidVersionResponse) }) } diff --git a/openstack/compute/apiversions/testing/requests_test.go b/openstack/compute/apiversions/testing/requests_test.go index 737976b750..9b58a2602b 100644 --- a/openstack/compute/apiversions/testing/requests_test.go +++ b/openstack/compute/apiversions/testing/requests_test.go @@ -10,12 +10,12 @@ import ( ) func TestListAPIVersions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) - allVersions, err := apiversions.List(client.ServiceClient()).AllPages(context.TODO()) + allVersions, err := apiversions.List(client.ServiceClient(fakeServer)).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := apiversions.ExtractAPIVersions(allVersions) @@ -25,23 +25,23 @@ func TestListAPIVersions(t *testing.T) { } func TestGetAPIVersion(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetResponse(t) + MockGetResponse(t, fakeServer) - actual, err := apiversions.Get(context.TODO(), client.ServiceClient(), "v2.1").Extract() + actual, err := apiversions.Get(context.TODO(), client.ServiceClient(fakeServer), "v2.1").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, NovaAPIVersion21Result, *actual) } func TestGetMultipleAPIVersion(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetMultipleResponses(t) + MockGetMultipleResponses(t, fakeServer) - _, err := apiversions.Get(context.TODO(), client.ServiceClient(), "v3").Extract() - th.AssertEquals(t, err.Error(), "Unable to find requested API version") + _, err := apiversions.Get(context.TODO(), client.ServiceClient(fakeServer), "v3").Extract() + th.AssertEquals(t, "Unable to find requested API version", err.Error()) } diff --git a/openstack/compute/v2/aggregates/requests.go b/openstack/compute/v2/aggregates/requests.go index 7111c203b0..bdf9ccb3d9 100644 --- a/openstack/compute/v2/aggregates/requests.go +++ b/openstack/compute/v2/aggregates/requests.go @@ -15,6 +15,12 @@ func List(client *gophercloud.ServiceClient) pagination.Pager { }) } +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToAggregatesCreateMap() (map[string]any, error) +} + type CreateOpts struct { // The name of the host aggregate. Name string `json:"name" required:"true"` @@ -31,7 +37,7 @@ func (opts CreateOpts) ToAggregatesCreateMap() (map[string]any, error) { } // Create makes a request against the API to create an aggregate. -func Create(ctx context.Context, client *gophercloud.ServiceClient, opts CreateOpts) (r CreateResult) { +func Create(ctx context.Context, client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { b, err := opts.ToAggregatesCreateMap() if err != nil { r.Err = err @@ -64,15 +70,21 @@ func Get(ctx context.Context, client *gophercloud.ServiceClient, aggregateID int return } +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToAggregatesUpdateMap() (map[string]any, error) +} + type UpdateOpts struct { // The name of the host aggregate. - Name string `json:"name,omitempty"` + Name *string `json:"name,omitempty"` // The availability zone of the host aggregate. // You should use a custom availability zone rather than // the default returned by the os-availability-zone API. // The availability zone must not include ‘:’ in its name. - AvailabilityZone string `json:"availability_zone,omitempty"` + AvailabilityZone *string `json:"availability_zone,omitempty"` } func (opts UpdateOpts) ToAggregatesUpdateMap() (map[string]any, error) { @@ -80,7 +92,7 @@ func (opts UpdateOpts) ToAggregatesUpdateMap() (map[string]any, error) { } // Update makes a request against the API to update a specific aggregate. -func Update(ctx context.Context, client *gophercloud.ServiceClient, aggregateID int, opts UpdateOpts) (r UpdateResult) { +func Update(ctx context.Context, client *gophercloud.ServiceClient, aggregateID int, opts UpdateOptsBuilder) (r UpdateResult) { v := strconv.Itoa(aggregateID) b, err := opts.ToAggregatesUpdateMap() diff --git a/openstack/compute/v2/aggregates/results.go b/openstack/compute/v2/aggregates/results.go index 0d4794acff..8e67edd87c 100644 --- a/openstack/compute/v2/aggregates/results.go +++ b/openstack/compute/v2/aggregates/results.go @@ -39,6 +39,10 @@ type Aggregate struct { // A boolean indicates whether this aggregate is deleted or not, // if it has not been deleted, false will appear. Deleted bool `json:"deleted"` + + // The UUID of the aggregate. + // The requires microversion 2.41 or later. + UUID string `json:"uuid"` } // UnmarshalJSON to override default diff --git a/openstack/compute/v2/aggregates/testing/fixtures_test.go b/openstack/compute/v2/aggregates/testing/fixtures_test.go index a67678404f..751ca766e7 100644 --- a/openstack/compute/v2/aggregates/testing/fixtures_test.go +++ b/openstack/compute/v2/aggregates/testing/fixtures_test.go @@ -258,29 +258,29 @@ var ( ) // HandleListSuccessfully configures the test server to respond to a List request. -func HandleListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-aggregates", func(w http.ResponseWriter, r *http.Request) { +func HandleListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-aggregates", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, AggregateListBody) + fmt.Fprint(w, AggregateListBody) }) } -func HandleCreateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-aggregates", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-aggregates", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, AggregateCreateBody) + fmt.Fprint(w, AggregateCreateBody) }) } -func HandleDeleteSuccessfully(t *testing.T) { +func HandleDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { v := strconv.Itoa(AggregateIDtoDelete) - th.Mux.HandleFunc("/os-aggregates/"+v, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/os-aggregates/"+v, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -288,57 +288,57 @@ func HandleDeleteSuccessfully(t *testing.T) { }) } -func HandleGetSuccessfully(t *testing.T) { +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { v := strconv.Itoa(AggregateIDtoGet) - th.Mux.HandleFunc("/os-aggregates/"+v, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/os-aggregates/"+v, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, AggregateGetBody) + fmt.Fprint(w, AggregateGetBody) }) } -func HandleUpdateSuccessfully(t *testing.T) { +func HandleUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { v := strconv.Itoa(AggregateIDtoUpdate) - th.Mux.HandleFunc("/os-aggregates/"+v, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/os-aggregates/"+v, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, AggregateUpdateBody) + fmt.Fprint(w, AggregateUpdateBody) }) } -func HandleAddHostSuccessfully(t *testing.T) { +func HandleAddHostSuccessfully(t *testing.T, fakeServer th.FakeServer) { v := strconv.Itoa(AggregateWithAddedHost.ID) - th.Mux.HandleFunc("/os-aggregates/"+v+"/action", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/os-aggregates/"+v+"/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, AggregateAddHostBody) + fmt.Fprint(w, AggregateAddHostBody) }) } -func HandleRemoveHostSuccessfully(t *testing.T) { +func HandleRemoveHostSuccessfully(t *testing.T, fakeServer th.FakeServer) { v := strconv.Itoa(AggregateWithRemovedHost.ID) - th.Mux.HandleFunc("/os-aggregates/"+v+"/action", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/os-aggregates/"+v+"/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, AggregateRemoveHostBody) + fmt.Fprint(w, AggregateRemoveHostBody) }) } -func HandleSetMetadataSuccessfully(t *testing.T) { +func HandleSetMetadataSuccessfully(t *testing.T, fakeServer th.FakeServer) { v := strconv.Itoa(AggregateWithUpdatedMetadata.ID) - th.Mux.HandleFunc("/os-aggregates/"+v+"/action", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/os-aggregates/"+v+"/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, AggregateSetMetadataBody) + fmt.Fprint(w, AggregateSetMetadataBody) }) } diff --git a/openstack/compute/v2/aggregates/testing/requests_test.go b/openstack/compute/v2/aggregates/testing/requests_test.go index 2e229bf71c..b6a8912a76 100644 --- a/openstack/compute/v2/aggregates/testing/requests_test.go +++ b/openstack/compute/v2/aggregates/testing/requests_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/gophercloud/gophercloud/v2/internal/ptr" "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/aggregates" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" @@ -11,12 +12,12 @@ import ( ) func TestListAggregates(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer) pages := 0 - err := aggregates.List(client.ServiceClient()).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := aggregates.List(client.ServiceClient(fakeServer)).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := aggregates.ExtractAggregates(page) @@ -41,9 +42,9 @@ func TestListAggregates(t *testing.T) { } func TestCreateAggregates(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateSuccessfully(t, fakeServer) expected := CreatedAggregate @@ -52,56 +53,55 @@ func TestCreateAggregates(t *testing.T) { AvailabilityZone: "london", } - actual, err := aggregates.Create(context.TODO(), client.ServiceClient(), opts).Extract() + actual, err := aggregates.Create(context.TODO(), client.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, &expected, actual) } func TestDeleteAggregates(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteSuccessfully(t, fakeServer) - err := aggregates.Delete(context.TODO(), client.ServiceClient(), AggregateIDtoDelete).ExtractErr() + err := aggregates.Delete(context.TODO(), client.ServiceClient(fakeServer), AggregateIDtoDelete).ExtractErr() th.AssertNoErr(t, err) } func TestGetAggregates(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSuccessfully(t, fakeServer) expected := SecondFakeAggregate - actual, err := aggregates.Get(context.TODO(), client.ServiceClient(), AggregateIDtoGet).Extract() + actual, err := aggregates.Get(context.TODO(), client.ServiceClient(fakeServer), AggregateIDtoGet).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, &expected, actual) } func TestUpdateAggregate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateSuccessfully(t, fakeServer) expected := UpdatedAggregate - opts := aggregates.UpdateOpts{ - Name: "test-aggregates2", - AvailabilityZone: "nova2", + Name: ptr.To("test-aggregates2"), + AvailabilityZone: ptr.To("nova2"), } - actual, err := aggregates.Update(context.TODO(), client.ServiceClient(), expected.ID, opts).Extract() + actual, err := aggregates.Update(context.TODO(), client.ServiceClient(fakeServer), expected.ID, opts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, &expected, actual) } func TestAddHostAggregate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAddHostSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAddHostSuccessfully(t, fakeServer) expected := AggregateWithAddedHost @@ -109,16 +109,16 @@ func TestAddHostAggregate(t *testing.T) { Host: "cmp1", } - actual, err := aggregates.AddHost(context.TODO(), client.ServiceClient(), expected.ID, opts).Extract() + actual, err := aggregates.AddHost(context.TODO(), client.ServiceClient(fakeServer), expected.ID, opts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, &expected, actual) } func TestRemoveHostAggregate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleRemoveHostSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleRemoveHostSuccessfully(t, fakeServer) expected := AggregateWithRemovedHost @@ -126,16 +126,16 @@ func TestRemoveHostAggregate(t *testing.T) { Host: "cmp1", } - actual, err := aggregates.RemoveHost(context.TODO(), client.ServiceClient(), expected.ID, opts).Extract() + actual, err := aggregates.RemoveHost(context.TODO(), client.ServiceClient(fakeServer), expected.ID, opts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, &expected, actual) } func TestSetMetadataAggregate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleSetMetadataSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleSetMetadataSuccessfully(t, fakeServer) expected := AggregateWithUpdatedMetadata @@ -143,7 +143,7 @@ func TestSetMetadataAggregate(t *testing.T) { Metadata: map[string]any{"key": "value"}, } - actual, err := aggregates.SetMetadata(context.TODO(), client.ServiceClient(), expected.ID, opts).Extract() + actual, err := aggregates.SetMetadata(context.TODO(), client.ServiceClient(fakeServer), expected.ID, opts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, &expected, actual) diff --git a/openstack/compute/v2/attachinterfaces/testing/fixtures_test.go b/openstack/compute/v2/attachinterfaces/testing/fixtures_test.go index 77bc48f55e..16effc6514 100644 --- a/openstack/compute/v2/attachinterfaces/testing/fixtures_test.go +++ b/openstack/compute/v2/attachinterfaces/testing/fixtures_test.go @@ -63,13 +63,13 @@ var CreateInterfacesExpected = attachinterfaces.Interface{ } // HandleInterfaceListSuccessfully sets up the test server to respond to a ListInterfaces request. -func HandleInterfaceListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/b07e7a3b-d951-4efc-a4f9-ac9f001afb7f/os-interface", func(w http.ResponseWriter, r *http.Request) { +func HandleInterfaceListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/b07e7a3b-d951-4efc-a4f9-ac9f001afb7f/os-interface", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "interfaceAttachments": [ { "port_state":"ACTIVE", @@ -93,13 +93,13 @@ func HandleInterfaceListSuccessfully(t *testing.T) { } // HandleInterfaceGetSuccessfully sets up the test server to respond to a GetInterface request. -func HandleInterfaceGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/b07e7a3b-d951-4efc-a4f9-ac9f001afb7f/os-interface/0dde1598-b374-474e-986f-5b8dd1df1d4e", func(w http.ResponseWriter, r *http.Request) { +func HandleInterfaceGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/b07e7a3b-d951-4efc-a4f9-ac9f001afb7f/os-interface/0dde1598-b374-474e-986f-5b8dd1df1d4e", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "interfaceAttachment": { "port_state":"ACTIVE", @@ -122,8 +122,8 @@ func HandleInterfaceGetSuccessfully(t *testing.T) { } // HandleInterfaceCreateSuccessfully sets up the test server to respond to a CreateInterface request. -func HandleInterfaceCreateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/b07e7a3b-d951-4efc-a4f9-ac9f001afb7f/os-interface", func(w http.ResponseWriter, r *http.Request) { +func HandleInterfaceCreateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/b07e7a3b-d951-4efc-a4f9-ac9f001afb7f/os-interface", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -133,7 +133,7 @@ func HandleInterfaceCreateSuccessfully(t *testing.T) { }`) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "interfaceAttachment": { "port_state":"ACTIVE", @@ -152,8 +152,8 @@ func HandleInterfaceCreateSuccessfully(t *testing.T) { } // HandleInterfaceDeleteSuccessfully sets up the test server to respond to a DeleteInterface request. -func HandleInterfaceDeleteSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/b07e7a3b-d951-4efc-a4f9-ac9f001afb7f/os-interface/0dde1598-b374-474e-986f-5b8dd1df1d4e", func(w http.ResponseWriter, r *http.Request) { +func HandleInterfaceDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/b07e7a3b-d951-4efc-a4f9-ac9f001afb7f/os-interface/0dde1598-b374-474e-986f-5b8dd1df1d4e", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) diff --git a/openstack/compute/v2/attachinterfaces/testing/requests_test.go b/openstack/compute/v2/attachinterfaces/testing/requests_test.go index d436bc081d..a8fe239467 100644 --- a/openstack/compute/v2/attachinterfaces/testing/requests_test.go +++ b/openstack/compute/v2/attachinterfaces/testing/requests_test.go @@ -11,13 +11,13 @@ import ( ) func TestListInterface(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleInterfaceListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleInterfaceListSuccessfully(t, fakeServer) expected := ListInterfacesExpected pages := 0 - err := attachinterfaces.List(client.ServiceClient(), "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f").EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := attachinterfaces.List(client.ServiceClient(fakeServer), "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f").EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := attachinterfaces.ExtractInterfaces(page) @@ -35,42 +35,42 @@ func TestListInterface(t *testing.T) { } func TestListInterfacesAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleInterfaceListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleInterfaceListSuccessfully(t, fakeServer) - allPages, err := attachinterfaces.List(client.ServiceClient(), "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f").AllPages(context.TODO()) + allPages, err := attachinterfaces.List(client.ServiceClient(fakeServer), "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f").AllPages(context.TODO()) th.AssertNoErr(t, err) _, err = attachinterfaces.ExtractInterfaces(allPages) th.AssertNoErr(t, err) } func TestGetInterface(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleInterfaceGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleInterfaceGetSuccessfully(t, fakeServer) expected := GetInterfaceExpected serverID := "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f" interfaceID := "0dde1598-b374-474e-986f-5b8dd1df1d4e" - actual, err := attachinterfaces.Get(context.TODO(), client.ServiceClient(), serverID, interfaceID).Extract() + actual, err := attachinterfaces.Get(context.TODO(), client.ServiceClient(fakeServer), serverID, interfaceID).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &expected, actual) } func TestCreateInterface(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleInterfaceCreateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleInterfaceCreateSuccessfully(t, fakeServer) expected := CreateInterfacesExpected serverID := "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f" networkID := "8a5fe506-7e9f-4091-899b-96336909d93c" - actual, err := attachinterfaces.Create(context.TODO(), client.ServiceClient(), serverID, attachinterfaces.CreateOpts{ + actual, err := attachinterfaces.Create(context.TODO(), client.ServiceClient(fakeServer), serverID, attachinterfaces.CreateOpts{ NetworkID: networkID, }).Extract() th.AssertNoErr(t, err) @@ -78,13 +78,13 @@ func TestCreateInterface(t *testing.T) { } func TestDeleteInterface(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleInterfaceDeleteSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleInterfaceDeleteSuccessfully(t, fakeServer) serverID := "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f" portID := "0dde1598-b374-474e-986f-5b8dd1df1d4e" - err := attachinterfaces.Delete(context.TODO(), client.ServiceClient(), serverID, portID).ExtractErr() + err := attachinterfaces.Delete(context.TODO(), client.ServiceClient(fakeServer), serverID, portID).ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/compute/v2/availabilityzones/testing/fixtures_test.go b/openstack/compute/v2/availabilityzones/testing/fixtures_test.go index 059364bcc0..53899fbf79 100644 --- a/openstack/compute/v2/availabilityzones/testing/fixtures_test.go +++ b/openstack/compute/v2/availabilityzones/testing/fixtures_test.go @@ -174,24 +174,24 @@ var AZDetailResult = []az.AvailabilityZone{ // HandleGetSuccessfully configures the test server to respond to a Get request // for availability zone information. -func HandleGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-availability-zone", func(w http.ResponseWriter, r *http.Request) { +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-availability-zone", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleGetDetailSuccessfully configures the test server to respond to a Get request // for detailed availability zone information. -func HandleGetDetailSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-availability-zone/detail", func(w http.ResponseWriter, r *http.Request) { +func HandleGetDetailSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-availability-zone/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetDetailOutput) + fmt.Fprint(w, GetDetailOutput) }) } diff --git a/openstack/compute/v2/availabilityzones/testing/requests_test.go b/openstack/compute/v2/availabilityzones/testing/requests_test.go index f537d9d18b..fb6a20ee25 100644 --- a/openstack/compute/v2/availabilityzones/testing/requests_test.go +++ b/openstack/compute/v2/availabilityzones/testing/requests_test.go @@ -11,12 +11,12 @@ import ( // Verifies that availability zones can be listed correctly func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleGetSuccessfully(t) + HandleGetSuccessfully(t, fakeServer) - allPages, err := az.List(client.ServiceClient()).AllPages(context.TODO()) + allPages, err := az.List(client.ServiceClient(fakeServer)).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := az.ExtractAvailabilityZones(allPages) @@ -27,12 +27,12 @@ func TestList(t *testing.T) { // Verifies that detailed availability zones can be listed correctly func TestListDetail(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleGetDetailSuccessfully(t) + HandleGetDetailSuccessfully(t, fakeServer) - allPages, err := az.ListDetail(client.ServiceClient()).AllPages(context.TODO()) + allPages, err := az.ListDetail(client.ServiceClient(fakeServer)).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := az.ExtractAvailabilityZones(allPages) diff --git a/openstack/compute/v2/diagnostics/testing/fixtures_test.go b/openstack/compute/v2/diagnostics/testing/fixtures_test.go index 35cfd134bb..41e5dcf0f0 100644 --- a/openstack/compute/v2/diagnostics/testing/fixtures_test.go +++ b/openstack/compute/v2/diagnostics/testing/fixtures_test.go @@ -9,8 +9,8 @@ import ( ) // HandleDiagnosticGetSuccessfully sets up the test server to respond to a diagnostic Get request. -func HandleDiagnosticGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/1234asdf/diagnostics", func(w http.ResponseWriter, r *http.Request) { +func HandleDiagnosticGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/1234asdf/diagnostics", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") diff --git a/openstack/compute/v2/diagnostics/testing/requests_test.go b/openstack/compute/v2/diagnostics/testing/requests_test.go index 983c4d18e9..6f3feceea4 100644 --- a/openstack/compute/v2/diagnostics/testing/requests_test.go +++ b/openstack/compute/v2/diagnostics/testing/requests_test.go @@ -10,14 +10,14 @@ import ( ) func TestGetDiagnostics(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleDiagnosticGetSuccessfully(t) + HandleDiagnosticGetSuccessfully(t, fakeServer) expected := map[string]any{"cpu0_time": float64(173), "memory": float64(524288)} - res, err := diagnostics.Get(context.TODO(), client.ServiceClient(), "1234asdf").Extract() + res, err := diagnostics.Get(context.TODO(), client.ServiceClient(fakeServer), "1234asdf").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, expected, res) diff --git a/openstack/compute/v2/extensions/testing/delegate_test.go b/openstack/compute/v2/extensions/testing/delegate_test.go index a2b8774c8e..8815b7427e 100644 --- a/openstack/compute/v2/extensions/testing/delegate_test.go +++ b/openstack/compute/v2/extensions/testing/delegate_test.go @@ -12,13 +12,13 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleListExtensionsSuccessfully(t) + HandleListExtensionsSuccessfully(t, fakeServer) count := 0 - err := extensions.List(client.ServiceClient()).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := extensions.List(client.ServiceClient(fakeServer)).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := extensions.ExtractExtensions(page) th.AssertNoErr(t, err) @@ -42,17 +42,17 @@ func TestList(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleGetExtensionsSuccessfully(t) + HandleGetExtensionsSuccessfully(t, fakeServer) - ext, err := extensions.Get(context.TODO(), client.ServiceClient(), "agent").Extract() + ext, err := extensions.Get(context.TODO(), client.ServiceClient(fakeServer), "agent").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, ext.Updated, "2013-02-03T10:00:00-00:00") - th.AssertEquals(t, ext.Name, "agent") - th.AssertEquals(t, ext.Namespace, "http://docs.openstack.org/ext/agent/api/v2.0") - th.AssertEquals(t, ext.Alias, "agent") - th.AssertEquals(t, ext.Description, "The agent management extension.") + th.AssertEquals(t, "2013-02-03T10:00:00-00:00", ext.Updated) + th.AssertEquals(t, "agent", ext.Name) + th.AssertEquals(t, "http://docs.openstack.org/ext/agent/api/v2.0", ext.Namespace) + th.AssertEquals(t, "agent", ext.Alias) + th.AssertEquals(t, "The agent management extension.", ext.Description) } diff --git a/openstack/compute/v2/extensions/testing/fixtures_test.go b/openstack/compute/v2/extensions/testing/fixtures_test.go index 6827dab20c..c330c9c869 100644 --- a/openstack/compute/v2/extensions/testing/fixtures_test.go +++ b/openstack/compute/v2/extensions/testing/fixtures_test.go @@ -9,14 +9,14 @@ import ( "github.com/gophercloud/gophercloud/v2/testhelper/client" ) -func HandleListExtensionsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/extensions", func(w http.ResponseWriter, r *http.Request) { +func HandleListExtensionsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/extensions", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "extensions": [ { @@ -33,15 +33,15 @@ func HandleListExtensionsSuccessfully(t *testing.T) { }) } -func HandleGetExtensionsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/extensions/agent", func(w http.ResponseWriter, r *http.Request) { +func HandleGetExtensionsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/extensions/agent", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "extension": { "updated": "2013-02-03T10:00:00-00:00", diff --git a/openstack/compute/v2/flavors/requests.go b/openstack/compute/v2/flavors/requests.go index 119f5e78e8..fe27fe97da 100644 --- a/openstack/compute/v2/flavors/requests.go +++ b/openstack/compute/v2/flavors/requests.go @@ -165,7 +165,7 @@ type UpdateOpts struct { // Description is a free form description of the flavor. Limited to // 65535 characters in length. Only printable characters are allowed. // New in version 2.55 - Description string `json:"description,omitempty"` + Description *string `json:"description,omitempty"` } // ToFlavorUpdateMap constructs a request body from UpdateOpts. diff --git a/openstack/compute/v2/flavors/results.go b/openstack/compute/v2/flavors/results.go index 387268af85..7d23f40909 100644 --- a/openstack/compute/v2/flavors/results.go +++ b/openstack/compute/v2/flavors/results.go @@ -137,7 +137,7 @@ func (page FlavorPage) IsEmpty() (bool, error) { // NextPageURL uses the response's embedded link reference to navigate to the // next page of results. -func (page FlavorPage) NextPageURL() (string, error) { +func (page FlavorPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"flavors_links"` } diff --git a/openstack/compute/v2/flavors/testing/fixtures_test.go b/openstack/compute/v2/flavors/testing/fixtures_test.go index 94ac8c8a59..1dfdbf695f 100644 --- a/openstack/compute/v2/flavors/testing/fixtures_test.go +++ b/openstack/compute/v2/flavors/testing/fixtures_test.go @@ -6,7 +6,7 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) // ExtraSpecsGetBody provides a GET result of the extra_specs for a flavor @@ -49,34 +49,34 @@ var UpdatedExtraSpec = map[string]string{ "hw:cpu_policy": "CPU-POLICY-2", } -func HandleExtraSpecsListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/flavors/1/os-extra_specs", func(w http.ResponseWriter, r *http.Request) { +func HandleExtraSpecsListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/flavors/1/os-extra_specs", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ExtraSpecsGetBody) + fmt.Fprint(w, ExtraSpecsGetBody) }) } -func HandleExtraSpecGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/flavors/1/os-extra_specs/hw:cpu_policy", func(w http.ResponseWriter, r *http.Request) { +func HandleExtraSpecGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/flavors/1/os-extra_specs/hw:cpu_policy", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetExtraSpecBody) + fmt.Fprint(w, GetExtraSpecBody) }) } -func HandleExtraSpecsCreateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/flavors/1/os-extra_specs", func(w http.ResponseWriter, r *http.Request) { +func HandleExtraSpecsCreateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/flavors/1/os-extra_specs", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, `{ "extra_specs": { @@ -87,14 +87,14 @@ func HandleExtraSpecsCreateSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ExtraSpecsGetBody) + fmt.Fprint(w, ExtraSpecsGetBody) }) } -func HandleExtraSpecUpdateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/flavors/1/os-extra_specs/hw:cpu_policy", func(w http.ResponseWriter, r *http.Request) { +func HandleExtraSpecUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/flavors/1/os-extra_specs/hw:cpu_policy", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, `{ "hw:cpu_policy": "CPU-POLICY-2" @@ -102,14 +102,14 @@ func HandleExtraSpecUpdateSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdatedExtraSpecBody) + fmt.Fprint(w, UpdatedExtraSpecBody) }) } -func HandleExtraSpecDeleteSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/flavors/1/os-extra_specs/hw:cpu_policy", func(w http.ResponseWriter, r *http.Request) { +func HandleExtraSpecDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/flavors/1/os-extra_specs/hw:cpu_policy", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) }) diff --git a/openstack/compute/v2/flavors/testing/requests_test.go b/openstack/compute/v2/flavors/testing/requests_test.go index d22e3dfa9c..8940fec659 100644 --- a/openstack/compute/v2/flavors/testing/requests_test.go +++ b/openstack/compute/v2/flavors/testing/requests_test.go @@ -7,19 +7,20 @@ import ( "reflect" "testing" + "github.com/gophercloud/gophercloud/v2/internal/ptr" "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/flavors" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestListFlavors(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/flavors/detail", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/flavors/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") if err := r.ParseForm(); err != nil { @@ -74,9 +75,9 @@ func TestListFlavors(t *testing.T) { } ] } - `, th.Server.URL) + `, fakeServer.Server.URL) case "2": - fmt.Fprintf(w, `{ "flavors": [] }`) + fmt.Fprint(w, `{ "flavors": [] }`) default: t.Fatalf("Unexpected marker: [%s]", marker) } @@ -84,7 +85,7 @@ func TestListFlavors(t *testing.T) { pages := 0 // Get public and private flavors - err := flavors.ListDetail(fake.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := flavors.ListDetail(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := flavors.ExtractFlavors(page) @@ -113,15 +114,15 @@ func TestListFlavors(t *testing.T) { } func TestGetFlavor(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/flavors/12345", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/flavors/12345", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "flavor": { "id": "1", @@ -140,7 +141,7 @@ func TestGetFlavor(t *testing.T) { `) }) - actual, err := flavors.Get(context.TODO(), fake.ServiceClient(), "12345").Extract() + actual, err := flavors.Get(context.TODO(), client.ServiceClient(fakeServer), "12345").Extract() if err != nil { t.Fatalf("Unable to get flavor: %v", err) } @@ -162,15 +163,15 @@ func TestGetFlavor(t *testing.T) { } func TestCreateFlavor(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/flavors", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/flavors", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "flavor": { "id": "1", @@ -196,7 +197,7 @@ func TestCreateFlavor(t *testing.T) { RxTxFactor: 1.0, Description: "foo", } - actual, err := flavors.Create(context.TODO(), fake.ServiceClient(), opts).Extract() + actual, err := flavors.Create(context.TODO(), client.ServiceClient(fakeServer), opts).Extract() if err != nil { t.Fatalf("Unable to create flavor: %v", err) } @@ -217,15 +218,15 @@ func TestCreateFlavor(t *testing.T) { } func TestUpdateFlavor(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/flavors/12345678", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/flavors/12345678", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "flavor": { "id": "1", @@ -242,9 +243,9 @@ func TestUpdateFlavor(t *testing.T) { }) opts := &flavors.UpdateOpts{ - Description: "foo", + Description: ptr.To("foo"), } - actual, err := flavors.Update(context.TODO(), fake.ServiceClient(), "12345678", opts).Extract() + actual, err := flavors.Update(context.TODO(), client.ServiceClient(fakeServer), "12345678", opts).Extract() if err != nil { t.Fatalf("Unable to update flavor: %v", err) } @@ -266,29 +267,29 @@ func TestUpdateFlavor(t *testing.T) { } func TestDeleteFlavor(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/flavors/12345678", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/flavors/12345678", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) }) - res := flavors.Delete(context.TODO(), fake.ServiceClient(), "12345678") + res := flavors.Delete(context.TODO(), client.ServiceClient(fakeServer), "12345678") th.AssertNoErr(t, res.Err) } func TestFlavorAccessesList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/flavors/12345678/os-flavor-access", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/flavors/12345678/os-flavor-access", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "flavor_access": [ { @@ -307,7 +308,7 @@ func TestFlavorAccessesList(t *testing.T) { }, } - allPages, err := flavors.ListAccesses(fake.ServiceClient(), "12345678").AllPages(context.TODO()) + allPages, err := flavors.ListAccesses(client.ServiceClient(fakeServer), "12345678").AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := flavors.ExtractAccesses(allPages) @@ -319,12 +320,12 @@ func TestFlavorAccessesList(t *testing.T) { } func TestFlavorAccessAdd(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/flavors/12345678/action", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/flavors/12345678/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "accept", "application/json") th.TestJSONRequest(t, r, ` { @@ -336,7 +337,7 @@ func TestFlavorAccessAdd(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "flavor_access": [ { @@ -359,7 +360,7 @@ func TestFlavorAccessAdd(t *testing.T) { Tenant: "2f954bcf047c4ee9b09a37d49ae6db54", } - actual, err := flavors.AddAccess(context.TODO(), fake.ServiceClient(), "12345678", addAccessOpts).Extract() + actual, err := flavors.AddAccess(context.TODO(), client.ServiceClient(fakeServer), "12345678", addAccessOpts).Extract() th.AssertNoErr(t, err) if !reflect.DeepEqual(expected, actual) { @@ -368,12 +369,12 @@ func TestFlavorAccessAdd(t *testing.T) { } func TestFlavorAccessRemove(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/flavors/12345678/action", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/flavors/12345678/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "accept", "application/json") th.TestJSONRequest(t, r, ` { @@ -385,7 +386,7 @@ func TestFlavorAccessRemove(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "flavor_access": [] } @@ -397,7 +398,7 @@ func TestFlavorAccessRemove(t *testing.T) { Tenant: "2f954bcf047c4ee9b09a37d49ae6db54", } - actual, err := flavors.RemoveAccess(context.TODO(), fake.ServiceClient(), "12345678", removeAccessOpts).Extract() + actual, err := flavors.RemoveAccess(context.TODO(), client.ServiceClient(fakeServer), "12345678", removeAccessOpts).Extract() th.AssertNoErr(t, err) if !reflect.DeepEqual(expected, actual) { @@ -406,61 +407,61 @@ func TestFlavorAccessRemove(t *testing.T) { } func TestFlavorExtraSpecsList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleExtraSpecsListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleExtraSpecsListSuccessfully(t, fakeServer) expected := ExtraSpecs - actual, err := flavors.ListExtraSpecs(context.TODO(), fake.ServiceClient(), "1").Extract() + actual, err := flavors.ListExtraSpecs(context.TODO(), client.ServiceClient(fakeServer), "1").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, expected, actual) } func TestFlavorExtraSpecGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleExtraSpecGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleExtraSpecGetSuccessfully(t, fakeServer) expected := ExtraSpec - actual, err := flavors.GetExtraSpec(context.TODO(), fake.ServiceClient(), "1", "hw:cpu_policy").Extract() + actual, err := flavors.GetExtraSpec(context.TODO(), client.ServiceClient(fakeServer), "1", "hw:cpu_policy").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, expected, actual) } func TestFlavorExtraSpecsCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleExtraSpecsCreateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleExtraSpecsCreateSuccessfully(t, fakeServer) createOpts := flavors.ExtraSpecsOpts{ "hw:cpu_policy": "CPU-POLICY", "hw:cpu_thread_policy": "CPU-THREAD-POLICY", } expected := ExtraSpecs - actual, err := flavors.CreateExtraSpecs(context.TODO(), fake.ServiceClient(), "1", createOpts).Extract() + actual, err := flavors.CreateExtraSpecs(context.TODO(), client.ServiceClient(fakeServer), "1", createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, expected, actual) } func TestFlavorExtraSpecUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleExtraSpecUpdateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleExtraSpecUpdateSuccessfully(t, fakeServer) updateOpts := flavors.ExtraSpecsOpts{ "hw:cpu_policy": "CPU-POLICY-2", } expected := UpdatedExtraSpec - actual, err := flavors.UpdateExtraSpec(context.TODO(), fake.ServiceClient(), "1", updateOpts).Extract() + actual, err := flavors.UpdateExtraSpec(context.TODO(), client.ServiceClient(fakeServer), "1", updateOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, expected, actual) } func TestFlavorExtraSpecDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleExtraSpecDeleteSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleExtraSpecDeleteSuccessfully(t, fakeServer) - res := flavors.DeleteExtraSpec(context.TODO(), fake.ServiceClient(), "1", "hw:cpu_policy") + res := flavors.DeleteExtraSpec(context.TODO(), client.ServiceClient(fakeServer), "1", "hw:cpu_policy") th.AssertNoErr(t, res.Err) } diff --git a/openstack/compute/v2/hypervisors/requests.go b/openstack/compute/v2/hypervisors/requests.go index 37ecbee17a..2483d73407 100644 --- a/openstack/compute/v2/hypervisors/requests.go +++ b/openstack/compute/v2/hypervisors/requests.go @@ -53,7 +53,7 @@ func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pa } return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { - return HypervisorPage{pagination.SinglePageBase(r)} + return HypervisorPage{pagination.LinkedPageBase{PageResult: r}} }) } @@ -66,9 +66,42 @@ func GetStatistics(ctx context.Context, client *gophercloud.ServiceClient) (r St return } +// GetOptsBuilder allows extensions to add additional parameters to the +// Get request. +type GetOptsBuilder interface { + ToHypervisorGetQuery() (string, error) +} + +// GetOpts allows the opt-in to add the servers to the response +type GetOpts struct { + // WithServers is a bool to include all servers which belong to the hypervisor + // This requires microversion 2.53 or later + WithServers *bool `q:"with_servers"` +} + +// ToHypervisorGetQuery formats a GetOpts into a query string. +func (opts GetOpts) ToHypervisorGetQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + // Get makes a request against the API to get details for specific hypervisor. func Get(ctx context.Context, client *gophercloud.ServiceClient, hypervisorID string) (r HypervisorResult) { - resp, err := client.Get(ctx, hypervisorsGetURL(client, hypervisorID), &r.Body, &gophercloud.RequestOpts{ + return GetExt(ctx, client, hypervisorID, nil) +} + +// Show makes a request against the API to get details for specific hypervisor with optional query parameters +func GetExt(ctx context.Context, client *gophercloud.ServiceClient, hypervisorID string, opts GetOptsBuilder) (r HypervisorResult) { + url := hypervisorsGetURL(client, hypervisorID) + if opts != nil { + query, err := opts.ToHypervisorGetQuery() + if err != nil { + return HypervisorResult{gophercloud.Result{Err: err}} + } + url += query + } + + resp, err := client.Get(ctx, url, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) diff --git a/openstack/compute/v2/hypervisors/results.go b/openstack/compute/v2/hypervisors/results.go index 2e8d244763..a6a5d86028 100644 --- a/openstack/compute/v2/hypervisors/results.go +++ b/openstack/compute/v2/hypervisors/results.go @@ -11,6 +11,7 @@ import ( // Topology represents a CPU Topology. type Topology struct { + Cells int `json:"cells"` Sockets int `json:"sockets"` Cores int `json:"cores"` Threads int `json:"threads"` @@ -158,27 +159,31 @@ func (r *Hypervisor) UnmarshalJSON(b []byte) error { *r = Hypervisor(s.tmp) - // Newer versions return the CPU info as the correct type. - // Older versions return the CPU info as a string and need to be - // unmarshalled by the json parser. - var tmpb []byte - - switch t := s.CPUInfo.(type) { - case string: - tmpb = []byte(t) - case map[string]any: - tmpb, err = json.Marshal(t) - if err != nil { - return err + // cpu_info doesn't exist after api version 2.87, + // see https://docs.openstack.org/api-ref/compute/#id288 + if s.CPUInfo != nil { + // api versions 2.28 to 2.87 return the CPU info as the correct type. + // api versions < 2.28 return the CPU info as a string and need to be + // unmarshalled by the json parser. + var tmpb []byte + + switch t := s.CPUInfo.(type) { + case string: + tmpb = []byte(t) + case map[string]any: + tmpb, err = json.Marshal(t) + if err != nil { + return err + } + default: + return fmt.Errorf("CPUInfo has unexpected type: %T", t) } - default: - return fmt.Errorf("CPUInfo has unexpected type: %T", t) - } - if len(tmpb) != 0 { - err = json.Unmarshal(tmpb, &r.CPUInfo) - if err != nil { - return err + if len(tmpb) != 0 { + err = json.Unmarshal(tmpb, &r.CPUInfo) + if err != nil { + return err + } } } @@ -190,25 +195,31 @@ func (r *Hypervisor) UnmarshalJSON(b []byte) error { case float64: r.HypervisorVersion = int(t) default: - return fmt.Errorf("Hypervisor version has unexpected type: %T", t) + return fmt.Errorf("HypervisorVersion has unexpected type: %T", t) } - switch t := s.FreeDiskGB.(type) { - case int: - r.FreeDiskGB = t - case float64: - r.FreeDiskGB = int(t) - default: - return fmt.Errorf("Free disk GB has unexpected type: %T", t) + // free_disk_gb doesn't exist after api version 2.87 + if s.FreeDiskGB != nil { + switch t := s.FreeDiskGB.(type) { + case int: + r.FreeDiskGB = t + case float64: + r.FreeDiskGB = int(t) + default: + return fmt.Errorf("FreeDiskGB has unexpected type: %T", t) + } } - switch t := s.LocalGB.(type) { - case int: - r.LocalGB = t - case float64: - r.LocalGB = int(t) - default: - return fmt.Errorf("Local GB has unexpected type: %T", t) + // local_gb doesn't exist after api version 2.87 + if s.LocalGB != nil { + switch t := s.LocalGB.(type) { + case int: + r.LocalGB = t + case float64: + r.LocalGB = int(t) + default: + return fmt.Errorf("LocalGB has unexpected type: %T", t) + } } // OpenStack Compute service returns ID in string representation since @@ -230,7 +241,7 @@ func (r *Hypervisor) UnmarshalJSON(b []byte) error { // HypervisorPage represents a single page of all Hypervisors from a List // request. type HypervisorPage struct { - pagination.SinglePageBase + pagination.LinkedPageBase } // IsEmpty determines whether or not a HypervisorPage is empty. @@ -243,6 +254,19 @@ func (page HypervisorPage) IsEmpty() (bool, error) { return len(va) == 0, err } +// NextPageURL uses the response's embedded link reference to navigate to the +// next page of results. +func (page HypervisorPage) NextPageURL(endpointURL string) (string, error) { + var s struct { + Links []gophercloud.Link `json:"hypervisors_links"` + } + err := page.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + // ExtractHypervisors interprets a page of results as a slice of Hypervisors. func ExtractHypervisors(p pagination.Page) ([]Hypervisor, error) { var h struct { diff --git a/openstack/compute/v2/hypervisors/testing/fixtures_test.go b/openstack/compute/v2/hypervisors/testing/fixtures_test.go index 1942c842e0..166c415106 100644 --- a/openstack/compute/v2/hypervisors/testing/fixtures_test.go +++ b/openstack/compute/v2/hypervisors/testing/fixtures_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/hypervisors" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" "github.com/gophercloud/gophercloud/v2/testhelper/client" ) @@ -85,8 +85,8 @@ const HypervisorListBodyPre253 = ` ] }` -// HypervisorListBody represents a raw hypervisor list result with Pike+ release. -const HypervisorListBody = ` +// HypervisorListBodyPage1 represents page 1 of a raw hypervisor list result with Pike+ release. +const HypervisorListBodyPage1 = ` { "hypervisors": [ { @@ -127,7 +127,20 @@ const HypervisorListBody = ` }, "vcpus": 1, "vcpus_used": 0 - }, + } + ], + "hypervisors_links": [ + { + "href": "%s/os-hypervisors/detail?marker=c48f6247-abe4-4a24-824e-ea39e108874f", + "rel": "next" + } + ] +}` + +// HypervisorListBodyPage2 represents page 2 of a raw hypervisor list result with Pike+ release. +const HypervisorListBodyPage2 = ` +{ + "hypervisors": [ { "cpu_info": "{\"arch\": \"x86_64\", \"model\": \"Nehalem\", \"vendor\": \"Intel\", \"features\": [\"pge\", \"clflush\"], \"topology\": {\"cores\": 1, \"threads\": 1, \"sockets\": 4}}", "current_workload": 0, @@ -157,6 +170,9 @@ const HypervisorListBody = ` ] }` +// HypervisorListBodyEmpty represents an empty raw hypervisor list result, marking the end of pagination. +const HypervisorListBodyEmpty = `{ "hypervisors": [] }` + // HypervisorListWithParametersBody represents a raw hypervisor list result with Pike+ release. const HypervisorListWithParametersBody = ` { @@ -313,6 +329,62 @@ const HypervisorGetBody = ` } ` +// HypervisorGetPost253Body represents a raw hypervisor GET result with Pike+ +// release with optional server list +const HypervisorGetPost253Body = ` +{ + "hypervisor":{ + "cpu_info":{ + "arch":"x86_64", + "model":"Nehalem", + "vendor":"Intel", + "features":[ + "pge", + "clflush" + ], + "topology":{ + "cores":1, + "threads":1, + "sockets":4 + } + }, + "current_workload":0, + "status":"enabled", + "state":"up", + "servers": [ + { + "name": "test_server1", + "uuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" + }, + { + "name": "test_server2", + "uuid": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb" + } + ], + "disk_available_least":0, + "host_ip":"1.1.1.1", + "free_disk_gb":1028, + "free_ram_mb":7680, + "hypervisor_hostname":"fake-mini", + "hypervisor_type":"fake", + "hypervisor_version":2002000, + "id":"c48f6247-abe4-4a24-824e-ea39e108874f", + "local_gb":1028, + "local_gb_used":0, + "memory_mb":8192, + "memory_mb_used":512, + "running_vms":2, + "service":{ + "host":"e6a37ee802d74863ab8b91ade8f12a67", + "id":"9c2566e7-7a54-4777-a1ae-c2662f0c407c", + "disabled_reason":null + }, + "vcpus":1, + "vcpus_used":0 + } +} +` + // HypervisorGetEmptyCPUInfoBody represents a raw hypervisor GET result with // no cpu_info const HypervisorGetEmptyCPUInfoBody = ` @@ -346,6 +418,36 @@ const HypervisorGetEmptyCPUInfoBody = ` } ` +// HypervisorAfterV287ResponseBody represents a raw hypervisor GET result with +// missing cpu_info, free_disk_gb, local_gb as seen after v2.87 +const HypervisorAfterV287ResponseBody = ` +{ + "hypervisor":{ + "current_workload":0, + "status":"enabled", + "state":"up", + "disk_available_least":0, + "host_ip":"1.1.1.1", + "free_ram_mb":7680, + "hypervisor_hostname":"fake-mini", + "hypervisor_type":"fake", + "hypervisor_version":2002000, + "id":"c48f6247-abe4-4a24-824e-ea39e108874f", + "local_gb_used":0, + "memory_mb":8192, + "memory_mb_used":512, + "running_vms":0, + "service":{ + "host":"e6a37ee802d74863ab8b91ade8f12a67", + "id":"9c2566e7-7a54-4777-a1ae-c2662f0c407c", + "disabled_reason":null + }, + "vcpus":1, + "vcpus_used":0 + } +} +` + // HypervisorUptimeBody represents a raw hypervisor uptime request result with // Pike+ release. const HypervisorUptimeBody = ` @@ -441,6 +543,56 @@ var ( VCPUsUsed: 0, } + HypervisorFakeWithServers = hypervisors.Hypervisor{ + CPUInfo: hypervisors.CPUInfo{ + Arch: "x86_64", + Model: "Nehalem", + Vendor: "Intel", + Features: []string{ + "pge", + "clflush", + }, + Topology: hypervisors.Topology{ + Cores: 1, + Threads: 1, + Sockets: 4, + }, + }, + CurrentWorkload: 0, + Status: "enabled", + State: "up", + Servers: &[]hypervisors.Server{ + { + Name: "test_server1", + UUID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + }, + { + Name: "test_server2", + UUID: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", + }, + }, + DiskAvailableLeast: 0, + HostIP: "1.1.1.1", + FreeDiskGB: 1028, + FreeRamMB: 7680, + HypervisorHostname: "fake-mini", + HypervisorType: "fake", + HypervisorVersion: 2002000, + ID: "c48f6247-abe4-4a24-824e-ea39e108874f", + LocalGB: 1028, + LocalGBUsed: 0, + MemoryMB: 8192, + MemoryMBUsed: 512, + RunningVMs: 2, + Service: hypervisors.Service{ + Host: "e6a37ee802d74863ab8b91ade8f12a67", + ID: "9c2566e7-7a54-4777-a1ae-c2662f0c407c", + DisabledReason: "", + }, + VCPUs: 1, + VCPUsUsed: 0, + } + HypervisorFakeWithParameters = hypervisors.Hypervisor{ CPUInfo: hypervisors.CPUInfo{ Arch: "x86_64", @@ -492,6 +644,7 @@ var ( } HypervisorEmptyCPUInfo = hypervisors.Hypervisor{ + CPUInfo: hypervisors.CPUInfo{}, CurrentWorkload: 0, Status: "enabled", State: "up", @@ -517,6 +670,33 @@ var ( VCPUsUsed: 0, } + HypervisorAfterV287Response = hypervisors.Hypervisor{ + CPUInfo: hypervisors.CPUInfo{}, + CurrentWorkload: 0, + Status: "enabled", + State: "up", + DiskAvailableLeast: 0, + HostIP: "1.1.1.1", + FreeDiskGB: 0, + FreeRamMB: 7680, + HypervisorHostname: "fake-mini", + HypervisorType: "fake", + HypervisorVersion: 2002000, + ID: "c48f6247-abe4-4a24-824e-ea39e108874f", + LocalGB: 0, + LocalGBUsed: 0, + MemoryMB: 8192, + MemoryMBUsed: 512, + RunningVMs: 0, + Service: hypervisors.Service{ + Host: "e6a37ee802d74863ab8b91ade8f12a67", + ID: "9c2566e7-7a54-4777-a1ae-c2662f0c407c", + DisabledReason: "", + }, + VCPUs: 1, + VCPUsUsed: 0, + } + HypervisorsStatisticsExpected = hypervisors.Statistics{ Count: 1, CurrentWorkload: 0, @@ -541,75 +721,107 @@ var ( } ) -func HandleHypervisorsStatisticsSuccessfully(t *testing.T) { - testhelper.Mux.HandleFunc("/os-hypervisors/statistics", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "GET") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandleHypervisorsStatisticsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-hypervisors/statistics", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, HypervisorsStatisticsBody) + fmt.Fprint(w, HypervisorsStatisticsBody) }) } -func HandleHypervisorListPre253Successfully(t *testing.T) { - testhelper.Mux.HandleFunc("/os-hypervisors/detail", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "GET") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandleHypervisorListPre253Successfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-hypervisors/detail", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, HypervisorListBodyPre253) + fmt.Fprint(w, HypervisorListBodyPre253) }) } -func HandleHypervisorListSuccessfully(t *testing.T) { - testhelper.Mux.HandleFunc("/os-hypervisors/detail", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "GET") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandleHypervisorListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-hypervisors/detail", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) - w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, HypervisorListBody) + switch r.URL.Query().Get("marker") { + case "": + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, HypervisorListBodyPage1, fakeServer.Server.URL) + case "c48f6247-abe4-4a24-824e-ea39e108874f": + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, HypervisorListBodyPage2) + default: + http.Error(w, "unexpected marker value", http.StatusInternalServerError) + } }) } -func HandleHypervisorListWithParametersSuccessfully(t *testing.T) { - testhelper.Mux.HandleFunc("/os-hypervisors/detail", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "GET") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) - testhelper.TestFormValues(t, r, map[string]string{ +func HandleHypervisorListWithParametersSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-hypervisors/detail", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestFormValues(t, r, map[string]string{ "with_servers": "true", }) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, HypervisorListWithParametersBody) + fmt.Fprint(w, HypervisorListWithParametersBody) + }) +} + +func HandleHypervisorGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-hypervisors/"+HypervisorFake.ID, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, HypervisorGetBody) + }) +} + +func HandleHypervisorGetPost253Successfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-hypervisors/"+HypervisorFake.ID, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + if r.URL.Query().Get("with_servers") == "true" { + fmt.Fprint(w, HypervisorGetPost253Body) + } else { + fmt.Fprint(w, HypervisorGetBody) + } }) } -func HandleHypervisorGetSuccessfully(t *testing.T) { - testhelper.Mux.HandleFunc("/os-hypervisors/"+HypervisorFake.ID, func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "GET") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandleHypervisorGetEmptyCPUInfoSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-hypervisors/"+HypervisorFake.ID, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, HypervisorGetBody) + fmt.Fprint(w, HypervisorGetEmptyCPUInfoBody) }) } -func HandleHypervisorGetEmptyCPUInfoSuccessfully(t *testing.T) { - testhelper.Mux.HandleFunc("/os-hypervisors/"+HypervisorFake.ID, func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "GET") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandleHypervisorAfterV287ResponseSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-hypervisors/"+HypervisorFake.ID, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, HypervisorGetEmptyCPUInfoBody) + fmt.Fprint(w, HypervisorAfterV287ResponseBody) }) } -func HandleHypervisorUptimeSuccessfully(t *testing.T) { - testhelper.Mux.HandleFunc("/os-hypervisors/"+HypervisorFake.ID+"/uptime", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "GET") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandleHypervisorUptimeSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-hypervisors/"+HypervisorFake.ID+"/uptime", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, HypervisorUptimeBody) + fmt.Fprint(w, HypervisorUptimeBody) }) } diff --git a/openstack/compute/v2/hypervisors/testing/requests_test.go b/openstack/compute/v2/hypervisors/testing/requests_test.go index 900eadc78b..c6c7d9141f 100644 --- a/openstack/compute/v2/hypervisors/testing/requests_test.go +++ b/openstack/compute/v2/hypervisors/testing/requests_test.go @@ -6,17 +6,17 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/hypervisors" "github.com/gophercloud/gophercloud/v2/pagination" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestListHypervisorsPre253(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - HandleHypervisorListPre253Successfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleHypervisorListPre253Successfully(t, fakeServer) pages := 0 - err := hypervisors.List(client.ServiceClient(), + err := hypervisors.List(client.ServiceClient(fakeServer), hypervisors.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ @@ -28,13 +28,13 @@ func TestListHypervisorsPre253(t *testing.T) { if len(actual) != 2 { t.Fatalf("Expected 2 hypervisors, got %d", len(actual)) } - testhelper.CheckDeepEquals(t, HypervisorFakePre253, actual[0]) - testhelper.CheckDeepEquals(t, HypervisorFakePre253, actual[1]) + th.CheckDeepEquals(t, HypervisorFakePre253, actual[0]) + th.CheckDeepEquals(t, HypervisorFakePre253, actual[1]) return true, nil }) - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) if pages != 1 { t.Errorf("Expected 1 page, saw %d", pages) @@ -42,25 +42,25 @@ func TestListHypervisorsPre253(t *testing.T) { } func TestListAllHypervisorsPre253(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - HandleHypervisorListPre253Successfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleHypervisorListPre253Successfully(t, fakeServer) - allPages, err := hypervisors.List(client.ServiceClient(), hypervisors.ListOpts{}).AllPages(context.TODO()) - testhelper.AssertNoErr(t, err) + allPages, err := hypervisors.List(client.ServiceClient(fakeServer), hypervisors.ListOpts{}).AllPages(context.TODO()) + th.AssertNoErr(t, err) actual, err := hypervisors.ExtractHypervisors(allPages) - testhelper.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, HypervisorFakePre253, actual[0]) - testhelper.CheckDeepEquals(t, HypervisorFakePre253, actual[1]) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, HypervisorFakePre253, actual[0]) + th.CheckDeepEquals(t, HypervisorFakePre253, actual[1]) } func TestListHypervisors(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - HandleHypervisorListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleHypervisorListSuccessfully(t, fakeServer) pages := 0 - err := hypervisors.List(client.ServiceClient(), + err := hypervisors.List(client.ServiceClient(fakeServer), hypervisors.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ @@ -69,93 +69,116 @@ func TestListHypervisors(t *testing.T) { return false, err } - if len(actual) != 2 { - t.Fatalf("Expected 2 hypervisors, got %d", len(actual)) + if len(actual) != 1 { + t.Fatalf("Expected 1 hypervisors on page %d, got %d", pages, len(actual)) } - testhelper.CheckDeepEquals(t, HypervisorFake, actual[0]) - testhelper.CheckDeepEquals(t, HypervisorFake, actual[1]) + th.CheckDeepEquals(t, HypervisorFake, actual[0]) return true, nil }) - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) - if pages != 1 { - t.Errorf("Expected 1 page, saw %d", pages) + if pages != 2 { + t.Errorf("Expected 2 pages, saw %d", pages) } } func TestListAllHypervisors(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - HandleHypervisorListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleHypervisorListSuccessfully(t, fakeServer) - allPages, err := hypervisors.List(client.ServiceClient(), hypervisors.ListOpts{}).AllPages(context.TODO()) - testhelper.AssertNoErr(t, err) + allPages, err := hypervisors.List(client.ServiceClient(fakeServer), hypervisors.ListOpts{}).AllPages(context.TODO()) + th.AssertNoErr(t, err) actual, err := hypervisors.ExtractHypervisors(allPages) - testhelper.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, HypervisorFake, actual[0]) - testhelper.CheckDeepEquals(t, HypervisorFake, actual[1]) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, HypervisorFake, actual[0]) + th.CheckDeepEquals(t, HypervisorFake, actual[1]) } func TestListAllHypervisorsWithParameters(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - HandleHypervisorListWithParametersSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleHypervisorListWithParametersSuccessfully(t, fakeServer) with_servers := true - allPages, err := hypervisors.List(client.ServiceClient(), hypervisors.ListOpts{WithServers: &with_servers}).AllPages(context.TODO()) - testhelper.AssertNoErr(t, err) + allPages, err := hypervisors.List(client.ServiceClient(fakeServer), hypervisors.ListOpts{WithServers: &with_servers}).AllPages(context.TODO()) + th.AssertNoErr(t, err) actual, err := hypervisors.ExtractHypervisors(allPages) - testhelper.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, HypervisorFakeWithParameters, actual[0]) - testhelper.CheckDeepEquals(t, HypervisorFakeWithParameters, actual[1]) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, HypervisorFakeWithParameters, actual[0]) + th.CheckDeepEquals(t, HypervisorFakeWithParameters, actual[1]) } func TestHypervisorsStatistics(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - HandleHypervisorsStatisticsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleHypervisorsStatisticsSuccessfully(t, fakeServer) expected := HypervisorsStatisticsExpected - actual, err := hypervisors.GetStatistics(context.TODO(), client.ServiceClient()).Extract() - testhelper.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, &expected, actual) + actual, err := hypervisors.GetStatistics(context.TODO(), client.ServiceClient(fakeServer)).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &expected, actual) } func TestGetHypervisor(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - HandleHypervisorGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleHypervisorGetSuccessfully(t, fakeServer) expected := HypervisorFake - actual, err := hypervisors.Get(context.TODO(), client.ServiceClient(), expected.ID).Extract() - testhelper.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, &expected, actual) + actual, err := hypervisors.Get(context.TODO(), client.ServiceClient(fakeServer), expected.ID).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &expected, actual) +} + +func TestGetWithServersHypervisor(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleHypervisorGetPost253Successfully(t, fakeServer) + + expected := HypervisorFakeWithServers + withServers := true + actual, err := hypervisors.GetExt(context.TODO(), client.ServiceClient(fakeServer), expected.ID, hypervisors.GetOpts{WithServers: &withServers}).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &expected, actual) } func TestGetHypervisorEmptyCPUInfo(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - HandleHypervisorGetEmptyCPUInfoSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleHypervisorGetEmptyCPUInfoSuccessfully(t, fakeServer) expected := HypervisorEmptyCPUInfo - actual, err := hypervisors.Get(context.TODO(), client.ServiceClient(), expected.ID).Extract() - testhelper.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, &expected, actual) + actual, err := hypervisors.Get(context.TODO(), client.ServiceClient(fakeServer), expected.ID).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &expected, actual) +} + +func TestGetHypervisorAfterV287Response(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleHypervisorAfterV287ResponseSuccessfully(t, fakeServer) + + expected := HypervisorAfterV287Response + + actual, err := hypervisors.Get(context.TODO(), client.ServiceClient(fakeServer), expected.ID).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &expected, actual) } func TestHypervisorsUptime(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - HandleHypervisorUptimeSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleHypervisorUptimeSuccessfully(t, fakeServer) expected := HypervisorUptimeExpected - actual, err := hypervisors.GetUptime(context.TODO(), client.ServiceClient(), HypervisorFake.ID).Extract() - testhelper.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, &expected, actual) + actual, err := hypervisors.GetUptime(context.TODO(), client.ServiceClient(fakeServer), HypervisorFake.ID).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &expected, actual) } diff --git a/openstack/compute/v2/instanceactions/results.go b/openstack/compute/v2/instanceactions/results.go index 2015be88c2..8ebc060542 100644 --- a/openstack/compute/v2/instanceactions/results.go +++ b/openstack/compute/v2/instanceactions/results.go @@ -186,9 +186,9 @@ func (r InstanceActionResult) Extract() (InstanceActionDetail, error) { } func (r InstanceActionResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "instanceAction") + return r.ExtractIntoStructPtr(v, "instanceAction") } func ExtractInstanceActionsInto(r pagination.Page, v any) error { - return r.(InstanceActionPage).Result.ExtractIntoSlicePtr(v, "instanceActions") + return r.(InstanceActionPage).ExtractIntoSlicePtr(v, "instanceActions") } diff --git a/openstack/compute/v2/instanceactions/testing/fixtures_test.go b/openstack/compute/v2/instanceactions/testing/fixtures_test.go index 3b24801c38..1a53cbc7c2 100644 --- a/openstack/compute/v2/instanceactions/testing/fixtures_test.go +++ b/openstack/compute/v2/instanceactions/testing/fixtures_test.go @@ -34,13 +34,13 @@ var ListExpected = []instanceactions.InstanceAction{ } // HandleInstanceActionListSuccessfully sets up the test server to respond to a ListAddresses request. -func HandleInstanceActionListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/asdfasdfasdf/os-instance-actions", func(w http.ResponseWriter, r *http.Request) { +func HandleInstanceActionListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/asdfasdfasdf/os-instance-actions", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "instanceActions": [ { "action": "stop", @@ -94,13 +94,13 @@ var GetExpected = instanceactions.InstanceActionDetail{ } // HandleInstanceActionGetSuccessfully sets up the test server to respond to a Get request. -func HandleInstanceActionGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/asdfasdfasdf/os-instance-actions/okzeorkmkfs", func(w http.ResponseWriter, r *http.Request) { +func HandleInstanceActionGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/asdfasdfasdf/os-instance-actions/okzeorkmkfs", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "instanceAction": { "action": "stop", diff --git a/openstack/compute/v2/instanceactions/testing/request_test.go b/openstack/compute/v2/instanceactions/testing/request_test.go index 459c776948..1b36152d74 100644 --- a/openstack/compute/v2/instanceactions/testing/request_test.go +++ b/openstack/compute/v2/instanceactions/testing/request_test.go @@ -11,13 +11,13 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleInstanceActionListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleInstanceActionListSuccessfully(t, fakeServer) expected := ListExpected pages := 0 - err := instanceactions.List(client.ServiceClient(), "asdfasdfasdf", nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := instanceactions.List(client.ServiceClient(fakeServer), "asdfasdfasdf", nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := instanceactions.ExtractInstanceActions(page) @@ -35,11 +35,11 @@ func TestList(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleInstanceActionGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleInstanceActionGetSuccessfully(t, fakeServer) - client := client.ServiceClient() + client := client.ServiceClient(fakeServer) actual, err := instanceactions.Get(context.TODO(), client, "asdfasdfasdf", "okzeorkmkfs").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) diff --git a/openstack/compute/v2/keypairs/doc.go b/openstack/compute/v2/keypairs/doc.go index 0e52dfc9a3..b54d7945da 100644 --- a/openstack/compute/v2/keypairs/doc.go +++ b/openstack/compute/v2/keypairs/doc.go @@ -87,15 +87,11 @@ Example to Delete a Key Pair owned by a certain user using microversion 2.10 or Example to Create a Server With a Key Pair - serverCreateOpts := servers.CreateOpts{ + createOpts := servers.CreateOpts{ Name: "server_name", ImageRef: "image-uuid", FlavorRef: "flavor-uuid", - } - - createOpts := keypairs.CreateOptsExt{ - CreateOptsBuilder: serverCreateOpts, - KeyName: "keypair-name", + KeyName: "keypair-name", } server, err := servers.Create(context.TODO(), computeClient, createOpts).Extract() diff --git a/openstack/compute/v2/keypairs/requests.go b/openstack/compute/v2/keypairs/requests.go index bc4cc270f1..b2d17c73b9 100644 --- a/openstack/compute/v2/keypairs/requests.go +++ b/openstack/compute/v2/keypairs/requests.go @@ -4,35 +4,9 @@ import ( "context" "github.com/gophercloud/gophercloud/v2" - "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/v2/pagination" ) -// CreateOptsExt adds a KeyPair option to the base CreateOpts. -type CreateOptsExt struct { - servers.CreateOptsBuilder - - // KeyName is the name of the key pair. - KeyName string `json:"key_name,omitempty"` -} - -// ToServerCreateMap adds the key_name to the base server creation options. -func (opts CreateOptsExt) ToServerCreateMap() (map[string]any, error) { - base, err := opts.CreateOptsBuilder.ToServerCreateMap() - if err != nil { - return nil, err - } - - if opts.KeyName == "" { - return base, nil - } - - serverMap := base["server"].(map[string]any) - serverMap["key_name"] = opts.KeyName - - return base, nil -} - // ListOptsBuilder allows extensions to add additional parameters to the // List request. type ListOptsBuilder interface { diff --git a/openstack/compute/v2/keypairs/testing/fixtures_test.go b/openstack/compute/v2/keypairs/testing/fixtures_test.go index 41532c03cc..ae61082cda 100644 --- a/openstack/compute/v2/keypairs/testing/fixtures_test.go +++ b/openstack/compute/v2/keypairs/testing/fixtures_test.go @@ -151,29 +151,29 @@ var ImportedKeyPair = keypairs.KeyPair{ } // HandleListSuccessfully configures the test server to respond to a List request. -func HandleListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-keypairs", func(w http.ResponseWriter, r *http.Request) { +func HandleListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-keypairs", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } // HandleGetSuccessfully configures the test server to respond to a Get request for "firstkey". -func HandleGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-keypairs/firstkey", func(w http.ResponseWriter, r *http.Request) { +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-keypairs/firstkey", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) if r.URL.Query().Get("user_id") == "fake2" { w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetOutputOtherUser) + fmt.Fprint(w, GetOutputOtherUser) } else { w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) } }) @@ -181,34 +181,34 @@ func HandleGetSuccessfully(t *testing.T) { // HandleCreateSuccessfully configures the test server to respond to a Create request for a new // keypair called "createdkey". -func HandleCreateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-keypairs", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-keypairs", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ "keypair": { "name": "createdkey" } }`) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, CreateOutput) + fmt.Fprint(w, CreateOutput) }) } // HandleCreateSuccessfullyOtherUser configures the test server to respond to a Create request for a new // keypair called "createdkey" for another user, different than the current one. -func HandleCreateSuccessfullyOtherUser(t *testing.T) { - th.Mux.HandleFunc("/os-keypairs", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateSuccessfullyOtherUser(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-keypairs", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ "keypair": { "name": "createdkey", "user_id": "fake2" } }`) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, CreateOutputOtherUser) + fmt.Fprint(w, CreateOutputOtherUser) }) } // HandleImportSuccessfully configures the test server to respond to an Import request for an // existing keypair called "importedkey". -func HandleImportSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-keypairs", func(w http.ResponseWriter, r *http.Request) { +func HandleImportSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-keypairs", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, ` @@ -221,17 +221,17 @@ func HandleImportSuccessfully(t *testing.T) { `) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ImportOutput) + fmt.Fprint(w, ImportOutput) }) } // HandleDeleteSuccessfully configures the test server to respond to a Delete request for a // keypair called "deletedkey". -func HandleDeleteSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-keypairs/deletedkey", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-keypairs/deletedkey", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) - th.AssertEquals(t, r.Form.Get("user_id"), "") + th.AssertEquals(t, "", r.Form.Get("user_id")) w.WriteHeader(http.StatusAccepted) }) @@ -239,8 +239,8 @@ func HandleDeleteSuccessfully(t *testing.T) { // HandleDeleteSuccessfully configures the test server to respond to a Delete request for a // keypair called "deletedkey" for another user. -func HandleDeleteSuccessfullyOtherUser(t *testing.T) { - th.Mux.HandleFunc("/os-keypairs/deletedkey", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteSuccessfullyOtherUser(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-keypairs/deletedkey", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestFormValues(t, r, map[string]string{"user_id": "fake2"}) diff --git a/openstack/compute/v2/keypairs/testing/requests_test.go b/openstack/compute/v2/keypairs/testing/requests_test.go index 35e9f7653b..43961a3b15 100644 --- a/openstack/compute/v2/keypairs/testing/requests_test.go +++ b/openstack/compute/v2/keypairs/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer) count := 0 - err := keypairs.List(client.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := keypairs.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := keypairs.ExtractKeyPairs(page) th.AssertNoErr(t, err) @@ -29,11 +29,11 @@ func TestList(t *testing.T) { } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateSuccessfully(t, fakeServer) - actual, err := keypairs.Create(context.TODO(), client.ServiceClient(), keypairs.CreateOpts{ + actual, err := keypairs.Create(context.TODO(), client.ServiceClient(fakeServer), keypairs.CreateOpts{ Name: "createdkey", }).Extract() th.AssertNoErr(t, err) @@ -41,11 +41,11 @@ func TestCreate(t *testing.T) { } func TestCreateOtherUser(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateSuccessfullyOtherUser(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateSuccessfullyOtherUser(t, fakeServer) - actual, err := keypairs.Create(context.TODO(), client.ServiceClient(), keypairs.CreateOpts{ + actual, err := keypairs.Create(context.TODO(), client.ServiceClient(fakeServer), keypairs.CreateOpts{ Name: "createdkey", UserID: "fake2", }).Extract() @@ -54,11 +54,11 @@ func TestCreateOtherUser(t *testing.T) { } func TestImport(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleImportSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleImportSuccessfully(t, fakeServer) - actual, err := keypairs.Create(context.TODO(), client.ServiceClient(), keypairs.CreateOpts{ + actual, err := keypairs.Create(context.TODO(), client.ServiceClient(fakeServer), keypairs.CreateOpts{ Name: "importedkey", PublicKey: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDx8nkQv/zgGgB4rMYmIf+6A4l6Rr+o/6lHBQdW5aYd44bd8JttDCE/F/pNRr0lRE+PiqSPO8nDPHw0010JeMH9gYgnnFlyY3/OcJ02RhIPyyxYpv9FhY+2YiUkpwFOcLImyrxEsYXpD/0d3ac30bNH6Sw9JD9UZHYcpSxsIbECHw== Generated by Nova", }).Extract() @@ -67,47 +67,47 @@ func TestImport(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSuccessfully(t, fakeServer) - actual, err := keypairs.Get(context.TODO(), client.ServiceClient(), "firstkey", nil).Extract() + actual, err := keypairs.Get(context.TODO(), client.ServiceClient(fakeServer), "firstkey", nil).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &FirstKeyPair, actual) } func TestGetOtherUser(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSuccessfully(t, fakeServer) getOpts := keypairs.GetOpts{ UserID: "fake2", } - actual, err := keypairs.Get(context.TODO(), client.ServiceClient(), "firstkey", getOpts).Extract() + actual, err := keypairs.Get(context.TODO(), client.ServiceClient(fakeServer), "firstkey", getOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &FirstKeyPairOtherUser, actual) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteSuccessfully(t, fakeServer) - err := keypairs.Delete(context.TODO(), client.ServiceClient(), "deletedkey", nil).ExtractErr() + err := keypairs.Delete(context.TODO(), client.ServiceClient(fakeServer), "deletedkey", nil).ExtractErr() th.AssertNoErr(t, err) } func TestDeleteOtherUser(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteSuccessfullyOtherUser(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteSuccessfullyOtherUser(t, fakeServer) deleteOpts := keypairs.DeleteOpts{ UserID: "fake2", } - err := keypairs.Delete(context.TODO(), client.ServiceClient(), "deletedkey", deleteOpts).ExtractErr() + err := keypairs.Delete(context.TODO(), client.ServiceClient(fakeServer), "deletedkey", deleteOpts).ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/compute/v2/limits/testing/fixtures_test.go b/openstack/compute/v2/limits/testing/fixtures_test.go index 3751c2d8b6..365b9d68b8 100644 --- a/openstack/compute/v2/limits/testing/fixtures_test.go +++ b/openstack/compute/v2/limits/testing/fixtures_test.go @@ -69,12 +69,12 @@ const TenantID = "555544443333222211110000ffffeeee" // HandleGetSuccessfully configures the test server to respond to a Get request // for a limit. -func HandleGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/limits", func(w http.ResponseWriter, r *http.Request) { +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/limits", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } diff --git a/openstack/compute/v2/limits/testing/requests_test.go b/openstack/compute/v2/limits/testing/requests_test.go index ae9650097e..d795ec958c 100644 --- a/openstack/compute/v2/limits/testing/requests_test.go +++ b/openstack/compute/v2/limits/testing/requests_test.go @@ -10,15 +10,15 @@ import ( ) func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSuccessfully(t, fakeServer) getOpts := limits.GetOpts{ TenantID: TenantID, } - actual, err := limits.Get(context.TODO(), client.ServiceClient(), getOpts).Extract() + actual, err := limits.Get(context.TODO(), client.ServiceClient(fakeServer), getOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &LimitsResult, actual) } diff --git a/openstack/compute/v2/quotasets/testing/fixtures_test.go b/openstack/compute/v2/quotasets/testing/fixtures_test.go index d1c714714b..a9bac9f36d 100644 --- a/openstack/compute/v2/quotasets/testing/fixtures_test.go +++ b/openstack/compute/v2/quotasets/testing/fixtures_test.go @@ -161,51 +161,51 @@ var UpdatedQuotaSet = quotasets.UpdateOpts{ } // HandleGetSuccessfully configures the test server to respond to a Get request for sample tenant -func HandleGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-quota-sets/"+FirstTenantID, func(w http.ResponseWriter, r *http.Request) { +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-quota-sets/"+FirstTenantID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleGetDetailSuccessfully configures the test server to respond to a Get Details request for sample tenant -func HandleGetDetailSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-quota-sets/"+FirstTenantID+"/detail", func(w http.ResponseWriter, r *http.Request) { +func HandleGetDetailSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-quota-sets/"+FirstTenantID+"/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetDetailsOutput) + fmt.Fprint(w, GetDetailsOutput) }) } // HandlePutSuccessfully configures the test server to respond to a Put request for sample tenant -func HandlePutSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-quota-sets/"+FirstTenantID, func(w http.ResponseWriter, r *http.Request) { +func HandlePutSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-quota-sets/"+FirstTenantID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, UpdateOutput) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }) } // HandlePartialPutSuccessfully configures the test server to respond to a Put request for sample tenant that only containes specific values -func HandlePartialPutSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-quota-sets/"+FirstTenantID, func(w http.ResponseWriter, r *http.Request) { +func HandlePartialPutSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-quota-sets/"+FirstTenantID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, PartialUpdateBody) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }) } // HandleDeleteSuccessfully configures the test server to respond to a Delete request for sample tenant -func HandleDeleteSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-quota-sets/"+FirstTenantID, func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-quota-sets/"+FirstTenantID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestBody(t, r, "") diff --git a/openstack/compute/v2/quotasets/testing/requests_test.go b/openstack/compute/v2/quotasets/testing/requests_test.go index eac6e99d0d..5b17b5777d 100644 --- a/openstack/compute/v2/quotasets/testing/requests_test.go +++ b/openstack/compute/v2/quotasets/testing/requests_test.go @@ -12,62 +12,62 @@ import ( ) func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSuccessfully(t) - actual, err := quotasets.Get(context.TODO(), client.ServiceClient(), FirstTenantID).Extract() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSuccessfully(t, fakeServer) + actual, err := quotasets.Get(context.TODO(), client.ServiceClient(fakeServer), FirstTenantID).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &FirstQuotaSet, actual) } func TestGetDetail(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetDetailSuccessfully(t) - actual, err := quotasets.GetDetail(context.TODO(), client.ServiceClient(), FirstTenantID).Extract() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetDetailSuccessfully(t, fakeServer) + actual, err := quotasets.GetDetail(context.TODO(), client.ServiceClient(fakeServer), FirstTenantID).Extract() th.CheckDeepEquals(t, FirstQuotaDetailsSet, actual) th.AssertNoErr(t, err) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandlePutSuccessfully(t) - actual, err := quotasets.Update(context.TODO(), client.ServiceClient(), FirstTenantID, UpdatedQuotaSet).Extract() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePutSuccessfully(t, fakeServer) + actual, err := quotasets.Update(context.TODO(), client.ServiceClient(fakeServer), FirstTenantID, UpdatedQuotaSet).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &FirstQuotaSet, actual) } func TestPartialUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandlePartialPutSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePartialPutSuccessfully(t, fakeServer) opts := quotasets.UpdateOpts{Cores: gophercloud.IntToPointer(200), Force: true} - actual, err := quotasets.Update(context.TODO(), client.ServiceClient(), FirstTenantID, opts).Extract() + actual, err := quotasets.Update(context.TODO(), client.ServiceClient(fakeServer), FirstTenantID, opts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &FirstQuotaSet, actual) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteSuccessfully(t) - _, err := quotasets.Delete(context.TODO(), client.ServiceClient(), FirstTenantID).Extract() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteSuccessfully(t, fakeServer) + _, err := quotasets.Delete(context.TODO(), client.ServiceClient(fakeServer), FirstTenantID).Extract() th.AssertNoErr(t, err) } type ErrorUpdateOpts quotasets.UpdateOpts func (opts ErrorUpdateOpts) ToComputeQuotaUpdateMap() (map[string]any, error) { - return nil, errors.New("This is an error") + return nil, errors.New("this is an error") } func TestErrorInToComputeQuotaUpdateMap(t *testing.T) { opts := &ErrorUpdateOpts{} - th.SetupHTTP() - defer th.TeardownHTTP() - HandlePutSuccessfully(t) - _, err := quotasets.Update(context.TODO(), client.ServiceClient(), FirstTenantID, opts).Extract() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePutSuccessfully(t, fakeServer) + _, err := quotasets.Update(context.TODO(), client.ServiceClient(fakeServer), FirstTenantID, opts).Extract() if err == nil { t.Fatal("Error handling failed") } diff --git a/openstack/compute/v2/remoteconsoles/doc.go b/openstack/compute/v2/remoteconsoles/doc.go index 65dc9b91d7..d30787bb5b 100644 --- a/openstack/compute/v2/remoteconsoles/doc.go +++ b/openstack/compute/v2/remoteconsoles/doc.go @@ -6,7 +6,7 @@ that API. Example of Creating a new RemoteConsole - computeClient, err := openstack.NewComputeV2(providerClient, endpointOptions) + computeClient, err := openstack.NewComputeV2(context.TODO(), providerClient, endpointOptions) computeClient.Microversion = "2.6" createOpts := remoteconsoles.CreateOpts{ diff --git a/openstack/compute/v2/remoteconsoles/testing/requests_test.go b/openstack/compute/v2/remoteconsoles/testing/requests_test.go index e96b897d0f..1ef52873da 100644 --- a/openstack/compute/v2/remoteconsoles/testing/requests_test.go +++ b/openstack/compute/v2/remoteconsoles/testing/requests_test.go @@ -8,16 +8,16 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/remoteconsoles" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/servers/b16ba811-199d-4ffd-8839-ba96c1185a67/remote-consoles", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/servers/b16ba811-199d-4ffd-8839-ba96c1185a67/remote-consoles", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, RemoteConsoleCreateRequest) @@ -25,17 +25,17 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, RemoteConsoleCreateResult) + fmt.Fprint(w, RemoteConsoleCreateResult) }) opts := remoteconsoles.CreateOpts{ Protocol: remoteconsoles.ConsoleProtocolVNC, Type: remoteconsoles.ConsoleTypeNoVNC, } - s, err := remoteconsoles.Create(context.TODO(), fake.ServiceClient(), "b16ba811-199d-4ffd-8839-ba96c1185a67", opts).Extract() + s, err := remoteconsoles.Create(context.TODO(), client.ServiceClient(fakeServer), "b16ba811-199d-4ffd-8839-ba96c1185a67", opts).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, s.Protocol, string(remoteconsoles.ConsoleProtocolVNC)) th.AssertEquals(t, s.Type, string(remoteconsoles.ConsoleTypeNoVNC)) - th.AssertEquals(t, s.URL, "http://192.168.0.4:6080/vnc_auto.html?token=9a2372b9-6a0e-4f71-aca1-56020e6bb677") + th.AssertEquals(t, "http://192.168.0.4:6080/vnc_auto.html?token=9a2372b9-6a0e-4f71-aca1-56020e6bb677", s.URL) } diff --git a/openstack/compute/v2/secgroups/requests.go b/openstack/compute/v2/secgroups/requests.go index d69a99b4f0..c43575aeb4 100644 --- a/openstack/compute/v2/secgroups/requests.go +++ b/openstack/compute/v2/secgroups/requests.go @@ -61,7 +61,7 @@ func Create(ctx context.Context, client *gophercloud.ServiceClient, opts CreateO // UpdateOpts is the struct responsible for updating an existing security group. type UpdateOpts struct { // the name of your security group. - Name string `json:"name,omitempty"` + Name *string `json:"name,omitempty"` // the description of your security group. Description *string `json:"description,omitempty"` } diff --git a/openstack/compute/v2/secgroups/testing/fixtures_test.go b/openstack/compute/v2/secgroups/testing/fixtures_test.go index 0b35cc71d8..cee5eb6d5e 100644 --- a/openstack/compute/v2/secgroups/testing/fixtures_test.go +++ b/openstack/compute/v2/secgroups/testing/fixtures_test.go @@ -6,7 +6,7 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) const rootPath = "/os-security-groups" @@ -25,35 +25,35 @@ const listGroupsJSON = ` } ` -func mockListGroupsResponse(t *testing.T) { - th.Mux.HandleFunc(rootPath, func(w http.ResponseWriter, r *http.Request) { +func mockListGroupsResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(rootPath, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, listGroupsJSON) + fmt.Fprint(w, listGroupsJSON) }) } -func mockListGroupsByServerResponse(t *testing.T, serverID string) { +func mockListGroupsByServerResponse(t *testing.T, fakeServer th.FakeServer, serverID string) { url := fmt.Sprintf("/servers/%s%s", serverID, rootPath) - th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, listGroupsJSON) + fmt.Fprint(w, listGroupsJSON) }) } -func mockCreateGroupResponse(t *testing.T) { - th.Mux.HandleFunc(rootPath, func(w http.ResponseWriter, r *http.Request) { +func mockCreateGroupResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(rootPath, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, ` { @@ -67,7 +67,7 @@ func mockCreateGroupResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "security_group": { "description": "something", @@ -81,11 +81,11 @@ func mockCreateGroupResponse(t *testing.T) { }) } -func mockUpdateGroupResponse(t *testing.T, groupID string) { +func mockUpdateGroupResponse(t *testing.T, fakeServer th.FakeServer, groupID string) { url := fmt.Sprintf("%s/%s", rootPath, groupID) - th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, ` { @@ -99,7 +99,7 @@ func mockUpdateGroupResponse(t *testing.T, groupID string) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "security_group": { "description": "something", @@ -113,16 +113,16 @@ func mockUpdateGroupResponse(t *testing.T, groupID string) { }) } -func mockGetGroupsResponse(t *testing.T, groupID string) { +func mockGetGroupsResponse(t *testing.T, fakeServer th.FakeServer, groupID string) { url := fmt.Sprintf("%s/%s", rootPath, groupID) - th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "security_group": { "description": "default", @@ -151,11 +151,11 @@ func mockGetGroupsResponse(t *testing.T, groupID string) { }) } -func mockGetNumericIDGroupResponse(t *testing.T, groupID int) { +func mockGetNumericIDGroupResponse(t *testing.T, fakeServer th.FakeServer, groupID int) { url := fmt.Sprintf("%s/%d", rootPath, groupID) - th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -170,11 +170,11 @@ func mockGetNumericIDGroupResponse(t *testing.T, groupID int) { }) } -func mockGetNumericIDGroupRuleResponse(t *testing.T, groupID int) { +func mockGetNumericIDGroupRuleResponse(t *testing.T, fakeServer th.FakeServer, groupID int) { url := fmt.Sprintf("%s/%d", rootPath, groupID) - th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -195,20 +195,20 @@ func mockGetNumericIDGroupRuleResponse(t *testing.T, groupID int) { }) } -func mockDeleteGroupResponse(t *testing.T, groupID string) { +func mockDeleteGroupResponse(t *testing.T, fakeServer th.FakeServer, groupID string) { url := fmt.Sprintf("%s/%s", rootPath, groupID) - th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) }) } -func mockAddRuleResponse(t *testing.T) { - th.Mux.HandleFunc("/os-security-group-rules", func(w http.ResponseWriter, r *http.Request) { +func mockAddRuleResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-security-group-rules", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, ` { @@ -224,7 +224,7 @@ func mockAddRuleResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "security_group_rule": { "from_port": 22, @@ -241,10 +241,10 @@ func mockAddRuleResponse(t *testing.T) { }) } -func mockAddRuleResponseICMPZero(t *testing.T) { - th.Mux.HandleFunc("/os-security-group-rules", func(w http.ResponseWriter, r *http.Request) { +func mockAddRuleResponseICMPZero(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-security-group-rules", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, ` { @@ -260,7 +260,7 @@ func mockAddRuleResponseICMPZero(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "security_group_rule": { "from_port": 0, @@ -277,21 +277,21 @@ func mockAddRuleResponseICMPZero(t *testing.T) { }) } -func mockDeleteRuleResponse(t *testing.T, ruleID string) { +func mockDeleteRuleResponse(t *testing.T, fakeServer th.FakeServer, ruleID string) { url := fmt.Sprintf("/os-security-group-rules/%s", ruleID) - th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) }) } -func mockAddServerToGroupResponse(t *testing.T, serverID string) { +func mockAddServerToGroupResponse(t *testing.T, fakeServer th.FakeServer, serverID string) { url := fmt.Sprintf("/servers/%s/action", serverID) - th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, ` { @@ -306,11 +306,11 @@ func mockAddServerToGroupResponse(t *testing.T, serverID string) { }) } -func mockRemoveServerFromGroupResponse(t *testing.T, serverID string) { +func mockRemoveServerFromGroupResponse(t *testing.T, fakeServer th.FakeServer, serverID string) { url := fmt.Sprintf("/servers/%s/action", serverID) - th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, ` { diff --git a/openstack/compute/v2/secgroups/testing/requests_test.go b/openstack/compute/v2/secgroups/testing/requests_test.go index 222295d5e8..38d819d06a 100644 --- a/openstack/compute/v2/secgroups/testing/requests_test.go +++ b/openstack/compute/v2/secgroups/testing/requests_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/gophercloud/gophercloud/v2/internal/ptr" "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/secgroups" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" @@ -17,14 +18,14 @@ const ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - mockListGroupsResponse(t) + mockListGroupsResponse(t, fakeServer) count := 0 - err := secgroups.List(client.ServiceClient()).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := secgroups.List(client.ServiceClient(fakeServer)).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := secgroups.ExtractSecurityGroups(page) if err != nil { @@ -52,14 +53,14 @@ func TestList(t *testing.T) { } func TestListByServer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - mockListGroupsByServerResponse(t, serverID) + mockListGroupsByServerResponse(t, fakeServer, serverID) count := 0 - err := secgroups.ListByServer(client.ServiceClient(), serverID).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := secgroups.ListByServer(client.ServiceClient(fakeServer), serverID).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := secgroups.ExtractSecurityGroups(page) if err != nil { @@ -87,17 +88,17 @@ func TestListByServer(t *testing.T) { } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - mockCreateGroupResponse(t) + mockCreateGroupResponse(t, fakeServer) opts := secgroups.CreateOpts{ Name: "test", Description: "something", } - group, err := secgroups.Create(context.TODO(), client.ServiceClient(), opts).Extract() + group, err := secgroups.Create(context.TODO(), client.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) expected := &secgroups.SecurityGroup{ @@ -111,18 +112,17 @@ func TestCreate(t *testing.T) { } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - mockUpdateGroupResponse(t, groupID) + mockUpdateGroupResponse(t, fakeServer, groupID) - description := "new_desc" opts := secgroups.UpdateOpts{ - Name: "new_name", - Description: &description, + Name: ptr.To("new_name"), + Description: ptr.To("new_desc"), } - group, err := secgroups.Update(context.TODO(), client.ServiceClient(), groupID, opts).Extract() + group, err := secgroups.Update(context.TODO(), client.ServiceClient(fakeServer), groupID, opts).Extract() th.AssertNoErr(t, err) expected := &secgroups.SecurityGroup{ @@ -136,12 +136,12 @@ func TestUpdate(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - mockGetGroupsResponse(t, groupID) + mockGetGroupsResponse(t, fakeServer, groupID) - group, err := secgroups.Get(context.TODO(), client.ServiceClient(), groupID).Extract() + group, err := secgroups.Get(context.TODO(), client.ServiceClient(fakeServer), groupID).Extract() th.AssertNoErr(t, err) expected := &secgroups.SecurityGroup{ @@ -166,14 +166,14 @@ func TestGet(t *testing.T) { } func TestGetNumericID(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() numericGroupID := 12345 - mockGetNumericIDGroupResponse(t, numericGroupID) + mockGetNumericIDGroupResponse(t, fakeServer, numericGroupID) - group, err := secgroups.Get(context.TODO(), client.ServiceClient(), "12345").Extract() + group, err := secgroups.Get(context.TODO(), client.ServiceClient(fakeServer), "12345").Extract() th.AssertNoErr(t, err) expected := &secgroups.SecurityGroup{ID: "12345"} @@ -181,14 +181,14 @@ func TestGetNumericID(t *testing.T) { } func TestGetNumericRuleID(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() numericGroupID := 12345 - mockGetNumericIDGroupRuleResponse(t, numericGroupID) + mockGetNumericIDGroupRuleResponse(t, fakeServer, numericGroupID) - group, err := secgroups.Get(context.TODO(), client.ServiceClient(), "12345").Extract() + group, err := secgroups.Get(context.TODO(), client.ServiceClient(fakeServer), "12345").Extract() th.AssertNoErr(t, err) expected := &secgroups.SecurityGroup{ @@ -204,20 +204,20 @@ func TestGetNumericRuleID(t *testing.T) { } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - mockDeleteGroupResponse(t, groupID) + mockDeleteGroupResponse(t, fakeServer, groupID) - err := secgroups.Delete(context.TODO(), client.ServiceClient(), groupID).ExtractErr() + err := secgroups.Delete(context.TODO(), client.ServiceClient(fakeServer), groupID).ExtractErr() th.AssertNoErr(t, err) } func TestAddRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - mockAddRuleResponse(t) + mockAddRuleResponse(t, fakeServer) opts := secgroups.CreateRuleOpts{ ParentGroupID: groupID, @@ -227,7 +227,7 @@ func TestAddRule(t *testing.T) { CIDR: "0.0.0.0/0", } - rule, err := secgroups.CreateRule(context.TODO(), client.ServiceClient(), opts).Extract() + rule, err := secgroups.CreateRule(context.TODO(), client.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) expected := &secgroups.Rule{ @@ -244,10 +244,10 @@ func TestAddRule(t *testing.T) { } func TestAddRuleICMPZero(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - mockAddRuleResponseICMPZero(t) + mockAddRuleResponseICMPZero(t, fakeServer) opts := secgroups.CreateRuleOpts{ ParentGroupID: groupID, @@ -257,7 +257,7 @@ func TestAddRuleICMPZero(t *testing.T) { CIDR: "0.0.0.0/0", } - rule, err := secgroups.CreateRule(context.TODO(), client.ServiceClient(), opts).Extract() + rule, err := secgroups.CreateRule(context.TODO(), client.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) expected := &secgroups.Rule{ @@ -274,31 +274,31 @@ func TestAddRuleICMPZero(t *testing.T) { } func TestDeleteRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - mockDeleteRuleResponse(t, ruleID) + mockDeleteRuleResponse(t, fakeServer, ruleID) - err := secgroups.DeleteRule(context.TODO(), client.ServiceClient(), ruleID).ExtractErr() + err := secgroups.DeleteRule(context.TODO(), client.ServiceClient(fakeServer), ruleID).ExtractErr() th.AssertNoErr(t, err) } func TestAddServer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - mockAddServerToGroupResponse(t, serverID) + mockAddServerToGroupResponse(t, fakeServer, serverID) - err := secgroups.AddServer(context.TODO(), client.ServiceClient(), serverID, "test").ExtractErr() + err := secgroups.AddServer(context.TODO(), client.ServiceClient(fakeServer), serverID, "test").ExtractErr() th.AssertNoErr(t, err) } func TestRemoveServer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - mockRemoveServerFromGroupResponse(t, serverID) + mockRemoveServerFromGroupResponse(t, fakeServer, serverID) - err := secgroups.RemoveServer(context.TODO(), client.ServiceClient(), serverID, "test").ExtractErr() + err := secgroups.RemoveServer(context.TODO(), client.ServiceClient(fakeServer), serverID, "test").ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/compute/v2/servergroups/testing/fixtures_test.go b/openstack/compute/v2/servergroups/testing/fixtures_test.go index de36c3ff89..f9ce09be22 100644 --- a/openstack/compute/v2/servergroups/testing/fixtures_test.go +++ b/openstack/compute/v2/servergroups/testing/fixtures_test.go @@ -57,9 +57,6 @@ const GetOutputMicroversion = ` "server_group": { "id": "616fb98f-46ca-475e-917e-2563e5a8cd19", "name": "test", - "policies": [ - "anti-affinity" - ], "policy": "anti-affinity", "rules": { "max_server_per_host": 3 @@ -91,9 +88,6 @@ const CreateOutputMicroversion = ` "server_group": { "id": "616fb98f-46ca-475e-917e-2563e5a8cd19", "name": "test", - "policies": [ - "anti-affinity" - ], "policy": "anti-affinity", "rules": { "max_server_per_host": 3 @@ -104,8 +98,10 @@ const CreateOutputMicroversion = ` } ` -// FirstServerGroup is the first result in ListOutput. -var FirstServerGroup = servergroups.ServerGroup{ +var policy = "anti-affinity" + +// ExpectedServerGroupGet is parsed result from GetOutput. +var ExpectedServerGroupGet = servergroups.ServerGroup{ ID: "616fb98f-46ca-475e-917e-2563e5a8cd19", Name: "test", Policies: []string{ @@ -115,23 +111,43 @@ var FirstServerGroup = servergroups.ServerGroup{ Metadata: map[string]any{}, } -// SecondServerGroup is the second result in ListOutput. -var SecondServerGroup = servergroups.ServerGroup{ - ID: "4d8c3732-a248-40ed-bebc-539a6ffd25c0", - Name: "test2", - Policies: []string{ - "affinity", +// ExpectedServerGroupGet is parsed result from GetOutputMicroversion. +var ExpectedServerGroupGetMicroversion = servergroups.ServerGroup{ + ID: "616fb98f-46ca-475e-917e-2563e5a8cd19", + Name: "test", + Policy: &policy, + Rules: &servergroups.Rules{ + MaxServerPerHost: 3, }, Members: []string{}, Metadata: map[string]any{}, } -// ExpectedServerGroupSlice is the slice of results that should be parsed +// ExpectedServerGroupList is the slice of results that should be parsed // from ListOutput, in the expected order. -var ExpectedServerGroupSlice = []servergroups.ServerGroup{FirstServerGroup, SecondServerGroup} +var ExpectedServerGroupList = []servergroups.ServerGroup{ + { + ID: "616fb98f-46ca-475e-917e-2563e5a8cd19", + Name: "test", + Policies: []string{ + "anti-affinity", + }, + Members: []string{}, + Metadata: map[string]any{}, + }, + { + ID: "4d8c3732-a248-40ed-bebc-539a6ffd25c0", + Name: "test2", + Policies: []string{ + "affinity", + }, + Members: []string{}, + Metadata: map[string]any{}, + }, +} -// CreatedServerGroup is the parsed result from CreateOutput. -var CreatedServerGroup = servergroups.ServerGroup{ +// ExpectedServerGroupCreate is the parsed result from CreateOutput. +var ExpectedServerGroupCreate = servergroups.ServerGroup{ ID: "616fb98f-46ca-475e-917e-2563e5a8cd19", Name: "test", Policies: []string{ @@ -141,45 +157,57 @@ var CreatedServerGroup = servergroups.ServerGroup{ Metadata: map[string]any{}, } +// CreatedServerGroup is the parsed result from CreateOutputMicroversion. +var ExpectedServerGroupCreateMicroversion = servergroups.ServerGroup{ + ID: "616fb98f-46ca-475e-917e-2563e5a8cd19", + Name: "test", + Policy: &policy, + Rules: &servergroups.Rules{ + MaxServerPerHost: 3, + }, + Members: []string{}, + Metadata: map[string]any{}, +} + // HandleListSuccessfully configures the test server to respond to a List request. -func HandleListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-server-groups", func(w http.ResponseWriter, r *http.Request) { +func HandleListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-server-groups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } // HandleGetSuccessfully configures the test server to respond to a Get request // for an existing server group -func HandleGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-server-groups/4d8c3732-a248-40ed-bebc-539a6ffd25c0", func(w http.ResponseWriter, r *http.Request) { +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-server-groups/4d8c3732-a248-40ed-bebc-539a6ffd25c0", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleGetMicroversionSuccessfully configures the test server to respond to a Get request // for an existing server group with microversion set to 2.64 -func HandleGetMicroversionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-server-groups/4d8c3732-a248-40ed-bebc-539a6ffd25c0", func(w http.ResponseWriter, r *http.Request) { +func HandleGetMicroversionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-server-groups/4d8c3732-a248-40ed-bebc-539a6ffd25c0", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetOutputMicroversion) + fmt.Fprint(w, GetOutputMicroversion) }) } // HandleCreateSuccessfully configures the test server to respond to a Create request // for a new server group -func HandleCreateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-server-groups", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-server-groups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, ` @@ -194,23 +222,20 @@ func HandleCreateSuccessfully(t *testing.T) { `) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, CreateOutput) + fmt.Fprint(w, CreateOutput) }) } // HandleCreateMicroversionSuccessfully configures the test server to respond to a Create request // for a new server group with microversion set to 2.64 -func HandleCreateMicroversionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-server-groups", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateMicroversionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-server-groups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, ` { "server_group": { "name": "test", - "policies": [ - "anti-affinity" - ], "policy": "anti-affinity", "rules": { "max_server_per_host": 3 @@ -220,14 +245,14 @@ func HandleCreateMicroversionSuccessfully(t *testing.T) { `) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, CreateOutputMicroversion) + fmt.Fprint(w, CreateOutputMicroversion) }) } // HandleDeleteSuccessfully configures the test server to respond to a Delete request for a // an existing server group -func HandleDeleteSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-server-groups/616fb98f-46ca-475e-917e-2563e5a8cd19", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-server-groups/616fb98f-46ca-475e-917e-2563e5a8cd19", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) diff --git a/openstack/compute/v2/servergroups/testing/requests_test.go b/openstack/compute/v2/servergroups/testing/requests_test.go index 91a9b75168..770d3f7266 100644 --- a/openstack/compute/v2/servergroups/testing/requests_test.go +++ b/openstack/compute/v2/servergroups/testing/requests_test.go @@ -11,16 +11,16 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer) count := 0 - err := servergroups.List(client.ServiceClient(), &servergroups.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := servergroups.List(client.ServiceClient(fakeServer), &servergroups.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := servergroups.ExtractServerGroups(page) th.AssertNoErr(t, err) - th.CheckDeepEquals(t, ExpectedServerGroupSlice, actual) + th.CheckDeepEquals(t, ExpectedServerGroupList, actual) return true, nil }) @@ -29,77 +29,59 @@ func TestList(t *testing.T) { } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateSuccessfully(t, fakeServer) - actual, err := servergroups.Create(context.TODO(), client.ServiceClient(), servergroups.CreateOpts{ + actual, err := servergroups.Create(context.TODO(), client.ServiceClient(fakeServer), servergroups.CreateOpts{ Name: "test", Policies: []string{"anti-affinity"}, }).Extract() th.AssertNoErr(t, err) - th.CheckDeepEquals(t, &CreatedServerGroup, actual) + th.CheckDeepEquals(t, &ExpectedServerGroupCreate, actual) } func TestCreateMicroversion(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateMicroversionSuccessfully(t) - - policy := "anti-affinity" - rules := servergroups.Rules{ - MaxServerPerHost: 3, - } - CreatedServerGroup.Policy = &policy - CreatedServerGroup.Rules = &rules - - result := servergroups.Create(context.TODO(), client.ServiceClient(), servergroups.CreateOpts{ - Name: "test", - Policies: []string{"anti-affinity"}, - Policy: policy, - Rules: &rules, + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateMicroversionSuccessfully(t, fakeServer) + + result := servergroups.Create(context.TODO(), client.ServiceClient(fakeServer), servergroups.CreateOpts{ + Name: "test", + Policy: policy, + Rules: ExpectedServerGroupCreateMicroversion.Rules, }) actual, err := result.Extract() th.AssertNoErr(t, err) - th.CheckDeepEquals(t, &CreatedServerGroup, actual) + th.CheckDeepEquals(t, &ExpectedServerGroupCreateMicroversion, actual) } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSuccessfully(t, fakeServer) - actual, err := servergroups.Get(context.TODO(), client.ServiceClient(), "4d8c3732-a248-40ed-bebc-539a6ffd25c0").Extract() + actual, err := servergroups.Get(context.TODO(), client.ServiceClient(fakeServer), "4d8c3732-a248-40ed-bebc-539a6ffd25c0").Extract() th.AssertNoErr(t, err) - th.CheckDeepEquals(t, &FirstServerGroup, actual) + th.CheckDeepEquals(t, &ExpectedServerGroupGet, actual) } func TestGetMicroversion(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetMicroversionSuccessfully(t) - - policy := "anti-affinity" - rules := servergroups.Rules{ - MaxServerPerHost: 3, - } - FirstServerGroup.Policy = &policy - FirstServerGroup.Rules = &rules + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetMicroversionSuccessfully(t, fakeServer) - result := servergroups.Get(context.TODO(), client.ServiceClient(), "4d8c3732-a248-40ed-bebc-539a6ffd25c0") - - // Extract basic fields. - actual, err := result.Extract() + actual, err := servergroups.Get(context.TODO(), client.ServiceClient(fakeServer), "4d8c3732-a248-40ed-bebc-539a6ffd25c0").Extract() th.AssertNoErr(t, err) - th.CheckDeepEquals(t, &FirstServerGroup, actual) + th.CheckDeepEquals(t, &ExpectedServerGroupGetMicroversion, actual) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteSuccessfully(t, fakeServer) - err := servergroups.Delete(context.TODO(), client.ServiceClient(), "616fb98f-46ca-475e-917e-2563e5a8cd19").ExtractErr() + err := servergroups.Delete(context.TODO(), client.ServiceClient(fakeServer), "616fb98f-46ca-475e-917e-2563e5a8cd19").ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/compute/v2/servers/requests.go b/openstack/compute/v2/servers/requests.go index dd3b132d1d..8d0685997d 100644 --- a/openstack/compute/v2/servers/requests.go +++ b/openstack/compute/v2/servers/requests.go @@ -1,10 +1,12 @@ package servers import ( + "bytes" "context" "encoding/base64" "encoding/json" "fmt" + "io" "maps" "net" "regexp" @@ -508,6 +510,12 @@ type CreateOpts struct { // DiskConfig [optional] controls how the created server's disk is partitioned. DiskConfig DiskConfig `json:"OS-DCF:diskConfig,omitempty"` + + // KeyName is the name of the key pair. + KeyName string `json:"key_name,omitempty"` + + // HypervisorHostname is the name of the hypervisor to which the server is scheduled. + HypervisorHostname string `json:"hypervisor_hostname,omitempty"` } // ToServerCreateMap assembles a request body based on the contents of a @@ -641,13 +649,19 @@ type UpdateOpts struct { // Name changes the displayed name of the server. // The server host name will *not* change. // Server names are not constrained to be unique, even within the same tenant. - Name string `json:"name,omitempty"` + Name *string `json:"name,omitempty"` // AccessIPv4 provides a new IPv4 address for the instance. - AccessIPv4 string `json:"accessIPv4,omitempty"` + AccessIPv4 *string `json:"accessIPv4,omitempty"` // AccessIPv6 provides a new IPv6 address for the instance. - AccessIPv6 string `json:"accessIPv6,omitempty"` + AccessIPv6 *string `json:"accessIPv6,omitempty"` + + // Hostname changes the hostname of the server. + // Requires microversion 2.90 or later. + // Note: This information is published via the metadata service and requires + // application such as cloud-init to propagate it through to the instance. + Hostname *string `json:"hostname,omitempty"` } // ToServerUpdateMap formats an UpdateOpts structure into a request body. @@ -1041,10 +1055,35 @@ func CreateImage(ctx context.Context, client *gophercloud.ServiceClient, id stri r.Err = err return } + resp, err := client.Post(ctx, actionURL(client, id), b, nil, &gophercloud.RequestOpts{ - OkCodes: []int{202}, + OkCodes: []int{202}, + KeepResponseBody: true, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + if r.Err != nil { + return + } + defer resp.Body.Close() + + if v := r.Header.Get("Content-Type"); v != "application/json" { + return + } + + // The response body is expected to be a small JSON object containing only "image_id". + // Read it fully into memory so the response body can be closed immediately. + // If the caller doesn't read from the buffer, it can still be safely garbage collected. + + var buf bytes.Buffer + + _, r.Err = io.Copy(&buf, resp.Body) + if r.Err != nil { + return + } + + r.Body = &buf + return } diff --git a/openstack/compute/v2/servers/results.go b/openstack/compute/v2/servers/results.go index dc51564176..df1ec67396 100644 --- a/openstack/compute/v2/servers/results.go +++ b/openstack/compute/v2/servers/results.go @@ -7,9 +7,11 @@ import ( "fmt" "net/url" "path" + "strconv" "time" "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack/utils" "github.com/gophercloud/gophercloud/v2/pagination" ) @@ -25,11 +27,11 @@ func (r serverResult) Extract() (*Server, error) { } func (r serverResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "server") + return r.ExtractIntoStructPtr(v, "server") } func ExtractServersInto(r pagination.Page, v any) error { - return r.(ServerPage).Result.ExtractIntoSlicePtr(v, "servers") + return r.(ServerPage).ExtractIntoSlicePtr(v, "servers") } // CreateResult is the response from a Create operation. Call its Extract @@ -117,11 +119,11 @@ func decryptPassword(encryptedPassword string, privateKey *rsa.PrivateKey) (stri n, err := base64.StdEncoding.Decode(b64EncryptedPassword, []byte(encryptedPassword)) if err != nil { - return "", fmt.Errorf("Failed to base64 decode encrypted password: %s", err) + return "", fmt.Errorf("failed to base64 decode encrypted password: %s", err) } password, err := rsa.DecryptPKCS1v15(nil, privateKey, b64EncryptedPassword[0:n]) if err != nil { - return "", fmt.Errorf("Failed to decrypt password: %s", err) + return "", fmt.Errorf("failed to decrypt password: %s", err) } return string(password), nil @@ -132,18 +134,49 @@ func (r CreateImageResult) ExtractImageID() (string, error) { if r.Err != nil { return "", r.Err } - // Get the image id from the header + + microversion := r.Header.Get("X-OpenStack-Nova-API-Version") + + major, minor, err := utils.ParseMicroversion(microversion) + if err != nil { + return "", fmt.Errorf("failed to parse X-OpenStack-Nova-API-Version header: %s", err) + } + + // In microversions prior to 2.45, the image ID was provided in the Location header. + if major < 2 || (major == 2 && minor < 45) { + return r.extractImageIDFromLocationHeader() + } + + // Starting from 2.45, it is included in the response body. + return r.extractImageIDFromResponseBody() +} + +func (r CreateImageResult) extractImageIDFromLocationHeader() (string, error) { u, err := url.ParseRequestURI(r.Header.Get("Location")) if err != nil { return "", err } + imageID := path.Base(u.Path) if imageID == "." || imageID == "/" { - return "", fmt.Errorf("Failed to parse the ID of newly created image: %s", u) + return "", fmt.Errorf("failed to parse the ID of newly created image: %s", u) } + return imageID, nil } +func (r CreateImageResult) extractImageIDFromResponseBody() (string, error) { + var response struct { + ImageID string `json:"image_id"` + } + + if err := r.ExtractInto(&response); err != nil { + return "", err + } + + return response.ImageID, nil +} + // Server represents a server/instance in the OpenStack cloud. type Server struct { // ID uniquely identifies this server amongst all other servers, @@ -279,6 +312,13 @@ type Server struct { // AvailabilityZone is the availabilty zone the server is in. AvailabilityZone string `json:"OS-EXT-AZ:availability_zone"` + + // Locked indicates the lock status of the server + // This requires microversion 2.9 or later + Locked *bool `json:"locked"` + + // ConfigDrive enables metadata injection through a configuration drive. + ConfigDrive bool `json:"-"` } type AttachedVolume struct { @@ -339,6 +379,7 @@ func (r *Server) UnmarshalJSON(b []byte) error { Image any `json:"image"` LaunchedAt gophercloud.JSONRFC3339MilliNoZ `json:"OS-SRV-USG:launched_at"` TerminatedAt gophercloud.JSONRFC3339MilliNoZ `json:"OS-SRV-USG:terminated_at"` + ConfigDrive any `json:"config_drive"` } err := json.Unmarshal(b, &s) if err != nil { @@ -360,6 +401,24 @@ func (r *Server) UnmarshalJSON(b []byte) error { r.LaunchedAt = time.Time(s.LaunchedAt) r.TerminatedAt = time.Time(s.TerminatedAt) + switch t := s.ConfigDrive.(type) { + case nil: + r.ConfigDrive = false + case bool: + r.ConfigDrive = t + case string: + if t == "" { + r.ConfigDrive = false + } else { + r.ConfigDrive, err = strconv.ParseBool(t) + if err != nil { + return fmt.Errorf("failed to parse ConfigDrive %q: %v", t, err) + } + } + default: + return fmt.Errorf("unknown type for ConfigDrive: %T (value: %v)", t, t) + } + return err } @@ -383,7 +442,7 @@ func (r ServerPage) IsEmpty() (bool, error) { // NextPageURL uses the response's embedded link reference to navigate to the // next page of results. -func (r ServerPage) NextPageURL() (string, error) { +func (r ServerPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"servers_links"` } diff --git a/openstack/compute/v2/servers/testing/fixtures_test.go b/openstack/compute/v2/servers/testing/fixtures_test.go index 2ce6282b02..206e20a53c 100644 --- a/openstack/compute/v2/servers/testing/fixtures_test.go +++ b/openstack/compute/v2/servers/testing/fixtures_test.go @@ -158,7 +158,8 @@ const ServerListBody = ` "progress": 0, "OS-EXT-STS:power_state": 1, "config_drive": "", - "metadata": {} + "metadata": {}, + "locked": true }, { "status": "ACTIVE", @@ -297,7 +298,8 @@ const SingleServerBody = ` "progress": 0, "OS-EXT-STS:power_state": 1, "config_drive": "", - "metadata": {} + "metadata": {}, + "locked": true } } ` @@ -631,6 +633,7 @@ var ( TerminatedAt: time.Time{}, DiskConfig: servers.Manual, AvailabilityZone: "nova", + Locked: func() *bool { b := true; return &b }(), } ConsoleOutput = "abc" @@ -726,8 +729,8 @@ func (opts CreateOptsWithCustomField) ToServerCreateMap() (map[string]any, error // HandleServerNoNetworkCreationSuccessfully sets up the test server with no // network to respond to a server creation request with a given response. -func HandleServerNoNetworkCreationSuccessfully(t *testing.T, response string) { - th.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { +func HandleServerNoNetworkCreationSuccessfully(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -741,14 +744,14 @@ func HandleServerNoNetworkCreationSuccessfully(t *testing.T, response string) { w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) } // HandleServerCreationSuccessfully sets up the test server to respond to a server creation request // with a given response. -func HandleServerCreationSuccessfully(t *testing.T, response string) { - th.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { +func HandleServerCreationSuccessfully(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -761,10 +764,10 @@ func HandleServerCreationSuccessfully(t *testing.T, response string) { w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) - th.Mux.HandleFunc("/images/detail", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/images/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -775,7 +778,7 @@ func HandleServerCreationSuccessfully(t *testing.T, response string) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "images": [ { @@ -804,13 +807,13 @@ func HandleServerCreationSuccessfully(t *testing.T, response string) { } `) case "2": - fmt.Fprintf(w, `{ "images": [] }`) + fmt.Fprint(w, `{ "images": [] }`) default: t.Fatalf("Unexpected marker: [%s]", marker) } }) - th.Mux.HandleFunc("/flavors/detail", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/flavors/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -848,9 +851,9 @@ func HandleServerCreationSuccessfully(t *testing.T, response string) { } ] } - `, th.Server.URL) + `, fakeServer.Server.URL) case "2": - fmt.Fprintf(w, `{ "flavors": [] }`) + fmt.Fprint(w, `{ "flavors": [] }`) default: t.Fatalf("Unexpected marker: [%s]", marker) } @@ -859,8 +862,8 @@ func HandleServerCreationSuccessfully(t *testing.T, response string) { // HandleServerCreationWithCustomFieldSuccessfully sets up the test server to respond to a server creation request // with a given response. -func HandleServersCreationSuccessfully(t *testing.T, response string) { - th.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { +func HandleServersCreationSuccessfully(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -875,14 +878,14 @@ func HandleServersCreationSuccessfully(t *testing.T, response string) { w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) } // HandleServerCreationWithCustomFieldSuccessfully sets up the test server to respond to a server creation request // with a given response. -func HandleServerCreationWithCustomFieldSuccessfully(t *testing.T, response string) { - th.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { +func HandleServerCreationWithCustomFieldSuccessfully(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -896,12 +899,12 @@ func HandleServerCreationWithCustomFieldSuccessfully(t *testing.T, response stri w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) } -func HandleServerCreationWithHostname(t *testing.T, response string) { - th.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { +func HandleServerCreationWithHostname(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -915,14 +918,14 @@ func HandleServerCreationWithHostname(t *testing.T, response string) { w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) } // HandleServerCreationWithUserdata sets up the test server to respond to a server creation request // with a given response. -func HandleServerCreationWithUserdata(t *testing.T, response string) { - th.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { +func HandleServerCreationWithUserdata(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -936,14 +939,14 @@ func HandleServerCreationWithUserdata(t *testing.T, response string) { w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) } // HandleServerCreationWithMetadata sets up the test server to respond to a server creation request // with a given response. -func HandleServerCreationWithMetadata(t *testing.T, response string) { - th.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { +func HandleServerCreationWithMetadata(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -959,13 +962,13 @@ func HandleServerCreationWithMetadata(t *testing.T, response string) { w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) } // HandleServerListSimpleSuccessfully sets up the test server to respond to a server List request. -func HandleServerListSimpleSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { +func HandleServerListSimpleSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -976,9 +979,9 @@ func HandleServerListSimpleSuccessfully(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, ServerListBody) + fmt.Fprint(w, ServerListBody) case "9e5476bd-a4ec-4653-93d6-72c93aa682ba": - fmt.Fprintf(w, `{ "servers": [] }`) + fmt.Fprint(w, `{ "servers": [] }`) default: t.Fatalf("/servers invoked with unexpected marker=[%s]", marker) } @@ -986,8 +989,8 @@ func HandleServerListSimpleSuccessfully(t *testing.T) { } // HandleServerListSuccessfully sets up the test server to respond to a server detail List request. -func HandleServerListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/detail", func(w http.ResponseWriter, r *http.Request) { +func HandleServerListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -998,9 +1001,9 @@ func HandleServerListSuccessfully(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, ServerListBody) + fmt.Fprint(w, ServerListBody) case "9e5476bd-a4ec-4653-93d6-72c93aa682ba": - fmt.Fprintf(w, `{ "servers": [] }`) + fmt.Fprint(w, `{ "servers": [] }`) default: t.Fatalf("/servers/detail invoked with unexpected marker=[%s]", marker) } @@ -1008,8 +1011,8 @@ func HandleServerListSuccessfully(t *testing.T) { } // HandleServerDeletionSuccessfully sets up the test server to respond to a server deletion request. -func HandleServerDeletionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/asdfasdfasdf", func(w http.ResponseWriter, r *http.Request) { +func HandleServerDeletionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/asdfasdfasdf", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -1019,8 +1022,8 @@ func HandleServerDeletionSuccessfully(t *testing.T) { // HandleServerForceDeletionSuccessfully sets up the test server to respond to a server force deletion // request. -func HandleServerForceDeletionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/asdfasdfasdf/action", func(w http.ResponseWriter, r *http.Request) { +func HandleServerForceDeletionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/asdfasdfasdf/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ "forceDelete": "" }`) @@ -1030,45 +1033,45 @@ func HandleServerForceDeletionSuccessfully(t *testing.T) { } // HandleServerGetSuccessfully sets up the test server to respond to a server Get request. -func HandleServerGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/1234asdf", func(w http.ResponseWriter, r *http.Request) { +func HandleServerGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/1234asdf", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleServerBody) + fmt.Fprint(w, SingleServerBody) }) } // HandleServerGetFaultSuccessfully sets up the test server to respond to a server Get // request which contains a fault. -func HandleServerGetFaultSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/1234asdf", func(w http.ResponseWriter, r *http.Request) { +func HandleServerGetFaultSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/1234asdf", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, FaultyServerBody) + fmt.Fprint(w, FaultyServerBody) }) } // HandleServerUpdateSuccessfully sets up the test server to respond to a server Update request. -func HandleServerUpdateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/1234asdf", func(w http.ResponseWriter, r *http.Request) { +func HandleServerUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/1234asdf", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "Content-Type", "application/json") th.TestJSONRequest(t, r, `{ "server": { "name": "new-name" } }`) - fmt.Fprintf(w, SingleServerBody) + fmt.Fprint(w, SingleServerBody) }) } // HandleAdminPasswordChangeSuccessfully sets up the test server to respond to a server password // change request. -func HandleAdminPasswordChangeSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) { +func HandleAdminPasswordChangeSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ "changePassword": { "adminPass": "new-password" } }`) @@ -1078,8 +1081,8 @@ func HandleAdminPasswordChangeSuccessfully(t *testing.T) { } // HandleRebootSuccessfully sets up the test server to respond to a reboot request with success. -func HandleRebootSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) { +func HandleRebootSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ "reboot": { "type": "SOFT" } }`) @@ -1089,21 +1092,21 @@ func HandleRebootSuccessfully(t *testing.T) { } // HandleShowConsoleOutputSuccessfully sets up the test server to respond to a os-getConsoleOutput request with success. -func HandleShowConsoleOutputSuccessfully(t *testing.T, response string) { - th.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) { +func HandleShowConsoleOutputSuccessfully(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ "os-getConsoleOutput": { "length": 50 } }`) w.WriteHeader(http.StatusOK) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) } // HandleRebuildSuccessfully sets up the test server to respond to a rebuild request with success. -func HandleRebuildSuccessfully(t *testing.T, response string) { - th.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) { +func HandleRebuildSuccessfully(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, ` @@ -1119,13 +1122,13 @@ func HandleRebuildSuccessfully(t *testing.T, response string) { w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) } // HandleMetadatumGetSuccessfully sets up the test server to respond to a metadatum Get request. -func HandleMetadatumGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/1234asdf/metadata/foo", func(w http.ResponseWriter, r *http.Request) { +func HandleMetadatumGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/1234asdf/metadata/foo", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") @@ -1138,8 +1141,8 @@ func HandleMetadatumGetSuccessfully(t *testing.T) { } // HandleMetadatumCreateSuccessfully sets up the test server to respond to a metadatum Create request. -func HandleMetadatumCreateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/1234asdf/metadata/foo", func(w http.ResponseWriter, r *http.Request) { +func HandleMetadatumCreateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/1234asdf/metadata/foo", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -1156,8 +1159,8 @@ func HandleMetadatumCreateSuccessfully(t *testing.T) { } // HandleMetadatumDeleteSuccessfully sets up the test server to respond to a metadatum Delete request. -func HandleMetadatumDeleteSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/1234asdf/metadata/foo", func(w http.ResponseWriter, r *http.Request) { +func HandleMetadatumDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/1234asdf/metadata/foo", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -1166,8 +1169,8 @@ func HandleMetadatumDeleteSuccessfully(t *testing.T) { } // HandleMetadataGetSuccessfully sets up the test server to respond to a metadata Get request. -func HandleMetadataGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/1234asdf/metadata", func(w http.ResponseWriter, r *http.Request) { +func HandleMetadataGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/1234asdf/metadata", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") @@ -1179,8 +1182,8 @@ func HandleMetadataGetSuccessfully(t *testing.T) { } // HandleMetadataResetSuccessfully sets up the test server to respond to a metadata Create request. -func HandleMetadataResetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/1234asdf/metadata", func(w http.ResponseWriter, r *http.Request) { +func HandleMetadataResetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/1234asdf/metadata", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -1198,8 +1201,8 @@ func HandleMetadataResetSuccessfully(t *testing.T) { } // HandleMetadataUpdateSuccessfully sets up the test server to respond to a metadata Update request. -func HandleMetadataUpdateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/1234asdf/metadata", func(w http.ResponseWriter, r *http.Request) { +func HandleMetadataUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/1234asdf/metadata", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -1237,13 +1240,13 @@ var ListAddressesExpected = map[string][]servers.Address{ } // HandleAddressListSuccessfully sets up the test server to respond to a ListAddresses request. -func HandleAddressListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/asdfasdfasdf/ips", func(w http.ResponseWriter, r *http.Request) { +func HandleAddressListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/asdfasdfasdf/ips", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "addresses": { "public": [ { @@ -1279,13 +1282,13 @@ var ListNetworkAddressesExpected = []servers.Address{ } // HandleNetworkAddressListSuccessfully sets up the test server to respond to a ListAddressesByNetwork request. -func HandleNetworkAddressListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/asdfasdfasdf/ips/public", func(w http.ResponseWriter, r *http.Request) { +func HandleNetworkAddressListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/asdfasdfasdf/ips/public", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "public": [ { "version": 4, @@ -1300,31 +1303,56 @@ func HandleNetworkAddressListSuccessfully(t *testing.T) { }) } -// HandleCreateServerImageSuccessfully sets up the test server to respond to a TestCreateServerImage request. -func HandleCreateServerImageSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/serverimage/action", func(w http.ResponseWriter, r *http.Request) { +// HandleCreateServerImageSuccessfullyBeforeMicroversion_2_45 sets up the test server to respond to a TestCreateServerImageBeforeMicroversion_2_45 request. +func HandleCreateServerImageSuccessfullyBeforeMicroversion_2_45(t *testing.T, fakeServer th.FakeServer) string { + imageID := "xxxx-xxxxx-xxxxx-xxxx" + + fakeServer.Mux.HandleFunc("/servers/serverimage/action", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Set("Location", fmt.Sprintf("https://0.0.0.0/images/%s", imageID)) + w.Header().Set("X-OpenStack-Nova-API-Version", "2.44") + w.WriteHeader(http.StatusAccepted) + }) + + return imageID +} + +// HandleCreateServerImageSuccessfullySinceMicroversion_2_45 sets up the test server to respond to a TestCreateServerImageSinceMicroversion_2_45 request. +func HandleCreateServerImageSuccessfullySinceMicroversion_2_45(t *testing.T, fakeServer th.FakeServer) string { + imageID := "yyyy-yyyyy-yyyyy-yyyyy" + + fakeServer.Mux.HandleFunc("/servers/serverimage/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) - w.Header().Add("Location", "https://0.0.0.0/images/xxxx-xxxxx-xxxxx-xxxx") + + w.Header().Set("Content-Type", "application/json") + w.Header().Set("X-OpenStack-Nova-API-Version", "2.45") w.WriteHeader(http.StatusAccepted) + + _, err := w.Write(fmt.Appendf(nil, `{"image_id":"%s"}`, imageID)) + th.AssertNoErr(t, err) }) + + return imageID } // HandlePasswordGetSuccessfully sets up the test server to respond to a password Get request. -func HandlePasswordGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/1234asdf/os-server-password", func(w http.ResponseWriter, r *http.Request) { +func HandlePasswordGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/1234asdf/os-server-password", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, ServerPasswordBody) + fmt.Fprint(w, ServerPasswordBody) }) } // HandleServerWithTagsCreationSuccessfully sets up the test server to respond // to a server creation request with a given response. -func HandleServerWithTagsCreationSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { +func HandleServerWithTagsCreationSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, ServerWithTagsCreateRequest) @@ -1332,6 +1360,20 @@ func HandleServerWithTagsCreationSuccessfully(t *testing.T) { w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, SingleServerWithTagsBody) + fmt.Fprint(w, SingleServerWithTagsBody) + }) +} + +// HandleServerHostnameUpdateSuccessfully sets up the test server to respond to a server update +// request changing the hostname. +func HandleServerHostnameUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/1234asdf", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, `{ "server": { "hostname": "new-hostname" } }`) + + fmt.Fprint(w, SingleServerBody) }) } diff --git a/openstack/compute/v2/servers/testing/requests_test.go b/openstack/compute/v2/servers/testing/requests_test.go index b6ebefce5e..7127a09a38 100644 --- a/openstack/compute/v2/servers/testing/requests_test.go +++ b/openstack/compute/v2/servers/testing/requests_test.go @@ -7,6 +7,7 @@ import ( "net/http" "testing" + "github.com/gophercloud/gophercloud/v2/internal/ptr" "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" @@ -14,12 +15,12 @@ import ( ) func TestListServers(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleServerListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleServerListSuccessfully(t, fakeServer) pages := 0 - err := servers.List(client.ServiceClient(), servers.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := servers.List(client.ServiceClient(fakeServer), servers.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := servers.ExtractServers(page) @@ -45,11 +46,11 @@ func TestListServers(t *testing.T) { } func TestListAllServers(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleServerListSimpleSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleServerListSimpleSuccessfully(t, fakeServer) - allPages, err := servers.ListSimple(client.ServiceClient(), servers.ListOpts{}).AllPages(context.TODO()) + allPages, err := servers.ListSimple(client.ServiceClient(fakeServer), servers.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := servers.ExtractServers(allPages) th.AssertNoErr(t, err) @@ -58,11 +59,11 @@ func TestListAllServers(t *testing.T) { } func TestListAllServersWithExtensions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleServerListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleServerListSuccessfully(t, fakeServer) - allPages, err := servers.List(client.ServiceClient(), servers.ListOpts{}).AllPages(context.TODO()) + allPages, err := servers.List(client.ServiceClient(fakeServer), servers.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) var actual []servers.Server @@ -77,11 +78,11 @@ func TestListAllServersWithExtensions(t *testing.T) { } func TestCreateServer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleServerCreationSuccessfully(t, SingleServerBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleServerCreationSuccessfully(t, fakeServer, SingleServerBody) - actual, err := servers.Create(context.TODO(), client.ServiceClient(), servers.CreateOpts{ + actual, err := servers.Create(context.TODO(), client.ServiceClient(fakeServer), servers.CreateOpts{ Name: "derp", ImageRef: "f90f6034-2570-4974-8351-6b49732ef2eb", FlavorRef: "1", @@ -92,11 +93,11 @@ func TestCreateServer(t *testing.T) { } func TestCreateServerNoNetwork(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleServerNoNetworkCreationSuccessfully(t, SingleServerBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleServerNoNetworkCreationSuccessfully(t, fakeServer, SingleServerBody) - actual, err := servers.Create(context.TODO(), client.ServiceClient(), servers.CreateOpts{ + actual, err := servers.Create(context.TODO(), client.ServiceClient(fakeServer), servers.CreateOpts{ Name: "derp", ImageRef: "f90f6034-2570-4974-8351-6b49732ef2eb", FlavorRef: "1", @@ -108,11 +109,11 @@ func TestCreateServerNoNetwork(t *testing.T) { } func TestCreateServers(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleServersCreationSuccessfully(t, SingleServerBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleServersCreationSuccessfully(t, fakeServer, SingleServerBody) - actual, err := servers.Create(context.TODO(), client.ServiceClient(), servers.CreateOpts{ + actual, err := servers.Create(context.TODO(), client.ServiceClient(fakeServer), servers.CreateOpts{ Name: "derp", ImageRef: "f90f6034-2570-4974-8351-6b49732ef2eb", FlavorRef: "1", @@ -125,11 +126,11 @@ func TestCreateServers(t *testing.T) { } func TestCreateServerWithCustomField(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleServerCreationWithCustomFieldSuccessfully(t, SingleServerBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleServerCreationWithCustomFieldSuccessfully(t, fakeServer, SingleServerBody) - actual, err := servers.Create(context.TODO(), client.ServiceClient(), CreateOptsWithCustomField{ + actual, err := servers.Create(context.TODO(), client.ServiceClient(fakeServer), CreateOptsWithCustomField{ CreateOpts: servers.CreateOpts{ Name: "derp", ImageRef: "f90f6034-2570-4974-8351-6b49732ef2eb", @@ -143,11 +144,11 @@ func TestCreateServerWithCustomField(t *testing.T) { } func TestCreateServerWithMetadata(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleServerCreationWithMetadata(t, SingleServerBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleServerCreationWithMetadata(t, fakeServer, SingleServerBody) - actual, err := servers.Create(context.TODO(), client.ServiceClient(), servers.CreateOpts{ + actual, err := servers.Create(context.TODO(), client.ServiceClient(fakeServer), servers.CreateOpts{ Name: "derp", ImageRef: "f90f6034-2570-4974-8351-6b49732ef2eb", FlavorRef: "1", @@ -161,11 +162,11 @@ func TestCreateServerWithMetadata(t *testing.T) { } func TestCreateServerWithUserdataString(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleServerCreationWithUserdata(t, SingleServerBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleServerCreationWithUserdata(t, fakeServer, SingleServerBody) - actual, err := servers.Create(context.TODO(), client.ServiceClient(), servers.CreateOpts{ + actual, err := servers.Create(context.TODO(), client.ServiceClient(fakeServer), servers.CreateOpts{ Name: "derp", ImageRef: "f90f6034-2570-4974-8351-6b49732ef2eb", FlavorRef: "1", @@ -177,13 +178,13 @@ func TestCreateServerWithUserdataString(t *testing.T) { } func TestCreateServerWithUserdataEncoded(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleServerCreationWithUserdata(t, SingleServerBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleServerCreationWithUserdata(t, fakeServer, SingleServerBody) encoded := base64.StdEncoding.EncodeToString([]byte("userdata string")) - actual, err := servers.Create(context.TODO(), client.ServiceClient(), servers.CreateOpts{ + actual, err := servers.Create(context.TODO(), client.ServiceClient(fakeServer), servers.CreateOpts{ Name: "derp", ImageRef: "f90f6034-2570-4974-8351-6b49732ef2eb", FlavorRef: "1", @@ -195,11 +196,11 @@ func TestCreateServerWithUserdataEncoded(t *testing.T) { } func TestCreateServerWithHostname(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleServerCreationWithHostname(t, SingleServerBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleServerCreationWithHostname(t, fakeServer, SingleServerBody) - actual, err := servers.Create(context.TODO(), client.ServiceClient(), servers.CreateOpts{ + actual, err := servers.Create(context.TODO(), client.ServiceClient(fakeServer), servers.CreateOpts{ Name: "derp", ImageRef: "f90f6034-2570-4974-8351-6b49732ef2eb", FlavorRef: "1", @@ -730,29 +731,29 @@ func TestCreateComplexSchedulerHints(t *testing.T) { } func TestDeleteServer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleServerDeletionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleServerDeletionSuccessfully(t, fakeServer) - res := servers.Delete(context.TODO(), client.ServiceClient(), "asdfasdfasdf") + res := servers.Delete(context.TODO(), client.ServiceClient(fakeServer), "asdfasdfasdf") th.AssertNoErr(t, res.Err) } func TestForceDeleteServer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleServerForceDeletionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleServerForceDeletionSuccessfully(t, fakeServer) - res := servers.ForceDelete(context.TODO(), client.ServiceClient(), "asdfasdfasdf") + res := servers.ForceDelete(context.TODO(), client.ServiceClient(fakeServer), "asdfasdfasdf") th.AssertNoErr(t, res.Err) } func TestGetServer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleServerGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleServerGetSuccessfully(t, fakeServer) - client := client.ServiceClient() + client := client.ServiceClient(fakeServer) actual, err := servers.Get(context.TODO(), client, "1234asdf").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) @@ -762,11 +763,11 @@ func TestGetServer(t *testing.T) { } func TestGetFaultyServer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleServerGetFaultSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleServerGetFaultSuccessfully(t, fakeServer) - client := client.ServiceClient() + client := client.ServiceClient(fakeServer) actual, err := servers.Get(context.TODO(), client, "1234asdf").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) @@ -774,19 +775,20 @@ func TestGetFaultyServer(t *testing.T) { FaultyServer := ServerDerp FaultyServer.Fault = DerpFault + FaultyServer.Locked = nil th.CheckDeepEquals(t, FaultyServer, *actual) } func TestGetServerWithExtensions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleServerGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleServerGetSuccessfully(t, fakeServer) var s struct { servers.Server } - client := client.ServiceClient() + client := client.ServiceClient(fakeServer) err := servers.Get(context.TODO(), client, "1234asdf").ExtractInto(&s) th.AssertNoErr(t, err) th.AssertEquals(t, "nova", s.AvailabilityZone) @@ -802,12 +804,12 @@ func TestGetServerWithExtensions(t *testing.T) { } func TestUpdateServer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleServerUpdateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleServerUpdateSuccessfully(t, fakeServer) - client := client.ServiceClient() - actual, err := servers.Update(context.TODO(), client, "1234asdf", servers.UpdateOpts{Name: "new-name"}).Extract() + client := client.ServiceClient(fakeServer) + actual, err := servers.Update(context.TODO(), client, "1234asdf", servers.UpdateOpts{Name: ptr.To("new-name")}).Extract() if err != nil { t.Fatalf("Unexpected Update error: %v", err) } @@ -816,52 +818,52 @@ func TestUpdateServer(t *testing.T) { } func TestChangeServerAdminPassword(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAdminPasswordChangeSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAdminPasswordChangeSuccessfully(t, fakeServer) - res := servers.ChangeAdminPassword(context.TODO(), client.ServiceClient(), "1234asdf", "new-password") + res := servers.ChangeAdminPassword(context.TODO(), client.ServiceClient(fakeServer), "1234asdf", "new-password") th.AssertNoErr(t, res.Err) } func TestShowConsoleOutput(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleShowConsoleOutputSuccessfully(t, ConsoleOutputBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleShowConsoleOutputSuccessfully(t, fakeServer, ConsoleOutputBody) outputOpts := &servers.ShowConsoleOutputOpts{ Length: 50, } - actual, err := servers.ShowConsoleOutput(context.TODO(), client.ServiceClient(), "1234asdf", outputOpts).Extract() + actual, err := servers.ShowConsoleOutput(context.TODO(), client.ServiceClient(fakeServer), "1234asdf", outputOpts).Extract() th.AssertNoErr(t, err) th.AssertByteArrayEquals(t, []byte(ConsoleOutput), []byte(actual)) } func TestGetPassword(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandlePasswordGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePasswordGetSuccessfully(t, fakeServer) - res := servers.GetPassword(context.TODO(), client.ServiceClient(), "1234asdf") + res := servers.GetPassword(context.TODO(), client.ServiceClient(fakeServer), "1234asdf") th.AssertNoErr(t, res.Err) } func TestRebootServer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleRebootSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleRebootSuccessfully(t, fakeServer) - res := servers.Reboot(context.TODO(), client.ServiceClient(), "1234asdf", servers.RebootOpts{ + res := servers.Reboot(context.TODO(), client.ServiceClient(fakeServer), "1234asdf", servers.RebootOpts{ Type: servers.SoftReboot, }) th.AssertNoErr(t, res.Err) } func TestRebuildServer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleRebuildSuccessfully(t, SingleServerBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleRebuildSuccessfully(t, fakeServer, SingleServerBody) opts := servers.RebuildOpts{ Name: "new-name", @@ -870,7 +872,7 @@ func TestRebuildServer(t *testing.T) { AccessIPv4: "1.2.3.4", } - actual, err := servers.Rebuild(context.TODO(), client.ServiceClient(), "1234asdf", opts).Extract() + actual, err := servers.Rebuild(context.TODO(), client.ServiceClient(fakeServer), "1234asdf", opts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, ServerDerp, *actual) @@ -900,10 +902,10 @@ func TestRebuildServerWithDiskConfig(t *testing.T) { } func TestResizeServer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ "resize": { "flavorRef": "2" } }`) @@ -911,7 +913,7 @@ func TestResizeServer(t *testing.T) { w.WriteHeader(http.StatusAccepted) }) - res := servers.Resize(context.TODO(), client.ServiceClient(), "1234asdf", servers.ResizeOpts{FlavorRef: "2"}) + res := servers.Resize(context.TODO(), client.ServiceClient(fakeServer), "1234asdf", servers.ResizeOpts{FlavorRef: "2"}) th.AssertNoErr(t, res.Err) } @@ -935,10 +937,10 @@ func TestResizeServerWithDiskConfig(t *testing.T) { } func TestConfirmResize(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ "confirmResize": null }`) @@ -946,15 +948,15 @@ func TestConfirmResize(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - res := servers.ConfirmResize(context.TODO(), client.ServiceClient(), "1234asdf") + res := servers.ConfirmResize(context.TODO(), client.ServiceClient(fakeServer), "1234asdf") th.AssertNoErr(t, res.Err) } func TestRevertResize(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ "revertResize": null }`) @@ -962,64 +964,64 @@ func TestRevertResize(t *testing.T) { w.WriteHeader(http.StatusAccepted) }) - res := servers.RevertResize(context.TODO(), client.ServiceClient(), "1234asdf") + res := servers.RevertResize(context.TODO(), client.ServiceClient(fakeServer), "1234asdf") th.AssertNoErr(t, res.Err) } func TestGetMetadatum(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleMetadatumGetSuccessfully(t) + HandleMetadatumGetSuccessfully(t, fakeServer) expected := map[string]string{"foo": "bar"} - actual, err := servers.Metadatum(context.TODO(), client.ServiceClient(), "1234asdf", "foo").Extract() + actual, err := servers.Metadatum(context.TODO(), client.ServiceClient(fakeServer), "1234asdf", "foo").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, expected, actual) } func TestCreateMetadatum(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleMetadatumCreateSuccessfully(t) + HandleMetadatumCreateSuccessfully(t, fakeServer) expected := map[string]string{"foo": "bar"} - actual, err := servers.CreateMetadatum(context.TODO(), client.ServiceClient(), "1234asdf", servers.MetadatumOpts{"foo": "bar"}).Extract() + actual, err := servers.CreateMetadatum(context.TODO(), client.ServiceClient(fakeServer), "1234asdf", servers.MetadatumOpts{"foo": "bar"}).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, expected, actual) } func TestDeleteMetadatum(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleMetadatumDeleteSuccessfully(t) + HandleMetadatumDeleteSuccessfully(t, fakeServer) - err := servers.DeleteMetadatum(context.TODO(), client.ServiceClient(), "1234asdf", "foo").ExtractErr() + err := servers.DeleteMetadatum(context.TODO(), client.ServiceClient(fakeServer), "1234asdf", "foo").ExtractErr() th.AssertNoErr(t, err) } func TestGetMetadata(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleMetadataGetSuccessfully(t) + HandleMetadataGetSuccessfully(t, fakeServer) expected := map[string]string{"foo": "bar", "this": "that"} - actual, err := servers.Metadata(context.TODO(), client.ServiceClient(), "1234asdf").Extract() + actual, err := servers.Metadata(context.TODO(), client.ServiceClient(fakeServer), "1234asdf").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, expected, actual) } func TestResetMetadata(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleMetadataResetSuccessfully(t) + HandleMetadataResetSuccessfully(t, fakeServer) expected := map[string]string{"foo": "bar", "this": "that"} - actual, err := servers.ResetMetadata(context.TODO(), client.ServiceClient(), "1234asdf", servers.MetadataOpts{ + actual, err := servers.ResetMetadata(context.TODO(), client.ServiceClient(fakeServer), "1234asdf", servers.MetadataOpts{ "foo": "bar", "this": "that", }).Extract() @@ -1028,13 +1030,13 @@ func TestResetMetadata(t *testing.T) { } func TestUpdateMetadata(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleMetadataUpdateSuccessfully(t) + HandleMetadataUpdateSuccessfully(t, fakeServer) expected := map[string]string{"foo": "baz", "this": "those"} - actual, err := servers.UpdateMetadata(context.TODO(), client.ServiceClient(), "1234asdf", servers.MetadataOpts{ + actual, err := servers.UpdateMetadata(context.TODO(), client.ServiceClient(fakeServer), "1234asdf", servers.MetadataOpts{ "foo": "baz", "this": "those", }).Extract() @@ -1043,13 +1045,13 @@ func TestUpdateMetadata(t *testing.T) { } func TestListAddresses(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAddressListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAddressListSuccessfully(t, fakeServer) expected := ListAddressesExpected pages := 0 - err := servers.ListAddresses(client.ServiceClient(), "asdfasdfasdf").EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := servers.ListAddresses(client.ServiceClient(fakeServer), "asdfasdfasdf").EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := servers.ExtractAddresses(page) @@ -1067,13 +1069,13 @@ func TestListAddresses(t *testing.T) { } func TestListAddressesByNetwork(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleNetworkAddressListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleNetworkAddressListSuccessfully(t, fakeServer) expected := ListNetworkAddressesExpected pages := 0 - err := servers.ListAddressesByNetwork(client.ServiceClient(), "asdfasdfasdf", "public").EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := servers.ListAddressesByNetwork(client.ServiceClient(fakeServer), "asdfasdfasdf", "public").EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := servers.ExtractNetworkAddresses(page) @@ -1090,13 +1092,24 @@ func TestListAddressesByNetwork(t *testing.T) { th.CheckEquals(t, 1, pages) } -func TestCreateServerImage(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateServerImageSuccessfully(t) +func TestCreateServerImageBeforeMicroversion_2_45(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + expected := HandleCreateServerImageSuccessfullyBeforeMicroversion_2_45(t, fakeServer) - _, err := servers.CreateImage(context.TODO(), client.ServiceClient(), "serverimage", servers.CreateImageOpts{Name: "test"}).ExtractImageID() + imageID, err := servers.CreateImage(context.TODO(), client.ServiceClient(fakeServer), "serverimage", servers.CreateImageOpts{Name: "test"}).ExtractImageID() th.AssertNoErr(t, err) + th.AssertEquals(t, expected, imageID) +} + +func TestCreateServerImageSinceMicroversion_2_45(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + expected := HandleCreateServerImageSuccessfullySinceMicroversion_2_45(t, fakeServer) + + imageID, err := servers.CreateImage(context.TODO(), client.ServiceClient(fakeServer), "serverimage", servers.CreateImageOpts{Name: "test"}).ExtractImageID() + th.AssertNoErr(t, err) + th.AssertEquals(t, expected, imageID) } func TestMarshalPersonality(t *testing.T) { @@ -1135,16 +1148,17 @@ func TestMarshalPersonality(t *testing.T) { } func TestCreateServerWithTags(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleServerWithTagsCreationSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleServerWithTagsCreationSuccessfully(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) c.Microversion = "2.52" tags := []string{"foo", "bar"} ServerDerpTags := ServerDerp ServerDerpTags.Tags = &tags + ServerDerpTags.Locked = nil createOpts := servers.CreateOpts{ Name: "derp", @@ -1158,3 +1172,39 @@ func TestCreateServerWithTags(t *testing.T) { th.AssertNoErr(t, err) th.CheckDeepEquals(t, ServerDerpTags, *actualServer) } + +func TestCreateServerWithHypervisorHostname(t *testing.T) { + opts := servers.CreateOpts{ + Name: "createdserver", + FlavorRef: "performance1-1", + ImageRef: "asdfasdfasdf", + HypervisorHostname: "test-hypervisor", + } + expected := ` + { + "server": { + "name":"createdserver", + "flavorRef":"performance1-1", + "imageRef":"asdfasdfasdf", + "hypervisor_hostname":"test-hypervisor" + } + } + ` + actual, err := opts.ToServerCreateMap() + th.AssertNoErr(t, err) + th.CheckJSONEquals(t, expected, actual) +} + +func TestUpdateServerHostname(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleServerHostnameUpdateSuccessfully(t, fakeServer) + + client := client.ServiceClient(fakeServer) + actual, err := servers.Update(context.TODO(), client, "1234asdf", servers.UpdateOpts{Hostname: ptr.To("new-hostname")}).Extract() + if err != nil { + t.Fatalf("Unexpected Update error: %v", err) + } + + th.CheckDeepEquals(t, ServerDerp, *actual) +} diff --git a/openstack/compute/v2/servers/testing/results_test.go b/openstack/compute/v2/servers/testing/results_test.go index eb751341fa..5c7711bdb3 100644 --- a/openstack/compute/v2/servers/testing/results_test.go +++ b/openstack/compute/v2/servers/testing/results_test.go @@ -26,7 +26,7 @@ func TestExtractPassword_no_pwd_data(t *testing.T) { pwd, err := resp.ExtractPassword(nil) th.AssertNoErr(t, err) - th.AssertEquals(t, pwd, "") + th.AssertEquals(t, "", pwd) } // Ok - return encrypted password when no private key is given. @@ -102,11 +102,11 @@ KSde3I0ybDz7iS2EtceKB7m4C0slYd+oBkm4efuF00rCOKDwpFq45m0= } func TestListAddressesAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAddressListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAddressListSuccessfully(t, fakeServer) - allPages, err := servers.ListAddresses(client.ServiceClient(), "asdfasdfasdf").AllPages(context.TODO()) + allPages, err := servers.ListAddresses(client.ServiceClient(fakeServer), "asdfasdfasdf").AllPages(context.TODO()) th.AssertNoErr(t, err) _, err = servers.ExtractAddresses(allPages) th.AssertNoErr(t, err) diff --git a/openstack/compute/v2/services/requests.go b/openstack/compute/v2/services/requests.go index 7f6ac1d6d8..6fce69eb15 100644 --- a/openstack/compute/v2/services/requests.go +++ b/openstack/compute/v2/services/requests.go @@ -51,6 +51,12 @@ const ( ServiceDisabled ServiceStatus = "disabled" ) +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToServiceUpdateMap() (map[string]any, error) +} + // UpdateOpts specifies the base attributes that may be updated on a service. type UpdateOpts struct { // Status represents the new service status. One of enabled or disabled. @@ -61,7 +67,7 @@ type UpdateOpts struct { // ForcedDown is a manual override to tell nova that the service in question // has been fenced manually by the operations team. - ForcedDown bool `json:"forced_down,omitempty"` + ForcedDown *bool `json:"forced_down,omitempty"` } // ToServiceUpdateMap formats an UpdateOpts structure into a request body. @@ -70,7 +76,7 @@ func (opts UpdateOpts) ToServiceUpdateMap() (map[string]any, error) { } // Update requests that various attributes of the indicated service be changed. -func Update(ctx context.Context, client *gophercloud.ServiceClient, id string, opts UpdateOpts) (r UpdateResult) { +func Update(ctx context.Context, client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { b, err := opts.ToServiceUpdateMap() if err != nil { r.Err = err diff --git a/openstack/compute/v2/services/testing/fixtures_test.go b/openstack/compute/v2/services/testing/fixtures_test.go index 2104dc08cb..2a17d7d120 100644 --- a/openstack/compute/v2/services/testing/fixtures_test.go +++ b/openstack/compute/v2/services/testing/fixtures_test.go @@ -238,6 +238,23 @@ const ServiceUpdate = ` } ` +const ServiceUpdateForceDown = ` +{ + "service": + { + "id": 1, + "binary": "nova-scheduler", + "disabled_reason": "test1", + "host": "host1", + "state": "up", + "status": "disabled", + "updated_at": "2012-10-29T13:42:02.000000", + "forced_down": true, + "zone": "internal" + } +} +` + // FakeServiceUpdateBody represents the updated service var FakeServiceUpdateBody = services.Service{ Binary: "nova-scheduler", @@ -251,48 +268,88 @@ var FakeServiceUpdateBody = services.Service{ Zone: "internal", } +var FakeServiceUpdateForceDownBody = services.Service{ + Binary: "nova-scheduler", + DisabledReason: "test1", + ForcedDown: true, + Host: "host1", + ID: "1", + State: "up", + Status: "disabled", + UpdatedAt: time.Date(2012, 10, 29, 13, 42, 2, 0, time.UTC), + Zone: "internal", +} + // HandleListPre253Successfully configures the test server to respond to a List // request to a Compute server API pre 2.53 microversion release. -func HandleListPre253Successfully(t *testing.T) { - th.Mux.HandleFunc("/os-services", func(w http.ResponseWriter, r *http.Request) { +func HandleListPre253Successfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-services", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ServiceListBodyPre253) + fmt.Fprint(w, ServiceListBodyPre253) }) } // HandleListSuccessfully configures the test server to respond to a List // request to a Compute server with Pike+ release. -func HandleListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-services", func(w http.ResponseWriter, r *http.Request) { +func HandleListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-services", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ServiceListBody) + fmt.Fprint(w, ServiceListBody) }) } // HandleUpdateSuccessfully configures the test server to respond to a Update // request to a Compute server with Pike+ release. -func HandleUpdateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-services/fake-service-id", func(w http.ResponseWriter, r *http.Request) { +func HandleUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-services/fake-service-id", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "Content-Type", "application/json") th.TestJSONRequest(t, r, `{"status": "disabled"}`) - fmt.Fprintf(w, ServiceUpdate) + fmt.Fprint(w, ServiceUpdate) + }) +} + +// HandleForceDownSuccessfully configures the test server to respond to a Update +// request to a Compute server with Pike+ release. +func HandleForceDownSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-services/fake-service-id", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, `{"forced_down": true}`) + + fmt.Fprint(w, ServiceUpdateForceDown) + }) +} + +// HandleDisableForceDownSuccessfully configures the test server to respond to a Update +// request to a Compute server with Pike+ release. +func HandleDisableForceDownSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-services/fake-service-id", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, `{"forced_down": false}`) + + fmt.Fprint(w, ServiceUpdate) }) } // HandleDeleteSuccessfully configures the test server to respond to a Delete // request to a Compute server with Pike+ release. -func HandleDeleteSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-services/fake-service-id", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-services/fake-service-id", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) diff --git a/openstack/compute/v2/services/testing/requests_test.go b/openstack/compute/v2/services/testing/requests_test.go index c201c9f4d6..d92d7d209e 100644 --- a/openstack/compute/v2/services/testing/requests_test.go +++ b/openstack/compute/v2/services/testing/requests_test.go @@ -6,17 +6,17 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/services" "github.com/gophercloud/gophercloud/v2/pagination" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestListServicesPre253(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - HandleListPre253Successfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListPre253Successfully(t, fakeServer) pages := 0 - err := services.List(client.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := services.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := services.ExtractServices(page) @@ -27,15 +27,15 @@ func TestListServicesPre253(t *testing.T) { if len(actual) != 4 { t.Fatalf("Expected 4 services, got %d", len(actual)) } - testhelper.CheckDeepEquals(t, FirstFakeServicePre253, actual[0]) - testhelper.CheckDeepEquals(t, SecondFakeServicePre253, actual[1]) - testhelper.CheckDeepEquals(t, ThirdFakeServicePre253, actual[2]) - testhelper.CheckDeepEquals(t, FourthFakeServicePre253, actual[3]) + th.CheckDeepEquals(t, FirstFakeServicePre253, actual[0]) + th.CheckDeepEquals(t, SecondFakeServicePre253, actual[1]) + th.CheckDeepEquals(t, ThirdFakeServicePre253, actual[2]) + th.CheckDeepEquals(t, FourthFakeServicePre253, actual[3]) return true, nil }) - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) if pages != 1 { t.Errorf("Expected 1 page, saw %d", pages) @@ -43,16 +43,16 @@ func TestListServicesPre253(t *testing.T) { } func TestListServices(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - HandleListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer) pages := 0 opts := services.ListOpts{ Binary: "fake-binary", Host: "host123", } - err := services.List(client.ServiceClient(), opts).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := services.List(client.ServiceClient(fakeServer), opts).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := services.ExtractServices(page) @@ -63,15 +63,15 @@ func TestListServices(t *testing.T) { if len(actual) != 4 { t.Fatalf("Expected 4 services, got %d", len(actual)) } - testhelper.CheckDeepEquals(t, FirstFakeService, actual[0]) - testhelper.CheckDeepEquals(t, SecondFakeService, actual[1]) - testhelper.CheckDeepEquals(t, ThirdFakeService, actual[2]) - testhelper.CheckDeepEquals(t, FourthFakeService, actual[3]) + th.CheckDeepEquals(t, FirstFakeService, actual[0]) + th.CheckDeepEquals(t, SecondFakeService, actual[1]) + th.CheckDeepEquals(t, ThirdFakeService, actual[2]) + th.CheckDeepEquals(t, FourthFakeService, actual[3]) return true, nil }) - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) if pages != 1 { t.Errorf("Expected 1 page, saw %d", pages) @@ -79,26 +79,56 @@ func TestListServices(t *testing.T) { } func TestUpdateService(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - HandleUpdateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateSuccessfully(t, fakeServer) - client := client.ServiceClient() + client := client.ServiceClient(fakeServer) actual, err := services.Update(context.TODO(), client, "fake-service-id", services.UpdateOpts{Status: services.ServiceDisabled}).Extract() if err != nil { t.Fatalf("Unexpected Update error: %v", err) } - testhelper.CheckDeepEquals(t, FakeServiceUpdateBody, *actual) + th.CheckDeepEquals(t, FakeServiceUpdateBody, *actual) +} + +func TestUpdateServiceForceDown(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleForceDownSuccessfully(t, fakeServer) + + client := client.ServiceClient(fakeServer) + trueVal := true + actual, err := services.Update(context.TODO(), client, "fake-service-id", services.UpdateOpts{ForcedDown: &trueVal}).Extract() + if err != nil { + t.Fatalf("Unexpected Update error: %v", err) + } + + th.CheckDeepEquals(t, FakeServiceUpdateForceDownBody, *actual) +} + +func TestUpdateServiceDisableForceDown(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDisableForceDownSuccessfully(t, fakeServer) + + client := client.ServiceClient(fakeServer) + falseVal := false + actual, err := services.Update(context.TODO(), client, "fake-service-id", services.UpdateOpts{ForcedDown: &falseVal}).Extract() + if err != nil { + t.Fatalf("Unexpected Update error: %v", err) + } + + th.CheckDeepEquals(t, FakeServiceUpdateBody, *actual) } func TestDeleteService(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - HandleDeleteSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteSuccessfully(t, fakeServer) - client := client.ServiceClient() + client := client.ServiceClient(fakeServer) res := services.Delete(context.TODO(), client, "fake-service-id") - testhelper.AssertNoErr(t, res.Err) + th.AssertNoErr(t, res.Err) } diff --git a/openstack/compute/v2/tags/testing/requests_test.go b/openstack/compute/v2/tags/testing/requests_test.go index 03f4260a7d..9f0c344c5b 100644 --- a/openstack/compute/v2/tags/testing/requests_test.go +++ b/openstack/compute/v2/tags/testing/requests_test.go @@ -12,32 +12,32 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/servers/uuid1/tags", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/servers/uuid1/tags", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodGet) th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - _, err := fmt.Fprintf(w, TagsListResponse) + _, err := fmt.Fprint(w, TagsListResponse) th.AssertNoErr(t, err) }) expected := []string{"foo", "bar", "baz"} - actual, err := tags.List(context.TODO(), client.ServiceClient(), "uuid1").Extract() + actual, err := tags.List(context.TODO(), client.ServiceClient(fakeServer), "uuid1").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, expected, actual) } func TestCheckOk(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/servers/uuid1/tags/foo", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/servers/uuid1/tags/foo", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodGet) th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -45,16 +45,16 @@ func TestCheckOk(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - exists, err := tags.Check(context.TODO(), client.ServiceClient(), "uuid1", "foo").Extract() + exists, err := tags.Check(context.TODO(), client.ServiceClient(fakeServer), "uuid1", "foo").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, true, exists) + th.AssertTrue(t, exists) } func TestCheckFail(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/servers/uuid1/tags/bar", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/servers/uuid1/tags/bar", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodGet) th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -62,38 +62,38 @@ func TestCheckFail(t *testing.T) { w.WriteHeader(http.StatusNotFound) }) - exists, err := tags.Check(context.TODO(), client.ServiceClient(), "uuid1", "bar").Extract() + exists, err := tags.Check(context.TODO(), client.ServiceClient(fakeServer), "uuid1", "bar").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, false, exists) + th.AssertFalse(t, exists) } func TestReplaceAll(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/servers/uuid1/tags", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/servers/uuid1/tags", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodPut) th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - _, err := fmt.Fprintf(w, TagsReplaceAllResponse) + _, err := fmt.Fprint(w, TagsReplaceAllResponse) th.AssertNoErr(t, err) }) expected := []string{"tag1", "tag2", "tag3"} - actual, err := tags.ReplaceAll(context.TODO(), client.ServiceClient(), "uuid1", tags.ReplaceAllOpts{Tags: []string{"tag1", "tag2", "tag3"}}).Extract() + actual, err := tags.ReplaceAll(context.TODO(), client.ServiceClient(fakeServer), "uuid1", tags.ReplaceAllOpts{Tags: []string{"tag1", "tag2", "tag3"}}).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, expected, actual) } func TestAddCreated(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/servers/uuid1/tags/foo", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/servers/uuid1/tags/foo", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodPut) th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -101,15 +101,15 @@ func TestAddCreated(t *testing.T) { w.WriteHeader(http.StatusCreated) }) - err := tags.Add(context.TODO(), client.ServiceClient(), "uuid1", "foo").ExtractErr() + err := tags.Add(context.TODO(), client.ServiceClient(fakeServer), "uuid1", "foo").ExtractErr() th.AssertNoErr(t, err) } func TestAddExists(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/servers/uuid1/tags/foo", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/servers/uuid1/tags/foo", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodPut) th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -117,15 +117,15 @@ func TestAddExists(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - err := tags.Add(context.TODO(), client.ServiceClient(), "uuid1", "foo").ExtractErr() + err := tags.Add(context.TODO(), client.ServiceClient(fakeServer), "uuid1", "foo").ExtractErr() th.AssertNoErr(t, err) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/servers/uuid1/tags/foo", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/servers/uuid1/tags/foo", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodDelete) th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -133,15 +133,15 @@ func TestDelete(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - err := tags.Delete(context.TODO(), client.ServiceClient(), "uuid1", "foo").ExtractErr() + err := tags.Delete(context.TODO(), client.ServiceClient(fakeServer), "uuid1", "foo").ExtractErr() th.AssertNoErr(t, err) } func TestDeleteAll(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/servers/uuid1/tags", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/servers/uuid1/tags", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodDelete) th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -149,6 +149,6 @@ func TestDeleteAll(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - err := tags.DeleteAll(context.TODO(), client.ServiceClient(), "uuid1").ExtractErr() + err := tags.DeleteAll(context.TODO(), client.ServiceClient(fakeServer), "uuid1").ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/compute/v2/usage/results.go b/openstack/compute/v2/usage/results.go index 81ed36053e..73492e7c38 100644 --- a/openstack/compute/v2/usage/results.go +++ b/openstack/compute/v2/usage/results.go @@ -132,7 +132,7 @@ func (r SingleTenantPage) IsEmpty() (bool, error) { // NextPageURL uses the response's embedded link reference to navigate to the // next page of results. -func (r SingleTenantPage) NextPageURL() (string, error) { +func (r SingleTenantPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"tenant_usage_links"` } @@ -179,7 +179,7 @@ func (r AllTenantsPage) IsEmpty() (bool, error) { // NextPageURL uses the response's embedded link reference to navigate to the // next page of results. -func (r AllTenantsPage) NextPageURL() (string, error) { +func (r AllTenantsPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"tenant_usages_links"` } diff --git a/openstack/compute/v2/usage/testing/fixtures_test.go b/openstack/compute/v2/usage/testing/fixtures_test.go index f5b92ca095..1922a0ddab 100644 --- a/openstack/compute/v2/usage/testing/fixtures_test.go +++ b/openstack/compute/v2/usage/testing/fixtures_test.go @@ -74,8 +74,8 @@ const GetSingleTenant = `{ // HandleGetSingleTenantSuccessfully configures the test server to respond to a // Get request for a single tenant -func HandleGetSingleTenantSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-simple-tenant-usage/"+FirstTenantID, func(w http.ResponseWriter, r *http.Request) { +func HandleGetSingleTenantSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-simple-tenant-usage/"+FirstTenantID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") @@ -224,8 +224,8 @@ const GetAllTenants = `{ // HandleGetAllTenantsSuccessfully configures the test server to respond to a // Get request for all tenants. -func HandleGetAllTenantsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-simple-tenant-usage", func(w http.ResponseWriter, r *http.Request) { +func HandleGetAllTenantsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-simple-tenant-usage", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") diff --git a/openstack/compute/v2/usage/testing/requests_test.go b/openstack/compute/v2/usage/testing/requests_test.go index 163ab1082d..f2697412b4 100644 --- a/openstack/compute/v2/usage/testing/requests_test.go +++ b/openstack/compute/v2/usage/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestGetTenant(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSingleTenantSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSingleTenantSuccessfully(t, fakeServer) count := 0 - err := usage.SingleTenant(client.ServiceClient(), FirstTenantID, nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := usage.SingleTenant(client.ServiceClient(fakeServer), FirstTenantID, nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := usage.ExtractSingleTenant(page) @@ -26,20 +26,20 @@ func TestGetTenant(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.AssertEquals(t, count, 1) + th.AssertEquals(t, 1, count) } func TestAllTenants(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetAllTenantsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetAllTenantsSuccessfully(t, fakeServer) getOpts := usage.AllTenantsOpts{ Detailed: true, } count := 0 - err := usage.AllTenants(client.ServiceClient(), getOpts).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := usage.AllTenants(client.ServiceClient(fakeServer), getOpts).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := usage.ExtractAllTenants(page) @@ -49,5 +49,5 @@ func TestAllTenants(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.AssertEquals(t, count, 1) + th.AssertEquals(t, 1, count) } diff --git a/openstack/compute/v2/volumeattach/testing/fixtures_test.go b/openstack/compute/v2/volumeattach/testing/fixtures_test.go index c5f0435a96..f1450719b1 100644 --- a/openstack/compute/v2/volumeattach/testing/fixtures_test.go +++ b/openstack/compute/v2/volumeattach/testing/fixtures_test.go @@ -56,32 +56,32 @@ const CreateOutput = ` ` // HandleListSuccessfully configures the test server to respond to a List request. -func HandleListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/4d8c3732-a248-40ed-bebc-539a6ffd25c0/os-volume_attachments", func(w http.ResponseWriter, r *http.Request) { +func HandleListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/4d8c3732-a248-40ed-bebc-539a6ffd25c0/os-volume_attachments", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } // HandleGetSuccessfully configures the test server to respond to a Get request // for an existing attachment -func HandleGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/4d8c3732-a248-40ed-bebc-539a6ffd25c0/os-volume_attachments/a26887c6-c47b-4654-abb5-dfadf7d3f804", func(w http.ResponseWriter, r *http.Request) { +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/4d8c3732-a248-40ed-bebc-539a6ffd25c0/os-volume_attachments/a26887c6-c47b-4654-abb5-dfadf7d3f804", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleCreateSuccessfully configures the test server to respond to a Create request // for a new attachment -func HandleCreateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/4d8c3732-a248-40ed-bebc-539a6ffd25c0/os-volume_attachments", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/4d8c3732-a248-40ed-bebc-539a6ffd25c0/os-volume_attachments", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, ` @@ -96,14 +96,14 @@ func HandleCreateSuccessfully(t *testing.T) { `) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, CreateOutput) + fmt.Fprint(w, CreateOutput) }) } // HandleDeleteSuccessfully configures the test server to respond to a Delete request for a // an existing attachment -func HandleDeleteSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/servers/4d8c3732-a248-40ed-bebc-539a6ffd25c0/os-volume_attachments/a26887c6-c47b-4654-abb5-dfadf7d3f804", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/servers/4d8c3732-a248-40ed-bebc-539a6ffd25c0/os-volume_attachments/a26887c6-c47b-4654-abb5-dfadf7d3f804", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) diff --git a/openstack/compute/v2/volumeattach/testing/requests_test.go b/openstack/compute/v2/volumeattach/testing/requests_test.go index 92da356cb1..21f323a353 100644 --- a/openstack/compute/v2/volumeattach/testing/requests_test.go +++ b/openstack/compute/v2/volumeattach/testing/requests_test.go @@ -44,15 +44,15 @@ var CreatedVolumeAttachment = volumeattach.VolumeAttachment{ } func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleListSuccessfully(t) + HandleListSuccessfully(t, fakeServer) serverID := "4d8c3732-a248-40ed-bebc-539a6ffd25c0" count := 0 - err := volumeattach.List(client.ServiceClient(), serverID).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := volumeattach.List(client.ServiceClient(fakeServer), serverID).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := volumeattach.ExtractVolumeAttachments(page) th.AssertNoErr(t, err) @@ -65,14 +65,14 @@ func TestList(t *testing.T) { } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleCreateSuccessfully(t) + HandleCreateSuccessfully(t, fakeServer) serverID := "4d8c3732-a248-40ed-bebc-539a6ffd25c0" - actual, err := volumeattach.Create(context.TODO(), client.ServiceClient(), serverID, volumeattach.CreateOpts{ + actual, err := volumeattach.Create(context.TODO(), client.ServiceClient(fakeServer), serverID, volumeattach.CreateOpts{ Device: "/dev/vdc", VolumeID: "a26887c6-c47b-4654-abb5-dfadf7d3f804", Tag: iTag, @@ -83,28 +83,28 @@ func TestCreate(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleGetSuccessfully(t) + HandleGetSuccessfully(t, fakeServer) aID := "a26887c6-c47b-4654-abb5-dfadf7d3f804" serverID := "4d8c3732-a248-40ed-bebc-539a6ffd25c0" - actual, err := volumeattach.Get(context.TODO(), client.ServiceClient(), serverID, aID).Extract() + actual, err := volumeattach.Get(context.TODO(), client.ServiceClient(fakeServer), serverID, aID).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &SecondVolumeAttachment, actual) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleDeleteSuccessfully(t) + HandleDeleteSuccessfully(t, fakeServer) aID := "a26887c6-c47b-4654-abb5-dfadf7d3f804" serverID := "4d8c3732-a248-40ed-bebc-539a6ffd25c0" - err := volumeattach.Delete(context.TODO(), client.ServiceClient(), serverID, aID).ExtractErr() + err := volumeattach.Delete(context.TODO(), client.ServiceClient(fakeServer), serverID, aID).ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/config/clouds/clouds.go b/openstack/config/clouds/clouds.go index e2310fbc10..a8eb357f7d 100644 --- a/openstack/config/clouds/clouds.go +++ b/openstack/config/clouds/clouds.go @@ -13,7 +13,7 @@ // panic(err) // } // -// networkClient, err := openstack.NewNetworkV2(providerClient, eo) +// networkClient, err := openstack.NewNetworkV2(ctx, providerClient, eo) // if err != nil { // panic(err) // } @@ -28,7 +28,7 @@ import ( "reflect" "github.com/gophercloud/gophercloud/v2" - "gopkg.in/yaml.v2" + "go.yaml.in/yaml/v3" ) // Parse fetches a clouds.yaml file from disk and returns the parsed @@ -40,7 +40,7 @@ import ( // clouds.yaml, use that location as the only search location for `clouds.yaml` and `secure.yaml`; // - otherwise, the search locations for `clouds.yaml` and `secure.yaml` are: // 1. the current working directory (on Linux: `./`) -// 2. the directory `openstack` under the standatd user config location for +// 2. the directory `openstack` under the standard user config location for // the operating system (on Linux: `${XDG_CONFIG_HOME:-$HOME/.config}/openstack/`) // 3. on Linux, `/etc/openstack/` // @@ -81,9 +81,15 @@ func Parse(opts ...ParseOption) (gophercloud.AuthOptions, gophercloud.EndpointOp if err != nil { return gophercloud.AuthOptions{}, gophercloud.EndpointOpts{}, nil, fmt.Errorf("failed to get the current working directory: %w", err) } - userConfig, err := os.UserConfigDir() - if err != nil { - return gophercloud.AuthOptions{}, gophercloud.EndpointOpts{}, nil, fmt.Errorf("failed to get the user config directory: %w", err) + // Use XDG_CONFIG_HOME or fall back to ~/.config, matching the + // OpenStack convention for clouds.yaml location on all platforms. + userConfig := os.Getenv("XDG_CONFIG_HOME") + if userConfig == "" { + homeDir, err := os.UserHomeDir() + if err != nil { + return gophercloud.AuthOptions{}, gophercloud.EndpointOpts{}, nil, fmt.Errorf("failed to get the user home directory: %w", err) + } + userConfig = path.Join(homeDir, ".config") } options.locations = []string{path.Join(cwd, "clouds.yaml"), path.Join(userConfig, "openstack", "clouds.yaml"), path.Join("/etc", "openstack", "clouds.yaml")} } diff --git a/openstack/config/clouds/clouds_test.go b/openstack/config/clouds/clouds_test.go index 384aea92d8..bd705833af 100644 --- a/openstack/config/clouds/clouds_test.go +++ b/openstack/config/clouds/clouds_test.go @@ -173,6 +173,119 @@ func TestParse(t *testing.T) { } }) + t.Run("uses XDG_CONFIG_HOME for clouds.yaml location", func(t *testing.T) { + const cloudsYAML = `clouds: + gophercloud-test: + auth: + auth_url: https://example.com/xdg-config:13000` + + // Create a temp dir to use as XDG_CONFIG_HOME with clouds.yaml inside. + xdgDir, err := os.MkdirTemp(os.TempDir(), tempDirPrefix) + if err != nil { + t.Fatalf("unable to create a temporary directory: %v", err) + } + defer rmTmpDirOrPanic(xdgDir) + + openstackDir := path.Join(xdgDir, "openstack") + if err := os.MkdirAll(openstackDir, 0755); err != nil { + t.Fatalf("unable to create openstack config directory: %v", err) + } + if err := os.WriteFile(path.Join(openstackDir, "clouds.yaml"), []byte(cloudsYAML), 0644); err != nil { + t.Fatalf("unable to create a mock clouds.yaml file: %v", err) + } + + // Change to an empty temp dir so cwd has no clouds.yaml. + emptyDir, err := os.MkdirTemp(os.TempDir(), tempDirPrefix) + if err != nil { + t.Fatalf("unable to create a temporary directory: %v", err) + } + defer rmTmpDirOrPanic(emptyDir) + + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("unable to determine the current working directory: %v", err) + } + if err := os.Chdir(emptyDir); err != nil { + t.Fatalf("unable to move to a temporary directory: %v", err) + } + defer func() { + if err := os.Chdir(cwd); err != nil { + panic("unable to reset the current working directory: " + err.Error()) + } + }() + + t.Setenv("XDG_CONFIG_HOME", xdgDir) + t.Setenv("OS_CLIENT_CONFIG_FILE", "") + + ao, _, _, err := clouds.Parse( + clouds.WithCloudName("gophercloud-test"), + ) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if got := ao.IdentityEndpoint; got != "https://example.com/xdg-config:13000" { + t.Errorf("unexpected identity endpoint: %q", got) + } + }) + + t.Run("falls back to ~/.config when XDG_CONFIG_HOME is not set", func(t *testing.T) { + const cloudsYAML = `clouds: + gophercloud-test: + auth: + auth_url: https://example.com/home-config:13000` + + // Create a temp dir to use as HOME with clouds.yaml in .config/openstack/. + homeDir, err := os.MkdirTemp(os.TempDir(), tempDirPrefix) + if err != nil { + t.Fatalf("unable to create a temporary directory: %v", err) + } + defer rmTmpDirOrPanic(homeDir) + + openstackDir := path.Join(homeDir, ".config", "openstack") + if err := os.MkdirAll(openstackDir, 0755); err != nil { + t.Fatalf("unable to create openstack config directory: %v", err) + } + if err := os.WriteFile(path.Join(openstackDir, "clouds.yaml"), []byte(cloudsYAML), 0644); err != nil { + t.Fatalf("unable to create a mock clouds.yaml file: %v", err) + } + + // Change to an empty temp dir so cwd has no clouds.yaml. + emptyDir, err := os.MkdirTemp(os.TempDir(), tempDirPrefix) + if err != nil { + t.Fatalf("unable to create a temporary directory: %v", err) + } + defer rmTmpDirOrPanic(emptyDir) + + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("unable to determine the current working directory: %v", err) + } + if err := os.Chdir(emptyDir); err != nil { + t.Fatalf("unable to move to a temporary directory: %v", err) + } + defer func() { + if err := os.Chdir(cwd); err != nil { + panic("unable to reset the current working directory: " + err.Error()) + } + }() + + t.Setenv("XDG_CONFIG_HOME", "") + t.Setenv("HOME", homeDir) + t.Setenv("OS_CLIENT_CONFIG_FILE", "") + + ao, _, _, err := clouds.Parse( + clouds.WithCloudName("gophercloud-test"), + ) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if got := ao.IdentityEndpoint; got != "https://example.com/home-config:13000" { + t.Errorf("unexpected identity endpoint: %q", got) + } + }) + t.Run("falls back to the next location if clouds.yaml is not found", func(t *testing.T) { const cloudsYAML1 = `clouds: gophercloud-test: diff --git a/openstack/container/v1/capsules/results.go b/openstack/container/v1/capsules/results.go index c417bb2038..3ad38512d6 100644 --- a/openstack/container/v1/capsules/results.go +++ b/openstack/container/v1/capsules/results.go @@ -228,7 +228,7 @@ type Address struct { // NextPageURL is invoked when a paginated collection of capsules has reached // the end of a page and the pager seeks to traverse over a new one. In order // to do this, it needs to construct the next page's URL. -func (r CapsulePage) NextPageURL() (string, error) { +func (r CapsulePage) NextPageURL(endpointURL string) (string, error) { var s struct { Next string `json:"next"` } @@ -258,7 +258,7 @@ func (r CapsulePage) IsEmpty() (bool, error) { return len(v) == 0, nil } - return false, fmt.Errorf("Unable to determine Capsule type") + return false, fmt.Errorf("unable to determine Capsule type") } // ExtractCapsulesBase accepts a Page struct, specifically a CapsulePage struct, diff --git a/openstack/container/v1/capsules/template.go b/openstack/container/v1/capsules/template.go index b3b4446131..06df6ae938 100644 --- a/openstack/container/v1/capsules/template.go +++ b/openstack/container/v1/capsules/template.go @@ -3,7 +3,7 @@ package capsules import ( "encoding/json" - yaml "gopkg.in/yaml.v2" + yaml "go.yaml.in/yaml/v3" ) // Template is a structure that represents OpenStack Zun Capsule templates diff --git a/openstack/container/v1/capsules/testing/fixtures_test.go b/openstack/container/v1/capsules/testing/fixtures_test.go index fe7b008013..00ef3404f6 100644 --- a/openstack/container/v1/capsules/testing/fixtures_test.go +++ b/openstack/container/v1/capsules/testing/fixtures_test.go @@ -4,10 +4,12 @@ import ( "fmt" "net/http" "testing" + "time" + "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/openstack/container/v1/capsules" th "github.com/gophercloud/gophercloud/v2/testhelper" - fakeclient "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) // ValidJSONTemplate is a valid OpenStack Capsule template in JSON format @@ -142,9 +144,9 @@ var ValidYAMLTemplateParsed = map[string]any{ "app1": "web1", }, }, - "spec": map[any]any{ + "spec": map[string]any{ "restartPolicy": "Always", - "containers": []map[any]any{ + "containers": []map[string]any{ { "image": "ubuntu", "command": []any{ @@ -153,20 +155,20 @@ var ValidYAMLTemplateParsed = map[string]any{ "imagePullPolicy": "ifnotpresent", "workDir": "/root", "ports": []any{ - map[any]any{ + map[string]any{ "name": "nginx-port", "containerPort": 80, "hostPort": 80, "protocol": "TCP", }, }, - "resources": map[any]any{ - "requests": map[any]any{ + "resources": map[string]any{ + "requests": map[string]any{ "cpu": 1, "memory": 1024, }, }, - "env": map[any]any{ + "env": map[string]any{ "ENV1": "/usr/local/bin", "ENV2": "/usr/bin", }, @@ -455,216 +457,234 @@ const CapsuleV132ListBody = ` ] }` -var ExpectedContainer1 = capsules.Container{ - Name: "test-demo-omicron-13", - UUID: "1739e28a-d391-4fd9-93a5-3ba3f29a4c9b", - UserID: "d33b18c384574fd2a3299447aac285f0", - ProjectID: "6b8ffef2a0ac42ee87887b9cc98bdf68", - CPU: float64(1), - Memory: "1024M", - Host: "test-host", - Status: "Running", - Image: "test", - Labels: map[string]string{ - "foo": "bar", - }, - WorkDir: "/root", - Disk: 0, - Command: []string{ - "testcmd", - }, - Ports: []int{ - 80, - }, - SecurityGroups: []string{ - "default", - }, - ImagePullPolicy: "ifnotpresent", - Runtime: "runc", - TaskState: "Creating", - HostName: "test-hostname", - Environment: map[string]string{ - "USER1": "test", - }, - StatusReason: "No reason", - StatusDetail: "Just created", - ImageDriver: "docker", - Interactive: true, - AutoRemove: false, - AutoHeal: false, - RestartPolicy: map[string]string{ - "MaximumRetryCount": "0", - "Name": "always", - }, - Addresses: map[string][]capsules.Address{ - "b1295212-64e1-471d-aa01-25ff46f9818d": { - { - PreserveOnDelete: false, - Addr: "172.24.4.11", - Port: "8439060f-381a-4386-a518-33d5a4058636", - Version: float64(4), - SubnetID: "4a2bcd64-93ad-4436-9f48-3a7f9b267e0a", +func GetFakeContainer() capsules.Container { + createdAt, _ := time.Parse(gophercloud.RFC3339ZNoTNoZ, "2018-01-12 09:37:25") + updatedAt, _ := time.Parse(gophercloud.RFC3339ZNoTNoZ, "2018-01-12 09:37:26") + startedAt, _ := time.Parse(gophercloud.RFC3339ZNoTNoZ, "2018-01-12 09:37:26") + + return capsules.Container{ + Name: "test-demo-omicron-13", + UUID: "1739e28a-d391-4fd9-93a5-3ba3f29a4c9b", + UserID: "d33b18c384574fd2a3299447aac285f0", + ProjectID: "6b8ffef2a0ac42ee87887b9cc98bdf68", + CreatedAt: createdAt, + UpdatedAt: updatedAt, + StartedAt: startedAt, + CPU: float64(1), + Memory: "1024M", + Host: "test-host", + Status: "Running", + Image: "test", + Labels: map[string]string{ + "foo": "bar", + }, + WorkDir: "/root", + Disk: 0, + Command: []string{ + "testcmd", + }, + Ports: []int{ + 80, + }, + SecurityGroups: []string{ + "default", + }, + ImagePullPolicy: "ifnotpresent", + Runtime: "runc", + TaskState: "Creating", + HostName: "test-hostname", + Environment: map[string]string{ + "USER1": "test", + }, + StatusReason: "No reason", + StatusDetail: "Just created", + ImageDriver: "docker", + Interactive: true, + AutoRemove: false, + AutoHeal: false, + RestartPolicy: map[string]string{ + "MaximumRetryCount": "0", + "Name": "always", + }, + Addresses: map[string][]capsules.Address{ + "b1295212-64e1-471d-aa01-25ff46f9818d": { + { + PreserveOnDelete: false, + Addr: "172.24.4.11", + Port: "8439060f-381a-4386-a518-33d5a4058636", + Version: float64(4), + SubnetID: "4a2bcd64-93ad-4436-9f48-3a7f9b267e0a", + }, }, }, - }, + } } -var ExpectedCapsule = capsules.Capsule{ - UUID: "cc654059-1a77-47a3-bfcf-715bde5aad9e", - Status: "Running", - UserID: "d33b18c384574fd2a3299447aac285f0", - ProjectID: "6b8ffef2a0ac42ee87887b9cc98bdf68", - CPU: float64(1), - Memory: "1024M", - MetaName: "test", - Links: []any{ - map[string]any{ - "href": "http://10.10.10.10/v1/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", - "rel": "self", +func GetFakeCapsule() capsules.Capsule { + createdAt, _ := time.Parse(gophercloud.RFC3339ZNoTNoZ, "2018-01-12 09:37:25") + updatedAt, _ := time.Parse(gophercloud.RFC3339ZNoTNoZ, "2018-01-12 09:37:26") + + return capsules.Capsule{ + UUID: "cc654059-1a77-47a3-bfcf-715bde5aad9e", + Status: "Running", + UserID: "d33b18c384574fd2a3299447aac285f0", + ProjectID: "6b8ffef2a0ac42ee87887b9cc98bdf68", + CreatedAt: createdAt, + UpdatedAt: updatedAt, + CPU: float64(1), + Memory: "1024M", + MetaName: "test", + Links: []any{ + map[string]any{ + "href": "http://10.10.10.10/v1/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", + "rel": "self", + }, + map[string]any{ + "href": "http://10.10.10.10/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", + "rel": "bookmark", + }, }, - map[string]any{ - "href": "http://10.10.10.10/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", - "rel": "bookmark", + CapsuleVersion: "beta", + RestartPolicy: "always", + MetaLabels: map[string]string{ + "web": "app", }, - }, - CapsuleVersion: "beta", - RestartPolicy: "always", - MetaLabels: map[string]string{ - "web": "app", - }, - ContainersUUIDs: []string{ - "1739e28a-d391-4fd9-93a5-3ba3f29a4c9b", - }, - Addresses: map[string][]capsules.Address{ - "b1295212-64e1-471d-aa01-25ff46f9818d": { - { - PreserveOnDelete: false, - Addr: "172.24.4.11", - Port: "8439060f-381a-4386-a518-33d5a4058636", - Version: float64(4), - SubnetID: "4a2bcd64-93ad-4436-9f48-3a7f9b267e0a", + ContainersUUIDs: []string{ + "1739e28a-d391-4fd9-93a5-3ba3f29a4c9b", + }, + Addresses: map[string][]capsules.Address{ + "b1295212-64e1-471d-aa01-25ff46f9818d": { + { + PreserveOnDelete: false, + Addr: "172.24.4.11", + Port: "8439060f-381a-4386-a518-33d5a4058636", + Version: float64(4), + SubnetID: "4a2bcd64-93ad-4436-9f48-3a7f9b267e0a", + }, }, }, - }, - VolumesInfo: map[string][]string{ - "67618d54-dd55-4f7e-91b3-39ffb3ba7f5f": { - "1739e28a-d391-4fd9-93a5-3ba3f29a4c9b", + VolumesInfo: map[string][]string{ + "67618d54-dd55-4f7e-91b3-39ffb3ba7f5f": { + "1739e28a-d391-4fd9-93a5-3ba3f29a4c9b", + }, }, - }, - Host: "test-host", - StatusReason: "No reason", - Containers: []capsules.Container{ - ExpectedContainer1, - }, + Host: "test-host", + StatusReason: "No reason", + Containers: []capsules.Container{ + GetFakeContainer(), + }, + } } -var ExpectedCapsuleV132 = capsules.CapsuleV132{ - UUID: "cc654059-1a77-47a3-bfcf-715bde5aad9e", - Status: "Running", - UserID: "d33b18c384574fd2a3299447aac285f0", - ProjectID: "6b8ffef2a0ac42ee87887b9cc98bdf68", - CPU: float64(1), - Memory: "1024M", - MetaName: "test", - Links: []any{ - map[string]any{ - "href": "http://10.10.10.10/v1/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", - "rel": "self", +func GetFakeCapsuleV132() capsules.CapsuleV132 { + return capsules.CapsuleV132{ + UUID: "cc654059-1a77-47a3-bfcf-715bde5aad9e", + Status: "Running", + UserID: "d33b18c384574fd2a3299447aac285f0", + ProjectID: "6b8ffef2a0ac42ee87887b9cc98bdf68", + CPU: float64(1), + Memory: "1024M", + MetaName: "test", + Links: []any{ + map[string]any{ + "href": "http://10.10.10.10/v1/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", + "rel": "self", + }, + map[string]any{ + "href": "http://10.10.10.10/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", + "rel": "bookmark", + }, }, - map[string]any{ - "href": "http://10.10.10.10/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", - "rel": "bookmark", + RestartPolicy: map[string]string{ + "MaximumRetryCount": "0", + "Name": "always", }, - }, - RestartPolicy: map[string]string{ - "MaximumRetryCount": "0", - "Name": "always", - }, - MetaLabels: map[string]string{ - "web": "app", - }, - Addresses: map[string][]capsules.Address{ - "b1295212-64e1-471d-aa01-25ff46f9818d": { - { - PreserveOnDelete: false, - Addr: "172.24.4.11", - Port: "8439060f-381a-4386-a518-33d5a4058636", - Version: float64(4), - SubnetID: "4a2bcd64-93ad-4436-9f48-3a7f9b267e0a", + MetaLabels: map[string]string{ + "web": "app", + }, + Addresses: map[string][]capsules.Address{ + "b1295212-64e1-471d-aa01-25ff46f9818d": { + { + PreserveOnDelete: false, + Addr: "172.24.4.11", + Port: "8439060f-381a-4386-a518-33d5a4058636", + Version: float64(4), + SubnetID: "4a2bcd64-93ad-4436-9f48-3a7f9b267e0a", + }, }, }, - }, - Host: "test-host", - StatusReason: "No reason", - Containers: []capsules.Container{ - ExpectedContainer1, - }, + Host: "test-host", + StatusReason: "No reason", + Containers: []capsules.Container{ + GetFakeContainer(), + }, + } } // HandleCapsuleGetOldTimeSuccessfully test setup -func HandleCapsuleGetOldTimeSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", func(w http.ResponseWriter, r *http.Request) { +func HandleCapsuleGetOldTimeSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, CapsuleGetBody_OldTime) + fmt.Fprint(w, CapsuleGetBody_OldTime) }) } // HandleCapsuleGetNewTimeSuccessfully test setup -func HandleCapsuleGetNewTimeSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", func(w http.ResponseWriter, r *http.Request) { +func HandleCapsuleGetNewTimeSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, CapsuleGetBody_NewTime) + fmt.Fprint(w, CapsuleGetBody_NewTime) }) } // HandleCapsuleCreateSuccessfully creates an HTTP handler at `/capsules` on the test handler mux // that responds with a `Create` response. -func HandleCapsuleCreateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/capsules", func(w http.ResponseWriter, r *http.Request) { +func HandleCapsuleCreateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/capsules", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, CapsuleGetBody_NewTime) + fmt.Fprint(w, CapsuleGetBody_NewTime) }) } // HandleCapsuleListSuccessfully test setup -func HandleCapsuleListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/capsules/", func(w http.ResponseWriter, r *http.Request) { +func HandleCapsuleListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/capsules/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, CapsuleListBody) + fmt.Fprint(w, CapsuleListBody) }) } // HandleCapsuleV132ListSuccessfully test setup -func HandleCapsuleV132ListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/capsules/", func(w http.ResponseWriter, r *http.Request) { +func HandleCapsuleV132ListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/capsules/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, CapsuleV132ListBody) + fmt.Fprint(w, CapsuleV132ListBody) }) } -func HandleCapsuleDeleteSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/capsules/963a239d-3946-452b-be5a-055eab65a421", func(w http.ResponseWriter, r *http.Request) { +func HandleCapsuleDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/capsules/963a239d-3946-452b-be5a-055eab65a421", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) } diff --git a/openstack/container/v1/capsules/testing/requests_test.go b/openstack/container/v1/capsules/testing/requests_test.go index 2925f93ecc..20d0850886 100644 --- a/openstack/container/v1/capsules/testing/requests_test.go +++ b/openstack/container/v1/capsules/testing/requests_test.go @@ -9,81 +9,74 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/container/v1/capsules" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fakeclient "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestGetCapsule_OldTime(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleCapsuleGetOldTimeSuccessfully(t) + HandleCapsuleGetOldTimeSuccessfully(t, fakeServer) createdAt, _ := time.Parse(gophercloud.RFC3339ZNoT, "2018-01-12 09:37:25+00:00") updatedAt, _ := time.Parse(gophercloud.RFC3339ZNoT, "2018-01-12 09:37:26+00:00") startedAt, _ := time.Parse(gophercloud.RFC3339ZNoT, "2018-01-12 09:37:26+00:00") - ExpectedCapsule.CreatedAt = createdAt - ExpectedCapsule.UpdatedAt = updatedAt - ExpectedCapsule.Containers[0].CreatedAt = createdAt - ExpectedCapsule.Containers[0].UpdatedAt = updatedAt - ExpectedCapsule.Containers[0].StartedAt = startedAt + ec := GetFakeCapsule() + ec.CreatedAt = createdAt + ec.UpdatedAt = updatedAt + ec.Containers[0].CreatedAt = createdAt + ec.Containers[0].UpdatedAt = updatedAt + ec.Containers[0].StartedAt = startedAt - actualCapsule, err := capsules.Get(context.TODO(), fakeclient.ServiceClient(), ExpectedCapsule.UUID).Extract() + actualCapsule, err := capsules.Get(context.TODO(), client.ServiceClient(fakeServer), ec.UUID).Extract() th.AssertNoErr(t, err) - th.AssertDeepEquals(t, &ExpectedCapsule, actualCapsule) + th.AssertDeepEquals(t, &ec, actualCapsule) } func TestGetCapsule_NewTime(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleCapsuleGetNewTimeSuccessfully(t) - - createdAt, _ := time.Parse(gophercloud.RFC3339ZNoTNoZ, "2018-01-12 09:37:25") - updatedAt, _ := time.Parse(gophercloud.RFC3339ZNoTNoZ, "2018-01-12 09:37:26") - startedAt, _ := time.Parse(gophercloud.RFC3339ZNoTNoZ, "2018-01-12 09:37:26") + HandleCapsuleGetNewTimeSuccessfully(t, fakeServer) - ExpectedCapsule.CreatedAt = createdAt - ExpectedCapsule.UpdatedAt = updatedAt - ExpectedCapsule.Containers[0].CreatedAt = createdAt - ExpectedCapsule.Containers[0].UpdatedAt = updatedAt - ExpectedCapsule.Containers[0].StartedAt = startedAt + ec := GetFakeCapsule() - actualCapsule, err := capsules.Get(context.TODO(), fakeclient.ServiceClient(), ExpectedCapsule.UUID).Extract() + actualCapsule, err := capsules.Get(context.TODO(), client.ServiceClient(fakeServer), ec.UUID).Extract() th.AssertNoErr(t, err) - th.AssertDeepEquals(t, &ExpectedCapsule, actualCapsule) + th.AssertDeepEquals(t, &ec, actualCapsule) } func TestCreateCapsule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCapsuleCreateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCapsuleCreateSuccessfully(t, fakeServer) + + ec := GetFakeCapsule() template := new(capsules.Template) template.Bin = []byte(ValidJSONTemplate) - createOpts := capsules.CreateOpts{ TemplateOpts: template, } - actualCapsule, err := capsules.Create(context.TODO(), fakeclient.ServiceClient(), createOpts).Extract() + actualCapsule, err := capsules.Create(context.TODO(), client.ServiceClient(fakeServer), createOpts).Extract() th.AssertNoErr(t, err) - th.AssertDeepEquals(t, &ExpectedCapsule, actualCapsule) + th.AssertDeepEquals(t, &ec, actualCapsule) } func TestListCapsule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleCapsuleListSuccessfully(t) + HandleCapsuleListSuccessfully(t, fakeServer) createdAt, _ := time.Parse(gophercloud.RFC3339ZNoT, "2018-01-12 09:37:25+00:00") updatedAt, _ := time.Parse(gophercloud.RFC3339ZNoT, "2018-01-12 09:37:25+01:00") - ec := ExpectedCapsule - + ec := GetFakeCapsule() ec.CreatedAt = createdAt ec.UpdatedAt = updatedAt ec.Containers = nil @@ -91,7 +84,7 @@ func TestListCapsule(t *testing.T) { expected := []capsules.Capsule{ec} count := 0 - results := capsules.List(fakeclient.ServiceClient(), nil) + results := capsules.List(client.ServiceClient(fakeServer), nil) err := results.EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := capsules.ExtractCapsules(page) @@ -112,16 +105,15 @@ func TestListCapsule(t *testing.T) { } func TestListCapsuleV132(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleCapsuleV132ListSuccessfully(t) + HandleCapsuleV132ListSuccessfully(t, fakeServer) createdAt, _ := time.Parse(gophercloud.RFC3339ZNoTNoZ, "2018-01-12 09:37:25") updatedAt, _ := time.Parse(gophercloud.RFC3339ZNoTNoZ, "2018-01-12 09:37:25") - ec := ExpectedCapsuleV132 - + ec := GetFakeCapsuleV132() ec.CreatedAt = createdAt ec.UpdatedAt = updatedAt ec.Containers = nil @@ -129,7 +121,7 @@ func TestListCapsuleV132(t *testing.T) { expected := []capsules.CapsuleV132{ec} count := 0 - results := capsules.List(fakeclient.ServiceClient(), nil) + results := capsules.List(client.ServiceClient(fakeServer), nil) err := results.EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := capsules.ExtractCapsules(page) @@ -150,11 +142,11 @@ func TestListCapsuleV132(t *testing.T) { } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleCapsuleDeleteSuccessfully(t) + HandleCapsuleDeleteSuccessfully(t, fakeServer) - res := capsules.Delete(context.TODO(), fakeclient.ServiceClient(), "963a239d-3946-452b-be5a-055eab65a421") + res := capsules.Delete(context.TODO(), client.ServiceClient(fakeServer), "963a239d-3946-452b-be5a-055eab65a421") th.AssertNoErr(t, res.Err) } diff --git a/openstack/containerinfra/apiversions/testing/fixtures_test.go b/openstack/containerinfra/apiversions/testing/fixtures_test.go index e95058151d..dd9711a6c0 100644 --- a/openstack/containerinfra/apiversions/testing/fixtures_test.go +++ b/openstack/containerinfra/apiversions/testing/fixtures_test.go @@ -63,26 +63,26 @@ var MagnumAllAPIVersionResults = []apiversions.APIVersion{ MagnumAPIVersion1Result, } -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { +func MockListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, MagnumAllAPIVersionsResponse) + fmt.Fprint(w, MagnumAllAPIVersionsResponse) }) } -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc("/v1/", func(w http.ResponseWriter, r *http.Request) { +func MockGetResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, MagnumAPIVersionResponse) + fmt.Fprint(w, MagnumAPIVersionResponse) }) } diff --git a/openstack/containerinfra/apiversions/testing/requests_test.go b/openstack/containerinfra/apiversions/testing/requests_test.go index 175be19c74..a2a478243c 100644 --- a/openstack/containerinfra/apiversions/testing/requests_test.go +++ b/openstack/containerinfra/apiversions/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestListAPIVersions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) - allVersions, err := apiversions.List(client.ServiceClient()).AllPages(context.TODO()) + allVersions, err := apiversions.List(client.ServiceClient(fakeServer)).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := apiversions.ExtractAPIVersions(allVersions) @@ -26,12 +26,12 @@ func TestListAPIVersions(t *testing.T) { } func TestGetAPIVersion(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetResponse(t) + MockGetResponse(t, fakeServer) - actual, err := apiversions.Get(context.TODO(), client.ServiceClient(), "v1").Extract() + actual, err := apiversions.Get(context.TODO(), client.ServiceClient(fakeServer), "v1").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, MagnumAPIVersion1Result, *actual) diff --git a/openstack/containerinfra/v1/certificates/testing/fixtures_test.go b/openstack/containerinfra/v1/certificates/testing/fixtures_test.go index 3f88c34242..67381f5787 100644 --- a/openstack/containerinfra/v1/certificates/testing/fixtures_test.go +++ b/openstack/containerinfra/v1/certificates/testing/fixtures_test.go @@ -8,7 +8,7 @@ import ( "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/openstack/containerinfra/v1/certificates" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) const CertificateResponse = ` @@ -67,10 +67,10 @@ var ExpectedCreateCertificateResponse = certificates.Certificate{ }, } -func HandleGetCertificateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v1/certificates/d564b18a-2890-4152-be3d-e05d784ff72", func(w http.ResponseWriter, r *http.Request) { +func HandleGetCertificateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/certificates/d564b18a-2890-4152-be3d-e05d784ff72", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.Header().Add("OpenStack-API-Minimum-Version", "container-infra 1.1") @@ -83,10 +83,10 @@ func HandleGetCertificateSuccessfully(t *testing.T) { }) } -func HandleCreateCertificateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v1/certificates/", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateCertificateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/certificates/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.Header().Add("OpenStack-API-Minimum-Version", "container-infra 1.1") @@ -99,13 +99,13 @@ func HandleCreateCertificateSuccessfully(t *testing.T) { }) } -func HandleUpdateCertificateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v1/certificates/d564b18a-2890-4152-be3d-e05d784ff72", +func HandleUpdateCertificateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/certificates/d564b18a-2890-4152-be3d-e05d784ff72", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } diff --git a/openstack/containerinfra/v1/certificates/testing/requests_test.go b/openstack/containerinfra/v1/certificates/testing/requests_test.go index 25a287e401..3725c86360 100644 --- a/openstack/containerinfra/v1/certificates/testing/requests_test.go +++ b/openstack/containerinfra/v1/certificates/testing/requests_test.go @@ -6,16 +6,16 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/containerinfra/v1/certificates" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestGetCertificates(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleGetCertificateSuccessfully(t) + HandleGetCertificateSuccessfully(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" actual, err := certificates.Get(context.TODO(), sc, "d564b18a-2890-4152-be3d-e05d784ff72").Extract() @@ -24,12 +24,12 @@ func TestGetCertificates(t *testing.T) { } func TestCreateCertificates(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleCreateCertificateSuccessfully(t) + HandleCreateCertificateSuccessfully(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" opts := certificates.CreateOpts{ @@ -43,12 +43,12 @@ func TestCreateCertificates(t *testing.T) { } func TestUpdateCertificates(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleUpdateCertificateSuccessfully(t) + HandleUpdateCertificateSuccessfully(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" err := certificates.Update(context.TODO(), sc, "d564b18a-2890-4152-be3d-e05d784ff72").ExtractErr() diff --git a/openstack/containerinfra/v1/clusters/results.go b/openstack/containerinfra/v1/clusters/results.go index 6c235dcd23..ee74caeb93 100644 --- a/openstack/containerinfra/v1/clusters/results.go +++ b/openstack/containerinfra/v1/clusters/results.go @@ -112,6 +112,7 @@ type Cluster struct { UpdatedAt time.Time `json:"updated_at"` UserID string `json:"user_id"` FloatingIPEnabled bool `json:"floating_ip_enabled"` + MasterLBEnabled bool `json:"master_lb_enabled"` FixedNetwork string `json:"fixed_network"` FixedSubnet string `json:"fixed_subnet"` HealthStatus string `json:"health_status"` @@ -122,7 +123,7 @@ type ClusterPage struct { pagination.LinkedPageBase } -func (r ClusterPage) NextPageURL() (string, error) { +func (r ClusterPage) NextPageURL(endpointURL string) (string, error) { var s struct { Next string `json:"next"` } diff --git a/openstack/containerinfra/v1/clusters/testing/fixtures_test.go b/openstack/containerinfra/v1/clusters/testing/fixtures_test.go index 640533e2c8..bf0a77c911 100644 --- a/openstack/containerinfra/v1/clusters/testing/fixtures_test.go +++ b/openstack/containerinfra/v1/clusters/testing/fixtures_test.go @@ -9,7 +9,7 @@ import ( "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/openstack/containerinfra/v1/clusters" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) const clusterUUID = "746e779a-751a-456b-a3e9-c883d734946f" @@ -50,6 +50,7 @@ var ExpectedCluster = clusters.Cluster{ UpdatedAt: time.Date(2016, 8, 29, 6, 53, 24, 0, time.UTC), UUID: clusterUUID, FloatingIPEnabled: true, + MasterLBEnabled: true, FixedNetwork: "private_network", FixedSubnet: "private_subnet", HealthStatus: "HEALTHY", @@ -85,6 +86,7 @@ var ExpectedCluster2 = clusters.Cluster{ UpdatedAt: time.Date(2016, 8, 29, 6, 53, 24, 0, time.UTC), UUID: clusterUUID2, FloatingIPEnabled: true, + MasterLBEnabled: true, FixedNetwork: "private_network", FixedSubnet: "private_subnet", HealthStatus: "HEALTHY", @@ -93,10 +95,10 @@ var ExpectedCluster2 = clusters.Cluster{ var ExpectedClusterUUID = clusterUUID -func HandleCreateClusterSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateClusterSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.Header().Add("X-OpenStack-Request-Id", requestUUID) @@ -106,10 +108,10 @@ func HandleCreateClusterSuccessfully(t *testing.T) { }) } -func HandleGetClusterSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/"+clusterUUID, func(w http.ResponseWriter, r *http.Request) { +func HandleGetClusterSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/"+clusterUUID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -152,6 +154,7 @@ var ClusterGetResponse = fmt.Sprintf(` "create_timeout":60, "name":"k8s", "floating_ip_enabled": true, + "master_lb_enabled": true, "fixed_network": "private_network", "fixed_subnet": "private_subnet", "health_status": "HEALTHY", @@ -194,6 +197,7 @@ var ClusterListResponse = fmt.Sprintf(` "updated_at":"2016-08-29T06:53:24+00:00", "uuid":"%s", "floating_ip_enabled": true, + "master_lb_enabled": true, "fixed_network": "private_network", "fixed_subnet": "private_subnet", "health_status": "HEALTHY", @@ -232,6 +236,7 @@ var ClusterListResponse = fmt.Sprintf(` "updated_at":null, "uuid":"%s", "floating_ip_enabled": true, + "master_lb_enabled": true, "fixed_network": "private_network", "fixed_subnet": "private_subnet", "health_status": "HEALTHY", @@ -242,10 +247,10 @@ var ClusterListResponse = fmt.Sprintf(` var ExpectedClusters = []clusters.Cluster{ExpectedCluster} -func HandleListClusterSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters", func(w http.ResponseWriter, r *http.Request) { +func HandleListClusterSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.Header().Add("X-OpenStack-Request-Id", requestUUID) @@ -255,10 +260,10 @@ func HandleListClusterSuccessfully(t *testing.T) { }) } -func HandleListDetailClusterSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/detail", func(w http.ResponseWriter, r *http.Request) { +func HandleListDetailClusterSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.Header().Add("X-OpenStack-Request-Id", requestUUID) @@ -273,10 +278,10 @@ var UpdateResponse = fmt.Sprintf(` "uuid":"%s" }`, clusterUUID) -func HandleUpdateClusterSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/"+clusterUUID, func(w http.ResponseWriter, r *http.Request) { +func HandleUpdateClusterSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/"+clusterUUID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.Header().Add("X-OpenStack-Request-Id", requestUUID) @@ -291,10 +296,10 @@ var UpgradeResponse = fmt.Sprintf(` "uuid":"%s" }`, clusterUUID) -func HandleUpgradeClusterSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/actions/upgrade", func(w http.ResponseWriter, r *http.Request) { +func HandleUpgradeClusterSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/actions/upgrade", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.Header().Add("X-OpenStack-Request-Id", requestUUID) @@ -304,10 +309,10 @@ func HandleUpgradeClusterSuccessfully(t *testing.T) { }) } -func HandleDeleteClusterSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/"+clusterUUID, func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteClusterSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/"+clusterUUID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.Header().Add("X-OpenStack-Request-Id", requestUUID) @@ -320,10 +325,10 @@ var ResizeResponse = fmt.Sprintf(` "uuid": "%s" }`, clusterUUID) -func HandleResizeClusterSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/actions/resize", func(w http.ResponseWriter, r *http.Request) { +func HandleResizeClusterSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/actions/resize", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.Header().Add("X-OpenStack-Request-Id", requestUUID) diff --git a/openstack/containerinfra/v1/clusters/testing/requests_test.go b/openstack/containerinfra/v1/clusters/testing/requests_test.go index c7cd21efcc..44a8a353d8 100644 --- a/openstack/containerinfra/v1/clusters/testing/requests_test.go +++ b/openstack/containerinfra/v1/clusters/testing/requests_test.go @@ -8,14 +8,14 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/containerinfra/v1/clusters" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestCreateCluster(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleCreateClusterSuccessfully(t) + HandleCreateClusterSuccessfully(t, fakeServer) masterCount := 1 nodeCount := 1 @@ -39,7 +39,7 @@ func TestCreateCluster(t *testing.T) { MergeLabels: gophercloud.Enabled, } - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" res := clusters.Create(context.TODO(), sc, opts) th.AssertNoErr(t, res.Err) @@ -54,12 +54,12 @@ func TestCreateCluster(t *testing.T) { } func TestGetCluster(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleGetClusterSuccessfully(t) + HandleGetClusterSuccessfully(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" actual, err := clusters.Get(context.TODO(), sc, "746e779a-751a-456b-a3e9-c883d734946f").Extract() th.AssertNoErr(t, err) @@ -69,13 +69,13 @@ func TestGetCluster(t *testing.T) { } func TestListClusters(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleListClusterSuccessfully(t) + HandleListClusterSuccessfully(t, fakeServer) count := 0 - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" err := clusters.List(sc, clusters.ListOpts{Limit: 2}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ @@ -97,13 +97,13 @@ func TestListClusters(t *testing.T) { } func TestListDetailClusters(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleListDetailClusterSuccessfully(t) + HandleListDetailClusterSuccessfully(t, fakeServer) count := 0 - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" err := clusters.ListDetail(sc, clusters.ListOpts{Limit: 2}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ @@ -125,10 +125,10 @@ func TestListDetailClusters(t *testing.T) { } func TestUpdateCluster(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleUpdateClusterSuccessfully(t) + HandleUpdateClusterSuccessfully(t, fakeServer) updateOpts := []clusters.UpdateOptsBuilder{ clusters.UpdateOpts{ @@ -143,7 +143,7 @@ func TestUpdateCluster(t *testing.T) { }, } - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" res := clusters.Update(context.TODO(), sc, clusterUUID, updateOpts) th.AssertNoErr(t, res.Err) @@ -158,16 +158,16 @@ func TestUpdateCluster(t *testing.T) { } func TestUpgradeCluster(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleUpgradeClusterSuccessfully(t) + HandleUpgradeClusterSuccessfully(t, fakeServer) opts := clusters.UpgradeOpts{ ClusterTemplate: "0562d357-8641-4759-8fed-8173f02c9633", } - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" res := clusters.Upgrade(context.TODO(), sc, clusterUUID, opts) th.AssertNoErr(t, res.Err) @@ -182,12 +182,12 @@ func TestUpgradeCluster(t *testing.T) { } func TestDeleteCluster(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleDeleteClusterSuccessfully(t) + HandleDeleteClusterSuccessfully(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" r := clusters.Delete(context.TODO(), sc, clusterUUID) err := r.ExtractErr() @@ -208,10 +208,10 @@ func TestDeleteCluster(t *testing.T) { } func TestResizeCluster(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleResizeClusterSuccessfully(t) + HandleResizeClusterSuccessfully(t, fakeServer) nodeCount := 2 @@ -219,7 +219,7 @@ func TestResizeCluster(t *testing.T) { NodeCount: &nodeCount, } - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" res := clusters.Resize(context.TODO(), sc, clusterUUID, opts) th.AssertNoErr(t, res.Err) diff --git a/openstack/containerinfra/v1/clustertemplates/results.go b/openstack/containerinfra/v1/clustertemplates/results.go index 945fdf95fe..7715e1318f 100644 --- a/openstack/containerinfra/v1/clustertemplates/results.go +++ b/openstack/containerinfra/v1/clustertemplates/results.go @@ -86,7 +86,7 @@ type ClusterTemplatePage struct { // NextPageURL is invoked when a paginated collection of cluster template has reached // the end of a page and the pager seeks to traverse over a new one. In order // to do this, it needs to construct the next page's URL. -func (r ClusterTemplatePage) NextPageURL() (string, error) { +func (r ClusterTemplatePage) NextPageURL(endpointURL string) (string, error) { var s struct { Next string `json:"next"` } diff --git a/openstack/containerinfra/v1/clustertemplates/testing/fixtures_test.go b/openstack/containerinfra/v1/clustertemplates/testing/fixtures_test.go index f269d99cb3..5d47da5988 100644 --- a/openstack/containerinfra/v1/clustertemplates/testing/fixtures_test.go +++ b/openstack/containerinfra/v1/clustertemplates/testing/fixtures_test.go @@ -9,7 +9,7 @@ import ( "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/openstack/containerinfra/v1/clustertemplates" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) const ClusterTemplateResponse = ` @@ -274,10 +274,32 @@ var ExpectedClusterTemplate_EmptyTime = clustertemplates.ClusterTemplate{ var ExpectedClusterTemplates = []clustertemplates.ClusterTemplate{ExpectedClusterTemplate, ExpectedClusterTemplate_EmptyTime} -func HandleCreateClusterTemplateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v1/clustertemplates", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateClusterTemplateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clustertemplates", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "coe": "kubernetes", + "dns_nameserver": "8.8.8.8", + "docker_storage_driver": "devicemapper", + "docker_volume_size": 3, + "external_network_id": "public", + "flavor_id": "m1.small", + "hidden": true, + "http_proxy": "http://10.164.177.169:8080", + "https_proxy": "http://10.164.177.169:8080", + "image_id": "Fedora-Atomic-27-20180212.2.x86_64", + "keypair_id": "kp", + "master_lb_enabled": true, + "name": "kubernetes-dev", + "network_driver": "flannel", + "no_proxy": "10.0.0.0/8,172.0.0.0/8,192.0.0.0/8,localhost", + "public": false, + "registry_enabled": false, + "server_type": "vm", + "tls_disabled": false, + "volume_driver": "cinder" + }`) w.Header().Add("Content-Type", "application/json") w.Header().Add("OpenStack-API-Minimum-Version", "container-infra 1.1") @@ -290,10 +312,10 @@ func HandleCreateClusterTemplateSuccessfully(t *testing.T) { }) } -func HandleDeleteClusterSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v1/clustertemplates/6dc6d336e3fc4c0a951b5698cd1236ee", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteClusterSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clustertemplates/6dc6d336e3fc4c0a951b5698cd1236ee", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.Header().Add("OpenStack-API-Minimum-Version", "container-infra 1.1") @@ -304,10 +326,10 @@ func HandleDeleteClusterSuccessfully(t *testing.T) { }) } -func HandleListClusterTemplateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v1/clustertemplates", func(w http.ResponseWriter, r *http.Request) { +func HandleListClusterTemplateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clustertemplates", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -316,10 +338,10 @@ func HandleListClusterTemplateSuccessfully(t *testing.T) { }) } -func HandleGetClusterTemplateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v1/clustertemplates/7d85f602-a948-4a30-afd4-e84f47471c15", func(w http.ResponseWriter, r *http.Request) { +func HandleGetClusterTemplateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clustertemplates/7d85f602-a948-4a30-afd4-e84f47471c15", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -328,10 +350,10 @@ func HandleGetClusterTemplateSuccessfully(t *testing.T) { }) } -func HandleGetClusterTemplateEmptyTimeSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v1/clustertemplates/7d85f602-a948-4a30-afd4-e84f47471c15", func(w http.ResponseWriter, r *http.Request) { +func HandleGetClusterTemplateEmptyTimeSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clustertemplates/7d85f602-a948-4a30-afd4-e84f47471c15", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -505,10 +527,10 @@ var ExpectedUpdateClusterTemplate_EmptyTime = clustertemplates.ClusterTemplate{ Hidden: false, } -func HandleUpdateClusterTemplateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v1/clustertemplates/7d85f602-a948-4a30-afd4-e84f47471c15", func(w http.ResponseWriter, r *http.Request) { +func HandleUpdateClusterTemplateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clustertemplates/7d85f602-a948-4a30-afd4-e84f47471c15", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -517,10 +539,10 @@ func HandleUpdateClusterTemplateSuccessfully(t *testing.T) { }) } -func HandleUpdateClusterTemplateEmptyTimeSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v1/clustertemplates/7d85f602-a948-4a30-afd4-e84f47471c15", func(w http.ResponseWriter, r *http.Request) { +func HandleUpdateClusterTemplateEmptyTimeSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clustertemplates/7d85f602-a948-4a30-afd4-e84f47471c15", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -529,10 +551,10 @@ func HandleUpdateClusterTemplateEmptyTimeSuccessfully(t *testing.T) { }) } -func HandleUpdateClusterTemplateInvalidUpdate(t *testing.T) { - th.Mux.HandleFunc("/v1/clustertemplates/7d85f602-a948-4a30-afd4-e84f47471c15", func(w http.ResponseWriter, r *http.Request) { +func HandleUpdateClusterTemplateInvalidUpdate(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clustertemplates/7d85f602-a948-4a30-afd4-e84f47471c15", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) diff --git a/openstack/containerinfra/v1/clustertemplates/testing/requests_test.go b/openstack/containerinfra/v1/clustertemplates/testing/requests_test.go index 8bab4017f2..03e5c1e05b 100644 --- a/openstack/containerinfra/v1/clustertemplates/testing/requests_test.go +++ b/openstack/containerinfra/v1/clustertemplates/testing/requests_test.go @@ -7,14 +7,14 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/containerinfra/v1/clustertemplates" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestCreateClusterTemplate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleCreateClusterTemplateSuccessfully(t) + HandleCreateClusterTemplateSuccessfully(t, fakeServer) boolFalse := false boolTrue := true @@ -46,7 +46,7 @@ func TestCreateClusterTemplate(t *testing.T) { Hidden: &boolTrue, } - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" res := clustertemplates.Create(context.TODO(), sc, opts) th.AssertNoErr(t, res.Err) @@ -62,12 +62,12 @@ func TestCreateClusterTemplate(t *testing.T) { } func TestDeleteClusterTemplate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleDeleteClusterSuccessfully(t) + HandleDeleteClusterSuccessfully(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" res := clustertemplates.Delete(context.TODO(), sc, "6dc6d336e3fc4c0a951b5698cd1236ee") th.AssertNoErr(t, res.Err) @@ -76,14 +76,14 @@ func TestDeleteClusterTemplate(t *testing.T) { } func TestListClusterTemplates(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleListClusterTemplateSuccessfully(t) + HandleListClusterTemplateSuccessfully(t, fakeServer) count := 0 - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" err := clustertemplates.List(sc, clustertemplates.ListOpts{Limit: 2}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ @@ -104,12 +104,12 @@ func TestListClusterTemplates(t *testing.T) { } func TestGetClusterTemplate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleGetClusterTemplateSuccessfully(t) + HandleGetClusterTemplateSuccessfully(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" actual, err := clustertemplates.Get(context.TODO(), sc, "7d85f602-a948-4a30-afd4-e84f47471c15").Extract() th.AssertNoErr(t, err) @@ -118,12 +118,12 @@ func TestGetClusterTemplate(t *testing.T) { } func TestGetClusterTemplateEmptyTime(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleGetClusterTemplateEmptyTimeSuccessfully(t) + HandleGetClusterTemplateEmptyTimeSuccessfully(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" actual, err := clustertemplates.Get(context.TODO(), sc, "7d85f602-a948-4a30-afd4-e84f47471c15").Extract() th.AssertNoErr(t, err) @@ -132,10 +132,10 @@ func TestGetClusterTemplateEmptyTime(t *testing.T) { } func TestUpdateClusterTemplate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleUpdateClusterTemplateSuccessfully(t) + HandleUpdateClusterTemplateSuccessfully(t, fakeServer) updateOpts := []clustertemplates.UpdateOptsBuilder{ clustertemplates.UpdateOpts{ @@ -150,7 +150,7 @@ func TestUpdateClusterTemplate(t *testing.T) { }, } - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" res := clustertemplates.Update(context.TODO(), sc, "7d85f602-a948-4a30-afd4-e84f47471c15", updateOpts) th.AssertNoErr(t, res.Err) @@ -162,10 +162,10 @@ func TestUpdateClusterTemplate(t *testing.T) { } func TestUpdateClusterTemplateEmptyTime(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleUpdateClusterTemplateEmptyTimeSuccessfully(t) + HandleUpdateClusterTemplateEmptyTimeSuccessfully(t, fakeServer) updateOpts := []clustertemplates.UpdateOptsBuilder{ clustertemplates.UpdateOpts{ @@ -180,7 +180,7 @@ func TestUpdateClusterTemplateEmptyTime(t *testing.T) { }, } - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" actual, err := clustertemplates.Update(context.TODO(), sc, "7d85f602-a948-4a30-afd4-e84f47471c15", updateOpts).Extract() th.AssertNoErr(t, err) @@ -188,10 +188,10 @@ func TestUpdateClusterTemplateEmptyTime(t *testing.T) { } func TestUpdateClusterTemplateInvalidUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleUpdateClusterTemplateInvalidUpdate(t) + HandleUpdateClusterTemplateInvalidUpdate(t, fakeServer) updateOpts := []clustertemplates.UpdateOptsBuilder{ clustertemplates.UpdateOpts{ @@ -208,8 +208,8 @@ func TestUpdateClusterTemplateInvalidUpdate(t *testing.T) { }, } - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" _, err := clustertemplates.Update(context.TODO(), sc, "7d85f602-a948-4a30-afd4-e84f47471c15", updateOpts).Extract() - th.AssertEquals(t, true, err != nil) + th.AssertTrue(t, err != nil) } diff --git a/openstack/containerinfra/v1/nodegroups/doc.go b/openstack/containerinfra/v1/nodegroups/doc.go index fb8073d672..824e7f30a4 100644 --- a/openstack/containerinfra/v1/nodegroups/doc.go +++ b/openstack/containerinfra/v1/nodegroups/doc.go @@ -16,7 +16,7 @@ Create a client to use: panic(err) } - client, err := openstack.NewContainerInfraV1(provider, gophercloud.EndpointOpts{Region: os.Getenv("OS_REGION_NAME")}) + client, err := openstack.NewContainerInfraV1(context.TODO(), provider, gophercloud.EndpointOpts{Region: os.Getenv("OS_REGION_NAME")}) if err != nil { panic(err) } diff --git a/openstack/containerinfra/v1/nodegroups/results.go b/openstack/containerinfra/v1/nodegroups/results.go index c94defe618..8d0cdd7c02 100644 --- a/openstack/containerinfra/v1/nodegroups/results.go +++ b/openstack/containerinfra/v1/nodegroups/results.go @@ -74,7 +74,7 @@ type NodeGroupPage struct { pagination.LinkedPageBase } -func (r NodeGroupPage) NextPageURL() (string, error) { +func (r NodeGroupPage) NextPageURL(endpointURL string) (string, error) { var s struct { Next string `json:"next"` } diff --git a/openstack/containerinfra/v1/nodegroups/testing/fixtures_test.go b/openstack/containerinfra/v1/nodegroups/testing/fixtures_test.go index 51e63cc318..95b9505c4d 100644 --- a/openstack/containerinfra/v1/nodegroups/testing/fixtures_test.go +++ b/openstack/containerinfra/v1/nodegroups/testing/fixtures_test.go @@ -9,7 +9,7 @@ import ( "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/openstack/containerinfra/v1/nodegroups" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) const ( @@ -125,10 +125,10 @@ var expectedUpdatedNodeGroup = nodegroups.NodeGroup{ UpdatedAt: nodeGroup2Updated, } -func handleGetNodeGroupSuccess(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups/"+nodeGroup1UUID, func(w http.ResponseWriter, r *http.Request) { +func handleGetNodeGroupSuccess(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups/"+nodeGroup1UUID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodGet) - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -137,10 +137,10 @@ func handleGetNodeGroupSuccess(t *testing.T) { }) } -func handleGetNodeGroupNotFound(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups/"+badNodeGroupUUID, func(w http.ResponseWriter, r *http.Request) { +func handleGetNodeGroupNotFound(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups/"+badNodeGroupUUID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodGet) - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusNotFound) @@ -149,10 +149,10 @@ func handleGetNodeGroupNotFound(t *testing.T) { }) } -func handleGetNodeGroupClusterNotFound(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/"+badClusterUUID+"/nodegroups/"+badNodeGroupUUID, func(w http.ResponseWriter, r *http.Request) { +func handleGetNodeGroupClusterNotFound(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/"+badClusterUUID+"/nodegroups/"+badNodeGroupUUID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodGet) - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusNotFound) @@ -161,10 +161,10 @@ func handleGetNodeGroupClusterNotFound(t *testing.T) { }) } -func handleListNodeGroupsSuccess(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups", func(w http.ResponseWriter, r *http.Request) { +func handleListNodeGroupsSuccess(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodGet) - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -173,10 +173,10 @@ func handleListNodeGroupsSuccess(t *testing.T) { }) } -func handleListNodeGroupsLimitSuccess(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups", func(w http.ResponseWriter, r *http.Request) { +func handleListNodeGroupsLimitSuccess(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodGet) - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -187,12 +187,12 @@ func handleListNodeGroupsLimitSuccess(t *testing.T) { if marker, ok := r.Form["marker"]; !ok { // No marker, this is the first request. th.TestFormValues(t, r, map[string]string{"limit": "1"}) - fmt.Fprintf(w, nodeGroupListLimitResponse1, th.Endpoint()) + fmt.Fprintf(w, nodeGroupListLimitResponse1, fakeServer.Endpoint()) } else { switch marker[0] { case nodeGroup1UUID: // Marker is the UUID of the first node group, return the second. - fmt.Fprintf(w, nodeGroupListLimitResponse2, th.Endpoint()) + fmt.Fprintf(w, nodeGroupListLimitResponse2, fakeServer.Endpoint()) case nodeGroup2UUID: // Marker is the UUID of the second node group, there are no more to return. fmt.Fprint(w, nodeGroupListLimitResponse3) @@ -201,10 +201,10 @@ func handleListNodeGroupsLimitSuccess(t *testing.T) { }) } -func handleListNodeGroupsClusterNotFound(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/"+badClusterUUID+"/nodegroups", func(w http.ResponseWriter, r *http.Request) { +func handleListNodeGroupsClusterNotFound(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/"+badClusterUUID+"/nodegroups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodGet) - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusNotFound) @@ -213,144 +213,144 @@ func handleListNodeGroupsClusterNotFound(t *testing.T) { }) } -func handleCreateNodeGroupSuccess(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups", func(w http.ResponseWriter, r *http.Request) { +func handleCreateNodeGroupSuccess(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodPost) - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, nodeGroupCreateResponse) + fmt.Fprint(w, nodeGroupCreateResponse) }) } -func handleCreateNodeGroupDuplicate(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups", func(w http.ResponseWriter, r *http.Request) { +func handleCreateNodeGroupDuplicate(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodPost) - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusConflict) - fmt.Fprintf(w, nodeGroupCreateDuplicateResponse) + fmt.Fprint(w, nodeGroupCreateDuplicateResponse) }) } -func handleCreateNodeGroupMaster(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups", func(w http.ResponseWriter, r *http.Request) { +func handleCreateNodeGroupMaster(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodPost) - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(w, nodeGroupCreateMasterResponse) + fmt.Fprint(w, nodeGroupCreateMasterResponse) }) } -func handleCreateNodeGroupBadSizes(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups", func(w http.ResponseWriter, r *http.Request) { +func handleCreateNodeGroupBadSizes(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodPost) - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusConflict) - fmt.Fprintf(w, nodeGroupCreateBadSizesResponse) + fmt.Fprint(w, nodeGroupCreateBadSizesResponse) }) } -func handleUpdateNodeGroupSuccess(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups/"+nodeGroup2UUID, func(w http.ResponseWriter, r *http.Request) { +func handleUpdateNodeGroupSuccess(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups/"+nodeGroup2UUID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodPatch) - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, nodeGroupUpdateResponse) + fmt.Fprint(w, nodeGroupUpdateResponse) }) } -func handleUpdateNodeGroupInternal(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups/"+nodeGroup2UUID, func(w http.ResponseWriter, r *http.Request) { +func handleUpdateNodeGroupInternal(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups/"+nodeGroup2UUID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodPatch) - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(w, nodeGroupUpdateInternalResponse) + fmt.Fprint(w, nodeGroupUpdateInternalResponse) }) } -func handleUpdateNodeGroupBadField(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups/"+nodeGroup2UUID, func(w http.ResponseWriter, r *http.Request) { +func handleUpdateNodeGroupBadField(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups/"+nodeGroup2UUID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodPatch) - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(w, nodeGroupUpdateBadFieldResponse) + fmt.Fprint(w, nodeGroupUpdateBadFieldResponse) }) } -func handleUpdateNodeGroupBadMin(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups/"+nodeGroup2UUID, func(w http.ResponseWriter, r *http.Request) { +func handleUpdateNodeGroupBadMin(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups/"+nodeGroup2UUID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodPatch) - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusConflict) - fmt.Fprintf(w, nodeGroupUpdateBadMinResponse) + fmt.Fprint(w, nodeGroupUpdateBadMinResponse) }) } -func handleDeleteNodeGroupSuccess(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups/"+nodeGroup2UUID, func(w http.ResponseWriter, r *http.Request) { +func handleDeleteNodeGroupSuccess(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups/"+nodeGroup2UUID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodDelete) - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) } -func handleDeleteNodeGroupNotFound(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups/"+badNodeGroupUUID, func(w http.ResponseWriter, r *http.Request) { +func handleDeleteNodeGroupNotFound(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups/"+badNodeGroupUUID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodDelete) - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusNotFound) - fmt.Fprintf(w, nodeGroupDeleteNotFoundResponse) + fmt.Fprint(w, nodeGroupDeleteNotFoundResponse) }) } -func handleDeleteNodeGroupClusterNotFound(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/"+badClusterUUID+"/nodegroups/"+badNodeGroupUUID, func(w http.ResponseWriter, r *http.Request) { +func handleDeleteNodeGroupClusterNotFound(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/"+badClusterUUID+"/nodegroups/"+badNodeGroupUUID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodDelete) - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusNotFound) - fmt.Fprintf(w, nodeGroupDeleteClusterNotFoundResponse) + fmt.Fprint(w, nodeGroupDeleteClusterNotFoundResponse) }) } -func handleDeleteNodeGroupDefault(t *testing.T) { - th.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups/"+nodeGroup2UUID, func(w http.ResponseWriter, r *http.Request) { +func handleDeleteNodeGroupDefault(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/clusters/"+clusterUUID+"/nodegroups/"+nodeGroup2UUID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, http.MethodDelete) - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(w, nodeGroupDeleteDefaultResponse) + fmt.Fprint(w, nodeGroupDeleteDefaultResponse) }) } diff --git a/openstack/containerinfra/v1/nodegroups/testing/requests_test.go b/openstack/containerinfra/v1/nodegroups/testing/requests_test.go index 6e929a2e17..d8429fe8bf 100644 --- a/openstack/containerinfra/v1/nodegroups/testing/requests_test.go +++ b/openstack/containerinfra/v1/nodegroups/testing/requests_test.go @@ -8,17 +8,17 @@ import ( "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/openstack/containerinfra/v1/nodegroups" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) // TestGetNodeGroupSuccess gets a node group successfully. func TestGetNodeGroupSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - handleGetNodeGroupSuccess(t) + handleGetNodeGroupSuccess(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" ng, err := nodegroups.Get(context.TODO(), sc, clusterUUID, nodeGroup1UUID).Extract() @@ -29,41 +29,41 @@ func TestGetNodeGroupSuccess(t *testing.T) { // TestGetNodeGroupNotFound tries to get a node group which does not exist. func TestGetNodeGroupNotFound(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - handleGetNodeGroupNotFound(t) + handleGetNodeGroupNotFound(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" _, err := nodegroups.Get(context.TODO(), sc, clusterUUID, badNodeGroupUUID).Extract() - th.AssertEquals(t, true, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) } // TestGetNodeGroupClusterNotFound tries to get a node group in // a cluster which does not exist. func TestGetNodeGroupClusterNotFound(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - handleGetNodeGroupClusterNotFound(t) + handleGetNodeGroupClusterNotFound(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" _, err := nodegroups.Get(context.TODO(), sc, badClusterUUID, badNodeGroupUUID).Extract() - th.AssertEquals(t, true, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) } // TestListNodeGroupsSuccess lists the node groups of a cluster successfully. func TestListNodeGroupsSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - handleListNodeGroupsSuccess(t) + handleListNodeGroupsSuccess(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" ngPages, err := nodegroups.List(sc, clusterUUID, nodegroups.ListOpts{}).AllPages(context.TODO()) @@ -80,12 +80,12 @@ func TestListNodeGroupsSuccess(t *testing.T) { // with each returned page limited to one node group and // also giving a URL to get the next page. func TestListNodeGroupsLimitSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - handleListNodeGroupsLimitSuccess(t) + handleListNodeGroupsLimitSuccess(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" listOpts := nodegroups.ListOpts{Limit: 1} @@ -102,26 +102,26 @@ func TestListNodeGroupsLimitSuccess(t *testing.T) { // TestListNodeGroupsClusterNotFound tries to list node groups // of a cluster which does not exist. func TestListNodeGroupsClusterNotFound(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - handleListNodeGroupsClusterNotFound(t) + handleListNodeGroupsClusterNotFound(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" _, err := nodegroups.List(sc, clusterUUID, nodegroups.ListOpts{}).AllPages(context.TODO()) - th.AssertEquals(t, true, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) } // TestCreateNodeGroupSuccess creates a node group successfully. func TestCreateNodeGroupSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - handleCreateNodeGroupSuccess(t) + handleCreateNodeGroupSuccess(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" createOpts := nodegroups.CreateOpts{ @@ -137,12 +137,12 @@ func TestCreateNodeGroupSuccess(t *testing.T) { // TestCreateNodeGroupDuplicate creates a node group with // the same name as an existing one. func TestCreateNodeGroupDuplicate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - handleCreateNodeGroupDuplicate(t) + handleCreateNodeGroupDuplicate(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" createOpts := nodegroups.CreateOpts{ @@ -150,18 +150,18 @@ func TestCreateNodeGroupDuplicate(t *testing.T) { } _, err := nodegroups.Create(context.TODO(), sc, clusterUUID, createOpts).Extract() - th.AssertEquals(t, true, gophercloud.ResponseCodeIs(err, http.StatusConflict)) + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusConflict)) } // TestCreateNodeGroupMaster creates a node group with // role=master which is not allowed. func TestCreateNodeGroupMaster(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - handleCreateNodeGroupMaster(t) + handleCreateNodeGroupMaster(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" createOpts := nodegroups.CreateOpts{ @@ -170,18 +170,18 @@ func TestCreateNodeGroupMaster(t *testing.T) { } _, err := nodegroups.Create(context.TODO(), sc, clusterUUID, createOpts).Extract() - th.AssertEquals(t, true, gophercloud.ResponseCodeIs(err, http.StatusBadRequest)) + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusBadRequest)) } // TestCreateNodeGroupBadSizes creates a node group with // min_nodes greater than max_nodes. func TestCreateNodeGroupBadSizes(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - handleCreateNodeGroupBadSizes(t) + handleCreateNodeGroupBadSizes(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" maxNodes := 3 @@ -192,17 +192,17 @@ func TestCreateNodeGroupBadSizes(t *testing.T) { } _, err := nodegroups.Create(context.TODO(), sc, clusterUUID, createOpts).Extract() - th.AssertEquals(t, true, gophercloud.ResponseCodeIs(err, http.StatusConflict)) + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusConflict)) } // TestUpdateNodeGroupSuccess updates a node group successfully. func TestUpdateNodeGroupSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - handleUpdateNodeGroupSuccess(t) + handleUpdateNodeGroupSuccess(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" updateOpts := []nodegroups.UpdateOptsBuilder{ @@ -221,12 +221,12 @@ func TestUpdateNodeGroupSuccess(t *testing.T) { // TestUpdateNodeGroupInternal tries to update an internal // property of the node group. func TestUpdateNodeGroupInternal(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - handleUpdateNodeGroupInternal(t) + handleUpdateNodeGroupInternal(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" updateOpts := []nodegroups.UpdateOptsBuilder{ @@ -238,18 +238,18 @@ func TestUpdateNodeGroupInternal(t *testing.T) { } _, err := nodegroups.Update(context.TODO(), sc, clusterUUID, nodeGroup2UUID, updateOpts).Extract() - th.AssertEquals(t, true, gophercloud.ResponseCodeIs(err, http.StatusBadRequest)) + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusBadRequest)) } // TestUpdateNodeGroupBadField tries to update a // field of the node group that does not exist. func TestUpdateNodeGroupBadField(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - handleUpdateNodeGroupBadField(t) + handleUpdateNodeGroupBadField(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" updateOpts := []nodegroups.UpdateOptsBuilder{ @@ -261,18 +261,18 @@ func TestUpdateNodeGroupBadField(t *testing.T) { } _, err := nodegroups.Update(context.TODO(), sc, clusterUUID, nodeGroup2UUID, updateOpts).Extract() - th.AssertEquals(t, true, gophercloud.ResponseCodeIs(err, http.StatusBadRequest)) + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusBadRequest)) } // TestUpdateNodeGroupBadMin tries to set a minimum node count // greater than the current node count func TestUpdateNodeGroupBadMin(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - handleUpdateNodeGroupBadMin(t) + handleUpdateNodeGroupBadMin(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" updateOpts := []nodegroups.UpdateOptsBuilder{ @@ -284,17 +284,17 @@ func TestUpdateNodeGroupBadMin(t *testing.T) { } _, err := nodegroups.Update(context.TODO(), sc, clusterUUID, nodeGroup2UUID, updateOpts).Extract() - th.AssertEquals(t, true, gophercloud.ResponseCodeIs(err, http.StatusConflict)) + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusConflict)) } // TestDeleteNodeGroupSuccess deletes a node group successfully. func TestDeleteNodeGroupSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - handleDeleteNodeGroupSuccess(t) + handleDeleteNodeGroupSuccess(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" err := nodegroups.Delete(context.TODO(), sc, clusterUUID, nodeGroup2UUID).ExtractErr() @@ -303,42 +303,42 @@ func TestDeleteNodeGroupSuccess(t *testing.T) { // TestDeleteNodeGroupNotFound tries to delete a node group that does not exist. func TestDeleteNodeGroupNotFound(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - handleDeleteNodeGroupNotFound(t) + handleDeleteNodeGroupNotFound(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" err := nodegroups.Delete(context.TODO(), sc, clusterUUID, badNodeGroupUUID).ExtractErr() - th.AssertEquals(t, true, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) } // TestDeleteNodeGroupClusterNotFound tries to delete a node group in a cluster that does not exist. func TestDeleteNodeGroupClusterNotFound(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - handleDeleteNodeGroupClusterNotFound(t) + handleDeleteNodeGroupClusterNotFound(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" err := nodegroups.Delete(context.TODO(), sc, badClusterUUID, badNodeGroupUUID).ExtractErr() - th.AssertEquals(t, true, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) } // TestDeleteNodeGroupDefault tries to delete a protected default node group. func TestDeleteNodeGroupDefault(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - handleDeleteNodeGroupDefault(t) + handleDeleteNodeGroupDefault(t, fakeServer) - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" err := nodegroups.Delete(context.TODO(), sc, clusterUUID, nodeGroup2UUID).ExtractErr() - th.AssertEquals(t, true, gophercloud.ResponseCodeIs(err, http.StatusBadRequest)) + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusBadRequest)) } diff --git a/openstack/containerinfra/v1/quotas/testing/fixtures_test.go b/openstack/containerinfra/v1/quotas/testing/fixtures_test.go index 2aad3abb66..f4ead5d06a 100644 --- a/openstack/containerinfra/v1/quotas/testing/fixtures_test.go +++ b/openstack/containerinfra/v1/quotas/testing/fixtures_test.go @@ -6,7 +6,7 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) const projectID = "aa5436ab58144c768ca4e9d2e9f5c3b2" @@ -22,10 +22,10 @@ var CreateResponse = fmt.Sprintf(` "id": 26 }`, projectID) -func HandleCreateQuotaSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v1/quotas", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateQuotaSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v1/quotas", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.Header().Add("X-OpenStack-Request-Id", requestUUID) diff --git a/openstack/containerinfra/v1/quotas/testing/requests_test.go b/openstack/containerinfra/v1/quotas/testing/requests_test.go index 75dde3000f..16d966cfa5 100644 --- a/openstack/containerinfra/v1/quotas/testing/requests_test.go +++ b/openstack/containerinfra/v1/quotas/testing/requests_test.go @@ -6,14 +6,14 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/containerinfra/v1/quotas" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestCreateQuota(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleCreateQuotaSuccessfully(t) + HandleCreateQuotaSuccessfully(t, fakeServer) opts := quotas.CreateOpts{ ProjectID: "aa5436ab58144c768ca4e9d2e9f5c3b2", @@ -21,7 +21,7 @@ func TestCreateQuota(t *testing.T) { HardLimit: 10, } - sc := fake.ServiceClient() + sc := client.ServiceClient(fakeServer) sc.Endpoint = sc.Endpoint + "v1/" res := quotas.Create(context.TODO(), sc, opts) diff --git a/openstack/db/v1/configurations/testing/requests_test.go b/openstack/db/v1/configurations/testing/requests_test.go index 49d928e94e..6c8d9f599f 100644 --- a/openstack/db/v1/configurations/testing/requests_test.go +++ b/openstack/db/v1/configurations/testing/requests_test.go @@ -8,7 +8,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/db/v1/instances" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" "github.com/gophercloud/gophercloud/v2/testhelper/fixture" ) @@ -27,12 +27,12 @@ var ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - fixture.SetupHandler(t, _baseURL, "GET", "", ListConfigsJSON, 200) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + fixture.SetupHandler(t, fakeServer, _baseURL, "GET", "", ListConfigsJSON, 200) count := 0 - err := configurations.List(fake.ServiceClient()).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := configurations.List(client.ServiceClient(fakeServer)).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := configurations.ExtractConfigs(page) th.AssertNoErr(t, err) @@ -48,19 +48,19 @@ func TestList(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - fixture.SetupHandler(t, resURL, "GET", "", GetConfigJSON, 200) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + fixture.SetupHandler(t, fakeServer, resURL, "GET", "", GetConfigJSON, 200) - config, err := configurations.Get(context.TODO(), fake.ServiceClient(), configID).Extract() + config, err := configurations.Get(context.TODO(), client.ServiceClient(fakeServer), configID).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, &ExampleConfig, config) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - fixture.SetupHandler(t, _baseURL, "POST", CreateReq, CreateConfigJSON, 200) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + fixture.SetupHandler(t, fakeServer, _baseURL, "POST", CreateReq, CreateConfigJSON, 200) opts := configurations.CreateOpts{ Datastore: &configurations.DatastoreOpts{ @@ -75,15 +75,15 @@ func TestCreate(t *testing.T) { }, } - config, err := configurations.Create(context.TODO(), fake.ServiceClient(), opts).Extract() + config, err := configurations.Create(context.TODO(), client.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, &ExampleConfigWithValues, config) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - fixture.SetupHandler(t, resURL, "PATCH", UpdateReq, "", 200) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + fixture.SetupHandler(t, fakeServer, resURL, "PATCH", UpdateReq, "", 200) opts := configurations.UpdateOpts{ Values: map[string]any{ @@ -91,14 +91,14 @@ func TestUpdate(t *testing.T) { }, } - err := configurations.Update(context.TODO(), fake.ServiceClient(), configID, opts).ExtractErr() + err := configurations.Update(context.TODO(), client.ServiceClient(fakeServer), configID, opts).ExtractErr() th.AssertNoErr(t, err) } func TestReplace(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - fixture.SetupHandler(t, resURL, "PUT", UpdateReq, "", 202) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + fixture.SetupHandler(t, fakeServer, resURL, "PUT", UpdateReq, "", 202) opts := configurations.UpdateOpts{ Values: map[string]any{ @@ -106,23 +106,23 @@ func TestReplace(t *testing.T) { }, } - err := configurations.Replace(context.TODO(), fake.ServiceClient(), configID, opts).ExtractErr() + err := configurations.Replace(context.TODO(), client.ServiceClient(fakeServer), configID, opts).ExtractErr() th.AssertNoErr(t, err) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - fixture.SetupHandler(t, resURL, "DELETE", "", "", 202) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + fixture.SetupHandler(t, fakeServer, resURL, "DELETE", "", "", 202) - err := configurations.Delete(context.TODO(), fake.ServiceClient(), configID).ExtractErr() + err := configurations.Delete(context.TODO(), client.ServiceClient(fakeServer), configID).ExtractErr() th.AssertNoErr(t, err) } func TestListInstances(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - fixture.SetupHandler(t, resURL+"/instances", "GET", "", ListInstancesJSON, 200) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + fixture.SetupHandler(t, fakeServer, resURL+"/instances", "GET", "", ListInstancesJSON, 200) expectedInstance := instances.Instance{ ID: "d4603f69-ec7e-4e9b-803f-600b9205576f", @@ -130,7 +130,7 @@ func TestListInstances(t *testing.T) { } pages := 0 - err := configurations.ListInstances(fake.ServiceClient(), configID).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := configurations.ListInstances(client.ServiceClient(fakeServer), configID).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := instances.ExtractInstances(page) @@ -138,7 +138,7 @@ func TestListInstances(t *testing.T) { return false, err } - th.AssertDeepEquals(t, actual, []instances.Instance{expectedInstance}) + th.AssertDeepEquals(t, []instances.Instance{expectedInstance}, actual) return true, nil }) @@ -148,12 +148,12 @@ func TestListInstances(t *testing.T) { } func TestListDSParams(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - fixture.SetupHandler(t, dsParamListURL, "GET", "", ListParamsJSON, 200) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + fixture.SetupHandler(t, fakeServer, dsParamListURL, "GET", "", ListParamsJSON, 200) pages := 0 - err := configurations.ListDatastoreParams(fake.ServiceClient(), dsID, versionID).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := configurations.ListDatastoreParams(client.ServiceClient(fakeServer), dsID, versionID).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := configurations.ExtractParams(page) @@ -178,11 +178,11 @@ func TestListDSParams(t *testing.T) { } func TestGetDSParam(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - fixture.SetupHandler(t, dsParamGetURL, "GET", "", GetParamJSON, 200) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + fixture.SetupHandler(t, fakeServer, dsParamGetURL, "GET", "", GetParamJSON, 200) - param, err := configurations.GetDatastoreParam(context.TODO(), fake.ServiceClient(), dsID, versionID, paramID).Extract() + param, err := configurations.GetDatastoreParam(context.TODO(), client.ServiceClient(fakeServer), dsID, versionID, paramID).Extract() th.AssertNoErr(t, err) expected := &configurations.Param{ @@ -193,12 +193,12 @@ func TestGetDSParam(t *testing.T) { } func TestListGlobalParams(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - fixture.SetupHandler(t, globalParamListURL, "GET", "", ListParamsJSON, 200) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + fixture.SetupHandler(t, fakeServer, globalParamListURL, "GET", "", ListParamsJSON, 200) pages := 0 - err := configurations.ListGlobalParams(fake.ServiceClient(), versionID).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := configurations.ListGlobalParams(client.ServiceClient(fakeServer), versionID).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := configurations.ExtractParams(page) @@ -223,11 +223,11 @@ func TestListGlobalParams(t *testing.T) { } func TestGetGlobalParam(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - fixture.SetupHandler(t, globalParamGetURL, "GET", "", GetParamJSON, 200) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + fixture.SetupHandler(t, fakeServer, globalParamGetURL, "GET", "", GetParamJSON, 200) - param, err := configurations.GetGlobalParam(context.TODO(), fake.ServiceClient(), versionID, paramID).Extract() + param, err := configurations.GetGlobalParam(context.TODO(), client.ServiceClient(fakeServer), versionID, paramID).Extract() th.AssertNoErr(t, err) expected := &configurations.Param{ diff --git a/openstack/db/v1/databases/results.go b/openstack/db/v1/databases/results.go index 7ca9da7f8b..ddd57a986a 100644 --- a/openstack/db/v1/databases/results.go +++ b/openstack/db/v1/databases/results.go @@ -44,7 +44,7 @@ func (page DBPage) IsEmpty() (bool, error) { } // NextPageURL will retrieve the next page URL. -func (page DBPage) NextPageURL() (string, error) { +func (page DBPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"databases_links"` } diff --git a/openstack/db/v1/databases/testing/fixtures_test.go b/openstack/db/v1/databases/testing/fixtures_test.go index b7362c0864..bedeeacab5 100644 --- a/openstack/db/v1/databases/testing/fixtures_test.go +++ b/openstack/db/v1/databases/testing/fixtures_test.go @@ -3,6 +3,7 @@ package testing import ( "testing" + th "github.com/gophercloud/gophercloud/v2/testhelper" "github.com/gophercloud/gophercloud/v2/testhelper/fixture" ) @@ -48,14 +49,14 @@ var listDBsResp = ` } ` -func HandleCreate(t *testing.T) { - fixture.SetupHandler(t, resURL, "POST", createDBsReq, "", 202) +func HandleCreate(t *testing.T, fakeServer th.FakeServer) { + fixture.SetupHandler(t, fakeServer, resURL, "POST", createDBsReq, "", 202) } -func HandleList(t *testing.T) { - fixture.SetupHandler(t, resURL, "GET", "", listDBsResp, 200) +func HandleList(t *testing.T, fakeServer th.FakeServer) { + fixture.SetupHandler(t, fakeServer, resURL, "GET", "", listDBsResp, 200) } -func HandleDelete(t *testing.T) { - fixture.SetupHandler(t, resURL+"/{dbName}", "DELETE", "", "", 202) +func HandleDelete(t *testing.T, fakeServer th.FakeServer) { + fixture.SetupHandler(t, fakeServer, resURL+"/{dbName}", "DELETE", "", "", 202) } diff --git a/openstack/db/v1/databases/testing/requests_test.go b/openstack/db/v1/databases/testing/requests_test.go index d509a7f1a9..d5d39f60ed 100644 --- a/openstack/db/v1/databases/testing/requests_test.go +++ b/openstack/db/v1/databases/testing/requests_test.go @@ -7,27 +7,27 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/db/v1/databases" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreate(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreate(t, fakeServer) opts := databases.BatchCreateOpts{ databases.CreateOpts{Name: "testingdb", CharSet: "utf8", Collate: "utf8_general_ci"}, databases.CreateOpts{Name: "sampledb"}, } - res := databases.Create(context.TODO(), fake.ServiceClient(), instanceID, opts) + res := databases.Create(context.TODO(), client.ServiceClient(fakeServer), instanceID, opts) th.AssertNoErr(t, res.Err) } func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleList(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleList(t, fakeServer) expectedDBs := []databases.Database{ {Name: "anotherexampledb"}, @@ -38,7 +38,7 @@ func TestList(t *testing.T) { } pages := 0 - err := databases.List(fake.ServiceClient(), instanceID).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := databases.List(client.ServiceClient(fakeServer), instanceID).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := databases.ExtractDBs(page) @@ -59,10 +59,10 @@ func TestList(t *testing.T) { } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDelete(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDelete(t, fakeServer) - err := databases.Delete(context.TODO(), fake.ServiceClient(), instanceID, "{dbName}").ExtractErr() + err := databases.Delete(context.TODO(), client.ServiceClient(fakeServer), instanceID, "{dbName}").ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/db/v1/datastores/testing/requests_test.go b/openstack/db/v1/datastores/testing/requests_test.go index 47e3dbfae4..e902a6f255 100644 --- a/openstack/db/v1/datastores/testing/requests_test.go +++ b/openstack/db/v1/datastores/testing/requests_test.go @@ -7,18 +7,18 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/db/v1/datastores" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" "github.com/gophercloud/gophercloud/v2/testhelper/fixture" ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - fixture.SetupHandler(t, "/datastores", "GET", "", ListDSResp, 200) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + fixture.SetupHandler(t, fakeServer, "/datastores", "GET", "", ListDSResp, 200) pages := 0 - err := datastores.List(fake.ServiceClient()).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := datastores.List(client.ServiceClient(fakeServer)).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := datastores.ExtractDatastores(page) @@ -36,23 +36,23 @@ func TestList(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - fixture.SetupHandler(t, "/datastores/{dsID}", "GET", "", GetDSResp, 200) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + fixture.SetupHandler(t, fakeServer, "/datastores/{dsID}", "GET", "", GetDSResp, 200) - ds, err := datastores.Get(context.TODO(), fake.ServiceClient(), "{dsID}").Extract() + ds, err := datastores.Get(context.TODO(), client.ServiceClient(fakeServer), "{dsID}").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, &ExampleDatastore, ds) } func TestListVersions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - fixture.SetupHandler(t, "/datastores/{dsID}/versions", "GET", "", ListVersionsResp, 200) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + fixture.SetupHandler(t, fakeServer, "/datastores/{dsID}/versions", "GET", "", ListVersionsResp, 200) pages := 0 - err := datastores.ListVersions(fake.ServiceClient(), "{dsID}").EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := datastores.ListVersions(client.ServiceClient(fakeServer), "{dsID}").EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := datastores.ExtractVersions(page) @@ -70,11 +70,11 @@ func TestListVersions(t *testing.T) { } func TestGetVersion(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - fixture.SetupHandler(t, "/datastores/{dsID}/versions/{versionID}", "GET", "", GetVersionResp, 200) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + fixture.SetupHandler(t, fakeServer, "/datastores/{dsID}/versions/{versionID}", "GET", "", GetVersionResp, 200) - ds, err := datastores.GetVersion(context.TODO(), fake.ServiceClient(), "{dsID}", "{versionID}").Extract() + ds, err := datastores.GetVersion(context.TODO(), client.ServiceClient(fakeServer), "{dsID}", "{versionID}").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, &ExampleVersion1, ds) } diff --git a/openstack/db/v1/flavors/results.go b/openstack/db/v1/flavors/results.go index b3a6d4e304..9b23c6ae1d 100644 --- a/openstack/db/v1/flavors/results.go +++ b/openstack/db/v1/flavors/results.go @@ -54,7 +54,7 @@ func (page FlavorPage) IsEmpty() (bool, error) { } // NextPageURL uses the response's embedded link reference to navigate to the next page of results. -func (page FlavorPage) NextPageURL() (string, error) { +func (page FlavorPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"flavors_links"` } diff --git a/openstack/db/v1/flavors/testing/fixtures_test.go b/openstack/db/v1/flavors/testing/fixtures_test.go index 048b23592e..8ba73b8eb8 100644 --- a/openstack/db/v1/flavors/testing/fixtures_test.go +++ b/openstack/db/v1/flavors/testing/fixtures_test.go @@ -4,6 +4,7 @@ import ( "fmt" "testing" + th "github.com/gophercloud/gophercloud/v2/testhelper" "github.com/gophercloud/gophercloud/v2/testhelper/fixture" ) @@ -43,10 +44,10 @@ var ( getFlavorResp = fmt.Sprintf(`{"flavor": %s}`, flavor1) ) -func HandleList(t *testing.T) { - fixture.SetupHandler(t, _baseURL, "GET", "", listFlavorsResp, 200) +func HandleList(t *testing.T, fakeServer th.FakeServer) { + fixture.SetupHandler(t, fakeServer, _baseURL, "GET", "", listFlavorsResp, 200) } -func HandleGet(t *testing.T) { - fixture.SetupHandler(t, resURL, "GET", "", getFlavorResp, 200) +func HandleGet(t *testing.T, fakeServer th.FakeServer) { + fixture.SetupHandler(t, fakeServer, resURL, "GET", "", getFlavorResp, 200) } diff --git a/openstack/db/v1/flavors/testing/requests_test.go b/openstack/db/v1/flavors/testing/requests_test.go index c4a419627a..a1a9ecd3ea 100644 --- a/openstack/db/v1/flavors/testing/requests_test.go +++ b/openstack/db/v1/flavors/testing/requests_test.go @@ -8,16 +8,16 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/db/v1/flavors" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestListFlavors(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleList(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleList(t, fakeServer) pages := 0 - err := flavors.List(fake.ServiceClient()).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := flavors.List(client.ServiceClient(fakeServer)).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := flavors.ExtractFlavors(page) @@ -87,11 +87,11 @@ func TestListFlavors(t *testing.T) { } func TestGetFlavor(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGet(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGet(t, fakeServer) - actual, err := flavors.Get(context.TODO(), fake.ServiceClient(), flavorID).Extract() + actual, err := flavors.Get(context.TODO(), client.ServiceClient(fakeServer), flavorID).Extract() th.AssertNoErr(t, err) expected := &flavors.Flavor{ diff --git a/openstack/db/v1/instances/requests.go b/openstack/db/v1/instances/requests.go index b4e946025f..7e66cb26c0 100644 --- a/openstack/db/v1/instances/requests.go +++ b/openstack/db/v1/instances/requests.go @@ -56,8 +56,8 @@ type CreateOpts struct { // Either the integer UUID (in string form) of the flavor, or its URI // reference as specified in the response from the List() call. Required. FlavorRef string - // Specifies the volume size in gigabytes (GB). The value must be between 1 - // and 300. Required. + // Specifies the volume size in gigabytes (GB). The value must be >= 1. The upper limit is specific to each OpenStack environment. + // Required. Size int // Specifies the volume type. VolumeType string @@ -77,11 +77,11 @@ type CreateOpts struct { // ToInstanceCreateMap will render a JSON map. func (opts CreateOpts) ToInstanceCreateMap() (map[string]any, error) { - if opts.Size > 300 || opts.Size < 1 { + if opts.Size < 1 { err := gophercloud.ErrInvalidInput{} err.Argument = "instances.CreateOpts.Size" err.Value = opts.Size - err.Info = "Size (GB) must be between 1-300" + err.Info = "Size (GB) must be >= 1" return nil, err } diff --git a/openstack/db/v1/instances/results.go b/openstack/db/v1/instances/results.go index d8903e2361..1978e63a6a 100644 --- a/openstack/db/v1/instances/results.go +++ b/openstack/db/v1/instances/results.go @@ -182,7 +182,7 @@ func (page InstancePage) IsEmpty() (bool, error) { } // NextPageURL will retrieve the next page URL. -func (page InstancePage) NextPageURL() (string, error) { +func (page InstancePage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"instances_links"` } diff --git a/openstack/db/v1/instances/testing/fixtures_test.go b/openstack/db/v1/instances/testing/fixtures_test.go index dc3b9dc0de..f31c2f5e24 100644 --- a/openstack/db/v1/instances/testing/fixtures_test.go +++ b/openstack/db/v1/instances/testing/fixtures_test.go @@ -8,6 +8,7 @@ import ( "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/openstack/db/v1/datastores" "github.com/gophercloud/gophercloud/v2/openstack/db/v1/instances" + th "github.com/gophercloud/gophercloud/v2/testhelper" "github.com/gophercloud/gophercloud/v2/testhelper/fixture" ) @@ -285,50 +286,50 @@ var expectedInstanceWithFault = instances.Instance{ }, } -func HandleCreate(t *testing.T) { - fixture.SetupHandler(t, rootURL, "POST", createReq, createResp, 200) +func HandleCreate(t *testing.T, fakeServer th.FakeServer) { + fixture.SetupHandler(t, fakeServer, rootURL, "POST", createReq, createResp, 200) } -func HandleCreateWithFault(t *testing.T) { - fixture.SetupHandler(t, rootURL, "POST", createReq, createWithFaultResp, 200) +func HandleCreateWithFault(t *testing.T, fakeServer th.FakeServer) { + fixture.SetupHandler(t, fakeServer, rootURL, "POST", createReq, createWithFaultResp, 200) } -func HandleList(t *testing.T) { - fixture.SetupHandler(t, rootURL, "GET", "", listInstancesResp, 200) +func HandleList(t *testing.T, fakeServer th.FakeServer) { + fixture.SetupHandler(t, fakeServer, rootURL, "GET", "", listInstancesResp, 200) } -func HandleGet(t *testing.T) { - fixture.SetupHandler(t, resURL, "GET", "", getInstanceResp, 200) +func HandleGet(t *testing.T, fakeServer th.FakeServer) { + fixture.SetupHandler(t, fakeServer, resURL, "GET", "", getInstanceResp, 200) } -func HandleDelete(t *testing.T) { - fixture.SetupHandler(t, resURL, "DELETE", "", "", 202) +func HandleDelete(t *testing.T, fakeServer th.FakeServer) { + fixture.SetupHandler(t, fakeServer, resURL, "DELETE", "", "", 202) } -func HandleEnableRoot(t *testing.T) { - fixture.SetupHandler(t, uRootURL, "POST", "", enableUserResp, 200) +func HandleEnableRoot(t *testing.T, fakeServer th.FakeServer) { + fixture.SetupHandler(t, fakeServer, uRootURL, "POST", "", enableUserResp, 200) } -func HandleIsRootEnabled(t *testing.T) { - fixture.SetupHandler(t, uRootURL, "GET", "", isUserEnabledResp, 200) +func HandleIsRootEnabled(t *testing.T, fakeServer th.FakeServer) { + fixture.SetupHandler(t, fakeServer, uRootURL, "GET", "", isUserEnabledResp, 200) } -func HandleRestart(t *testing.T) { - fixture.SetupHandler(t, aURL, "POST", restartReq, "", 202) +func HandleRestart(t *testing.T, fakeServer th.FakeServer) { + fixture.SetupHandler(t, fakeServer, aURL, "POST", restartReq, "", 202) } -func HandleResize(t *testing.T) { - fixture.SetupHandler(t, aURL, "POST", resizeReq, "", 202) +func HandleResize(t *testing.T, fakeServer th.FakeServer) { + fixture.SetupHandler(t, fakeServer, aURL, "POST", resizeReq, "", 202) } -func HandleResizeVol(t *testing.T) { - fixture.SetupHandler(t, aURL, "POST", resizeVolReq, "", 202) +func HandleResizeVol(t *testing.T, fakeServer th.FakeServer) { + fixture.SetupHandler(t, fakeServer, aURL, "POST", resizeVolReq, "", 202) } -func HandleAttachConfigurationGroup(t *testing.T) { - fixture.SetupHandler(t, resURL, "PUT", attachConfigurationGroupReq, "", 202) +func HandleAttachConfigurationGroup(t *testing.T, fakeServer th.FakeServer) { + fixture.SetupHandler(t, fakeServer, resURL, "PUT", attachConfigurationGroupReq, "", 202) } -func HandleDetachConfigurationGroup(t *testing.T) { - fixture.SetupHandler(t, resURL, "PUT", detachConfigurationGroupReq, "", 202) +func HandleDetachConfigurationGroup(t *testing.T, fakeServer th.FakeServer) { + fixture.SetupHandler(t, fakeServer, resURL, "PUT", detachConfigurationGroupReq, "", 202) } diff --git a/openstack/db/v1/instances/testing/requests_test.go b/openstack/db/v1/instances/testing/requests_test.go index 1e08a796ac..01eb9c6f71 100644 --- a/openstack/db/v1/instances/testing/requests_test.go +++ b/openstack/db/v1/instances/testing/requests_test.go @@ -9,13 +9,13 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/db/v1/users" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreate(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreate(t, fakeServer) opts := instances.CreateOpts{ AvailabilityZone: "us-east1", @@ -39,16 +39,16 @@ func TestCreate(t *testing.T) { VolumeType: "ssd", } - instance, err := instances.Create(context.TODO(), fake.ServiceClient(), opts).Extract() + instance, err := instances.Create(context.TODO(), client.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, &expectedInstance, instance) } func TestCreateWithFault(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateWithFault(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateWithFault(t, fakeServer) opts := instances.CreateOpts{ AvailabilityZone: "us-east1", @@ -72,19 +72,45 @@ func TestCreateWithFault(t *testing.T) { VolumeType: "ssd", } - instance, err := instances.Create(context.TODO(), fake.ServiceClient(), opts).Extract() + instance, err := instances.Create(context.TODO(), client.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, &expectedInstanceWithFault, instance) } +func TestCreateOptsToInstanceCreateMapAllowsSizeAbove300(t *testing.T) { + opts := instances.CreateOpts{ + FlavorRef: "1", + Size: 301, + } + + body, err := opts.ToInstanceCreateMap() + th.AssertNoErr(t, err) + + instance := body["instance"].(map[string]any) + volume := instance["volume"].(map[string]any) + th.AssertEquals(t, 301, volume["size"]) +} + +func TestCreateOptsToInstanceCreateMapRejectsSizeBelow1(t *testing.T) { + opts := instances.CreateOpts{ + FlavorRef: "1", + Size: 0, + } + + _, err := opts.ToInstanceCreateMap() + if err == nil { + t.Fatalf("expected error for invalid size, got nil") + } +} + func TestInstanceList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleList(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleList(t, fakeServer) pages := 0 - err := instances.List(fake.ServiceClient()).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := instances.List(client.ServiceClient(fakeServer)).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := instances.ExtractInstances(page) @@ -101,89 +127,89 @@ func TestInstanceList(t *testing.T) { } func TestGetInstance(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGet(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGet(t, fakeServer) - instance, err := instances.Get(context.TODO(), fake.ServiceClient(), instanceID).Extract() + instance, err := instances.Get(context.TODO(), client.ServiceClient(fakeServer), instanceID).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, &expectedGetInstance, instance) } func TestDeleteInstance(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDelete(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDelete(t, fakeServer) - res := instances.Delete(context.TODO(), fake.ServiceClient(), instanceID) + res := instances.Delete(context.TODO(), client.ServiceClient(fakeServer), instanceID) th.AssertNoErr(t, res.Err) } func TestEnableRootUser(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleEnableRoot(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleEnableRoot(t, fakeServer) expected := &users.User{Name: "root", Password: "secretsecret"} - user, err := instances.EnableRootUser(context.TODO(), fake.ServiceClient(), instanceID).Extract() + user, err := instances.EnableRootUser(context.TODO(), client.ServiceClient(fakeServer), instanceID).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, expected, user) } func TestIsRootEnabled(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleIsRootEnabled(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleIsRootEnabled(t, fakeServer) - isEnabled, err := instances.IsRootEnabled(context.TODO(), fake.ServiceClient(), instanceID).Extract() + isEnabled, err := instances.IsRootEnabled(context.TODO(), client.ServiceClient(fakeServer), instanceID).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, true, isEnabled) + th.AssertTrue(t, isEnabled) } func TestRestart(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleRestart(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleRestart(t, fakeServer) - res := instances.Restart(context.TODO(), fake.ServiceClient(), instanceID) + res := instances.Restart(context.TODO(), client.ServiceClient(fakeServer), instanceID) th.AssertNoErr(t, res.Err) } func TestResize(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleResize(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleResize(t, fakeServer) - res := instances.Resize(context.TODO(), fake.ServiceClient(), instanceID, "2") + res := instances.Resize(context.TODO(), client.ServiceClient(fakeServer), instanceID, "2") th.AssertNoErr(t, res.Err) } func TestResizeVolume(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleResizeVol(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleResizeVol(t, fakeServer) - res := instances.ResizeVolume(context.TODO(), fake.ServiceClient(), instanceID, 4) + res := instances.ResizeVolume(context.TODO(), client.ServiceClient(fakeServer), instanceID, 4) th.AssertNoErr(t, res.Err) } func TestAttachConfigurationGroup(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAttachConfigurationGroup(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAttachConfigurationGroup(t, fakeServer) - res := instances.AttachConfigurationGroup(context.TODO(), fake.ServiceClient(), instanceID, configGroupID) + res := instances.AttachConfigurationGroup(context.TODO(), client.ServiceClient(fakeServer), instanceID, configGroupID) th.AssertNoErr(t, res.Err) } func TestDetachConfigurationGroup(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDetachConfigurationGroup(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDetachConfigurationGroup(t, fakeServer) - res := instances.DetachConfigurationGroup(context.TODO(), fake.ServiceClient(), instanceID) + res := instances.DetachConfigurationGroup(context.TODO(), client.ServiceClient(fakeServer), instanceID) th.AssertNoErr(t, res.Err) } diff --git a/openstack/db/v1/quotas/doc.go b/openstack/db/v1/quotas/doc.go new file mode 100644 index 0000000000..a4b62479d2 --- /dev/null +++ b/openstack/db/v1/quotas/doc.go @@ -0,0 +1,7 @@ +// Package quotas provides information and interaction with the database API +// resource in the OpenStack Database service. +// +// Quotas define operational limits, such as the maximum number of database +// instances or volume storage allowed, to manage resource usage within a +// single tenant environment in the OpenStack Database service. +package quotas diff --git a/openstack/db/v1/quotas/requests.go b/openstack/db/v1/quotas/requests.go new file mode 100644 index 0000000000..185e4e50b1 --- /dev/null +++ b/openstack/db/v1/quotas/requests.go @@ -0,0 +1,14 @@ +package quotas + +import ( + "context" + + "github.com/gophercloud/gophercloud/v2" +) + +// Get retrieves the details of quotas for a specified tenant. +func Get(ctx context.Context, client *gophercloud.ServiceClient, projectID string) (r GetResult) { + resp, err := client.Get(ctx, baseURL(client, projectID), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/db/v1/quotas/results.go b/openstack/db/v1/quotas/results.go new file mode 100644 index 0000000000..ae7312df8d --- /dev/null +++ b/openstack/db/v1/quotas/results.go @@ -0,0 +1,29 @@ +package quotas + +import ( + "github.com/gophercloud/gophercloud/v2" +) + +// QuotaDetail represents a Quota API resource. +type QuotaDetail struct { + Resource string + Limit int + InUse int `json:"in_use"` + Reserved int +} + +// GetResult is the result of a Get operation. Call its Extract method to +// interpret the result as a []QuotaDetail. +type GetResult struct { + gophercloud.Result +} + +// Extract interprets a GetResult as a []QuotaDetail. +// An error is returned if the original call or the extraction failed. +func (r GetResult) Extract() ([]QuotaDetail, error) { + var s struct { + Quotas []QuotaDetail `json:"quotas"` + } + err := r.ExtractInto(&s) + return s.Quotas, err +} diff --git a/openstack/db/v1/quotas/testing/doc.go b/openstack/db/v1/quotas/testing/doc.go new file mode 100644 index 0000000000..e8785db231 --- /dev/null +++ b/openstack/db/v1/quotas/testing/doc.go @@ -0,0 +1,2 @@ +// db_quotas_v1 +package testing diff --git a/openstack/db/v1/quotas/testing/fixtures_test.go b/openstack/db/v1/quotas/testing/fixtures_test.go new file mode 100644 index 0000000000..e2fe2a285e --- /dev/null +++ b/openstack/db/v1/quotas/testing/fixtures_test.go @@ -0,0 +1,43 @@ +package testing + +import ( + "testing" + + th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/fixture" +) + +var ( + projectID = "{projectID}" + resURL = "/mgmt/" + "quotas/" + projectID +) + +// getQuotasResp is a sample response to a Get call. +var getQuotasResp = ` +{ + "quotas": [ + { + "in_use": 5, + "limit": 15, + "reserved": 0, + "resource": "instances" + }, + { + "in_use": 2, + "limit": 50, + "reserved": 0, + "resource": "backups" + }, + { + "in_use": 1, + "limit": 40, + "reserved": 0, + "resource": "volumes" + } + ] +} +` + +func HandleGet(t *testing.T, fakeServer th.FakeServer) { + fixture.SetupHandler(t, fakeServer, resURL, "GET", "", getQuotasResp, 200) +} diff --git a/openstack/db/v1/quotas/testing/requests_test.go b/openstack/db/v1/quotas/testing/requests_test.go new file mode 100644 index 0000000000..ed52125e38 --- /dev/null +++ b/openstack/db/v1/quotas/testing/requests_test.go @@ -0,0 +1,26 @@ +package testing + +import ( + "context" + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/db/v1/quotas" + th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/client" +) + +func TestGet(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGet(t, fakeServer) + + expectedQuotas := []quotas.QuotaDetail{ + {Resource: "instances", Limit: 15, InUse: 5, Reserved: 0}, + {Resource: "backups", Limit: 50, InUse: 2, Reserved: 0}, + {Resource: "volumes", Limit: 40, InUse: 1, Reserved: 0}, + } + + actual, err := quotas.Get(context.TODO(), client.ServiceClient(fakeServer), "e131f89a-c1d8-11ef-bfaa-370c246e2439").Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, expectedQuotas, actual) +} diff --git a/openstack/db/v1/quotas/urls.go b/openstack/db/v1/quotas/urls.go new file mode 100644 index 0000000000..e0377500ae --- /dev/null +++ b/openstack/db/v1/quotas/urls.go @@ -0,0 +1,7 @@ +package quotas + +import "github.com/gophercloud/gophercloud/v2" + +func baseURL(c *gophercloud.ServiceClient, projectID string) string { + return c.ServiceURL("mgmt", "quotas", projectID) +} diff --git a/openstack/db/v1/users/results.go b/openstack/db/v1/users/results.go index 650ddc076f..9ffe105db2 100644 --- a/openstack/db/v1/users/results.go +++ b/openstack/db/v1/users/results.go @@ -44,7 +44,7 @@ func (page UserPage) IsEmpty() (bool, error) { } // NextPageURL will retrieve the next page URL. -func (page UserPage) NextPageURL() (string, error) { +func (page UserPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"users_links"` } diff --git a/openstack/db/v1/users/testing/fixtures_test.go b/openstack/db/v1/users/testing/fixtures_test.go index 961e4f6ecd..db758d8040 100644 --- a/openstack/db/v1/users/testing/fixtures_test.go +++ b/openstack/db/v1/users/testing/fixtures_test.go @@ -4,6 +4,7 @@ import ( "fmt" "testing" + th "github.com/gophercloud/gophercloud/v2/testhelper" "github.com/gophercloud/gophercloud/v2/testhelper/fixture" ) @@ -24,14 +25,14 @@ var ( listResp = fmt.Sprintf(`{"users":[%s, %s]}`, fmt.Sprintf(user1, ""), fmt.Sprintf(user2, "")) ) -func HandleCreate(t *testing.T) { - fixture.SetupHandler(t, _rootURL, "POST", createReq, "", 202) +func HandleCreate(t *testing.T, fakeServer th.FakeServer) { + fixture.SetupHandler(t, fakeServer, _rootURL, "POST", createReq, "", 202) } -func HandleList(t *testing.T) { - fixture.SetupHandler(t, _rootURL, "GET", "", listResp, 200) +func HandleList(t *testing.T, fakeServer th.FakeServer) { + fixture.SetupHandler(t, fakeServer, _rootURL, "GET", "", listResp, 200) } -func HandleDelete(t *testing.T) { - fixture.SetupHandler(t, _rootURL+"/{userName}", "DELETE", "", "", 202) +func HandleDelete(t *testing.T, fakeServer th.FakeServer) { + fixture.SetupHandler(t, fakeServer, _rootURL+"/{userName}", "DELETE", "", "", 202) } diff --git a/openstack/db/v1/users/testing/requests_test.go b/openstack/db/v1/users/testing/requests_test.go index 40713f5446..33665061e0 100644 --- a/openstack/db/v1/users/testing/requests_test.go +++ b/openstack/db/v1/users/testing/requests_test.go @@ -8,13 +8,13 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/db/v1/users" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreate(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreate(t, fakeServer) opts := users.BatchCreateOpts{ { @@ -34,14 +34,14 @@ func TestCreate(t *testing.T) { }, } - res := users.Create(context.TODO(), fake.ServiceClient(), instanceID, opts) + res := users.Create(context.TODO(), client.ServiceClient(fakeServer), instanceID, opts) th.AssertNoErr(t, res.Err) } func TestUserList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleList(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleList(t, fakeServer) expectedUsers := []users.User{ { @@ -60,7 +60,7 @@ func TestUserList(t *testing.T) { } pages := 0 - err := users.List(fake.ServiceClient(), instanceID).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := users.List(client.ServiceClient(fakeServer), instanceID).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := users.ExtractUsers(page) @@ -77,10 +77,10 @@ func TestUserList(t *testing.T) { } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDelete(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDelete(t, fakeServer) - res := users.Delete(context.TODO(), fake.ServiceClient(), instanceID, "{userName}") + res := users.Delete(context.TODO(), client.ServiceClient(fakeServer), instanceID, "{userName}") th.AssertNoErr(t, res.Err) } diff --git a/openstack/dns/v2/quotas/doc.go b/openstack/dns/v2/quotas/doc.go new file mode 100644 index 0000000000..a0c6973ea3 --- /dev/null +++ b/openstack/dns/v2/quotas/doc.go @@ -0,0 +1,28 @@ +/* +Package quotas provides the ability to retrieve DNS quotas through the Designate API. + +Example to Get a Quota Set + + projectID = "23d5d3f79dfa4f73b72b8b0b0063ec55" + quotasInfo, err := quotas.Get(context.TODO(), dnsClient, projectID).Extract() + if err != nil { + log.Fatal(err) + } + + fmt.Printf("quotas: %#v\n", quotasInfo) + +Example to Update a Quota Set + + projectID = "23d5d3f79dfa4f73b72b8b0b0063ec55" + zones := 10 + quota := "as.UpdateOpts{ + Zones: &zones, + } + quotasInfo, err := quotas.Update(context.TODO(), dnsClient, projectID, quota).Extract() + if err != nil { + log.Fatal(err) + } + + fmt.Printf("quotas: %#v\n", quotasInfo) +*/ +package quotas diff --git a/openstack/dns/v2/quotas/requests.go b/openstack/dns/v2/quotas/requests.go new file mode 100644 index 0000000000..25a8ac2381 --- /dev/null +++ b/openstack/dns/v2/quotas/requests.go @@ -0,0 +1,49 @@ +package quotas + +import ( + "context" + + "github.com/gophercloud/gophercloud/v2" +) + +// Get returns information about the quota for a given project ID. +func Get(ctx context.Context, client *gophercloud.ServiceClient, projectID string) (r Result) { + resp, err := client.Get(ctx, URL(client, projectID), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToQuotaUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts represents options used to update the DNS Quotas. +type UpdateOpts struct { + APIExporterSize *int `json:"api_export_size,omitempty"` + RecordsetRecords *int `json:"recordset_records,omitempty"` + ZoneRecords *int `json:"zone_records,omitempty"` + ZoneRecordsets *int `json:"zone_recordsets,omitempty"` + Zones *int `json:"zones,omitempty"` +} + +// ToQuotaUpdateMap builds a request body from UpdateOpts. +func (opts UpdateOpts) ToQuotaUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// Update accepts a UpdateOpts struct and updates an existing DNS Quotas using the +// values provided. +func Update(ctx context.Context, c *gophercloud.ServiceClient, projectID string, opts UpdateOptsBuilder) (r Result) { + b, err := opts.ToQuotaUpdateMap() + if err != nil { + r.Err = err + return + } + resp, err := c.Patch(ctx, URL(c, projectID), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/dns/v2/quotas/results.go b/openstack/dns/v2/quotas/results.go new file mode 100644 index 0000000000..cd9c5da69d --- /dev/null +++ b/openstack/dns/v2/quotas/results.go @@ -0,0 +1,28 @@ +package quotas + +import ( + "github.com/gophercloud/gophercloud/v2" +) + +// Extract interprets a GetResult, CreateResult or UpdateResult as a Quota. +// An error is returned if the original call or the extraction failed. +func (r Result) Extract() (*Quota, error) { + var s *Quota + err := r.ExtractInto(&s) + return s, err +} + +// ListResult is the result of a Create request. Call its Extract method +// to interpret the result as a Zone. +type Result struct { + gophercloud.Result +} + +// Quota represents a quotas on the system. +type Quota struct { + APIExporterSize int `json:"api_export_size"` + RecordsetRecords int `json:"recordset_records"` + ZoneRecords int `json:"zone_records"` + ZoneRecordsets int `json:"zone_recordsets"` + Zones int `json:"zones"` +} diff --git a/openstack/dns/v2/quotas/testing/doc.go b/openstack/dns/v2/quotas/testing/doc.go new file mode 100644 index 0000000000..b9b6286d75 --- /dev/null +++ b/openstack/dns/v2/quotas/testing/doc.go @@ -0,0 +1,2 @@ +// zones unit tests +package testing diff --git a/openstack/dns/v2/quotas/testing/fixtures_test.go b/openstack/dns/v2/quotas/testing/fixtures_test.go new file mode 100644 index 0000000000..9702547e3d --- /dev/null +++ b/openstack/dns/v2/quotas/testing/fixtures_test.go @@ -0,0 +1,63 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/client" + + "github.com/gophercloud/gophercloud/v2/openstack/dns/v2/quotas" +) + +// List Output is a sample response to a List call. +const QuotaOutput = ` +{ + "api_export_size": 1000, + "recordset_records": 20, + "zone_records": 500, + "zone_recordsets": 500, + "zones": 100 +} +` + +// UpdateQuotaRequest is a sample request body for updating quotas. +const UpdateQuotaRequest = ` +{ + "zones": 100 +} +` + +var ( + Quota = "as.Quota{ + APIExporterSize: 1000, + RecordsetRecords: 20, + ZoneRecords: 500, + ZoneRecordsets: 500, + Zones: 100, + } +) + +// HandleGetSuccessfully configures the test server to respond to a Get request. +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/quotas/a86dba58-0043-4cc6-a1bb-69d5e86f3ca3", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, QuotaOutput) + }) +} + +// HandleUpdateSuccessfully configures the test server to respond to an Update request. +func HandleUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/quotas/a86dba58-0043-4cc6-a1bb-69d5e86f3ca3", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, UpdateQuotaRequest) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, QuotaOutput) + }) +} diff --git a/openstack/dns/v2/quotas/testing/requests_test.go b/openstack/dns/v2/quotas/testing/requests_test.go new file mode 100644 index 0000000000..d753e58392 --- /dev/null +++ b/openstack/dns/v2/quotas/testing/requests_test.go @@ -0,0 +1,35 @@ +package testing + +import ( + "context" + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/dns/v2/quotas" + th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/client" +) + +func TestGet(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSuccessfully(t, fakeServer) + + actual, err := quotas.Get(context.TODO(), client.ServiceClient(fakeServer), "a86dba58-0043-4cc6-a1bb-69d5e86f3ca3").Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, Quota, actual) +} + +func TestUpdate(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateSuccessfully(t, fakeServer) + + zones := 100 + updateOpts := quotas.UpdateOpts{ + Zones: &zones, + } + + actual, err := quotas.Update(context.TODO(), client.ServiceClient(fakeServer), "a86dba58-0043-4cc6-a1bb-69d5e86f3ca3", updateOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, Quota, actual) +} diff --git a/openstack/dns/v2/quotas/urls.go b/openstack/dns/v2/quotas/urls.go new file mode 100644 index 0000000000..e60ba18292 --- /dev/null +++ b/openstack/dns/v2/quotas/urls.go @@ -0,0 +1,7 @@ +package quotas + +import "github.com/gophercloud/gophercloud/v2" + +func URL(c *gophercloud.ServiceClient, projectID string) string { + return c.ServiceURL("quotas", projectID) +} diff --git a/openstack/dns/v2/recordsets/requests.go b/openstack/dns/v2/recordsets/requests.go index 49e629b393..2bf952446c 100644 --- a/openstack/dns/v2/recordsets/requests.go +++ b/openstack/dns/v2/recordsets/requests.go @@ -42,7 +42,7 @@ func (opts ListOpts) ToRecordSetListQuery() (string, error) { return q.String(), err } -// ListByZone implements the recordset list request. +// ListByZone implements the recordset list request for a specific zone. func ListByZone(client *gophercloud.ServiceClient, zoneID string, opts ListOptsBuilder) pagination.Pager { url := baseURL(client, zoneID) if opts != nil { @@ -57,6 +57,21 @@ func ListByZone(client *gophercloud.ServiceClient, zoneID string, opts ListOptsB }) } +// ListAll implements the recordset list request across all zones. +func ListAll(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listAllRecordSetsURL(client) + if opts != nil { + query, err := opts.ToRecordSetListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return RecordSetPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + // Get implements the recordset Get request. func Get(ctx context.Context, client *gophercloud.ServiceClient, zoneID string, rrsetID string) (r GetResult) { resp, err := client.Get(ctx, rrsetURL(client, zoneID, rrsetID), &r.Body, nil) @@ -155,7 +170,7 @@ func (opts UpdateOpts) ToRecordSetUpdateMap() (map[string]any, error) { return b, nil } -// Update updates a recordset in a given zone +// Update updates a recordset in a given zone. func Update(ctx context.Context, client *gophercloud.ServiceClient, zoneID string, rrsetID string, opts UpdateOptsBuilder) (r UpdateResult) { b, err := opts.ToRecordSetUpdateMap() if err != nil { diff --git a/openstack/dns/v2/recordsets/testing/fixtures_test.go b/openstack/dns/v2/recordsets/testing/fixtures_test.go index 9d2289f100..037f3d374d 100644 --- a/openstack/dns/v2/recordsets/testing/fixtures_test.go +++ b/openstack/dns/v2/recordsets/testing/fixtures_test.go @@ -199,35 +199,46 @@ var ExpectedRecordSetSlice = []recordsets.RecordSet{FirstRecordSet, SecondRecord // from ListByZoneOutput. var ExpectedRecordSetSliceLimited = []recordsets.RecordSet{SecondRecordSet} +func handleListSuccessfully(t *testing.T, w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + if err := r.ParseForm(); err != nil { + t.Errorf("Failed to parse request form %v", err) + } + marker := r.Form.Get("marker") + switch marker { + case "f7b10e9b-0cae-4a91-b162-562bc6096648": + fmt.Fprint(w, ListByZoneOutputLimited) + case "": + fmt.Fprint(w, ListByZoneOutput) + } +} + // HandleListByZoneSuccessfully configures the test server to respond to a ListByZone request. -func HandleListByZoneSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/zones/2150b1bf-dee2-4221-9d85-11f7886fb15f/recordsets", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandleListByZoneSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/zones/2150b1bf-dee2-4221-9d85-11f7886fb15f/recordsets", func(w http.ResponseWriter, r *http.Request) { + handleListSuccessfully(t, w, r) + }) +} - w.Header().Add("Content-Type", "application/json") - if err := r.ParseForm(); err != nil { - t.Errorf("Failed to parse request form %v", err) - } - marker := r.Form.Get("marker") - switch marker { - case "f7b10e9b-0cae-4a91-b162-562bc6096648": - fmt.Fprintf(w, ListByZoneOutputLimited) - case "": - fmt.Fprintf(w, ListByZoneOutput) - } +// HandleListAllSuccessfully configures the test server to respond to a ListAll request. +func HandleListAllSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/recordsets", func(w http.ResponseWriter, r *http.Request) { + handleListSuccessfully(t, w, r) }) } // HandleGetSuccessfully configures the test server to respond to a Get request. -func HandleGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/zones/2150b1bf-dee2-4221-9d85-11f7886fb15f/recordsets/f7b10e9b-0cae-4a91-b162-562bc6096648", +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/zones/2150b1bf-dee2-4221-9d85-11f7886fb15f/recordsets/f7b10e9b-0cae-4a91-b162-562bc6096648", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -273,8 +284,8 @@ const CreateRecordSetResponse = ` var CreatedRecordSet = FirstRecordSet // HandleZoneCreationSuccessfully configures the test server to respond to a Create request. -func HandleCreateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/zones/2150b1bf-dee2-4221-9d85-11f7886fb15f/recordsets", +func HandleCreateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/zones/2150b1bf-dee2-4221-9d85-11f7886fb15f/recordsets", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -282,7 +293,7 @@ func HandleCreateSuccessfully(t *testing.T) { w.WriteHeader(http.StatusCreated) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, CreateRecordSetResponse) + fmt.Fprint(w, CreateRecordSetResponse) }) } @@ -325,8 +336,8 @@ const UpdateRecordSetResponse = ` ` // HandleUpdateSuccessfully configures the test server to respond to an Update request. -func HandleUpdateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/zones/2150b1bf-dee2-4221-9d85-11f7886fb15f/recordsets/f7b10e9b-0cae-4a91-b162-562bc6096648", +func HandleUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/zones/2150b1bf-dee2-4221-9d85-11f7886fb15f/recordsets/f7b10e9b-0cae-4a91-b162-562bc6096648", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -334,7 +345,7 @@ func HandleUpdateSuccessfully(t *testing.T) { w.WriteHeader(http.StatusOK) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, UpdateRecordSetResponse) + fmt.Fprint(w, UpdateRecordSetResponse) }) } @@ -365,14 +376,14 @@ const DeleteRecordSetResponse = ` ` // HandleDeleteSuccessfully configures the test server to respond to an Delete request. -func HandleDeleteSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/zones/2150b1bf-dee2-4221-9d85-11f7886fb15f/recordsets/f7b10e9b-0cae-4a91-b162-562bc6096648", +func HandleDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/zones/2150b1bf-dee2-4221-9d85-11f7886fb15f/recordsets/f7b10e9b-0cae-4a91-b162-562bc6096648", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) //w.Header().Add("Content-Type", "application/json") - //fmt.Fprintf(w, DeleteZoneResponse) + //fmt.Fprint(w, DeleteZoneResponse) }) } diff --git a/openstack/dns/v2/recordsets/testing/requests_test.go b/openstack/dns/v2/recordsets/testing/requests_test.go index 0c2a51b219..a28518702b 100644 --- a/openstack/dns/v2/recordsets/testing/requests_test.go +++ b/openstack/dns/v2/recordsets/testing/requests_test.go @@ -12,12 +12,12 @@ import ( ) func TestListByZone(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListByZoneSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListByZoneSuccessfully(t, fakeServer) count := 0 - err := recordsets.ListByZone(client.ServiceClient(), "2150b1bf-dee2-4221-9d85-11f7886fb15f", nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := recordsets.ListByZone(client.ServiceClient(fakeServer), "2150b1bf-dee2-4221-9d85-11f7886fb15f", nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := recordsets.ExtractRecordSets(page) th.AssertNoErr(t, err) @@ -30,16 +30,16 @@ func TestListByZone(t *testing.T) { } func TestListByZoneLimited(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListByZoneSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListByZoneSuccessfully(t, fakeServer) count := 0 listOpts := recordsets.ListOpts{ Limit: 1, Marker: "f7b10e9b-0cae-4a91-b162-562bc6096648", } - err := recordsets.ListByZone(client.ServiceClient(), "2150b1bf-dee2-4221-9d85-11f7886fb15f", listOpts).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := recordsets.ListByZone(client.ServiceClient(fakeServer), "2150b1bf-dee2-4221-9d85-11f7886fb15f", listOpts).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := recordsets.ExtractRecordSets(page) th.AssertNoErr(t, err) @@ -52,11 +52,63 @@ func TestListByZoneLimited(t *testing.T) { } func TestListByZoneAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListByZoneSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListByZoneSuccessfully(t, fakeServer) - allPages, err := recordsets.ListByZone(client.ServiceClient(), "2150b1bf-dee2-4221-9d85-11f7886fb15f", nil).AllPages(context.TODO()) + allPages, err := recordsets.ListByZone(client.ServiceClient(fakeServer), "2150b1bf-dee2-4221-9d85-11f7886fb15f", nil).AllPages(context.TODO()) + th.AssertNoErr(t, err) + allRecordSets, err := recordsets.ExtractRecordSets(allPages) + th.AssertNoErr(t, err) + th.CheckEquals(t, 2, len(allRecordSets)) +} + +func TestListAll(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListAllSuccessfully(t, fakeServer) + + count := 0 + err := recordsets.ListAll(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + count++ + actual, err := recordsets.ExtractRecordSets(page) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedRecordSetSlice, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, 1, count) +} + +func TestListAllLimited(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListAllSuccessfully(t, fakeServer) + + count := 0 + listOpts := recordsets.ListOpts{ + Limit: 1, + Marker: "f7b10e9b-0cae-4a91-b162-562bc6096648", + } + err := recordsets.ListAll(client.ServiceClient(fakeServer), listOpts).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + count++ + actual, err := recordsets.ExtractRecordSets(page) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedRecordSetSliceLimited, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, 1, count) +} + +func TestListAllPages(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListAllSuccessfully(t, fakeServer) + + allPages, err := recordsets.ListAll(client.ServiceClient(fakeServer), nil).AllPages(context.TODO()) th.AssertNoErr(t, err) allRecordSets, err := recordsets.ExtractRecordSets(allPages) th.AssertNoErr(t, err) @@ -64,11 +116,11 @@ func TestListByZoneAllPages(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSuccessfully(t, fakeServer) - actual, err := recordsets.Get(context.TODO(), client.ServiceClient(), "2150b1bf-dee2-4221-9d85-11f7886fb15f", "f7b10e9b-0cae-4a91-b162-562bc6096648").Extract() + actual, err := recordsets.Get(context.TODO(), client.ServiceClient(fakeServer), "2150b1bf-dee2-4221-9d85-11f7886fb15f", "f7b10e9b-0cae-4a91-b162-562bc6096648").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &FirstRecordSet, actual) } @@ -82,15 +134,15 @@ func TestNextPageURL(t *testing.T) { } page.Body = body expected := "http://127.0.0.1:9001/v2/zones/2150b1bf-dee2-4221-9d85-11f7886fb15f/recordsets?limit=1&marker=f7b10e9b-0cae-4a91-b162-562bc6096648" - actual, err := page.NextPageURL() + actual, err := page.NextPageURL("http://127.0.0.1:9001") th.AssertNoErr(t, err) th.CheckEquals(t, expected, actual) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateSuccessfully(t, fakeServer) createOpts := recordsets.CreateOpts{ Name: "example.org.", @@ -100,15 +152,15 @@ func TestCreate(t *testing.T) { Records: []string{"10.1.0.2"}, } - actual, err := recordsets.Create(context.TODO(), client.ServiceClient(), "2150b1bf-dee2-4221-9d85-11f7886fb15f", createOpts).Extract() + actual, err := recordsets.Create(context.TODO(), client.ServiceClient(fakeServer), "2150b1bf-dee2-4221-9d85-11f7886fb15f", createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &CreatedRecordSet, actual) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateSuccessfully(t, fakeServer) var description = "Updated description" ttl := 0 @@ -125,15 +177,15 @@ func TestUpdate(t *testing.T) { UpdatedRecordSet.Records = []string{"10.1.0.2", "10.1.0.3"} UpdatedRecordSet.Version = 2 - actual, err := recordsets.Update(context.TODO(), client.ServiceClient(), UpdatedRecordSet.ZoneID, UpdatedRecordSet.ID, updateOpts).Extract() + actual, err := recordsets.Update(context.TODO(), client.ServiceClient(fakeServer), UpdatedRecordSet.ZoneID, UpdatedRecordSet.ID, updateOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &UpdatedRecordSet, actual) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteSuccessfully(t, fakeServer) DeletedRecordSet := CreatedRecordSet DeletedRecordSet.Status = "PENDING" @@ -142,7 +194,7 @@ func TestDelete(t *testing.T) { DeletedRecordSet.Records = []string{"10.1.0.2", "10.1.0.3"} DeletedRecordSet.Version = 2 - err := recordsets.Delete(context.TODO(), client.ServiceClient(), DeletedRecordSet.ZoneID, DeletedRecordSet.ID).ExtractErr() + err := recordsets.Delete(context.TODO(), client.ServiceClient(fakeServer), DeletedRecordSet.ZoneID, DeletedRecordSet.ID).ExtractErr() th.AssertNoErr(t, err) //th.CheckDeepEquals(t, &DeletedZone, actual) } diff --git a/openstack/dns/v2/recordsets/urls.go b/openstack/dns/v2/recordsets/urls.go index 26d9384aa0..8e539d2311 100644 --- a/openstack/dns/v2/recordsets/urls.go +++ b/openstack/dns/v2/recordsets/urls.go @@ -6,6 +6,10 @@ func baseURL(c *gophercloud.ServiceClient, zoneID string) string { return c.ServiceURL("zones", zoneID, "recordsets") } +func listAllRecordSetsURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("recordsets") +} + func rrsetURL(c *gophercloud.ServiceClient, zoneID string, rrsetID string) string { return c.ServiceURL("zones", zoneID, "recordsets", rrsetID) } diff --git a/openstack/dns/v2/transfer/accept/testing/accepts_test.go b/openstack/dns/v2/transfer/accept/testing/accepts_test.go index db868bd779..1261cd1583 100644 --- a/openstack/dns/v2/transfer/accept/testing/accepts_test.go +++ b/openstack/dns/v2/transfer/accept/testing/accepts_test.go @@ -11,12 +11,12 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer) count := 0 - err := transferAccepts.List(client.ServiceClient(), nil).EachPage( + err := transferAccepts.List(client.ServiceClient(fakeServer), nil).EachPage( context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ @@ -30,15 +30,15 @@ func TestList(t *testing.T) { } func TestListWithOpts(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleFilteredListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleFilteredListSuccessfully(t, fakeServer) listOpts := transferAccepts.ListOpts{ Status: "ACTIVE", } - allPages, err := transferAccepts.List(client.ServiceClient(), listOpts).AllPages(context.TODO()) + allPages, err := transferAccepts.List(client.ServiceClient(fakeServer), listOpts).AllPages(context.TODO()) th.AssertNoErr(t, err) allTransferAccepts, err := transferAccepts.ExtractTransferAccepts(allPages) th.AssertNoErr(t, err) @@ -46,11 +46,11 @@ func TestListWithOpts(t *testing.T) { } func TestListAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer) - allPages, err := transferAccepts.List(client.ServiceClient(), nil).AllPages(context.TODO()) + allPages, err := transferAccepts.List(client.ServiceClient(fakeServer), nil).AllPages(context.TODO()) th.AssertNoErr(t, err) allTransferAccepts, err := transferAccepts.ExtractTransferAccepts(allPages) th.AssertNoErr(t, err) @@ -58,20 +58,20 @@ func TestListAllPages(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSuccessfully(t, fakeServer) actual, err := transferAccepts.Get( - context.TODO(), client.ServiceClient(), "92236f39-0fad-4f8f-bf25-fbdf027de34d").Extract() + context.TODO(), client.ServiceClient(fakeServer), "92236f39-0fad-4f8f-bf25-fbdf027de34d").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &FirstTransferAccept, actual) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateSuccessfully(t, fakeServer) createOpts := transferAccepts.CreateOpts{ Key: "M2KA0Y20", @@ -79,7 +79,7 @@ func TestCreate(t *testing.T) { } actual, err := transferAccepts.Create( - context.TODO(), client.ServiceClient(), createOpts).Extract() + context.TODO(), client.ServiceClient(fakeServer), createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &CreatedTransferAccept, actual) } diff --git a/openstack/dns/v2/transfer/accept/testing/fixtures_test.go b/openstack/dns/v2/transfer/accept/testing/fixtures_test.go index b8c3fe5888..4ef02e7489 100644 --- a/openstack/dns/v2/transfer/accept/testing/fixtures_test.go +++ b/openstack/dns/v2/transfer/accept/testing/fixtures_test.go @@ -126,38 +126,38 @@ var SecondTransferAccept = transferAccepts.TransferAccept{ var ExpectedTransferAcceptSlice = []transferAccepts.TransferAccept{FirstTransferAccept, SecondTransferAccept} // HandleListSuccessfully configures the test server to respond to a List request. -func HandleListSuccessfully(t *testing.T) { +func HandleListSuccessfully(t *testing.T, fakeServer th.FakeServer) { baseURL := "/zones/tasks/transfer_accepts" - th.Mux.HandleFunc(baseURL, + fakeServer.Mux.HandleFunc(baseURL, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } // HandleFilteredListSuccessfully configures the test server to respond to a List request with Opts. -func HandleFilteredListSuccessfully(t *testing.T) { +func HandleFilteredListSuccessfully(t *testing.T, fakeServer th.FakeServer) { baseURL := "/zones/tasks/transfer_accepts" - th.Mux.HandleFunc(baseURL, + fakeServer.Mux.HandleFunc(baseURL, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, FilteredListOutput) + fmt.Fprint(w, FilteredListOutput) }) } // HandleGetSuccessfully configures the test server to respond to a List request. -func HandleGetSuccessfully(t *testing.T) { +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { baseURL := "/zones/tasks/transfer_accepts" - th.Mux.HandleFunc(s.Join([]string{baseURL, FirstTransferAccept.ID}, "/"), + fakeServer.Mux.HandleFunc(s.Join([]string{baseURL, FirstTransferAccept.ID}, "/"), func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -190,9 +190,9 @@ const CreateTransferAcceptResponse = ` var CreatedTransferAccept = FirstTransferAccept // HandleTransferRequestCreationSuccessfully configures the test server to respond to a Create request. -func HandleCreateSuccessfully(t *testing.T) { +func HandleCreateSuccessfully(t *testing.T, fakeServer th.FakeServer) { baseURL := "/zones/tasks/transfer_accepts" - th.Mux.HandleFunc(baseURL, + fakeServer.Mux.HandleFunc(baseURL, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -200,6 +200,6 @@ func HandleCreateSuccessfully(t *testing.T) { w.WriteHeader(http.StatusCreated) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, CreateTransferAcceptResponse) + fmt.Fprint(w, CreateTransferAcceptResponse) }) } diff --git a/openstack/dns/v2/transfer/request/testing/fixtures_test.go b/openstack/dns/v2/transfer/request/testing/fixtures_test.go index 758b566604..52ed6b6d3a 100644 --- a/openstack/dns/v2/transfer/request/testing/fixtures_test.go +++ b/openstack/dns/v2/transfer/request/testing/fixtures_test.go @@ -112,26 +112,26 @@ var SecondTransferRequest = transferRequests.TransferRequest{ var ExpectedTransferRequestsSlice = []transferRequests.TransferRequest{FirstTransferRequest, SecondTransferRequest} // HandleListSuccessfully configures the test server to respond to a List request. -func HandleListSuccessfully(t *testing.T) { +func HandleListSuccessfully(t *testing.T, fakeServer th.FakeServer) { baseURL := "/zones/tasks/transfer_requests" - th.Mux.HandleFunc(baseURL, + fakeServer.Mux.HandleFunc(baseURL, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } // HandleGetSuccessfully configures the test server to respond to a List request. -func HandleGetSuccessfully(t *testing.T) { +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { baseURL := "/zones/tasks/transfer_requests" - th.Mux.HandleFunc(s.Join([]string{baseURL, FirstTransferRequest.ID}, "/"), + fakeServer.Mux.HandleFunc(s.Join([]string{baseURL, FirstTransferRequest.ID}, "/"), func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -165,9 +165,9 @@ const CreateTransferRequestResponse = ` var CreatedTransferRequest = FirstTransferRequest // HandleTransferRequestCreationSuccessfully configures the test server to respond to a Create request. -func HandleCreateSuccessfully(t *testing.T) { +func HandleCreateSuccessfully(t *testing.T, fakeServer th.FakeServer) { createURL := "/zones/a6a8515c-5d80-48c0-955b-fde631b59791/tasks/transfer_requests" - th.Mux.HandleFunc(createURL, + fakeServer.Mux.HandleFunc(createURL, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -175,7 +175,7 @@ func HandleCreateSuccessfully(t *testing.T) { w.WriteHeader(http.StatusCreated) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, CreateTransferRequestResponse) + fmt.Fprint(w, CreateTransferRequestResponse) }) } @@ -205,9 +205,9 @@ const UpdatedTransferRequestResponse = ` ` // HandleTransferRequestUpdateSuccessfully configures the test server to respond to an Update request. -func HandleUpdateSuccessfully(t *testing.T) { +func HandleUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { baseURL := "/zones/tasks/transfer_requests" - th.Mux.HandleFunc(s.Join([]string{baseURL, FirstTransferRequest.ID}, "/"), + fakeServer.Mux.HandleFunc(s.Join([]string{baseURL, FirstTransferRequest.ID}, "/"), func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -215,14 +215,14 @@ func HandleUpdateSuccessfully(t *testing.T) { w.WriteHeader(http.StatusOK) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, UpdatedTransferRequestResponse) + fmt.Fprint(w, UpdatedTransferRequestResponse) }) } // HandleTransferRequestDeleteSuccessfully configures the test server to respond to an Delete request. -func HandleDeleteSuccessfully(t *testing.T) { +func HandleDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { baseURL := "/zones/tasks/transfer_requests" - th.Mux.HandleFunc(s.Join([]string{baseURL, FirstTransferRequest.ID}, "/"), + fakeServer.Mux.HandleFunc(s.Join([]string{baseURL, FirstTransferRequest.ID}, "/"), func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) diff --git a/openstack/dns/v2/transfer/request/testing/requests_test.go b/openstack/dns/v2/transfer/request/testing/requests_test.go index daad13b664..b2213f61af 100644 --- a/openstack/dns/v2/transfer/request/testing/requests_test.go +++ b/openstack/dns/v2/transfer/request/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer) count := 0 - err := transferRequests.List(client.ServiceClient(), nil).EachPage( + err := transferRequests.List(client.ServiceClient(fakeServer), nil).EachPage( context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ @@ -30,15 +30,15 @@ func TestList(t *testing.T) { } func TestListWithOpts(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer) listOpts := transferRequests.ListOpts{ Status: "ACTIVE", } - allPages, err := transferRequests.List(client.ServiceClient(), listOpts).AllPages(context.TODO()) + allPages, err := transferRequests.List(client.ServiceClient(fakeServer), listOpts).AllPages(context.TODO()) th.AssertNoErr(t, err) allTransferRequests, err := transferRequests.ExtractTransferRequests(allPages) th.AssertNoErr(t, err) @@ -46,11 +46,11 @@ func TestListWithOpts(t *testing.T) { } func TestListAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer) - allPages, err := transferRequests.List(client.ServiceClient(), nil).AllPages(context.TODO()) + allPages, err := transferRequests.List(client.ServiceClient(fakeServer), nil).AllPages(context.TODO()) th.AssertNoErr(t, err) allTransferRequests, err := transferRequests.ExtractTransferRequests(allPages) th.AssertNoErr(t, err) @@ -58,20 +58,20 @@ func TestListAllPages(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSuccessfully(t, fakeServer) actual, err := transferRequests.Get( - context.TODO(), client.ServiceClient(), "a86dba58-0043-4cc6-a1bb-69d5e86f3ca3").Extract() + context.TODO(), client.ServiceClient(fakeServer), "a86dba58-0043-4cc6-a1bb-69d5e86f3ca3").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &FirstTransferRequest, actual) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateSuccessfully(t, fakeServer) createOpts := transferRequests.CreateOpts{ TargetProjectID: "05d98711-b3a1-4264-a395-f46383671ee6", @@ -79,15 +79,15 @@ func TestCreate(t *testing.T) { } actual, err := transferRequests.Create( - context.TODO(), client.ServiceClient(), FirstTransferRequest.ZoneID, createOpts).Extract() + context.TODO(), client.ServiceClient(fakeServer), FirstTransferRequest.ZoneID, createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &CreatedTransferRequest, actual) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateSuccessfully(t, fakeServer) var description = "Updated Description" updateOpts := transferRequests.UpdateOpts{ @@ -98,18 +98,18 @@ func TestUpdate(t *testing.T) { UpdatedTransferRequest.Description = "Updated Description" actual, err := transferRequests.Update( - context.TODO(), client.ServiceClient(), UpdatedTransferRequest.ID, updateOpts).Extract() + context.TODO(), client.ServiceClient(fakeServer), UpdatedTransferRequest.ID, updateOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &UpdatedTransferRequest, actual) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteSuccessfully(t, fakeServer) DeletedZone := CreatedTransferRequest - err := transferRequests.Delete(context.TODO(), client.ServiceClient(), DeletedZone.ID).ExtractErr() + err := transferRequests.Delete(context.TODO(), client.ServiceClient(fakeServer), DeletedZone.ID).ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/dns/v2/tsigkeys/doc.go b/openstack/dns/v2/tsigkeys/doc.go new file mode 100644 index 0000000000..7e99c6dbe4 --- /dev/null +++ b/openstack/dns/v2/tsigkeys/doc.go @@ -0,0 +1,72 @@ +/* +Package tsigkeys provides information and interaction with the TSIG key API +resource for the OpenStack DNS service. + +TSIG (Transaction SIGnature) keys are used to authenticate DNS transactions +between servers, such as zone transfers and dynamic updates. + +Example to List TSIG Keys + + listOpts := tsigkeys.ListOpts{ + Scope: "POOL", + } + + allPages, err := tsigkeys.List(dnsClient, listOpts).AllPages(context.TODO()) + if err != nil { + panic(err) + } + + allTSIGKeys, err := tsigkeys.ExtractTSIGKeys(allPages) + if err != nil { + panic(err) + } + + for _, tsigkey := range allTSIGKeys { + fmt.Printf("%+v\n", tsigkey) + } + +Example to Create a TSIG Key + + createOpts := tsigkeys.CreateOpts{ + Name: "mytsigkey", + Algorithm: "hmac-sha256", + Secret: "example-secret-key-value==", + Scope: "POOL", + ResourceID: "pool-id-here", + } + + tsigkey, err := tsigkeys.Create(context.TODO(), dnsClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Get a TSIG Key + + tsigkeyID := "99d10f68-5623-4491-91a0-6daafa32b60e" + tsigkey, err := tsigkeys.Get(context.TODO(), dnsClient, tsigkeyID).Extract() + if err != nil { + panic(err) + } + +Example to Update a TSIG Key + + tsigkeyID := "99d10f68-5623-4491-91a0-6daafa32b60e" + updateOpts := tsigkeys.UpdateOpts{ + Name: "updatedname", + Secret: "updated-secret-key-value==", + } + + tsigkey, err := tsigkeys.Update(context.TODO(), dnsClient, tsigkeyID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a TSIG Key + + tsigkeyID := "99d10f68-5623-4491-91a0-6daafa32b60e" + err := tsigkeys.Delete(context.TODO(), dnsClient, tsigkeyID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package tsigkeys diff --git a/openstack/dns/v2/tsigkeys/requests.go b/openstack/dns/v2/tsigkeys/requests.go new file mode 100644 index 0000000000..2f8b0a4aa5 --- /dev/null +++ b/openstack/dns/v2/tsigkeys/requests.go @@ -0,0 +1,158 @@ +package tsigkeys + +import ( + "context" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/pagination" +) + +// ListOptsBuilder allows extensions to add parameters to the List request. +type ListOptsBuilder interface { + ToTSIGKeyListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the server attributes you want to see returned. Marker and Limit are used +// for pagination. +// https://docs.openstack.org/api-ref/dns/ +type ListOpts struct { + // Integer value for the limit of values to return. + Limit int `q:"limit"` + + // UUID of the TSIG key at which you want to set a marker. + Marker string `q:"marker"` + + // Name of the TSIG key. + Name string `q:"name"` + + // Algorithm used by the TSIG key. + Algorithm string `q:"algorithm"` + + // Scope of the TSIG key (ZONE or POOL). + Scope string `q:"scope"` +} + +// ToTSIGKeyListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToTSIGKeyListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List implements a TSIG key List request. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := baseURL(client) + if opts != nil { + query, err := opts.ToTSIGKeyListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return TSIGKeyPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get returns information about a TSIG key, given its ID. +func Get(ctx context.Context, client *gophercloud.ServiceClient, tsigkeyID string) (r GetResult) { + resp, err := client.Get(ctx, tsigkeyURL(client, tsigkeyID), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// CreateOptsBuilder allows extensions to add additional attributes to the +// Create request. +type CreateOptsBuilder interface { + ToTSIGKeyCreateMap() (map[string]any, error) +} + +// CreateOpts specifies the attributes used to create a TSIG key. +type CreateOpts struct { + // Name of the TSIG key. + Name string `json:"name" required:"true"` + + // Algorithm is the TSIG algorithm (e.g., hmac-sha256, hmac-sha512). + Algorithm string `json:"algorithm" required:"true"` + + // Secret is the base64-encoded secret key. + Secret string `json:"secret" required:"true"` + + // Scope defines the scope of the TSIG key (ZONE or POOL). + Scope string `json:"scope" required:"true"` + + // ResourceID is the ID of the resource (zone or pool) this key is associated with. + ResourceID string `json:"resource_id" required:"true"` +} + +// ToTSIGKeyCreateMap formats a CreateOpts structure into a request body. +func (opts CreateOpts) ToTSIGKeyCreateMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// Create implements a TSIG key create request. +func Create(ctx context.Context, client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToTSIGKeyCreateMap() + if err != nil { + r.Err = err + return + } + resp, err := client.Post(ctx, baseURL(client), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// UpdateOptsBuilder allows extensions to add additional attributes to the +// Update request. +type UpdateOptsBuilder interface { + ToTSIGKeyUpdateMap() (map[string]any, error) +} + +// UpdateOpts specifies the attributes to update a TSIG key. +type UpdateOpts struct { + // Name of the TSIG key. + Name string `json:"name,omitempty"` + + // Algorithm is the TSIG algorithm. + Algorithm string `json:"algorithm,omitempty"` + + // Secret is the base64-encoded secret key. + Secret string `json:"secret,omitempty"` + + // Scope defines the scope of the TSIG key. + Scope string `json:"scope,omitempty"` + + // ResourceID is the ID of the resource this key is associated with. + ResourceID string `json:"resource_id,omitempty"` +} + +// ToTSIGKeyUpdateMap formats an UpdateOpts structure into a request body. +func (opts UpdateOpts) ToTSIGKeyUpdateMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// Update implements a TSIG key update request. +func Update(ctx context.Context, client *gophercloud.ServiceClient, tsigkeyID string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToTSIGKeyUpdateMap() + if err != nil { + r.Err = err + return + } + resp, err := client.Patch(ctx, tsigkeyURL(client, tsigkeyID), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// Delete implements a TSIG key delete request. +func Delete(ctx context.Context, client *gophercloud.ServiceClient, tsigkeyID string) (r DeleteResult) { + resp, err := client.Delete(ctx, tsigkeyURL(client, tsigkeyID), &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/dns/v2/tsigkeys/results.go b/openstack/dns/v2/tsigkeys/results.go new file mode 100644 index 0000000000..02382e9b7a --- /dev/null +++ b/openstack/dns/v2/tsigkeys/results.go @@ -0,0 +1,119 @@ +package tsigkeys + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/pagination" +) + +type commonResult struct { + gophercloud.Result +} + +// Extract interprets a GetResult, CreateResult or UpdateResult as a TSIGKey. +// An error is returned if the original call or the extraction failed. +func (r commonResult) Extract() (*TSIGKey, error) { + var s *TSIGKey + err := r.ExtractInto(&s) + return s, err +} + +// CreateResult is the result of a Create request. Call its Extract method +// to interpret the result as a TSIGKey. +type CreateResult struct { + commonResult +} + +// GetResult is the result of a Get request. Call its Extract method +// to interpret the result as a TSIGKey. +type GetResult struct { + commonResult +} + +// UpdateResult is the result of an Update request. Call its Extract method +// to interpret the result as a TSIGKey. +type UpdateResult struct { + commonResult +} + +// DeleteResult is the result of a Delete request. Call its ExtractErr method +// to determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// TSIGKeyPage is a single page of TSIGKey results. +type TSIGKeyPage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if the page contains no results. +func (r TSIGKeyPage) IsEmpty() (bool, error) { + if r.StatusCode == 204 { + return true, nil + } + + s, err := ExtractTSIGKeys(r) + return len(s) == 0, err +} + +// ExtractTSIGKeys extracts a slice of TSIGKeys from a List result. +func ExtractTSIGKeys(r pagination.Page) ([]TSIGKey, error) { + var s struct { + TSIGKeys []TSIGKey `json:"tsigkeys"` + } + err := (r.(TSIGKeyPage)).ExtractInto(&s) + return s.TSIGKeys, err +} + +// TSIGKey represents a TSIG key for DNS transaction authentication. +type TSIGKey struct { + // ID uniquely identifies this TSIG key. + ID string `json:"id"` + + // Name is the name of the TSIG key. + Name string `json:"name"` + + // Algorithm is the TSIG algorithm used (e.g., hmac-sha256, hmac-sha512). + Algorithm string `json:"algorithm"` + + // Secret is the base64-encoded secret key. + Secret string `json:"secret"` + + // Scope defines the scope of the TSIG key (ZONE or POOL). + Scope string `json:"scope"` + + // ResourceID is the ID of the resource (zone or pool) this key is associated with. + ResourceID string `json:"resource_id"` + + // CreatedAt is the date when the TSIG key was created. + CreatedAt time.Time `json:"-"` + + // UpdatedAt is the date when the TSIG key was last updated. + UpdatedAt time.Time `json:"-"` + + // Links includes HTTP references to the itself. + Links map[string]any `json:"links"` +} + +func (r *TSIGKey) UnmarshalJSON(b []byte) error { + type tmp TSIGKey + var s struct { + tmp + CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"` + UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"` + } + + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = TSIGKey(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + r.UpdatedAt = time.Time(s.UpdatedAt) + + return nil +} diff --git a/openstack/dns/v2/tsigkeys/testing/doc.go b/openstack/dns/v2/tsigkeys/testing/doc.go new file mode 100644 index 0000000000..6f54fc2d97 --- /dev/null +++ b/openstack/dns/v2/tsigkeys/testing/doc.go @@ -0,0 +1,2 @@ +// tsigkeys unit tests +package testing diff --git a/openstack/dns/v2/tsigkeys/testing/fixtures_test.go b/openstack/dns/v2/tsigkeys/testing/fixtures_test.go new file mode 100644 index 0000000000..dc15e74197 --- /dev/null +++ b/openstack/dns/v2/tsigkeys/testing/fixtures_test.go @@ -0,0 +1,224 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack/dns/v2/tsigkeys" + th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/client" +) + +// ListOutput is a sample response to a List call. +const ListOutput = ` +{ + "links": { + "self": "http://example.com:9001/v2/tsigkeys" + }, + "metadata": { + "total_count": 2 + }, + "tsigkeys": [ + { + "id": "8add45a3-0f29-489f-854e-7609baf8d7a1", + "name": "poolsecondarykey", + "algorithm": "hmac-sha256", + "secret": "my-base64-secret-example==", + "scope": "POOL", + "resource_id": "adcc2fb6-7984-4453-a6f9-2cc2a24a38bb", + "created_at": "2025-08-13T15:54:18.000000", + "updated_at": null, + "links": { + "self": "http://127.0.0.1:9001/v2/tsigkeys/8add45a3-0f29-489f-854e-7609baf8d7a1" + } + }, + { + "id": "9bef46b4-1f3a-59a0-965f-8710caf9e8b2", + "name": "zonekey", + "algorithm": "hmac-sha512", + "secret": "another-base64-secret-example==", + "scope": "ZONE", + "resource_id": "c4d5e6f7-8a9b-0c1d-2e3f-4a5b6c7d8e9f", + "created_at": "2025-08-14T10:30:45.000000", + "updated_at": "2025-08-15T14:22:33.000000", + "links": { + "self": "http://127.0.0.1:9001/v2/tsigkeys/9bef46b4-1f3a-59a0-965f-8710caf9e8b2" + } + } + ] +} +` + +// GetOutput is a sample response to a Get call. +const GetOutput = ` +{ + "id": "8add45a3-0f29-489f-854e-7609baf8d7a1", + "name": "poolsecondarykey", + "algorithm": "hmac-sha256", + "secret": "my-base64-secret-example==", + "scope": "POOL", + "resource_id": "adcc2fb6-7984-4453-a6f9-2cc2a24a38bb", + "created_at": "2025-08-13T15:54:18.000000", + "updated_at": null, + "links": { + "self": "http://127.0.0.1:9001/v2/tsigkeys/8add45a3-0f29-489f-854e-7609baf8d7a1" + } +} +` + +// FirstTSIGKeyCreatedAt is the created at time for the first TSIG key +var FirstTSIGKeyCreatedAt, _ = time.Parse(gophercloud.RFC3339MilliNoZ, "2025-08-13T15:54:18.000000") + +// FirstTSIGKey is the first result in ListOutput +var FirstTSIGKey = tsigkeys.TSIGKey{ + ID: "8add45a3-0f29-489f-854e-7609baf8d7a1", + Name: "poolsecondarykey", + Algorithm: "hmac-sha256", + Secret: "my-base64-secret-example==", + Scope: "POOL", + ResourceID: "adcc2fb6-7984-4453-a6f9-2cc2a24a38bb", + CreatedAt: FirstTSIGKeyCreatedAt, + Links: map[string]any{ + "self": "http://127.0.0.1:9001/v2/tsigkeys/8add45a3-0f29-489f-854e-7609baf8d7a1", + }, +} + +var SecondTSIGKeyCreatedAt, _ = time.Parse(gophercloud.RFC3339MilliNoZ, "2025-08-14T10:30:45.000000") +var SecondTSIGKeyUpdatedAt, _ = time.Parse(gophercloud.RFC3339MilliNoZ, "2025-08-15T14:22:33.000000") + +var SecondTSIGKey = tsigkeys.TSIGKey{ + ID: "9bef46b4-1f3a-59a0-965f-8710caf9e8b2", + Name: "zonekey", + Algorithm: "hmac-sha512", + Secret: "another-base64-secret-example==", + Scope: "ZONE", + ResourceID: "c4d5e6f7-8a9b-0c1d-2e3f-4a5b6c7d8e9f", + CreatedAt: SecondTSIGKeyCreatedAt, + UpdatedAt: SecondTSIGKeyUpdatedAt, + Links: map[string]any{ + "self": "http://127.0.0.1:9001/v2/tsigkeys/9bef46b4-1f3a-59a0-965f-8710caf9e8b2", + }, +} + +// ExpectedTSIGKeysSlice is the slice of results that should be parsed +// from ListOutput, in the expected order. +var ExpectedTSIGKeysSlice = []tsigkeys.TSIGKey{FirstTSIGKey, SecondTSIGKey} + +// HandleListSuccessfully configures the test server to respond to a List request. +func HandleListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/tsigkeys", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, ListOutput) + }) +} + +// HandleGetSuccessfully configures the test server to respond to a Get request. +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/tsigkeys/8add45a3-0f29-489f-854e-7609baf8d7a1", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, GetOutput) + }) +} + +// CreateTSIGKeyRequest is a sample request to create a TSIG key. +const CreateTSIGKeyRequest = ` +{ + "name": "poolsecondarykey", + "algorithm": "hmac-sha256", + "secret": "my-base64-secret-example==", + "scope": "POOL", + "resource_id": "adcc2fb6-7984-4453-a6f9-2cc2a24a38bb" +} +` + +// CreateTSIGKeyResponse is a sample response to a create request. +const CreateTSIGKeyResponse = ` +{ + "id": "8add45a3-0f29-489f-854e-7609baf8d7a1", + "name": "poolsecondarykey", + "algorithm": "hmac-sha256", + "secret": "my-base64-secret-example==", + "scope": "POOL", + "resource_id": "adcc2fb6-7984-4453-a6f9-2cc2a24a38bb", + "created_at": "2025-08-13T15:54:18.000000", + "updated_at": null, + "links": { + "self": "http://127.0.0.1:9001/v2/tsigkeys/8add45a3-0f29-489f-854e-7609baf8d7a1" + } +} +` + +// CreatedTSIGKey is the expected created TSIG key +var CreatedTSIGKey = FirstTSIGKey + +// HandleCreateSuccessfully configures the test server to respond to a Create request. +func HandleCreateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/tsigkeys", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, CreateTSIGKeyRequest) + + w.WriteHeader(http.StatusCreated) + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, CreateTSIGKeyResponse) + }) +} + +// UpdateTSIGKeyRequest is a sample request to update a TSIG key. +const UpdateTSIGKeyRequest = ` +{ + "name": "updatedsecondarykey", + "secret": "new-base64-secret-example==" +} +` + +// UpdateTSIGKeyResponse is a sample response to update a TSIG key. +const UpdateTSIGKeyResponse = ` +{ + "id": "8add45a3-0f29-489f-854e-7609baf8d7a1", + "name": "updatedsecondarykey", + "algorithm": "hmac-sha256", + "secret": "new-base64-secret-example==", + "scope": "POOL", + "resource_id": "adcc2fb6-7984-4453-a6f9-2cc2a24a38bb", + "created_at": "2025-08-13T15:54:18.000000", + "updated_at": "2025-08-16T09:15:22.000000", + "links": { + "self": "http://127.0.0.1:9001/v2/tsigkeys/8add45a3-0f29-489f-854e-7609baf8d7a1" + } +} +` + +// HandleUpdateSuccessfully configures the test server to respond to an Update request. +func HandleUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/tsigkeys/8add45a3-0f29-489f-854e-7609baf8d7a1", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, UpdateTSIGKeyRequest) + + w.WriteHeader(http.StatusOK) + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, UpdateTSIGKeyResponse) + }) +} + +// HandleDeleteSuccessfully configures the test server to respond to a Delete request. +func HandleDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/tsigkeys/8add45a3-0f29-489f-854e-7609baf8d7a1", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} diff --git a/openstack/dns/v2/tsigkeys/testing/requests_test.go b/openstack/dns/v2/tsigkeys/testing/requests_test.go new file mode 100644 index 0000000000..3bc163d5b4 --- /dev/null +++ b/openstack/dns/v2/tsigkeys/testing/requests_test.go @@ -0,0 +1,98 @@ +package testing + +import ( + "context" + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/dns/v2/tsigkeys" + "github.com/gophercloud/gophercloud/v2/pagination" + th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/client" +) + +func TestList(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer) + + count := 0 + err := tsigkeys.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + count++ + actual, err := tsigkeys.ExtractTSIGKeys(page) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedTSIGKeysSlice, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, 1, count) +} + +func TestListAllPages(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer) + + allPages, err := tsigkeys.List(client.ServiceClient(fakeServer), nil).AllPages(context.TODO()) + th.AssertNoErr(t, err) + allTSIGKeys, err := tsigkeys.ExtractTSIGKeys(allPages) + th.AssertNoErr(t, err) + th.CheckEquals(t, 2, len(allTSIGKeys)) +} + +func TestGet(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSuccessfully(t, fakeServer) + + actual, err := tsigkeys.Get(context.TODO(), client.ServiceClient(fakeServer), "8add45a3-0f29-489f-854e-7609baf8d7a1").Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &FirstTSIGKey, actual) +} + +func TestCreate(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateSuccessfully(t, fakeServer) + + createOpts := tsigkeys.CreateOpts{ + Name: "poolsecondarykey", + Algorithm: "hmac-sha256", + Secret: "my-base64-secret-example==", + Scope: "POOL", + ResourceID: "adcc2fb6-7984-4453-a6f9-2cc2a24a38bb", + } + + actual, err := tsigkeys.Create(context.TODO(), client.ServiceClient(fakeServer), createOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &CreatedTSIGKey, actual) +} + +func TestUpdate(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateSuccessfully(t, fakeServer) + + updateOpts := tsigkeys.UpdateOpts{ + Name: "updatedsecondarykey", + Secret: "new-base64-secret-example==", + } + + UpdatedTSIGKey := CreatedTSIGKey + UpdatedTSIGKey.Name = "updatedsecondarykey" + UpdatedTSIGKey.Secret = "new-base64-secret-example==" + + actual, err := tsigkeys.Update(context.TODO(), client.ServiceClient(fakeServer), UpdatedTSIGKey.ID, updateOpts).Extract() + th.AssertNoErr(t, err) + th.CheckEquals(t, UpdatedTSIGKey.Name, actual.Name) + th.CheckEquals(t, UpdatedTSIGKey.Secret, actual.Secret) +} + +func TestDelete(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteSuccessfully(t, fakeServer) + + err := tsigkeys.Delete(context.TODO(), client.ServiceClient(fakeServer), "8add45a3-0f29-489f-854e-7609baf8d7a1").ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/openstack/dns/v2/tsigkeys/urls.go b/openstack/dns/v2/tsigkeys/urls.go new file mode 100644 index 0000000000..f7c4b0cfe3 --- /dev/null +++ b/openstack/dns/v2/tsigkeys/urls.go @@ -0,0 +1,13 @@ +package tsigkeys + +import "github.com/gophercloud/gophercloud/v2" + +// baseURL returns the base URL for TSIG keys. +func baseURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("tsigkeys") +} + +// tsigkeyURL returns the URL for a specific TSIG key. +func tsigkeyURL(c *gophercloud.ServiceClient, tsigkeyID string) string { + return c.ServiceURL("tsigkeys", tsigkeyID) +} diff --git a/openstack/dns/v2/zones/doc.go b/openstack/dns/v2/zones/doc.go index 6bd7d98cb5..4537eda5d4 100644 --- a/openstack/dns/v2/zones/doc.go +++ b/openstack/dns/v2/zones/doc.go @@ -22,6 +22,26 @@ Example to List Zones fmt.Printf("%+v\n", zone) } +Example to List Zones Across All Projects + + listOpts := zones.ListOpts{ + AllProjects: true, + } + + allPages, err := zones.List(dnsClient, listOpts).AllPages(context.TODO()) + if err != nil { + panic(err) + } + + allZones, err := zones.ExtractZones(allPages) + if err != nil { + panic(err) + } + + for _, zone := range allZones { + fmt.Printf("%+v\n", zone) + } + Example to Create a Zone createOpts := zones.CreateOpts{ diff --git a/openstack/dns/v2/zones/requests.go b/openstack/dns/v2/zones/requests.go index fba4371ced..f234766f9f 100644 --- a/openstack/dns/v2/zones/requests.go +++ b/openstack/dns/v2/zones/requests.go @@ -12,6 +12,11 @@ type ListOptsBuilder interface { ToZoneListQuery() (string, error) } +// ListOptsHeadersBuilder allows extensions to add additional headers to the List request. +type ListOptsHeadersBuilder interface { + ToZoneListHeaders() (map[string]string, error) +} + // ListOpts allows the filtering and sorting of paginated collections through // the API. Filtering is achieved by passing in struct field values that map to // the server attributes you want to see returned. Marker and Limit are used @@ -32,6 +37,12 @@ type ListOpts struct { Status string `q:"status"` TTL int `q:"ttl"` Type string `q:"type"` + + // All projects header + AllProjects bool `h:"X-Auth-All-Projects"` + + // SudoTenantID impersonates the given project. + SudoTenantID string `h:"X-Auth-Sudo-Tenant-ID"` } // ToZoneListQuery formats a ListOpts into a query string. @@ -40,19 +51,37 @@ func (opts ListOpts) ToZoneListQuery() (string, error) { return q.String(), err } +// ToZoneListHeaders formats a ListOpts into header parameters. +func (opts ListOpts) ToZoneListHeaders() (map[string]string, error) { + return gophercloud.BuildHeaders(opts) +} + // List implements a zone List request. func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { url := baseURL(client) + var h map[string]string + if opts != nil { query, err := opts.ToZoneListQuery() if err != nil { return pagination.Pager{Err: err} } url += query + + // Check if opts implements the optional headers interface + if optsWithHeaders, ok := opts.(ListOptsHeadersBuilder); ok { + h, err = optsWithHeaders.ToZoneListHeaders() + if err != nil { + return pagination.Pager{Err: err} + } + } } - return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + + pager := pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { return ZonePage{pagination.LinkedPageBase{PageResult: r}} }) + pager.Headers = h + return pager } // Get returns information about a zone, given its ID. @@ -178,3 +207,87 @@ func Delete(ctx context.Context, client *gophercloud.ServiceClient, zoneID strin _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } + +// ListSharesOptsBuilder allows extensions to add additional parameters to the List +// request. +type ListSharesOptsBuilder interface { + ToZoneListSharesHeadersMap() (map[string]string, error) +} + +// ListSharesOpts is a structure that holds parameters for listing zone shares. +type ListSharesOpts struct { + AllProjects bool `h:"X-Auth-All-Projects"` +} + +// ToZoneListSharesHeadersMap formats a ListSharesOpts into header parameters. +func (opts ListSharesOpts) ToZoneListSharesHeadersMap() (map[string]string, error) { + return gophercloud.BuildHeaders(opts) +} + +// ListShares implements a zone list shares request. +func ListShares(client *gophercloud.ServiceClient, zoneID string, opts ListSharesOptsBuilder) pagination.Pager { + var h map[string]string + var err error + + if opts != nil { + h, err = opts.ToZoneListSharesHeadersMap() + if err != nil { + return pagination.Pager{Err: err} + } + } + + pager := pagination.NewPager(client, sharesBaseURL(client, zoneID), func(r pagination.PageResult) pagination.Page { + return ZoneSharePage{pagination.LinkedPageBase{PageResult: r}} + }) + pager.Headers = h + return pager +} + +// GetShare returns information about a shared zone, given its ID. +func GetShare(ctx context.Context, client *gophercloud.ServiceClient, zoneID, shareID string) (r ZoneShareResult) { + resp, err := client.Get(ctx, shareURL(client, zoneID, shareID), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// request body for sharing a zone. +type ShareOptsBuilder interface { + ToShareMap() (map[string]interface{}, error) +} + +// ShareZoneOpts specifies the target project for sharing a zone. +type ShareZoneOpts struct { + // TargetProjectID is the ID of the project to share the zone with. + TargetProjectID string `json:"target_project_id" required:"true"` +} + +// ToShareMap constructs a request body from a ShareZoneOpts. +func (opts ShareZoneOpts) ToShareMap() (map[string]interface{}, error) { + return map[string]interface{}{ + "target_project_id": opts.TargetProjectID, + }, nil +} + +// Share shares a zone with another project. +func Share(ctx context.Context, client *gophercloud.ServiceClient, zoneID string, opts ShareOptsBuilder) (r ZoneShareResult) { + body, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + r.Err = err + return + } + + resp, err := client.Post(ctx, sharesBaseURL(client, zoneID), body, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// Unshare removes a share for a zone. +func Unshare(ctx context.Context, client *gophercloud.ServiceClient, zoneID, shareID string) (r gophercloud.ErrResult) { + resp, err := client.Delete(ctx, shareURL(client, zoneID, shareID), &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/dns/v2/zones/results.go b/openstack/dns/v2/zones/results.go index ae2f27dbc5..9b93e4f4b0 100644 --- a/openstack/dns/v2/zones/results.go +++ b/openstack/dns/v2/zones/results.go @@ -50,6 +50,11 @@ type ZonePage struct { pagination.LinkedPageBase } +// ErrResult represents a generic error result. +type ErrResult struct { + gophercloud.ErrResult +} + // IsEmpty returns true if the page contains no results. func (r ZonePage) IsEmpty() (bool, error) { if r.StatusCode == 204 { @@ -168,3 +173,81 @@ func (r *Zone) UnmarshalJSON(b []byte) error { return err } + +// ZoneShare represents a shared zone. +type ZoneShare struct { + // ID uniquely identifies this zone share. + ID string `json:"id"` + + // ZoneID is the ID of the zone being shared. + ZoneID string `json:"zone_id"` + + // ProjectID is the ID of the project with which the zone is shared. + ProjectID string `json:"project_id"` + + // TargetProjectID is the ID of the project with which the zone is shared. + TargetProjectID string `json:"target_project_id"` + + // CreatedAt is the date when the zone share was created. + CreatedAt time.Time `json:"-"` + + // UpdatedAt is the date when the zone share was last updated. + UpdatedAt time.Time `json:"-"` +} + +func (r *ZoneShare) UnmarshalJSON(b []byte) error { + type tmp ZoneShare + var s struct { + tmp + CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"` + UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"` + } + + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = ZoneShare(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + r.UpdatedAt = time.Time(s.UpdatedAt) + + return nil +} + +// ZoneShareResult is the result of a GetZoneShare request. +type ZoneShareResult struct { + gophercloud.Result +} + +// Extract interprets a GetResult, CreateResult or UpdateResult as a Zone. +// An error is returned if the original call or the extraction failed. +func (r ZoneShareResult) Extract() (*ZoneShare, error) { + var s *ZoneShare + err := r.ExtractInto(&s) + return s, err +} + +// ZoneSharePage is a single page of ZoneShare results. +type ZoneSharePage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if the page contains no results. +func (r ZoneSharePage) IsEmpty() (bool, error) { + if r.StatusCode == 204 { + return true, nil + } + + s, err := ExtractZoneShares(r) + return len(s) == 0, err +} + +// ExtractZoneShares extracts a slice of ZoneShares from a List result. +func ExtractZoneShares(r pagination.Page) ([]ZoneShare, error) { + var s struct { + ZoneShares []ZoneShare `json:"shared_zones"` + } + err := (r.(ZoneSharePage)).ExtractInto(&s) + return s.ZoneShares, err +} diff --git a/openstack/dns/v2/zones/testing/fixtures_test.go b/openstack/dns/v2/zones/testing/fixtures_test.go index d7d9120643..120a6818ad 100644 --- a/openstack/dns/v2/zones/testing/fixtures_test.go +++ b/openstack/dns/v2/zones/testing/fixtures_test.go @@ -143,24 +143,37 @@ var SecondZone = zones.Zone{ var ExpectedZonesSlice = []zones.Zone{FirstZone, SecondZone} // HandleListSuccessfully configures the test server to respond to a List request. -func HandleListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/zones", func(w http.ResponseWriter, r *http.Request) { +func HandleListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/zones", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) + }) +} + +// HandleListAllProjectsSuccessfully configures the test server to respond to a List request +// with the X-Auth-All-Projects header set. +func HandleListAllProjectsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/zones", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "X-Auth-All-Projects", "true") + + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, ListOutput) }) } // HandleGetSuccessfully configures the test server to respond to a List request. -func HandleGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/zones/a86dba58-0043-4cc6-a1bb-69d5e86f3ca3", func(w http.ResponseWriter, r *http.Request) { +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/zones/a86dba58-0043-4cc6-a1bb-69d5e86f3ca3", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -204,15 +217,15 @@ const CreateZoneResponse = ` var CreatedZone = FirstZone // HandleZoneCreationSuccessfully configures the test server to respond to a Create request. -func HandleCreateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/zones", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/zones", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateZoneRequest) w.WriteHeader(http.StatusCreated) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, CreateZoneResponse) + fmt.Fprint(w, CreateZoneResponse) }) } @@ -250,8 +263,8 @@ const UpdateZoneResponse = ` ` // HandleZoneUpdateSuccessfully configures the test server to respond to an Update request. -func HandleUpdateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/zones/a86dba58-0043-4cc6-a1bb-69d5e86f3ca3", +func HandleUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/zones/a86dba58-0043-4cc6-a1bb-69d5e86f3ca3", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -259,7 +272,7 @@ func HandleUpdateSuccessfully(t *testing.T) { w.WriteHeader(http.StatusOK) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, UpdateZoneResponse) + fmt.Fprint(w, UpdateZoneResponse) }) } @@ -289,14 +302,68 @@ const DeleteZoneResponse = ` ` // HandleZoneDeleteSuccessfully configures the test server to respond to an Delete request. -func HandleDeleteSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/zones/a86dba58-0043-4cc6-a1bb-69d5e86f3ca3", +func HandleDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/zones/a86dba58-0043-4cc6-a1bb-69d5e86f3ca3", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, DeleteZoneResponse) + fmt.Fprint(w, DeleteZoneResponse) }) } + +// ShareZoneResponse is a sample response to share a zone. +const ShareZoneResponse = ` +{ + "id": "fd40b017-bf97-461c-8d30-d4e922b28edd", + "zone_id": "a3365b47-ee93-43ad-9a60-2b2ca96b1898", + "project_id": "16ade46c85a1435bb86d9138d37da57e", + "target_project_id": "232e37df46af42089710e2ae39111c2f", + "created_at": "2022-11-30T22:20:27.000000", + "updated_at": null, + "links": { + "self": "http://127.0.0.1:60053/v2/zones/a3365b47-ee93-43ad-9a60-2b2ca96b1898/shares/fd40b017-bf97-461c-8d30-d4e922b28edd", + "zone": "http://127.0.0.1:60053/v2/zones/a3365b47-ee93-43ad-9a60-2b2ca96b1898" + } +} +` + +// ShareZoneCreatedAt is the expected created at time for the shared zone +var ShareZoneCreatedAt, _ = time.Parse(gophercloud.RFC3339MilliNoZ, "2022-11-30T22:20:27.000000") + +// ShareZone is the expected shared zone +var ShareZone = zones.ZoneShare{ + ID: "fd40b017-bf97-461c-8d30-d4e922b28edd", + ZoneID: "a3365b47-ee93-43ad-9a60-2b2ca96b1898", + ProjectID: "16ade46c85a1435bb86d9138d37da57e", + TargetProjectID: "232e37df46af42089710e2ae39111c2f", + CreatedAt: ShareZoneCreatedAt, +} + +// ListSharesResponse is a sample response to list zone shares. +const ListSharesResponse = ` +{ + "shared_zones": [ + { + "id": "fd40b017-bf97-461c-8d30-d4e922b28edd", + "zone_id": "a3365b47-ee93-43ad-9a60-2b2ca96b1898", + "project_id": "16ade46c85a1435bb86d9138d37da57e", + "target_project_id": "232e37df46af42089710e2ae39111c2f", + "created_at": "2022-11-30T22:20:27.000000", + "updated_at": null, + "links": { + "self": "http://127.0.0.1:60053/v2/zones/a3365b47-ee93-43ad-9a60-2b2ca96b1898/shares/fd40b017-bf97-461c-8d30-d4e922b28edd", + "zone": "http://127.0.0.1:60053/v2/zones/a3365b47-ee93-43ad-9a60-2b2ca96b1898" + } + } + ], + "links": { + "self": "http://127.0.0.1:60053/v2/zones/a3365b47-ee93-43ad-9a60-2b2ca96b1898/shares" + } +} +` + +// ListZoneShares is the expected list of shared zones +var ListZoneShares = []zones.ZoneShare{ShareZone} diff --git a/openstack/dns/v2/zones/testing/requests_test.go b/openstack/dns/v2/zones/testing/requests_test.go index 0b64e81ecf..a5bb2ccbfb 100644 --- a/openstack/dns/v2/zones/testing/requests_test.go +++ b/openstack/dns/v2/zones/testing/requests_test.go @@ -2,6 +2,10 @@ package testing import ( "context" + "encoding/json" + "fmt" + "io" + "net/http" "testing" "github.com/gophercloud/gophercloud/v2/openstack/dns/v2/zones" @@ -11,12 +15,12 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer) count := 0 - err := zones.List(client.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := zones.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := zones.ExtractZones(page) th.AssertNoErr(t, err) @@ -29,11 +33,24 @@ func TestList(t *testing.T) { } func TestListAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer) - allPages, err := zones.List(client.ServiceClient(), nil).AllPages(context.TODO()) + allPages, err := zones.List(client.ServiceClient(fakeServer), nil).AllPages(context.TODO()) + th.AssertNoErr(t, err) + allZones, err := zones.ExtractZones(allPages) + th.AssertNoErr(t, err) + th.CheckEquals(t, 2, len(allZones)) +} + +func TestListWithAllProjects(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListAllProjectsSuccessfully(t, fakeServer) + + opts := zones.ListOpts{AllProjects: true} + allPages, err := zones.List(client.ServiceClient(fakeServer), opts).AllPages(context.TODO()) th.AssertNoErr(t, err) allZones, err := zones.ExtractZones(allPages) th.AssertNoErr(t, err) @@ -41,19 +58,19 @@ func TestListAllPages(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSuccessfully(t, fakeServer) - actual, err := zones.Get(context.TODO(), client.ServiceClient(), "a86dba58-0043-4cc6-a1bb-69d5e86f3ca3").Extract() + actual, err := zones.Get(context.TODO(), client.ServiceClient(fakeServer), "a86dba58-0043-4cc6-a1bb-69d5e86f3ca3").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &FirstZone, actual) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateSuccessfully(t, fakeServer) createOpts := zones.CreateOpts{ Name: "example.org.", @@ -63,15 +80,15 @@ func TestCreate(t *testing.T) { Description: "This is an example zone.", } - actual, err := zones.Create(context.TODO(), client.ServiceClient(), createOpts).Extract() + actual, err := zones.Create(context.TODO(), client.ServiceClient(fakeServer), createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &CreatedZone, actual) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateSuccessfully(t, fakeServer) var description = "Updated Description" updateOpts := zones.UpdateOpts{ @@ -85,15 +102,15 @@ func TestUpdate(t *testing.T) { UpdatedZone.TTL = 600 UpdatedZone.Description = "Updated Description" - actual, err := zones.Update(context.TODO(), client.ServiceClient(), UpdatedZone.ID, updateOpts).Extract() + actual, err := zones.Update(context.TODO(), client.ServiceClient(fakeServer), UpdatedZone.ID, updateOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &UpdatedZone, actual) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteSuccessfully(t, fakeServer) DeletedZone := CreatedZone DeletedZone.Status = "PENDING" @@ -101,7 +118,70 @@ func TestDelete(t *testing.T) { DeletedZone.TTL = 600 DeletedZone.Description = "Updated Description" - actual, err := zones.Delete(context.TODO(), client.ServiceClient(), DeletedZone.ID).Extract() + actual, err := zones.Delete(context.TODO(), client.ServiceClient(fakeServer), DeletedZone.ID).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &DeletedZone, actual) } + +func TestShare(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/zones/zone-id/shares", func(w http.ResponseWriter, r *http.Request) { + th.AssertEquals(t, "POST", r.Method) + + body, err := io.ReadAll(r.Body) + defer r.Body.Close() + th.AssertNoErr(t, err) + + var reqBody map[string]string + err = json.Unmarshal(body, &reqBody) + th.AssertNoErr(t, err) + expectedBody := map[string]string{"target_project_id": "project-id"} + th.CheckDeepEquals(t, expectedBody, reqBody) + + w.WriteHeader(http.StatusCreated) + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, ShareZoneResponse) + }) + + opts := zones.ShareZoneOpts{TargetProjectID: "project-id"} + zone, err := zones.Share(context.TODO(), client.ServiceClient(fakeServer), "zone-id", opts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ShareZone, *zone) +} + +func TestUnshare(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/zones/zone-id/shares/share-id", func(w http.ResponseWriter, r *http.Request) { + th.AssertEquals(t, "DELETE", r.Method) + w.WriteHeader(http.StatusNoContent) + }) + + err := zones.Unshare(context.TODO(), client.ServiceClient(fakeServer), "zone-id", "share-id").ExtractErr() + th.AssertNoErr(t, err) +} + +func TestListShares(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/zones/zone-id/shares", func(w http.ResponseWriter, r *http.Request) { + th.AssertEquals(t, "GET", r.Method) + th.AssertEquals(t, "true", r.Header.Get("X-Auth-All-Projects")) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, ListSharesResponse) + }) + + opts := zones.ListSharesOpts{ + AllProjects: true, + } + pages, err := zones.ListShares(client.ServiceClient(fakeServer), "zone-id", opts).AllPages(context.TODO()) + th.AssertNoErr(t, err) + actual, err := zones.ExtractZoneShares(pages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ListZoneShares, actual) +} diff --git a/openstack/dns/v2/zones/urls.go b/openstack/dns/v2/zones/urls.go index d157b30ef3..99416a3e19 100644 --- a/openstack/dns/v2/zones/urls.go +++ b/openstack/dns/v2/zones/urls.go @@ -2,10 +2,22 @@ package zones import "github.com/gophercloud/gophercloud/v2" +// baseURL returns the base URL for zones. func baseURL(c *gophercloud.ServiceClient) string { return c.ServiceURL("zones") } +// zoneURL returns the URL for a specific zone. func zoneURL(c *gophercloud.ServiceClient, zoneID string) string { return c.ServiceURL("zones", zoneID) } + +// sharesBaseURL returns the URL for shared zones. +func sharesBaseURL(c *gophercloud.ServiceClient, zoneID string) string { + return c.ServiceURL("zones", zoneID, "shares") +} + +// shareURL returns the URL for a shared zone. +func shareURL(c *gophercloud.ServiceClient, zoneID, sharedZoneID string) string { + return c.ServiceURL("zones", zoneID, "shares", sharedZoneID) +} diff --git a/openstack/doc.go b/openstack/doc.go index 4d89fb5d0b..538b93f76e 100644 --- a/openstack/doc.go +++ b/openstack/doc.go @@ -7,7 +7,7 @@ Example of Creating a Service Client ao, err := openstack.AuthOptionsFromEnv() provider, err := openstack.AuthenticatedClient(context.TODO(), ao) - client, err := openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{ + client, err := openstack.NewNetworkV2(context.TODO(), provider, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) */ diff --git a/openstack/endpoint.go b/openstack/endpoint.go new file mode 100644 index 0000000000..6178434423 --- /dev/null +++ b/openstack/endpoint.go @@ -0,0 +1,190 @@ +package openstack + +import ( + "context" + "regexp" + "slices" + "strconv" + + "github.com/gophercloud/gophercloud/v2" + tokens2 "github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens" + tokens3 "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens" + "github.com/gophercloud/gophercloud/v2/openstack/utils" +) + +var versionedServiceTypeAliasRegexp = regexp.MustCompile(`^.*v(\d)$`) + +func extractServiceTypeVersion(serviceType string) int { + matches := versionedServiceTypeAliasRegexp.FindAllStringSubmatch(serviceType, 1) + if matches != nil { + // no point converting to an int + ret, err := strconv.Atoi(matches[0][1]) + if err != nil { + return 0 + } + return ret + } + return 0 +} + +func endpointSupportsVersion(ctx context.Context, client *gophercloud.ProviderClient, serviceType, endpointURL string, expectedVersion int) (bool, error) { + // Swift doesn't support version discovery :( + if expectedVersion == 0 || serviceType == "object-store" { + return true, nil + } + + // Repeating verbatim from keystoneauth1 [1]: + // + // > The sins of our fathers become the blood on our hands. + // > If a user requests an old-style service type such as volumev2, then they + // > are inherently requesting the major API version 2. It's not a good + // > interface, but it's the one that was imposed on the world years ago + // > because the client libraries all hid the version discovery document. + // > In order to be able to ensure that a user who requests volumev2 does not + // > get a block-storage endpoint that only provides v3 of the block-storage + // > service, we need to pull the version out of the service_type. The + // > service-types-authority will prevent the growth of new monstrosities such + // > as this, but in order to move forward without breaking people, we have + // > to just cry in the corner while striking ourselves with thorned branches. + // > That said, for sure only do this hack for officially known service_types. + // + // So yeah, what mordred said. + // + // https://github.com/openstack/keystoneauth/blob/5.10.0/keystoneauth1/discover.py#L270-L290 + impliedVersion := extractServiceTypeVersion(serviceType) + if impliedVersion != 0 && impliedVersion != expectedVersion { + return false, nil + } + + // NOTE(stephenfin) In addition to the above, keystoneauth also supports a URL + // hack whereby it will extract the version from the URL. We may wish to + // implement this too. + + endpointURL, err := utils.BaseVersionedEndpoint(endpointURL) + if err != nil { + return false, err + } + + supportedVersions, err := utils.GetServiceVersions(ctx, client, endpointURL, false) + if err != nil { + return false, err + } + + for _, supportedVersion := range supportedVersions { + if supportedVersion.Major == expectedVersion { + return true, nil + } + } + + return false, nil +} + +/* +V2Endpoint discovers the endpoint URL for a specific service from a +ServiceCatalog acquired during the v2 identity service. + +The specified EndpointOpts are used to identify a unique, unambiguous endpoint +to return. It's an error both when multiple endpoints match the provided +criteria and when none do. The minimum that can be specified is a Type, but you +will also often need to specify a Name and/or a Region depending on what's +available on your OpenStack deployment. +*/ +func V2Endpoint(ctx context.Context, client *gophercloud.ProviderClient, catalog *tokens2.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) { + // Extract Endpoints from the catalog entries that match the requested Type, Name if provided, and Region if provided. + // + // If multiple endpoints are found, we return the first result and disregard the rest. + // This behavior matches the Python library. See GH-1764. + for _, entry := range catalog.Entries { + if (slices.Contains(opts.Types(), entry.Type)) && (opts.Name == "" || entry.Name == opts.Name) { + for _, endpoint := range entry.Endpoints { + if opts.Region != "" && endpoint.Region != opts.Region { + continue + } + + var endpointURL string + switch opts.Availability { + case gophercloud.AvailabilityPublic: + endpointURL = gophercloud.NormalizeURL(endpoint.PublicURL) + case gophercloud.AvailabilityInternal: + endpointURL = gophercloud.NormalizeURL(endpoint.InternalURL) + case gophercloud.AvailabilityAdmin: + endpointURL = gophercloud.NormalizeURL(endpoint.AdminURL) + default: + err := &ErrInvalidAvailabilityProvided{} + err.Argument = "Availability" + err.Value = opts.Availability + return "", err + } + + endpointSupportsVersion, err := endpointSupportsVersion(ctx, client, entry.Type, endpointURL, opts.Version) + if err != nil { + return "", err + } + if !endpointSupportsVersion { + continue + } + + return endpointURL, nil + } + } + } + + // Report an error if there were no matching endpoints. + err := &gophercloud.ErrEndpointNotFound{} + return "", err +} + +/* +V3Endpoint discovers the endpoint URL for a specific service from a Catalog +acquired during the v3 identity service. + +The specified EndpointOpts are used to identify a unique, unambiguous endpoint +to return. It's an error both when multiple endpoints match the provided +criteria and when none do. The minimum that can be specified is a Type, but you +will also often need to specify a Name and/or a Region depending on what's +available on your OpenStack deployment. +*/ +func V3Endpoint(ctx context.Context, client *gophercloud.ProviderClient, catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) { + if opts.Availability != gophercloud.AvailabilityAdmin && + opts.Availability != gophercloud.AvailabilityPublic && + opts.Availability != gophercloud.AvailabilityInternal { + err := &ErrInvalidAvailabilityProvided{} + err.Argument = "Availability" + err.Value = opts.Availability + return "", err + } + + // Extract Endpoints from the catalog entries that match the requested Type, Interface, + // Name if provided, and Region if provided. + // + // If multiple endpoints are found, we return the first result and disregard the rest. + // This behavior matches the Python library. See GH-1764. + for _, entry := range catalog.Entries { + if (slices.Contains(opts.Types(), entry.Type)) && (opts.Name == "" || entry.Name == opts.Name) { + for _, endpoint := range entry.Endpoints { + if opts.Availability != gophercloud.Availability(endpoint.Interface) { + continue + } + if opts.Region != "" && endpoint.Region != opts.Region && endpoint.RegionID != opts.Region { + continue + } + + endpointURL := gophercloud.NormalizeURL(endpoint.URL) + + endpointSupportsVersion, err := endpointSupportsVersion(ctx, client, entry.Type, endpointURL, opts.Version) + if err != nil { + return "", err + } + if !endpointSupportsVersion { + continue + } + + return endpointURL, nil + } + } + } + + // Report an error if there were no matching endpoints. + err := &gophercloud.ErrEndpointNotFound{} + return "", err +} diff --git a/openstack/endpoint_location.go b/openstack/endpoint_location.go index 2cdbd3e7f7..579139c26d 100644 --- a/openstack/endpoint_location.go +++ b/openstack/endpoint_location.go @@ -1,11 +1,15 @@ package openstack import ( + "slices" + "github.com/gophercloud/gophercloud/v2" tokens2 "github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens" tokens3 "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens" ) +// TODO(stephenfin): Remove this module in v3. The functions below are no longer used. + /* V2EndpointURL discovers the endpoint URL for a specific service from a ServiceCatalog acquired during the v2 identity service. @@ -15,42 +19,39 @@ to return. It's an error both when multiple endpoints match the provided criteria and when none do. The minimum that can be specified is a Type, but you will also often need to specify a Name and/or a Region depending on what's available on your OpenStack deployment. + +Deprecated: This function does not respect the [gophercloud.EndpointOpts.Version] parameter, +which can result in random endpoints being selected. Use [V2Endpoint] instead. */ func V2EndpointURL(catalog *tokens2.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) { // Extract Endpoints from the catalog entries that match the requested Type, Name if provided, and Region if provided. - var endpoints = make([]tokens2.Endpoint, 0, 1) + // + // If multiple endpoints are found, we return the first result and disregard the rest. + // This behavior matches the Python library. See GH-1764. for _, entry := range catalog.Entries { - if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) { + if (slices.Contains(opts.Types(), entry.Type)) && (opts.Name == "" || entry.Name == opts.Name) { for _, endpoint := range entry.Endpoints { - if opts.Region == "" || endpoint.Region == opts.Region { - endpoints = append(endpoints, endpoint) + if opts.Region != "" && endpoint.Region != opts.Region { + continue } - } - } - } - // If multiple endpoints were found, use the first result - // and disregard the other endpoints. - // - // This behavior matches the Python library. See GH-1764. - if len(endpoints) > 1 { - endpoints = endpoints[0:1] - } + var endpointURL string + switch opts.Availability { + case gophercloud.AvailabilityPublic: + endpointURL = gophercloud.NormalizeURL(endpoint.PublicURL) + case gophercloud.AvailabilityInternal: + endpointURL = gophercloud.NormalizeURL(endpoint.InternalURL) + case gophercloud.AvailabilityAdmin: + endpointURL = gophercloud.NormalizeURL(endpoint.AdminURL) + default: + err := &ErrInvalidAvailabilityProvided{} + err.Argument = "Availability" + err.Value = opts.Availability + return "", err + } - // Extract the appropriate URL from the matching Endpoint. - for _, endpoint := range endpoints { - switch opts.Availability { - case gophercloud.AvailabilityPublic: - return gophercloud.NormalizeURL(endpoint.PublicURL), nil - case gophercloud.AvailabilityInternal: - return gophercloud.NormalizeURL(endpoint.InternalURL), nil - case gophercloud.AvailabilityAdmin: - return gophercloud.NormalizeURL(endpoint.AdminURL), nil - default: - err := &ErrInvalidAvailabilityProvided{} - err.Argument = "Availability" - err.Value = opts.Availability - return "", err + return endpointURL, nil + } } } @@ -68,43 +69,40 @@ to return. It's an error both when multiple endpoints match the provided criteria and when none do. The minimum that can be specified is a Type, but you will also often need to specify a Name and/or a Region depending on what's available on your OpenStack deployment. + +Deprecated: This function does not respect the [gophercloud.EndpointOpts.Version] parameter, +which can result in random endpoint being selected. Use [V3Endpoint] instead. */ func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) { + if opts.Availability != gophercloud.AvailabilityAdmin && + opts.Availability != gophercloud.AvailabilityPublic && + opts.Availability != gophercloud.AvailabilityInternal { + err := &ErrInvalidAvailabilityProvided{} + err.Argument = "Availability" + err.Value = opts.Availability + return "", err + } + // Extract Endpoints from the catalog entries that match the requested Type, Interface, // Name if provided, and Region if provided. - var endpoints = make([]tokens3.Endpoint, 0, 1) + // + // If multiple endpoints are found, we return the first result and disregard the rest. + // This behavior matches the Python library. See GH-1764. for _, entry := range catalog.Entries { - if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) { + if (slices.Contains(opts.Types(), entry.Type)) && (opts.Name == "" || entry.Name == opts.Name) { for _, endpoint := range entry.Endpoints { - if opts.Availability != gophercloud.AvailabilityAdmin && - opts.Availability != gophercloud.AvailabilityPublic && - opts.Availability != gophercloud.AvailabilityInternal { - err := &ErrInvalidAvailabilityProvided{} - err.Argument = "Availability" - err.Value = opts.Availability - return "", err + if opts.Availability != gophercloud.Availability(endpoint.Interface) { + continue } - if (opts.Availability == gophercloud.Availability(endpoint.Interface)) && - (opts.Region == "" || endpoint.Region == opts.Region || endpoint.RegionID == opts.Region) { - endpoints = append(endpoints, endpoint) + if opts.Region != "" && endpoint.Region != opts.Region && endpoint.RegionID != opts.Region { + continue } + + return gophercloud.NormalizeURL(endpoint.URL), nil } } } - // If multiple endpoints were found, use the first result - // and disregard the other endpoints. - // - // This behavior matches the Python library. See GH-1764. - if len(endpoints) > 1 { - endpoints = endpoints[0:1] - } - - // Extract the URL from the matching Endpoint. - for _, endpoint := range endpoints { - return gophercloud.NormalizeURL(endpoint.URL), nil - } - // Report an error if there were no matching endpoints. err := &gophercloud.ErrEndpointNotFound{} return "", err diff --git a/openstack/identity/v2/extensions/testing/fixtures_test.go b/openstack/identity/v2/extensions/testing/fixtures_test.go index 3733eba312..cffbc1ee3f 100644 --- a/openstack/identity/v2/extensions/testing/fixtures_test.go +++ b/openstack/identity/v2/extensions/testing/fixtures_test.go @@ -30,14 +30,14 @@ const ListOutput = ` // HandleListExtensionsSuccessfully creates an HTTP handler that returns ListOutput for a List // call. -func HandleListExtensionsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/extensions", func(w http.ResponseWriter, r *http.Request) { +func HandleListExtensionsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/extensions", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "extensions": { "values": [ diff --git a/openstack/identity/v2/extensions/testing/requests_test.go b/openstack/identity/v2/extensions/testing/requests_test.go index 53c10ab169..0ad8310e14 100644 --- a/openstack/identity/v2/extensions/testing/requests_test.go +++ b/openstack/identity/v2/extensions/testing/requests_test.go @@ -12,12 +12,12 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListExtensionsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListExtensionsSuccessfully(t, fakeServer) count := 0 - err := extensions.List(client.ServiceClient()).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := extensions.List(client.ServiceClient(fakeServer)).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := extensions.ExtractExtensions(page) th.AssertNoErr(t, err) @@ -30,11 +30,11 @@ func TestList(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - common.HandleGetExtensionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + common.HandleGetExtensionSuccessfully(t, fakeServer) - actual, err := extensions.Get(context.TODO(), client.ServiceClient(), "agent").Extract() + actual, err := extensions.Get(context.TODO(), client.ServiceClient(fakeServer), "agent").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, common.SingleExtension, actual) } diff --git a/openstack/identity/v2/roles/testing/fixtures_test.go b/openstack/identity/v2/roles/testing/fixtures_test.go index 0b5af6e541..ea67ae9876 100644 --- a/openstack/identity/v2/roles/testing/fixtures_test.go +++ b/openstack/identity/v2/roles/testing/fixtures_test.go @@ -6,18 +6,18 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) -func MockListRoleResponse(t *testing.T) { - th.Mux.HandleFunc("/OS-KSADM/roles", func(w http.ResponseWriter, r *http.Request) { +func MockListRoleResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-KSADM/roles", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "roles": [ { @@ -31,18 +31,18 @@ func MockListRoleResponse(t *testing.T) { }) } -func MockAddUserRoleResponse(t *testing.T) { - th.Mux.HandleFunc("/tenants/{tenant_id}/users/{user_id}/roles/OS-KSADM/{role_id}", func(w http.ResponseWriter, r *http.Request) { +func MockAddUserRoleResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/tenants/{tenant_id}/users/{user_id}/roles/OS-KSADM/{role_id}", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusCreated) }) } -func MockDeleteUserRoleResponse(t *testing.T) { - th.Mux.HandleFunc("/tenants/{tenant_id}/users/{user_id}/roles/OS-KSADM/{role_id}", func(w http.ResponseWriter, r *http.Request) { +func MockDeleteUserRoleResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/tenants/{tenant_id}/users/{user_id}/roles/OS-KSADM/{role_id}", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) } diff --git a/openstack/identity/v2/roles/testing/requests_test.go b/openstack/identity/v2/roles/testing/requests_test.go index a75f362208..52b4c09415 100644 --- a/openstack/identity/v2/roles/testing/requests_test.go +++ b/openstack/identity/v2/roles/testing/requests_test.go @@ -11,14 +11,14 @@ import ( ) func TestRole(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListRoleResponse(t) + MockListRoleResponse(t, fakeServer) count := 0 - err := roles.List(client.ServiceClient()).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := roles.List(client.ServiceClient(fakeServer)).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := roles.ExtractRoles(page) if err != nil { @@ -44,23 +44,23 @@ func TestRole(t *testing.T) { } func TestAddUser(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockAddUserRoleResponse(t) + MockAddUserRoleResponse(t, fakeServer) - err := roles.AddUser(context.TODO(), client.ServiceClient(), "{tenant_id}", "{user_id}", "{role_id}").ExtractErr() + err := roles.AddUser(context.TODO(), client.ServiceClient(fakeServer), "{tenant_id}", "{user_id}", "{role_id}").ExtractErr() th.AssertNoErr(t, err) } func TestDeleteUser(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDeleteUserRoleResponse(t) + MockDeleteUserRoleResponse(t, fakeServer) - err := roles.DeleteUser(context.TODO(), client.ServiceClient(), "{tenant_id}", "{user_id}", "{role_id}").ExtractErr() + err := roles.DeleteUser(context.TODO(), client.ServiceClient(fakeServer), "{tenant_id}", "{user_id}", "{role_id}").ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/identity/v2/tenants/requests.go b/openstack/identity/v2/tenants/requests.go index a08980df2c..84a8b9df1d 100644 --- a/openstack/identity/v2/tenants/requests.go +++ b/openstack/identity/v2/tenants/requests.go @@ -7,6 +7,12 @@ import ( "github.com/gophercloud/gophercloud/v2/pagination" ) +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToTenantListQuery() (string, error) +} + // ListOpts filters the Tenants that are returned by the List call. type ListOpts struct { // Marker is the ID of the last Tenant on the previous page. @@ -16,15 +22,21 @@ type ListOpts struct { Limit int `q:"limit"` } +// ToTenantListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToTenantListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + // List enumerates the Tenants to which the current token has access. -func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager { +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { url := listURL(client) if opts != nil { - q, err := gophercloud.BuildQueryString(opts) + query, err := opts.ToTenantListQuery() if err != nil { return pagination.Pager{Err: err} } - url += q.String() + url += query } return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { return TenantPage{pagination.LinkedPageBase{PageResult: r}} diff --git a/openstack/identity/v2/tenants/results.go b/openstack/identity/v2/tenants/results.go index 2569ffec05..4ab04de9da 100644 --- a/openstack/identity/v2/tenants/results.go +++ b/openstack/identity/v2/tenants/results.go @@ -36,7 +36,7 @@ func (r TenantPage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the tenants_links section of the result. -func (r TenantPage) NextPageURL() (string, error) { +func (r TenantPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"tenants_links"` } diff --git a/openstack/identity/v2/tenants/testing/fixtures_test.go b/openstack/identity/v2/tenants/testing/fixtures_test.go index 7c50152faa..e35fd52752 100644 --- a/openstack/identity/v2/tenants/testing/fixtures_test.go +++ b/openstack/identity/v2/tenants/testing/fixtures_test.go @@ -51,20 +51,20 @@ var ExpectedTenantSlice = []tenants.Tenant{RedTeam, BlueTeam} // HandleListTenantsSuccessfully creates an HTTP handler at `/tenants` on the test handler mux that // responds with a list of two tenants. -func HandleListTenantsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/tenants", func(w http.ResponseWriter, r *http.Request) { +func HandleListTenantsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/tenants", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } -func mockCreateTenantResponse(t *testing.T) { - th.Mux.HandleFunc("/tenants", func(w http.ResponseWriter, r *http.Request) { +func mockCreateTenantResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/tenants", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -81,7 +81,7 @@ func mockCreateTenantResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "tenant": { "name": "new_tenant", @@ -94,16 +94,16 @@ func mockCreateTenantResponse(t *testing.T) { }) } -func mockDeleteTenantResponse(t *testing.T) { - th.Mux.HandleFunc("/tenants/2466f69cd4714d89a548a68ed97ffcd4", func(w http.ResponseWriter, r *http.Request) { +func mockDeleteTenantResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/tenants/2466f69cd4714d89a548a68ed97ffcd4", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) } -func mockUpdateTenantResponse(t *testing.T) { - th.Mux.HandleFunc("/tenants/5c62ef576dc7444cbb73b1fe84b97648", func(w http.ResponseWriter, r *http.Request) { +func mockUpdateTenantResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/tenants/5c62ef576dc7444cbb73b1fe84b97648", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -120,7 +120,7 @@ func mockUpdateTenantResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "tenant": { "name": "new_name", @@ -133,15 +133,15 @@ func mockUpdateTenantResponse(t *testing.T) { }) } -func mockGetTenantResponse(t *testing.T) { - th.Mux.HandleFunc("/tenants/5c62ef576dc7444cbb73b1fe84b97648", func(w http.ResponseWriter, r *http.Request) { +func mockGetTenantResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/tenants/5c62ef576dc7444cbb73b1fe84b97648", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "tenant": { "name": "new_tenant", diff --git a/openstack/identity/v2/tenants/testing/requests_test.go b/openstack/identity/v2/tenants/testing/requests_test.go index 1e3e05ce3c..feb3bc19a6 100644 --- a/openstack/identity/v2/tenants/testing/requests_test.go +++ b/openstack/identity/v2/tenants/testing/requests_test.go @@ -12,12 +12,12 @@ import ( ) func TestListTenants(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListTenantsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListTenantsSuccessfully(t, fakeServer) count := 0 - err := tenants.List(client.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := tenants.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := tenants.ExtractTenants(page) @@ -28,14 +28,14 @@ func TestListTenants(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestCreateTenant(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - mockCreateTenantResponse(t) + mockCreateTenantResponse(t, fakeServer) opts := tenants.CreateOpts{ Name: "new_tenant", @@ -43,7 +43,7 @@ func TestCreateTenant(t *testing.T) { Enabled: gophercloud.Enabled, } - tenant, err := tenants.Create(context.TODO(), client.ServiceClient(), opts).Extract() + tenant, err := tenants.Create(context.TODO(), client.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) @@ -58,20 +58,20 @@ func TestCreateTenant(t *testing.T) { } func TestDeleteTenant(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - mockDeleteTenantResponse(t) + mockDeleteTenantResponse(t, fakeServer) - err := tenants.Delete(context.TODO(), client.ServiceClient(), "2466f69cd4714d89a548a68ed97ffcd4").ExtractErr() + err := tenants.Delete(context.TODO(), client.ServiceClient(fakeServer), "2466f69cd4714d89a548a68ed97ffcd4").ExtractErr() th.AssertNoErr(t, err) } func TestUpdateTenant(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - mockUpdateTenantResponse(t) + mockUpdateTenantResponse(t, fakeServer) id := "5c62ef576dc7444cbb73b1fe84b97648" description := "This is new name" @@ -81,7 +81,7 @@ func TestUpdateTenant(t *testing.T) { Enabled: gophercloud.Enabled, } - tenant, err := tenants.Update(context.TODO(), client.ServiceClient(), id, opts).Extract() + tenant, err := tenants.Update(context.TODO(), client.ServiceClient(fakeServer), id, opts).Extract() th.AssertNoErr(t, err) @@ -96,12 +96,12 @@ func TestUpdateTenant(t *testing.T) { } func TestGetTenant(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - mockGetTenantResponse(t) + mockGetTenantResponse(t, fakeServer) - tenant, err := tenants.Get(context.TODO(), client.ServiceClient(), "5c62ef576dc7444cbb73b1fe84b97648").Extract() + tenant, err := tenants.Get(context.TODO(), client.ServiceClient(fakeServer), "5c62ef576dc7444cbb73b1fe84b97648").Extract() th.AssertNoErr(t, err) expected := &tenants.Tenant{ diff --git a/openstack/identity/v2/tokens/testing/fixtures_test.go b/openstack/identity/v2/tokens/testing/fixtures_test.go index 8eb7d6f7ea..bd093fc7d5 100644 --- a/openstack/identity/v2/tokens/testing/fixtures_test.go +++ b/openstack/identity/v2/tokens/testing/fixtures_test.go @@ -9,7 +9,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tenants" "github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens" th "github.com/gophercloud/gophercloud/v2/testhelper" - thclient "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) // ExpectedToken is the token that should be parsed from TokenCreationResponse. @@ -142,8 +142,8 @@ const TokenGetResponse = ` // HandleTokenPost expects a POST against a /tokens handler, ensures that the request body has been // constructed properly given certain auth options, and returns the result. -func HandleTokenPost(t *testing.T, requestJSON string) { - th.Mux.HandleFunc("/tokens", func(w http.ResponseWriter, r *http.Request) { +func HandleTokenPost(t *testing.T, fakeServer th.FakeServer, requestJSON string) { + fakeServer.Mux.HandleFunc("/tokens", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") @@ -152,20 +152,20 @@ func HandleTokenPost(t *testing.T, requestJSON string) { } w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, TokenCreationResponse) + fmt.Fprint(w, TokenCreationResponse) }) } // HandleTokenGet expects a Get against a /tokens handler, ensures that the request body has been // constructed properly given certain auth options, and returns the result. -func HandleTokenGet(t *testing.T, token string) { - th.Mux.HandleFunc("/tokens/"+token, func(w http.ResponseWriter, r *http.Request) { +func HandleTokenGet(t *testing.T, fakeServer th.FakeServer, token string) { + fakeServer.Mux.HandleFunc("/tokens/"+token, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") - th.TestHeader(t, r, "X-Auth-Token", thclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, TokenGetResponse) + fmt.Fprint(w, TokenGetResponse) }) } diff --git a/openstack/identity/v2/tokens/testing/requests_test.go b/openstack/identity/v2/tokens/testing/requests_test.go index a688369d65..21ff0a814d 100644 --- a/openstack/identity/v2/tokens/testing/requests_test.go +++ b/openstack/identity/v2/tokens/testing/requests_test.go @@ -11,19 +11,19 @@ import ( ) func tokenPost(t *testing.T, options gophercloud.AuthOptions, requestJSON string) tokens.CreateResult { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleTokenPost(t, requestJSON) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleTokenPost(t, fakeServer, requestJSON) - return tokens.Create(context.TODO(), client.ServiceClient(), options) + return tokens.Create(context.TODO(), client.ServiceClient(fakeServer), options) } func tokenPostErr(t *testing.T, options gophercloud.AuthOptions, expectedErr error) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleTokenPost(t, "") + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleTokenPost(t, fakeServer, "") - actualErr := tokens.Create(context.TODO(), client.ServiceClient(), options).Err + actualErr := tokens.Create(context.TODO(), client.ServiceClient(fakeServer), options).Err th.CheckDeepEquals(t, expectedErr, actualErr) } @@ -94,10 +94,10 @@ func TestRequireUsername(t *testing.T) { } func tokenGet(t *testing.T, tokenId string) tokens.GetResult { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleTokenGet(t, tokenId) - return tokens.Get(context.TODO(), client.ServiceClient(), tokenId) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleTokenGet(t, fakeServer, tokenId) + return tokens.Get(context.TODO(), client.ServiceClient(fakeServer), tokenId) } func TestGetWithToken(t *testing.T) { diff --git a/openstack/identity/v2/users/testing/fixtures_test.go b/openstack/identity/v2/users/testing/fixtures_test.go index f38f05dba2..260333293a 100644 --- a/openstack/identity/v2/users/testing/fixtures_test.go +++ b/openstack/identity/v2/users/testing/fixtures_test.go @@ -6,18 +6,18 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) -func MockListUserResponse(t *testing.T) { - th.Mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) { +func MockListUserResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "users":[ { @@ -42,10 +42,10 @@ func MockListUserResponse(t *testing.T) { }) } -func mockCreateUserResponse(t *testing.T) { - th.Mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) { +func mockCreateUserResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, ` { @@ -61,7 +61,7 @@ func mockCreateUserResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "user": { "name": "new_user", @@ -75,15 +75,15 @@ func mockCreateUserResponse(t *testing.T) { }) } -func mockGetUserResponse(t *testing.T) { - th.Mux.HandleFunc("/users/new_user", func(w http.ResponseWriter, r *http.Request) { +func mockGetUserResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/new_user", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "user": { "name": "new_user", @@ -97,10 +97,10 @@ func mockGetUserResponse(t *testing.T) { }) } -func mockUpdateUserResponse(t *testing.T) { - th.Mux.HandleFunc("/users/c39e3de9be2d4c779f1dfd6abacc176d", func(w http.ResponseWriter, r *http.Request) { +func mockUpdateUserResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/c39e3de9be2d4c779f1dfd6abacc176d", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, ` { @@ -115,7 +115,7 @@ func mockUpdateUserResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "user": { "name": "new_name", @@ -129,23 +129,23 @@ func mockUpdateUserResponse(t *testing.T) { }) } -func mockDeleteUserResponse(t *testing.T) { - th.Mux.HandleFunc("/users/c39e3de9be2d4c779f1dfd6abacc176d", func(w http.ResponseWriter, r *http.Request) { +func mockDeleteUserResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/c39e3de9be2d4c779f1dfd6abacc176d", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) } -func mockListRolesResponse(t *testing.T) { - th.Mux.HandleFunc("/tenants/1d8b6120dcc640fda4fc9194ffc80273/users/c39e3de9be2d4c779f1dfd6abacc176d/roles", func(w http.ResponseWriter, r *http.Request) { +func mockListRolesResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/tenants/1d8b6120dcc640fda4fc9194ffc80273/users/c39e3de9be2d4c779f1dfd6abacc176d/roles", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "roles": [ { diff --git a/openstack/identity/v2/users/testing/requests_test.go b/openstack/identity/v2/users/testing/requests_test.go index c66207d0c3..78553213e4 100644 --- a/openstack/identity/v2/users/testing/requests_test.go +++ b/openstack/identity/v2/users/testing/requests_test.go @@ -12,14 +12,14 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListUserResponse(t) + MockListUserResponse(t, fakeServer) count := 0 - err := users.List(client.ServiceClient()).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := users.List(client.ServiceClient(fakeServer)).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := users.ExtractUsers(page) th.AssertNoErr(t, err) @@ -50,10 +50,10 @@ func TestList(t *testing.T) { } func TestCreateUser(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - mockCreateUserResponse(t) + mockCreateUserResponse(t, fakeServer) opts := users.CreateOpts{ Name: "new_user", @@ -62,7 +62,7 @@ func TestCreateUser(t *testing.T) { Email: "new_user@foo.com", } - user, err := users.Create(context.TODO(), client.ServiceClient(), opts).Extract() + user, err := users.Create(context.TODO(), client.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) @@ -78,12 +78,12 @@ func TestCreateUser(t *testing.T) { } func TestGetUser(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - mockGetUserResponse(t) + mockGetUserResponse(t, fakeServer) - user, err := users.Get(context.TODO(), client.ServiceClient(), "new_user").Extract() + user, err := users.Get(context.TODO(), client.ServiceClient(fakeServer), "new_user").Extract() th.AssertNoErr(t, err) expected := &users.User{ @@ -98,10 +98,10 @@ func TestGetUser(t *testing.T) { } func TestUpdateUser(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - mockUpdateUserResponse(t) + mockUpdateUserResponse(t, fakeServer) id := "c39e3de9be2d4c779f1dfd6abacc176d" opts := users.UpdateOpts{ @@ -110,7 +110,7 @@ func TestUpdateUser(t *testing.T) { Email: "new_email@foo.com", } - user, err := users.Update(context.TODO(), client.ServiceClient(), id, opts).Extract() + user, err := users.Update(context.TODO(), client.ServiceClient(fakeServer), id, opts).Extract() th.AssertNoErr(t, err) @@ -126,25 +126,25 @@ func TestUpdateUser(t *testing.T) { } func TestDeleteUser(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - mockDeleteUserResponse(t) + mockDeleteUserResponse(t, fakeServer) - res := users.Delete(context.TODO(), client.ServiceClient(), "c39e3de9be2d4c779f1dfd6abacc176d") + res := users.Delete(context.TODO(), client.ServiceClient(fakeServer), "c39e3de9be2d4c779f1dfd6abacc176d") th.AssertNoErr(t, res.Err) } func TestListingUserRoles(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - mockListRolesResponse(t) + mockListRolesResponse(t, fakeServer) tenantID := "1d8b6120dcc640fda4fc9194ffc80273" userID := "c39e3de9be2d4c779f1dfd6abacc176d" - err := users.ListRoles(client.ServiceClient(), tenantID, userID).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := users.ListRoles(client.ServiceClient(fakeServer), tenantID, userID).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { actual, err := users.ExtractRoles(page) th.AssertNoErr(t, err) diff --git a/openstack/identity/v3/applicationcredentials/results.go b/openstack/identity/v3/applicationcredentials/results.go index c1745e7bec..8d15902d75 100644 --- a/openstack/identity/v3/applicationcredentials/results.go +++ b/openstack/identity/v3/applicationcredentials/results.go @@ -114,7 +114,7 @@ func (r ApplicationCredentialPage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (r ApplicationCredentialPage) NextPageURL() (string, error) { +func (r ApplicationCredentialPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links struct { Next string `json:"next"` @@ -168,7 +168,7 @@ func (r AccessRulePage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (r AccessRulePage) NextPageURL() (string, error) { +func (r AccessRulePage) NextPageURL(endpointURL string) (string, error) { var s struct { Links struct { Next string `json:"next"` diff --git a/openstack/identity/v3/applicationcredentials/testing/fixtures_test.go b/openstack/identity/v3/applicationcredentials/testing/fixtures_test.go index fabb021a0c..5289cc29b3 100644 --- a/openstack/identity/v3/applicationcredentials/testing/fixtures_test.go +++ b/openstack/identity/v3/applicationcredentials/testing/fixtures_test.go @@ -407,73 +407,73 @@ var ExpectedApplicationCredentialsSlice = []applicationcredentials.ApplicationCr // HandleListApplicationCredentialsSuccessfully creates an HTTP handler at `/users` on the // test handler mux that responds with a list of two applicationcredentials. -func HandleListApplicationCredentialsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/application_credentials", func(w http.ResponseWriter, r *http.Request) { +func HandleListApplicationCredentialsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/application_credentials", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } // HandleGetApplicationCredentialSuccessfully creates an HTTP handler at `/users` on the // test handler mux that responds with a single application credential. -func HandleGetApplicationCredentialSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/application_credentials/f741662395b249c9b8acdebf1722c5ae", func(w http.ResponseWriter, r *http.Request) { +func HandleGetApplicationCredentialSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/application_credentials/f741662395b249c9b8acdebf1722c5ae", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleCreateApplicationCredentialSuccessfully creates an HTTP handler at `/users` on the // test handler mux that tests application credential creation. -func HandleCreateApplicationCredentialSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/application_credentials", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateApplicationCredentialSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/application_credentials", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) } // HandleCreateNoOptionsApplicationCredentialSuccessfully creates an HTTP handler at `/users` on the // test handler mux that tests application credential creation. -func HandleCreateNoSecretApplicationCredentialSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/application_credentials", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateNoSecretApplicationCredentialSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/application_credentials", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateNoSecretRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateNoSecretResponse) + fmt.Fprint(w, CreateNoSecretResponse) }) } -func HandleCreateUnrestrictedApplicationCredentialSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/application_credentials", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateUnrestrictedApplicationCredentialSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/application_credentials", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateUnrestrictedRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateUnrestrictedResponse) + fmt.Fprint(w, CreateUnrestrictedResponse) }) } // HandleDeleteApplicationCredentialSuccessfully creates an HTTP handler at `/users` on the // test handler mux that tests application credential deletion. -func HandleDeleteApplicationCredentialSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/application_credentials/f741662395b249c9b8acdebf1722c5ae", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteApplicationCredentialSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/application_credentials/f741662395b249c9b8acdebf1722c5ae", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -483,36 +483,36 @@ func HandleDeleteApplicationCredentialSuccessfully(t *testing.T) { // HandleListAccessRulesSuccessfully creates an HTTP handler at `/users` on the // test handler mux that responds with a list of two applicationcredentials. -func HandleListAccessRulesSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/access_rules", func(w http.ResponseWriter, r *http.Request) { +func HandleListAccessRulesSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/access_rules", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListAccessRulesOutput) + fmt.Fprint(w, ListAccessRulesOutput) }) } // HandleGetAccessRuleSuccessfully creates an HTTP handler at `/users` on the // test handler mux that responds with a single application credential. -func HandleGetAccessRuleSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/access_rules/07d719df00f349ef8de77d542edf010c", func(w http.ResponseWriter, r *http.Request) { +func HandleGetAccessRuleSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/access_rules/07d719df00f349ef8de77d542edf010c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetAccessRuleOutput) + fmt.Fprint(w, GetAccessRuleOutput) }) } // HandleDeleteAccessRuleSuccessfully creates an HTTP handler at `/users` on the // test handler mux that tests application credential deletion. -func HandleDeleteAccessRuleSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/access_rules/07d719df00f349ef8de77d542edf010c", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteAccessRuleSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/access_rules/07d719df00f349ef8de77d542edf010c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) diff --git a/openstack/identity/v3/applicationcredentials/testing/requests_test.go b/openstack/identity/v3/applicationcredentials/testing/requests_test.go index 11d863609f..b992e79146 100644 --- a/openstack/identity/v3/applicationcredentials/testing/requests_test.go +++ b/openstack/identity/v3/applicationcredentials/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestListApplicationCredentials(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListApplicationCredentialsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListApplicationCredentialsSuccessfully(t, fakeServer) count := 0 - err := applicationcredentials.List(client.ServiceClient(), userID, nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := applicationcredentials.List(client.ServiceClient(fakeServer), userID, nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := applicationcredentials.ExtractApplicationCredentials(page) @@ -27,37 +27,37 @@ func TestListApplicationCredentials(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListApplicationCredentialsAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListApplicationCredentialsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListApplicationCredentialsSuccessfully(t, fakeServer) - allPages, err := applicationcredentials.List(client.ServiceClient(), userID, nil).AllPages(context.TODO()) + allPages, err := applicationcredentials.List(client.ServiceClient(fakeServer), userID, nil).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := applicationcredentials.ExtractApplicationCredentials(allPages) th.AssertNoErr(t, err) th.CheckDeepEquals(t, ExpectedApplicationCredentialsSlice, actual) - th.AssertDeepEquals(t, ExpectedApplicationCredentialsSlice[0].Roles, []applicationcredentials.Role{{ID: "31f87923ae4a4d119aa0b85dcdbeed13", Name: "compute_viewer"}}) - th.AssertDeepEquals(t, ExpectedApplicationCredentialsSlice[1].Roles, []applicationcredentials.Role{{ID: "31f87923ae4a4d119aa0b85dcdbeed13", Name: "compute_viewer"}, {ID: "4494bc5bea1a4105ad7fbba6a7eb9ef4", Name: "network_viewer"}}) + th.AssertDeepEquals(t, []applicationcredentials.Role{{ID: "31f87923ae4a4d119aa0b85dcdbeed13", Name: "compute_viewer"}}, ExpectedApplicationCredentialsSlice[0].Roles) + th.AssertDeepEquals(t, []applicationcredentials.Role{{ID: "31f87923ae4a4d119aa0b85dcdbeed13", Name: "compute_viewer"}, {ID: "4494bc5bea1a4105ad7fbba6a7eb9ef4", Name: "network_viewer"}}, ExpectedApplicationCredentialsSlice[1].Roles) } func TestGetApplicationCredential(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetApplicationCredentialSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetApplicationCredentialSuccessfully(t, fakeServer) - actual, err := applicationcredentials.Get(context.TODO(), client.ServiceClient(), userID, applicationCredentialID).Extract() + actual, err := applicationcredentials.Get(context.TODO(), client.ServiceClient(fakeServer), userID, applicationCredentialID).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, ApplicationCredential, *actual) } func TestCreateApplicationCredential(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateApplicationCredentialSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateApplicationCredentialSuccessfully(t, fakeServer) createOpts := applicationcredentials.CreateOpts{ Name: "test", @@ -77,15 +77,15 @@ func TestCreateApplicationCredential(t *testing.T) { ApplicationCredentialResponse := ApplicationCredential ApplicationCredentialResponse.Secret = "mysecret" - actual, err := applicationcredentials.Create(context.TODO(), client.ServiceClient(), userID, createOpts).Extract() + actual, err := applicationcredentials.Create(context.TODO(), client.ServiceClient(fakeServer), userID, createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, ApplicationCredentialResponse, *actual) } func TestCreateNoSecretApplicationCredential(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateNoSecretApplicationCredentialSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateNoSecretApplicationCredentialSuccessfully(t, fakeServer) createOpts := applicationcredentials.CreateOpts{ Name: "test1", @@ -94,15 +94,15 @@ func TestCreateNoSecretApplicationCredential(t *testing.T) { }, } - actual, err := applicationcredentials.Create(context.TODO(), client.ServiceClient(), userID, createOpts).Extract() + actual, err := applicationcredentials.Create(context.TODO(), client.ServiceClient(fakeServer), userID, createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, ApplicationCredentialNoSecretResponse, *actual) } func TestCreateUnrestrictedApplicationCredential(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateUnrestrictedApplicationCredentialSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateUnrestrictedApplicationCredentialSuccessfully(t, fakeServer) createOpts := applicationcredentials.CreateOpts{ Name: "test2", @@ -117,27 +117,27 @@ func TestCreateUnrestrictedApplicationCredential(t *testing.T) { UnrestrictedApplicationCredentialResponse := UnrestrictedApplicationCredential UnrestrictedApplicationCredentialResponse.Secret = "generated_secret" - actual, err := applicationcredentials.Create(context.TODO(), client.ServiceClient(), userID, createOpts).Extract() + actual, err := applicationcredentials.Create(context.TODO(), client.ServiceClient(fakeServer), userID, createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, UnrestrictedApplicationCredentialResponse, *actual) } func TestDeleteApplicationCredential(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteApplicationCredentialSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteApplicationCredentialSuccessfully(t, fakeServer) - res := applicationcredentials.Delete(context.TODO(), client.ServiceClient(), userID, applicationCredentialID) + res := applicationcredentials.Delete(context.TODO(), client.ServiceClient(fakeServer), userID, applicationCredentialID) th.AssertNoErr(t, res.Err) } func TestListAccessRules(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListAccessRulesSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListAccessRulesSuccessfully(t, fakeServer) count := 0 - err := applicationcredentials.ListAccessRules(client.ServiceClient(), userID).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := applicationcredentials.ListAccessRules(client.ServiceClient(fakeServer), userID).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := applicationcredentials.ExtractAccessRules(page) @@ -148,24 +148,24 @@ func TestListAccessRules(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestGetAccessRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetAccessRuleSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetAccessRuleSuccessfully(t, fakeServer) - actual, err := applicationcredentials.GetAccessRule(context.TODO(), client.ServiceClient(), userID, accessRuleID).Extract() + actual, err := applicationcredentials.GetAccessRule(context.TODO(), client.ServiceClient(fakeServer), userID, accessRuleID).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, AccessRule, *actual) } func TestDeleteAccessRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteAccessRuleSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteAccessRuleSuccessfully(t, fakeServer) - res := applicationcredentials.DeleteAccessRule(context.TODO(), client.ServiceClient(), userID, accessRuleID) + res := applicationcredentials.DeleteAccessRule(context.TODO(), client.ServiceClient(fakeServer), userID, accessRuleID) th.AssertNoErr(t, res.Err) } diff --git a/openstack/identity/v3/catalog/testing/catalog_test.go b/openstack/identity/v3/catalog/testing/catalog_test.go index 4b055081f9..65e4c1dc3d 100644 --- a/openstack/identity/v3/catalog/testing/catalog_test.go +++ b/openstack/identity/v3/catalog/testing/catalog_test.go @@ -11,12 +11,12 @@ import ( ) func TestListCatalog(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListCatalogSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListCatalogSuccessfully(t, fakeServer) count := 0 - err := catalog.List(client.ServiceClient()).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := catalog.List(client.ServiceClient(fakeServer)).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := catalog.ExtractServiceCatalog(page) @@ -27,5 +27,5 @@ func TestListCatalog(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } diff --git a/openstack/identity/v3/catalog/testing/fixtures_test.go b/openstack/identity/v3/catalog/testing/fixtures_test.go index 541fcac4af..310a40e68f 100644 --- a/openstack/identity/v3/catalog/testing/fixtures_test.go +++ b/openstack/identity/v3/catalog/testing/fixtures_test.go @@ -77,14 +77,14 @@ var ExpectedCatalogSlice = []tokens.CatalogEntry{{ // HandleListCatalogSuccessfully creates an HTTP handler at `/domains` on the // test handler mux that responds with a list of two domains. -func HandleListCatalogSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/auth/catalog", func(w http.ResponseWriter, r *http.Request) { +func HandleListCatalogSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/auth/catalog", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - _, _ = fmt.Fprintf(w, ListOutput) + _, _ = fmt.Fprint(w, ListOutput) }) } diff --git a/openstack/identity/v3/credentials/results.go b/openstack/identity/v3/credentials/results.go index e61b631ffe..37603bc7dd 100644 --- a/openstack/identity/v3/credentials/results.go +++ b/openstack/identity/v3/credentials/results.go @@ -65,7 +65,7 @@ func (r CredentialPage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (r CredentialPage) NextPageURL() (string, error) { +func (r CredentialPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links struct { Next string `json:"next"` diff --git a/openstack/identity/v3/credentials/testing/fixtures_test.go b/openstack/identity/v3/credentials/testing/fixtures_test.go index 8da4769ed6..206700ec04 100644 --- a/openstack/identity/v3/credentials/testing/fixtures_test.go +++ b/openstack/identity/v3/credentials/testing/fixtures_test.go @@ -153,49 +153,49 @@ var ExpectedCredentialsSlice = []credentials.Credential{FirstCredential, SecondC // HandleListCredentialsSuccessfully creates an HTTP handler at `/credentials` on the // test handler mux that responds with a list of two credentials. -func HandleListCredentialsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/credentials", func(w http.ResponseWriter, r *http.Request) { +func HandleListCredentialsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/credentials", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } // HandleGetCredentialSuccessfully creates an HTTP handler at `/credentials` on the // test handler mux that responds with a single credential. -func HandleGetCredentialSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/credentials/3d3367228f9c7665266604462ec60029bcd83ad89614021a80b2eb879c572510", func(w http.ResponseWriter, r *http.Request) { +func HandleGetCredentialSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/credentials/3d3367228f9c7665266604462ec60029bcd83ad89614021a80b2eb879c572510", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleCreateCredentialSuccessfully creates an HTTP handler at `/credentials` on the // test handler mux that tests credential creation. -func HandleCreateCredentialSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/credentials", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateCredentialSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/credentials", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleDeleteCredentialSuccessfully creates an HTTP handler at `/credentials` on the // test handler mux that tests credential deletion. -func HandleDeleteCredentialSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/credentials/3d3367228f9c7665266604462ec60029bcd83ad89614021a80b2eb879c572510", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteCredentialSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/credentials/3d3367228f9c7665266604462ec60029bcd83ad89614021a80b2eb879c572510", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -205,13 +205,13 @@ func HandleDeleteCredentialSuccessfully(t *testing.T) { // HandleUpdateCredentialsSuccessfully creates an HTTP handler at `/credentials` on the // test handler mux that tests credentials update. -func HandleUpdateCredentialSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/credentials/2441494e52ab6d594a34d74586075cb299489bdd1e9389e3ab06467a4f460609", func(w http.ResponseWriter, r *http.Request) { +func HandleUpdateCredentialSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/credentials/2441494e52ab6d594a34d74586075cb299489bdd1e9389e3ab06467a4f460609", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }) } diff --git a/openstack/identity/v3/credentials/testing/requests_test.go b/openstack/identity/v3/credentials/testing/requests_test.go index 7b90817c59..19578bc001 100644 --- a/openstack/identity/v3/credentials/testing/requests_test.go +++ b/openstack/identity/v3/credentials/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestListCredentials(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListCredentialsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListCredentialsSuccessfully(t, fakeServer) count := 0 - err := credentials.List(client.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := credentials.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := credentials.ExtractCredentials(page) @@ -27,37 +27,37 @@ func TestListCredentials(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListCredentialsAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListCredentialsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListCredentialsSuccessfully(t, fakeServer) - allPages, err := credentials.List(client.ServiceClient(), nil).AllPages(context.TODO()) + allPages, err := credentials.List(client.ServiceClient(fakeServer), nil).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := credentials.ExtractCredentials(allPages) th.AssertNoErr(t, err) th.CheckDeepEquals(t, ExpectedCredentialsSlice, actual) - th.AssertDeepEquals(t, ExpectedCredentialsSlice[0].Blob, "{\"access\":\"181920\",\"secret\":\"secretKey\"}") - th.AssertDeepEquals(t, ExpectedCredentialsSlice[1].Blob, "{\"access\":\"7da79ff0aa364e1396f067e352b9b79a\",\"secret\":\"7a18d68ba8834b799d396f3ff6f1e98c\"}") + th.AssertDeepEquals(t, "{\"access\":\"181920\",\"secret\":\"secretKey\"}", ExpectedCredentialsSlice[0].Blob) + th.AssertDeepEquals(t, "{\"access\":\"7da79ff0aa364e1396f067e352b9b79a\",\"secret\":\"7a18d68ba8834b799d396f3ff6f1e98c\"}", ExpectedCredentialsSlice[1].Blob) } func TestGetCredential(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetCredentialSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetCredentialSuccessfully(t, fakeServer) - actual, err := credentials.Get(context.TODO(), client.ServiceClient(), credentialID).Extract() + actual, err := credentials.Get(context.TODO(), client.ServiceClient(fakeServer), credentialID).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, Credential, *actual) } func TestCreateCredential(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateCredentialSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateCredentialSuccessfully(t, fakeServer) createOpts := credentials.CreateOpts{ ProjectID: projectID, @@ -68,24 +68,24 @@ func TestCreateCredential(t *testing.T) { CredentialResponse := Credential - actual, err := credentials.Create(context.TODO(), client.ServiceClient(), createOpts).Extract() + actual, err := credentials.Create(context.TODO(), client.ServiceClient(fakeServer), createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, CredentialResponse, *actual) } func TestDeleteCredential(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteCredentialSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteCredentialSuccessfully(t, fakeServer) - res := credentials.Delete(context.TODO(), client.ServiceClient(), credentialID) + res := credentials.Delete(context.TODO(), client.ServiceClient(fakeServer), credentialID) th.AssertNoErr(t, res.Err) } func TestUpdateCredential(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateCredentialSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateCredentialSuccessfully(t, fakeServer) updateOpts := credentials.UpdateOpts{ ProjectID: "731fc6f265cd486d900f16e84c5cb594", @@ -94,7 +94,7 @@ func TestUpdateCredential(t *testing.T) { Blob: "{\"access\":\"181920\",\"secret\":\"secretKey\"}", } - actual, err := credentials.Update(context.TODO(), client.ServiceClient(), "2441494e52ab6d594a34d74586075cb299489bdd1e9389e3ab06467a4f460609", updateOpts).Extract() + actual, err := credentials.Update(context.TODO(), client.ServiceClient(fakeServer), "2441494e52ab6d594a34d74586075cb299489bdd1e9389e3ab06467a4f460609", updateOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondCredentialUpdated, *actual) } diff --git a/openstack/identity/v3/domains/doc.go b/openstack/identity/v3/domains/doc.go index 618b2f6d42..34611f5c18 100644 --- a/openstack/identity/v3/domains/doc.go +++ b/openstack/identity/v3/domains/doc.go @@ -3,7 +3,7 @@ Package domains manages and retrieves Domains in the OpenStack Identity Service. Example to List Domains - var iTrue bool = true + var iTrue = true listOpts := domains.ListOpts{ Enabled: &iTrue, } @@ -38,7 +38,7 @@ Example to Update a Domain domainID := "0fe36e73809d46aeae6705c39077b1b3" - var iFalse bool = false + var iFalse = false updateOpts := domains.UpdateOpts{ Enabled: &iFalse, } diff --git a/openstack/identity/v3/domains/requests.go b/openstack/identity/v3/domains/requests.go index 8ce72a9b33..94bb567f3c 100644 --- a/openstack/identity/v3/domains/requests.go +++ b/openstack/identity/v3/domains/requests.go @@ -20,6 +20,9 @@ type ListOpts struct { // Name filters the response by domain name. Name string `q:"name"` + + // Limit limits the number of projects returned per page. + Limit int `q:"limit"` } // ToDomainListQuery formats a ListOpts into a query string. diff --git a/openstack/identity/v3/domains/results.go b/openstack/identity/v3/domains/results.go index 7227748236..1c364c7466 100644 --- a/openstack/identity/v3/domains/results.go +++ b/openstack/identity/v3/domains/results.go @@ -67,7 +67,7 @@ func (r DomainPage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (r DomainPage) NextPageURL() (string, error) { +func (r DomainPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links struct { Next string `json:"next"` diff --git a/openstack/identity/v3/domains/testing/fixtures_test.go b/openstack/identity/v3/domains/testing/fixtures_test.go index f5025a15c8..6c0f570059 100644 --- a/openstack/identity/v3/domains/testing/fixtures_test.go +++ b/openstack/identity/v3/domains/testing/fixtures_test.go @@ -181,63 +181,63 @@ var ExpectedDomainsSlice = []domains.Domain{FirstDomain, SecondDomain} // HandleListAvailableDomainsSuccessfully creates an HTTP handler at `/auth/domains` // on the test handler mux that responds with a list of two domains. -func HandleListAvailableDomainsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/auth/domains", func(w http.ResponseWriter, r *http.Request) { +func HandleListAvailableDomainsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/auth/domains", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListAvailableOutput) + fmt.Fprint(w, ListAvailableOutput) }) } // HandleListDomainsSuccessfully creates an HTTP handler at `/domains` on the // test handler mux that responds with a list of two domains. -func HandleListDomainsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/domains", func(w http.ResponseWriter, r *http.Request) { +func HandleListDomainsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/domains", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } // HandleGetDomainSuccessfully creates an HTTP handler at `/domains` on the // test handler mux that responds with a single domain. -func HandleGetDomainSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/domains/9fe1d3", func(w http.ResponseWriter, r *http.Request) { +func HandleGetDomainSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/domains/9fe1d3", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleCreateDomainSuccessfully creates an HTTP handler at `/domains` on the // test handler mux that tests domain creation. -func HandleCreateDomainSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/domains", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateDomainSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/domains", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleDeleteDomainSuccessfully creates an HTTP handler at `/domains` on the // test handler mux that tests domain deletion. -func HandleDeleteDomainSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/domains/9fe1d3", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteDomainSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/domains/9fe1d3", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -247,13 +247,13 @@ func HandleDeleteDomainSuccessfully(t *testing.T) { // HandleUpdateDomainSuccessfully creates an HTTP handler at `/domains` on the // test handler mux that tests domain update. -func HandleUpdateDomainSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/domains/9fe1d3", func(w http.ResponseWriter, r *http.Request) { +func HandleUpdateDomainSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/domains/9fe1d3", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }) } diff --git a/openstack/identity/v3/domains/testing/requests_test.go b/openstack/identity/v3/domains/testing/requests_test.go index 1e0f5ef8f4..3f070b0cbd 100644 --- a/openstack/identity/v3/domains/testing/requests_test.go +++ b/openstack/identity/v3/domains/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestListAvailableDomains(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListAvailableDomainsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListAvailableDomainsSuccessfully(t, fakeServer) count := 0 - err := domains.ListAvailable(client.ServiceClient()).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := domains.ListAvailable(client.ServiceClient(fakeServer)).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := domains.ExtractDomains(page) @@ -27,16 +27,16 @@ func TestListAvailableDomains(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListDomains(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListDomainsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListDomainsSuccessfully(t, fakeServer) count := 0 - err := domains.List(client.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := domains.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := domains.ExtractDomains(page) @@ -47,15 +47,15 @@ func TestListDomains(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListDomainsAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListDomainsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListDomainsSuccessfully(t, fakeServer) - allPages, err := domains.List(client.ServiceClient(), nil).AllPages(context.TODO()) + allPages, err := domains.List(client.ServiceClient(fakeServer), nil).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := domains.ExtractDomains(allPages) th.AssertNoErr(t, err) @@ -63,49 +63,49 @@ func TestListDomainsAllPages(t *testing.T) { } func TestGetDomain(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetDomainSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetDomainSuccessfully(t, fakeServer) - actual, err := domains.Get(context.TODO(), client.ServiceClient(), "9fe1d3").Extract() + actual, err := domains.Get(context.TODO(), client.ServiceClient(fakeServer), "9fe1d3").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondDomain, *actual) } func TestCreateDomain(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateDomainSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateDomainSuccessfully(t, fakeServer) createOpts := domains.CreateOpts{ Name: "domain two", } - actual, err := domains.Create(context.TODO(), client.ServiceClient(), createOpts).Extract() + actual, err := domains.Create(context.TODO(), client.ServiceClient(fakeServer), createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondDomain, *actual) } func TestDeleteDomain(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteDomainSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteDomainSuccessfully(t, fakeServer) - res := domains.Delete(context.TODO(), client.ServiceClient(), "9fe1d3") + res := domains.Delete(context.TODO(), client.ServiceClient(fakeServer), "9fe1d3") th.AssertNoErr(t, res.Err) } func TestUpdateDomain(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateDomainSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateDomainSuccessfully(t, fakeServer) var description = "Staging Domain" updateOpts := domains.UpdateOpts{ Description: &description, } - actual, err := domains.Update(context.TODO(), client.ServiceClient(), "9fe1d3", updateOpts).Extract() + actual, err := domains.Update(context.TODO(), client.ServiceClient(fakeServer), "9fe1d3", updateOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondDomainUpdated, *actual) } diff --git a/openstack/identity/v3/ec2credentials/requests.go b/openstack/identity/v3/ec2credentials/requests.go index aefb2b14b7..df3c59aff2 100644 --- a/openstack/identity/v3/ec2credentials/requests.go +++ b/openstack/identity/v3/ec2credentials/requests.go @@ -22,6 +22,12 @@ func Get(ctx context.Context, client *gophercloud.ServiceClient, userID string, return } +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToCredentialCreateMap() (map[string]any, error) +} + // CreateOpts provides options used to create an EC2 credential. type CreateOpts struct { // TenantID is the project ID scope of the EC2 credential. @@ -34,7 +40,7 @@ func (opts CreateOpts) ToCredentialCreateMap() (map[string]any, error) { } // Create creates a new EC2 Credential. -func Create(ctx context.Context, client *gophercloud.ServiceClient, userID string, opts CreateOpts) (r CreateResult) { +func Create(ctx context.Context, client *gophercloud.ServiceClient, userID string, opts CreateOptsBuilder) (r CreateResult) { b, err := opts.ToCredentialCreateMap() if err != nil { r.Err = err diff --git a/openstack/identity/v3/ec2credentials/results.go b/openstack/identity/v3/ec2credentials/results.go index fd718a1a03..230565237a 100644 --- a/openstack/identity/v3/ec2credentials/results.go +++ b/openstack/identity/v3/ec2credentials/results.go @@ -59,7 +59,7 @@ func (r CredentialPage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (r CredentialPage) NextPageURL() (string, error) { +func (r CredentialPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links struct { Next string `json:"next"` diff --git a/openstack/identity/v3/ec2credentials/testing/fixtures_test.go b/openstack/identity/v3/ec2credentials/testing/fixtures_test.go index 738161477d..eb29689deb 100644 --- a/openstack/identity/v3/ec2credentials/testing/fixtures_test.go +++ b/openstack/identity/v3/ec2credentials/testing/fixtures_test.go @@ -111,49 +111,49 @@ var ExpectedEC2CredentialsSlice = []ec2credentials.Credential{EC2Credential, Sec // HandleListEC2CredentialsSuccessfully creates an HTTP handler at `/users` on the // test handler mux that responds with a list of two applicationcredentials. -func HandleListEC2CredentialsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/credentials/OS-EC2", func(w http.ResponseWriter, r *http.Request) { +func HandleListEC2CredentialsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/credentials/OS-EC2", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } // HandleGetEC2CredentialSuccessfully creates an HTTP handler at `/users` on the // test handler mux that responds with a single application credential. -func HandleGetEC2CredentialSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/credentials/OS-EC2/f741662395b249c9b8acdebf1722c5ae", func(w http.ResponseWriter, r *http.Request) { +func HandleGetEC2CredentialSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/credentials/OS-EC2/f741662395b249c9b8acdebf1722c5ae", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleCreateEC2CredentialSuccessfully creates an HTTP handler at `/users` on the // test handler mux that tests application credential creation. -func HandleCreateEC2CredentialSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/credentials/OS-EC2", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateEC2CredentialSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/credentials/OS-EC2", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) } // HandleDeleteEC2CredentialSuccessfully creates an HTTP handler at `/users` on the // test handler mux that tests application credential deletion. -func HandleDeleteEC2CredentialSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/credentials/OS-EC2/f741662395b249c9b8acdebf1722c5ae", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteEC2CredentialSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/2844b2a08be147a08ef58317d6471f1f/credentials/OS-EC2/f741662395b249c9b8acdebf1722c5ae", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) diff --git a/openstack/identity/v3/ec2credentials/testing/requests_test.go b/openstack/identity/v3/ec2credentials/testing/requests_test.go index a20e4b70f9..aa30f295e4 100644 --- a/openstack/identity/v3/ec2credentials/testing/requests_test.go +++ b/openstack/identity/v3/ec2credentials/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestListEC2Credentials(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListEC2CredentialsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListEC2CredentialsSuccessfully(t, fakeServer) count := 0 - err := ec2credentials.List(client.ServiceClient(), userID).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := ec2credentials.List(client.ServiceClient(fakeServer), userID).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := ec2credentials.ExtractCredentials(page) @@ -27,15 +27,15 @@ func TestListEC2Credentials(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListEC2CredentialsAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListEC2CredentialsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListEC2CredentialsSuccessfully(t, fakeServer) - allPages, err := ec2credentials.List(client.ServiceClient(), userID).AllPages(context.TODO()) + allPages, err := ec2credentials.List(client.ServiceClient(fakeServer), userID).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := ec2credentials.ExtractCredentials(allPages) th.AssertNoErr(t, err) @@ -43,34 +43,34 @@ func TestListEC2CredentialsAllPages(t *testing.T) { } func TestGetEC2Credential(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetEC2CredentialSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetEC2CredentialSuccessfully(t, fakeServer) - actual, err := ec2credentials.Get(context.TODO(), client.ServiceClient(), userID, credentialID).Extract() + actual, err := ec2credentials.Get(context.TODO(), client.ServiceClient(fakeServer), userID, credentialID).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, EC2Credential, *actual) } func TestCreateEC2Credential(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateEC2CredentialSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateEC2CredentialSuccessfully(t, fakeServer) createOpts := ec2credentials.CreateOpts{ TenantID: "6238dee2fec940a6bf31e49e9faf995a", } - actual, err := ec2credentials.Create(context.TODO(), client.ServiceClient(), userID, createOpts).Extract() + actual, err := ec2credentials.Create(context.TODO(), client.ServiceClient(fakeServer), userID, createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, EC2Credential, *actual) } func TestDeleteEC2Credential(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteEC2CredentialSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteEC2CredentialSuccessfully(t, fakeServer) - res := ec2credentials.Delete(context.TODO(), client.ServiceClient(), userID, credentialID) + res := ec2credentials.Delete(context.TODO(), client.ServiceClient(fakeServer), userID, credentialID) th.AssertNoErr(t, res.Err) } diff --git a/openstack/identity/v3/ec2tokens/requests.go b/openstack/identity/v3/ec2tokens/requests.go index 5b1f3d6882..1d4cb54928 100644 --- a/openstack/identity/v3/ec2tokens/requests.go +++ b/openstack/identity/v3/ec2tokens/requests.go @@ -300,8 +300,7 @@ func Create(ctx context.Context, c *gophercloud.ServiceClient, opts tokens.AuthO deleteBodyElements(b, "token") resp, err := c.Post(ctx, ec2tokensURL(c), b, &r.Body, &gophercloud.RequestOpts{ - MoreHeaders: map[string]string{"X-Auth-Token": ""}, - OkCodes: []int{200}, + OkCodes: []int{200}, }) _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return @@ -320,8 +319,7 @@ func ValidateS3Token(ctx context.Context, c *gophercloud.ServiceClient, opts tok deleteBodyElements(b, "body_hash", "headers", "host", "params", "path", "verb") resp, err := c.Post(ctx, s3tokensURL(c), b, &r.Body, &gophercloud.RequestOpts{ - MoreHeaders: map[string]string{"X-Auth-Token": ""}, - OkCodes: []int{200}, + OkCodes: []int{200}, }) _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return diff --git a/openstack/identity/v3/ec2tokens/testing/requests_test.go b/openstack/identity/v3/ec2tokens/testing/requests_test.go index 188329daab..ae075a4c89 100644 --- a/openstack/identity/v3/ec2tokens/testing/requests_test.go +++ b/openstack/identity/v3/ec2tokens/testing/requests_test.go @@ -12,27 +12,27 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/ec2tokens" "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens" tokens_testing "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens/testing" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" ) // authTokenPost verifies that providing certain AuthOptions and Scope results in an expected JSON structure. func authTokenPost(t *testing.T, options ec2tokens.AuthOptions, requestJSON string) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() client := gophercloud.ServiceClient{ ProviderClient: &gophercloud.ProviderClient{}, - Endpoint: testhelper.Endpoint(), + Endpoint: fakeServer.Endpoint(), } - testhelper.Mux.HandleFunc("/ec2tokens", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "POST") - testhelper.TestHeader(t, r, "Content-Type", "application/json") - testhelper.TestHeader(t, r, "Accept", "application/json") - testhelper.TestJSONRequest(t, r, requestJSON) + fakeServer.Mux.HandleFunc("/ec2tokens", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, requestJSON) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, tokens_testing.TokenOutput) + fmt.Fprint(w, tokens_testing.TokenOutput) }) expected := &tokens.Token{ @@ -40,8 +40,8 @@ func authTokenPost(t *testing.T, options ec2tokens.AuthOptions, requestJSON stri } actual, err := ec2tokens.Create(context.TODO(), &client, &options).Extract() - testhelper.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, expected, actual) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, expected, actual) } func TestCreateV2(t *testing.T) { @@ -224,7 +224,7 @@ func TestEC2CredentialsBuildCanonicalQueryStringV2(t *testing.T) { "Value": "bar", } expected := "Action=foo&Value=bar" - testhelper.CheckEquals(t, expected, ec2tokens.EC2CredentialsBuildCanonicalQueryStringV2(params)) + th.CheckEquals(t, expected, ec2tokens.EC2CredentialsBuildCanonicalQueryStringV2(params)) } func TestEC2CredentialsBuildStringToSignV2(t *testing.T) { @@ -238,7 +238,7 @@ func TestEC2CredentialsBuildStringToSignV2(t *testing.T) { }, } expected := []byte("GET\nlocalhost\n/\nAction=foo&Value=bar") - testhelper.CheckDeepEquals(t, expected, ec2tokens.EC2CredentialsBuildStringToSignV2(opts)) + th.CheckDeepEquals(t, expected, ec2tokens.EC2CredentialsBuildStringToSignV2(opts)) } func TestEC2CredentialsBuildCanonicalQueryStringV4(t *testing.T) { @@ -247,8 +247,8 @@ func TestEC2CredentialsBuildCanonicalQueryStringV4(t *testing.T) { "Value": "bar", } expected := "Action=foo&Value=bar" - testhelper.CheckEquals(t, expected, ec2tokens.EC2CredentialsBuildCanonicalQueryStringV4("foo", params)) - testhelper.CheckEquals(t, "", ec2tokens.EC2CredentialsBuildCanonicalQueryStringV4("POST", params)) + th.CheckEquals(t, expected, ec2tokens.EC2CredentialsBuildCanonicalQueryStringV4("foo", params)) + th.CheckEquals(t, "", ec2tokens.EC2CredentialsBuildCanonicalQueryStringV4("POST", params)) } func TestEC2CredentialsBuildCanonicalHeadersV4(t *testing.T) { @@ -258,12 +258,12 @@ func TestEC2CredentialsBuildCanonicalHeadersV4(t *testing.T) { } signedHeaders := "foo;baz" expected := "foo:bar\nbaz:qux\n" - testhelper.CheckEquals(t, expected, ec2tokens.EC2CredentialsBuildCanonicalHeadersV4(headers, signedHeaders)) + th.CheckEquals(t, expected, ec2tokens.EC2CredentialsBuildCanonicalHeadersV4(headers, signedHeaders)) } func TestEC2CredentialsBuildSignatureKeyV4(t *testing.T) { expected := "246626bd815b0a0cae4bedc3f4e124ca25e208cd75fd812d836aeae184de038a" - testhelper.CheckEquals(t, expected, hex.EncodeToString((ec2tokens.EC2CredentialsBuildSignatureKeyV4("foo", "bar", "baz", time.Time{})))) + th.CheckEquals(t, expected, hex.EncodeToString((ec2tokens.EC2CredentialsBuildSignatureKeyV4("foo", "bar", "baz", time.Time{})))) } func TestEC2CredentialsBuildSignatureV4(t *testing.T) { @@ -284,5 +284,5 @@ func TestEC2CredentialsBuildSignatureV4(t *testing.T) { stringToSign := ec2tokens.EC2CredentialsBuildStringToSignV4(opts, "host", "foo", date) key := ec2tokens.EC2CredentialsBuildSignatureKeyV4("", "", "", date) - testhelper.CheckEquals(t, expected, ec2tokens.EC2CredentialsBuildSignatureV4(key, stringToSign)) + th.CheckEquals(t, expected, ec2tokens.EC2CredentialsBuildSignatureV4(key, stringToSign)) } diff --git a/openstack/identity/v3/endpoints/requests.go b/openstack/identity/v3/endpoints/requests.go index 35eb966077..a5dca90a9d 100644 --- a/openstack/identity/v3/endpoints/requests.go +++ b/openstack/identity/v3/endpoints/requests.go @@ -18,8 +18,15 @@ type CreateOpts struct { // or public), referenced by the gophercloud.Availability type. Availability gophercloud.Availability `json:"interface" required:"true"` + // Description is the description of the Endpoint. + Description string `json:"description,omitempty"` + + // Enabled indicates whether the Endpoint appears in the service + // or not. + Enabled *bool `json:"enabled,omitempty"` + // Name is the name of the Endpoint. - Name string `json:"name" required:"true"` + Name string `json:"name,omitempty"` // Region is the region the Endpoint is located in. // This field can be omitted or left as a blank string. @@ -90,6 +97,13 @@ func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pa }) } +// Get retrieves details on a single endpoint, by ID. +func Get(ctx context.Context, client *gophercloud.ServiceClient, id string) (r GetResult) { + resp, err := client.Get(ctx, endpointURL(client, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + // UpdateOptsBuilder allows extensions to add parameters to the Update request. type UpdateOptsBuilder interface { ToEndpointUpdateMap() (map[string]any, error) @@ -105,6 +119,10 @@ type UpdateOpts struct { // Name is the name of the Endpoint. Name string `json:"name,omitempty"` + // Enabled indicates whether the Endpoint appears in the service + // or not. + Enabled *bool `json:"enabled,omitempty"` + // Region is the region the Endpoint is located in. // This field can be omitted or left as a blank string. Region string `json:"region,omitempty"` @@ -114,6 +132,9 @@ type UpdateOpts struct { // ServiceID is the ID of the service the Endpoint refers to. ServiceID string `json:"service_id,omitempty"` + + // Description is an updated description of the endpoint. + Description *string `json:"description,omitempty"` } // ToEndpointUpdateMap builds an update request body from the Update options. diff --git a/openstack/identity/v3/endpoints/results.go b/openstack/identity/v3/endpoints/results.go index 9efec30af2..7dbb1c9f7d 100644 --- a/openstack/identity/v3/endpoints/results.go +++ b/openstack/identity/v3/endpoints/results.go @@ -19,6 +19,12 @@ func (r commonResult) Extract() (*Endpoint, error) { return s.Endpoint, err } +// GetResult is the response from a Get operation. Call its Extract method +// to interpret it as an Endpoint. +type GetResult struct { + commonResult +} + // CreateResult is the response from a Create operation. Call its Extract // method to interpret it as an Endpoint. type CreateResult struct { @@ -60,6 +66,9 @@ type Endpoint struct { // Enabled is whether or not the endpoint is enabled. Enabled bool `json:"enabled"` + + // Description is the description of the Endpoint. + Description string `json:"description"` } // EndpointPage is a single page of Endpoint results. diff --git a/openstack/identity/v3/endpoints/testing/requests_test.go b/openstack/identity/v3/endpoints/testing/requests_test.go index 8ea857bad4..5ffde6638c 100644 --- a/openstack/identity/v3/endpoints/testing/requests_test.go +++ b/openstack/identity/v3/endpoints/testing/requests_test.go @@ -14,112 +14,111 @@ import ( ) func TestCreateSuccessful(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/endpoints", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/endpoints", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) - th.TestJSONRequest(t, r, ` - { - "endpoint": { - "interface": "public", - "name": "the-endiest-of-points", - "region": "underground", - "url": "https://1.2.3.4:9000/", - "service_id": "asdfasdfasdfasdf" - } - } - `) + th.TestJSONRequest(t, r, `{ + "endpoint": { + "interface": "public", + "region": "underground", + "url": "https://1.2.3.4:9000/", + "service_id": "asdfasdfasdfasdf", + "description": "Test description", + "enabled": false + } + }`) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ` - { - "endpoint": { - "id": "12", - "interface": "public", - "enabled": true, - "links": { - "self": "https://localhost:5000/v3/endpoints/12" - }, - "name": "the-endiest-of-points", - "region": "underground", - "service_id": "asdfasdfasdfasdf", - "url": "https://1.2.3.4:9000/" - } - } - `) + fmt.Fprint(w, `{ + "endpoint": { + "id": "12", + "interface": "public", + "enabled": false, + "links": { + "self": "https://localhost:5000/v3/endpoints/12" + }, + "region": "underground", + "service_id": "asdfasdfasdfasdf", + "url": "https://1.2.3.4:9000/", + "description": "Test description" + } + }`) }) - actual, err := endpoints.Create(context.TODO(), client.ServiceClient(), endpoints.CreateOpts{ + enabled := false + actual, err := endpoints.Create(context.TODO(), client.ServiceClient(fakeServer), endpoints.CreateOpts{ Availability: gophercloud.AvailabilityPublic, - Name: "the-endiest-of-points", Region: "underground", URL: "https://1.2.3.4:9000/", ServiceID: "asdfasdfasdfasdf", + Description: "Test description", + Enabled: &enabled, }).Extract() th.AssertNoErr(t, err) expected := &endpoints.Endpoint{ ID: "12", Availability: gophercloud.AvailabilityPublic, - Enabled: true, - Name: "the-endiest-of-points", + Enabled: false, Region: "underground", ServiceID: "asdfasdfasdfasdf", URL: "https://1.2.3.4:9000/", + Description: "Test description", } th.AssertDeepEquals(t, expected, actual) } func TestListEndpoints(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/endpoints", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/endpoints", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` - { - "endpoints": [ - { - "id": "12", - "interface": "public", - "enabled": true, - "links": { - "self": "https://localhost:5000/v3/endpoints/12" - }, - "name": "the-endiest-of-points", - "region": "underground", - "service_id": "asdfasdfasdfasdf", - "url": "https://1.2.3.4:9000/" + fmt.Fprint(w, `{ + "endpoints": [ + { + "id": "12", + "interface": "public", + "enabled": true, + "links": { + "self": "https://localhost:5000/v3/endpoints/12" }, - { - "id": "13", - "interface": "internal", - "enabled": false, - "links": { - "self": "https://localhost:5000/v3/endpoints/13" - }, - "name": "shhhh", - "region": "underground", - "service_id": "asdfasdfasdfasdf", - "url": "https://1.2.3.4:9001/" - } - ], - "links": { - "next": null, - "previous": null + "name": "the-endiest-of-points", + "region": "underground", + "service_id": "asdfasdfasdfasdf", + "url": "https://1.2.3.4:9000/", + "description": "List endpoint1 test" + }, + { + "id": "13", + "interface": "internal", + "enabled": false, + "links": { + "self": "https://localhost:5000/v3/endpoints/13" + }, + "name": "shhhh", + "region": "underground", + "service_id": "asdfasdfasdfasdf", + "url": "https://1.2.3.4:9001/", + "description": "List endpoint2 test" } + ], + "links": { + "next": null, + "previous": null } - `) + }`) }) count := 0 - err := endpoints.List(client.ServiceClient(), endpoints.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := endpoints.List(client.ServiceClient(fakeServer), endpoints.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := endpoints.ExtractEndpoints(page) if err != nil { @@ -136,6 +135,7 @@ func TestListEndpoints(t *testing.T) { Region: "underground", ServiceID: "asdfasdfasdfasdf", URL: "https://1.2.3.4:9000/", + Description: "List endpoint1 test", }, { ID: "13", @@ -145,6 +145,7 @@ func TestListEndpoints(t *testing.T) { Region: "underground", ServiceID: "asdfasdfasdfasdf", URL: "https://1.2.3.4:9001/", + Description: "List endpoint2 test", }, } th.AssertDeepEquals(t, expected, actual) @@ -154,43 +155,89 @@ func TestListEndpoints(t *testing.T) { th.AssertEquals(t, 1, count) } +func TestGetEndpoint(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/endpoints/12", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + fmt.Fprint(w, `{ + "endpoint": { + "id": "12", + "interface": "public", + "enabled": true, + "links": { + "self": "https://localhost:5000/v3/endpoints/12" + }, + "name": "the-endiest-of-points", + "region": "underground", + "service_id": "asdfasdfasdfasdf", + "url": "https://1.2.3.4:9000/", + "description": "Get endpoint test" + } + }`) + }) + + actual, err := endpoints.Get(context.TODO(), client.ServiceClient(fakeServer), "12").Extract() + if err != nil { + t.Fatalf("Unexpected error from Get: %v", err) + } + + expected := &endpoints.Endpoint{ + ID: "12", + Availability: gophercloud.AvailabilityPublic, + Enabled: true, + Name: "the-endiest-of-points", + Region: "underground", + ServiceID: "asdfasdfasdfasdf", + URL: "https://1.2.3.4:9000/", + Description: "Get endpoint test", + } + th.AssertDeepEquals(t, expected, actual) +} + func TestUpdateEndpoint(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/endpoints/12", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/endpoints/12", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) - th.TestJSONRequest(t, r, ` - { - "endpoint": { - "name": "renamed", - "region": "somewhere-else" - } - } - `) + th.TestJSONRequest(t, r, `{ + "endpoint": { + "name": "renamed", + "region": "somewhere-else", + "description": "Changed description", + "enabled": false + } + }`) - fmt.Fprintf(w, ` - { + fmt.Fprint(w, `{ "endpoint": { "id": "12", "interface": "public", - "enabled": true, + "enabled": false, "links": { "self": "https://localhost:5000/v3/endpoints/12" }, "name": "renamed", "region": "somewhere-else", "service_id": "asdfasdfasdfasdf", - "url": "https://1.2.3.4:9000/" + "url": "https://1.2.3.4:9000/", + "description": "Changed description" } - } - `) + }`) }) - actual, err := endpoints.Update(context.TODO(), client.ServiceClient(), "12", endpoints.UpdateOpts{ - Name: "renamed", - Region: "somewhere-else", + enabled := false + description := "Changed description" + actual, err := endpoints.Update(context.TODO(), client.ServiceClient(fakeServer), "12", endpoints.UpdateOpts{ + Name: "renamed", + Region: "somewhere-else", + Description: &description, + Enabled: &enabled, }).Extract() if err != nil { t.Fatalf("Unexpected error from Update: %v", err) @@ -199,26 +246,27 @@ func TestUpdateEndpoint(t *testing.T) { expected := &endpoints.Endpoint{ ID: "12", Availability: gophercloud.AvailabilityPublic, - Enabled: true, + Enabled: false, Name: "renamed", Region: "somewhere-else", ServiceID: "asdfasdfasdfasdf", URL: "https://1.2.3.4:9000/", + Description: "Changed description", } th.AssertDeepEquals(t, expected, actual) } func TestDeleteEndpoint(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/endpoints/34", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/endpoints/34", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := endpoints.Delete(context.TODO(), client.ServiceClient(), "34") + res := endpoints.Delete(context.TODO(), client.ServiceClient(fakeServer), "34") th.AssertNoErr(t, res.Err) } diff --git a/openstack/identity/v3/federation/results.go b/openstack/identity/v3/federation/results.go index f0d8bf23ae..53ab624550 100644 --- a/openstack/identity/v3/federation/results.go +++ b/openstack/identity/v3/federation/results.go @@ -184,7 +184,7 @@ func (c MappingsPage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (c MappingsPage) NextPageURL() (string, error) { +func (c MappingsPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links struct { Next string `json:"next"` diff --git a/openstack/identity/v3/federation/testing/fixtures_test.go b/openstack/identity/v3/federation/testing/fixtures_test.go index 225cc57997..7213e3a913 100644 --- a/openstack/identity/v3/federation/testing/fixtures_test.go +++ b/openstack/identity/v3/federation/testing/fixtures_test.go @@ -281,62 +281,62 @@ var ExpectedMappingsSlice = []federation.Mapping{MappingACME} // HandleListMappingsSuccessfully creates an HTTP handler at `/mappings` on the // test handler mux that responds with a list of two mappings. -func HandleListMappingsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/OS-FEDERATION/mappings", func(w http.ResponseWriter, r *http.Request) { +func HandleListMappingsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-FEDERATION/mappings", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } // HandleCreateMappingSuccessfully creates an HTTP handler at `/mappings` on the // test handler mux that tests mapping creation. -func HandleCreateMappingSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/OS-FEDERATION/mappings/ACME", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateMappingSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-FEDERATION/mappings/ACME", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateOutput) + fmt.Fprint(w, CreateOutput) }) } // HandleGetMappingSuccessfully creates an HTTP handler at `/mappings` on the // test handler mux that responds with a single mapping. -func HandleGetMappingSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/OS-FEDERATION/mappings/ACME", func(w http.ResponseWriter, r *http.Request) { +func HandleGetMappingSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-FEDERATION/mappings/ACME", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleUpdateMappingSuccessfully creates an HTTP handler at `/mappings` on the // test handler mux that tests mapping update. -func HandleUpdateMappingSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/OS-FEDERATION/mappings/ACME", func(w http.ResponseWriter, r *http.Request) { +func HandleUpdateMappingSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-FEDERATION/mappings/ACME", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }) } // HandleDeleteMappingSuccessfully creates an HTTP handler at `/mappings` on the // test handler mux that tests mapping deletion. -func HandleDeleteMappingSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/OS-FEDERATION/mappings/ACME", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteMappingSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-FEDERATION/mappings/ACME", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) diff --git a/openstack/identity/v3/federation/testing/requests_test.go b/openstack/identity/v3/federation/testing/requests_test.go index 0d156b8d19..410bfe91a0 100644 --- a/openstack/identity/v3/federation/testing/requests_test.go +++ b/openstack/identity/v3/federation/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestListMappings(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListMappingsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListMappingsSuccessfully(t, fakeServer) count := 0 - err := federation.ListMappings(client.ServiceClient()).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := federation.ListMappings(client.ServiceClient(fakeServer)).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := federation.ExtractMappings(page) @@ -27,15 +27,15 @@ func TestListMappings(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListMappingsAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListMappingsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListMappingsSuccessfully(t, fakeServer) - allPages, err := federation.ListMappings(client.ServiceClient()).AllPages(context.TODO()) + allPages, err := federation.ListMappings(client.ServiceClient(fakeServer)).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := federation.ExtractMappings(allPages) th.AssertNoErr(t, err) @@ -43,9 +43,9 @@ func TestListMappingsAllPages(t *testing.T) { } func TestCreateMappings(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateMappingSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateMappingSuccessfully(t, fakeServer) createOpts := federation.CreateMappingOpts{ Rules: []federation.MappingRule{ @@ -78,25 +78,25 @@ func TestCreateMappings(t *testing.T) { }, } - actual, err := federation.CreateMapping(context.TODO(), client.ServiceClient(), "ACME", createOpts).Extract() + actual, err := federation.CreateMapping(context.TODO(), client.ServiceClient(fakeServer), "ACME", createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, MappingACME, *actual) } func TestGetMapping(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetMappingSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetMappingSuccessfully(t, fakeServer) - actual, err := federation.GetMapping(context.TODO(), client.ServiceClient(), "ACME").Extract() + actual, err := federation.GetMapping(context.TODO(), client.ServiceClient(fakeServer), "ACME").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, MappingACME, *actual) } func TestUpdateMapping(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateMappingSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateMappingSuccessfully(t, fakeServer) updateOpts := federation.UpdateMappingOpts{ Rules: []federation.MappingRule{ @@ -129,16 +129,16 @@ func TestUpdateMapping(t *testing.T) { }, } - actual, err := federation.UpdateMapping(context.TODO(), client.ServiceClient(), "ACME", updateOpts).Extract() + actual, err := federation.UpdateMapping(context.TODO(), client.ServiceClient(fakeServer), "ACME", updateOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, MappingUpdated, *actual) } func TestDeleteMapping(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteMappingSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteMappingSuccessfully(t, fakeServer) - res := federation.DeleteMapping(context.TODO(), client.ServiceClient(), "ACME") + res := federation.DeleteMapping(context.TODO(), client.ServiceClient(fakeServer), "ACME") th.AssertNoErr(t, res.Err) } diff --git a/openstack/identity/v3/groups/results.go b/openstack/identity/v3/groups/results.go index 5d1f5ceede..b29aa92ca2 100644 --- a/openstack/identity/v3/groups/results.go +++ b/openstack/identity/v3/groups/results.go @@ -102,7 +102,7 @@ func (r GroupPage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (r GroupPage) NextPageURL() (string, error) { +func (r GroupPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links struct { Next string `json:"next"` diff --git a/openstack/identity/v3/groups/testing/fixtures_test.go b/openstack/identity/v3/groups/testing/fixtures_test.go index 0357a1fe06..fb5bd178dd 100644 --- a/openstack/identity/v3/groups/testing/fixtures_test.go +++ b/openstack/identity/v3/groups/testing/fixtures_test.go @@ -152,62 +152,62 @@ var ExpectedGroupsSlice = []groups.Group{FirstGroup, SecondGroup} // HandleListGroupsSuccessfully creates an HTTP handler at `/groups` on the // test handler mux that responds with a list of two groups. -func HandleListGroupsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/groups", func(w http.ResponseWriter, r *http.Request) { +func HandleListGroupsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/groups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } // HandleGetGroupSuccessfully creates an HTTP handler at `/groups` on the // test handler mux that responds with a single group. -func HandleGetGroupSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/groups/9fe1d3", func(w http.ResponseWriter, r *http.Request) { +func HandleGetGroupSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/groups/9fe1d3", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleCreateGroupSuccessfully creates an HTTP handler at `/groups` on the // test handler mux that tests group creation. -func HandleCreateGroupSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/groups", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateGroupSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/groups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleUpdateGroupSuccessfully creates an HTTP handler at `/groups` on the // test handler mux that tests group update. -func HandleUpdateGroupSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/groups/9fe1d3", func(w http.ResponseWriter, r *http.Request) { +func HandleUpdateGroupSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/groups/9fe1d3", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }) } // HandleDeleteGroupSuccessfully creates an HTTP handler at `/groups` on the // test handler mux that tests group deletion. -func HandleDeleteGroupSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/groups/9fe1d3", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteGroupSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/groups/9fe1d3", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) diff --git a/openstack/identity/v3/groups/testing/requests_test.go b/openstack/identity/v3/groups/testing/requests_test.go index 5f6b7b9f56..017c6f1e61 100644 --- a/openstack/identity/v3/groups/testing/requests_test.go +++ b/openstack/identity/v3/groups/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestListGroups(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListGroupsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListGroupsSuccessfully(t, fakeServer) count := 0 - err := groups.List(client.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := groups.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := groups.ExtractGroups(page) @@ -27,21 +27,21 @@ func TestListGroups(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListGroupsAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListGroupsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListGroupsSuccessfully(t, fakeServer) - allPages, err := groups.List(client.ServiceClient(), nil).AllPages(context.TODO()) + allPages, err := groups.List(client.ServiceClient(fakeServer), nil).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := groups.ExtractGroups(allPages) th.AssertNoErr(t, err) th.CheckDeepEquals(t, ExpectedGroupsSlice, actual) - th.AssertEquals(t, ExpectedGroupsSlice[0].Extra["email"], "support@localhost") - th.AssertEquals(t, ExpectedGroupsSlice[1].Extra["email"], "support@example.com") + th.AssertEquals(t, "support@localhost", ExpectedGroupsSlice[0].Extra["email"]) + th.AssertEquals(t, "support@example.com", ExpectedGroupsSlice[1].Extra["email"]) } func TestListGroupsFiltersCheck(t *testing.T) { @@ -77,21 +77,21 @@ func TestListGroupsFiltersCheck(t *testing.T) { } func TestGetGroup(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetGroupSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetGroupSuccessfully(t, fakeServer) - actual, err := groups.Get(context.TODO(), client.ServiceClient(), "9fe1d3").Extract() + actual, err := groups.Get(context.TODO(), client.ServiceClient(fakeServer), "9fe1d3").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondGroup, *actual) - th.AssertEquals(t, SecondGroup.Extra["email"], "support@example.com") + th.AssertEquals(t, "support@example.com", SecondGroup.Extra["email"]) } func TestCreateGroup(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateGroupSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateGroupSuccessfully(t, fakeServer) createOpts := groups.CreateOpts{ Name: "support", @@ -102,15 +102,15 @@ func TestCreateGroup(t *testing.T) { }, } - actual, err := groups.Create(context.TODO(), client.ServiceClient(), createOpts).Extract() + actual, err := groups.Create(context.TODO(), client.ServiceClient(fakeServer), createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondGroup, *actual) } func TestUpdateGroup(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateGroupSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateGroupSuccessfully(t, fakeServer) var description = "L2 Support Team" updateOpts := groups.UpdateOpts{ @@ -120,16 +120,16 @@ func TestUpdateGroup(t *testing.T) { }, } - actual, err := groups.Update(context.TODO(), client.ServiceClient(), "9fe1d3", updateOpts).Extract() + actual, err := groups.Update(context.TODO(), client.ServiceClient(fakeServer), "9fe1d3", updateOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondGroupUpdated, *actual) } func TestDeleteGroup(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteGroupSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteGroupSuccessfully(t, fakeServer) - res := groups.Delete(context.TODO(), client.ServiceClient(), "9fe1d3") + res := groups.Delete(context.TODO(), client.ServiceClient(fakeServer), "9fe1d3") th.AssertNoErr(t, res.Err) } diff --git a/openstack/identity/v3/limits/results.go b/openstack/identity/v3/limits/results.go index 00eb738824..514f37db0f 100644 --- a/openstack/identity/v3/limits/results.go +++ b/openstack/identity/v3/limits/results.go @@ -113,7 +113,7 @@ func (r LimitPage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (r LimitPage) NextPageURL() (string, error) { +func (r LimitPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links struct { Next string `json:"next"` diff --git a/openstack/identity/v3/limits/testing/fixtures_test.go b/openstack/identity/v3/limits/testing/fixtures_test.go index 6275235b54..0674ffc081 100644 --- a/openstack/identity/v3/limits/testing/fixtures_test.go +++ b/openstack/identity/v3/limits/testing/fixtures_test.go @@ -175,76 +175,76 @@ var ExpectedLimitsSlice = []limits.Limit{FirstLimit, SecondLimit} // HandleGetEnforcementModelSuccessfully creates an HTTP handler at `/limits/model` on the // test handler mux that responds with a enforcement model. -func HandleGetEnforcementModelSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/limits/model", func(w http.ResponseWriter, r *http.Request) { +func HandleGetEnforcementModelSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/limits/model", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetEnforcementModelOutput) + fmt.Fprint(w, GetEnforcementModelOutput) }) } // HandleListLimitsSuccessfully creates an HTTP handler at `/limits` on the // test handler mux that responds with a list of two limits. -func HandleListLimitsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/limits", func(w http.ResponseWriter, r *http.Request) { +func HandleListLimitsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/limits", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } // HandleCreateLimitSuccessfully creates an HTTP handler at `/limits` on the // test handler mux that tests limit creation. -func HandleCreateLimitSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/limits", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateLimitSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/limits", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateOutput) + fmt.Fprint(w, CreateOutput) }) } // HandleGetLimitSuccessfully creates an HTTP handler at `/limits` on the // test handler mux that responds with a single limit. -func HandleGetLimitSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/limits/25a04c7a065c430590881c646cdcdd58", func(w http.ResponseWriter, r *http.Request) { +func HandleGetLimitSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/limits/25a04c7a065c430590881c646cdcdd58", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleUpdateLimitSuccessfully creates an HTTP handler at `/limits` on the // test handler mux that tests limit update. -func HandleUpdateLimitSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/limits/3229b3849f584faea483d6851f7aab05", func(w http.ResponseWriter, r *http.Request) { +func HandleUpdateLimitSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/limits/3229b3849f584faea483d6851f7aab05", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }) } // HandleDeleteLimitSuccessfully creates an HTTP handler at `/limits` on the // test handler mux that tests limit deletion. -func HandleDeleteLimitSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/limits/3229b3849f584faea483d6851f7aab05", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteLimitSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/limits/3229b3849f584faea483d6851f7aab05", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) diff --git a/openstack/identity/v3/limits/testing/requests_test.go b/openstack/identity/v3/limits/testing/requests_test.go index 15cf4dbb8e..22af6f8548 100644 --- a/openstack/identity/v3/limits/testing/requests_test.go +++ b/openstack/identity/v3/limits/testing/requests_test.go @@ -11,22 +11,22 @@ import ( ) func TestGetEnforcementModel(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetEnforcementModelSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetEnforcementModelSuccessfully(t, fakeServer) - actual, err := limits.GetEnforcementModel(context.TODO(), client.ServiceClient()).Extract() + actual, err := limits.GetEnforcementModel(context.TODO(), client.ServiceClient(fakeServer)).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, Model, *actual) } func TestListLimits(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListLimitsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListLimitsSuccessfully(t, fakeServer) count := 0 - err := limits.List(client.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := limits.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := limits.ExtractLimits(page) @@ -37,15 +37,15 @@ func TestListLimits(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListLimitsAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListLimitsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListLimitsSuccessfully(t, fakeServer) - allPages, err := limits.List(client.ServiceClient(), nil).AllPages(context.TODO()) + allPages, err := limits.List(client.ServiceClient(fakeServer), nil).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := limits.ExtractLimits(allPages) th.AssertNoErr(t, err) @@ -53,9 +53,9 @@ func TestListLimitsAllPages(t *testing.T) { } func TestCreateLimits(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateLimitSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateLimitSuccessfully(t, fakeServer) createOpts := limits.BatchCreateOpts{ limits.CreateOpts{ @@ -74,25 +74,25 @@ func TestCreateLimits(t *testing.T) { }, } - actual, err := limits.BatchCreate(context.TODO(), client.ServiceClient(), createOpts).Extract() + actual, err := limits.BatchCreate(context.TODO(), client.ServiceClient(fakeServer), createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, ExpectedLimitsSlice, actual) } func TestGetLimit(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetLimitSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetLimitSuccessfully(t, fakeServer) - actual, err := limits.Get(context.TODO(), client.ServiceClient(), "25a04c7a065c430590881c646cdcdd58").Extract() + actual, err := limits.Get(context.TODO(), client.ServiceClient(fakeServer), "25a04c7a065c430590881c646cdcdd58").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, FirstLimit, *actual) } func TestUpdateLimit(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateLimitSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateLimitSuccessfully(t, fakeServer) var description = "Number of snapshots for project 3a705b9f56bb439381b43c4fe59dccce" var resourceLimit = 5 @@ -101,16 +101,16 @@ func TestUpdateLimit(t *testing.T) { ResourceLimit: &resourceLimit, } - actual, err := limits.Update(context.TODO(), client.ServiceClient(), "3229b3849f584faea483d6851f7aab05", updateOpts).Extract() + actual, err := limits.Update(context.TODO(), client.ServiceClient(fakeServer), "3229b3849f584faea483d6851f7aab05", updateOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondLimitUpdated, *actual) } func TestDeleteLimit(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteLimitSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteLimitSuccessfully(t, fakeServer) - err := limits.Delete(context.TODO(), client.ServiceClient(), "3229b3849f584faea483d6851f7aab05").ExtractErr() + err := limits.Delete(context.TODO(), client.ServiceClient(fakeServer), "3229b3849f584faea483d6851f7aab05").ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/identity/v3/oauth1/requests.go b/openstack/identity/v3/oauth1/requests.go index 8c66b36e20..0b23269ffa 100644 --- a/openstack/identity/v3/oauth1/requests.go +++ b/openstack/identity/v3/oauth1/requests.go @@ -214,6 +214,12 @@ func GetConsumer(ctx context.Context, client *gophercloud.ServiceClient, id stri return } +// UpdateConsumerOptsBuilder allows extensions to add additional parameters to the +// UpdateConsumer request. +type UpdateConsumerOptsBuilder interface { + ToOAuth1UpdateConsumerMap() (map[string]any, error) +} + // UpdateConsumerOpts provides options used to update a consumer. type UpdateConsumerOpts struct { // Description is the consumer description. @@ -227,7 +233,7 @@ func (opts UpdateConsumerOpts) ToOAuth1UpdateConsumerMap() (map[string]any, erro } // UpdateConsumer updates an existing Consumer. -func UpdateConsumer(ctx context.Context, client *gophercloud.ServiceClient, id string, opts UpdateConsumerOpts) (r UpdateConsumerResult) { +func UpdateConsumer(ctx context.Context, client *gophercloud.ServiceClient, id string, opts UpdateConsumerOptsBuilder) (r UpdateConsumerResult) { b, err := opts.ToOAuth1UpdateConsumerMap() if err != nil { r.Err = err diff --git a/openstack/identity/v3/oauth1/results.go b/openstack/identity/v3/oauth1/results.go index 2ed75bd74a..75b1b7cc31 100644 --- a/openstack/identity/v3/oauth1/results.go +++ b/openstack/identity/v3/oauth1/results.go @@ -61,7 +61,7 @@ func (c ConsumersPage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (c ConsumersPage) NextPageURL() (string, error) { +func (c ConsumersPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links struct { Next string `json:"next"` @@ -221,7 +221,7 @@ func (r AccessTokensPage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (r AccessTokensPage) NextPageURL() (string, error) { +func (r AccessTokensPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links struct { Next string `json:"next"` @@ -268,7 +268,7 @@ func (r AccessTokenRolesPage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (r AccessTokenRolesPage) NextPageURL() (string, error) { +func (r AccessTokenRolesPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links struct { Next string `json:"next"` diff --git a/openstack/identity/v3/oauth1/testing/fixtures_test.go b/openstack/identity/v3/oauth1/testing/fixtures_test.go index 8a2bfe5cdb..493adad8ef 100644 --- a/openstack/identity/v3/oauth1/testing/fixtures_test.go +++ b/openstack/identity/v3/oauth1/testing/fixtures_test.go @@ -8,7 +8,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/oauth1" tokens "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens/testing" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" "github.com/gophercloud/gophercloud/v2/testhelper/client" ) @@ -220,40 +220,40 @@ var ExpectedUserAccessTokenRolesSlice = []oauth1.AccessTokenRole{UserAccessToken // HandleCreateConsumer creates an HTTP handler at `/OS-OAUTH1/consumers` on the // test handler mux that tests consumer creation. -func HandleCreateConsumer(t *testing.T) { - testhelper.Mux.HandleFunc("/OS-OAUTH1/consumers", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "POST") - testhelper.TestHeader(t, r, "Content-Type", "application/json") - testhelper.TestHeader(t, r, "Accept", "application/json") - testhelper.TestJSONRequest(t, r, CreateConsumerRequest) +func HandleCreateConsumer(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-OAUTH1/consumers", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, CreateConsumerRequest) w.WriteHeader(http.StatusCreated) - _, err := fmt.Fprintf(w, CreateConsumerResponse) - testhelper.AssertNoErr(t, err) + _, err := fmt.Fprint(w, CreateConsumerResponse) + th.AssertNoErr(t, err) }) } // HandleUpdateConsumer creates an HTTP handler at `/OS-OAUTH1/consumers/7fea2d` on the // test handler mux that tests consumer update. -func HandleUpdateConsumer(t *testing.T) { - testhelper.Mux.HandleFunc("/OS-OAUTH1/consumers/7fea2d", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "PATCH") - testhelper.TestHeader(t, r, "Content-Type", "application/json") - testhelper.TestHeader(t, r, "Accept", "application/json") - testhelper.TestJSONRequest(t, r, UpdateConsumerRequest) +func HandleUpdateConsumer(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-OAUTH1/consumers/7fea2d", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, UpdateConsumerRequest) w.WriteHeader(http.StatusOK) - _, err := fmt.Fprintf(w, UpdateConsumerResponse) - testhelper.AssertNoErr(t, err) + _, err := fmt.Fprint(w, UpdateConsumerResponse) + th.AssertNoErr(t, err) }) } // HandleDeleteConsumer creates an HTTP handler at `/OS-OAUTH1/consumers/7fea2d` on the // test handler mux that tests consumer deletion. -func HandleDeleteConsumer(t *testing.T) { - testhelper.Mux.HandleFunc("/OS-OAUTH1/consumers/7fea2d", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "DELETE") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandleDeleteConsumer(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-OAUTH1/consumers/7fea2d", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) @@ -261,15 +261,15 @@ func HandleDeleteConsumer(t *testing.T) { // HandleGetConsumer creates an HTTP handler at `/OS-OAUTH1/consumers/7fea2d` on the // test handler mux that responds with a single consumer. -func HandleGetConsumer(t *testing.T) { - testhelper.Mux.HandleFunc("/OS-OAUTH1/consumers/7fea2d", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "GET") - testhelper.TestHeader(t, r, "Accept", "application/json") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandleGetConsumer(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-OAUTH1/consumers/7fea2d", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetConsumerResponse) + fmt.Fprint(w, GetConsumerResponse) }) } @@ -298,15 +298,15 @@ var ExpectedConsumersSlice = []oauth1.Consumer{FirstConsumer, SecondConsumer} // HandleListConsumers creates an HTTP handler at `/OS-OAUTH1/consumers` on the // test handler mux that responds with a list of two consumers. -func HandleListConsumers(t *testing.T) { - testhelper.Mux.HandleFunc("/OS-OAUTH1/consumers", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "GET") - testhelper.TestHeader(t, r, "Accept", "application/json") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandleListConsumers(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-OAUTH1/consumers", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListConsumersResponse) + fmt.Fprint(w, ListConsumersResponse) }) } @@ -318,33 +318,33 @@ var Token = oauth1.Token{ // HandleRequestToken creates an HTTP handler at `/OS-OAUTH1/request_token` on the // test handler mux that responds with a OAuth1 unauthorized token. -func HandleRequestToken(t *testing.T) { - testhelper.Mux.HandleFunc("/OS-OAUTH1/request_token", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "POST") - testhelper.TestHeader(t, r, "Accept", "application/json") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) - testhelper.TestHeader(t, r, "Authorization", `OAuth oauth_callback="oob", oauth_consumer_key="7fea2d", oauth_nonce="71416001758914252991586795052", oauth_signature_method="HMAC-SHA1", oauth_timestamp="0", oauth_version="1.0", oauth_signature="jCSPVryCYF52Ks0VNNmBmeKSGuw%3D"`) - testhelper.TestHeader(t, r, "Requested-Project-Id", "1df927e8a466498f98788ed73d3c8ab4") - testhelper.TestBody(t, r, "") +func HandleRequestToken(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-OAUTH1/request_token", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Authorization", `OAuth oauth_callback="oob", oauth_consumer_key="7fea2d", oauth_nonce="71416001758914252991586795052", oauth_signature_method="HMAC-SHA1", oauth_timestamp="0", oauth_version="1.0", oauth_signature="jCSPVryCYF52Ks0VNNmBmeKSGuw%3D"`) + th.TestHeader(t, r, "Requested-Project-Id", "1df927e8a466498f98788ed73d3c8ab4") + th.TestBody(t, r, "") w.Header().Set("Content-Type", oauth1.OAuth1TokenContentType) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, `oauth_token=29971f&oauth_token_secret=238eb8&oauth_expires_at=2013-09-11T06:07:51.501805Z`) + fmt.Fprint(w, `oauth_token=29971f&oauth_token_secret=238eb8&oauth_expires_at=2013-09-11T06:07:51.501805Z`) }) } // HandleAuthorizeToken creates an HTTP handler at `/OS-OAUTH1/authorize/29971f` on the // test handler mux that tests unauthorized token authorization. -func HandleAuthorizeToken(t *testing.T) { - testhelper.Mux.HandleFunc("/OS-OAUTH1/authorize/29971f", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "PUT") - testhelper.TestHeader(t, r, "Content-Type", "application/json") - testhelper.TestHeader(t, r, "Accept", "application/json") - testhelper.TestJSONRequest(t, r, AuthorizeTokenRequest) +func HandleAuthorizeToken(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-OAUTH1/authorize/29971f", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, AuthorizeTokenRequest) w.WriteHeader(http.StatusOK) - _, err := fmt.Fprintf(w, AuthorizeTokenResponse) - testhelper.AssertNoErr(t, err) + _, err := fmt.Fprint(w, AuthorizeTokenResponse) + th.AssertNoErr(t, err) }) } @@ -356,40 +356,40 @@ var AccessToken = oauth1.Token{ // HandleCreateAccessToken creates an HTTP handler at `/OS-OAUTH1/access_token` on the // test handler mux that responds with a OAuth1 access token. -func HandleCreateAccessToken(t *testing.T) { - testhelper.Mux.HandleFunc("/OS-OAUTH1/access_token", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "POST") - testhelper.TestHeader(t, r, "Accept", "application/json") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) - testhelper.TestHeader(t, r, "Authorization", `OAuth oauth_consumer_key="7fea2d", oauth_nonce="66148873158553341551586804894", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1586804894", oauth_token="29971f", oauth_verifier="8171", oauth_version="1.0", oauth_signature="usQ89Y3IYG0IBE7%2Ft8aVsc8XgEk%3D"`) - testhelper.TestBody(t, r, "") +func HandleCreateAccessToken(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-OAUTH1/access_token", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Authorization", `OAuth oauth_consumer_key="7fea2d", oauth_nonce="66148873158553341551586804894", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1586804894", oauth_token="29971f", oauth_verifier="8171", oauth_version="1.0", oauth_signature="usQ89Y3IYG0IBE7%2Ft8aVsc8XgEk%3D"`) + th.TestBody(t, r, "") w.Header().Set("Content-Type", oauth1.OAuth1TokenContentType) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, `oauth_token=accd36&oauth_token_secret=aa47da&oauth_expires_at=2013-09-11T06:07:51.501805Z`) + fmt.Fprint(w, `oauth_token=accd36&oauth_token_secret=aa47da&oauth_expires_at=2013-09-11T06:07:51.501805Z`) }) } // HandleGetAccessToken creates an HTTP handler at `/users/ce9e07/OS-OAUTH1/access_tokens/6be26a` on the // test handler mux that responds with a single access token. -func HandleGetAccessToken(t *testing.T) { - testhelper.Mux.HandleFunc("/users/ce9e07/OS-OAUTH1/access_tokens/6be26a", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "GET") - testhelper.TestHeader(t, r, "Accept", "application/json") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandleGetAccessToken(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/ce9e07/OS-OAUTH1/access_tokens/6be26a", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetUserAccessTokenResponse) + fmt.Fprint(w, GetUserAccessTokenResponse) }) } // HandleRevokeAccessToken creates an HTTP handler at `/users/ce9e07/OS-OAUTH1/access_tokens/6be26a` on the // test handler mux that tests access token deletion. -func HandleRevokeAccessToken(t *testing.T) { - testhelper.Mux.HandleFunc("/users/ce9e07/OS-OAUTH1/access_tokens/6be26a", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "DELETE") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandleRevokeAccessToken(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/ce9e07/OS-OAUTH1/access_tokens/6be26a", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) @@ -397,58 +397,58 @@ func HandleRevokeAccessToken(t *testing.T) { // HandleListAccessTokens creates an HTTP handler at `/users/ce9e07/OS-OAUTH1/access_tokens` on the // test handler mux that responds with a slice of access tokens. -func HandleListAccessTokens(t *testing.T) { - testhelper.Mux.HandleFunc("/users/ce9e07/OS-OAUTH1/access_tokens", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "GET") - testhelper.TestHeader(t, r, "Accept", "application/json") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandleListAccessTokens(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/ce9e07/OS-OAUTH1/access_tokens", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListUserAccessTokensResponse) + fmt.Fprint(w, ListUserAccessTokensResponse) }) } // HandleListAccessTokenRoles creates an HTTP handler at `/users/ce9e07/OS-OAUTH1/access_tokens/6be26a/roles` on the // test handler mux that responds with a slice of access token roles. -func HandleListAccessTokenRoles(t *testing.T) { - testhelper.Mux.HandleFunc("/users/ce9e07/OS-OAUTH1/access_tokens/6be26a/roles", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "GET") - testhelper.TestHeader(t, r, "Accept", "application/json") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandleListAccessTokenRoles(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/ce9e07/OS-OAUTH1/access_tokens/6be26a/roles", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListUserAccessTokenRolesResponse) + fmt.Fprint(w, ListUserAccessTokenRolesResponse) }) } // HandleGetAccessTokenRole creates an HTTP handler at `/users/ce9e07/OS-OAUTH1/access_tokens/6be26a/roles/5ad150` on the // test handler mux that responds with an access token role. -func HandleGetAccessTokenRole(t *testing.T) { - testhelper.Mux.HandleFunc("/users/ce9e07/OS-OAUTH1/access_tokens/6be26a/roles/5ad150", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "GET") - testhelper.TestHeader(t, r, "Accept", "application/json") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandleGetAccessTokenRole(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/ce9e07/OS-OAUTH1/access_tokens/6be26a/roles/5ad150", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListUserAccessTokenRoleResponse) + fmt.Fprint(w, ListUserAccessTokenRoleResponse) }) } // HandleAuthenticate creates an HTTP handler at `/auth/tokens` on the // test handler mux that responds with an OpenStack token. -func HandleAuthenticate(t *testing.T) { - testhelper.Mux.HandleFunc("/auth/tokens", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "POST") - testhelper.TestHeader(t, r, "Content-Type", "application/json") - testhelper.TestHeader(t, r, "Accept", "application/json") - testhelper.TestHeader(t, r, "Authorization", `OAuth oauth_consumer_key="7fea2d", oauth_nonce="66148873158553341551586804894", oauth_signature_method="HMAC-SHA1", oauth_timestamp="0", oauth_token="accd36", oauth_version="1.0", oauth_signature="JgMHu4e7rXGlqz3A%2FLhHDMvtjp8%3D"`) - testhelper.TestJSONRequest(t, r, `{"auth": {"identity": {"oauth1": {}, "methods": ["oauth1"]}}}`) +func HandleAuthenticate(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/auth/tokens", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Authorization", `OAuth oauth_consumer_key="7fea2d", oauth_nonce="66148873158553341551586804894", oauth_signature_method="HMAC-SHA1", oauth_timestamp="0", oauth_token="accd36", oauth_version="1.0", oauth_signature="JgMHu4e7rXGlqz3A%2FLhHDMvtjp8%3D"`) + th.TestJSONRequest(t, r, `{"auth": {"identity": {"oauth1": {}, "methods": ["oauth1"]}}}`) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, tokens.TokenOutput) + fmt.Fprint(w, tokens.TokenOutput) }) } diff --git a/openstack/identity/v3/oauth1/testing/requests_test.go b/openstack/identity/v3/oauth1/testing/requests_test.go index 75f37bca0f..4febcb8c07 100644 --- a/openstack/identity/v3/oauth1/testing/requests_test.go +++ b/openstack/identity/v3/oauth1/testing/requests_test.go @@ -13,11 +13,11 @@ import ( ) func TestCreateConsumer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateConsumer(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateConsumer(t, fakeServer) - consumer, err := oauth1.CreateConsumer(context.TODO(), client.ServiceClient(), oauth1.CreateConsumerOpts{ + consumer, err := oauth1.CreateConsumer(context.TODO(), client.ServiceClient(fakeServer), oauth1.CreateConsumerOpts{ Description: "My consumer", }).Extract() th.AssertNoErr(t, err) @@ -26,11 +26,11 @@ func TestCreateConsumer(t *testing.T) { } func TestUpdateConsumer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateConsumer(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateConsumer(t, fakeServer) - consumer, err := oauth1.UpdateConsumer(context.TODO(), client.ServiceClient(), "7fea2d", oauth1.UpdateConsumerOpts{ + consumer, err := oauth1.UpdateConsumer(context.TODO(), client.ServiceClient(fakeServer), "7fea2d", oauth1.UpdateConsumerOpts{ Description: "My new consumer", }).Extract() th.AssertNoErr(t, err) @@ -39,32 +39,32 @@ func TestUpdateConsumer(t *testing.T) { } func TestDeleteConsumer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteConsumer(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteConsumer(t, fakeServer) - err := oauth1.DeleteConsumer(context.TODO(), client.ServiceClient(), "7fea2d").ExtractErr() + err := oauth1.DeleteConsumer(context.TODO(), client.ServiceClient(fakeServer), "7fea2d").ExtractErr() th.AssertNoErr(t, err) } func TestGetConsumer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetConsumer(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetConsumer(t, fakeServer) - consumer, err := oauth1.GetConsumer(context.TODO(), client.ServiceClient(), "7fea2d").Extract() + consumer, err := oauth1.GetConsumer(context.TODO(), client.ServiceClient(fakeServer), "7fea2d").Extract() th.AssertNoErr(t, err) th.AssertEquals(t, FirstConsumer, *consumer) } func TestListConsumers(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListConsumers(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListConsumers(t, fakeServer) count := 0 - err := oauth1.ListConsumers(client.ServiceClient()).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := oauth1.ListConsumers(client.ServiceClient(fakeServer)).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := oauth1.ExtractConsumers(page) @@ -75,15 +75,15 @@ func TestListConsumers(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListConsumersAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListConsumers(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListConsumers(t, fakeServer) - allPages, err := oauth1.ListConsumers(client.ServiceClient()).AllPages(context.TODO()) + allPages, err := oauth1.ListConsumers(client.ServiceClient(fakeServer)).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := oauth1.ExtractConsumers(allPages) th.AssertNoErr(t, err) @@ -91,12 +91,12 @@ func TestListConsumersAllPages(t *testing.T) { } func TestRequestToken(t *testing.T) { - th.SetupPersistentPortHTTP(t, 33199) - defer th.TeardownHTTP() - HandleRequestToken(t) + fakeServer := th.SetupPersistentPortHTTP(t, 33199) + defer fakeServer.Teardown() + HandleRequestToken(t, fakeServer) ts := time.Unix(0, 0) - token, err := oauth1.RequestToken(context.TODO(), client.ServiceClient(), oauth1.RequestTokenOpts{ + token, err := oauth1.RequestToken(context.TODO(), client.ServiceClient(fakeServer), oauth1.RequestTokenOpts{ OAuthConsumerKey: Consumer.ID, OAuthConsumerSecret: Consumer.Secret, OAuthSignatureMethod: oauth1.HMACSHA1, @@ -110,11 +110,11 @@ func TestRequestToken(t *testing.T) { } func TestAuthorizeToken(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAuthorizeToken(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAuthorizeToken(t, fakeServer) - token, err := oauth1.AuthorizeToken(context.TODO(), client.ServiceClient(), "29971f", oauth1.AuthorizeTokenOpts{ + token, err := oauth1.AuthorizeToken(context.TODO(), client.ServiceClient(fakeServer), "29971f", oauth1.AuthorizeTokenOpts{ Roles: []oauth1.Role{ { ID: "a3b29b", @@ -130,12 +130,12 @@ func TestAuthorizeToken(t *testing.T) { } func TestCreateAccessToken(t *testing.T) { - th.SetupPersistentPortHTTP(t, 33199) - defer th.TeardownHTTP() - HandleCreateAccessToken(t) + fakeServer := th.SetupPersistentPortHTTP(t, 33199) + defer fakeServer.Teardown() + HandleCreateAccessToken(t, fakeServer) ts := time.Unix(1586804894, 0) - token, err := oauth1.CreateAccessToken(context.TODO(), client.ServiceClient(), oauth1.CreateAccessTokenOpts{ + token, err := oauth1.CreateAccessToken(context.TODO(), client.ServiceClient(fakeServer), oauth1.CreateAccessTokenOpts{ OAuthConsumerKey: Consumer.ID, OAuthConsumerSecret: Consumer.Secret, OAuthToken: Token.OAuthToken, @@ -151,32 +151,32 @@ func TestCreateAccessToken(t *testing.T) { } func TestGetAccessToken(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetAccessToken(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetAccessToken(t, fakeServer) - token, err := oauth1.GetAccessToken(context.TODO(), client.ServiceClient(), "ce9e07", "6be26a").Extract() + token, err := oauth1.GetAccessToken(context.TODO(), client.ServiceClient(fakeServer), "ce9e07", "6be26a").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, UserAccessToken, *token) } func TestRevokeAccessToken(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleRevokeAccessToken(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleRevokeAccessToken(t, fakeServer) - err := oauth1.RevokeAccessToken(context.TODO(), client.ServiceClient(), "ce9e07", "6be26a").ExtractErr() + err := oauth1.RevokeAccessToken(context.TODO(), client.ServiceClient(fakeServer), "ce9e07", "6be26a").ExtractErr() th.AssertNoErr(t, err) } func TestListAccessTokens(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListAccessTokens(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListAccessTokens(t, fakeServer) count := 0 - err := oauth1.ListAccessTokens(client.ServiceClient(), "ce9e07").EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := oauth1.ListAccessTokens(client.ServiceClient(fakeServer), "ce9e07").EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := oauth1.ExtractAccessTokens(page) @@ -187,15 +187,15 @@ func TestListAccessTokens(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListAccessTokensAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListAccessTokens(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListAccessTokens(t, fakeServer) - allPages, err := oauth1.ListAccessTokens(client.ServiceClient(), "ce9e07").AllPages(context.TODO()) + allPages, err := oauth1.ListAccessTokens(client.ServiceClient(fakeServer), "ce9e07").AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := oauth1.ExtractAccessTokens(allPages) th.AssertNoErr(t, err) @@ -203,12 +203,12 @@ func TestListAccessTokensAllPages(t *testing.T) { } func TestListAccessTokenRoles(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListAccessTokenRoles(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListAccessTokenRoles(t, fakeServer) count := 0 - err := oauth1.ListAccessTokenRoles(client.ServiceClient(), "ce9e07", "6be26a").EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := oauth1.ListAccessTokenRoles(client.ServiceClient(fakeServer), "ce9e07", "6be26a").EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := oauth1.ExtractAccessTokenRoles(page) @@ -219,15 +219,15 @@ func TestListAccessTokenRoles(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListAccessTokenRolesAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListAccessTokenRoles(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListAccessTokenRoles(t, fakeServer) - allPages, err := oauth1.ListAccessTokenRoles(client.ServiceClient(), "ce9e07", "6be26a").AllPages(context.TODO()) + allPages, err := oauth1.ListAccessTokenRoles(client.ServiceClient(fakeServer), "ce9e07", "6be26a").AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := oauth1.ExtractAccessTokenRoles(allPages) th.AssertNoErr(t, err) @@ -235,20 +235,20 @@ func TestListAccessTokenRolesAllPages(t *testing.T) { } func TestGetAccessTokenRole(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetAccessTokenRole(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetAccessTokenRole(t, fakeServer) - role, err := oauth1.GetAccessTokenRole(context.TODO(), client.ServiceClient(), "ce9e07", "6be26a", "5ad150").Extract() + role, err := oauth1.GetAccessTokenRole(context.TODO(), client.ServiceClient(fakeServer), "ce9e07", "6be26a", "5ad150").Extract() th.AssertNoErr(t, err) th.AssertEquals(t, UserAccessTokenRole, *role) } func TestAuthenticate(t *testing.T) { - th.SetupPersistentPortHTTP(t, 33199) - defer th.TeardownHTTP() - HandleAuthenticate(t) + fakeServer := th.SetupPersistentPortHTTP(t, 33199) + defer fakeServer.Teardown() + HandleAuthenticate(t, fakeServer) expected := &tokens.Token{ ExpiresAt: time.Date(2017, 6, 3, 2, 19, 49, 0, time.UTC), @@ -265,7 +265,7 @@ func TestAuthenticate(t *testing.T) { OAuthNonce: "66148873158553341551586804894", } - actual, err := oauth1.Create(context.TODO(), client.ServiceClient(), options).Extract() + actual, err := oauth1.Create(context.TODO(), client.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, expected, actual) } diff --git a/openstack/identity/v3/osinherit/testing/fixtures_test.go b/openstack/identity/v3/osinherit/testing/fixtures_test.go index e0c33646f6..b053bba456 100644 --- a/openstack/identity/v3/osinherit/testing/fixtures_test.go +++ b/openstack/identity/v3/osinherit/testing/fixtures_test.go @@ -5,83 +5,83 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) -func HandleAssignSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/OS-INHERIT/projects/{project_id}/users/{user_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { +func HandleAssignSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-INHERIT/projects/{project_id}/users/{user_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) - th.Mux.HandleFunc("/OS-INHERIT/projects/{project_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/OS-INHERIT/projects/{project_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) - th.Mux.HandleFunc("/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) - th.Mux.HandleFunc("/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) } -func HandleValidateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/OS-INHERIT/projects/{project_id}/users/{user_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { +func HandleValidateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-INHERIT/projects/{project_id}/users/{user_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "HEAD") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) - th.Mux.HandleFunc("/OS-INHERIT/projects/{project_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/OS-INHERIT/projects/{project_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "HEAD") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) - th.Mux.HandleFunc("/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "HEAD") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) - th.Mux.HandleFunc("/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "HEAD") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) } -func HandleUnassignSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/OS-INHERIT/projects/{project_id}/users/{user_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { +func HandleUnassignSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-INHERIT/projects/{project_id}/users/{user_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) - th.Mux.HandleFunc("/OS-INHERIT/projects/{project_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/OS-INHERIT/projects/{project_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) - th.Mux.HandleFunc("/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) - th.Mux.HandleFunc("/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) } diff --git a/openstack/identity/v3/osinherit/testing/requests_test.go b/openstack/identity/v3/osinherit/testing/requests_test.go index 7901a689c7..8b90f26498 100644 --- a/openstack/identity/v3/osinherit/testing/requests_test.go +++ b/openstack/identity/v3/osinherit/testing/requests_test.go @@ -10,41 +10,41 @@ import ( ) func TestAssign(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAssignSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAssignSuccessfully(t, fakeServer) - err := osinherit.Assign(context.TODO(), client.ServiceClient(), "{role_id}", osinherit.AssignOpts{ + err := osinherit.Assign(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", osinherit.AssignOpts{ UserID: "{user_id}", ProjectID: "{project_id}", }).ExtractErr() th.AssertNoErr(t, err) - err = osinherit.Assign(context.TODO(), client.ServiceClient(), "{role_id}", osinherit.AssignOpts{ + err = osinherit.Assign(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", osinherit.AssignOpts{ UserID: "{user_id}", DomainID: "{domain_id}", }).ExtractErr() th.AssertNoErr(t, err) - err = osinherit.Assign(context.TODO(), client.ServiceClient(), "{role_id}", osinherit.AssignOpts{ + err = osinherit.Assign(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", osinherit.AssignOpts{ GroupID: "{group_id}", ProjectID: "{project_id}", }).ExtractErr() th.AssertNoErr(t, err) - err = osinherit.Assign(context.TODO(), client.ServiceClient(), "{role_id}", osinherit.AssignOpts{ + err = osinherit.Assign(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", osinherit.AssignOpts{ GroupID: "{group_id}", DomainID: "{domain_id}", }).ExtractErr() th.AssertNoErr(t, err) - err = osinherit.Assign(context.TODO(), client.ServiceClient(), "{role_id}", osinherit.AssignOpts{ + err = osinherit.Assign(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", osinherit.AssignOpts{ GroupID: "{group_id}", UserID: "{user_id}", }).ExtractErr() th.AssertErr(t, err) - err = osinherit.Assign(context.TODO(), client.ServiceClient(), "{role_id}", osinherit.AssignOpts{ + err = osinherit.Assign(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", osinherit.AssignOpts{ ProjectID: "{project_id}", DomainID: "{domain_id}", }).ExtractErr() @@ -52,41 +52,41 @@ func TestAssign(t *testing.T) { } func TestValidate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleValidateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleValidateSuccessfully(t, fakeServer) - err := osinherit.Validate(context.TODO(), client.ServiceClient(), "{role_id}", osinherit.ValidateOpts{ + err := osinherit.Validate(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", osinherit.ValidateOpts{ UserID: "{user_id}", ProjectID: "{project_id}", }).ExtractErr() th.AssertNoErr(t, err) - err = osinherit.Validate(context.TODO(), client.ServiceClient(), "{role_id}", osinherit.ValidateOpts{ + err = osinherit.Validate(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", osinherit.ValidateOpts{ UserID: "{user_id}", DomainID: "{domain_id}", }).ExtractErr() th.AssertNoErr(t, err) - err = osinherit.Validate(context.TODO(), client.ServiceClient(), "{role_id}", osinherit.ValidateOpts{ + err = osinherit.Validate(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", osinherit.ValidateOpts{ GroupID: "{group_id}", ProjectID: "{project_id}", }).ExtractErr() th.AssertNoErr(t, err) - err = osinherit.Validate(context.TODO(), client.ServiceClient(), "{role_id}", osinherit.ValidateOpts{ + err = osinherit.Validate(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", osinherit.ValidateOpts{ GroupID: "{group_id}", DomainID: "{domain_id}", }).ExtractErr() th.AssertNoErr(t, err) - err = osinherit.Validate(context.TODO(), client.ServiceClient(), "{role_id}", osinherit.ValidateOpts{ + err = osinherit.Validate(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", osinherit.ValidateOpts{ GroupID: "{group_id}", UserID: "{user_id}", }).ExtractErr() th.AssertErr(t, err) - err = osinherit.Validate(context.TODO(), client.ServiceClient(), "{role_id}", osinherit.ValidateOpts{ + err = osinherit.Validate(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", osinherit.ValidateOpts{ ProjectID: "{project_id}", DomainID: "{domain_id}", }).ExtractErr() @@ -94,41 +94,41 @@ func TestValidate(t *testing.T) { } func TestUnassign(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUnassignSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUnassignSuccessfully(t, fakeServer) - err := osinherit.Unassign(context.TODO(), client.ServiceClient(), "{role_id}", osinherit.UnassignOpts{ + err := osinherit.Unassign(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", osinherit.UnassignOpts{ UserID: "{user_id}", ProjectID: "{project_id}", }).ExtractErr() th.AssertNoErr(t, err) - err = osinherit.Unassign(context.TODO(), client.ServiceClient(), "{role_id}", osinherit.UnassignOpts{ + err = osinherit.Unassign(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", osinherit.UnassignOpts{ UserID: "{user_id}", DomainID: "{domain_id}", }).ExtractErr() th.AssertNoErr(t, err) - err = osinherit.Unassign(context.TODO(), client.ServiceClient(), "{role_id}", osinherit.UnassignOpts{ + err = osinherit.Unassign(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", osinherit.UnassignOpts{ GroupID: "{group_id}", ProjectID: "{project_id}", }).ExtractErr() th.AssertNoErr(t, err) - err = osinherit.Unassign(context.TODO(), client.ServiceClient(), "{role_id}", osinherit.UnassignOpts{ + err = osinherit.Unassign(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", osinherit.UnassignOpts{ GroupID: "{group_id}", DomainID: "{domain_id}", }).ExtractErr() th.AssertNoErr(t, err) - err = osinherit.Unassign(context.TODO(), client.ServiceClient(), "{role_id}", osinherit.UnassignOpts{ + err = osinherit.Unassign(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", osinherit.UnassignOpts{ GroupID: "{group_id}", UserID: "{user_id}", }).ExtractErr() th.AssertErr(t, err) - err = osinherit.Unassign(context.TODO(), client.ServiceClient(), "{role_id}", osinherit.UnassignOpts{ + err = osinherit.Unassign(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", osinherit.UnassignOpts{ ProjectID: "{project_id}", DomainID: "{domain_id}", }).ExtractErr() diff --git a/openstack/identity/v3/policies/results.go b/openstack/identity/v3/policies/results.go index 90a8436ea1..08bea1376b 100644 --- a/openstack/identity/v3/policies/results.go +++ b/openstack/identity/v3/policies/results.go @@ -100,7 +100,7 @@ func (r PolicyPage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (r PolicyPage) NextPageURL() (string, error) { +func (r PolicyPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links struct { Next string `json:"next"` diff --git a/openstack/identity/v3/policies/testing/fixtures_test.go b/openstack/identity/v3/policies/testing/fixtures_test.go index 2ebc6c6c41..50e9366fad 100644 --- a/openstack/identity/v3/policies/testing/fixtures_test.go +++ b/openstack/identity/v3/policies/testing/fixtures_test.go @@ -7,7 +7,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/policies" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) // ListOutput provides a single page of Policy results. @@ -154,19 +154,19 @@ var ExpectedPoliciesSlice = []policies.Policy{FirstPolicy, SecondPolicy} // HandleListPoliciesSuccessfully creates an HTTP handler at `/policies` on the // test handler mux that responds with a list of two policies. -func HandleListPoliciesSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/policies", func(w http.ResponseWriter, r *http.Request) { +func HandleListPoliciesSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/policies", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) switch r.URL.Query().Get("type") { case "": - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) case "application/json": - fmt.Fprintf(w, ListWithFilterOutput) + fmt.Fprint(w, ListWithFilterOutput) default: w.WriteHeader(http.StatusBadRequest) } @@ -175,54 +175,54 @@ func HandleListPoliciesSuccessfully(t *testing.T) { // HandleCreatePolicySuccessfully creates an HTTP handler at `/policies` on the // test handler mux that tests policy creation. -func HandleCreatePolicySuccessfully(t *testing.T) { - th.Mux.HandleFunc("/policies", func(w http.ResponseWriter, r *http.Request) { +func HandleCreatePolicySuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/policies", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleGetPolicySuccessfully creates an HTTP handler at `/policies` on the // test handler mux that responds with a single policy. -func HandleGetPolicySuccessfully(t *testing.T) { - th.Mux.HandleFunc("/policies/b49884da9d31494ea02aff38d4b4e701", +func HandleGetPolicySuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/policies/b49884da9d31494ea02aff38d4b4e701", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }, ) } // HandleUpdatePolicySuccessfully creates an HTTP handler at `/policies` on the // test handler mux that tests role update. -func HandleUpdatePolicySuccessfully(t *testing.T) { - th.Mux.HandleFunc("/policies/b49884da9d31494ea02aff38d4b4e701", +func HandleUpdatePolicySuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/policies/b49884da9d31494ea02aff38d4b4e701", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }, ) } // HandleDeletePolicySuccessfully creates an HTTP handler at `/policies` on the // test handler mux that tests policy deletion. -func HandleDeletePolicySuccessfully(t *testing.T) { - th.Mux.HandleFunc("/policies/9fe1d3", func(w http.ResponseWriter, r *http.Request) { +func HandleDeletePolicySuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/policies/9fe1d3", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) diff --git a/openstack/identity/v3/policies/testing/requests_test.go b/openstack/identity/v3/policies/testing/requests_test.go index f837543b31..2950abd327 100644 --- a/openstack/identity/v3/policies/testing/requests_test.go +++ b/openstack/identity/v3/policies/testing/requests_test.go @@ -12,12 +12,12 @@ import ( ) func TestListPolicies(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListPoliciesSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListPoliciesSuccessfully(t, fakeServer) count := 0 - err := policies.List(client.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := policies.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := policies.ExtractPolicies(page) @@ -28,15 +28,15 @@ func TestListPolicies(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListPoliciesAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListPoliciesSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListPoliciesSuccessfully(t, fakeServer) - allPages, err := policies.List(client.ServiceClient(), nil).AllPages(context.TODO()) + allPages, err := policies.List(client.ServiceClient(fakeServer), nil).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := policies.ExtractPolicies(allPages) th.AssertNoErr(t, err) @@ -44,14 +44,14 @@ func TestListPoliciesAllPages(t *testing.T) { } func TestListPoliciesWithFilter(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListPoliciesSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListPoliciesSuccessfully(t, fakeServer) listOpts := policies.ListOpts{ Type: "application/json", } - allPages, err := policies.List(client.ServiceClient(), listOpts).AllPages(context.TODO()) + allPages, err := policies.List(client.ServiceClient(fakeServer), listOpts).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := policies.ExtractPolicies(allPages) th.AssertNoErr(t, err) @@ -91,9 +91,9 @@ func TestListPoliciesFiltersCheck(t *testing.T) { } func TestCreatePolicy(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreatePolicySuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreatePolicySuccessfully(t, fakeServer) createOpts := policies.CreateOpts{ Type: "application/json", @@ -103,7 +103,7 @@ func TestCreatePolicy(t *testing.T) { }, } - actual, err := policies.Create(context.TODO(), client.ServiceClient(), createOpts).Extract() + actual, err := policies.Create(context.TODO(), client.ServiceClient(fakeServer), createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondPolicy, *actual) } @@ -152,21 +152,21 @@ func TestCreatePolicyTypeLengthCheck(t *testing.T) { } func TestGetPolicy(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetPolicySuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetPolicySuccessfully(t, fakeServer) id := "b49884da9d31494ea02aff38d4b4e701" - actual, err := policies.Get(context.TODO(), client.ServiceClient(), id).Extract() + actual, err := policies.Get(context.TODO(), client.ServiceClient(fakeServer), id).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondPolicy, *actual) } func TestUpdatePolicy(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdatePolicySuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdatePolicySuccessfully(t, fakeServer) updateOpts := policies.UpdateOpts{ Extra: map[string]any{ @@ -175,7 +175,7 @@ func TestUpdatePolicy(t *testing.T) { } id := "b49884da9d31494ea02aff38d4b4e701" - actual, err := policies.Update(context.TODO(), client.ServiceClient(), id, updateOpts).Extract() + actual, err := policies.Update(context.TODO(), client.ServiceClient(fakeServer), id, updateOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondPolicyUpdated, *actual) } @@ -221,10 +221,10 @@ func TestUpdatePolicyTypeLengthCheck(t *testing.T) { } func TestDeletePolicy(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeletePolicySuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeletePolicySuccessfully(t, fakeServer) - res := policies.Delete(context.TODO(), client.ServiceClient(), "9fe1d3") + res := policies.Delete(context.TODO(), client.ServiceClient(fakeServer), "9fe1d3") th.AssertNoErr(t, res.Err) } diff --git a/openstack/identity/v3/projectendpoints/testing/requests_test.go b/openstack/identity/v3/projectendpoints/testing/requests_test.go index 9a9c17139f..7e4f3c152a 100644 --- a/openstack/identity/v3/projectendpoints/testing/requests_test.go +++ b/openstack/identity/v3/projectendpoints/testing/requests_test.go @@ -14,30 +14,30 @@ import ( ) func TestCreateSuccessful(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/OS-EP-FILTER/projects/project-id/endpoints/endpoint-id", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/OS-EP-FILTER/projects/project-id/endpoints/endpoint-id", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) - err := projectendpoints.Create(context.TODO(), client.ServiceClient(), "project-id", "endpoint-id").Err + err := projectendpoints.Create(context.TODO(), client.ServiceClient(fakeServer), "project-id", "endpoint-id").Err th.AssertNoErr(t, err) } func TestListEndpoints(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/OS-EP-FILTER/projects/project-id/endpoints", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/OS-EP-FILTER/projects/project-id/endpoints", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "endpoints": [ { @@ -71,7 +71,7 @@ func TestListEndpoints(t *testing.T) { }) count := 0 - err := projectendpoints.List(client.ServiceClient(), "project-id").EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := projectendpoints.List(client.ServiceClient(fakeServer), "project-id").EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := projectendpoints.ExtractEndpoints(page) if err != nil { @@ -103,16 +103,16 @@ func TestListEndpoints(t *testing.T) { } func TestDeleteEndpoint(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/OS-EP-FILTER/projects/project-id/endpoints/endpoint-id", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/OS-EP-FILTER/projects/project-id/endpoints/endpoint-id", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := projectendpoints.Delete(context.TODO(), client.ServiceClient(), "project-id", "endpoint-id") + res := projectendpoints.Delete(context.TODO(), client.ServiceClient(fakeServer), "project-id", "endpoint-id") th.AssertNoErr(t, res.Err) } diff --git a/openstack/identity/v3/projects/doc.go b/openstack/identity/v3/projects/doc.go index 6aea466a51..6100327397 100644 --- a/openstack/identity/v3/projects/doc.go +++ b/openstack/identity/v3/projects/doc.go @@ -73,7 +73,7 @@ Example to List all tags of a Project panic(err) } -Example to modify all tags of a Project +Example to modify all tags of a Project projectID := "966b3c7d36a24facaf20b7e458bf2192" tags := ["foo", "bar"] diff --git a/openstack/identity/v3/projects/requests.go b/openstack/identity/v3/projects/requests.go index 30338fc514..5aecc94d93 100644 --- a/openstack/identity/v3/projects/requests.go +++ b/openstack/identity/v3/projects/requests.go @@ -45,6 +45,9 @@ type ListOpts struct { // NotTagsAny filters on specific project tags. At least one of the tags must be absent for the project. NotTagsAny string `q:"not-tags-any"` + // Limit limits the number of projects returned per page. + Limit int `q:"limit"` + // Filters filters the response by custom filters such as // 'name__contains=foo' Filters map[string]string `q:"-"` @@ -276,8 +279,7 @@ func (opts ModifyTagsOpts) ToModifyTagsCreateMap() (map[string]any, error) { } // ModifyTags deletes all tags of a project and adds new ones. -func ModifyTags(ctx context.Context, client *gophercloud.ServiceClient, projectID string, opts ModifyTagsOpts) (r ModifyTagsResult) { - +func ModifyTags(ctx context.Context, client *gophercloud.ServiceClient, projectID string, opts ModifyTagsOptsBuilder) (r ModifyTagsResult) { b, err := opts.ToModifyTagsCreateMap() if err != nil { r.Err = err diff --git a/openstack/identity/v3/projects/results.go b/openstack/identity/v3/projects/results.go index 131b5c5166..581f131af0 100644 --- a/openstack/identity/v3/projects/results.go +++ b/openstack/identity/v3/projects/results.go @@ -122,7 +122,7 @@ func (r ProjectPage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (r ProjectPage) NextPageURL() (string, error) { +func (r ProjectPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links struct { Next string `json:"next"` diff --git a/openstack/identity/v3/projects/testing/fixtures_test.go b/openstack/identity/v3/projects/testing/fixtures_test.go index a54edfe21f..5782fde87c 100644 --- a/openstack/identity/v3/projects/testing/fixtures_test.go +++ b/openstack/identity/v3/projects/testing/fixtures_test.go @@ -59,7 +59,19 @@ const ListOutput = ` "parent_id": null, "tags": ["Red", "Team"], "test": "old" - }, + } + ], + "links": { + "next": "%sprojects?limit=1&marker=1234", + "self": "%sprojects?limit=1", + "previous": null + } +} +` + +const ListOutputSecondPage = ` +{ + "projects": [ { "is_domain": false, "description": "The team that is blue", @@ -73,8 +85,21 @@ const ListOutput = ` } } ], + "links": { + "next": "%sprojects?limit=1&marker=9876", + "self": "%sprojects?limit=1&marker=1234", + "previous": null + } +} +` + +const ListOutputThirdPage = ` +{ + "projects": [ + ], "links": { "next": null, + "self": "%sprojects?limit=1&marker=9876", "previous": null } } @@ -277,63 +302,71 @@ var ExpectedProjects = projects.ProjectTags{ // HandleListAvailableProjectsSuccessfully creates an HTTP handler at `/auth/projects` // on the test handler mux that responds with a list of two tenants. -func HandleListAvailableProjectsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/auth/projects", func(w http.ResponseWriter, r *http.Request) { +func HandleListAvailableProjectsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/auth/projects", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListAvailableOutput) + fmt.Fprint(w, ListAvailableOutput) }) } // HandleListProjectsSuccessfully creates an HTTP handler at `/projects` on the // test handler mux that responds with a list of two tenants. -func HandleListProjectsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/projects", func(w http.ResponseWriter, r *http.Request) { +func HandleListProjectsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/projects", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + + switch r.URL.Query().Get("marker") { + case "": + fmt.Fprintf(w, ListOutput, fakeServer.Endpoint(), fakeServer.Endpoint()) + case "1234": + fmt.Fprintf(w, ListOutputSecondPage, fakeServer.Endpoint(), fakeServer.Endpoint()) + case "9876": + fmt.Fprintf(w, ListOutputThirdPage, fakeServer.Endpoint()) + } }) } // HandleGetProjectSuccessfully creates an HTTP handler at `/projects` on the // test handler mux that responds with a single project. -func HandleGetProjectSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/projects/1234", func(w http.ResponseWriter, r *http.Request) { +func HandleGetProjectSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/projects/1234", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleCreateProjectSuccessfully creates an HTTP handler at `/projects` on the // test handler mux that tests project creation. -func HandleCreateProjectSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/projects", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateProjectSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/projects", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleDeleteProjectSuccessfully creates an HTTP handler at `/projects` on the // test handler mux that tests project deletion. -func HandleDeleteProjectSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/projects/1234", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteProjectSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/projects/1234", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -343,39 +376,39 @@ func HandleDeleteProjectSuccessfully(t *testing.T) { // HandleUpdateProjectSuccessfully creates an HTTP handler at `/projects` on the // test handler mux that tests project updates. -func HandleUpdateProjectSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/projects/1234", func(w http.ResponseWriter, r *http.Request) { +func HandleUpdateProjectSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/projects/1234", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }) } -func HandleListProjectTagsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/projects/966b3c7d36a24facaf20b7e458bf2192/tags", func(w http.ResponseWriter, r *http.Request) { +func HandleListProjectTagsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/projects/966b3c7d36a24facaf20b7e458bf2192/tags", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListTagsOutput) + fmt.Fprint(w, ListTagsOutput) }) } -func HandleModifyProjectTagsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/projects/966b3c7d36a24facaf20b7e458bf2192/tags", func(w http.ResponseWriter, r *http.Request) { +func HandleModifyProjectTagsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/projects/966b3c7d36a24facaf20b7e458bf2192/tags", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, ModifyProjectTagsRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ModifyProjectTagsOutput) + fmt.Fprint(w, ModifyProjectTagsOutput) }) } -func HandleDeleteProjectTagsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/projects/966b3c7d36a24facaf20b7e458bf2192/tags", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteProjectTagsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/projects/966b3c7d36a24facaf20b7e458bf2192/tags", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) diff --git a/openstack/identity/v3/projects/testing/requests_test.go b/openstack/identity/v3/projects/testing/requests_test.go index aa27c50946..a04a64c7e4 100644 --- a/openstack/identity/v3/projects/testing/requests_test.go +++ b/openstack/identity/v3/projects/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestListAvailableProjects(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListAvailableProjectsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListAvailableProjectsSuccessfully(t, fakeServer) count := 0 - err := projects.ListAvailable(client.ServiceClient()).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := projects.ListAvailable(client.ServiceClient(fakeServer)).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := projects.ExtractProjects(page) @@ -27,27 +27,34 @@ func TestListAvailableProjects(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListProjects(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListProjectsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListProjectsSuccessfully(t, fakeServer) + actualProjects := make([]projects.Project, 0, 2) count := 0 - err := projects.List(client.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + opts := projects.ListOpts{ + Limit: 1, + } + err := projects.List(client.ServiceClient(fakeServer), opts).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := projects.ExtractProjects(page) th.AssertNoErr(t, err) - th.CheckDeepEquals(t, ExpectedProjectSlice, actual) + actualProjects = append(actualProjects, actual...) return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 2, count) + + th.CheckEquals(t, 2, len(actualProjects)) + th.CheckDeepEquals(t, ExpectedProjectSlice, actualProjects) } func TestListGroupsFiltersCheck(t *testing.T) { @@ -83,19 +90,19 @@ func TestListGroupsFiltersCheck(t *testing.T) { } func TestGetProject(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetProjectSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetProjectSuccessfully(t, fakeServer) - actual, err := projects.Get(context.TODO(), client.ServiceClient(), "1234").Extract() + actual, err := projects.Get(context.TODO(), client.ServiceClient(fakeServer), "1234").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, RedTeam, *actual) } func TestCreateProject(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateProjectSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateProjectSuccessfully(t, fakeServer) createOpts := projects.CreateOpts{ Name: "Red Team", @@ -104,24 +111,24 @@ func TestCreateProject(t *testing.T) { Extra: map[string]any{"test": "old"}, } - actual, err := projects.Create(context.TODO(), client.ServiceClient(), createOpts).Extract() + actual, err := projects.Create(context.TODO(), client.ServiceClient(fakeServer), createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, RedTeam, *actual) } func TestDeleteProject(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteProjectSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteProjectSuccessfully(t, fakeServer) - res := projects.Delete(context.TODO(), client.ServiceClient(), "1234") + res := projects.Delete(context.TODO(), client.ServiceClient(fakeServer), "1234") th.AssertNoErr(t, res.Err) } func TestUpdateProject(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateProjectSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateProjectSuccessfully(t, fakeServer) var description = "The team that is bright red" updateOpts := projects.UpdateOpts{ @@ -131,40 +138,40 @@ func TestUpdateProject(t *testing.T) { Extra: map[string]any{"test": "new"}, } - actual, err := projects.Update(context.TODO(), client.ServiceClient(), "1234", updateOpts).Extract() + actual, err := projects.Update(context.TODO(), client.ServiceClient(fakeServer), "1234", updateOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, UpdatedRedTeam, *actual) - t.Log(projects.Update(context.TODO(), client.ServiceClient(), "1234", updateOpts)) + t.Log(projects.Update(context.TODO(), client.ServiceClient(fakeServer), "1234", updateOpts)) } func TestListProjectTags(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListProjectTagsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListProjectTagsSuccessfully(t, fakeServer) - actual, err := projects.ListTags(context.TODO(), client.ServiceClient(), "966b3c7d36a24facaf20b7e458bf2192").Extract() + actual, err := projects.ListTags(context.TODO(), client.ServiceClient(fakeServer), "966b3c7d36a24facaf20b7e458bf2192").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, ExpectedTags, *actual) } func TestModifyProjectTags(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleModifyProjectTagsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleModifyProjectTagsSuccessfully(t, fakeServer) modifyOpts := projects.ModifyTagsOpts{ Tags: []string{"foo", "bar"}, } - actual, err := projects.ModifyTags(context.TODO(), client.ServiceClient(), "966b3c7d36a24facaf20b7e458bf2192", modifyOpts).Extract() + actual, err := projects.ModifyTags(context.TODO(), client.ServiceClient(fakeServer), "966b3c7d36a24facaf20b7e458bf2192", modifyOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, ExpectedProjects, *actual) } func TestDeleteTags(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteProjectTagsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteProjectTagsSuccessfully(t, fakeServer) - err := projects.DeleteTags(context.TODO(), client.ServiceClient(), "966b3c7d36a24facaf20b7e458bf2192").ExtractErr() + err := projects.DeleteTags(context.TODO(), client.ServiceClient(fakeServer), "966b3c7d36a24facaf20b7e458bf2192").ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/identity/v3/regions/results.go b/openstack/identity/v3/regions/results.go index 7847c9def8..e260bb5108 100644 --- a/openstack/identity/v3/regions/results.go +++ b/openstack/identity/v3/regions/results.go @@ -99,7 +99,7 @@ func (r RegionPage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (r RegionPage) NextPageURL() (string, error) { +func (r RegionPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links struct { Next string `json:"next"` diff --git a/openstack/identity/v3/regions/testing/fixtures_test.go b/openstack/identity/v3/regions/testing/fixtures_test.go index a4c3a40c38..d3d1500939 100644 --- a/openstack/identity/v3/regions/testing/fixtures_test.go +++ b/openstack/identity/v3/regions/testing/fixtures_test.go @@ -164,62 +164,62 @@ var ExpectedRegionsSlice = []regions.Region{FirstRegion, SecondRegion} // HandleListRegionsSuccessfully creates an HTTP handler at `/regions` on the // test handler mux that responds with a list of two regions. -func HandleListRegionsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/regions", func(w http.ResponseWriter, r *http.Request) { +func HandleListRegionsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/regions", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } // HandleGetRegionSuccessfully creates an HTTP handler at `/regions` on the // test handler mux that responds with a single region. -func HandleGetRegionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/regions/RegionOne-West", func(w http.ResponseWriter, r *http.Request) { +func HandleGetRegionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/regions/RegionOne-West", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleCreateRegionSuccessfully creates an HTTP handler at `/regions` on the // test handler mux that tests region creation. -func HandleCreateRegionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/regions", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateRegionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/regions", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleUpdateRegionSuccessfully creates an HTTP handler at `/regions` on the // test handler mux that tests region update. -func HandleUpdateRegionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/regions/RegionOne-West", func(w http.ResponseWriter, r *http.Request) { +func HandleUpdateRegionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/regions/RegionOne-West", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }) } // HandleDeleteRegionSuccessfully creates an HTTP handler at `/regions` on the // test handler mux that tests region deletion. -func HandleDeleteRegionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/regions/RegionOne-West", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteRegionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/regions/RegionOne-West", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) diff --git a/openstack/identity/v3/regions/testing/requests_test.go b/openstack/identity/v3/regions/testing/requests_test.go index b85b112d13..50a66a20d5 100644 --- a/openstack/identity/v3/regions/testing/requests_test.go +++ b/openstack/identity/v3/regions/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestListRegions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListRegionsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListRegionsSuccessfully(t, fakeServer) count := 0 - err := regions.List(client.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := regions.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := regions.ExtractRegions(page) @@ -27,37 +27,37 @@ func TestListRegions(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListRegionsAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListRegionsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListRegionsSuccessfully(t, fakeServer) - allPages, err := regions.List(client.ServiceClient(), nil).AllPages(context.TODO()) + allPages, err := regions.List(client.ServiceClient(fakeServer), nil).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := regions.ExtractRegions(allPages) th.AssertNoErr(t, err) th.CheckDeepEquals(t, ExpectedRegionsSlice, actual) - th.AssertEquals(t, ExpectedRegionsSlice[1].Extra["email"], "westsupport@example.com") + th.AssertEquals(t, "westsupport@example.com", ExpectedRegionsSlice[1].Extra["email"]) } func TestGetRegion(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetRegionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetRegionSuccessfully(t, fakeServer) - actual, err := regions.Get(context.TODO(), client.ServiceClient(), "RegionOne-West").Extract() + actual, err := regions.Get(context.TODO(), client.ServiceClient(fakeServer), "RegionOne-West").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondRegion, *actual) } func TestCreateRegion(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateRegionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateRegionSuccessfully(t, fakeServer) createOpts := regions.CreateOpts{ ID: "RegionOne-West", @@ -68,15 +68,15 @@ func TestCreateRegion(t *testing.T) { ParentRegionID: "RegionOne", } - actual, err := regions.Create(context.TODO(), client.ServiceClient(), createOpts).Extract() + actual, err := regions.Create(context.TODO(), client.ServiceClient(fakeServer), createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondRegion, *actual) } func TestUpdateRegion(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateRegionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateRegionSuccessfully(t, fakeServer) var description = "First West sub-region of RegionOne" updateOpts := regions.UpdateOpts{ @@ -92,16 +92,16 @@ func TestUpdateRegion(t *testing.T) { */ } - actual, err := regions.Update(context.TODO(), client.ServiceClient(), "RegionOne-West", updateOpts).Extract() + actual, err := regions.Update(context.TODO(), client.ServiceClient(fakeServer), "RegionOne-West", updateOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondRegionUpdated, *actual) } func TestDeleteRegion(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteRegionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteRegionSuccessfully(t, fakeServer) - res := regions.Delete(context.TODO(), client.ServiceClient(), "RegionOne-West") + res := regions.Delete(context.TODO(), client.ServiceClient(fakeServer), "RegionOne-West") th.AssertNoErr(t, res.Err) } diff --git a/openstack/identity/v3/registeredlimits/results.go b/openstack/identity/v3/registeredlimits/results.go index 5709e2ac35..c9ce19582f 100644 --- a/openstack/identity/v3/registeredlimits/results.go +++ b/openstack/identity/v3/registeredlimits/results.go @@ -107,7 +107,7 @@ func (r RegisteredLimitPage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (r RegisteredLimitPage) NextPageURL() (string, error) { +func (r RegisteredLimitPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links struct { Next string `json:"next"` diff --git a/openstack/identity/v3/registeredlimits/testing/fixtures_test.go b/openstack/identity/v3/registeredlimits/testing/fixtures_test.go index 37e11ed6eb..81ddc3cd7b 100644 --- a/openstack/identity/v3/registeredlimits/testing/fixtures_test.go +++ b/openstack/identity/v3/registeredlimits/testing/fixtures_test.go @@ -155,49 +155,49 @@ var ExpectedRegisteredLimitsSlice = []registeredlimits.RegisteredLimit{FirstRegi // HandleListRegisteredLimitsSuccessfully creates an HTTP handler at `/registered_limits` on the // test handler mux that responds with a list of two registered limits. -func HandleListRegisteredLimitsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/registered_limits", func(w http.ResponseWriter, r *http.Request) { +func HandleListRegisteredLimitsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/registered_limits", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } // HandleGetRegisteredLimitSuccessfully creates an HTTP handler at `/registered_limits` on the // test handler mux that responds with a single project. -func HandleGetRegisteredLimitSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/registered_limits/3229b3849f584faea483d6851f7aab05", func(w http.ResponseWriter, r *http.Request) { +func HandleGetRegisteredLimitSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/registered_limits/3229b3849f584faea483d6851f7aab05", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleCreateRegisteredLimitSuccessfully creates an HTTP handler at `/registered_limits` on the // test handler mux that tests registered limit creation. -func HandleCreateRegisteredLimitSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/registered_limits", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateRegisteredLimitSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/registered_limits", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateOutput) + fmt.Fprint(w, CreateOutput) }) } // HandleDeleteRegisteredLimitSuccessfully creates an HTTP handler at `/registered_limits` on the // test handler mux that tests registered_limit deletion. -func HandleDeleteRegisteredLimitSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/registered_limits/3229b3849f584faea483d6851f7aab05", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteRegisteredLimitSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/registered_limits/3229b3849f584faea483d6851f7aab05", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -207,13 +207,13 @@ func HandleDeleteRegisteredLimitSuccessfully(t *testing.T) { // HandleUpdateRegisteredLimitSuccessfully creates an HTTP handler at `/registered_limits` on the // test handler mux that tests registered limits updates. -func HandleUpdateRegisteredLimitSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/registered_limits/3229b3849f584faea483d6851f7aab05", func(w http.ResponseWriter, r *http.Request) { +func HandleUpdateRegisteredLimitSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/registered_limits/3229b3849f584faea483d6851f7aab05", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }) } diff --git a/openstack/identity/v3/registeredlimits/testing/requests_test.go b/openstack/identity/v3/registeredlimits/testing/requests_test.go index ad2a30cb3f..1d31d02e0b 100644 --- a/openstack/identity/v3/registeredlimits/testing/requests_test.go +++ b/openstack/identity/v3/registeredlimits/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestListRegisteredLimits(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListRegisteredLimitsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListRegisteredLimitsSuccessfully(t, fakeServer) count := 0 - err := registeredlimits.List(client.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := registeredlimits.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := registeredlimits.ExtractRegisteredLimits(page) @@ -27,15 +27,15 @@ func TestListRegisteredLimits(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListRegisteredLimitsAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListRegisteredLimitsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListRegisteredLimitsSuccessfully(t, fakeServer) - allPages, err := registeredlimits.List(client.ServiceClient(), nil).AllPages(context.TODO()) + allPages, err := registeredlimits.List(client.ServiceClient(fakeServer), nil).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := registeredlimits.ExtractRegisteredLimits(allPages) th.AssertNoErr(t, err) @@ -43,9 +43,9 @@ func TestListRegisteredLimitsAllPages(t *testing.T) { } func TestCreateRegisteredLimits(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateRegisteredLimitSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateRegisteredLimitSuccessfully(t, fakeServer) createOpts := registeredlimits.BatchCreateOpts{ registeredlimits.CreateOpts{ @@ -63,34 +63,34 @@ func TestCreateRegisteredLimits(t *testing.T) { }, } - actual, err := registeredlimits.BatchCreate(context.TODO(), client.ServiceClient(), createOpts).Extract() + actual, err := registeredlimits.BatchCreate(context.TODO(), client.ServiceClient(fakeServer), createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, ExpectedRegisteredLimitsSlice, actual) } func TestGetRegisteredLimit(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetRegisteredLimitSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetRegisteredLimitSuccessfully(t, fakeServer) - actual, err := registeredlimits.Get(context.TODO(), client.ServiceClient(), "3229b3849f584faea483d6851f7aab05").Extract() + actual, err := registeredlimits.Get(context.TODO(), client.ServiceClient(fakeServer), "3229b3849f584faea483d6851f7aab05").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondRegisteredLimit, *actual) } func TestDeleteRegisteredLimit(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteRegisteredLimitSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteRegisteredLimitSuccessfully(t, fakeServer) - res := registeredlimits.Delete(context.TODO(), client.ServiceClient(), "3229b3849f584faea483d6851f7aab05") + res := registeredlimits.Delete(context.TODO(), client.ServiceClient(fakeServer), "3229b3849f584faea483d6851f7aab05") th.AssertNoErr(t, res.Err) } func TestUpdateRegisteredLimit(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateRegisteredLimitSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateRegisteredLimitSuccessfully(t, fakeServer) defaultLimit := 15 updateOpts := registeredlimits.UpdateOpts{ @@ -99,7 +99,7 @@ func TestUpdateRegisteredLimit(t *testing.T) { DefaultLimit: &defaultLimit, } - actual, err := registeredlimits.Update(context.TODO(), client.ServiceClient(), "3229b3849f584faea483d6851f7aab05", updateOpts).Extract() + actual, err := registeredlimits.Update(context.TODO(), client.ServiceClient(fakeServer), "3229b3849f584faea483d6851f7aab05", updateOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, UpdatedSecondRegisteredLimit, *actual) } diff --git a/openstack/identity/v3/roles/requests.go b/openstack/identity/v3/roles/requests.go index 2e40e93d6d..260aa153ce 100644 --- a/openstack/identity/v3/roles/requests.go +++ b/openstack/identity/v3/roles/requests.go @@ -9,6 +9,14 @@ import ( "github.com/gophercloud/gophercloud/v2/pagination" ) +// Option is a specific option defined at the API to enable features +// on a role. +type Option string + +const ( + Immutable Option = "immutable" +) + // ListOptsBuilder allows extensions to add additional parameters to // the List request type ListOptsBuilder interface { @@ -86,8 +94,14 @@ type CreateOpts struct { // DomainID is the ID of the domain the role belongs to. DomainID string `json:"domain_id,omitempty"` + // Description is the description of the new role. + Description string `json:"description,omitempty"` + // Extra is free-form extra key/value pairs to describe the role. Extra map[string]any `json:"-"` + + // Options are defined options in the API to enable certain features. + Options map[Option]any `json:"options,omitempty"` } // ToRoleCreateMap formats a CreateOpts into a create request. @@ -130,11 +144,17 @@ type UpdateOptsBuilder interface { // UpdateOpts provides options for updating a role. type UpdateOpts struct { - // Name is the name of the new role. + // Name is an updated name for the role. Name string `json:"name,omitempty"` + // Description is an updated description for the role. + Description *string `json:"description,omitempty"` + // Extra is free-form extra key/value pairs to describe the role. Extra map[string]any `json:"-"` + + // Options are defined options in the API to enable certain features. + Options map[Option]any `json:"options,omitempty"` } // ToRoleUpdateMap formats a UpdateOpts into an update request. diff --git a/openstack/identity/v3/roles/results.go b/openstack/identity/v3/roles/results.go index 075e3cbe73..cf9d1cb99e 100644 --- a/openstack/identity/v3/roles/results.go +++ b/openstack/identity/v3/roles/results.go @@ -21,8 +21,14 @@ type Role struct { // Name is the role name Name string `json:"name"` + // Description is the description of the role. + Description string `json:"description"` + // Extra is a collection of miscellaneous key/values. Extra map[string]any `json:"-"` + + // Options are a set of defined options that allow certain features for a role + Options map[Option]any `json:"options"` } func (r *Role) UnmarshalJSON(b []byte) error { @@ -99,7 +105,7 @@ func (r RolePage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (r RolePage) NextPageURL() (string, error) { +func (r RolePage) NextPageURL(endpointURL string) (string, error) { var s struct { Links struct { Next string `json:"next"` @@ -196,7 +202,7 @@ func (r RoleAssignmentPage) IsEmpty() (bool, error) { // NextPageURL uses the response's embedded link reference to navigate to // the next page of results. -func (r RoleAssignmentPage) NextPageURL() (string, error) { +func (r RoleAssignmentPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links struct { Next string `json:"next"` diff --git a/openstack/identity/v3/roles/testing/fixtures_test.go b/openstack/identity/v3/roles/testing/fixtures_test.go index 755f14d4e8..7cb374d2d7 100644 --- a/openstack/identity/v3/roles/testing/fixtures_test.go +++ b/openstack/identity/v3/roles/testing/fixtures_test.go @@ -7,7 +7,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) // ListOutput provides a single page of Role results. @@ -34,8 +34,9 @@ const ListOutput = ` "self": "https://example.com/identity/v3/roles/9fe1d3" }, "name": "support", + "description": "read-only support role", "extra": { - "description": "read-only support role" + "test": "this is for the test" } } ] @@ -52,9 +53,31 @@ const GetOutput = ` "self": "https://example.com/identity/v3/roles/9fe1d3" }, "name": "support", - "extra": { - "description": "read-only support role" - } + "description": "read-only support role", + "extra": { + "test": "this is for the test" + } + } +} +` + +// GetOutputWithOptions provides a Get result of a role with options. +const GetOutputWithOptions = ` +{ + "role": { + "domain_id": "1789d1", + "id": "9fe1d3", + "links": { + "self": "https://example.com/identity/v3/roles/9fe1d3" + }, + "name": "support", + "description": "read-only support role", + "options": { + "immutable": true + }, + "extra": { + "test": "this is for the test" + } } } ` @@ -65,8 +88,24 @@ const CreateRequest = ` "role": { "domain_id": "1789d1", "name": "support", - "description": "read-only support role" - } + "description": "read-only support role", + "test": "this is for the test" + } +} +` + +// CreateRequestWithOptions provides the input to a Create request with Options. +const CreateRequestWithOptions = ` +{ + "role": { + "domain_id": "1789d1", + "name": "support", + "description": "read-only support role", + "test": "this is for the test", + "options": { + "immutable": true + } + } } ` @@ -74,7 +113,10 @@ const CreateRequest = ` const UpdateRequest = ` { "role": { - "description": "admin read-only support role" + "description": "admin read-only support role", + "options": { + "immutable": false + } } } ` @@ -91,7 +133,10 @@ const UpdateOutput = ` "name": "support", "extra": { "description": "admin read-only support role" - } + }, + "options": { + "immutable": false + } } } ` @@ -318,9 +363,27 @@ var SecondRole = roles.Role{ Links: map[string]any{ "self": "https://example.com/identity/v3/roles/9fe1d3", }, - Name: "support", + Name: "support", + Description: "read-only support role", Extra: map[string]any{ - "description": "read-only support role", + "test": "this is for the test", + }, +} + +// SecondRoleWithOptions is the second role in the List request +var SecondRoleWithOptions = roles.Role{ + DomainID: "1789d1", + ID: "9fe1d3", + Links: map[string]any{ + "self": "https://example.com/identity/v3/roles/9fe1d3", + }, + Name: "support", + Description: "read-only support role", + Extra: map[string]any{ + "test": "this is for the test", + }, + Options: map[roles.Option]any{ + roles.Immutable: true, }, } @@ -335,6 +398,9 @@ var SecondRoleUpdated = roles.Role{ Extra: map[string]any{ "description": "admin read-only support role", }, + Options: map[roles.Option]any{ + roles.Immutable: false, + }, } // ExpectedRolesSlice is the slice of roles expected to be returned from ListOutput. @@ -342,117 +408,130 @@ var ExpectedRolesSlice = []roles.Role{FirstRole, SecondRole} // HandleListRolesSuccessfully creates an HTTP handler at `/roles` on the // test handler mux that responds with a list of two roles. -func HandleListRolesSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/roles", func(w http.ResponseWriter, r *http.Request) { +func HandleListRolesSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/roles", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } // HandleGetRoleSuccessfully creates an HTTP handler at `/roles` on the // test handler mux that responds with a single role. -func HandleGetRoleSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/roles/9fe1d3", func(w http.ResponseWriter, r *http.Request) { +func HandleGetRoleSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/roles/9fe1d3", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleCreateRoleSuccessfully creates an HTTP handler at `/roles` on the // test handler mux that tests role creation. -func HandleCreateRoleSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/roles", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateRoleSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/roles", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) + }) +} + +// HandleCreateRoleWithOptionsSuccessfully creates an HTTP handler at `/roles` on the +// test handler mux that tests role creation. +func HandleCreateWithOptionsRoleSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/roles", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, CreateRequestWithOptions) + + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, GetOutputWithOptions) }) } // HandleUpdateRoleSuccessfully creates an HTTP handler at `/roles` on the // test handler mux that tests role update. -func HandleUpdateRoleSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/roles/9fe1d3", func(w http.ResponseWriter, r *http.Request) { +func HandleUpdateRoleSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/roles/9fe1d3", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }) } // HandleDeleteRoleSuccessfully creates an HTTP handler at `/roles` on the // test handler mux that tests role deletion. -func HandleDeleteRoleSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/roles/9fe1d3", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteRoleSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/roles/9fe1d3", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) } -func HandleAssignSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/projects/{project_id}/users/{user_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { +func HandleAssignSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/projects/{project_id}/users/{user_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) - th.Mux.HandleFunc("/projects/{project_id}/groups/{group_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/projects/{project_id}/groups/{group_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) - th.Mux.HandleFunc("/domains/{domain_id}/users/{user_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/domains/{domain_id}/users/{user_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) - th.Mux.HandleFunc("/domains/{domain_id}/groups/{group_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/domains/{domain_id}/groups/{group_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) } -func HandleUnassignSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/projects/{project_id}/users/{user_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { +func HandleUnassignSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/projects/{project_id}/users/{user_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) - th.Mux.HandleFunc("/projects/{project_id}/groups/{group_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/projects/{project_id}/groups/{group_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) - th.Mux.HandleFunc("/domains/{domain_id}/users/{user_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/domains/{domain_id}/users/{user_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) - th.Mux.HandleFunc("/domains/{domain_id}/groups/{group_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/domains/{domain_id}/groups/{group_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) } @@ -491,45 +570,45 @@ var ExpectedRoleAssignmentsWithNamesSlice = []roles.RoleAssignment{ThirdRoleAssi // HandleListRoleAssignmentsSuccessfully creates an HTTP handler at `/role_assignments` on the // test handler mux that responds with a list of two role assignments. -func HandleListRoleAssignmentsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/role_assignments", func(w http.ResponseWriter, r *http.Request) { +func HandleListRoleAssignmentsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/role_assignments", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListAssignmentOutput) + fmt.Fprint(w, ListAssignmentOutput) }) } // HandleListRoleAssignmentsSuccessfully creates an HTTP handler at `/role_assignments` on the // test handler mux that responds with a list of two role assignments. -func HandleListRoleAssignmentsWithNamesSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/role_assignments", func(w http.ResponseWriter, r *http.Request) { +func HandleListRoleAssignmentsWithNamesSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/role_assignments", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.AssertEquals(t, "include_names=true", r.URL.RawQuery) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListAssignmentWithNamesOutput) + fmt.Fprint(w, ListAssignmentWithNamesOutput) }) } // HandleListRoleAssignmentsWithSubtreeSuccessfully creates an HTTP handler at `/role_assignments` on the // test handler mux that responds with a list of two role assignments. -func HandleListRoleAssignmentsWithSubtreeSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/role_assignments", func(w http.ResponseWriter, r *http.Request) { +func HandleListRoleAssignmentsWithSubtreeSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/role_assignments", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.AssertEquals(t, "include_subtree=true", r.URL.RawQuery) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListAssignmentOutput) + fmt.Fprint(w, ListAssignmentOutput) }) } @@ -549,60 +628,60 @@ var RoleOnResource = roles.Role{ // from ListAssignmentsOnResourceOutput. var ExpectedRolesOnResourceSlice = []roles.Role{RoleOnResource} -func HandleListAssignmentsOnResourceSuccessfully_ProjectsUsers(t *testing.T) { +func HandleListAssignmentsOnResourceSuccessfully_ProjectsUsers(t *testing.T, fakeServer th.FakeServer) { fn := func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListAssignmentsOnResourceOutput) + fmt.Fprint(w, ListAssignmentsOnResourceOutput) } - th.Mux.HandleFunc("/projects/{project_id}/users/{user_id}/roles", fn) + fakeServer.Mux.HandleFunc("/projects/{project_id}/users/{user_id}/roles", fn) } -func HandleListAssignmentsOnResourceSuccessfully_ProjectsGroups(t *testing.T) { +func HandleListAssignmentsOnResourceSuccessfully_ProjectsGroups(t *testing.T, fakeServer th.FakeServer) { fn := func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListAssignmentsOnResourceOutput) + fmt.Fprint(w, ListAssignmentsOnResourceOutput) } - th.Mux.HandleFunc("/projects/{project_id}/groups/{group_id}/roles", fn) + fakeServer.Mux.HandleFunc("/projects/{project_id}/groups/{group_id}/roles", fn) } -func HandleListAssignmentsOnResourceSuccessfully_DomainsUsers(t *testing.T) { +func HandleListAssignmentsOnResourceSuccessfully_DomainsUsers(t *testing.T, fakeServer th.FakeServer) { fn := func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListAssignmentsOnResourceOutput) + fmt.Fprint(w, ListAssignmentsOnResourceOutput) } - th.Mux.HandleFunc("/domains/{domain_id}/users/{user_id}/roles", fn) + fakeServer.Mux.HandleFunc("/domains/{domain_id}/users/{user_id}/roles", fn) } -func HandleListAssignmentsOnResourceSuccessfully_DomainsGroups(t *testing.T) { +func HandleListAssignmentsOnResourceSuccessfully_DomainsGroups(t *testing.T, fakeServer th.FakeServer) { fn := func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListAssignmentsOnResourceOutput) + fmt.Fprint(w, ListAssignmentsOnResourceOutput) } - th.Mux.HandleFunc("/domains/{domain_id}/groups/{group_id}/roles", fn) + fakeServer.Mux.HandleFunc("/domains/{domain_id}/groups/{group_id}/roles", fn) } var expectedRoleInferenceRule = roles.RoleInferenceRule{ @@ -627,18 +706,18 @@ var expectedRoleInferenceRule = roles.RoleInferenceRule{ }, } -func HandleCreateRoleInferenceRule(t *testing.T) { +func HandleCreateRoleInferenceRule(t *testing.T, fakeServer th.FakeServer) { fn := func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "Accept", "application/json") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateRoleInferenceRuleOutput) + fmt.Fprint(w, CreateRoleInferenceRuleOutput) } - th.Mux.HandleFunc("/roles/7ceab6192ea34a548cc71b24f72e762c/implies/97e2f5d38bc94842bc3da818c16762ed", fn) + fakeServer.Mux.HandleFunc("/roles/7ceab6192ea34a548cc71b24f72e762c/implies/97e2f5d38bc94842bc3da818c16762ed", fn) } var expectedRoleInferenceRuleList = roles.RoleInferenceRuleList{ @@ -705,40 +784,40 @@ var expectedRoleInferenceRuleList = roles.RoleInferenceRuleList{ }, } -func HandleListRoleInferenceRules(t *testing.T) { +func HandleListRoleInferenceRules(t *testing.T, fakeServer th.FakeServer) { fn := func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListRoleInferenceRulesOutput) + fmt.Fprint(w, ListRoleInferenceRulesOutput) } - th.Mux.HandleFunc("/role_inferences", fn) + fakeServer.Mux.HandleFunc("/role_inferences", fn) } -func HandleDeleteRoleInferenceRule(t *testing.T) { +func HandleDeleteRoleInferenceRule(t *testing.T, fakeServer th.FakeServer) { fn := func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) } - th.Mux.HandleFunc("/roles/7ceab6192ea34a548cc71b24f72e762c/implies/97e2f5d38bc94842bc3da818c16762ed", fn) + fakeServer.Mux.HandleFunc("/roles/7ceab6192ea34a548cc71b24f72e762c/implies/97e2f5d38bc94842bc3da818c16762ed", fn) } -func HandleGetRoleInferenceRule(t *testing.T) { +func HandleGetRoleInferenceRule(t *testing.T, fakeServer th.FakeServer) { fn := func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, CreateRoleInferenceRuleOutput) + fmt.Fprint(w, CreateRoleInferenceRuleOutput) } - th.Mux.HandleFunc("/roles/7ceab6192ea34a548cc71b24f72e762c/implies/97e2f5d38bc94842bc3da818c16762ed", fn) + fakeServer.Mux.HandleFunc("/roles/7ceab6192ea34a548cc71b24f72e762c/implies/97e2f5d38bc94842bc3da818c16762ed", fn) } diff --git a/openstack/identity/v3/roles/testing/requests_test.go b/openstack/identity/v3/roles/testing/requests_test.go index 5268fc06c7..a70ca16856 100644 --- a/openstack/identity/v3/roles/testing/requests_test.go +++ b/openstack/identity/v3/roles/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestListRoles(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListRolesSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListRolesSuccessfully(t, fakeServer) count := 0 - err := roles.List(client.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := roles.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := roles.ExtractRoles(page) @@ -27,20 +27,20 @@ func TestListRoles(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListRolesAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListRolesSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListRolesSuccessfully(t, fakeServer) - allPages, err := roles.List(client.ServiceClient(), nil).AllPages(context.TODO()) + allPages, err := roles.List(client.ServiceClient(fakeServer), nil).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := roles.ExtractRoles(allPages) th.AssertNoErr(t, err) th.CheckDeepEquals(t, ExpectedRolesSlice, actual) - th.AssertEquals(t, ExpectedRolesSlice[1].Extra["description"], "read-only support role") + th.AssertEquals(t, "this is for the test", ExpectedRolesSlice[1].Extra["test"]) } func TestListUsersFiltersCheck(t *testing.T) { @@ -76,66 +76,92 @@ func TestListUsersFiltersCheck(t *testing.T) { } func TestGetRole(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetRoleSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetRoleSuccessfully(t, fakeServer) - actual, err := roles.Get(context.TODO(), client.ServiceClient(), "9fe1d3").Extract() + actual, err := roles.Get(context.TODO(), client.ServiceClient(fakeServer), "9fe1d3").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondRole, *actual) } func TestCreateRole(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateRoleSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateRoleSuccessfully(t, fakeServer) createOpts := roles.CreateOpts{ - Name: "support", - DomainID: "1789d1", + Name: "support", + DomainID: "1789d1", + Description: "read-only support role", Extra: map[string]any{ - "description": "read-only support role", + "test": "this is for the test", }, } - actual, err := roles.Create(context.TODO(), client.ServiceClient(), createOpts).Extract() + actual, err := roles.Create(context.TODO(), client.ServiceClient(fakeServer), createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondRole, *actual) } +func TestCreateWithOptionsRole(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateWithOptionsRoleSuccessfully(t, fakeServer) + + createOpts := roles.CreateOpts{ + Name: "support", + DomainID: "1789d1", + Description: "read-only support role", + Options: map[roles.Option]any{ + roles.Immutable: true, + }, + Extra: map[string]any{ + "test": "this is for the test", + }, + } + + actual, err := roles.Create(context.TODO(), client.ServiceClient(fakeServer), createOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, SecondRoleWithOptions, *actual) +} + func TestUpdateRole(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateRoleSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateRoleSuccessfully(t, fakeServer) updateOpts := roles.UpdateOpts{ Extra: map[string]any{ "description": "admin read-only support role", }, + Options: map[roles.Option]any{ + roles.Immutable: false, + }, } - actual, err := roles.Update(context.TODO(), client.ServiceClient(), "9fe1d3", updateOpts).Extract() + actual, err := roles.Update(context.TODO(), client.ServiceClient(fakeServer), "9fe1d3", updateOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondRoleUpdated, *actual) } func TestDeleteRole(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteRoleSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteRoleSuccessfully(t, fakeServer) - res := roles.Delete(context.TODO(), client.ServiceClient(), "9fe1d3") + res := roles.Delete(context.TODO(), client.ServiceClient(fakeServer), "9fe1d3") th.AssertNoErr(t, res.Err) } func TestListAssignmentsSinglePage(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListRoleAssignmentsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListRoleAssignmentsSuccessfully(t, fakeServer) count := 0 - err := roles.ListAssignments(client.ServiceClient(), roles.ListAssignmentsOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := roles.ListAssignments(client.ServiceClient(fakeServer), roles.ListAssignmentsOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := roles.ExtractRoleAssignments(page) th.AssertNoErr(t, err) @@ -145,13 +171,13 @@ func TestListAssignmentsSinglePage(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListAssignmentsWithNamesSinglePage(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListRoleAssignmentsWithNamesSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListRoleAssignmentsWithNamesSuccessfully(t, fakeServer) var includeNames = true listOpts := roles.ListAssignmentsOpts{ @@ -159,7 +185,7 @@ func TestListAssignmentsWithNamesSinglePage(t *testing.T) { } count := 0 - err := roles.ListAssignments(client.ServiceClient(), listOpts).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := roles.ListAssignments(client.ServiceClient(fakeServer), listOpts).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := roles.ExtractRoleAssignments(page) th.AssertNoErr(t, err) @@ -169,13 +195,13 @@ func TestListAssignmentsWithNamesSinglePage(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListAssignmentsWithSubtreeSinglePage(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListRoleAssignmentsWithSubtreeSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListRoleAssignmentsWithSubtreeSuccessfully(t, fakeServer) var includeSubtree = true listOpts := roles.ListAssignmentsOpts{ @@ -183,7 +209,7 @@ func TestListAssignmentsWithSubtreeSinglePage(t *testing.T) { } count := 0 - err := roles.ListAssignments(client.ServiceClient(), listOpts).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := roles.ListAssignments(client.ServiceClient(fakeServer), listOpts).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := roles.ExtractRoleAssignments(page) th.AssertNoErr(t, err) @@ -193,16 +219,16 @@ func TestListAssignmentsWithSubtreeSinglePage(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListAssignmentsOnResource_ProjectsUsers(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListAssignmentsOnResourceSuccessfully_ProjectsUsers(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListAssignmentsOnResourceSuccessfully_ProjectsUsers(t, fakeServer) count := 0 - err := roles.ListAssignmentsOnResource(client.ServiceClient(), roles.ListAssignmentsOnResourceOpts{ + err := roles.ListAssignmentsOnResource(client.ServiceClient(fakeServer), roles.ListAssignmentsOnResourceOpts{ UserID: "{user_id}", ProjectID: "{project_id}", }).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { @@ -215,16 +241,16 @@ func TestListAssignmentsOnResource_ProjectsUsers(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListAssignmentsOnResource_DomainsUsers(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListAssignmentsOnResourceSuccessfully_DomainsUsers(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListAssignmentsOnResourceSuccessfully_DomainsUsers(t, fakeServer) count := 0 - err := roles.ListAssignmentsOnResource(client.ServiceClient(), roles.ListAssignmentsOnResourceOpts{ + err := roles.ListAssignmentsOnResource(client.ServiceClient(fakeServer), roles.ListAssignmentsOnResourceOpts{ UserID: "{user_id}", DomainID: "{domain_id}", }).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { @@ -237,16 +263,16 @@ func TestListAssignmentsOnResource_DomainsUsers(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListAssignmentsOnResource_ProjectsGroups(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListAssignmentsOnResourceSuccessfully_ProjectsGroups(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListAssignmentsOnResourceSuccessfully_ProjectsGroups(t, fakeServer) count := 0 - err := roles.ListAssignmentsOnResource(client.ServiceClient(), roles.ListAssignmentsOnResourceOpts{ + err := roles.ListAssignmentsOnResource(client.ServiceClient(fakeServer), roles.ListAssignmentsOnResourceOpts{ GroupID: "{group_id}", ProjectID: "{project_id}", }).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { @@ -259,16 +285,16 @@ func TestListAssignmentsOnResource_ProjectsGroups(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListAssignmentsOnResource_DomainsGroups(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListAssignmentsOnResourceSuccessfully_DomainsGroups(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListAssignmentsOnResourceSuccessfully_DomainsGroups(t, fakeServer) count := 0 - err := roles.ListAssignmentsOnResource(client.ServiceClient(), roles.ListAssignmentsOnResourceOpts{ + err := roles.ListAssignmentsOnResource(client.ServiceClient(fakeServer), roles.ListAssignmentsOnResourceOpts{ GroupID: "{group_id}", DomainID: "{domain_id}", }).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { @@ -281,33 +307,33 @@ func TestListAssignmentsOnResource_DomainsGroups(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestAssign(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAssignSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAssignSuccessfully(t, fakeServer) - err := roles.Assign(context.TODO(), client.ServiceClient(), "{role_id}", roles.AssignOpts{ + err := roles.Assign(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", roles.AssignOpts{ UserID: "{user_id}", ProjectID: "{project_id}", }).ExtractErr() th.AssertNoErr(t, err) - err = roles.Assign(context.TODO(), client.ServiceClient(), "{role_id}", roles.AssignOpts{ + err = roles.Assign(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", roles.AssignOpts{ UserID: "{user_id}", DomainID: "{domain_id}", }).ExtractErr() th.AssertNoErr(t, err) - err = roles.Assign(context.TODO(), client.ServiceClient(), "{role_id}", roles.AssignOpts{ + err = roles.Assign(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", roles.AssignOpts{ GroupID: "{group_id}", ProjectID: "{project_id}", }).ExtractErr() th.AssertNoErr(t, err) - err = roles.Assign(context.TODO(), client.ServiceClient(), "{role_id}", roles.AssignOpts{ + err = roles.Assign(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", roles.AssignOpts{ GroupID: "{group_id}", DomainID: "{domain_id}", }).ExtractErr() @@ -315,29 +341,29 @@ func TestAssign(t *testing.T) { } func TestUnassign(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUnassignSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUnassignSuccessfully(t, fakeServer) - err := roles.Unassign(context.TODO(), client.ServiceClient(), "{role_id}", roles.UnassignOpts{ + err := roles.Unassign(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", roles.UnassignOpts{ UserID: "{user_id}", ProjectID: "{project_id}", }).ExtractErr() th.AssertNoErr(t, err) - err = roles.Unassign(context.TODO(), client.ServiceClient(), "{role_id}", roles.UnassignOpts{ + err = roles.Unassign(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", roles.UnassignOpts{ UserID: "{user_id}", DomainID: "{domain_id}", }).ExtractErr() th.AssertNoErr(t, err) - err = roles.Unassign(context.TODO(), client.ServiceClient(), "{role_id}", roles.UnassignOpts{ + err = roles.Unassign(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", roles.UnassignOpts{ GroupID: "{group_id}", ProjectID: "{project_id}", }).ExtractErr() th.AssertNoErr(t, err) - err = roles.Unassign(context.TODO(), client.ServiceClient(), "{role_id}", roles.UnassignOpts{ + err = roles.Unassign(context.TODO(), client.ServiceClient(fakeServer), "{role_id}", roles.UnassignOpts{ GroupID: "{group_id}", DomainID: "{domain_id}", }).ExtractErr() @@ -345,40 +371,40 @@ func TestUnassign(t *testing.T) { } func TestCreateRoleInferenceRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateRoleInferenceRule(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateRoleInferenceRule(t, fakeServer) - actual, err := roles.CreateRoleInferenceRule(context.TODO(), client.ServiceClient(), "7ceab6192ea34a548cc71b24f72e762c", "97e2f5d38bc94842bc3da818c16762ed").Extract() + actual, err := roles.CreateRoleInferenceRule(context.TODO(), client.ServiceClient(fakeServer), "7ceab6192ea34a548cc71b24f72e762c", "97e2f5d38bc94842bc3da818c16762ed").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, expectedRoleInferenceRule, *actual) } func TestListRoleInferenceRules(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListRoleInferenceRules(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListRoleInferenceRules(t, fakeServer) - actual, err := roles.ListRoleInferenceRules(context.TODO(), client.ServiceClient()).Extract() + actual, err := roles.ListRoleInferenceRules(context.TODO(), client.ServiceClient(fakeServer)).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, expectedRoleInferenceRuleList, *actual) } func TestDeleteRoleInferenceRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteRoleInferenceRule(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteRoleInferenceRule(t, fakeServer) - err := roles.DeleteRoleInferenceRule(context.TODO(), client.ServiceClient(), "7ceab6192ea34a548cc71b24f72e762c", "97e2f5d38bc94842bc3da818c16762ed").ExtractErr() + err := roles.DeleteRoleInferenceRule(context.TODO(), client.ServiceClient(fakeServer), "7ceab6192ea34a548cc71b24f72e762c", "97e2f5d38bc94842bc3da818c16762ed").ExtractErr() th.AssertNoErr(t, err) } func TestGetInferenceRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetRoleInferenceRule(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetRoleInferenceRule(t, fakeServer) - actual, err := roles.GetRoleInferenceRule(context.TODO(), client.ServiceClient(), "7ceab6192ea34a548cc71b24f72e762c", "97e2f5d38bc94842bc3da818c16762ed").Extract() + actual, err := roles.GetRoleInferenceRule(context.TODO(), client.ServiceClient(fakeServer), "7ceab6192ea34a548cc71b24f72e762c", "97e2f5d38bc94842bc3da818c16762ed").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, expectedRoleInferenceRule, *actual) } diff --git a/openstack/identity/v3/services/doc.go b/openstack/identity/v3/services/doc.go index 3d980b4349..51659f9fa7 100644 --- a/openstack/identity/v3/services/doc.go +++ b/openstack/identity/v3/services/doc.go @@ -41,7 +41,7 @@ Example to Update a Service serviceID := "3c7bbe9a6ecb453ca1789586291380ed" - var iFalse bool = false + var iFalse = false updateOpts := services.UpdateOpts{ Enabled: &iFalse, Extra: map[string]any{ diff --git a/openstack/identity/v3/services/requests.go b/openstack/identity/v3/services/requests.go index c1e07908ad..0a25c18179 100644 --- a/openstack/identity/v3/services/requests.go +++ b/openstack/identity/v3/services/requests.go @@ -15,6 +15,12 @@ type CreateOptsBuilder interface { // CreateOpts provides options used to create a service. type CreateOpts struct { + // Name is the name of the service. + Name string `json:"name,omitempty"` + + // Description is the description of the service. + Description string `json:"description,omitempty"` + // Type is the type of the service. Type string `json:"type"` @@ -108,8 +114,14 @@ type UpdateOptsBuilder interface { // UpdateOpts provides options for updating a service. type UpdateOpts struct { + // Name is an updated name for the service. + Name *string `json:"name,omitempty"` + + // Description is an update description for the service. + Description *string `json:"description,omitempty"` + // Type is the type of the service. - Type string `json:"type"` + Type string `json:"type,omitempty"` // Enabled is whether or not the service is enabled. Enabled *bool `json:"enabled,omitempty"` diff --git a/openstack/identity/v3/services/results.go b/openstack/identity/v3/services/results.go index 8309065000..576370fc6d 100644 --- a/openstack/identity/v3/services/results.go +++ b/openstack/identity/v3/services/results.go @@ -50,6 +50,12 @@ type Service struct { // ID is the unique ID of the service. ID string `json:"id"` + // Name is the name of the service. + Name string `json:"name"` + + // Description is the description of the service. + Description string `json:"description"` + // Type is the type of the service. Type string `json:"type"` @@ -75,8 +81,6 @@ func (r *Service) UnmarshalJSON(b []byte) error { } *r = Service(s.tmp) - // Collect other fields and bundle them into Extra - // but only if a field titled "extra" wasn't sent. if s.Extra != nil { r.Extra = s.Extra } else { @@ -85,8 +89,19 @@ func (r *Service) UnmarshalJSON(b []byte) error { if err != nil { return err } + if resultMap, ok := result.(map[string]any); ok { r.Extra = gophercloud.RemainingKeys(Service{}, resultMap) + + // the following code is required for backward compatibility with the + // old behavior, when both description and name were in extra + if description, ok := resultMap["description"]; ok { + r.Extra["description"] = description + } + + if name, ok := resultMap["name"]; ok { + r.Extra["name"] = name + } } } @@ -109,7 +124,7 @@ func (p ServicePage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (r ServicePage) NextPageURL() (string, error) { +func (r ServicePage) NextPageURL(endpointURL string) (string, error) { var s struct { Links struct { Next string `json:"next"` diff --git a/openstack/identity/v3/services/testing/fixtures_test.go b/openstack/identity/v3/services/testing/fixtures_test.go index da713482f8..42811a09d6 100644 --- a/openstack/identity/v3/services/testing/fixtures_test.go +++ b/openstack/identity/v3/services/testing/fixtures_test.go @@ -23,11 +23,11 @@ const ListOutput = ` "links": { "self": "https://example.com/identity/v3/services/1234" }, + "name": "service-one", "type": "identity", "enabled": false, "extra": { - "name": "service-one", - "description": "Service One" + "email": "service-one@example.com" } }, { @@ -35,11 +35,11 @@ const ListOutput = ` "links": { "self": "https://example.com/identity/v3/services/9876" }, + "name": "service-two", + "description": "Service Two", "type": "compute", "enabled": false, "extra": { - "name": "service-two", - "description": "Service Two", "email": "service@example.com" } } @@ -55,11 +55,11 @@ const GetOutput = ` "links": { "self": "https://example.com/identity/v3/services/9876" }, + "name": "service-two", + "description": "Service Two", "type": "compute", "enabled": false, "extra": { - "name": "service-two", - "description": "Service Two", "email": "service@example.com" } } @@ -96,11 +96,11 @@ const UpdateOutput = ` "links": { "self": "https://example.com/identity/v3/services/9876" }, + "name": "service-two", + "description": "Service Two Updated", "type": "compute2", "enabled": false, "extra": { - "name": "service-two", - "description": "Service Two Updated", "email": "service@example.com" } } @@ -113,11 +113,11 @@ var FirstService = services.Service{ Links: map[string]any{ "self": "https://example.com/identity/v3/services/1234", }, + Name: "service-one", Type: "identity", Enabled: false, Extra: map[string]any{ - "name": "service-one", - "description": "Service One", + "email": "service-one@example.com", }, } @@ -127,12 +127,12 @@ var SecondService = services.Service{ Links: map[string]any{ "self": "https://example.com/identity/v3/services/9876", }, - Type: "compute", - Enabled: false, + Name: "service-two", + Description: "Service Two", + Type: "compute", + Enabled: false, Extra: map[string]any{ - "name": "service-two", - "description": "Service Two", - "email": "service@example.com", + "email": "service@example.com", }, } @@ -142,12 +142,12 @@ var SecondServiceUpdated = services.Service{ Links: map[string]any{ "self": "https://example.com/identity/v3/services/9876", }, - Type: "compute2", - Enabled: false, + Name: "service-two", + Description: "Service Two Updated", + Type: "compute2", + Enabled: false, Extra: map[string]any{ - "name": "service-two", - "description": "Service Two Updated", - "email": "service@example.com", + "email": "service@example.com", }, } @@ -156,54 +156,54 @@ var ExpectedServicesSlice = []services.Service{FirstService, SecondService} // HandleListServicesSuccessfully creates an HTTP handler at `/services` on the // test handler mux that responds with a list of two services. -func HandleListServicesSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/services", func(w http.ResponseWriter, r *http.Request) { +func HandleListServicesSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/services", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } // HandleGetServiceSuccessfully creates an HTTP handler at `/services` on the // test handler mux that responds with a single service. -func HandleGetServiceSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/services/9876", func(w http.ResponseWriter, r *http.Request) { +func HandleGetServiceSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/services/9876", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleCreateServiceSuccessfully creates an HTTP handler at `/services` on the // test handler mux that tests service creation. -func HandleCreateServiceSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/services", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateServiceSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/services", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleUpdateServiceSuccessfully creates an HTTP handler at `/services` on the // test handler mux that tests service update. -func HandleUpdateServiceSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/services/9876", func(w http.ResponseWriter, r *http.Request) { +func HandleUpdateServiceSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/services/9876", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }) } diff --git a/openstack/identity/v3/services/testing/requests_test.go b/openstack/identity/v3/services/testing/requests_test.go index 77a212654e..1f1f53c720 100644 --- a/openstack/identity/v3/services/testing/requests_test.go +++ b/openstack/identity/v3/services/testing/requests_test.go @@ -12,31 +12,31 @@ import ( ) func TestCreateSuccessful(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateServiceSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateServiceSuccessfully(t, fakeServer) createOpts := services.CreateOpts{ - Type: "compute", + Name: "service-two", + Description: "Service Two", + Type: "compute", Extra: map[string]any{ - "name": "service-two", - "description": "Service Two", - "email": "service@example.com", + "email": "service@example.com", }, } - actual, err := services.Create(context.TODO(), client.ServiceClient(), createOpts).Extract() + actual, err := services.Create(context.TODO(), client.ServiceClient(fakeServer), createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondService, *actual) } func TestListServices(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListServicesSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListServicesSuccessfully(t, fakeServer) count := 0 - err := services.List(client.ServiceClient(), services.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := services.List(client.ServiceClient(fakeServer), services.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := services.ExtractServices(page) @@ -47,62 +47,61 @@ func TestListServices(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListServicesAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListServicesSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListServicesSuccessfully(t, fakeServer) - allPages, err := services.List(client.ServiceClient(), nil).AllPages(context.TODO()) + allPages, err := services.List(client.ServiceClient(fakeServer), nil).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := services.ExtractServices(allPages) th.AssertNoErr(t, err) th.CheckDeepEquals(t, ExpectedServicesSlice, actual) - th.AssertEquals(t, ExpectedServicesSlice[0].Extra["name"], "service-one") - th.AssertEquals(t, ExpectedServicesSlice[1].Extra["email"], "service@example.com") + th.AssertEquals(t, "service-one@example.com", ExpectedServicesSlice[0].Extra["email"]) + th.AssertEquals(t, "service@example.com", ExpectedServicesSlice[1].Extra["email"]) } func TestGetSuccessful(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetServiceSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetServiceSuccessfully(t, fakeServer) - actual, err := services.Get(context.TODO(), client.ServiceClient(), "9876").Extract() + actual, err := services.Get(context.TODO(), client.ServiceClient(fakeServer), "9876").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondService, *actual) - th.AssertEquals(t, SecondService.Extra["email"], "service@example.com") + th.AssertEquals(t, "service@example.com", SecondService.Extra["email"]) } func TestUpdateSuccessful(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateServiceSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateServiceSuccessfully(t, fakeServer) + updatedDescription := "Service Two Updated" updateOpts := services.UpdateOpts{ - Type: "compute2", - Extra: map[string]any{ - "description": "Service Two Updated", - }, + Description: &updatedDescription, + Type: "compute2", } - actual, err := services.Update(context.TODO(), client.ServiceClient(), "9876", updateOpts).Extract() + actual, err := services.Update(context.TODO(), client.ServiceClient(fakeServer), "9876", updateOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondServiceUpdated, *actual) - th.AssertEquals(t, SecondServiceUpdated.Extra["description"], "Service Two Updated") + th.AssertEquals(t, "Service Two Updated", SecondServiceUpdated.Description) } func TestDeleteSuccessful(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/services/12345", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/services/12345", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := services.Delete(context.TODO(), client.ServiceClient(), "12345") + res := services.Delete(context.TODO(), client.ServiceClient(fakeServer), "12345") th.AssertNoErr(t, res.Err) } diff --git a/openstack/identity/v3/tokens/doc.go b/openstack/identity/v3/tokens/doc.go index c711d0c28e..597d781dce 100644 --- a/openstack/identity/v3/tokens/doc.go +++ b/openstack/identity/v3/tokens/doc.go @@ -103,5 +103,53 @@ Example to Create a Token from a Username and Password with Project Name Scope if err != nil { panic(err) } + +Example to Get a Token + + token, err := tokens.Get(context.TODO(), identityClient, "token_id", nil).ExtractToken() + if err != nil { + panic(err) + } + +# Example to Get a Token Created with Application Credentials Access Rules + +When validating or retrieving tokens that were created using application +credentials with access rules, the OpenStack-Identity-Access-Rules header +must be sent. Without this header, Keystone will return a 404 Not Found error. + +See https://docs.openstack.org/keystone/latest/user/application_credentials.html + + getOpts := tokens.GetOpts{ + AccessRulesVersion: "1.0", + } + token, err := tokens.Get(context.TODO(), identityClient, "token_id", getOpts).ExtractToken() + if err != nil { + panic(err) + } + +Example to Validate a Token + + ok, err := tokens.Validate(context.TODO(), identityClient, "token_id", nil) + if err != nil { + panic(err) + } + + if ok { + fmt.Println("Token is valid") + } + +Example to Validate a Token Created with Application Credentials Access Rules + + validateOpts := tokens.ValidateOpts{ + AccessRulesVersion: "1.0", + } + ok, err := tokens.Validate(context.TODO(), identityClient, "token_id", validateOpts) + if err != nil { + panic(err) + } + + if ok { + fmt.Println("Token is valid") + } */ package tokens diff --git a/openstack/identity/v3/tokens/requests.go b/openstack/identity/v3/tokens/requests.go index fa8b925d04..40b41e44db 100644 --- a/openstack/identity/v3/tokens/requests.go +++ b/openstack/identity/v3/tokens/requests.go @@ -2,10 +2,13 @@ package tokens import ( "context" + "maps" "github.com/gophercloud/gophercloud/v2" ) +const xSubjectTokenHeader = "X-Subject-Token" + // Scope allows a created token to be limited to a specific domain or project. type Scope struct { ProjectID string @@ -118,12 +121,6 @@ func (opts *AuthOptions) ToTokenV3HeadersMap(map[string]any) (map[string]string, return nil, nil } -func subjectTokenHeaders(subjectToken string) map[string]string { - return map[string]string{ - "X-Subject-Token": subjectToken, - } -} - // Create authenticates and either generates a new token, or changes the Scope // of an existing token. func Create(ctx context.Context, c *gophercloud.ServiceClient, opts AuthOptionsBuilder) (r CreateResult) { @@ -146,20 +143,79 @@ func Create(ctx context.Context, c *gophercloud.ServiceClient, opts AuthOptionsB return } +// GetOptsBuilder allows extensions to add additional parameters to +// the Get request. +type GetOptsBuilder interface { + ToTokenGetParams() (map[string]string, error) +} + +// GetOpts provides options for the Get request. +type GetOpts struct { + // AccessRulesVersion specifies the OpenStack-Identity-Access-Rules header + // version. This is required when getting tokens that were created using + // application credentials with access rules. Versions less than 1 will cause + // Keystone to ignore access rules validation. + AccessRulesVersion string `h:"OpenStack-Identity-Access-Rules"` +} + +// ToTokenGetParams formats GetOpts into request headers. +func (opts GetOpts) ToTokenGetParams() (map[string]string, error) { + return gophercloud.BuildHeaders(opts) +} + // Get validates and retrieves information about another token. -func Get(ctx context.Context, c *gophercloud.ServiceClient, token string) (r GetResult) { +func Get(ctx context.Context, c *gophercloud.ServiceClient, token string, opts GetOptsBuilder) (r GetResult) { + h := map[string]string{xSubjectTokenHeader: token} + if opts != nil { + b, err := opts.ToTokenGetParams() + if err != nil { + r.Err = err + return + } + maps.Copy(h, b) + } + resp, err := c.Get(ctx, tokenURL(c), &r.Body, &gophercloud.RequestOpts{ - MoreHeaders: subjectTokenHeaders(token), + MoreHeaders: h, OkCodes: []int{200, 203}, }) _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } +// ValidateOptsBuilder allows extensions to add additional parameters to +// the Validate request. +type ValidateOptsBuilder interface { + ToTokenValidateParams() (map[string]string, error) +} + +// ValidateOpts provides options for the Validate request. +type ValidateOpts struct { + // AccessRulesVersion specifies the OpenStack-Identity-Access-Rules header + // version. This is required when validating tokens that were created using + // application credentials with access rules. Versions less than 1 will cause + // Keystone to ignore access rules validation. + AccessRulesVersion string `h:"OpenStack-Identity-Access-Rules"` +} + +// ToTokenValidateParams formats ValidateOpts into request headers. +func (opts ValidateOpts) ToTokenValidateParams() (map[string]string, error) { + return gophercloud.BuildHeaders(opts) +} + // Validate determines if a specified token is valid or not. -func Validate(ctx context.Context, c *gophercloud.ServiceClient, token string) (bool, error) { +func Validate(ctx context.Context, c *gophercloud.ServiceClient, token string, opts ValidateOptsBuilder) (bool, error) { + h := map[string]string{xSubjectTokenHeader: token} + if opts != nil { + b, err := opts.ToTokenValidateParams() + if err != nil { + return false, err + } + maps.Copy(h, b) + } + resp, err := c.Head(ctx, tokenURL(c), &gophercloud.RequestOpts{ - MoreHeaders: subjectTokenHeaders(token), + MoreHeaders: h, OkCodes: []int{200, 204, 404}, }) if err != nil { @@ -172,7 +228,7 @@ func Validate(ctx context.Context, c *gophercloud.ServiceClient, token string) ( // Revoke immediately makes specified token invalid. func Revoke(ctx context.Context, c *gophercloud.ServiceClient, token string) (r RevokeResult) { resp, err := c.Delete(ctx, tokenURL(c), &gophercloud.RequestOpts{ - MoreHeaders: subjectTokenHeaders(token), + MoreHeaders: map[string]string{xSubjectTokenHeader: token}, }) _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return diff --git a/openstack/identity/v3/tokens/results.go b/openstack/identity/v3/tokens/results.go index bd61a9a67d..1ec1ce4001 100644 --- a/openstack/identity/v3/tokens/results.go +++ b/openstack/identity/v3/tokens/results.go @@ -88,6 +88,33 @@ type Trust struct { TrustorUserID TrustUser `json:"trustor_user"` } +// AccessRule represents an access rule for an application credential. +type AccessRule struct { + // The ID of the access rule. + ID string `json:"id"` + // The API path that the application credential is permitted to access. + Path string `json:"path"` + // The request method that the application credential is permitted to use + // for a given API endpoint. + Method string `json:"method"` + // The service type identifier for the service that the application + // credential is permitted to access. + Service string `json:"service"` +} + +// ApplicationCredential represents the application credential information +// included in a token when the token was created using an application credential. +type ApplicationCredential struct { + // The ID of the application credential. + ID string `json:"id"` + // The name of the application credential. + Name string `json:"name"` + // A flag indicating whether the application credential is restricted. + Restricted bool `json:"restricted"` + // A list of access rules for the application credential. + AccessRules []AccessRule `json:"access_rules"` +} + // commonResult is the response from a request. A commonResult has various // methods which can be used to extract different details about the result. type commonResult struct { @@ -181,6 +208,17 @@ func (r commonResult) ExtractTrust() (*Trust, error) { return s.Trust, err } +// ExtractApplicationCredential returns the ApplicationCredential that was used +// to create the token. This is only present when the token was created using +// an application credential. +func (r commonResult) ExtractApplicationCredential() (*ApplicationCredential, error) { + var s struct { + ApplicationCredential *ApplicationCredential `json:"application_credential"` + } + err := r.ExtractInto(&s) + return s.ApplicationCredential, err +} + // CreateResult is the response from a Create request. Use ExtractToken() // to interpret it as a Token, or ExtractServiceCatalog() to interpret it // as a service catalog. diff --git a/openstack/identity/v3/tokens/testing/fixtures.go b/openstack/identity/v3/tokens/testing/fixtures.go index 0b4ccafa26..f0580521cd 100644 --- a/openstack/identity/v3/tokens/testing/fixtures.go +++ b/openstack/identity/v3/tokens/testing/fixtures.go @@ -8,7 +8,7 @@ import ( "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" ) const testTokenID = "130f6c17-420e-4a0b-97b0-0c9cf2a05f30" @@ -310,7 +310,7 @@ func getGetResult(t *testing.T) tokens.GetResult { "X-Subject-Token": []string{testTokenID}, } err := json.Unmarshal([]byte(TokenOutput), &result.Body) - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) return result } @@ -320,6 +320,76 @@ func getGetDomainResult(t *testing.T) tokens.GetResult { "X-Subject-Token": []string{testTokenID}, } err := json.Unmarshal([]byte(DomainToken), &result.Body) - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) + return result +} + +// ApplicationCredentialTokenOutput is a sample response to a Token call +// using application credentials with access rules. +const ApplicationCredentialTokenOutput = ` +{ + "token":{ + "methods":["application_credential"], + "expires_at":"2017-06-03T02:19:49.000000Z", + "user":{ + "domain":{ + "id":"default", + "name":"Default" + }, + "id":"0fe36e73809d46aeae6705c39077b1b3", + "name":"admin" + }, + "application_credential":{ + "id":"d832f05beee743c696672ef65ee073ff", + "name":"test", + "restricted":true, + "access_rules":[ + { + "id":"4641f614301d48bfae1ad323e3e1c44c", + "service":"identity", + "path":"/v3/auth/tokens", + "method":"GET" + }, + { + "id":"968c404cb9b44672a574e5ee9d3db987", + "service":"identity", + "path":"/v3/**", + "method":"HEAD" + } + ] + }, + "issued_at":"2017-06-03T01:19:49.000000Z" + } +}` + +// ExpectedApplicationCredential contains expected application credential +// extracted from token response. +var ExpectedApplicationCredential = tokens.ApplicationCredential{ + ID: "d832f05beee743c696672ef65ee073ff", + Name: "test", + Restricted: true, + AccessRules: []tokens.AccessRule{ + { + ID: "4641f614301d48bfae1ad323e3e1c44c", + Service: "identity", + Path: "/v3/auth/tokens", + Method: "GET", + }, + { + ID: "968c404cb9b44672a574e5ee9d3db987", + Service: "identity", + Path: "/v3/**", + Method: "HEAD", + }, + }, +} + +func getGetApplicationCredentialResult(t *testing.T) tokens.GetResult { + result := tokens.GetResult{} + result.Header = http.Header{ + "X-Subject-Token": []string{testTokenID}, + } + err := json.Unmarshal([]byte(ApplicationCredentialTokenOutput), &result.Body) + th.AssertNoErr(t, err) return result } diff --git a/openstack/identity/v3/tokens/testing/requests_test.go b/openstack/identity/v3/tokens/testing/requests_test.go index 9a9a59fc14..d099ef11ca 100644 --- a/openstack/identity/v3/tokens/testing/requests_test.go +++ b/openstack/identity/v3/tokens/testing/requests_test.go @@ -9,28 +9,28 @@ import ( "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" "github.com/gophercloud/gophercloud/v2/testhelper/client" ) // authTokenPost verifies that providing certain AuthOptions and Scope results in an expected JSON structure. func authTokenPost(t *testing.T, options tokens.AuthOptions, scope *tokens.Scope, requestJSON string) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() client := gophercloud.ServiceClient{ ProviderClient: &gophercloud.ProviderClient{}, - Endpoint: testhelper.Endpoint(), + Endpoint: fakeServer.Endpoint(), } - testhelper.Mux.HandleFunc("/auth/tokens", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "POST") - testhelper.TestHeader(t, r, "Content-Type", "application/json") - testhelper.TestHeader(t, r, "Accept", "application/json") - testhelper.TestJSONRequest(t, r, requestJSON) + fakeServer.Mux.HandleFunc("/auth/tokens", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, requestJSON) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "token": { "expires_at": "2014-10-02T13:45:00.000000Z" } @@ -45,17 +45,17 @@ func authTokenPost(t *testing.T, options tokens.AuthOptions, scope *tokens.Scope ExpiresAt: time.Date(2014, 10, 2, 13, 45, 0, 0, time.UTC), } actual, err := tokens.Create(context.TODO(), &client, &options).Extract() - testhelper.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, expected, actual) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, expected, actual) } func authTokenPostErr(t *testing.T, options tokens.AuthOptions, scope *tokens.Scope, includeToken bool, expectedErr error) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() client := gophercloud.ServiceClient{ ProviderClient: &gophercloud.ProviderClient{}, - Endpoint: testhelper.Endpoint(), + Endpoint: fakeServer.Endpoint(), } if includeToken { client.TokenID = "abcdef123456" @@ -303,8 +303,8 @@ func TestCreateSystemScope(t *testing.T) { } func TestCreateUserIDPasswordTrustID(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() requestJSON := `{ "auth": { @@ -362,14 +362,14 @@ func TestCreateUserIDPasswordTrustID(t *testing.T) { } } }` - testhelper.Mux.HandleFunc("/auth/tokens", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "POST") - testhelper.TestHeader(t, r, "Content-Type", "application/json") - testhelper.TestHeader(t, r, "Accept", "application/json") - testhelper.TestJSONRequest(t, r, requestJSON) + fakeServer.Mux.HandleFunc("/auth/tokens", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, requestJSON) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, responseJSON) + fmt.Fprint(w, responseJSON) }) ao := gophercloud.AuthOptions{ @@ -380,21 +380,17 @@ func TestCreateUserIDPasswordTrustID(t *testing.T) { }, } - rsp := tokens.Create(context.TODO(), client.ServiceClient(), &ao) + rsp := tokens.Create(context.TODO(), client.ServiceClient(fakeServer), &ao) token, err := rsp.Extract() - if err != nil { - t.Errorf("Create returned an error: %v", err) - } + th.AssertNoErr(t, err) expectedToken := &tokens.Token{ ExpiresAt: time.Date(2024, 02, 28, 12, 10, 39, 0, time.UTC), } - testhelper.AssertDeepEquals(t, expectedToken, token) + th.AssertDeepEquals(t, expectedToken, token) trust, err := rsp.ExtractTrust() - if err != nil { - t.Errorf("ExtractTrust returned an error: %v", err) - } + th.AssertNoErr(t, err) expectedTrust := &tokens.Trust{ ID: "95946f9eef864fdc993079d8fe3e5747", Impersonation: false, @@ -405,7 +401,7 @@ func TestCreateUserIDPasswordTrustID(t *testing.T) { ID: "c88693b7c81c408e9084ac1e51082bfb", }, } - testhelper.AssertDeepEquals(t, expectedTrust, trust) + th.AssertDeepEquals(t, expectedTrust, trust) } func TestCreateApplicationCredentialIDAndSecret(t *testing.T) { @@ -513,19 +509,19 @@ func TestCreatePasswordTOTPProjectNameAndDomainNameScope(t *testing.T) { } func TestCreateExtractsTokenFromResponse(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() client := gophercloud.ServiceClient{ ProviderClient: &gophercloud.ProviderClient{}, - Endpoint: testhelper.Endpoint(), + Endpoint: fakeServer.Endpoint(), } - testhelper.Mux.HandleFunc("/auth/tokens", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/auth/tokens", func(w http.ResponseWriter, r *http.Request) { w.Header().Add("X-Subject-Token", "aaa111") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "token": { "expires_at": "2014-10-02T13:45:00.000000Z" } @@ -652,33 +648,31 @@ func TestCreateFailureEmptyScope(t *testing.T) { */ func TestGetRequest(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() client := gophercloud.ServiceClient{ ProviderClient: &gophercloud.ProviderClient{ TokenID: "12345abcdef", }, - Endpoint: testhelper.Endpoint(), + Endpoint: fakeServer.Endpoint(), } - testhelper.Mux.HandleFunc("/auth/tokens", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "GET") - testhelper.TestHeaderUnset(t, r, "Content-Type") - testhelper.TestHeader(t, r, "Accept", "application/json") - testhelper.TestHeader(t, r, "X-Auth-Token", "12345abcdef") - testhelper.TestHeader(t, r, "X-Subject-Token", "abcdef12345") + fakeServer.Mux.HandleFunc("/auth/tokens", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeaderUnset(t, r, "Content-Type") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", "12345abcdef") + th.TestHeader(t, r, "X-Subject-Token", "abcdef12345") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "token": { "expires_at": "2014-08-29T13:10:01.000000Z" } } `) }) - token, err := tokens.Get(context.TODO(), &client, "abcdef12345").Extract() - if err != nil { - t.Errorf("Info returned an error: %v", err) - } + token, err := tokens.Get(context.TODO(), &client, "abcdef12345", nil).Extract() + th.AssertNoErr(t, err) expected, _ := time.Parse(time.UnixDate, "Fri Aug 29 13:10:01 UTC 2014") if token.ExpiresAt != expected { @@ -686,20 +680,20 @@ func TestGetRequest(t *testing.T) { } } -func prepareAuthTokenHandler(t *testing.T, expectedMethod string, status int) gophercloud.ServiceClient { +func prepareAuthTokenHandler(t *testing.T, fakeServer th.FakeServer, expectedMethod string, status int) gophercloud.ServiceClient { client := gophercloud.ServiceClient{ ProviderClient: &gophercloud.ProviderClient{ TokenID: "12345abcdef", }, - Endpoint: testhelper.Endpoint(), + Endpoint: fakeServer.Endpoint(), } - testhelper.Mux.HandleFunc("/auth/tokens", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, expectedMethod) - testhelper.TestHeaderUnset(t, r, "Content-Type") - testhelper.TestHeader(t, r, "Accept", "application/json") - testhelper.TestHeader(t, r, "X-Auth-Token", "12345abcdef") - testhelper.TestHeader(t, r, "X-Subject-Token", "abcdef12345") + fakeServer.Mux.HandleFunc("/auth/tokens", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, expectedMethod) + th.TestHeaderUnset(t, r, "Content-Type") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", "12345abcdef") + th.TestHeader(t, r, "X-Subject-Token", "abcdef12345") w.WriteHeader(status) }) @@ -708,11 +702,11 @@ func prepareAuthTokenHandler(t *testing.T, expectedMethod string, status int) go } func TestValidateRequestSuccessful(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - client := prepareAuthTokenHandler(t, "HEAD", http.StatusNoContent) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + client := prepareAuthTokenHandler(t, fakeServer, "HEAD", http.StatusNoContent) - ok, err := tokens.Validate(context.TODO(), &client, "abcdef12345") + ok, err := tokens.Validate(context.TODO(), &client, "abcdef12345", nil) if err != nil { t.Errorf("Unexpected error from Validate: %v", err) } @@ -723,11 +717,11 @@ func TestValidateRequestSuccessful(t *testing.T) { } func TestValidateRequestFailure(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - client := prepareAuthTokenHandler(t, "HEAD", http.StatusNotFound) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + client := prepareAuthTokenHandler(t, fakeServer, "HEAD", http.StatusNotFound) - ok, err := tokens.Validate(context.TODO(), &client, "abcdef12345") + ok, err := tokens.Validate(context.TODO(), &client, "abcdef12345", nil) if err != nil { t.Errorf("Unexpected error from Validate: %v", err) } @@ -738,29 +732,29 @@ func TestValidateRequestFailure(t *testing.T) { } func TestValidateRequestError(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - client := prepareAuthTokenHandler(t, "HEAD", http.StatusMethodNotAllowed) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + client := prepareAuthTokenHandler(t, fakeServer, "HEAD", http.StatusMethodNotAllowed) - _, err := tokens.Validate(context.TODO(), &client, "abcdef12345") + _, err := tokens.Validate(context.TODO(), &client, "abcdef12345", nil) if err == nil { t.Errorf("Missing expected error from Validate") } } func TestRevokeRequestSuccessful(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - client := prepareAuthTokenHandler(t, "DELETE", http.StatusNoContent) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + client := prepareAuthTokenHandler(t, fakeServer, "DELETE", http.StatusNoContent) res := tokens.Revoke(context.TODO(), &client, "abcdef12345") - testhelper.AssertNoErr(t, res.Err) + th.AssertNoErr(t, res.Err) } func TestRevokeRequestError(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - client := prepareAuthTokenHandler(t, "DELETE", http.StatusNotFound) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + client := prepareAuthTokenHandler(t, fakeServer, "DELETE", http.StatusNotFound) res := tokens.Revoke(context.TODO(), &client, "abcdef12345") if res.Err == nil { @@ -768,21 +762,89 @@ func TestRevokeRequestError(t *testing.T) { } } +func TestGetRequestWithAccessRules(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + client := gophercloud.ServiceClient{ + ProviderClient: &gophercloud.ProviderClient{ + TokenID: "12345abcdef", + }, + Endpoint: fakeServer.Endpoint(), + } + + fakeServer.Mux.HandleFunc("/auth/tokens", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", "12345abcdef") + th.TestHeader(t, r, "X-Subject-Token", "abcdef12345") + th.TestHeader(t, r, "OpenStack-Identity-Access-Rules", "1") + + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, ` + { "token": { "expires_at": "2014-08-29T13:10:01.000000Z" } } + `) + }) + + getOpts := tokens.GetOpts{ + AccessRulesVersion: "1", + } + token, err := tokens.Get(context.TODO(), &client, "abcdef12345", getOpts).Extract() + th.AssertNoErr(t, err) + + expected, _ := time.Parse(time.UnixDate, "Fri Aug 29 13:10:01 UTC 2014") + if token.ExpiresAt != expected { + t.Errorf("Expected expiration time %s, but was %s", expected.Format(time.UnixDate), time.Time(token.ExpiresAt).Format(time.UnixDate)) + } +} + +func TestValidateRequestWithAccessRules(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + client := gophercloud.ServiceClient{ + ProviderClient: &gophercloud.ProviderClient{ + TokenID: "12345abcdef", + }, + Endpoint: fakeServer.Endpoint(), + } + + fakeServer.Mux.HandleFunc("/auth/tokens", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "HEAD") + th.TestHeader(t, r, "X-Auth-Token", "12345abcdef") + th.TestHeader(t, r, "X-Subject-Token", "abcdef12345") + th.TestHeader(t, r, "OpenStack-Identity-Access-Rules", "1") + + w.WriteHeader(http.StatusNoContent) + }) + + validateOpts := tokens.ValidateOpts{ + AccessRulesVersion: "1", + } + ok, err := tokens.Validate(context.TODO(), &client, "abcdef12345", validateOpts) + if err != nil { + t.Errorf("Unexpected error from Validate: %v", err) + } + + if !ok { + t.Errorf("Validate returned false for a valid token") + } +} + func TestNoTokenInResponse(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() client := gophercloud.ServiceClient{ ProviderClient: &gophercloud.ProviderClient{}, - Endpoint: testhelper.Endpoint(), + Endpoint: fakeServer.Endpoint(), } - testhelper.Mux.HandleFunc("/auth/tokens", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/auth/tokens", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) options := tokens.AuthOptions{UserID: "me", Password: "squirrel!"} _, err := tokens.Create(context.TODO(), &client, &options).Extract() - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) } diff --git a/openstack/identity/v3/tokens/testing/results_test.go b/openstack/identity/v3/tokens/testing/results_test.go index e142ac9c57..f5e4029c23 100644 --- a/openstack/identity/v3/tokens/testing/results_test.go +++ b/openstack/identity/v3/tokens/testing/results_test.go @@ -3,59 +3,79 @@ package testing import ( "testing" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" ) func TestExtractToken(t *testing.T) { result := getGetResult(t) token, err := result.ExtractToken() - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, &ExpectedToken, token) + th.CheckDeepEquals(t, &ExpectedToken, token) } func TestExtractCatalog(t *testing.T) { result := getGetResult(t) catalog, err := result.ExtractServiceCatalog() - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, &ExpectedServiceCatalog, catalog) + th.CheckDeepEquals(t, &ExpectedServiceCatalog, catalog) } func TestExtractUser(t *testing.T) { result := getGetResult(t) user, err := result.ExtractUser() - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, &ExpectedUser, user) + th.CheckDeepEquals(t, &ExpectedUser, user) } func TestExtractRoles(t *testing.T) { result := getGetResult(t) roles, err := result.ExtractRoles() - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, ExpectedRoles, roles) + th.CheckDeepEquals(t, ExpectedRoles, roles) } func TestExtractProject(t *testing.T) { result := getGetResult(t) project, err := result.ExtractProject() - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, &ExpectedProject, project) + th.CheckDeepEquals(t, &ExpectedProject, project) } func TestExtractDomain(t *testing.T) { result := getGetDomainResult(t) domain, err := result.ExtractDomain() - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, &ExpectedDomain, domain) + th.CheckDeepEquals(t, &ExpectedDomain, domain) +} + +func TestExtractApplicationCredential(t *testing.T) { + result := getGetApplicationCredentialResult(t) + + appCred, err := result.ExtractApplicationCredential() + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, &ExpectedApplicationCredential, appCred) +} + +func TestExtractApplicationCredentialNotPresent(t *testing.T) { + result := getGetResult(t) + + appCred, err := result.ExtractApplicationCredential() + th.AssertNoErr(t, err) + + if appCred != nil { + t.Errorf("Expected nil application credential, got %v", appCred) + } } diff --git a/openstack/identity/v3/trusts/results.go b/openstack/identity/v3/trusts/results.go index b09c617d97..16f68211a1 100644 --- a/openstack/identity/v3/trusts/results.go +++ b/openstack/identity/v3/trusts/results.go @@ -45,7 +45,7 @@ func (t TrustPage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (t TrustPage) NextPageURL() (string, error) { +func (t TrustPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links struct { Next string `json:"next"` @@ -122,7 +122,7 @@ func (r RolesPage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (r RolesPage) NextPageURL() (string, error) { +func (r RolesPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links struct { Next string `json:"next"` diff --git a/openstack/identity/v3/trusts/testing/fixtures_test.go b/openstack/identity/v3/trusts/testing/fixtures_test.go index 19ede76569..0962b53e0e 100644 --- a/openstack/identity/v3/trusts/testing/fixtures_test.go +++ b/openstack/identity/v3/trusts/testing/fixtures_test.go @@ -7,7 +7,7 @@ import ( "time" "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/trusts" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" "github.com/gophercloud/gophercloud/v2/testhelper/client" ) @@ -200,38 +200,38 @@ var ExpectedTrustRolesSlice = []trusts.Role{FirstRole, SecondRole} // HandleCreateTrust creates an HTTP handler at `/OS-TRUST/trusts` on the // test handler mux that tests trust creation. -func HandleCreateTrust(t *testing.T) { - testhelper.Mux.HandleFunc("/OS-TRUST/trusts", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "POST") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) - testhelper.TestJSONRequest(t, r, CreateRequest) +func HandleCreateTrust(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-TRUST/trusts", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - _, err := fmt.Fprintf(w, CreateResponse) - testhelper.AssertNoErr(t, err) + _, err := fmt.Fprint(w, CreateResponse) + th.AssertNoErr(t, err) }) } // HandleCreateTrustNoExpire creates an HTTP handler at `/OS-TRUST/trusts` on the // test handler mux that tests trust creation. -func HandleCreateTrustNoExpire(t *testing.T) { - testhelper.Mux.HandleFunc("/OS-TRUST/trusts", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "POST") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) - testhelper.TestJSONRequest(t, r, CreateRequestNoExpire) +func HandleCreateTrustNoExpire(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-TRUST/trusts", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, CreateRequestNoExpire) w.WriteHeader(http.StatusCreated) - _, err := fmt.Fprintf(w, CreateResponseNoExpire) - testhelper.AssertNoErr(t, err) + _, err := fmt.Fprint(w, CreateResponseNoExpire) + th.AssertNoErr(t, err) }) } // HandleDeleteUserSuccessfully creates an HTTP handler at `/users` on the // test handler mux that tests user deletion. -func HandleDeleteTrust(t *testing.T) { - testhelper.Mux.HandleFunc("/OS-TRUST/trusts/3422b7c113894f5d90665e1a79655e23", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "DELETE") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandleDeleteTrust(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-TRUST/trusts/3422b7c113894f5d90665e1a79655e23", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) @@ -239,15 +239,15 @@ func HandleDeleteTrust(t *testing.T) { // HandleGetTrustSuccessfully creates an HTTP handler at `/OS-TRUST/trusts` on the // test handler mux that responds with a single trusts. -func HandleGetTrustSuccessfully(t *testing.T) { - testhelper.Mux.HandleFunc("/OS-TRUST/trusts/987fe8", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "GET") - testhelper.TestHeader(t, r, "Accept", "application/json") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandleGetTrustSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-TRUST/trusts/987fe8", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) } @@ -309,53 +309,53 @@ var ExpectedTrustsSlice = []trusts.Trust{FirstTrust, SecondTrust} // HandleListTrustsSuccessfully creates an HTTP handler at `/OS-TRUST/trusts` on the // test handler mux that responds with a list of two trusts. -func HandleListTrustsSuccessfully(t *testing.T) { - testhelper.Mux.HandleFunc("/OS-TRUST/trusts", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "GET") - testhelper.TestHeader(t, r, "Accept", "application/json") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandleListTrustsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-TRUST/trusts", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) } // HandleListTrustRolesSuccessfully creates an HTTP handler at `/OS-TRUST/trusts/987fe8/roles` on the // test handler mux that responds with a list trust roles. -func HandleListTrustRolesSuccessfully(t *testing.T) { - testhelper.Mux.HandleFunc("/OS-TRUST/trusts/987fe8/roles", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "GET") - testhelper.TestHeader(t, r, "Accept", "application/json") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandleListTrustRolesSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-TRUST/trusts/987fe8/roles", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListTrustRolesResponse) + fmt.Fprint(w, ListTrustRolesResponse) }) } // HandleGetTrustRoleSuccessfully creates an HTTP handler at `/OS-TRUST/trusts/987fe8/roles/c1648e` on the // test handler mux that responds with a trust role details. -func HandleGetTrustRoleSuccessfully(t *testing.T) { - testhelper.Mux.HandleFunc("/OS-TRUST/trusts/987fe8/roles/c1648e", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "GET") - testhelper.TestHeader(t, r, "Accept", "application/json") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandleGetTrustRoleSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-TRUST/trusts/987fe8/roles/c1648e", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetTrustRoleResponse) + fmt.Fprint(w, GetTrustRoleResponse) }) } // HandleCheckTrustRoleSuccessfully creates an HTTP handler at `/OS-TRUST/trusts/987fe8/roles/c1648e` on the // test handler mux that responds with a list trust roles. -func HandleCheckTrustRoleSuccessfully(t *testing.T) { - testhelper.Mux.HandleFunc("/OS-TRUST/trusts/987fe8/roles/c1648e", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "HEAD") - testhelper.TestHeader(t, r, "Accept", "application/json") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandleCheckTrustRoleSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/OS-TRUST/trusts/987fe8/roles/c1648e", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "HEAD") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) }) diff --git a/openstack/identity/v3/trusts/testing/requests_test.go b/openstack/identity/v3/trusts/testing/requests_test.go index 0b2d8149cb..30bde153b4 100644 --- a/openstack/identity/v3/trusts/testing/requests_test.go +++ b/openstack/identity/v3/trusts/testing/requests_test.go @@ -12,12 +12,12 @@ import ( ) func TestCreateTrust(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateTrust(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateTrust(t, fakeServer) expiresAt := time.Date(2019, 12, 1, 14, 0, 0, 0, time.UTC) - result, err := trusts.Create(context.TODO(), client.ServiceClient(), trusts.CreateOpts{ + result, err := trusts.Create(context.TODO(), client.ServiceClient(fakeServer), trusts.CreateOpts{ ExpiresAt: &expiresAt, AllowRedelegation: true, ProjectID: "9b71012f5a4a4aef9193f1995fe159b2", @@ -35,11 +35,11 @@ func TestCreateTrust(t *testing.T) { } func TestCreateTrustNoExpire(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateTrustNoExpire(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateTrustNoExpire(t, fakeServer) - result, err := trusts.Create(context.TODO(), client.ServiceClient(), trusts.CreateOpts{ + result, err := trusts.Create(context.TODO(), client.ServiceClient(fakeServer), trusts.CreateOpts{ AllowRedelegation: true, ProjectID: "9b71012f5a4a4aef9193f1995fe159b2", Roles: []trusts.Role{ @@ -56,30 +56,30 @@ func TestCreateTrustNoExpire(t *testing.T) { } func TestDeleteTrust(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteTrust(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteTrust(t, fakeServer) - res := trusts.Delete(context.TODO(), client.ServiceClient(), "3422b7c113894f5d90665e1a79655e23") + res := trusts.Delete(context.TODO(), client.ServiceClient(fakeServer), "3422b7c113894f5d90665e1a79655e23") th.AssertNoErr(t, res.Err) } func TestGetTrust(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetTrustSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetTrustSuccessfully(t, fakeServer) - res := trusts.Get(context.TODO(), client.ServiceClient(), "987fe8") + res := trusts.Get(context.TODO(), client.ServiceClient(fakeServer), "987fe8") th.AssertNoErr(t, res.Err) } func TestListTrusts(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListTrustsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListTrustsSuccessfully(t, fakeServer) count := 0 - err := trusts.List(client.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := trusts.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := trusts.ExtractTrusts(page) @@ -90,15 +90,15 @@ func TestListTrusts(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListTrustsAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListTrustsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListTrustsSuccessfully(t, fakeServer) - allPages, err := trusts.List(client.ServiceClient(), nil).AllPages(context.TODO()) + allPages, err := trusts.List(client.ServiceClient(fakeServer), nil).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := trusts.ExtractTrusts(allPages) th.AssertNoErr(t, err) @@ -106,13 +106,13 @@ func TestListTrustsAllPages(t *testing.T) { } func TestListTrustsFiltered(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListTrustsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListTrustsSuccessfully(t, fakeServer) trustsListOpts := trusts.ListOpts{ TrustorUserID: "86c0d5", } - allPages, err := trusts.List(client.ServiceClient(), trustsListOpts).AllPages(context.TODO()) + allPages, err := trusts.List(client.ServiceClient(fakeServer), trustsListOpts).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := trusts.ExtractTrusts(allPages) th.AssertNoErr(t, err) @@ -120,12 +120,12 @@ func TestListTrustsFiltered(t *testing.T) { } func TestListTrustRoles(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListTrustRolesSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListTrustRolesSuccessfully(t, fakeServer) count := 0 - err := trusts.ListRoles(client.ServiceClient(), "987fe8").EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := trusts.ListRoles(client.ServiceClient(fakeServer), "987fe8").EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := trusts.ExtractRoles(page) @@ -136,15 +136,15 @@ func TestListTrustRoles(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListTrustRolesAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListTrustRolesSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListTrustRolesSuccessfully(t, fakeServer) - allPages, err := trusts.ListRoles(client.ServiceClient(), "987fe8").AllPages(context.TODO()) + allPages, err := trusts.ListRoles(client.ServiceClient(fakeServer), "987fe8").AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := trusts.ExtractRoles(allPages) th.AssertNoErr(t, err) @@ -152,21 +152,21 @@ func TestListTrustRolesAllPages(t *testing.T) { } func TestGetTrustRole(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetTrustRoleSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetTrustRoleSuccessfully(t, fakeServer) - role, err := trusts.GetRole(context.TODO(), client.ServiceClient(), "987fe8", "c1648e").Extract() + role, err := trusts.GetRole(context.TODO(), client.ServiceClient(fakeServer), "987fe8", "c1648e").Extract() th.AssertNoErr(t, err) th.AssertEquals(t, FirstRole, *role) } func TestCheckTrustRole(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCheckTrustRoleSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCheckTrustRoleSuccessfully(t, fakeServer) - err := trusts.CheckRole(context.TODO(), client.ServiceClient(), "987fe8", "c1648e").ExtractErr() + err := trusts.CheckRole(context.TODO(), client.ServiceClient(fakeServer), "987fe8", "c1648e").ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/identity/v3/users/results.go b/openstack/identity/v3/users/results.go index dc48da2a2f..3d6c5ab1c6 100644 --- a/openstack/identity/v3/users/results.go +++ b/openstack/identity/v3/users/results.go @@ -2,6 +2,8 @@ package users import ( "encoding/json" + "fmt" + "strconv" "time" "github.com/gophercloud/gophercloud/v2" @@ -20,7 +22,7 @@ type User struct { DomainID string `json:"domain_id"` // Enabled is whether or not the user is enabled. - Enabled bool `json:"enabled"` + Enabled bool `json:"-"` // Extra is a collection of miscellaneous key/values. Extra map[string]any `json:"-"` @@ -45,6 +47,7 @@ func (r *User) UnmarshalJSON(b []byte) error { type tmp User var s struct { tmp + Enabled any `json:"enabled"` Extra map[string]any `json:"extra"` PasswordExpiresAt gophercloud.JSONRFC3339MilliNoZ `json:"password_expires_at"` } @@ -56,6 +59,20 @@ func (r *User) UnmarshalJSON(b []byte) error { r.PasswordExpiresAt = time.Time(s.PasswordExpiresAt) + switch t := s.Enabled.(type) { + case nil: + r.Enabled = false + case bool: + r.Enabled = t + case string: + r.Enabled, err = strconv.ParseBool(t) + if err != nil { + return fmt.Errorf("failed to parse Enabled %q: %v", t, err) + } + default: + return fmt.Errorf("unknown type for Enabled: %T (value: %v)", t, t) + } + // Collect other fields and bundle them into Extra // but only if a field titled "extra" wasn't sent. if s.Extra != nil { @@ -144,7 +161,7 @@ func (r UserPage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (r UserPage) NextPageURL() (string, error) { +func (r UserPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links struct { Next string `json:"next"` diff --git a/openstack/identity/v3/users/testing/fixtures_test.go b/openstack/identity/v3/users/testing/fixtures_test.go index 023124cb79..8d409be0b9 100644 --- a/openstack/identity/v3/users/testing/fixtures_test.go +++ b/openstack/identity/v3/users/testing/fixtures_test.go @@ -25,7 +25,7 @@ const ListOutput = ` "users": [ { "domain_id": "default", - "enabled": true, + "enabled": "True", "id": "2844b2a08be147a08ef58317d6471f1f", "links": { "self": "http://example.com/identity/v3/users/2844b2a08be147a08ef58317d6471f1f" @@ -374,75 +374,75 @@ var ExpectedProjectsSlice = []projects.Project{FirstProject, SecondProject} // HandleListUsersSuccessfully creates an HTTP handler at `/users` on the // test handler mux that responds with a list of two users. -func HandleListUsersSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) { +func HandleListUsersSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } // HandleGetUserSuccessfully creates an HTTP handler at `/users` on the // test handler mux that responds with a single user. -func HandleGetUserSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/users/9fe1d3", func(w http.ResponseWriter, r *http.Request) { +func HandleGetUserSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/9fe1d3", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleCreateUserSuccessfully creates an HTTP handler at `/users` on the // test handler mux that tests user creation. -func HandleCreateUserSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateUserSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } // HandleCreateNoOptionsUserSuccessfully creates an HTTP handler at `/users` on the // test handler mux that tests user creation. -func HandleCreateNoOptionsUserSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateNoOptionsUserSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateNoOptionsRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, GetOutputNoOptions) + fmt.Fprint(w, GetOutputNoOptions) }) } // HandleUpdateUserSuccessfully creates an HTTP handler at `/users` on the // test handler mux that tests user update. -func HandleUpdateUserSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/users/9fe1d3", func(w http.ResponseWriter, r *http.Request) { +func HandleUpdateUserSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/9fe1d3", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }) } // HandleChangeUserPasswordSuccessfully creates an HTTP handler at `/users` on the // test handler mux that tests change user password. -func HandleChangeUserPasswordSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/users/9fe1d3/password", func(w http.ResponseWriter, r *http.Request) { +func HandleChangeUserPasswordSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/9fe1d3/password", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, ChangePasswordRequest) @@ -453,8 +453,8 @@ func HandleChangeUserPasswordSuccessfully(t *testing.T) { // HandleDeleteUserSuccessfully creates an HTTP handler at `/users` on the // test handler mux that tests user deletion. -func HandleDeleteUserSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/users/9fe1d3", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteUserSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/9fe1d3", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -464,22 +464,22 @@ func HandleDeleteUserSuccessfully(t *testing.T) { // HandleListUserGroupsSuccessfully creates an HTTP handler at /users/{userID}/groups // on the test handler mux that respons with a list of two groups -func HandleListUserGroupsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/users/9fe1d3/groups", func(w http.ResponseWriter, r *http.Request) { +func HandleListUserGroupsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/9fe1d3/groups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListGroupsOutput) + fmt.Fprint(w, ListGroupsOutput) }) } // HandleAddToGroupSuccessfully creates an HTTP handler at /groups/{groupID}/users/{userID} // on the test handler mux that tests adding user to group. -func HandleAddToGroupSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/groups/ea167b/users/9fe1d3", func(w http.ResponseWriter, r *http.Request) { +func HandleAddToGroupSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/groups/ea167b/users/9fe1d3", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -489,8 +489,8 @@ func HandleAddToGroupSuccessfully(t *testing.T) { // HandleIsMemberOfGroupSuccessfully creates an HTTP handler at /groups/{groupID}/users/{userID} // on the test handler mux that tests checking whether user belongs to group. -func HandleIsMemberOfGroupSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/groups/ea167b/users/9fe1d3", func(w http.ResponseWriter, r *http.Request) { +func HandleIsMemberOfGroupSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/groups/ea167b/users/9fe1d3", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "HEAD") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -500,8 +500,8 @@ func HandleIsMemberOfGroupSuccessfully(t *testing.T) { // HandleRemoveFromGroupSuccessfully creates an HTTP handler at /groups/{groupID}/users/{userID} // on the test handler mux that tests removing user from group. -func HandleRemoveFromGroupSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/groups/ea167b/users/9fe1d3", func(w http.ResponseWriter, r *http.Request) { +func HandleRemoveFromGroupSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/groups/ea167b/users/9fe1d3", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -511,28 +511,28 @@ func HandleRemoveFromGroupSuccessfully(t *testing.T) { // HandleListUserProjectsSuccessfully creates an HTTP handler at /users/{userID}/projects // on the test handler mux that respons wit a list of two projects -func HandleListUserProjectsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/users/9fe1d3/projects", func(w http.ResponseWriter, r *http.Request) { +func HandleListUserProjectsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/users/9fe1d3/projects", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListProjectsOutput) + fmt.Fprint(w, ListProjectsOutput) }) } // HandleListInGroupSuccessfully creates an HTTP handler at /groups/{groupID}/users // on the test handler mux that response with a list of two users -func HandleListInGroupSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/groups/ea167b/users", func(w http.ResponseWriter, r *http.Request) { +func HandleListInGroupSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/groups/ea167b/users", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } diff --git a/openstack/identity/v3/users/testing/requests_test.go b/openstack/identity/v3/users/testing/requests_test.go index cb890808ee..5d2bafdda6 100644 --- a/openstack/identity/v3/users/testing/requests_test.go +++ b/openstack/identity/v3/users/testing/requests_test.go @@ -13,12 +13,12 @@ import ( ) func TestListUsers(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListUsersSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListUsersSuccessfully(t, fakeServer) count := 0 - err := users.List(client.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := users.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := users.ExtractUsers(page) @@ -29,21 +29,21 @@ func TestListUsers(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListUsersAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListUsersSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListUsersSuccessfully(t, fakeServer) - allPages, err := users.List(client.ServiceClient(), nil).AllPages(context.TODO()) + allPages, err := users.List(client.ServiceClient(fakeServer), nil).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := users.ExtractUsers(allPages) th.AssertNoErr(t, err) th.CheckDeepEquals(t, ExpectedUsersSlice, actual) - th.AssertEquals(t, ExpectedUsersSlice[0].Extra["email"], "glance@localhost") - th.AssertEquals(t, ExpectedUsersSlice[1].Extra["email"], "jsmith@example.com") + th.AssertEquals(t, "glance@localhost", ExpectedUsersSlice[0].Extra["email"]) + th.AssertEquals(t, "jsmith@example.com", ExpectedUsersSlice[1].Extra["email"]) } func TestListUsersFiltersCheck(t *testing.T) { @@ -79,20 +79,20 @@ func TestListUsersFiltersCheck(t *testing.T) { } func TestGetUser(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetUserSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetUserSuccessfully(t, fakeServer) - actual, err := users.Get(context.TODO(), client.ServiceClient(), "9fe1d3").Extract() + actual, err := users.Get(context.TODO(), client.ServiceClient(fakeServer), "9fe1d3").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondUser, *actual) - th.AssertEquals(t, SecondUser.Extra["email"], "jsmith@example.com") + th.AssertEquals(t, "jsmith@example.com", SecondUser.Extra["email"]) } func TestCreateUser(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateUserSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateUserSuccessfully(t, fakeServer) iTrue := true createOpts := users.CreateOpts{ @@ -113,15 +113,15 @@ func TestCreateUser(t *testing.T) { }, } - actual, err := users.Create(context.TODO(), client.ServiceClient(), createOpts).Extract() + actual, err := users.Create(context.TODO(), client.ServiceClient(fakeServer), createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondUser, *actual) } func TestCreateNoOptionsUser(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateNoOptionsUserSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateNoOptionsUserSuccessfully(t, fakeServer) iTrue := true createOpts := users.CreateOpts{ @@ -135,15 +135,15 @@ func TestCreateNoOptionsUser(t *testing.T) { }, } - actual, err := users.Create(context.TODO(), client.ServiceClient(), createOpts).Extract() + actual, err := users.Create(context.TODO(), client.ServiceClient(fakeServer), createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondUserNoOptions, *actual) } func TestUpdateUser(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateUserSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateUserSuccessfully(t, fakeServer) iFalse := false updateOpts := users.UpdateOpts{ @@ -156,39 +156,39 @@ func TestUpdateUser(t *testing.T) { }, } - actual, err := users.Update(context.TODO(), client.ServiceClient(), "9fe1d3", updateOpts).Extract() + actual, err := users.Update(context.TODO(), client.ServiceClient(fakeServer), "9fe1d3", updateOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, SecondUserUpdated, *actual) } func TestChangeUserPassword(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleChangeUserPasswordSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleChangeUserPasswordSuccessfully(t, fakeServer) changePasswordOpts := users.ChangePasswordOpts{ OriginalPassword: "secretsecret", Password: "new_secretsecret", } - res := users.ChangePassword(context.TODO(), client.ServiceClient(), "9fe1d3", changePasswordOpts) + res := users.ChangePassword(context.TODO(), client.ServiceClient(fakeServer), "9fe1d3", changePasswordOpts) th.AssertNoErr(t, res.Err) } func TestDeleteUser(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteUserSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteUserSuccessfully(t, fakeServer) - res := users.Delete(context.TODO(), client.ServiceClient(), "9fe1d3") + res := users.Delete(context.TODO(), client.ServiceClient(fakeServer), "9fe1d3") th.AssertNoErr(t, res.Err) } func TestListUserGroups(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListUserGroupsSuccessfully(t) - allPages, err := users.ListGroups(client.ServiceClient(), "9fe1d3").AllPages(context.TODO()) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListUserGroupsSuccessfully(t, fakeServer) + allPages, err := users.ListGroups(client.ServiceClient(fakeServer), "9fe1d3").AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := groups.ExtractGroups(allPages) th.AssertNoErr(t, err) @@ -196,35 +196,35 @@ func TestListUserGroups(t *testing.T) { } func TestAddToGroup(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAddToGroupSuccessfully(t) - res := users.AddToGroup(context.TODO(), client.ServiceClient(), "ea167b", "9fe1d3") + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAddToGroupSuccessfully(t, fakeServer) + res := users.AddToGroup(context.TODO(), client.ServiceClient(fakeServer), "ea167b", "9fe1d3") th.AssertNoErr(t, res.Err) } func TestIsMemberOfGroup(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleIsMemberOfGroupSuccessfully(t) - ok, err := users.IsMemberOfGroup(context.TODO(), client.ServiceClient(), "ea167b", "9fe1d3").Extract() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleIsMemberOfGroupSuccessfully(t, fakeServer) + ok, err := users.IsMemberOfGroup(context.TODO(), client.ServiceClient(fakeServer), "ea167b", "9fe1d3").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, true, ok) + th.AssertTrue(t, ok) } func TestRemoveFromGroup(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleRemoveFromGroupSuccessfully(t) - res := users.RemoveFromGroup(context.TODO(), client.ServiceClient(), "ea167b", "9fe1d3") + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleRemoveFromGroupSuccessfully(t, fakeServer) + res := users.RemoveFromGroup(context.TODO(), client.ServiceClient(fakeServer), "ea167b", "9fe1d3") th.AssertNoErr(t, res.Err) } func TestListUserProjects(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListUserProjectsSuccessfully(t) - allPages, err := users.ListProjects(client.ServiceClient(), "9fe1d3").AllPages(context.TODO()) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListUserProjectsSuccessfully(t, fakeServer) + allPages, err := users.ListProjects(client.ServiceClient(fakeServer), "9fe1d3").AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := projects.ExtractProjects(allPages) th.AssertNoErr(t, err) @@ -232,16 +232,16 @@ func TestListUserProjects(t *testing.T) { } func TestListInGroup(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListInGroupSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListInGroupSuccessfully(t, fakeServer) iTrue := true listOpts := users.ListOpts{ Enabled: &iTrue, } - allPages, err := users.ListInGroup(client.ServiceClient(), "ea167b", listOpts).AllPages(context.TODO()) + allPages, err := users.ListInGroup(client.ServiceClient(fakeServer), "ea167b", listOpts).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := users.ExtractUsers(allPages) th.AssertNoErr(t, err) diff --git a/openstack/image/v2/imagedata/testing/fixtures_test.go b/openstack/image/v2/imagedata/testing/fixtures_test.go index fa3b4af781..d9af6c1702 100644 --- a/openstack/image/v2/imagedata/testing/fixtures_test.go +++ b/openstack/image/v2/imagedata/testing/fixtures_test.go @@ -6,14 +6,14 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fakeclient "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) // HandlePutImageDataSuccessfully setup -func HandlePutImageDataSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/file", func(w http.ResponseWriter, r *http.Request) { +func HandlePutImageDataSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/file", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) b, err := io.ReadAll(r.Body) if err != nil { @@ -27,10 +27,10 @@ func HandlePutImageDataSuccessfully(t *testing.T) { } // HandleStageImageDataSuccessfully setup -func HandleStageImageDataSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/stage", func(w http.ResponseWriter, r *http.Request) { +func HandleStageImageDataSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/stage", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) b, err := io.ReadAll(r.Body) if err != nil { @@ -44,10 +44,10 @@ func HandleStageImageDataSuccessfully(t *testing.T) { } // HandleGetImageDataSuccessfully setup -func HandleGetImageDataSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/file", func(w http.ResponseWriter, r *http.Request) { +func HandleGetImageDataSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/file", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) diff --git a/openstack/image/v2/imagedata/testing/requests_test.go b/openstack/image/v2/imagedata/testing/requests_test.go index 8e1461e1c7..63294a1323 100644 --- a/openstack/image/v2/imagedata/testing/requests_test.go +++ b/openstack/image/v2/imagedata/testing/requests_test.go @@ -8,18 +8,18 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/image/v2/imagedata" th "github.com/gophercloud/gophercloud/v2/testhelper" - fakeclient "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestUpload(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandlePutImageDataSuccessfully(t) + HandlePutImageDataSuccessfully(t, fakeServer) err := imagedata.Upload( context.TODO(), - fakeclient.ServiceClient(), + client.ServiceClient(fakeServer), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", readSeekerOfBytes([]byte{5, 3, 7, 24})).ExtractErr() @@ -27,14 +27,14 @@ func TestUpload(t *testing.T) { } func TestStage(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleStageImageDataSuccessfully(t) + HandleStageImageDataSuccessfully(t, fakeServer) err := imagedata.Stage( context.TODO(), - fakeclient.ServiceClient(), + client.ServiceClient(fakeServer), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", readSeekerOfBytes([]byte{5, 3, 7, 24})).ExtractErr() @@ -74,26 +74,27 @@ func min(a int, b int) int { func (rs *RS) Seek(offset int64, whence int) (int64, error) { var offsetInt = int(offset) - if whence == 0 { + switch whence { + case 0: rs.offset = offsetInt - } else if whence == 1 { + case 1: rs.offset = rs.offset + offsetInt - } else if whence == 2 { + case 2: rs.offset = len(rs.bs) - offsetInt - } else { - return 0, fmt.Errorf("For parameter `whence`, expected value in {0,1,2} but got: %#v", whence) + default: + return 0, fmt.Errorf("for parameter `whence`, expected value in {0,1,2} but got: %#v", whence) } return int64(rs.offset), nil } func TestDownload(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleGetImageDataSuccessfully(t) + HandleGetImageDataSuccessfully(t, fakeServer) - rdr, err := imagedata.Download(context.TODO(), fakeclient.ServiceClient(), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea").Extract() + rdr, err := imagedata.Download(context.TODO(), client.ServiceClient(fakeServer), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea").Extract() th.AssertNoErr(t, err) defer rdr.Close() diff --git a/openstack/image/v2/imageimport/testing/requests_test.go b/openstack/image/v2/imageimport/testing/requests_test.go index ae353ad0ac..b46055953f 100644 --- a/openstack/image/v2/imageimport/testing/requests_test.go +++ b/openstack/image/v2/imageimport/testing/requests_test.go @@ -8,21 +8,21 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/image/v2/imageimport" th "github.com/gophercloud/gophercloud/v2/testhelper" - fakeclient "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/info/import", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/info/import", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ImportGetResult) + fmt.Fprint(w, ImportGetResult) }) validImportMethods := []string{ @@ -30,32 +30,32 @@ func TestGet(t *testing.T) { string(imageimport.WebDownloadMethod), } - s, err := imageimport.Get(context.TODO(), fakeclient.ServiceClient()).Extract() + s, err := imageimport.Get(context.TODO(), client.ServiceClient(fakeServer)).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.ImportMethods.Description, "Import methods available.") - th.AssertEquals(t, s.ImportMethods.Type, "array") + th.AssertEquals(t, "Import methods available.", s.ImportMethods.Description) + th.AssertEquals(t, "array", s.ImportMethods.Type) th.AssertDeepEquals(t, s.ImportMethods.Value, validImportMethods) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/import", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/import", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, ImportCreateRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) opts := imageimport.CreateOpts{ Name: imageimport.WebDownloadMethod, URI: "http://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img", } - err := imageimport.Create(context.TODO(), fakeclient.ServiceClient(), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", opts).ExtractErr() + err := imageimport.Create(context.TODO(), client.ServiceClient(fakeServer), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", opts).ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/image/v2/images/requests.go b/openstack/image/v2/images/requests.go index a5f61dd615..c5c10bb7c3 100644 --- a/openstack/image/v2/images/requests.go +++ b/openstack/image/v2/images/requests.go @@ -133,12 +133,7 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { url += query } return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { - imagePage := ImagePage{ - serviceURL: c.ServiceURL(), - LinkedPageBase: pagination.LinkedPageBase{PageResult: r}, - } - - return imagePage + return ImagePage{pagination.LinkedPageBase{PageResult: r}} }) } diff --git a/openstack/image/v2/images/results.go b/openstack/image/v2/images/results.go index 6652f0e791..566bbb4e82 100644 --- a/openstack/image/v2/images/results.go +++ b/openstack/image/v2/images/results.go @@ -70,7 +70,7 @@ type Image struct { // Properties is a set of key-value pairs, if any, that are associated with // the image. - Properties map[string]any + Properties map[string]any `json:"-"` // CreatedAt is the date when the image has been created. CreatedAt time.Time `json:"created_at"` @@ -102,6 +102,7 @@ func (r *Image) UnmarshalJSON(b []byte) error { type tmp Image var s struct { tmp + Properties string `json:"properties"` SizeBytes any `json:"size"` OpenStackImageImportMethods string `json:"openstack-image-import-methods"` OpenStackImageStoreIDs string `json:"openstack-image-store-ids"` @@ -120,10 +121,10 @@ func (r *Image) UnmarshalJSON(b []byte) error { case float64: r.SizeBytes = int64(t) default: - return fmt.Errorf("Unknown type for SizeBytes: %v (value: %v)", reflect.TypeOf(t), t) + return fmt.Errorf("unknown type for SizeBytes: %v (value: %v)", reflect.TypeOf(t), t) } - // Bundle all other fields into Properties + // Bundle all other fields into Properties, except for the field named "properties" var result any err = json.Unmarshal(b, &result) if err != nil { @@ -137,6 +138,11 @@ func (r *Image) UnmarshalJSON(b []byte) error { r.Properties = gophercloud.RemainingKeys(Image{}, resultMap) } + // Add the "properties" field to the image since it's not included in above step (i.e. in remaining keys) + if s.Properties != "" { + r.Properties["properties"] = s.Properties + } + if v := strings.FieldsFunc(strings.TrimSpace(s.OpenStackImageImportMethods), splitFunc); len(v) > 0 { r.OpenStackImageImportMethods = v } @@ -198,7 +204,6 @@ type DeleteResult struct { // ImagePage represents the results of a List request. type ImagePage struct { - serviceURL string pagination.LinkedPageBase } @@ -214,7 +219,7 @@ func (r ImagePage) IsEmpty() (bool, error) { // NextPageURL uses the response's embedded link reference to navigate to // the next page of results. -func (r ImagePage) NextPageURL() (string, error) { +func (r ImagePage) NextPageURL(endpointURL string) (string, error) { var s struct { Next string `json:"next"` } @@ -227,7 +232,7 @@ func (r ImagePage) NextPageURL() (string, error) { return "", nil } - return nextPageURL(r.serviceURL, s.Next) + return nextPageURL(endpointURL, s.Next) } // ExtractImages interprets the results of a single page from a List() call, diff --git a/openstack/image/v2/images/testing/fixtures_test.go b/openstack/image/v2/images/testing/fixtures_test.go index 0c9a7b0318..0f47975677 100644 --- a/openstack/image/v2/images/testing/fixtures_test.go +++ b/openstack/image/v2/images/testing/fixtures_test.go @@ -8,7 +8,7 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fakeclient "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) type imageEntry struct { @@ -17,9 +17,9 @@ type imageEntry struct { } // HandleImageListSuccessfully test setup -func HandleImageListSuccessfully(t *testing.T) { +func HandleImageListSuccessfully(t *testing.T, fakeServer th.FakeServer) { - images := make([]imageEntry, 3) + images := make([]imageEntry, 4) images[0] = imageEntry{"cirros-0.3.4-x86_64-uec", `{ @@ -98,10 +98,50 @@ func HandleImageListSuccessfully(t *testing.T) { "hw_disk_bus_model": "virtio-scsi", "hw_scsi_model": "virtio-scsi" }`} + images[3] = imageEntry{"ubuntu-24.04-server-cloudimg-amd64.img", + `{ + "owner_specified.openstack.md5": "", + "owner_specified.openstack.object": "images/johndoe-noble-iso-test", + "owner_specified.openstack.sha256": "", + "properties": "{'hypervisor_type': 'qemu', 'architecture': 'x86_64'}", + "additional_property": "{\"name\": \"noble\", \"size\": 100, \"hidden\": false}", + "name": "johndoe-noble-iso-test", + "disk_format": "iso", + "container_format": "bare", + "visibility": "shared", + "size": 3303444480, + "virtual_size": null, + "status": "active", + "checksum": "19fe5793f7411cfe124d9fa0c0f4d449", + "protected": false, + "min_ram": 0, + "min_disk": 0, + "owner": "dfdfed1a51504178abe8c5eeaeb8343b", + "os_hidden": false, + "os_hash_algo": "sha256", + "os_hash_value": "c3514bf0056180d09376462a7a1b4f213c1d6e8ea67fae5c25099c6fd3d8274b", + "id": "370ba8b7-3e72-4f9d-8623-7d9c95e4c28d", + "created_at": "2025-12-18T02:05:15Z", + "updated_at": "2025-12-18T02:42:05Z", + "locations": [ + { + "url": "rbd://eea9d068-c18c-11ed-8dc0-013aacb71b80/glance/370ba8b7-3e72-4f9d-8623-7d9c95e4c28d/snap", + "metadata": { + "store": "ceph" + } + } + ], + "direct_url": "rbd://eea9d068-c18c-11ed-8dc0-013aacb71b80/glance/370ba8b7-3e72-4f9d-8623-7d9c95e4c28d/snap", + "tags": [], + "self": "/v2/images/370ba8b7-3e72-4f9d-8623-7d9c95e4c28d", + "file": "/v2/images/370ba8b7-3e72-4f9d-8623-7d9c95e4c28d/file", + "schema": "/v2/schemas/image", + "stores": "ceph" + }`} - th.Mux.HandleFunc("/images", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/images", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") @@ -130,7 +170,7 @@ func HandleImageListSuccessfully(t *testing.T) { addNext := false var imageJSON []string - fmt.Fprintf(w, `{"images": [`) + fmt.Fprint(w, `{"images": [`) for _, i := range images { if marker == "" || addNext { @@ -149,7 +189,7 @@ func HandleImageListSuccessfully(t *testing.T) { } } t.Logf("Writing out %v image(s)", len(imageJSON)) - fmt.Fprintf(w, strings.Join(imageJSON, ",")) + fmt.Fprint(w, strings.Join(imageJSON, ",")) fmt.Fprintf(w, `], "next": "/images?marker=%s&limit=%v", @@ -160,10 +200,10 @@ func HandleImageListSuccessfully(t *testing.T) { } // HandleImageCreationSuccessfully test setup -func HandleImageCreationSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/images", func(w http.ResponseWriter, r *http.Request) { +func HandleImageCreationSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/images", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ "id": "e7db3b45-8db7-47ad-8109-3fb55c2c24fd", "name": "Ubuntu 12.10", @@ -176,7 +216,7 @@ func HandleImageCreationSuccessfully(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "status": "queued", "name": "Ubuntu 12.10", "protected": false, @@ -206,10 +246,10 @@ func HandleImageCreationSuccessfully(t *testing.T) { // HandleImageCreationSuccessfullyNulls test setup // JSON null values could be also returned according to behaviour https://bugs.launchpad.net/glance/+bug/1481512 -func HandleImageCreationSuccessfullyNulls(t *testing.T) { - th.Mux.HandleFunc("/images", func(w http.ResponseWriter, r *http.Request) { +func HandleImageCreationSuccessfullyNulls(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/images", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ "id": "e7db3b45-8db7-47ad-8109-3fb55c2c24fd", "architecture": "x86_64", @@ -224,7 +264,7 @@ func HandleImageCreationSuccessfullyNulls(t *testing.T) { w.Header().Set("OpenStack-image-import-methods", "glance-direct,web-download") w.Header().Set("OpenStack-image-store-ids", "123,456") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "architecture": "x86_64", "status": "queued", "name": "Ubuntu 12.10", @@ -251,14 +291,14 @@ func HandleImageCreationSuccessfullyNulls(t *testing.T) { } // HandleImageGetSuccessfully test setup -func HandleImageGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/images/1bea47ed-f6a9-463b-b423-14b9cca9ad27", func(w http.ResponseWriter, r *http.Request) { +func HandleImageGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/images/1bea47ed-f6a9-463b-b423-14b9cca9ad27", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "status": "active", "name": "cirros-0.3.2-x86_64-disk", "tags": [], @@ -281,26 +321,27 @@ func HandleImageGetSuccessfully(t *testing.T) { "virtual_size": null, "hw_disk_bus": "scsi", "hw_disk_bus_model": "virtio-scsi", - "hw_scsi_model": "virtio-scsi" + "hw_scsi_model": "virtio-scsi", + "properties": "{\"test\": true}" }`) }) } // HandleImageDeleteSuccessfully test setup -func HandleImageDeleteSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/images/1bea47ed-f6a9-463b-b423-14b9cca9ad27", func(w http.ResponseWriter, r *http.Request) { +func HandleImageDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/images/1bea47ed-f6a9-463b-b423-14b9cca9ad27", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) } // HandleImageUpdateSuccessfully setup -func HandleImageUpdateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea", func(w http.ResponseWriter, r *http.Request) { +func HandleImageUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `[ { @@ -347,7 +388,7 @@ func HandleImageUpdateSuccessfully(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "id": "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", "name": "Fedora 17", "status": "active", @@ -380,16 +421,16 @@ func HandleImageUpdateSuccessfully(t *testing.T) { } // HandleImageListByTagsSuccessfully tests a list operation with tags. -func HandleImageListByTagsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/images", func(w http.ResponseWriter, r *http.Request) { +func HandleImageListByTagsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/images", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "images": [ { "status": "active", @@ -422,10 +463,10 @@ func HandleImageListByTagsSuccessfully(t *testing.T) { } // HandleImageUpdatePropertiesSuccessfully setup -func HandleImageUpdatePropertiesSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea", func(w http.ResponseWriter, r *http.Request) { +func HandleImageUpdatePropertiesSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `[ { @@ -449,7 +490,7 @@ func HandleImageUpdatePropertiesSuccessfully(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "id": "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", "name": "Fedora 17", "status": "active", diff --git a/openstack/image/v2/images/testing/requests_test.go b/openstack/image/v2/images/testing/requests_test.go index 8b857871af..d98ac88ae0 100644 --- a/openstack/image/v2/images/testing/requests_test.go +++ b/openstack/image/v2/images/testing/requests_test.go @@ -8,18 +8,18 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/image/v2/images" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fakeclient "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestListImage(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleImageListSuccessfully(t) + HandleImageListSuccessfully(t, fakeServer) t.Logf("Id\tName\tOwner\tChecksum\tSizeBytes") - pager := images.List(fakeclient.ServiceClient(), images.ListOpts{Limit: 1}) + pager := images.List(client.ServiceClient(fakeServer), images.ListOpts{Limit: 1}) t.Logf("Pager state %v", pager) count, pages := 0, 0 err := pager.EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { @@ -40,33 +40,33 @@ func TestListImage(t *testing.T) { th.AssertNoErr(t, err) t.Logf("--------\n%d images listed on %d pages.\n", count, pages) - th.AssertEquals(t, 3, pages) - th.AssertEquals(t, 3, count) + th.AssertEquals(t, 4, pages) + th.AssertEquals(t, 4, count) } func TestAllPagesImage(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleImageListSuccessfully(t) + HandleImageListSuccessfully(t, fakeServer) - pages, err := images.List(fakeclient.ServiceClient(), nil).AllPages(context.TODO()) + pages, err := images.List(client.ServiceClient(fakeServer), nil).AllPages(context.TODO()) th.AssertNoErr(t, err) images, err := images.ExtractImages(pages) th.AssertNoErr(t, err) - th.AssertEquals(t, 3, len(images)) + th.AssertEquals(t, 4, len(images)) } func TestCreateImage(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleImageCreationSuccessfully(t) + HandleImageCreationSuccessfully(t, fakeServer) id := "e7db3b45-8db7-47ad-8109-3fb55c2c24fd" name := "Ubuntu 12.10" - actualImage, err := images.Create(context.TODO(), fakeclient.ServiceClient(), images.CreateOpts{ + actualImage, err := images.Create(context.TODO(), client.ServiceClient(fakeServer), images.CreateOpts{ ID: id, Name: name, Properties: map[string]string{ @@ -119,15 +119,15 @@ func TestCreateImage(t *testing.T) { } func TestCreateImageNulls(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleImageCreationSuccessfullyNulls(t) + HandleImageCreationSuccessfullyNulls(t, fakeServer) id := "e7db3b45-8db7-47ad-8109-3fb55c2c24fd" name := "Ubuntu 12.10" - actualImage, err := images.Create(context.TODO(), fakeclient.ServiceClient(), images.CreateOpts{ + actualImage, err := images.Create(context.TODO(), client.ServiceClient(fakeServer), images.CreateOpts{ ID: id, Name: name, Tags: []string{"ubuntu", "quantal"}, @@ -188,12 +188,12 @@ func TestCreateImageNulls(t *testing.T) { } func TestGetImage(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleImageGetSuccessfully(t) + HandleImageGetSuccessfully(t, fakeServer) - actualImage, err := images.Get(context.TODO(), fakeclient.ServiceClient(), "1bea47ed-f6a9-463b-b423-14b9cca9ad27").Extract() + actualImage, err := images.Get(context.TODO(), client.ServiceClient(fakeServer), "1bea47ed-f6a9-463b-b423-14b9cca9ad27").Extract() th.AssertNoErr(t, err) @@ -239,6 +239,7 @@ func TestGetImage(t *testing.T) { "hw_disk_bus": "scsi", "hw_disk_bus_model": "virtio-scsi", "hw_scsi_model": "virtio-scsi", + "properties": "{\"test\": true}", }, } @@ -246,22 +247,22 @@ func TestGetImage(t *testing.T) { } func TestDeleteImage(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleImageDeleteSuccessfully(t) + HandleImageDeleteSuccessfully(t, fakeServer) - result := images.Delete(context.TODO(), fakeclient.ServiceClient(), "1bea47ed-f6a9-463b-b423-14b9cca9ad27") + result := images.Delete(context.TODO(), client.ServiceClient(fakeServer), "1bea47ed-f6a9-463b-b423-14b9cca9ad27") th.AssertNoErr(t, result.Err) } func TestUpdateImage(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleImageUpdateSuccessfully(t) + HandleImageUpdateSuccessfully(t, fakeServer) - actualImage, err := images.Update(context.TODO(), fakeclient.ServiceClient(), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", images.UpdateOpts{ + actualImage, err := images.Update(context.TODO(), client.ServiceClient(fakeServer), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", images.UpdateOpts{ images.ReplaceImageName{NewName: "Fedora 17"}, images.ReplaceImageTags{NewTags: []string{"fedora", "beefy"}}, images.ReplaceImageMinDisk{NewMinDisk: 21}, @@ -342,10 +343,10 @@ func TestImageDateQuery(t *testing.T) { } func TestImageListByTags(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleImageListByTagsSuccessfully(t) + HandleImageListByTagsSuccessfully(t, fakeServer) listOpts := images.ListOpts{ Tags: []string{"foo", "bar"}, @@ -356,7 +357,7 @@ func TestImageListByTags(t *testing.T) { th.AssertNoErr(t, err) th.AssertEquals(t, expectedQueryString, actualQueryString) - pages, err := images.List(fakeclient.ServiceClient(), listOpts).AllPages(context.TODO()) + pages, err := images.List(client.ServiceClient(fakeServer), listOpts).AllPages(context.TODO()) th.AssertNoErr(t, err) allImages, err := images.ExtractImages(pages) th.AssertNoErr(t, err) @@ -410,12 +411,12 @@ func TestImageListByTags(t *testing.T) { } func TestUpdateImageProperties(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleImageUpdatePropertiesSuccessfully(t) + HandleImageUpdatePropertiesSuccessfully(t, fakeServer) - actualImage, err := images.Update(context.TODO(), fakeclient.ServiceClient(), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", images.UpdateOpts{ + actualImage, err := images.Update(context.TODO(), client.ServiceClient(fakeServer), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", images.UpdateOpts{ images.UpdateImageProperty{ Op: images.AddOp, Name: "hw_disk_bus", diff --git a/openstack/image/v2/images/types.go b/openstack/image/v2/images/types.go index 147be19927..eedc13a330 100644 --- a/openstack/image/v2/images/types.go +++ b/openstack/image/v2/images/types.go @@ -13,10 +13,14 @@ const ( // been reserved for an image in the image registry. ImageStatusQueued ImageStatus = "queued" - // ImageStatusSaving denotes that an image’s raw data is currently being + // ImageStatusSaving denotes that an image's raw data is currently being // uploaded to Glance ImageStatusSaving ImageStatus = "saving" + // ImageStatusUploading denotes that an image's raw data is currently being + // uploaded to Glance through the upload process + ImageStatusUploading ImageStatus = "uploading" + // ImageStatusActive denotes an image that is fully available in Glance. ImageStatusActive ImageStatus = "active" diff --git a/openstack/image/v2/images/urls.go b/openstack/image/v2/images/urls.go index c3007a6129..8b464971e2 100644 --- a/openstack/image/v2/images/urls.go +++ b/openstack/image/v2/images/urls.go @@ -40,8 +40,8 @@ func deleteURL(c *gophercloud.ServiceClient, imageID string) string { } // builds next page full url based on current url -func nextPageURL(serviceURL, requestedNext string) (string, error) { - base, err := utils.BaseEndpoint(serviceURL) +func nextPageURL(endpointURL, requestedNext string) (string, error) { + base, err := utils.BaseEndpoint(endpointURL) if err != nil { return "", err } diff --git a/openstack/image/v2/members/testing/fixtures_test.go b/openstack/image/v2/members/testing/fixtures_test.go index 4100b186f4..8c2f9261a5 100644 --- a/openstack/image/v2/members/testing/fixtures_test.go +++ b/openstack/image/v2/members/testing/fixtures_test.go @@ -6,19 +6,19 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fakeclient "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) // HandleCreateImageMemberSuccessfully setup -func HandleCreateImageMemberSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/members", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateImageMemberSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/members", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{"member": "8989447062e04a818baf9e073fd04fa7"}`) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "created_at": "2013-09-20T19:22:19Z", "image_id": "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", "member_id": "8989447062e04a818baf9e073fd04fa7", @@ -31,13 +31,13 @@ func HandleCreateImageMemberSuccessfully(t *testing.T) { } // HandleImageMemberList happy path setup -func HandleImageMemberList(t *testing.T) { - th.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/members", func(w http.ResponseWriter, r *http.Request) { +func HandleImageMemberList(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/members", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "members": [ { "created_at": "2013-10-07T17:58:03Z", @@ -62,13 +62,13 @@ func HandleImageMemberList(t *testing.T) { } // HandleImageMemberEmptyList happy path setup -func HandleImageMemberEmptyList(t *testing.T) { - th.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/members", func(w http.ResponseWriter, r *http.Request) { +func HandleImageMemberEmptyList(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/members", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "members": [], "schema": "/v2/schemas/members" }`) @@ -76,13 +76,13 @@ func HandleImageMemberEmptyList(t *testing.T) { } // HandleImageMemberDetails setup -func HandleImageMemberDetails(t *testing.T) { - th.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/members/8989447062e04a818baf9e073fd04fa7", func(w http.ResponseWriter, r *http.Request) { +func HandleImageMemberDetails(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/members/8989447062e04a818baf9e073fd04fa7", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "status": "pending", "created_at": "2013-11-26T07:21:21Z", "updated_at": "2013-11-26T07:21:21Z", @@ -94,13 +94,13 @@ func HandleImageMemberDetails(t *testing.T) { } // HandleImageMemberDeleteSuccessfully setup -func HandleImageMemberDeleteSuccessfully(t *testing.T) *CallsCounter { +func HandleImageMemberDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) *CallsCounter { var counter CallsCounter - th.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/members/8989447062e04a818baf9e073fd04fa7", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/members/8989447062e04a818baf9e073fd04fa7", func(w http.ResponseWriter, r *http.Request) { counter.Counter = counter.Counter + 1 th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) @@ -108,19 +108,19 @@ func HandleImageMemberDeleteSuccessfully(t *testing.T) *CallsCounter { } // HandleImageMemberUpdate setup -func HandleImageMemberUpdate(t *testing.T) *CallsCounter { +func HandleImageMemberUpdate(t *testing.T, fakeServer th.FakeServer) *CallsCounter { var counter CallsCounter - th.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/members/8989447062e04a818baf9e073fd04fa7", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/members/8989447062e04a818baf9e073fd04fa7", func(w http.ResponseWriter, r *http.Request) { counter.Counter = counter.Counter + 1 th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{"status": "accepted"}`) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "status": "accepted", "created_at": "2013-11-26T07:21:21Z", "updated_at": "2013-11-26T07:21:21Z", diff --git a/openstack/image/v2/members/testing/requests_test.go b/openstack/image/v2/members/testing/requests_test.go index 273d82581d..6ad8f61836 100644 --- a/openstack/image/v2/members/testing/requests_test.go +++ b/openstack/image/v2/members/testing/requests_test.go @@ -8,18 +8,18 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/image/v2/members" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fakeclient "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) const createdAtString = "2013-09-20T19:22:19Z" const updatedAtString = "2013-09-20T19:25:31Z" func TestCreateMemberSuccessfully(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleCreateImageMemberSuccessfully(t) - im, err := members.Create(context.TODO(), fakeclient.ServiceClient(), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", + HandleCreateImageMemberSuccessfully(t, fakeServer) + im, err := members.Create(context.TODO(), client.ServiceClient(fakeServer), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", "8989447062e04a818baf9e073fd04fa7").Extract() th.AssertNoErr(t, err) @@ -41,12 +41,12 @@ func TestCreateMemberSuccessfully(t *testing.T) { } func TestMemberListSuccessfully(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleImageMemberList(t) + HandleImageMemberList(t, fakeServer) - pager := members.List(fakeclient.ServiceClient(), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea") + pager := members.List(client.ServiceClient(fakeServer), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea") t.Logf("Pager state %v", pager) count, pages := 0, 0 err := pager.EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { @@ -71,12 +71,12 @@ func TestMemberListSuccessfully(t *testing.T) { } func TestMemberListEmpty(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleImageMemberEmptyList(t) + HandleImageMemberEmptyList(t, fakeServer) - pager := members.List(fakeclient.ServiceClient(), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea") + pager := members.List(client.ServiceClient(fakeServer), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea") t.Logf("Pager state %v", pager) count, pages := 0, 0 err := pager.EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { @@ -101,18 +101,15 @@ func TestMemberListEmpty(t *testing.T) { } func TestShowMemberDetails(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleImageMemberDetails(t) - md, err := members.Get(context.TODO(), fakeclient.ServiceClient(), + HandleImageMemberDetails(t, fakeServer) + md, err := members.Get(context.TODO(), client.ServiceClient(fakeServer), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", "8989447062e04a818baf9e073fd04fa7").Extract() th.AssertNoErr(t, err) - if md == nil { - t.Fatalf("Expected non-nil value for md") - } createdAt, err := time.Parse(time.RFC3339, "2013-11-26T07:21:21Z") th.AssertNoErr(t, err) @@ -131,23 +128,23 @@ func TestShowMemberDetails(t *testing.T) { } func TestDeleteMember(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - counter := HandleImageMemberDeleteSuccessfully(t) + counter := HandleImageMemberDeleteSuccessfully(t, fakeServer) - result := members.Delete(context.TODO(), fakeclient.ServiceClient(), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", + result := members.Delete(context.TODO(), client.ServiceClient(fakeServer), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", "8989447062e04a818baf9e073fd04fa7") th.AssertEquals(t, 1, counter.Counter) th.AssertNoErr(t, result.Err) } func TestMemberUpdateSuccessfully(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - counter := HandleImageMemberUpdate(t) - im, err := members.Update(context.TODO(), fakeclient.ServiceClient(), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", + counter := HandleImageMemberUpdate(t, fakeServer) + im, err := members.Update(context.TODO(), client.ServiceClient(fakeServer), "da3b75d9-3f4a-40e7-8a2c-bfab23927dea", "8989447062e04a818baf9e073fd04fa7", members.UpdateOpts{ Status: "accepted", diff --git a/openstack/image/v2/tasks/requests.go b/openstack/image/v2/tasks/requests.go index 80eb69bd1f..029321d44b 100644 --- a/openstack/image/v2/tasks/requests.go +++ b/openstack/image/v2/tasks/requests.go @@ -81,12 +81,7 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { url += query } return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { - taskPage := TaskPage{ - serviceURL: c.ServiceURL(), - LinkedPageBase: pagination.LinkedPageBase{PageResult: r}, - } - - return taskPage + return TaskPage{pagination.LinkedPageBase{PageResult: r}} }) } diff --git a/openstack/image/v2/tasks/results.go b/openstack/image/v2/tasks/results.go index 2e11e5c3a7..c88747567c 100644 --- a/openstack/image/v2/tasks/results.go +++ b/openstack/image/v2/tasks/results.go @@ -75,7 +75,6 @@ func (r commonResult) Extract() (*Task, error) { // TaskPage represents the results of a List request. type TaskPage struct { - serviceURL string pagination.LinkedPageBase } @@ -91,7 +90,7 @@ func (r TaskPage) IsEmpty() (bool, error) { // NextPageURL uses the response's embedded link reference to navigate to // the next page of results. -func (r TaskPage) NextPageURL() (string, error) { +func (r TaskPage) NextPageURL(endpointURL string) (string, error) { var s struct { Next string `json:"next"` } @@ -104,7 +103,7 @@ func (r TaskPage) NextPageURL() (string, error) { return "", nil } - return nextPageURL(r.serviceURL, s.Next) + return nextPageURL(endpointURL, s.Next) } // ExtractTasks interprets the results of a single page from a List() call, diff --git a/openstack/image/v2/tasks/testing/requests_test.go b/openstack/image/v2/tasks/testing/requests_test.go index feb74068bc..3656919982 100644 --- a/openstack/image/v2/tasks/testing/requests_test.go +++ b/openstack/image/v2/tasks/testing/requests_test.go @@ -10,26 +10,26 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/image/v2/tasks" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fakeclient "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/tasks", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/tasks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, TasksListResult) + fmt.Fprint(w, TasksListResult) }) count := 0 - err := tasks.List(fakeclient.ServiceClient(), tasks.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := tasks.List(client.ServiceClient(fakeServer), tasks.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := tasks.ExtractTasks(page) if err != nil { @@ -54,55 +54,55 @@ func TestList(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/tasks/1252f636-1246-4319-bfba-c47cde0efbe0", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/tasks/1252f636-1246-4319-bfba-c47cde0efbe0", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, TasksGetResult) + fmt.Fprint(w, TasksGetResult) }) - s, err := tasks.Get(context.TODO(), fakeclient.ServiceClient(), "1252f636-1246-4319-bfba-c47cde0efbe0").Extract() + s, err := tasks.Get(context.TODO(), client.ServiceClient(fakeServer), "1252f636-1246-4319-bfba-c47cde0efbe0").Extract() th.AssertNoErr(t, err) th.AssertEquals(t, s.Status, string(tasks.TaskStatusPending)) th.AssertEquals(t, s.CreatedAt, time.Date(2018, 7, 25, 8, 59, 13, 0, time.UTC)) th.AssertEquals(t, s.UpdatedAt, time.Date(2018, 7, 25, 8, 59, 14, 0, time.UTC)) - th.AssertEquals(t, s.Self, "/v2/tasks/1252f636-1246-4319-bfba-c47cde0efbe0") - th.AssertEquals(t, s.Owner, "424e7cf0243c468ca61732ba45973b3e") - th.AssertEquals(t, s.Message, "") - th.AssertEquals(t, s.Type, "import") - th.AssertEquals(t, s.ID, "1252f636-1246-4319-bfba-c47cde0efbe0") - th.AssertEquals(t, s.Schema, "/v2/schemas/task") + th.AssertEquals(t, "/v2/tasks/1252f636-1246-4319-bfba-c47cde0efbe0", s.Self) + th.AssertEquals(t, "424e7cf0243c468ca61732ba45973b3e", s.Owner) + th.AssertEquals(t, "", s.Message) + th.AssertEquals(t, "import", s.Type) + th.AssertEquals(t, "1252f636-1246-4319-bfba-c47cde0efbe0", s.ID) + th.AssertEquals(t, "/v2/schemas/task", s.Schema) th.AssertDeepEquals(t, s.Result, map[string]any(nil)) - th.AssertDeepEquals(t, s.Input, map[string]any{ + th.AssertDeepEquals(t, map[string]any{ "import_from": "http://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img", "import_from_format": "raw", "image_properties": map[string]any{ "container_format": "bare", "disk_format": "raw", }, - }) + }, s.Input) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/tasks", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/tasks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, TaskCreateRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, TaskCreateResult) + fmt.Fprint(w, TaskCreateResult) }) opts := tasks.CreateOpts{ @@ -116,25 +116,25 @@ func TestCreate(t *testing.T) { "import_from": "https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64.img", }, } - s, err := tasks.Create(context.TODO(), fakeclient.ServiceClient(), opts).Extract() + s, err := tasks.Create(context.TODO(), client.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, s.Status, string(tasks.TaskStatusPending)) th.AssertEquals(t, s.CreatedAt, time.Date(2018, 7, 25, 11, 7, 54, 0, time.UTC)) th.AssertEquals(t, s.UpdatedAt, time.Date(2018, 7, 25, 11, 7, 54, 0, time.UTC)) - th.AssertEquals(t, s.Self, "/v2/tasks/d550c87d-86ed-430a-9895-c7a1f5ce87e9") - th.AssertEquals(t, s.Owner, "fb57277ef2f84a0e85b9018ec2dedbf7") - th.AssertEquals(t, s.Message, "") - th.AssertEquals(t, s.Type, "import") - th.AssertEquals(t, s.ID, "d550c87d-86ed-430a-9895-c7a1f5ce87e9") - th.AssertEquals(t, s.Schema, "/v2/schemas/task") + th.AssertEquals(t, "/v2/tasks/d550c87d-86ed-430a-9895-c7a1f5ce87e9", s.Self) + th.AssertEquals(t, "fb57277ef2f84a0e85b9018ec2dedbf7", s.Owner) + th.AssertEquals(t, "", s.Message) + th.AssertEquals(t, "import", s.Type) + th.AssertEquals(t, "d550c87d-86ed-430a-9895-c7a1f5ce87e9", s.ID) + th.AssertEquals(t, "/v2/schemas/task", s.Schema) th.AssertDeepEquals(t, s.Result, map[string]any(nil)) - th.AssertDeepEquals(t, s.Input, map[string]any{ + th.AssertDeepEquals(t, map[string]any{ "import_from": "https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64.img", "import_from_format": "raw", "image_properties": map[string]any{ "container_format": "bare", "disk_format": "raw", }, - }) + }, s.Input) } diff --git a/openstack/image/v2/tasks/urls.go b/openstack/image/v2/tasks/urls.go index aa79479396..6f9d03707c 100644 --- a/openstack/image/v2/tasks/urls.go +++ b/openstack/image/v2/tasks/urls.go @@ -30,8 +30,8 @@ func createURL(c *gophercloud.ServiceClient) string { return rootURL(c) } -func nextPageURL(serviceURL, requestedNext string) (string, error) { - base, err := utils.BaseEndpoint(serviceURL) +func nextPageURL(endpointURL, requestedNext string) (string, error) { + base, err := utils.BaseEndpoint(endpointURL) if err != nil { return "", err } diff --git a/openstack/keymanager/v1/acls/testing/fixtures_test.go b/openstack/keymanager/v1/acls/testing/fixtures_test.go index 7db2b37b4f..cc2624985b 100644 --- a/openstack/keymanager/v1/acls/testing/fixtures_test.go +++ b/openstack/keymanager/v1/acls/testing/fixtures_test.go @@ -67,88 +67,88 @@ const UpdateRequest = ` // HandleGetSecretACLSuccessfully creates an HTTP handler at `/secrets/uuid/acl` // on the test handler mux that responds with an acl. -func HandleGetSecretACLSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { +func HandleGetSecretACLSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) } // HandleGetContainerACLSuccessfully creates an HTTP handler at `/secrets/uuid/acl` // on the test handler mux that responds with an acl. -func HandleGetContainerACLSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/containers/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { +func HandleGetContainerACLSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/containers/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) } // HandleSetSecretACLSuccessfully creates an HTTP handler at `/secrets` on the // test handler mux that tests secret creation. -func HandleSetSecretACLSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { +func HandleSetSecretACLSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, SetRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, SecretSetResponse) + fmt.Fprint(w, SecretSetResponse) }) } // HandleSetContainerACLSuccessfully creates an HTTP handler at `/secrets` on the // test handler mux that tests secret creation. -func HandleSetContainerACLSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/containers/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { +func HandleSetContainerACLSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/containers/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, SetRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ContainerSetResponse) + fmt.Fprint(w, ContainerSetResponse) }) } // HandleUpdateSecretACLSuccessfully creates an HTTP handler at `/secrets` on the // test handler mux that tests secret creation. -func HandleUpdateSecretACLSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { +func HandleUpdateSecretACLSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, SecretSetResponse) + fmt.Fprint(w, SecretSetResponse) }) } // HandleUpdateContainerACLSuccessfully creates an HTTP handler at `/secrets` on the // test handler mux that tests secret creation. -func HandleUpdateContainerACLSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/containers/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { +func HandleUpdateContainerACLSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/containers/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ContainerSetResponse) + fmt.Fprint(w, ContainerSetResponse) }) } // HandleDeleteSecretACLSuccessfully creates an HTTP handler at `/secrets` on the // test handler mux that tests secret deletion. -func HandleDeleteSecretACLSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteSecretACLSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -158,8 +158,8 @@ func HandleDeleteSecretACLSuccessfully(t *testing.T) { // HandleDeleteContainerACLSuccessfully creates an HTTP handler at `/secrets` on the // test handler mux that tests secret deletion. -func HandleDeleteContainerACLSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/containers/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteContainerACLSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/containers/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/acl", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) diff --git a/openstack/keymanager/v1/acls/testing/requests_test.go b/openstack/keymanager/v1/acls/testing/requests_test.go index 3367381520..6b8237fb06 100644 --- a/openstack/keymanager/v1/acls/testing/requests_test.go +++ b/openstack/keymanager/v1/acls/testing/requests_test.go @@ -10,29 +10,29 @@ import ( ) func TestGetSecretACL(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSecretACLSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSecretACLSuccessfully(t, fakeServer) - actual, err := acls.GetSecretACL(context.TODO(), client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c").Extract() + actual, err := acls.GetSecretACL(context.TODO(), client.ServiceClient(fakeServer), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, ExpectedACL, *actual) } func TestGetContainerACL(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetContainerACLSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetContainerACLSuccessfully(t, fakeServer) - actual, err := acls.GetContainerACL(context.TODO(), client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c").Extract() + actual, err := acls.GetContainerACL(context.TODO(), client.ServiceClient(fakeServer), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, ExpectedACL, *actual) } func TestSetSecretACL(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleSetSecretACLSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleSetSecretACLSuccessfully(t, fakeServer) users := []string{"GG27dVwR9gBMnsOaRoJ1DFJmZfdVjIdW"} iFalse := false @@ -44,15 +44,15 @@ func TestSetSecretACL(t *testing.T) { }, } - actual, err := acls.SetSecretACL(context.TODO(), client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", setOpts).Extract() + actual, err := acls.SetSecretACL(context.TODO(), client.ServiceClient(fakeServer), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", setOpts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, ExpectedSecretACLRef, *actual) } func TestSetContainerACL(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleSetContainerACLSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleSetContainerACLSuccessfully(t, fakeServer) users := []string{"GG27dVwR9gBMnsOaRoJ1DFJmZfdVjIdW"} iFalse := false @@ -64,33 +64,33 @@ func TestSetContainerACL(t *testing.T) { }, } - actual, err := acls.SetContainerACL(context.TODO(), client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", setOpts).Extract() + actual, err := acls.SetContainerACL(context.TODO(), client.ServiceClient(fakeServer), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", setOpts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, ExpectedContainerACLRef, *actual) } func TestDeleteSecretACL(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteSecretACLSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteSecretACLSuccessfully(t, fakeServer) - res := acls.DeleteSecretACL(context.TODO(), client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c") + res := acls.DeleteSecretACL(context.TODO(), client.ServiceClient(fakeServer), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c") th.AssertNoErr(t, res.Err) } func TestDeleteContainerACL(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteContainerACLSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteContainerACLSuccessfully(t, fakeServer) - res := acls.DeleteContainerACL(context.TODO(), client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c") + res := acls.DeleteContainerACL(context.TODO(), client.ServiceClient(fakeServer), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c") th.AssertNoErr(t, res.Err) } func TestUpdateSecretACL(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateSecretACLSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateSecretACLSuccessfully(t, fakeServer) newUsers := []string{} updateOpts := acls.SetOpts{ @@ -100,15 +100,15 @@ func TestUpdateSecretACL(t *testing.T) { }, } - actual, err := acls.UpdateSecretACL(context.TODO(), client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", updateOpts).Extract() + actual, err := acls.UpdateSecretACL(context.TODO(), client.ServiceClient(fakeServer), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", updateOpts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, ExpectedSecretACLRef, *actual) } func TestUpdateContainerACL(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateContainerACLSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateContainerACLSuccessfully(t, fakeServer) newUsers := []string{} updateOpts := acls.SetOpts{ @@ -118,7 +118,7 @@ func TestUpdateContainerACL(t *testing.T) { }, } - actual, err := acls.UpdateContainerACL(context.TODO(), client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", updateOpts).Extract() + actual, err := acls.UpdateContainerACL(context.TODO(), client.ServiceClient(fakeServer), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", updateOpts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, ExpectedContainerACLRef, *actual) } diff --git a/openstack/keymanager/v1/containers/requests.go b/openstack/keymanager/v1/containers/requests.go index a611ef91e0..5797153b14 100644 --- a/openstack/keymanager/v1/containers/requests.go +++ b/openstack/keymanager/v1/containers/requests.go @@ -123,7 +123,7 @@ type ListConsumersOpts struct { // ToContainerListConsumersQuery formats a ListConsumersOpts into a query // string. -func (opts ListOpts) ToContainerListConsumersQuery() (string, error) { +func (opts ListConsumersOpts) ToContainerListConsumersQuery() (string, error) { q, err := gophercloud.BuildQueryString(opts) return q.String(), err } diff --git a/openstack/keymanager/v1/containers/results.go b/openstack/keymanager/v1/containers/results.go index cf8ab3b678..6f13c917b6 100644 --- a/openstack/keymanager/v1/containers/results.go +++ b/openstack/keymanager/v1/containers/results.go @@ -117,7 +117,7 @@ func (r ContainerPage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (r ContainerPage) NextPageURL() (string, error) { +func (r ContainerPage) NextPageURL(endpointURL string) (string, error) { var s struct { Next string `json:"next"` Previous string `json:"previous"` @@ -206,7 +206,7 @@ func (r ConsumerPage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (r ConsumerPage) NextPageURL() (string, error) { +func (r ConsumerPage) NextPageURL(endpointURL string) (string, error) { var s struct { Next string `json:"next"` Previous string `json:"previous"` diff --git a/openstack/keymanager/v1/containers/testing/fixtures_test.go b/openstack/keymanager/v1/containers/testing/fixtures_test.go index 4b3997f786..a253e0bfda 100644 --- a/openstack/keymanager/v1/containers/testing/fixtures_test.go +++ b/openstack/keymanager/v1/containers/testing/fixtures_test.go @@ -227,49 +227,49 @@ const DeleteConsumerRequest = ` // HandleListContainersSuccessfully creates an HTTP handler at `/containers` on the // test handler mux that responds with a list of two containers. -func HandleListContainersSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/containers", func(w http.ResponseWriter, r *http.Request) { +func HandleListContainersSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/containers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) } // HandleGetContainerSuccessfully creates an HTTP handler at `/containers` on the // test handler mux that responds with a single resource. -func HandleGetContainerSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/containers/dfdb88f3-4ddb-4525-9da6-066453caa9b0", func(w http.ResponseWriter, r *http.Request) { +func HandleGetContainerSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/containers/dfdb88f3-4ddb-4525-9da6-066453caa9b0", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) } // HandleCreateContainerSuccessfully creates an HTTP handler at `/containers` on the // test handler mux that tests resource creation. -func HandleCreateContainerSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/containers", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateContainerSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/containers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) } // HandleDeleteContainerSuccessfully creates an HTTP handler at `/containers` on the // test handler mux that tests resource deletion. -func HandleDeleteContainerSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/containers/dfdb88f3-4ddb-4525-9da6-066453caa9b0", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteContainerSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/containers/dfdb88f3-4ddb-4525-9da6-066453caa9b0", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -280,42 +280,42 @@ func HandleDeleteContainerSuccessfully(t *testing.T) { // HandleListConsumersSuccessfully creates an HTTP handler at // `/containers/uuid/consumers` on the test handler mux that responds with // a list of consumers. -func HandleListConsumersSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/containers/dfdb88f3-4ddb-4525-9da6-066453caa9b0/consumers", func(w http.ResponseWriter, r *http.Request) { +func HandleListConsumersSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/containers/dfdb88f3-4ddb-4525-9da6-066453caa9b0/consumers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListConsumersResponse) + fmt.Fprint(w, ListConsumersResponse) }) } // HandleCreateConsumerSuccessfully creates an HTTP handler at // `/containers/uuid/consumers` on the test handler mux that tests resource // creation. -func HandleCreateConsumerSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/containers/dfdb88f3-4ddb-4525-9da6-066453caa9b0/consumers", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateConsumerSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/containers/dfdb88f3-4ddb-4525-9da6-066453caa9b0/consumers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateConsumerRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, CreateConsumerResponse) + fmt.Fprint(w, CreateConsumerResponse) }) } // HandleDeleteConsumerSuccessfully creates an HTTP handler at // `/containers/uuid/consumers` on the test handler mux that tests resource // deletion. -func HandleDeleteConsumerSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/containers/dfdb88f3-4ddb-4525-9da6-066453caa9b0/consumers", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteConsumerSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/containers/dfdb88f3-4ddb-4525-9da6-066453caa9b0/consumers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateConsumerRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) } diff --git a/openstack/keymanager/v1/containers/testing/requests_test.go b/openstack/keymanager/v1/containers/testing/requests_test.go index 44ed27b217..3fb490c876 100644 --- a/openstack/keymanager/v1/containers/testing/requests_test.go +++ b/openstack/keymanager/v1/containers/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestListContainers(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListContainersSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListContainersSuccessfully(t, fakeServer) count := 0 - err := containers.List(client.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := containers.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := containers.ExtractContainers(page) @@ -27,15 +27,15 @@ func TestListContainers(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.AssertEquals(t, count, 1) + th.AssertEquals(t, 1, count) } func TestListContainersAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListContainersSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListContainersSuccessfully(t, fakeServer) - allPages, err := containers.List(client.ServiceClient(), nil).AllPages(context.TODO()) + allPages, err := containers.List(client.ServiceClient(fakeServer), nil).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := containers.ExtractContainers(allPages) th.AssertNoErr(t, err) @@ -43,19 +43,19 @@ func TestListContainersAllPages(t *testing.T) { } func TestGetContainer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetContainerSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetContainerSuccessfully(t, fakeServer) - actual, err := containers.Get(context.TODO(), client.ServiceClient(), "dfdb88f3-4ddb-4525-9da6-066453caa9b0").Extract() + actual, err := containers.Get(context.TODO(), client.ServiceClient(fakeServer), "dfdb88f3-4ddb-4525-9da6-066453caa9b0").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, FirstContainer, *actual) } func TestCreateContainer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateContainerSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateContainerSuccessfully(t, fakeServer) createOpts := containers.CreateOpts{ Type: containers.GenericContainer, @@ -68,27 +68,27 @@ func TestCreateContainer(t *testing.T) { }, } - actual, err := containers.Create(context.TODO(), client.ServiceClient(), createOpts).Extract() + actual, err := containers.Create(context.TODO(), client.ServiceClient(fakeServer), createOpts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, FirstContainer, *actual) } func TestDeleteContainer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteContainerSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteContainerSuccessfully(t, fakeServer) - res := containers.Delete(context.TODO(), client.ServiceClient(), "dfdb88f3-4ddb-4525-9da6-066453caa9b0") + res := containers.Delete(context.TODO(), client.ServiceClient(fakeServer), "dfdb88f3-4ddb-4525-9da6-066453caa9b0") th.AssertNoErr(t, res.Err) } func TestListConsumers(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListConsumersSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListConsumersSuccessfully(t, fakeServer) count := 0 - err := containers.ListConsumers(client.ServiceClient(), "dfdb88f3-4ddb-4525-9da6-066453caa9b0", nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := containers.ListConsumers(client.ServiceClient(fakeServer), "dfdb88f3-4ddb-4525-9da6-066453caa9b0", nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := containers.ExtractConsumers(page) @@ -99,15 +99,15 @@ func TestListConsumers(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.AssertEquals(t, count, 1) + th.AssertEquals(t, 1, count) } func TestListConsumersAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListConsumersSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListConsumersSuccessfully(t, fakeServer) - allPages, err := containers.ListConsumers(client.ServiceClient(), "dfdb88f3-4ddb-4525-9da6-066453caa9b0", nil).AllPages(context.TODO()) + allPages, err := containers.ListConsumers(client.ServiceClient(fakeServer), "dfdb88f3-4ddb-4525-9da6-066453caa9b0", nil).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := containers.ExtractConsumers(allPages) th.AssertNoErr(t, err) @@ -115,31 +115,31 @@ func TestListConsumersAllPages(t *testing.T) { } func TestCreateConsumer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateConsumerSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateConsumerSuccessfully(t, fakeServer) createOpts := containers.CreateConsumerOpts{ Name: "CONSUMER-LZILN1zq", URL: "http://example.com", } - actual, err := containers.CreateConsumer(context.TODO(), client.ServiceClient(), "dfdb88f3-4ddb-4525-9da6-066453caa9b0", createOpts).Extract() + actual, err := containers.CreateConsumer(context.TODO(), client.ServiceClient(fakeServer), "dfdb88f3-4ddb-4525-9da6-066453caa9b0", createOpts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, ExpectedCreatedConsumer, *actual) } func TestDeleteConsumer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteConsumerSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteConsumerSuccessfully(t, fakeServer) deleteOpts := containers.DeleteConsumerOpts{ Name: "CONSUMER-LZILN1zq", URL: "http://example.com", } - actual, err := containers.DeleteConsumer(context.TODO(), client.ServiceClient(), "dfdb88f3-4ddb-4525-9da6-066453caa9b0", deleteOpts).Extract() + actual, err := containers.DeleteConsumer(context.TODO(), client.ServiceClient(fakeServer), "dfdb88f3-4ddb-4525-9da6-066453caa9b0", deleteOpts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, FirstContainer, *actual) } diff --git a/openstack/keymanager/v1/orders/results.go b/openstack/keymanager/v1/orders/results.go index 3cd3a2ec11..0c191f9fb9 100644 --- a/openstack/keymanager/v1/orders/results.go +++ b/openstack/keymanager/v1/orders/results.go @@ -144,7 +144,7 @@ func (r OrderPage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (r OrderPage) NextPageURL() (string, error) { +func (r OrderPage) NextPageURL(endpointURL string) (string, error) { var s struct { Next string `json:"next"` Previous string `json:"previous"` diff --git a/openstack/keymanager/v1/orders/testing/fixtures_test.go b/openstack/keymanager/v1/orders/testing/fixtures_test.go index 636fe7eb3f..40d55c21f4 100644 --- a/openstack/keymanager/v1/orders/testing/fixtures_test.go +++ b/openstack/keymanager/v1/orders/testing/fixtures_test.go @@ -135,49 +135,49 @@ var ExpectedOrdersSlice = []orders.Order{FirstOrder, SecondOrder} // HandleListOrdersSuccessfully creates an HTTP handler at `/orders` on the // test handler mux that responds with a list of two orders. -func HandleListOrdersSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/orders", func(w http.ResponseWriter, r *http.Request) { +func HandleListOrdersSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/orders", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) } // HandleGetOrderSuccessfully creates an HTTP handler at `/orders` on the // test handler mux that responds with a single resource. -func HandleGetOrderSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/orders/46f73695-82bb-447a-bf96-6635f0fb0ce7", func(w http.ResponseWriter, r *http.Request) { +func HandleGetOrderSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/orders/46f73695-82bb-447a-bf96-6635f0fb0ce7", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) } // HandleCreateOrderSuccessfully creates an HTTP handler at `/orders` on the // test handler mux that tests resource creation. -func HandleCreateOrderSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/orders", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateOrderSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/orders", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) } // HandleDeleteOrderSuccessfully creates an HTTP handler at `/orders` on the // test handler mux that tests resource deletion. -func HandleDeleteOrderSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/orders/46f73695-82bb-447a-bf96-6635f0fb0ce7", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteOrderSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/orders/46f73695-82bb-447a-bf96-6635f0fb0ce7", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) diff --git a/openstack/keymanager/v1/orders/testing/requests_test.go b/openstack/keymanager/v1/orders/testing/requests_test.go index df2b10d9c1..1267cd3283 100644 --- a/openstack/keymanager/v1/orders/testing/requests_test.go +++ b/openstack/keymanager/v1/orders/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestListOrders(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListOrdersSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListOrdersSuccessfully(t, fakeServer) count := 0 - err := orders.List(client.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := orders.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := orders.ExtractOrders(page) @@ -27,15 +27,15 @@ func TestListOrders(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.AssertEquals(t, count, 1) + th.AssertEquals(t, 1, count) } func TestListOrdersAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListOrdersSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListOrdersSuccessfully(t, fakeServer) - allPages, err := orders.List(client.ServiceClient(), nil).AllPages(context.TODO()) + allPages, err := orders.List(client.ServiceClient(fakeServer), nil).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := orders.ExtractOrders(allPages) th.AssertNoErr(t, err) @@ -43,19 +43,19 @@ func TestListOrdersAllPages(t *testing.T) { } func TestGetOrder(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetOrderSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetOrderSuccessfully(t, fakeServer) - actual, err := orders.Get(context.TODO(), client.ServiceClient(), "46f73695-82bb-447a-bf96-6635f0fb0ce7").Extract() + actual, err := orders.Get(context.TODO(), client.ServiceClient(fakeServer), "46f73695-82bb-447a-bf96-6635f0fb0ce7").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, SecondOrder, *actual) } func TestCreateOrder(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateOrderSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateOrderSuccessfully(t, fakeServer) createOpts := orders.CreateOpts{ Type: orders.KeyOrder, @@ -67,16 +67,16 @@ func TestCreateOrder(t *testing.T) { }, } - actual, err := orders.Create(context.TODO(), client.ServiceClient(), createOpts).Extract() + actual, err := orders.Create(context.TODO(), client.ServiceClient(fakeServer), createOpts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, SecondOrder, *actual) } func TestDeleteOrder(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteOrderSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteOrderSuccessfully(t, fakeServer) - res := orders.Delete(context.TODO(), client.ServiceClient(), "46f73695-82bb-447a-bf96-6635f0fb0ce7") + res := orders.Delete(context.TODO(), client.ServiceClient(fakeServer), "46f73695-82bb-447a-bf96-6635f0fb0ce7") th.AssertNoErr(t, res.Err) } diff --git a/openstack/keymanager/v1/secrets/results.go b/openstack/keymanager/v1/secrets/results.go index 82facc0dee..5b6a88d6ce 100644 --- a/openstack/keymanager/v1/secrets/results.go +++ b/openstack/keymanager/v1/secrets/results.go @@ -144,7 +144,7 @@ func (r SecretPage) IsEmpty() (bool, error) { } // NextPageURL extracts the "next" link from the links section of the result. -func (r SecretPage) NextPageURL() (string, error) { +func (r SecretPage) NextPageURL(endpointURL string) (string, error) { var s struct { Next string `json:"next"` Previous string `json:"previous"` diff --git a/openstack/keymanager/v1/secrets/testing/fixtures_test.go b/openstack/keymanager/v1/secrets/testing/fixtures_test.go index ff13102354..e20732515d 100644 --- a/openstack/keymanager/v1/secrets/testing/fixtures_test.go +++ b/openstack/keymanager/v1/secrets/testing/fixtures_test.go @@ -190,62 +190,62 @@ var ExpectedMetadatum = secrets.Metadatum{ // HandleListSecretsSuccessfully creates an HTTP handler at `/secrets` on the // test handler mux that responds with a list of two secrets. -func HandleListSecretsSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/secrets", func(w http.ResponseWriter, r *http.Request) { +func HandleListSecretsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/secrets", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) } // HandleGetSecretSuccessfully creates an HTTP handler at `/secrets` on the // test handler mux that responds with a single secret. -func HandleGetSecretSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", func(w http.ResponseWriter, r *http.Request) { +func HandleGetSecretSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) } // HandleGetPayloadSuccessfully creates an HTTP handler at `/secrets` on the // test handler mux that responds with a single secret. -func HandleGetPayloadSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/payload", func(w http.ResponseWriter, r *http.Request) { +func HandleGetPayloadSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/payload", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetPayloadResponse) + fmt.Fprint(w, GetPayloadResponse) }) } // HandleCreateSecretSuccessfully creates an HTTP handler at `/secrets` on the // test handler mux that tests secret creation. -func HandleCreateSecretSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/secrets", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateSecretSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/secrets", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) } // HandleDeleteSecretSuccessfully creates an HTTP handler at `/secrets` on the // test handler mux that tests secret deletion. -func HandleDeleteSecretSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteSecretSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -255,8 +255,8 @@ func HandleDeleteSecretSuccessfully(t *testing.T) { // HandleUpdateSecretSuccessfully creates an HTTP handler at `/secrets` on the // test handler mux that tests secret updates. -func HandleUpdateSecretSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", func(w http.ResponseWriter, r *http.Request) { +func HandleUpdateSecretSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestBody(t, r, `foobar`) @@ -269,23 +269,23 @@ func HandleUpdateSecretSuccessfully(t *testing.T) { // HandleGetMetadataSuccessfully creates an HTTP handler at // `/secrets/uuid/metadata` on the test handler mux that responds with // retrieved metadata. -func HandleGetMetadataSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/metadata", func(w http.ResponseWriter, r *http.Request) { +func HandleGetMetadataSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/metadata", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetMetadataResponse) + fmt.Fprint(w, GetMetadataResponse) }) } // HandleCreateMetadataSuccessfully creates an HTTP handler at // `/secrets/uuid/metadata` on the test handler mux that responds with // a metadata reference URL. -func HandleCreateMetadataSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/metadata", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateMetadataSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/metadata", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -293,30 +293,30 @@ func HandleCreateMetadataSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateMetadataResponse) + fmt.Fprint(w, CreateMetadataResponse) }) } // HandleGetMetadatumSuccessfully creates an HTTP handler at // `/secrets/uuid/metadata/foo` on the test handler mux that responds with a // single metadatum. -func HandleGetMetadatumSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/metadata/foo", func(w http.ResponseWriter, r *http.Request) { +func HandleGetMetadatumSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/metadata/foo", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, MetadatumResponse) + fmt.Fprint(w, MetadatumResponse) }) } // HandleCreateMetadatumSuccessfully creates an HTTP handler at // `/secrets/uuid/metadata` on the test handler mux that responds with // a single created metadata. -func HandleCreateMetadatumSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/metadata", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateMetadatumSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/metadata", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -324,15 +324,15 @@ func HandleCreateMetadatumSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, MetadatumResponse) + fmt.Fprint(w, MetadatumResponse) }) } // HandleUpdateMetadatumSuccessfully creates an HTTP handler at // `/secrets/uuid/metadata/foo` on the test handler mux that responds with a // single updated metadatum. -func HandleUpdateMetadatumSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/metadata/foo", func(w http.ResponseWriter, r *http.Request) { +func HandleUpdateMetadatumSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/metadata/foo", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -340,15 +340,15 @@ func HandleUpdateMetadatumSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, MetadatumResponse) + fmt.Fprint(w, MetadatumResponse) }) } // HandleDeleteMetadatumSuccessfully creates an HTTP handler at // `/secrets/uuid/metadata/key` on the test handler mux that tests metadata // deletion. -func HandleDeleteMetadatumSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/metadata/foo", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteMetadatumSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/secrets/1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c/metadata/foo", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) diff --git a/openstack/keymanager/v1/secrets/testing/requests_test.go b/openstack/keymanager/v1/secrets/testing/requests_test.go index 0458354bc5..5c6e408396 100644 --- a/openstack/keymanager/v1/secrets/testing/requests_test.go +++ b/openstack/keymanager/v1/secrets/testing/requests_test.go @@ -12,12 +12,12 @@ import ( ) func TestListSecrets(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListSecretsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSecretsSuccessfully(t, fakeServer) count := 0 - err := secrets.List(client.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := secrets.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := secrets.ExtractSecrets(page) @@ -28,15 +28,15 @@ func TestListSecrets(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.AssertEquals(t, count, 1) + th.AssertEquals(t, 1, count) } func TestListSecretsAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListSecretsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSecretsSuccessfully(t, fakeServer) - allPages, err := secrets.List(client.ServiceClient(), nil).AllPages(context.TODO()) + allPages, err := secrets.List(client.ServiceClient(fakeServer), nil).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := secrets.ExtractSecrets(allPages) th.AssertNoErr(t, err) @@ -44,19 +44,19 @@ func TestListSecretsAllPages(t *testing.T) { } func TestGetSecret(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSecretSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSecretSuccessfully(t, fakeServer) - actual, err := secrets.Get(context.TODO(), client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c").Extract() + actual, err := secrets.Get(context.TODO(), client.ServiceClient(fakeServer), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, FirstSecret, *actual) } func TestCreateSecret(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateSecretSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateSecretSuccessfully(t, fakeServer) expiration := time.Date(2028, 6, 21, 2, 49, 48, 0, time.UTC) createOpts := secrets.CreateOpts{ @@ -70,39 +70,39 @@ func TestCreateSecret(t *testing.T) { Expiration: &expiration, } - actual, err := secrets.Create(context.TODO(), client.ServiceClient(), createOpts).Extract() + actual, err := secrets.Create(context.TODO(), client.ServiceClient(fakeServer), createOpts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, ExpectedCreateResult, *actual) } func TestDeleteSecret(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteSecretSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteSecretSuccessfully(t, fakeServer) - res := secrets.Delete(context.TODO(), client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c") + res := secrets.Delete(context.TODO(), client.ServiceClient(fakeServer), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c") th.AssertNoErr(t, res.Err) } func TestUpdateSecret(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateSecretSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateSecretSuccessfully(t, fakeServer) updateOpts := secrets.UpdateOpts{ Payload: "foobar", } - err := secrets.Update(context.TODO(), client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", updateOpts).ExtractErr() + err := secrets.Update(context.TODO(), client.ServiceClient(fakeServer), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", updateOpts).ExtractErr() th.AssertNoErr(t, err) } func TestGetPayloadSecret(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetPayloadSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetPayloadSuccessfully(t, fakeServer) - res := secrets.GetPayload(context.TODO(), client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", nil) + res := secrets.GetPayload(context.TODO(), client.ServiceClient(fakeServer), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", nil) th.AssertNoErr(t, res.Err) payload, err := res.Extract() th.AssertNoErr(t, err) @@ -110,74 +110,74 @@ func TestGetPayloadSecret(t *testing.T) { } func TestGetMetadataSuccessfully(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetMetadataSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetMetadataSuccessfully(t, fakeServer) - actual, err := secrets.GetMetadata(context.TODO(), client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c").Extract() + actual, err := secrets.GetMetadata(context.TODO(), client.ServiceClient(fakeServer), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, ExpectedMetadata, actual) } func TestCreateMetadataSuccessfully(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateMetadataSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateMetadataSuccessfully(t, fakeServer) createOpts := secrets.MetadataOpts{ "foo": "bar", "something": "something else", } - actual, err := secrets.CreateMetadata(context.TODO(), client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", createOpts).Extract() + actual, err := secrets.CreateMetadata(context.TODO(), client.ServiceClient(fakeServer), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", createOpts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, ExpectedCreateMetadataResult, actual) } func TestGetMetadatumSuccessfully(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetMetadatumSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetMetadatumSuccessfully(t, fakeServer) - actual, err := secrets.GetMetadatum(context.TODO(), client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", "foo").Extract() + actual, err := secrets.GetMetadatum(context.TODO(), client.ServiceClient(fakeServer), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", "foo").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, ExpectedMetadatum, *actual) } func TestCreateMetadatumSuccessfully(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateMetadatumSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateMetadatumSuccessfully(t, fakeServer) createOpts := secrets.MetadatumOpts{ Key: "foo", Value: "bar", } - err := secrets.CreateMetadatum(context.TODO(), client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", createOpts).ExtractErr() + err := secrets.CreateMetadatum(context.TODO(), client.ServiceClient(fakeServer), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", createOpts).ExtractErr() th.AssertNoErr(t, err) } func TestUpdateMetadatumSuccessfully(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateMetadatumSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateMetadatumSuccessfully(t, fakeServer) updateOpts := secrets.MetadatumOpts{ Key: "foo", Value: "bar", } - actual, err := secrets.UpdateMetadatum(context.TODO(), client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", updateOpts).Extract() + actual, err := secrets.UpdateMetadatum(context.TODO(), client.ServiceClient(fakeServer), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", updateOpts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, ExpectedMetadatum, *actual) } func TestDeleteMetadatumSuccessfully(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteMetadatumSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteMetadatumSuccessfully(t, fakeServer) - err := secrets.DeleteMetadatum(context.TODO(), client.ServiceClient(), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", "foo").ExtractErr() + err := secrets.DeleteMetadatum(context.TODO(), client.ServiceClient(fakeServer), "1b8068c4-3bb6-4be6-8f1e-da0d1ea0b67c", "foo").ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/loadbalancer/v2/amphorae/results.go b/openstack/loadbalancer/v2/amphorae/results.go index 113fe4a9bf..18ec7fa399 100644 --- a/openstack/loadbalancer/v2/amphorae/results.go +++ b/openstack/loadbalancer/v2/amphorae/results.go @@ -100,7 +100,7 @@ type AmphoraPage struct { // NextPageURL is invoked when a paginated collection of amphoraes has // reached the end of a page and the pager seeks to traverse over a new one. // In order to do this, it needs to construct the next page's URL. -func (r AmphoraPage) NextPageURL() (string, error) { +func (r AmphoraPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"amphorae_links"` } diff --git a/openstack/loadbalancer/v2/amphorae/testing/fixtures_test.go b/openstack/loadbalancer/v2/amphorae/testing/fixtures_test.go index 8ffafa253e..7c18ccea4e 100644 --- a/openstack/loadbalancer/v2/amphorae/testing/fixtures_test.go +++ b/openstack/loadbalancer/v2/amphorae/testing/fixtures_test.go @@ -138,8 +138,8 @@ var SecondAmphora = amphorae.Amphora{ var ExpectedAmphoraeSlice = []amphorae.Amphora{FirstAmphora, SecondAmphora} // HandleAmphoraListSuccessfully sets up the test server to respond to a amphorae List request. -func HandleAmphoraListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/octavia/amphorae", func(w http.ResponseWriter, r *http.Request) { +func HandleAmphoraListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/octavia/amphorae", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -150,9 +150,9 @@ func HandleAmphoraListSuccessfully(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, AmphoraeListBody) + fmt.Fprint(w, AmphoraeListBody) case "7f890893-ced0-46ed-8697-33415d070e5a": - fmt.Fprintf(w, `{ "amphorae": [] }`) + fmt.Fprint(w, `{ "amphorae": [] }`) default: t.Fatalf("/v2.0/octavia/amphorae invoked with unexpected marker=[%s]", marker) } @@ -160,19 +160,19 @@ func HandleAmphoraListSuccessfully(t *testing.T) { } // HandleAmphoraGetSuccessfully sets up the test server to respond to am amphora Get request. -func HandleAmphoraGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/octavia/amphorae/45f40289-0551-483a-b089-47214bc2a8a4", func(w http.ResponseWriter, r *http.Request) { +func HandleAmphoraGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/octavia/amphorae/45f40289-0551-483a-b089-47214bc2a8a4", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleAmphoraBody) + fmt.Fprint(w, SingleAmphoraBody) }) } // HandleAmphoraFailoverSuccessfully sets up the test server to respond to an amphora failover request. -func HandleAmphoraFailoverSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/octavia/amphorae/36e08a3e-a78f-4b40-a229-1e7e23eee1ab/failover", func(w http.ResponseWriter, r *http.Request) { +func HandleAmphoraFailoverSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/octavia/amphorae/36e08a3e-a78f-4b40-a229-1e7e23eee1ab/failover", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) diff --git a/openstack/loadbalancer/v2/amphorae/testing/requests_test.go b/openstack/loadbalancer/v2/amphorae/testing/requests_test.go index bc5feed401..bd65f0d2f6 100644 --- a/openstack/loadbalancer/v2/amphorae/testing/requests_test.go +++ b/openstack/loadbalancer/v2/amphorae/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestListAmphorae(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAmphoraListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAmphoraListSuccessfully(t, fakeServer) pages := 0 - err := amphorae.List(fake.ServiceClient(), amphorae.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := amphorae.List(fake.ServiceClient(fakeServer), amphorae.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := amphorae.ExtractAmphorae(page) @@ -39,11 +39,11 @@ func TestListAmphorae(t *testing.T) { } func TestListAllAmphorae(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAmphoraListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAmphoraListSuccessfully(t, fakeServer) - allPages, err := amphorae.List(fake.ServiceClient(), amphorae.ListOpts{}).AllPages(context.TODO()) + allPages, err := amphorae.List(fake.ServiceClient(fakeServer), amphorae.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := amphorae.ExtractAmphorae(allPages) th.AssertNoErr(t, err) @@ -52,11 +52,11 @@ func TestListAllAmphorae(t *testing.T) { } func TestGetAmphora(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAmphoraGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAmphoraGetSuccessfully(t, fakeServer) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) actual, err := amphorae.Get(context.TODO(), client, "45f40289-0551-483a-b089-47214bc2a8a4").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) @@ -66,10 +66,10 @@ func TestGetAmphora(t *testing.T) { } func TestFailoverAmphora(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAmphoraFailoverSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAmphoraFailoverSuccessfully(t, fakeServer) - res := amphorae.Failover(context.TODO(), fake.ServiceClient(), "36e08a3e-a78f-4b40-a229-1e7e23eee1ab") + res := amphorae.Failover(context.TODO(), fake.ServiceClient(fakeServer), "36e08a3e-a78f-4b40-a229-1e7e23eee1ab") th.AssertNoErr(t, res.Err) } diff --git a/openstack/loadbalancer/v2/apiversions/results.go b/openstack/loadbalancer/v2/apiversions/results.go index 4d3d1ae222..11780a2a4d 100644 --- a/openstack/loadbalancer/v2/apiversions/results.go +++ b/openstack/loadbalancer/v2/apiversions/results.go @@ -5,7 +5,7 @@ import "github.com/gophercloud/gophercloud/v2/pagination" // APIVersion represents an API version for load balancer. It contains // the status of the API, and its unique ID. type APIVersion struct { - Status string `son:"status"` + Status string `json:"status"` ID string `json:"id"` } diff --git a/openstack/loadbalancer/v2/apiversions/testing/fixture.go b/openstack/loadbalancer/v2/apiversions/testing/fixture.go index d21507dbab..61db7b7d02 100644 --- a/openstack/loadbalancer/v2/apiversions/testing/fixture.go +++ b/openstack/loadbalancer/v2/apiversions/testing/fixture.go @@ -80,14 +80,14 @@ var OctaviaAllAPIVersionResults = []apiversions.APIVersion{ }, } -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { +func MockListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, OctaviaAllAPIVersionsResponse) + fmt.Fprint(w, OctaviaAllAPIVersionsResponse) }) } diff --git a/openstack/loadbalancer/v2/apiversions/testing/requests_test.go b/openstack/loadbalancer/v2/apiversions/testing/requests_test.go index c0e3aa181c..4a8da72dc1 100644 --- a/openstack/loadbalancer/v2/apiversions/testing/requests_test.go +++ b/openstack/loadbalancer/v2/apiversions/testing/requests_test.go @@ -10,12 +10,12 @@ import ( ) func TestListVersions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) - allVersions, err := apiversions.List(client.ServiceClient()).AllPages(context.TODO()) + allVersions, err := apiversions.List(client.ServiceClient(fakeServer)).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := apiversions.ExtractAPIVersions(allVersions) diff --git a/openstack/loadbalancer/v2/availabilityzoneprofiles/doc.go b/openstack/loadbalancer/v2/availabilityzoneprofiles/doc.go new file mode 100644 index 0000000000..30a3223da2 --- /dev/null +++ b/openstack/loadbalancer/v2/availabilityzoneprofiles/doc.go @@ -0,0 +1,57 @@ +/* +Package availabilityzoneprofiles provides information and interaction +with AvailabilityZoneProfiles for the OpenStack Load-balancing service. + +Example to List AvailabilityZoneProfiles + + listOpts := availabilityzoneprofiles.ListOpts{} + + allPages, err := availabilityzoneprofiles.List(octaviaClient, listOpts).AllPages(context.TODO()) + if err != nil { + panic(err) + } + + allAvailabilityZoneProfiles, err := availabilityzoneprofiles.ExtractAvailabilityZoneProfiles(allPages) + if err != nil { + panic(err) + } + + for _, availabilityZoneProfile := range allAvailabilityZoneProfiles { + fmt.Printf("%+v\n", availabilityZoneProfile) + } + +Example to Create a AvailabilityZoneProfile + + createOpts := availabilityzoneprofiles.CreateOpts{ + Name: "availability-zone-profile", + ProviderName: "amphora", + AvailabilityZoneData: "{\"compute_zone\": \"nova\"}", + } + + availabilityZoneProfile, err := availabilityzoneprofiles.Create(context.TODO(), octaviaClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a AvailabilityZoneProfile + + availabilityZoneProfileID := "0c359d38-6164-498f-8409-5b11d05b6226" + + updateOpts := availabilityzoneprofiles.UpdateOpts{ + Name: "availability-zone-profile-updated", + } + + availabilityZoneProfile, err := availabilityzoneprofiles.Update(context.TODO(), octaviaClient, availabilityZoneProfileID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a AvailabilityZoneProfile + + availabilityZoneProfileID := "0c359d38-6164-498f-8409-5b11d05b6226" + err := availabilityzoneprofiles.Delete(context.TODO(), octaviaClient, availabilityZoneProfileID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package availabilityzoneprofiles diff --git a/openstack/loadbalancer/v2/availabilityzoneprofiles/requests.go b/openstack/loadbalancer/v2/availabilityzoneprofiles/requests.go new file mode 100644 index 0000000000..869bf0d84d --- /dev/null +++ b/openstack/loadbalancer/v2/availabilityzoneprofiles/requests.go @@ -0,0 +1,145 @@ +package availabilityzoneprofiles + +import ( + "context" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToAvailabilityZoneProfileListQuery() (string, error) +} + +// ListOpts allows to manage the output of the request. +type ListOpts struct { + // The name of the availability zone profile to filter by. + Name string `q:"name"` + // The provider name of the availability zone profile to filter by. + ProviderName string `q:"provider_name"` + // The fields that you want the server to return + Fields []string `q:"fields"` +} + +// ToAvailabilityZoneProfileListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToAvailabilityZoneProfileListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// AvailabilityZoneProfiles. It accepts a ListOpts struct, which allows you to +// filter and sort the returned collection for greater efficiency. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToAvailabilityZoneProfileListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return AvailabilityZoneProfilePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToAvailabilityZoneProfileCreateMap() (map[string]any, error) +} + +// CreateOpts is the common options struct used in this package's create +// operation. +type CreateOpts struct { + // Human-readable name for the avaialability zone profile. + // Does not have to be unique. + Name string `json:"name" required:"true"` + + // Providing the name of the provider supported by the Octavia installation. + ProviderName string `json:"provider_name" required:"true"` + + // Providing the json string containing the availability zone metadata. + AvailabilityZoneData string `json:"availability_zone_data" required:"true"` +} + +// ToAvailabilityZoneProfileCreateMap builds a request body from CreateOpts. +func (opts CreateOpts) ToAvailabilityZoneProfileCreateMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "availability_zone_profile") +} + +// Create is and operation which add a new AvailabilityZoneProfile into the database. +// CreateResult will be returned. +func Create(ctx context.Context, c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToAvailabilityZoneProfileCreateMap() + if err != nil { + r.Err = err + return + } + resp, err := c.Post(ctx, rootURL(c), b, &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// Get retrieves a particular AvailabilityZoneProfile based on its unique ID. +func Get(ctx context.Context, c *gophercloud.ServiceClient, id string) (r GetResult) { + resp, err := c.Get(ctx, resourceURL(c, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// update request. +type UpdateOptsBuilder interface { + ToAvailabiltyZoneProfileUpdateMap() (map[string]any, error) +} + +// UpdateOpts is the common options struct used in this package's update +// operation. +type UpdateOpts struct { + // Human-readable name for the availability zone profile. + // Does not have to be unique. + Name *string `json:"name,omitempty"` + + // Providing the name of the provider supported by the Octavia installation. + ProviderName *string `json:"provider_name,omitempty"` + + // Providing the json string containing the availability zone metadata. + AvailabiltyZoneData *string `json:"availability_zone_data,omitempty"` +} + +// ToAvailabiltyZoneProfileUpdateMap builds a request body from UpdateOpts. +func (opts UpdateOpts) ToAvailabiltyZoneProfileUpdateMap() (map[string]any, error) { + b, err := gophercloud.BuildRequestBody(opts, "availability_zone_profile") + if err != nil { + return nil, err + } + + return b, nil +} + +// Update is an operation which modifies the attributes of the specified +// AvailabilityZoneProfile. +func Update(ctx context.Context, c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToAvailabiltyZoneProfileUpdateMap() + if err != nil { + r.Err = err + return + } + resp, err := c.Put(ctx, resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 202}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// Delete will permanently delete a particular AvailabiltyZoneProfile based on +// its unique ID. +func Delete(ctx context.Context, c *gophercloud.ServiceClient, id string) (r DeleteResult) { + resp, err := c.Delete(ctx, resourceURL(c, id), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/loadbalancer/v2/availabilityzoneprofiles/results.go b/openstack/loadbalancer/v2/availabilityzoneprofiles/results.go new file mode 100644 index 0000000000..b002ba9ad0 --- /dev/null +++ b/openstack/loadbalancer/v2/availabilityzoneprofiles/results.go @@ -0,0 +1,96 @@ +package availabilityzoneprofiles + +import ( + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/pagination" +) + +type AvailabilityZoneProfile struct { + // The unique ID for the AvailabilityZoneProfile + ID string `json:"id"` + + // Human-readable name for the AvailabilityZoneProfile. + // Does not have to be unique. + Name string `json:"name"` + + // Name of the provider + ProviderName string `json:"provider_name"` + + // Availability zone data + AvailabilityZoneData string `json:"availability_zone_data"` +} + +// AvailabilityZoneProfile is the page returned by a pager when traversing +// over a collection of profiles. +type AvailabilityZoneProfilePage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of profiles has +// reached the end of a page and the pager seeks to traverse over a new one. +// In order to do this, it needs to construct the next page's URL. +func (r AvailabilityZoneProfilePage) NextPageURL(endpointURL string) (string, error) { + var s struct { + Links []gophercloud.Link `json:"availabilityzoneprofiles_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a AvailabilityZoneProfilePage struct is empty. +func (r AvailabilityZoneProfilePage) IsEmpty() (bool, error) { + is, err := ExtractAvailabilityZoneProfiles(r) + return len(is) == 0, err +} + +// ExtractAvailabilityZoneProfiles accepts a Page struct, specifically a +// AvailabilityZoneProfilePage struct, and extracts the elements into a slice +// of Flavor structs. In other words, a generic collection is mapped into a +// relevant slice. +func ExtractAvailabilityZoneProfiles(r pagination.Page) ([]AvailabilityZoneProfile, error) { + var s struct { + AvailabilityZoneProfiles []AvailabilityZoneProfile `json:"availability_zone_profiles"` + } + err := (r.(AvailabilityZoneProfilePage)).ExtractInto(&s) + return s.AvailabilityZoneProfiles, err +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a flavor. +func (r commonResult) Extract() (*AvailabilityZoneProfile, error) { + var s struct { + AvailabilityZoneProfile *AvailabilityZoneProfile `json:"availability_zone_profile"` + } + err := r.ExtractInto(&s) + return s.AvailabilityZoneProfile, err +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Flavor. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a Flavor. +type GetResult struct { + commonResult +} + +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a Flavor. +type UpdateResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/pagination/testing/doc.go b/openstack/loadbalancer/v2/availabilityzoneprofiles/testing/doc.go similarity index 53% rename from pagination/testing/doc.go rename to openstack/loadbalancer/v2/availabilityzoneprofiles/testing/doc.go index 0bc1eb3807..7603f836a0 100644 --- a/pagination/testing/doc.go +++ b/openstack/loadbalancer/v2/availabilityzoneprofiles/testing/doc.go @@ -1,2 +1 @@ -// pagination package testing diff --git a/openstack/loadbalancer/v2/availabilityzoneprofiles/testing/fixtures.go b/openstack/loadbalancer/v2/availabilityzoneprofiles/testing/fixtures.go new file mode 100644 index 0000000000..b60ce711ac --- /dev/null +++ b/openstack/loadbalancer/v2/availabilityzoneprofiles/testing/fixtures.go @@ -0,0 +1,159 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/availabilityzoneprofiles" + + th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/client" +) + +const AvailabilityZoneProfilesListBody = ` +{ + "availability_zone_profiles": [ + { + "id": "1d334061-d807-4997-8f34-9fe428ba37df", + "name": "availability-zone-profile-first", + "provider_name": "amphora", + "availability_zone_data": "{\"compute_zone\": \"nova\"}" + }, + { + "id": "56f45d00-86e4-4bea-8525-19e835776c4e", + "name": "availability-zone-profile-second", + "provider_name": "amphora", + "availability_zone_data": "{\"compute_zone\": \"nova\"}" + } + ] +} +` + +const SingleAvailabilityZoneProfileBody = ` +{ + "availability_zone_profile": { + "id": "13be083b-f502-426e-8500-07600f98b91b", + "name": "availability-zone-profile", + "provider_name": "amphora", + "availability_zone_data": "{\"compute_zone\": \"nova\"}" + } +} +` + +const PostUpdateAvailabilityZoneFlavorBody = ` +{ + "availability_zone_profile": { + "id": "13be083b-f502-426e-8500-07600f98b91b", + "name": "availability-zone-profile-updated", + "provider_name": "amphora", + "availability_zone_data": "{\"compute_zone\": \"nova\"}" + } +} +` + +var ( + AvailabilityZoneProfileSingle = availabilityzoneprofiles.AvailabilityZoneProfile{ + ID: "1d334061-d807-4997-8f34-9fe428ba37df", + Name: "availability-zone-profile-first", + ProviderName: "amphora", + AvailabilityZoneData: "{\"compute_zone\": \"nova\"}", + } + + AvailabilityZoneProfileAct = availabilityzoneprofiles.AvailabilityZoneProfile{ + ID: "56f45d00-86e4-4bea-8525-19e835776c4e", + Name: "availability-zone-profile-second", + ProviderName: "amphora", + AvailabilityZoneData: "{\"compute_zone\": \"nova\"}", + } + + AvailabilityZoneProfileDb = availabilityzoneprofiles.AvailabilityZoneProfile{ + ID: "13be083b-f502-426e-8500-07600f98b91b", + Name: "availability-zone-profile", + ProviderName: "amphora", + AvailabilityZoneData: "{\"compute_zone\": \"nova\"}", + } + + AvailabilityZoneProfileUpdated = availabilityzoneprofiles.AvailabilityZoneProfile{ + ID: "13be083b-f502-426e-8500-07600f98b91b", + Name: "availability-zone-profile-updated", + ProviderName: "amphora", + AvailabilityZoneData: "{\"compute_zone\": \"nova\"}", + } +) + +func HandleAvailabilityZoneProfileListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/availabilityzoneprofiles", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + if err := r.ParseForm(); err != nil { + t.Errorf("Failed to parse request form %v", err) + } + marker := r.Form.Get("marker") + switch marker { + case "": + fmt.Fprint(w, AvailabilityZoneProfilesListBody) + case "56f45d00-86e4-4bea-8525-19e835776c4e": + fmt.Fprint(w, `{ "availability_zone_profiles": [] }`) + default: + t.Fatalf("/v2.0/lbaas/availabilityzoneprofiles invoked with unexpected marker=[%s]", marker) + } + }) +} + +func HandleAvailabilityZoneProfileCreationSuccessfully(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/availabilityzoneprofiles", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "availability_zone_profile": { + "name": "availability-zone-profile", + "provider_name": "amphora", + "availability_zone_data": "{\"compute_zone\": \"nova\"}" + } + }`) + + w.WriteHeader(http.StatusAccepted) + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, response) + }) +} + +func HandleAvailabilityZoneProfileGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/availabilityzoneprofiles/13be083b-f502-426e-8500-07600f98b91b", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + fmt.Fprint(w, SingleAvailabilityZoneProfileBody) + }) +} + +func HandleAvailabilityZoneProfileDeletionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/availabilityzoneprofiles/13be083b-f502-426e-8500-07600f98b91b", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +func HandleAvailabilityZoneProfileUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/availabilityzoneprofiles/dcd65be5-f117-4260-ab3d-b32cc5bd1272", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, `{ + "availability_zone_profile": { + "name": "availability-zone-profile-updated", + "provider_name": "amphora", + "availability_zone_data": "{\"compute_zone\": \"nova\"}" + } + }`) + + fmt.Fprint(w, PostUpdateAvailabilityZoneFlavorBody) + }) +} diff --git a/openstack/loadbalancer/v2/availabilityzoneprofiles/testing/requests_test.go b/openstack/loadbalancer/v2/availabilityzoneprofiles/testing/requests_test.go new file mode 100644 index 0000000000..7ce47457b6 --- /dev/null +++ b/openstack/loadbalancer/v2/availabilityzoneprofiles/testing/requests_test.go @@ -0,0 +1,122 @@ +package testing + +import ( + "context" + "testing" + + "github.com/gophercloud/gophercloud/v2/internal/ptr" + "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/availabilityzoneprofiles" + "github.com/gophercloud/gophercloud/v2/pagination" + + fake "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func TestListAvailabiltyZoneProfiles(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAvailabilityZoneProfileListSuccessfully(t, fakeServer) + + pages := 0 + err := availabilityzoneprofiles.List(fake.ServiceClient(fakeServer), availabilityzoneprofiles.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + pages++ + + actual, err := availabilityzoneprofiles.ExtractAvailabilityZoneProfiles(page) + if err != nil { + return false, err + } + + if len(actual) != 2 { + t.Fatalf("Expected 2 avaialbility zone profiles, got %d", len(actual)) + } + th.CheckDeepEquals(t, AvailabilityZoneProfileSingle, actual[0]) + th.CheckDeepEquals(t, AvailabilityZoneProfileAct, actual[1]) + + return true, nil + }) + + th.AssertNoErr(t, err) + + if pages != 1 { + t.Errorf("Expected 1 page, saw %d", pages) + } +} + +func TestListAllAvailabilityZoneProfiles(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAvailabilityZoneProfileListSuccessfully(t, fakeServer) + + allPages, err := availabilityzoneprofiles.List(fake.ServiceClient(fakeServer), availabilityzoneprofiles.ListOpts{}).AllPages(context.TODO()) + th.AssertNoErr(t, err) + actual, err := availabilityzoneprofiles.ExtractAvailabilityZoneProfiles(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, AvailabilityZoneProfileSingle, actual[0]) + th.CheckDeepEquals(t, AvailabilityZoneProfileAct, actual[1]) +} + +func TestCreateAvailabilityZoneProfile(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAvailabilityZoneProfileCreationSuccessfully(t, fakeServer, SingleAvailabilityZoneProfileBody) + + actual, err := availabilityzoneprofiles.Create(context.TODO(), fake.ServiceClient(fakeServer), availabilityzoneprofiles.CreateOpts{ + Name: "availability-zone-profile", + ProviderName: "amphora", + AvailabilityZoneData: "{\"compute_zone\": \"nova\"}", + }).Extract() + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, AvailabilityZoneProfileDb, *actual) +} + +func TestRequiredCreateOpts(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + res := availabilityzoneprofiles.Create(context.TODO(), fake.ServiceClient(fakeServer), availabilityzoneprofiles.CreateOpts{}) + if res.Err == nil { + t.Fatalf("Expected error, got none") + } +} + +func TestGetAvailabilityZoneProfiles(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAvailabilityZoneProfileGetSuccessfully(t, fakeServer) + + client := fake.ServiceClient(fakeServer) + actual, err := availabilityzoneprofiles.Get(context.TODO(), client, "13be083b-f502-426e-8500-07600f98b91b").Extract() + if err != nil { + t.Fatalf("Unexpected Get error: %v", err) + } + + th.CheckDeepEquals(t, AvailabilityZoneProfileDb, *actual) +} + +func TestDeleteAvailabilityZoneProfile(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAvailabilityZoneProfileDeletionSuccessfully(t, fakeServer) + + res := availabilityzoneprofiles.Delete(context.TODO(), fake.ServiceClient(fakeServer), "13be083b-f502-426e-8500-07600f98b91b") + th.AssertNoErr(t, res.Err) +} + +func TestUpdateAvailabililtyZoneProfile(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAvailabilityZoneProfileUpdateSuccessfully(t, fakeServer) + + client := fake.ServiceClient(fakeServer) + actual, err := availabilityzoneprofiles.Update(context.TODO(), client, "dcd65be5-f117-4260-ab3d-b32cc5bd1272", availabilityzoneprofiles.UpdateOpts{ + Name: ptr.To("availability-zone-profile-updated"), + ProviderName: ptr.To("amphora"), + AvailabiltyZoneData: ptr.To(`{"compute_zone": "nova"}`), + }).Extract() + if err != nil { + t.Fatalf("Unexpected Update error: %v", err) + } + + th.CheckDeepEquals(t, AvailabilityZoneProfileUpdated, *actual) +} diff --git a/openstack/loadbalancer/v2/availabilityzoneprofiles/urls.go b/openstack/loadbalancer/v2/availabilityzoneprofiles/urls.go new file mode 100644 index 0000000000..e2321402a3 --- /dev/null +++ b/openstack/loadbalancer/v2/availabilityzoneprofiles/urls.go @@ -0,0 +1,16 @@ +package availabilityzoneprofiles + +import "github.com/gophercloud/gophercloud/v2" + +const ( + rootPath = "lbaas" + resourcePath = "availabilityzoneprofiles" +) + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +} diff --git a/openstack/loadbalancer/v2/flavorprofiles/requests.go b/openstack/loadbalancer/v2/flavorprofiles/requests.go index 594b79739a..a541c6c772 100644 --- a/openstack/loadbalancer/v2/flavorprofiles/requests.go +++ b/openstack/loadbalancer/v2/flavorprofiles/requests.go @@ -15,6 +15,10 @@ type ListOptsBuilder interface { // ListOpts allows to manage the output of the request. type ListOpts struct { + // The name of the flavor profile to filter by. + Name string `q:"name"` + // The provider name of the flavor profile to filter by. + ProviderName string `q:"provider_name"` // The fields that you want the server to return Fields []string `q:"fields"` } @@ -96,13 +100,13 @@ type UpdateOptsBuilder interface { // operation. type UpdateOpts struct { // Human-readable name for the Loadbalancer. Does not have to be unique. - Name string `json:"name,omitempty"` + Name *string `json:"name,omitempty"` // Providing the name of the provider supported by the Octavia installation. - ProviderName string `json:"provider_name,omitempty"` + ProviderName *string `json:"provider_name,omitempty"` // Providing the json string containing the flavor metadata. - FlavorData string `json:"flavor_data,omitempty"` + FlavorData *string `json:"flavor_data,omitempty"` } // ToFlavorProfileUpdateMap builds a request body from UpdateOpts. @@ -117,7 +121,7 @@ func (opts UpdateOpts) ToFlavorProfileUpdateMap() (map[string]any, error) { // Update is an operation which modifies the attributes of the specified // FlavorProfile. -func Update(ctx context.Context, c *gophercloud.ServiceClient, id string, opts UpdateOpts) (r UpdateResult) { +func Update(ctx context.Context, c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { b, err := opts.ToFlavorProfileUpdateMap() if err != nil { r.Err = err diff --git a/openstack/loadbalancer/v2/flavorprofiles/results.go b/openstack/loadbalancer/v2/flavorprofiles/results.go index 1dd6b07ff8..67fdcdbede 100644 --- a/openstack/loadbalancer/v2/flavorprofiles/results.go +++ b/openstack/loadbalancer/v2/flavorprofiles/results.go @@ -29,7 +29,7 @@ type FlavorProfilePage struct { // NextPageURL is invoked when a paginated collection of flavor profiles has // reached the end of a page and the pager seeks to traverse over a new one. // In order to do this, it needs to construct the next page's URL. -func (r FlavorProfilePage) NextPageURL() (string, error) { +func (r FlavorProfilePage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"flavorprofiles_links"` } diff --git a/openstack/loadbalancer/v2/flavorprofiles/testing/fixtures.go b/openstack/loadbalancer/v2/flavorprofiles/testing/fixtures.go index adbdf1b7b3..2ef66175ba 100644 --- a/openstack/loadbalancer/v2/flavorprofiles/testing/fixtures.go +++ b/openstack/loadbalancer/v2/flavorprofiles/testing/fixtures.go @@ -82,8 +82,8 @@ var ( } ) -func HandleFlavorProfileListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/flavorprofiles", func(w http.ResponseWriter, r *http.Request) { +func HandleFlavorProfileListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/flavorprofiles", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -94,17 +94,17 @@ func HandleFlavorProfileListSuccessfully(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, FlavorProfilesListBody) + fmt.Fprint(w, FlavorProfilesListBody) case "3a0d060b-fcec-4250-9ab6-940b806a12dd": - fmt.Fprintf(w, `{ "flavors": [] }`) + fmt.Fprint(w, `{ "flavors": [] }`) default: t.Fatalf("/v2.0/lbaas/flavors invoked with unexpected marker=[%s]", marker) } }) } -func HandleFlavorProfileCreationSuccessfully(t *testing.T, response string) { - th.Mux.HandleFunc("/v2.0/lbaas/flavorprofiles", func(w http.ResponseWriter, r *http.Request) { +func HandleFlavorProfileCreationSuccessfully(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/flavorprofiles", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -117,22 +117,22 @@ func HandleFlavorProfileCreationSuccessfully(t *testing.T, response string) { w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) } -func HandleFlavorProfileGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/flavorprofiles/dcd65be5-f117-4260-ab3d-b32cc5bd1272", func(w http.ResponseWriter, r *http.Request) { +func HandleFlavorProfileGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/flavorprofiles/dcd65be5-f117-4260-ab3d-b32cc5bd1272", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleFlavorProfileBody) + fmt.Fprint(w, SingleFlavorProfileBody) }) } -func HandleFlavorProfileDeletionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/flavorprofiles/dcd65be5-f117-4260-ab3d-b32cc5bd1272", func(w http.ResponseWriter, r *http.Request) { +func HandleFlavorProfileDeletionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/flavorprofiles/dcd65be5-f117-4260-ab3d-b32cc5bd1272", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -140,8 +140,8 @@ func HandleFlavorProfileDeletionSuccessfully(t *testing.T) { }) } -func HandleFlavorProfileUpdateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/flavorprofiles/dcd65be5-f117-4260-ab3d-b32cc5bd1272", func(w http.ResponseWriter, r *http.Request) { +func HandleFlavorProfileUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/flavorprofiles/dcd65be5-f117-4260-ab3d-b32cc5bd1272", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") @@ -154,6 +154,6 @@ func HandleFlavorProfileUpdateSuccessfully(t *testing.T) { } }`) - fmt.Fprintf(w, PostUpdateFlavorBody) + fmt.Fprint(w, PostUpdateFlavorBody) }) } diff --git a/openstack/loadbalancer/v2/flavorprofiles/testing/requests_test.go b/openstack/loadbalancer/v2/flavorprofiles/testing/requests_test.go index aebb2c9408..1f838adb6e 100644 --- a/openstack/loadbalancer/v2/flavorprofiles/testing/requests_test.go +++ b/openstack/loadbalancer/v2/flavorprofiles/testing/requests_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/gophercloud/gophercloud/v2/internal/ptr" "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/flavorprofiles" "github.com/gophercloud/gophercloud/v2/pagination" @@ -12,12 +13,12 @@ import ( ) func TestListFlavorProfiles(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleFlavorProfileListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleFlavorProfileListSuccessfully(t, fakeServer) pages := 0 - err := flavorprofiles.List(fake.ServiceClient(), flavorprofiles.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := flavorprofiles.List(fake.ServiceClient(fakeServer), flavorprofiles.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := flavorprofiles.ExtractFlavorProfiles(page) @@ -42,11 +43,11 @@ func TestListFlavorProfiles(t *testing.T) { } func TestListAllFlavorProfiles(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleFlavorProfileListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleFlavorProfileListSuccessfully(t, fakeServer) - allPages, err := flavorprofiles.List(fake.ServiceClient(), flavorprofiles.ListOpts{}).AllPages(context.TODO()) + allPages, err := flavorprofiles.List(fake.ServiceClient(fakeServer), flavorprofiles.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := flavorprofiles.ExtractFlavorProfiles(allPages) th.AssertNoErr(t, err) @@ -55,11 +56,11 @@ func TestListAllFlavorProfiles(t *testing.T) { } func TestCreateFlavorProfile(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleFlavorProfileCreationSuccessfully(t, SingleFlavorProfileBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleFlavorProfileCreationSuccessfully(t, fakeServer, SingleFlavorProfileBody) - actual, err := flavorprofiles.Create(context.TODO(), fake.ServiceClient(), flavorprofiles.CreateOpts{ + actual, err := flavorprofiles.Create(context.TODO(), fake.ServiceClient(fakeServer), flavorprofiles.CreateOpts{ Name: "amphora-test", ProviderName: "amphora", FlavorData: "{\"loadbalancer_topology\": \"ACTIVE_STANDBY\"}", @@ -70,18 +71,21 @@ func TestCreateFlavorProfile(t *testing.T) { } func TestRequiredCreateOpts(t *testing.T) { - res := flavorprofiles.Create(context.TODO(), fake.ServiceClient(), flavorprofiles.CreateOpts{}) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + res := flavorprofiles.Create(context.TODO(), fake.ServiceClient(fakeServer), flavorprofiles.CreateOpts{}) if res.Err == nil { t.Fatalf("Expected error, got none") } } func TestGetFlavorProfiles(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleFlavorProfileGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleFlavorProfileGetSuccessfully(t, fakeServer) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) actual, err := flavorprofiles.Get(context.TODO(), client, "dcd65be5-f117-4260-ab3d-b32cc5bd1272").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) @@ -91,24 +95,24 @@ func TestGetFlavorProfiles(t *testing.T) { } func TestDeleteFlavorProfile(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleFlavorProfileDeletionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleFlavorProfileDeletionSuccessfully(t, fakeServer) - res := flavorprofiles.Delete(context.TODO(), fake.ServiceClient(), "dcd65be5-f117-4260-ab3d-b32cc5bd1272") + res := flavorprofiles.Delete(context.TODO(), fake.ServiceClient(fakeServer), "dcd65be5-f117-4260-ab3d-b32cc5bd1272") th.AssertNoErr(t, res.Err) } func TestUpdateFlavorProfile(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleFlavorProfileUpdateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleFlavorProfileUpdateSuccessfully(t, fakeServer) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) actual, err := flavorprofiles.Update(context.TODO(), client, "dcd65be5-f117-4260-ab3d-b32cc5bd1272", flavorprofiles.UpdateOpts{ - Name: "amphora-test-updated", - ProviderName: "amphora", - FlavorData: "{\"loadbalancer_topology\": \"SINGLE\"}", + Name: ptr.To("amphora-test-updated"), + ProviderName: ptr.To("amphora"), + FlavorData: ptr.To(`{"loadbalancer_topology": "SINGLE"}`), }).Extract() if err != nil { t.Fatalf("Unexpected Update error: %v", err) diff --git a/openstack/loadbalancer/v2/flavors/doc.go b/openstack/loadbalancer/v2/flavors/doc.go index 34139cae47..3b2233611d 100644 --- a/openstack/loadbalancer/v2/flavors/doc.go +++ b/openstack/loadbalancer/v2/flavors/doc.go @@ -26,7 +26,7 @@ Example to Create a Flavor Name: "Flavor name", Description: "My flavor description", Enable: true, - FlavorProfileId: "9daa2768-74e7-4d13-bf5d-1b8e0dc239e1", + FlavorProfileID: "9daa2768-74e7-4d13-bf5d-1b8e0dc239e1", } flavor, err := flavors.Create(context.TODO(), octaviaClient, createOpts).Extract() diff --git a/openstack/loadbalancer/v2/flavors/requests.go b/openstack/loadbalancer/v2/flavors/requests.go index 6c4859116c..6c2d91fac2 100644 --- a/openstack/loadbalancer/v2/flavors/requests.go +++ b/openstack/loadbalancer/v2/flavors/requests.go @@ -15,6 +15,12 @@ type ListOptsBuilder interface { // ListOpts allows to manage the output of the request. type ListOpts struct { + // The name of the flavor to filter by. + Name string `q:"name"` + // The flavor profile id to filter by. + FlavorProfileID string `q:"flavor_profile_id"` + // The enabled status of the flavor to filter by. + Enabled *bool `q:"enabled"` // The fields that you want the server to return Fields []string `q:"fields"` } @@ -59,10 +65,10 @@ type CreateOpts struct { // The ID of the FlavorProfile which give the metadata for the creation of // a LoadBalancer. - FlavorProfileId string `json:"flavor_profile_id" required:"true"` + FlavorProfileID string `json:"flavor_profile_id" required:"true"` // If the resource is available for use. The default is True. - Enabled bool `json:"enabled,omitempty"` + Enabled *bool `json:"enabled,omitempty"` } // ToFlavorCreateMap builds a request body from CreateOpts. @@ -100,13 +106,13 @@ type UpdateOptsBuilder interface { // operation. type UpdateOpts struct { // Human-readable name for the Loadbalancer. Does not have to be unique. - Name string `json:"name,omitempty"` + Name *string `json:"name,omitempty"` // Human-readable description for the Flavor. - Description string `json:"description,omitempty"` + Description *string `json:"description,omitempty"` // If the resource is available for use. - Enabled bool `json:"enabled,omitempty"` + Enabled *bool `json:"enabled,omitempty"` } // ToFlavorUpdateMap builds a request body from UpdateOpts. @@ -121,7 +127,7 @@ func (opts UpdateOpts) ToFlavorUpdateMap() (map[string]any, error) { // Update is an operation which modifies the attributes of the specified // Flavor. -func Update(ctx context.Context, c *gophercloud.ServiceClient, id string, opts UpdateOpts) (r UpdateResult) { +func Update(ctx context.Context, c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { b, err := opts.ToFlavorUpdateMap() if err != nil { r.Err = err diff --git a/openstack/loadbalancer/v2/flavors/results.go b/openstack/loadbalancer/v2/flavors/results.go index 105a6cfec4..7f2c963ac3 100644 --- a/openstack/loadbalancer/v2/flavors/results.go +++ b/openstack/loadbalancer/v2/flavors/results.go @@ -20,7 +20,7 @@ type Flavor struct { Enabled bool `json:"enabled"` // Flavor Profile apply to this Flavor. - FlavorProfileId string `json:"flavor_profile_id"` + FlavorProfileID string `json:"flavor_profile_id"` } // FlavorPage is the page returned by a pager when traversing over a @@ -32,7 +32,7 @@ type FlavorPage struct { // NextPageURL is invoked when a paginated collection of flavors has // reached the end of a page and the pager seeks to traverse over a new one. // In order to do this, it needs to construct the next page's URL. -func (r FlavorPage) NextPageURL() (string, error) { +func (r FlavorPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"flavors_links"` } diff --git a/openstack/loadbalancer/v2/flavors/testing/fixtures.go b/openstack/loadbalancer/v2/flavors/testing/fixtures.go index a3169d7ece..d68477be5b 100644 --- a/openstack/loadbalancer/v2/flavors/testing/fixtures.go +++ b/openstack/loadbalancer/v2/flavors/testing/fixtures.go @@ -44,6 +44,18 @@ const SingleFlavorBody = ` } ` +const SingleFlavorDisabledBody = ` +{ + "flavor": { + "id": "5548c807-e6e8-43d7-9ea4-b38d34dd74a0", + "name": "Basic", + "description": "A basic standalone Octavia load balancer.", + "enabled": false, + "flavor_profile_id": "9daa2768-74e7-4d13-bf5d-1b8e0dc239e1" + } +} +` + const PostUpdateFlavorBody = ` { "flavor": { @@ -62,7 +74,7 @@ var ( Name: "Basic", Description: "A basic standalone Octavia load balancer.", Enabled: true, - FlavorProfileId: "bdba88c7-beab-4fc9-a5dd-3635de59185b", + FlavorProfileID: "bdba88c7-beab-4fc9-a5dd-3635de59185b", } FlavorAdvance = flavors.Flavor{ @@ -70,7 +82,7 @@ var ( Name: "Advance", Description: "A advance standalone Octavia load balancer.", Enabled: false, - FlavorProfileId: "c221abc6-a845-45a0-925c-27110c9d7bdc", + FlavorProfileID: "c221abc6-a845-45a0-925c-27110c9d7bdc", } FlavorDb = flavors.Flavor{ @@ -78,7 +90,15 @@ var ( Name: "Basic", Description: "A basic standalone Octavia load balancer.", Enabled: true, - FlavorProfileId: "9daa2768-74e7-4d13-bf5d-1b8e0dc239e1", + FlavorProfileID: "9daa2768-74e7-4d13-bf5d-1b8e0dc239e1", + } + + FlavorDisabled = flavors.Flavor{ + ID: "5548c807-e6e8-43d7-9ea4-b38d34dd74a0", + Name: "Basic", + Description: "A basic standalone Octavia load balancer.", + Enabled: false, + FlavorProfileID: "9daa2768-74e7-4d13-bf5d-1b8e0dc239e1", } FlavorUpdated = flavors.Flavor{ @@ -86,12 +106,12 @@ var ( Name: "Basic v2", Description: "Rename flavor", Enabled: false, - FlavorProfileId: "9daa2768-74e7-4d13-bf5d-1b8e0dc239e1", + FlavorProfileID: "9daa2768-74e7-4d13-bf5d-1b8e0dc239e1", } ) -func HandleFlavorListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/flavors", func(w http.ResponseWriter, r *http.Request) { +func HandleFlavorListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/flavors", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -102,17 +122,17 @@ func HandleFlavorListSuccessfully(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, FlavorsListBody) + fmt.Fprint(w, FlavorsListBody) case "3a0d060b-fcec-4250-9ab6-940b806a12dd": - fmt.Fprintf(w, `{ "flavors": [] }`) + fmt.Fprint(w, `{ "flavors": [] }`) default: t.Fatalf("/v2.0/lbaas/flavors invoked with unexpected marker=[%s]", marker) } }) } -func HandleFlavorCreationSuccessfully(t *testing.T, response string) { - th.Mux.HandleFunc("/v2.0/lbaas/flavors", func(w http.ResponseWriter, r *http.Request) { +func HandleFlavorCreationSuccessfully(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/flavors", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -126,22 +146,41 @@ func HandleFlavorCreationSuccessfully(t *testing.T, response string) { w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, response) + fmt.Fprint(w, response) + }) +} + +func HandleFlavorCreationSuccessfullyDisabled(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/flavors", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{ + "flavor": { + "name": "Basic", + "description": "A basic standalone Octavia load balancer.", + "enabled": false, + "flavor_profile_id": "9daa2768-74e7-4d13-bf5d-1b8e0dc239e1" + } + }`) + + w.WriteHeader(http.StatusAccepted) + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, response) }) } -func HandleFlavorGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/flavors/5548c807-e6e8-43d7-9ea4-b38d34dd74a0", func(w http.ResponseWriter, r *http.Request) { +func HandleFlavorGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/flavors/5548c807-e6e8-43d7-9ea4-b38d34dd74a0", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleFlavorBody) + fmt.Fprint(w, SingleFlavorBody) }) } -func HandleFlavorDeletionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/flavors/5548c807-e6e8-43d7-9ea4-b38d34dd74a0", func(w http.ResponseWriter, r *http.Request) { +func HandleFlavorDeletionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/flavors/5548c807-e6e8-43d7-9ea4-b38d34dd74a0", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -149,8 +188,8 @@ func HandleFlavorDeletionSuccessfully(t *testing.T) { }) } -func HandleFlavorUpdateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/flavors/5548c807-e6e8-43d7-9ea4-b38d34dd74a0", func(w http.ResponseWriter, r *http.Request) { +func HandleFlavorUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/flavors/5548c807-e6e8-43d7-9ea4-b38d34dd74a0", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") @@ -163,6 +202,6 @@ func HandleFlavorUpdateSuccessfully(t *testing.T) { } }`) - fmt.Fprintf(w, PostUpdateFlavorBody) + fmt.Fprint(w, PostUpdateFlavorBody) }) } diff --git a/openstack/loadbalancer/v2/flavors/testing/requests_test.go b/openstack/loadbalancer/v2/flavors/testing/requests_test.go index 14a74dbe45..ae4ca6c2ae 100644 --- a/openstack/loadbalancer/v2/flavors/testing/requests_test.go +++ b/openstack/loadbalancer/v2/flavors/testing/requests_test.go @@ -2,22 +2,26 @@ package testing import ( "context" + "fmt" + "net/http" "testing" + "github.com/gophercloud/gophercloud/v2/internal/ptr" "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/flavors" "github.com/gophercloud/gophercloud/v2/pagination" fake "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/testhelper" th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestListFlavors(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleFlavorListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleFlavorListSuccessfully(t, fakeServer) pages := 0 - err := flavors.List(fake.ServiceClient(), flavors.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := flavors.List(fake.ServiceClient(fakeServer), flavors.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := flavors.ExtractFlavors(page) @@ -41,12 +45,56 @@ func TestListFlavors(t *testing.T) { } } +func TestListFlavorsEnabled(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + func() { + testCases := []string{ + "true", + "false", + "", + } + + cases := 0 + fakeServer.Mux.HandleFunc("/v2.0/lbaas/flavors", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + if err := r.ParseForm(); err != nil { + t.Errorf("Failed to parse request form %v", err) + } + enabled := r.Form.Get("enabled") + if enabled != testCases[cases] { + t.Errorf("Expected enabled=%s got %q", testCases[cases], enabled) + } + cases++ + fmt.Fprint(w, `{"flavorprofiles":[]}`) + }) + }() + + var nilBool *bool + enabled := true + filters := []*bool{ + &enabled, + new(bool), + nilBool, + } + for _, filter := range filters { + allPages, err := flavors.List(fake.ServiceClient(fakeServer), flavors.ListOpts{Enabled: filter}).AllPages(context.TODO()) + th.AssertNoErr(t, err) + _, err = flavors.ExtractFlavors(allPages) + th.AssertNoErr(t, err) + } +} + func TestListAllFlavors(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleFlavorListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleFlavorListSuccessfully(t, fakeServer) - allPages, err := flavors.List(fake.ServiceClient(), flavors.ListOpts{}).AllPages(context.TODO()) + allPages, err := flavors.List(fake.ServiceClient(fakeServer), flavors.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := flavors.ExtractFlavors(allPages) th.AssertNoErr(t, err) @@ -55,34 +103,53 @@ func TestListAllFlavors(t *testing.T) { } func TestCreateFlavor(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleFlavorCreationSuccessfully(t, SingleFlavorBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleFlavorCreationSuccessfully(t, fakeServer, SingleFlavorBody) - actual, err := flavors.Create(context.TODO(), fake.ServiceClient(), flavors.CreateOpts{ + actual, err := flavors.Create(context.TODO(), fake.ServiceClient(fakeServer), flavors.CreateOpts{ Name: "Basic", Description: "A basic standalone Octavia load balancer.", - Enabled: true, - FlavorProfileId: "9daa2768-74e7-4d13-bf5d-1b8e0dc239e1", + Enabled: ptr.To(true), + FlavorProfileID: "9daa2768-74e7-4d13-bf5d-1b8e0dc239e1", }).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, FlavorDb, *actual) } +func TestCreateFlavorDisabled(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleFlavorCreationSuccessfullyDisabled(t, fakeServer, SingleFlavorDisabledBody) + + actual, err := flavors.Create(context.TODO(), fake.ServiceClient(fakeServer), flavors.CreateOpts{ + Name: "Basic", + Description: "A basic standalone Octavia load balancer.", + Enabled: ptr.To(false), + FlavorProfileID: "9daa2768-74e7-4d13-bf5d-1b8e0dc239e1", + }).Extract() + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, FlavorDisabled, *actual) +} + func TestRequiredCreateOpts(t *testing.T) { - res := flavors.Create(context.TODO(), fake.ServiceClient(), flavors.CreateOpts{}) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + res := flavors.Create(context.TODO(), fake.ServiceClient(fakeServer), flavors.CreateOpts{}) if res.Err == nil { t.Fatalf("Expected error, got none") } } func TestGetFlavor(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleFlavorGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleFlavorGetSuccessfully(t, fakeServer) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) actual, err := flavors.Get(context.TODO(), client, "5548c807-e6e8-43d7-9ea4-b38d34dd74a0").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) @@ -92,24 +159,24 @@ func TestGetFlavor(t *testing.T) { } func TestDeleteFlavor(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleFlavorDeletionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleFlavorDeletionSuccessfully(t, fakeServer) - res := flavors.Delete(context.TODO(), fake.ServiceClient(), "5548c807-e6e8-43d7-9ea4-b38d34dd74a0") + res := flavors.Delete(context.TODO(), fake.ServiceClient(fakeServer), "5548c807-e6e8-43d7-9ea4-b38d34dd74a0") th.AssertNoErr(t, res.Err) } func TestUpdateFlavor(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleFlavorUpdateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleFlavorUpdateSuccessfully(t, fakeServer) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) actual, err := flavors.Update(context.TODO(), client, "5548c807-e6e8-43d7-9ea4-b38d34dd74a0", flavors.UpdateOpts{ - Name: "Basic v2", - Description: "Rename flavor", - Enabled: true, + Name: ptr.To("Basic v2"), + Description: ptr.To("Rename flavor"), + Enabled: ptr.To(true), }).Extract() if err != nil { t.Fatalf("Unexpected Update error: %v", err) diff --git a/openstack/loadbalancer/v2/l7policies/requests.go b/openstack/loadbalancer/v2/l7policies/requests.go index 62a4f179ee..ab0b22c6bc 100644 --- a/openstack/loadbalancer/v2/l7policies/requests.go +++ b/openstack/loadbalancer/v2/l7policies/requests.go @@ -263,6 +263,12 @@ func Update(ctx context.Context, c *gophercloud.ServiceClient, id string, opts U return } +// CreateRuleOptsBuilder allows extensions to add additional parameters to the +// CreateRule request. +type CreateRuleOptsBuilder interface { + ToRuleCreateMap() (map[string]any, error) +} + // CreateRuleOpts is the common options struct used in this package's CreateRule // operation. type CreateRuleOpts struct { @@ -300,7 +306,7 @@ func (opts CreateRuleOpts) ToRuleCreateMap() (map[string]any, error) { } // CreateRule will create and associate a Rule with a particular L7Policy. -func CreateRule(ctx context.Context, c *gophercloud.ServiceClient, policyID string, opts CreateRuleOpts) (r CreateRuleResult) { +func CreateRule(ctx context.Context, c *gophercloud.ServiceClient, policyID string, opts CreateRuleOptsBuilder) (r CreateRuleResult) { b, err := opts.ToRuleCreateMap() if err != nil { r.Err = err diff --git a/openstack/loadbalancer/v2/l7policies/results.go b/openstack/loadbalancer/v2/l7policies/results.go index 214d59d121..f29becc8b5 100644 --- a/openstack/loadbalancer/v2/l7policies/results.go +++ b/openstack/loadbalancer/v2/l7policies/results.go @@ -132,7 +132,7 @@ type L7PolicyPage struct { // NextPageURL is invoked when a paginated collection of l7policies has reached // the end of a page and the pager seeks to traverse over a new one. In order // to do this, it needs to construct the next page's URL. -func (r L7PolicyPage) NextPageURL() (string, error) { +func (r L7PolicyPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"l7policies_links"` } @@ -210,7 +210,7 @@ type RulePage struct { // NextPageURL is invoked when a paginated collection of rules has reached // the end of a page and the pager seeks to traverse over a new one. In order // to do this, it needs to construct the next page's URL. -func (r RulePage) NextPageURL() (string, error) { +func (r RulePage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"rules_links"` } diff --git a/openstack/loadbalancer/v2/l7policies/testing/fixtures_test.go b/openstack/loadbalancer/v2/l7policies/testing/fixtures_test.go index a1773287f9..98efa14b88 100644 --- a/openstack/loadbalancer/v2/l7policies/testing/fixtures_test.go +++ b/openstack/loadbalancer/v2/l7policies/testing/fixtures_test.go @@ -116,8 +116,8 @@ var ( // HandleL7PolicyCreationSuccessfully sets up the test server to respond to a l7policy creation request // with a given response. -func HandleL7PolicyCreationSuccessfully(t *testing.T, response string) { - th.Mux.HandleFunc("/v2.0/lbaas/l7policies", func(w http.ResponseWriter, r *http.Request) { +func HandleL7PolicyCreationSuccessfully(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/l7policies", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -131,7 +131,7 @@ func HandleL7PolicyCreationSuccessfully(t *testing.T, response string) { w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) } @@ -206,8 +206,8 @@ const PostUpdateL7PolicyNullRedirectURLBody = ` ` // HandleL7PolicyListSuccessfully sets up the test server to respond to a l7policy List request. -func HandleL7PolicyListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/l7policies", func(w http.ResponseWriter, r *http.Request) { +func HandleL7PolicyListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/l7policies", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -218,9 +218,9 @@ func HandleL7PolicyListSuccessfully(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, L7PoliciesListBody) + fmt.Fprint(w, L7PoliciesListBody) case "45e08a3e-a78f-4b40-a229-1e7e23eee1ab": - fmt.Fprintf(w, `{ "l7policies": [] }`) + fmt.Fprint(w, `{ "l7policies": [] }`) default: t.Fatalf("/v2.0/lbaas/l7policies invoked with unexpected marker=[%s]", marker) } @@ -228,19 +228,19 @@ func HandleL7PolicyListSuccessfully(t *testing.T) { } // HandleL7PolicyGetSuccessfully sets up the test server to respond to a l7policy Get request. -func HandleL7PolicyGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd", func(w http.ResponseWriter, r *http.Request) { +func HandleL7PolicyGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleL7PolicyBody) + fmt.Fprint(w, SingleL7PolicyBody) }) } // HandleL7PolicyDeletionSuccessfully sets up the test server to respond to a l7policy deletion request. -func HandleL7PolicyDeletionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd", func(w http.ResponseWriter, r *http.Request) { +func HandleL7PolicyDeletionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -249,8 +249,8 @@ func HandleL7PolicyDeletionSuccessfully(t *testing.T) { } // HandleL7PolicyUpdateSuccessfully sets up the test server to respond to a l7policy Update request. -func HandleL7PolicyUpdateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd", func(w http.ResponseWriter, r *http.Request) { +func HandleL7PolicyUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") @@ -263,13 +263,13 @@ func HandleL7PolicyUpdateSuccessfully(t *testing.T) { } }`) - fmt.Fprintf(w, PostUpdateL7PolicyBody) + fmt.Fprint(w, PostUpdateL7PolicyBody) }) } // HandleL7PolicyUpdateNullRedirectURLSuccessfully sets up the test server to respond to a l7policy Update request. -func HandleL7PolicyUpdateNullRedirectURLSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd", func(w http.ResponseWriter, r *http.Request) { +func HandleL7PolicyUpdateNullRedirectURLSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") @@ -281,7 +281,7 @@ func HandleL7PolicyUpdateNullRedirectURLSuccessfully(t *testing.T) { } }`) - fmt.Fprintf(w, PostUpdateL7PolicyNullRedirectURLBody) + fmt.Fprint(w, PostUpdateL7PolicyNullRedirectURLBody) }) } @@ -303,8 +303,8 @@ const SingleRuleBody = ` // HandleRuleCreationSuccessfully sets up the test server to respond to a rule creation request // with a given response. -func HandleRuleCreationSuccessfully(t *testing.T, response string) { - th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd/rules", func(w http.ResponseWriter, r *http.Request) { +func HandleRuleCreationSuccessfully(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd/rules", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -317,7 +317,7 @@ func HandleRuleCreationSuccessfully(t *testing.T, response string) { w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) } @@ -350,8 +350,8 @@ const RulesListBody = ` ` // HandleRuleListSuccessfully sets up the test server to respond to a rule List request. -func HandleRuleListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd/rules", func(w http.ResponseWriter, r *http.Request) { +func HandleRuleListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd/rules", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -362,9 +362,9 @@ func HandleRuleListSuccessfully(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, RulesListBody) + fmt.Fprint(w, RulesListBody) case "45e08a3e-a78f-4b40-a229-1e7e23eee1ab": - fmt.Fprintf(w, `{ "rules": [] }`) + fmt.Fprint(w, `{ "rules": [] }`) default: t.Fatalf("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd/rules invoked with unexpected marker=[%s]", marker) } @@ -372,19 +372,19 @@ func HandleRuleListSuccessfully(t *testing.T) { } // HandleRuleGetSuccessfully sets up the test server to respond to a rule Get request. -func HandleRuleGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd/rules/16621dbb-a736-4888-a57a-3ecd53df784c", func(w http.ResponseWriter, r *http.Request) { +func HandleRuleGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd/rules/16621dbb-a736-4888-a57a-3ecd53df784c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleRuleBody) + fmt.Fprint(w, SingleRuleBody) }) } // HandleRuleDeletionSuccessfully sets up the test server to respond to a rule deletion request. -func HandleRuleDeletionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd/rules/16621dbb-a736-4888-a57a-3ecd53df784c", func(w http.ResponseWriter, r *http.Request) { +func HandleRuleDeletionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd/rules/16621dbb-a736-4888-a57a-3ecd53df784c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -409,8 +409,8 @@ const PostUpdateRuleBody = ` ` // HandleRuleUpdateSuccessfully sets up the test server to respond to a rule Update request. -func HandleRuleUpdateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd/rules/16621dbb-a736-4888-a57a-3ecd53df784c", func(w http.ResponseWriter, r *http.Request) { +func HandleRuleUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd/rules/16621dbb-a736-4888-a57a-3ecd53df784c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") @@ -425,6 +425,6 @@ func HandleRuleUpdateSuccessfully(t *testing.T) { } }`) - fmt.Fprintf(w, PostUpdateRuleBody) + fmt.Fprint(w, PostUpdateRuleBody) }) } diff --git a/openstack/loadbalancer/v2/l7policies/testing/requests_test.go b/openstack/loadbalancer/v2/l7policies/testing/requests_test.go index 328215982d..324515152c 100644 --- a/openstack/loadbalancer/v2/l7policies/testing/requests_test.go +++ b/openstack/loadbalancer/v2/l7policies/testing/requests_test.go @@ -11,11 +11,11 @@ import ( ) func TestCreateL7Policy(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleL7PolicyCreationSuccessfully(t, SingleL7PolicyBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleL7PolicyCreationSuccessfully(t, fakeServer, SingleL7PolicyBody) - actual, err := l7policies.Create(context.TODO(), fake.ServiceClient(), l7policies.CreateOpts{ + actual, err := l7policies.Create(context.TODO(), fake.ServiceClient(fakeServer), l7policies.CreateOpts{ Name: "redirect-example.com", ListenerID: "023f2e34-7806-443b-bfae-16c324569a3d", Action: l7policies.ActionRedirectToURL, @@ -27,14 +27,17 @@ func TestCreateL7Policy(t *testing.T) { } func TestRequiredL7PolicyCreateOpts(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + // no param specified. - res := l7policies.Create(context.TODO(), fake.ServiceClient(), l7policies.CreateOpts{}) + res := l7policies.Create(context.TODO(), fake.ServiceClient(fakeServer), l7policies.CreateOpts{}) if res.Err == nil { t.Fatalf("Expected error, got none") } // Action is invalid. - res = l7policies.Create(context.TODO(), fake.ServiceClient(), l7policies.CreateOpts{ + res = l7policies.Create(context.TODO(), fake.ServiceClient(fakeServer), l7policies.CreateOpts{ ListenerID: "023f2e34-7806-443b-bfae-16c324569a3d", Action: l7policies.Action("invalid"), }) @@ -44,12 +47,12 @@ func TestRequiredL7PolicyCreateOpts(t *testing.T) { } func TestListL7Policies(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleL7PolicyListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleL7PolicyListSuccessfully(t, fakeServer) pages := 0 - err := l7policies.List(fake.ServiceClient(), l7policies.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := l7policies.List(fake.ServiceClient(fakeServer), l7policies.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := l7policies.ExtractL7Policies(page) @@ -74,11 +77,11 @@ func TestListL7Policies(t *testing.T) { } func TestListAllL7Policies(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleL7PolicyListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleL7PolicyListSuccessfully(t, fakeServer) - allPages, err := l7policies.List(fake.ServiceClient(), l7policies.ListOpts{}).AllPages(context.TODO()) + allPages, err := l7policies.List(fake.ServiceClient(fakeServer), l7policies.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := l7policies.ExtractL7Policies(allPages) th.AssertNoErr(t, err) @@ -87,11 +90,11 @@ func TestListAllL7Policies(t *testing.T) { } func TestGetL7Policy(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleL7PolicyGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleL7PolicyGetSuccessfully(t, fakeServer) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) actual, err := l7policies.Get(context.TODO(), client, "8a1412f0-4c32-4257-8b07-af4770b604fd").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) @@ -101,20 +104,20 @@ func TestGetL7Policy(t *testing.T) { } func TestDeleteL7Policy(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleL7PolicyDeletionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleL7PolicyDeletionSuccessfully(t, fakeServer) - res := l7policies.Delete(context.TODO(), fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd") + res := l7policies.Delete(context.TODO(), fake.ServiceClient(fakeServer), "8a1412f0-4c32-4257-8b07-af4770b604fd") th.AssertNoErr(t, res.Err) } func TestUpdateL7Policy(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleL7PolicyUpdateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleL7PolicyUpdateSuccessfully(t, fakeServer) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) newName := "NewL7PolicyName" redirectURL := "http://www.new-example.com" actual, err := l7policies.Update(context.TODO(), client, "8a1412f0-4c32-4257-8b07-af4770b604fd", @@ -131,11 +134,11 @@ func TestUpdateL7Policy(t *testing.T) { } func TestUpdateL7PolicyNullRedirectURL(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleL7PolicyUpdateNullRedirectURLSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleL7PolicyUpdateNullRedirectURLSuccessfully(t, fakeServer) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) newName := "NewL7PolicyName" redirectURL := "" actual, err := l7policies.Update(context.TODO(), client, "8a1412f0-4c32-4257-8b07-af4770b604fd", @@ -151,10 +154,10 @@ func TestUpdateL7PolicyNullRedirectURL(t *testing.T) { } func TestUpdateL7PolicyWithInvalidOpts(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - res := l7policies.Update(context.TODO(), fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.UpdateOpts{ + res := l7policies.Update(context.TODO(), fake.ServiceClient(fakeServer), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.UpdateOpts{ Action: l7policies.Action("invalid"), }) if res.Err == nil { @@ -163,11 +166,11 @@ func TestUpdateL7PolicyWithInvalidOpts(t *testing.T) { } func TestCreateRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleRuleCreationSuccessfully(t, SingleRuleBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleRuleCreationSuccessfully(t, fakeServer, SingleRuleBody) - actual, err := l7policies.CreateRule(context.TODO(), fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.CreateRuleOpts{ + actual, err := l7policies.CreateRule(context.TODO(), fake.ServiceClient(fakeServer), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.CreateRuleOpts{ RuleType: l7policies.TypePath, CompareType: l7policies.CompareTypeRegex, Value: "/images*", @@ -178,20 +181,20 @@ func TestCreateRule(t *testing.T) { } func TestRequiredRuleCreateOpts(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - res := l7policies.CreateRule(context.TODO(), fake.ServiceClient(), "", l7policies.CreateRuleOpts{}) + res := l7policies.CreateRule(context.TODO(), fake.ServiceClient(fakeServer), "", l7policies.CreateRuleOpts{}) if res.Err == nil { t.Fatalf("Expected error, got none") } - res = l7policies.CreateRule(context.TODO(), fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.CreateRuleOpts{ + res = l7policies.CreateRule(context.TODO(), fake.ServiceClient(fakeServer), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.CreateRuleOpts{ RuleType: l7policies.TypePath, }) if res.Err == nil { t.Fatalf("Expected error, but got none") } - res = l7policies.CreateRule(context.TODO(), fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.CreateRuleOpts{ + res = l7policies.CreateRule(context.TODO(), fake.ServiceClient(fakeServer), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.CreateRuleOpts{ RuleType: l7policies.RuleType("invalid"), CompareType: l7policies.CompareTypeRegex, Value: "/images*", @@ -199,7 +202,7 @@ func TestRequiredRuleCreateOpts(t *testing.T) { if res.Err == nil { t.Fatalf("Expected error, but got none") } - res = l7policies.CreateRule(context.TODO(), fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.CreateRuleOpts{ + res = l7policies.CreateRule(context.TODO(), fake.ServiceClient(fakeServer), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.CreateRuleOpts{ RuleType: l7policies.TypePath, CompareType: l7policies.CompareType("invalid"), Value: "/images*", @@ -210,12 +213,12 @@ func TestRequiredRuleCreateOpts(t *testing.T) { } func TestListRules(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleRuleListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleRuleListSuccessfully(t, fakeServer) pages := 0 - err := l7policies.ListRules(fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.ListRulesOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := l7policies.ListRules(fake.ServiceClient(fakeServer), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.ListRulesOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := l7policies.ExtractRules(page) @@ -240,11 +243,11 @@ func TestListRules(t *testing.T) { } func TestListAllRules(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleRuleListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleRuleListSuccessfully(t, fakeServer) - allPages, err := l7policies.ListRules(fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.ListRulesOpts{}).AllPages(context.TODO()) + allPages, err := l7policies.ListRules(fake.ServiceClient(fakeServer), "8a1412f0-4c32-4257-8b07-af4770b604fd", l7policies.ListRulesOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := l7policies.ExtractRules(allPages) @@ -254,11 +257,11 @@ func TestListAllRules(t *testing.T) { } func TestGetRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleRuleGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleRuleGetSuccessfully(t, fakeServer) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) actual, err := l7policies.GetRule(context.TODO(), client, "8a1412f0-4c32-4257-8b07-af4770b604fd", "16621dbb-a736-4888-a57a-3ecd53df784c").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) @@ -268,20 +271,20 @@ func TestGetRule(t *testing.T) { } func TestDeleteRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleRuleDeletionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleRuleDeletionSuccessfully(t, fakeServer) - res := l7policies.DeleteRule(context.TODO(), fake.ServiceClient(), "8a1412f0-4c32-4257-8b07-af4770b604fd", "16621dbb-a736-4888-a57a-3ecd53df784c") + res := l7policies.DeleteRule(context.TODO(), fake.ServiceClient(fakeServer), "8a1412f0-4c32-4257-8b07-af4770b604fd", "16621dbb-a736-4888-a57a-3ecd53df784c") th.AssertNoErr(t, res.Err) } func TestUpdateRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleRuleUpdateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleRuleUpdateSuccessfully(t, fakeServer) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) invert := false key := "" actual, err := l7policies.UpdateRule(context.TODO(), client, "8a1412f0-4c32-4257-8b07-af4770b604fd", "16621dbb-a736-4888-a57a-3ecd53df784c", l7policies.UpdateRuleOpts{ @@ -299,17 +302,17 @@ func TestUpdateRule(t *testing.T) { } func TestUpdateRuleWithInvalidOpts(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - res := l7policies.UpdateRule(context.TODO(), fake.ServiceClient(), "", "", l7policies.UpdateRuleOpts{ + res := l7policies.UpdateRule(context.TODO(), fake.ServiceClient(fakeServer), "", "", l7policies.UpdateRuleOpts{ RuleType: l7policies.RuleType("invalid"), }) if res.Err == nil { t.Fatalf("Expected error, got none") } - res = l7policies.UpdateRule(context.TODO(), fake.ServiceClient(), "", "", l7policies.UpdateRuleOpts{ + res = l7policies.UpdateRule(context.TODO(), fake.ServiceClient(fakeServer), "", "", l7policies.UpdateRuleOpts{ CompareType: l7policies.CompareType("invalid"), }) if res.Err == nil { diff --git a/openstack/loadbalancer/v2/listeners/requests.go b/openstack/loadbalancer/v2/listeners/requests.go index ea5b10dd2f..abd5d08970 100644 --- a/openstack/loadbalancer/v2/listeners/requests.go +++ b/openstack/loadbalancer/v2/listeners/requests.go @@ -37,6 +37,15 @@ const ( TLSVersionTLSv1_3 TLSVersion = "TLSv1.3" ) +// ClientAuthentication represents the TLS client authentication mode. +type ClientAuthentication string + +const ( + ClientAuthenticationNone ClientAuthentication = "NONE" + ClientAuthenticationOptional ClientAuthentication = "OPTIONAL" + ClientAuthenticationMandatory ClientAuthentication = "MANDATORY" +) + // ListOptsBuilder allows extensions to add additional parameters to the // List request. type ListOptsBuilder interface { @@ -170,6 +179,46 @@ type CreateOpts struct { // A list of IPv4, IPv6 or mix of both CIDRs AllowedCIDRs []string `json:"allowed_cidrs,omitempty"` + // A list of ALPN protocols. Available protocols: http/1.0, http/1.1, + // h2. Available from microversion 2.20. + ALPNProtocols []string `json:"alpn_protocols,omitempty"` + + // The TLS client authentication mode. One of the options NONE, + // OPTIONAL or MANDATORY. Available from microversion 2.8. + ClientAuthentication ClientAuthentication `json:"client_authentication,omitempty"` + + // The ref of the key manager service secret containing a PEM format + // client CA certificate bundle for TERMINATED_HTTPS listeners. + // Available from microversion 2.8. + ClientCATLSContainerRef string `json:"client_ca_tls_container_ref,omitempty"` + + // The URI of the key manager service secret containing a PEM format CA + // revocation list file for TERMINATED_HTTPS listeners. Available from + // microversion 2.8. + ClientCRLContainerRef string `json:"client_crl_container_ref,omitempty"` + + // Defines whether the includeSubDomains directive should be added to + // the Strict-Transport-Security HTTP response header. This requires + // setting the hsts_max_age option as well in order to become + // effective. Available from microversion 2.27. + HSTSIncludeSubdomains bool `json:"hsts_include_subdomains,omitempty"` + + // The value of the max_age directive for the Strict-Transport-Security + // HTTP response header. Setting this enables HTTP Strict Transport + // Security (HSTS) for the TLS-terminated listener. Available from + // microversion 2.27. + HSTSMaxAge int `json:"hsts_max_age,omitempty"` + + // Defines whether the preload directive should be added to the + // Strict-Transport-Security HTTP response header. This requires + // setting the hsts_max_age option as well in order to become + // effective. Available from microversion 2.27. + HSTSPreload bool `json:"hsts_preload,omitempty"` + + // List of ciphers in OpenSSL format (colon-separated). Available from + // microversion 2.15. + TLSCiphers string `json:"tls_ciphers,omitempty"` + // A list of TLS protocol versions. Available from microversion 2.17 TLSVersions []TLSVersion `json:"tls_versions,omitempty"` @@ -255,6 +304,46 @@ type UpdateOpts struct { // A list of IPv4, IPv6 or mix of both CIDRs AllowedCIDRs *[]string `json:"allowed_cidrs,omitempty"` + // A list of ALPN protocols. Available protocols: http/1.0, http/1.1, + // h2. Available from microversion 2.20. + ALPNProtocols *[]string `json:"alpn_protocols,omitempty"` + + // The TLS client authentication mode. One of the options NONE, + // OPTIONAL or MANDATORY. Available from microversion 2.8. + ClientAuthentication *ClientAuthentication `json:"client_authentication,omitempty"` + + // The ref of the key manager service secret containing a PEM format + // client CA certificate bundle for TERMINATED_HTTPS listeners. + // Available from microversion 2.8. + ClientCATLSContainerRef *string `json:"client_ca_tls_container_ref,omitempty"` + + // The URI of the key manager service secret containing a PEM format CA + // revocation list file for TERMINATED_HTTPS listeners. Available from + // microversion 2.8. + ClientCRLContainerRef *string `json:"client_crl_container_ref,omitempty"` + + // Defines whether the includeSubDomains directive should be added to + // the Strict-Transport-Security HTTP response header. This requires + // setting the hsts_max_age option as well in order to become + // effective. Available from microversion 2.27. + HSTSIncludeSubdomains *bool `json:"hsts_include_subdomains,omitempty"` + + // The value of the max_age directive for the Strict-Transport-Security + // HTTP response header. Setting this enables HTTP Strict Transport + // Security (HSTS) for the TLS-terminated listener. Available from + // microversion 2.27. + HSTSMaxAge *int `json:"hsts_max_age,omitempty"` + + // Defines whether the preload directive should be added to the + // Strict-Transport-Security HTTP response header. This requires + // setting the hsts_max_age option as well in order to become + // effective. Available from microversion 2.27. + HSTSPreload *bool `json:"hsts_preload,omitempty"` + + // List of ciphers in OpenSSL format (colon-separated). Available from + // microversion 2.15. + TLSCiphers *string `json:"tls_ciphers,omitempty"` + // A list of TLS protocol versions. Available from microversion 2.17 TLSVersions *[]TLSVersion `json:"tls_versions,omitempty"` @@ -269,16 +358,29 @@ func (opts UpdateOpts) ToListenerUpdateMap() (map[string]any, error) { return nil, err } - if m := b["listener"].(map[string]any); m["default_pool_id"] == "" { + m := b["listener"].(map[string]any) + + // allow to unset default_pool_id on empty string + if m["default_pool_id"] == "" { m["default_pool_id"] = nil } + // allow to unset alpn_protocols on empty slice + if opts.ALPNProtocols != nil && len(*opts.ALPNProtocols) == 0 { + m["alpn_protocols"] = nil + } + + // allow to unset tls_versions on empty slice + if opts.TLSVersions != nil && len(*opts.TLSVersions) == 0 { + m["tls_versions"] = nil + } + return b, nil } // Update is an operation which modifies the attributes of the specified // Listener. -func Update(ctx context.Context, c *gophercloud.ServiceClient, id string, opts UpdateOpts) (r UpdateResult) { +func Update(ctx context.Context, c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { b, err := opts.ToListenerUpdateMap() if err != nil { r.Err = err diff --git a/openstack/loadbalancer/v2/listeners/results.go b/openstack/loadbalancer/v2/listeners/results.go index 562a7618dd..06d6298cee 100644 --- a/openstack/loadbalancer/v2/listeners/results.go +++ b/openstack/loadbalancer/v2/listeners/results.go @@ -114,6 +114,24 @@ type Listener struct { // New in version 2.8 ClientCRLContainerRef string `json:"client_crl_container_ref"` + // Defines whether the includeSubDomains directive should be added to + // the Strict-Transport-Security HTTP response header. This requires + // setting the hsts_max_age option as well in order to become + // effective. Available from microversion 2.27. + HSTSIncludeSubdomains bool `json:"hsts_include_subdomains"` + + // The value of the max_age directive for the Strict-Transport-Security + // HTTP response header. Setting this enables HTTP Strict Transport + // Security (HSTS) for the TLS-terminated listener. Available from + // microversion 2.27. + HSTSMaxAge int `json:"hsts_max_age"` + + // Defines whether the preload directive should be added to the + // Strict-Transport-Security HTTP response header. This requires + // setting the hsts_max_age option as well in order to become + // effective. Available from microversion 2.27. + HSTSPreload bool `json:"hsts_preload"` + // The operating status of the resource OperatingStatus string `json:"operating_status"` } @@ -144,7 +162,7 @@ type ListenerPage struct { // NextPageURL is invoked when a paginated collection of listeners has reached // the end of a page and the pager seeks to traverse over a new one. In order // to do this, it needs to construct the next page's URL. -func (r ListenerPage) NextPageURL() (string, error) { +func (r ListenerPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"listeners_links"` } diff --git a/openstack/loadbalancer/v2/listeners/testing/fixtures_test.go b/openstack/loadbalancer/v2/listeners/testing/fixtures_test.go index edda9b2bac..9cc5d3a0a2 100644 --- a/openstack/loadbalancer/v2/listeners/testing/fixtures_test.go +++ b/openstack/loadbalancer/v2/listeners/testing/fixtures_test.go @@ -206,8 +206,8 @@ var ( ) // HandleListenerListSuccessfully sets up the test server to respond to a listener List request. -func HandleListenerListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/listeners", func(w http.ResponseWriter, r *http.Request) { +func HandleListenerListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/listeners", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -218,9 +218,9 @@ func HandleListenerListSuccessfully(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, ListenersListBody) + fmt.Fprint(w, ListenersListBody) case "45e08a3e-a78f-4b40-a229-1e7e23eee1ab": - fmt.Fprintf(w, `{ "listeners": [] }`) + fmt.Fprint(w, `{ "listeners": [] }`) default: t.Fatalf("/v2.0/lbaas/listeners invoked with unexpected marker=[%s]", marker) } @@ -229,8 +229,8 @@ func HandleListenerListSuccessfully(t *testing.T) { // HandleListenerCreationSuccessfully sets up the test server to respond to a listener creation request // with a given response. -func HandleListenerCreationSuccessfully(t *testing.T, response string) { - th.Mux.HandleFunc("/v2.0/lbaas/listeners", func(w http.ResponseWriter, r *http.Request) { +func HandleListenerCreationSuccessfully(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/listeners", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -255,24 +255,24 @@ func HandleListenerCreationSuccessfully(t *testing.T, response string) { w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) } // HandleListenerGetSuccessfully sets up the test server to respond to a listener Get request. -func HandleListenerGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/listeners/4ec89087-d057-4e2c-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { +func HandleListenerGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/listeners/4ec89087-d057-4e2c-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleListenerBody) + fmt.Fprint(w, SingleListenerBody) }) } // HandleListenerDeletionSuccessfully sets up the test server to respond to a listener deletion request. -func HandleListenerDeletionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/listeners/4ec89087-d057-4e2c-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { +func HandleListenerDeletionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/listeners/4ec89087-d057-4e2c-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -281,8 +281,8 @@ func HandleListenerDeletionSuccessfully(t *testing.T) { } // HandleListenerUpdateSuccessfully sets up the test server to respond to a listener Update request. -func HandleListenerUpdateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/listeners/4ec89087-d057-4e2c-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { +func HandleListenerUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/listeners/4ec89087-d057-4e2c-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") @@ -304,17 +304,17 @@ func HandleListenerUpdateSuccessfully(t *testing.T) { } }`) - fmt.Fprintf(w, PostUpdateListenerBody) + fmt.Fprint(w, PostUpdateListenerBody) }) } // HandleListenerGetStatsTree sets up the test server to respond to a listener Get stats tree request. -func HandleListenerGetStatsTree(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/listeners/4ec89087-d057-4e2c-911f-60a3b47ee304/stats", func(w http.ResponseWriter, r *http.Request) { +func HandleListenerGetStatsTree(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/listeners/4ec89087-d057-4e2c-911f-60a3b47ee304/stats", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, GetListenerStatsBody) + fmt.Fprint(w, GetListenerStatsBody) }) } diff --git a/openstack/loadbalancer/v2/listeners/testing/requests_test.go b/openstack/loadbalancer/v2/listeners/testing/requests_test.go index 220119a43a..346aa86135 100644 --- a/openstack/loadbalancer/v2/listeners/testing/requests_test.go +++ b/openstack/loadbalancer/v2/listeners/testing/requests_test.go @@ -12,12 +12,12 @@ import ( ) func TestListListeners(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListenerListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListenerListSuccessfully(t, fakeServer) pages := 0 - err := listeners.List(fake.ServiceClient(), listeners.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := listeners.List(fake.ServiceClient(fakeServer), listeners.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := listeners.ExtractListeners(page) @@ -42,11 +42,11 @@ func TestListListeners(t *testing.T) { } func TestListAllListeners(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListenerListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListenerListSuccessfully(t, fakeServer) - allPages, err := listeners.List(fake.ServiceClient(), listeners.ListOpts{}).AllPages(context.TODO()) + allPages, err := listeners.List(fake.ServiceClient(fakeServer), listeners.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := listeners.ExtractListeners(allPages) th.AssertNoErr(t, err) @@ -55,11 +55,11 @@ func TestListAllListeners(t *testing.T) { } func TestCreateListener(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListenerCreationSuccessfully(t, SingleListenerBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListenerCreationSuccessfully(t, fakeServer, SingleListenerBody) - actual, err := listeners.Create(context.TODO(), fake.ServiceClient(), listeners.CreateOpts{ + actual, err := listeners.Create(context.TODO(), fake.ServiceClient(fakeServer), listeners.CreateOpts{ Protocol: "TCP", Name: "db", LoadbalancerID: "79e05663-7f03-45d2-a092-8b94062f22ab", @@ -77,34 +77,37 @@ func TestCreateListener(t *testing.T) { } func TestRequiredCreateOpts(t *testing.T) { - res := listeners.Create(context.TODO(), fake.ServiceClient(), listeners.CreateOpts{}) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + res := listeners.Create(context.TODO(), fake.ServiceClient(fakeServer), listeners.CreateOpts{}) if res.Err == nil { t.Fatalf("Expected error, got none") } - res = listeners.Create(context.TODO(), fake.ServiceClient(), listeners.CreateOpts{Name: "foo"}) + res = listeners.Create(context.TODO(), fake.ServiceClient(fakeServer), listeners.CreateOpts{Name: "foo"}) if res.Err == nil { t.Fatalf("Expected error, got none") } - res = listeners.Create(context.TODO(), fake.ServiceClient(), listeners.CreateOpts{Name: "foo", ProjectID: "bar"}) + res = listeners.Create(context.TODO(), fake.ServiceClient(fakeServer), listeners.CreateOpts{Name: "foo", ProjectID: "bar"}) if res.Err == nil { t.Fatalf("Expected error, got none") } - res = listeners.Create(context.TODO(), fake.ServiceClient(), listeners.CreateOpts{Name: "foo", ProjectID: "bar", Protocol: "bar"}) + res = listeners.Create(context.TODO(), fake.ServiceClient(fakeServer), listeners.CreateOpts{Name: "foo", ProjectID: "bar", Protocol: "bar"}) if res.Err == nil { t.Fatalf("Expected error, got none") } - res = listeners.Create(context.TODO(), fake.ServiceClient(), listeners.CreateOpts{Name: "foo", ProjectID: "bar", Protocol: "bar", ProtocolPort: 80}) + res = listeners.Create(context.TODO(), fake.ServiceClient(fakeServer), listeners.CreateOpts{Name: "foo", ProjectID: "bar", Protocol: "bar", ProtocolPort: 80}) if res.Err == nil { t.Fatalf("Expected error, got none") } } func TestGetListener(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListenerGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListenerGetSuccessfully(t, fakeServer) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) actual, err := listeners.Get(context.TODO(), client, "4ec89087-d057-4e2c-911f-60a3b47ee304").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) @@ -114,20 +117,20 @@ func TestGetListener(t *testing.T) { } func TestDeleteListener(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListenerDeletionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListenerDeletionSuccessfully(t, fakeServer) - res := listeners.Delete(context.TODO(), fake.ServiceClient(), "4ec89087-d057-4e2c-911f-60a3b47ee304") + res := listeners.Delete(context.TODO(), fake.ServiceClient(fakeServer), "4ec89087-d057-4e2c-911f-60a3b47ee304") th.AssertNoErr(t, res.Err) } func TestUpdateListener(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListenerUpdateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListenerUpdateSuccessfully(t, fakeServer) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) i1001 := 1001 i181000 := 181000 name := "NewListenerName" @@ -156,11 +159,11 @@ func TestUpdateListener(t *testing.T) { } func TestGetListenerStatsTree(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListenerGetStatsTree(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListenerGetStatsTree(t, fakeServer) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) actual, err := listeners.GetStats(context.TODO(), client, "4ec89087-d057-4e2c-911f-60a3b47ee304").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) diff --git a/openstack/loadbalancer/v2/loadbalancers/requests.go b/openstack/loadbalancer/v2/loadbalancers/requests.go index f815806f39..095170edd3 100644 --- a/openstack/loadbalancer/v2/loadbalancers/requests.go +++ b/openstack/loadbalancer/v2/loadbalancers/requests.go @@ -208,7 +208,7 @@ func (opts UpdateOpts) ToLoadBalancerUpdateMap() (map[string]any, error) { // Update is an operation which modifies the attributes of the specified // LoadBalancer. -func Update(ctx context.Context, c *gophercloud.ServiceClient, id string, opts UpdateOpts) (r UpdateResult) { +func Update(ctx context.Context, c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { b, err := opts.ToLoadBalancerUpdateMap() if err != nil { r.Err = err diff --git a/openstack/loadbalancer/v2/loadbalancers/results.go b/openstack/loadbalancer/v2/loadbalancers/results.go index 67e5c749df..8433da0110 100644 --- a/openstack/loadbalancer/v2/loadbalancers/results.go +++ b/openstack/loadbalancer/v2/loadbalancers/results.go @@ -158,7 +158,7 @@ type LoadBalancerPage struct { // NextPageURL is invoked when a paginated collection of load balancers has // reached the end of a page and the pager seeks to traverse over a new one. // In order to do this, it needs to construct the next page's URL. -func (r LoadBalancerPage) NextPageURL() (string, error) { +func (r LoadBalancerPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"loadbalancers_links"` } diff --git a/openstack/loadbalancer/v2/loadbalancers/testing/fixtures_test.go b/openstack/loadbalancer/v2/loadbalancers/testing/fixtures_test.go index d4f714f7c5..a9e7f0a621 100644 --- a/openstack/loadbalancer/v2/loadbalancers/testing/fixtures_test.go +++ b/openstack/loadbalancer/v2/loadbalancers/testing/fixtures_test.go @@ -441,8 +441,8 @@ var ( ) // HandleLoadbalancerListSuccessfully sets up the test server to respond to a loadbalancer List request. -func HandleLoadbalancerListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers", func(w http.ResponseWriter, r *http.Request) { +func HandleLoadbalancerListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/loadbalancers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -453,9 +453,9 @@ func HandleLoadbalancerListSuccessfully(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, LoadbalancersListBody) + fmt.Fprint(w, LoadbalancersListBody) case "45e08a3e-a78f-4b40-a229-1e7e23eee1ab": - fmt.Fprintf(w, `{ "loadbalancers": [] }`) + fmt.Fprint(w, `{ "loadbalancers": [] }`) default: t.Fatalf("/v2.0/lbaas/loadbalancers invoked with unexpected marker=[%s]", marker) } @@ -464,8 +464,8 @@ func HandleLoadbalancerListSuccessfully(t *testing.T) { // HandleFullyPopulatedLoadbalancerCreationSuccessfully sets up the test server to respond to a // fully populated loadbalancer creation request with a given response. -func HandleFullyPopulatedLoadbalancerCreationSuccessfully(t *testing.T, response string) { - th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers", func(w http.ResponseWriter, r *http.Request) { +func HandleFullyPopulatedLoadbalancerCreationSuccessfully(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/loadbalancers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -533,14 +533,14 @@ func HandleFullyPopulatedLoadbalancerCreationSuccessfully(t *testing.T, response w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) } // HandleLoadbalancerCreationSuccessfully sets up the test server to respond to a loadbalancer creation request // with a given response. -func HandleLoadbalancerCreationSuccessfully(t *testing.T, response string) { - th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers", func(w http.ResponseWriter, r *http.Request) { +func HandleLoadbalancerCreationSuccessfully(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/loadbalancers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -559,35 +559,35 @@ func HandleLoadbalancerCreationSuccessfully(t *testing.T, response string) { w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) } // HandleLoadbalancerGetSuccessfully sets up the test server to respond to a loadbalancer Get request. -func HandleLoadbalancerGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/36e08a3e-a78f-4b40-a229-1e7e23eee1ab", func(w http.ResponseWriter, r *http.Request) { +func HandleLoadbalancerGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/36e08a3e-a78f-4b40-a229-1e7e23eee1ab", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleLoadbalancerBody) + fmt.Fprint(w, SingleLoadbalancerBody) }) } // HandleLoadbalancerGetStatusesTree sets up the test server to respond to a loadbalancer Get statuses tree request. -func HandleLoadbalancerGetStatusesTree(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/36e08a3e-a78f-4b40-a229-1e7e23eee1ab/status", func(w http.ResponseWriter, r *http.Request) { +func HandleLoadbalancerGetStatusesTree(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/36e08a3e-a78f-4b40-a229-1e7e23eee1ab/status", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, GetLoadbalancerStatusesBody) + fmt.Fprint(w, GetLoadbalancerStatusesBody) }) } // HandleLoadbalancerDeletionSuccessfully sets up the test server to respond to a loadbalancer deletion request. -func HandleLoadbalancerDeletionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/36e08a3e-a78f-4b40-a229-1e7e23eee1ab", func(w http.ResponseWriter, r *http.Request) { +func HandleLoadbalancerDeletionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/36e08a3e-a78f-4b40-a229-1e7e23eee1ab", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -596,8 +596,8 @@ func HandleLoadbalancerDeletionSuccessfully(t *testing.T) { } // HandleLoadbalancerUpdateSuccessfully sets up the test server to respond to a loadbalancer Update request. -func HandleLoadbalancerUpdateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/36e08a3e-a78f-4b40-a229-1e7e23eee1ab", func(w http.ResponseWriter, r *http.Request) { +func HandleLoadbalancerUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/36e08a3e-a78f-4b40-a229-1e7e23eee1ab", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") @@ -609,24 +609,24 @@ func HandleLoadbalancerUpdateSuccessfully(t *testing.T) { } }`) - fmt.Fprintf(w, PostUpdateLoadbalancerBody) + fmt.Fprint(w, PostUpdateLoadbalancerBody) }) } // HandleLoadbalancerGetStatsTree sets up the test server to respond to a loadbalancer Get stats tree request. -func HandleLoadbalancerGetStatsTree(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/36e08a3e-a78f-4b40-a229-1e7e23eee1ab/stats", func(w http.ResponseWriter, r *http.Request) { +func HandleLoadbalancerGetStatsTree(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/36e08a3e-a78f-4b40-a229-1e7e23eee1ab/stats", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, GetLoadbalancerStatsBody) + fmt.Fprint(w, GetLoadbalancerStatsBody) }) } // HandleLoadbalancerFailoverSuccessfully sets up the test server to respond to a loadbalancer failover request. -func HandleLoadbalancerFailoverSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/36e08a3e-a78f-4b40-a229-1e7e23eee1ab/failover", func(w http.ResponseWriter, r *http.Request) { +func HandleLoadbalancerFailoverSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/loadbalancers/36e08a3e-a78f-4b40-a229-1e7e23eee1ab/failover", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) diff --git a/openstack/loadbalancer/v2/loadbalancers/testing/requests_test.go b/openstack/loadbalancer/v2/loadbalancers/testing/requests_test.go index 04fe8549d3..b5a2a3bafe 100644 --- a/openstack/loadbalancer/v2/loadbalancers/testing/requests_test.go +++ b/openstack/loadbalancer/v2/loadbalancers/testing/requests_test.go @@ -17,12 +17,12 @@ import ( ) func TestListLoadbalancers(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleLoadbalancerListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleLoadbalancerListSuccessfully(t, fakeServer) pages := 0 - err := loadbalancers.List(fake.ServiceClient(), loadbalancers.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := loadbalancers.List(fake.ServiceClient(fakeServer), loadbalancers.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := loadbalancers.ExtractLoadBalancers(page) @@ -47,11 +47,11 @@ func TestListLoadbalancers(t *testing.T) { } func TestListAllLoadbalancers(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleLoadbalancerListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleLoadbalancerListSuccessfully(t, fakeServer) - allPages, err := loadbalancers.List(fake.ServiceClient(), loadbalancers.ListOpts{}).AllPages(context.TODO()) + allPages, err := loadbalancers.List(fake.ServiceClient(fakeServer), loadbalancers.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := loadbalancers.ExtractLoadBalancers(allPages) th.AssertNoErr(t, err) @@ -60,11 +60,11 @@ func TestListAllLoadbalancers(t *testing.T) { } func TestCreateLoadbalancer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleLoadbalancerCreationSuccessfully(t, SingleLoadbalancerBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleLoadbalancerCreationSuccessfully(t, fakeServer, SingleLoadbalancerBody) - actual, err := loadbalancers.Create(context.TODO(), fake.ServiceClient(), loadbalancers.CreateOpts{ + actual, err := loadbalancers.Create(context.TODO(), fake.ServiceClient(fakeServer), loadbalancers.CreateOpts{ Name: "db_lb", AdminStateUp: gophercloud.Enabled, VipPortID: "2bf413c8-41a9-4477-b505-333d5cbe8b55", @@ -86,11 +86,11 @@ func TestCreateLoadbalancer(t *testing.T) { } func TestCreateFullyPopulatedLoadbalancer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleFullyPopulatedLoadbalancerCreationSuccessfully(t, PostFullyPopulatedLoadbalancerBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleFullyPopulatedLoadbalancerCreationSuccessfully(t, fakeServer, PostFullyPopulatedLoadbalancerBody) - actual, err := loadbalancers.Create(context.TODO(), fake.ServiceClient(), loadbalancers.CreateOpts{ + actual, err := loadbalancers.Create(context.TODO(), fake.ServiceClient(fakeServer), loadbalancers.CreateOpts{ Name: "db_lb", AdminStateUp: gophercloud.Enabled, VipPortID: "2bf413c8-41a9-4477-b505-333d5cbe8b55", @@ -144,11 +144,11 @@ func TestCreateFullyPopulatedLoadbalancer(t *testing.T) { } func TestGetLoadbalancer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleLoadbalancerGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleLoadbalancerGetSuccessfully(t, fakeServer) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) actual, err := loadbalancers.Get(context.TODO(), client, "36e08a3e-a78f-4b40-a229-1e7e23eee1ab").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) @@ -158,11 +158,11 @@ func TestGetLoadbalancer(t *testing.T) { } func TestGetLoadbalancerStatusesTree(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleLoadbalancerGetStatusesTree(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleLoadbalancerGetStatusesTree(t, fakeServer) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) actual, err := loadbalancers.GetStatuses(context.TODO(), client, "36e08a3e-a78f-4b40-a229-1e7e23eee1ab").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) @@ -172,20 +172,20 @@ func TestGetLoadbalancerStatusesTree(t *testing.T) { } func TestDeleteLoadbalancer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleLoadbalancerDeletionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleLoadbalancerDeletionSuccessfully(t, fakeServer) - res := loadbalancers.Delete(context.TODO(), fake.ServiceClient(), "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", nil) + res := loadbalancers.Delete(context.TODO(), fake.ServiceClient(fakeServer), "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", nil) th.AssertNoErr(t, res.Err) } func TestUpdateLoadbalancer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleLoadbalancerUpdateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleLoadbalancerUpdateSuccessfully(t, fakeServer) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) name := "NewLoadbalancerName" tags := []string{"test"} actual, err := loadbalancers.Update(context.TODO(), client, "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", loadbalancers.UpdateOpts{ @@ -200,29 +200,29 @@ func TestUpdateLoadbalancer(t *testing.T) { } func TestCascadingDeleteLoadbalancer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleLoadbalancerDeletionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleLoadbalancerDeletionSuccessfully(t, fakeServer) - sc := fake.ServiceClient() + sc := fake.ServiceClient(fakeServer) deleteOpts := loadbalancers.DeleteOpts{ Cascade: true, } query, err := deleteOpts.ToLoadBalancerDeleteQuery() th.AssertNoErr(t, err) - th.AssertEquals(t, query, "?cascade=true") + th.AssertEquals(t, "?cascade=true", query) err = loadbalancers.Delete(context.TODO(), sc, "36e08a3e-a78f-4b40-a229-1e7e23eee1ab", deleteOpts).ExtractErr() th.AssertNoErr(t, err) } func TestGetLoadbalancerStatsTree(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleLoadbalancerGetStatsTree(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleLoadbalancerGetStatsTree(t, fakeServer) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) actual, err := loadbalancers.GetStats(context.TODO(), client, "36e08a3e-a78f-4b40-a229-1e7e23eee1ab").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) @@ -232,10 +232,10 @@ func TestGetLoadbalancerStatsTree(t *testing.T) { } func TestFailoverLoadbalancer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleLoadbalancerFailoverSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleLoadbalancerFailoverSuccessfully(t, fakeServer) - res := loadbalancers.Failover(context.TODO(), fake.ServiceClient(), "36e08a3e-a78f-4b40-a229-1e7e23eee1ab") + res := loadbalancers.Failover(context.TODO(), fake.ServiceClient(fakeServer), "36e08a3e-a78f-4b40-a229-1e7e23eee1ab") th.AssertNoErr(t, res.Err) } diff --git a/openstack/loadbalancer/v2/monitors/requests.go b/openstack/loadbalancer/v2/monitors/requests.go index 5667955add..15a503badc 100644 --- a/openstack/loadbalancer/v2/monitors/requests.go +++ b/openstack/loadbalancer/v2/monitors/requests.go @@ -2,6 +2,7 @@ package monitors import ( "context" + "strconv" "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/pagination" @@ -119,6 +120,10 @@ type CreateOpts struct { // is not specified, it defaults to "GET". Required for HTTP(S) types. HTTPMethod string `json:"http_method,omitempty"` + // The HTTP version. One of 1.0 or 1.1. The default is 1.0. New in + // version 2.10. + HTTPVersion string `json:"http_version,omitempty"` + // Expected HTTP codes for a passing HTTP(S) Monitor. You can either specify // a single status like "200", a range like "200-202", or a combination like // "200-202, 401". @@ -139,13 +144,35 @@ type CreateOpts struct { // or false (DOWN). AdminStateUp *bool `json:"admin_state_up,omitempty"` + // The domain name, which be injected into the HTTP Host Header to the + // backend server for HTTP health check. New in version 2.10 + DomainName string `json:"domain_name,omitempty"` + // Tags is a set of resource tags. New in version 2.5 Tags []string `json:"tags,omitempty"` } // ToMonitorCreateMap builds a request body from CreateOpts. func (opts CreateOpts) ToMonitorCreateMap() (map[string]any, error) { - return gophercloud.BuildRequestBody(opts, "healthmonitor") + b, err := gophercloud.BuildRequestBody(opts, "healthmonitor") + if err != nil { + return nil, err + } + + if v, ok := b["healthmonitor"]; ok { + if m, ok := v.(map[string]any); ok { + if v, ok := m["http_version"]; ok { + if v, ok := v.(string); ok { + m["http_version"], err = strconv.ParseFloat(v, 64) + if err != nil { + return nil, err + } + } + } + } + } + + return b, nil } /* @@ -213,6 +240,10 @@ type UpdateOpts struct { // is not specified, it defaults to "GET". Required for HTTP(S) types. HTTPMethod string `json:"http_method,omitempty"` + // The HTTP version. One of 1.0 or 1.1. The default is 1.0. New in + // version 2.10. + HTTPVersion *string `json:"http_version,omitempty"` + // Expected HTTP codes for a passing HTTP(S) Monitor. You can either specify // a single status like "200", or a range like "200-202". Required for HTTP(S) // types. @@ -221,6 +252,10 @@ type UpdateOpts struct { // The Name of the Monitor. Name *string `json:"name,omitempty"` + // The domain name, which be injected into the HTTP Host Header to the + // backend server for HTTP health check. New in version 2.10 + DomainName *string `json:"domain_name,omitempty"` + // The administrative state of the Monitor. A valid value is true (UP) // or false (DOWN). AdminStateUp *bool `json:"admin_state_up,omitempty"` @@ -231,7 +266,25 @@ type UpdateOpts struct { // ToMonitorUpdateMap builds a request body from UpdateOpts. func (opts UpdateOpts) ToMonitorUpdateMap() (map[string]any, error) { - return gophercloud.BuildRequestBody(opts, "healthmonitor") + b, err := gophercloud.BuildRequestBody(opts, "healthmonitor") + if err != nil { + return nil, err + } + + if v, ok := b["healthmonitor"]; ok { + if m, ok := v.(map[string]any); ok { + if v, ok := m["http_version"]; ok { + if v, ok := v.(string); ok { + m["http_version"], err = strconv.ParseFloat(v, 64) + if err != nil { + return nil, err + } + } + } + } + } + + return b, nil } // Update is an operation which modifies the attributes of the specified diff --git a/openstack/loadbalancer/v2/monitors/results.go b/openstack/loadbalancer/v2/monitors/results.go index 3a29da9f40..65ca74a878 100644 --- a/openstack/loadbalancer/v2/monitors/results.go +++ b/openstack/loadbalancer/v2/monitors/results.go @@ -1,6 +1,9 @@ package monitors import ( + "encoding/json" + "strconv" + "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/pagination" ) @@ -60,6 +63,9 @@ type Monitor struct { // The HTTP method that the monitor uses for requests. HTTPMethod string `json:"http_method"` + // The HTTP version that the monitor uses for requests. + HTTPVersion string `json:"-"` + // The HTTP path of the request sent by the monitor to test the health of a // member. Must be a string beginning with a forward slash (/). URLPath string `json:"url_path" ` @@ -67,6 +73,9 @@ type Monitor struct { // Expected HTTP codes for a passing HTTP(S) monitor. ExpectedCodes string `json:"expected_codes"` + // The HTTP host header that the monitor uses for requests. + DomainName string `json:"domain_name"` + // The administrative state of the health monitor, which is up (true) or // down (false). AdminStateUp bool `json:"admin_state_up"` @@ -90,6 +99,26 @@ type Monitor struct { Tags []string `json:"tags"` } +func (r *Monitor) UnmarshalJSON(b []byte) error { + type tmp Monitor + var s struct { + tmp + HTTPVersion float64 `json:"http_version"` + } + + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = Monitor(s.tmp) + if s.HTTPVersion != 0 { + r.HTTPVersion = strconv.FormatFloat(s.HTTPVersion, 'f', 1, 64) + } + + return nil +} + // MonitorPage is the page returned by a pager when traversing over a // collection of health monitors. type MonitorPage struct { @@ -99,7 +128,7 @@ type MonitorPage struct { // NextPageURL is invoked when a paginated collection of monitors has reached // the end of a page and the pager seeks to traverse over a new one. In order // to do this, it needs to construct the next page's URL. -func (r MonitorPage) NextPageURL() (string, error) { +func (r MonitorPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"healthmonitors_links"` } diff --git a/openstack/loadbalancer/v2/monitors/testing/fixtures_test.go b/openstack/loadbalancer/v2/monitors/testing/fixtures_test.go index 9526b9cbca..039da6708e 100644 --- a/openstack/loadbalancer/v2/monitors/testing/fixtures_test.go +++ b/openstack/loadbalancer/v2/monitors/testing/fixtures_test.go @@ -31,11 +31,13 @@ const HealthmonitorsListBody = ` "admin_state_up":true, "project_id":"83657cfcdfe44cd5920adaf26c48ceea", "delay":5, + "domain_name": "www.example.com", "name":"db", "expected_codes":"200", "max_retries":2, "max_retries_down":4, "http_method":"GET", + "http_version": 1.1, "timeout":2, "url_path":"/", "type":"HTTP", @@ -54,11 +56,13 @@ const SingleHealthmonitorBody = ` "admin_state_up":true, "project_id":"83657cfcdfe44cd5920adaf26c48ceea", "delay":5, + "domain_name": "www.example.com", "name":"db", "expected_codes":"200", "max_retries":2, "max_retries_down":4, "http_method":"GET", + "http_version": 1.1, "timeout":2, "url_path":"/", "type":"HTTP", @@ -76,11 +80,13 @@ const PostUpdateHealthmonitorBody = ` "admin_state_up":true, "project_id":"83657cfcdfe44cd5920adaf26c48ceea", "delay":3, + "domain_name": "www.example.com", "name":"NewHealthmonitorName", "expected_codes":"301", "max_retries":10, "max_retries_down":8, "http_method":"GET", + "http_version": 1.1, "timeout":20, "url_path":"/another_check", "type":"HTTP", @@ -107,6 +113,7 @@ var ( } HealthmonitorDb = monitors.Monitor{ AdminStateUp: true, + DomainName: "www.example.com", Name: "db", ProjectID: "83657cfcdfe44cd5920adaf26c48ceea", Delay: 5, @@ -117,12 +124,14 @@ var ( URLPath: "/", Type: "HTTP", HTTPMethod: "GET", + HTTPVersion: "1.1", ID: "5d4b5228-33b0-4e60-b225-9b727c1a20e7", Pools: []monitors.PoolID{{ID: "d459f7d8-c6ee-439d-8713-d3fc08aeed8d"}}, Tags: []string{}, } HealthmonitorUpdated = monitors.Monitor{ AdminStateUp: true, + DomainName: "www.example.com", Name: "NewHealthmonitorName", ProjectID: "83657cfcdfe44cd5920adaf26c48ceea", Delay: 3, @@ -133,6 +142,7 @@ var ( URLPath: "/another_check", Type: "HTTP", HTTPMethod: "GET", + HTTPVersion: "1.1", ID: "5d4b5228-33b0-4e60-b225-9b727c1a20e7", Pools: []monitors.PoolID{{ID: "d459f7d8-c6ee-439d-8713-d3fc08aeed8d"}}, Tags: []string{}, @@ -140,8 +150,8 @@ var ( ) // HandleHealthmonitorListSuccessfully sets up the test server to respond to a healthmonitor List request. -func HandleHealthmonitorListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/healthmonitors", func(w http.ResponseWriter, r *http.Request) { +func HandleHealthmonitorListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/healthmonitors", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -152,9 +162,9 @@ func HandleHealthmonitorListSuccessfully(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, HealthmonitorsListBody) + fmt.Fprint(w, HealthmonitorsListBody) case "556c8345-28d8-4f84-a246-e04380b0461d": - fmt.Fprintf(w, `{ "healthmonitors": [] }`) + fmt.Fprint(w, `{ "healthmonitors": [] }`) default: t.Fatalf("/v2.0/lbaas/healthmonitors invoked with unexpected marker=[%s]", marker) } @@ -163,8 +173,8 @@ func HandleHealthmonitorListSuccessfully(t *testing.T) { // HandleHealthmonitorCreationSuccessfully sets up the test server to respond to a healthmonitor creation request // with a given response. -func HandleHealthmonitorCreationSuccessfully(t *testing.T, response string) { - th.Mux.HandleFunc("/v2.0/lbaas/healthmonitors", func(w http.ResponseWriter, r *http.Request) { +func HandleHealthmonitorCreationSuccessfully(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/healthmonitors", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -173,7 +183,9 @@ func HandleHealthmonitorCreationSuccessfully(t *testing.T, response string) { "pool_id":"84f1b61f-58c4-45bf-a8a9-2dafb9e5214d", "project_id":"453105b9-1754-413f-aab1-55f1af620750", "delay":20, + "domain_name": "www.example.com", "name":"db", + "http_version": 1.1, "timeout":10, "max_retries":5, "max_retries_down":4, @@ -184,24 +196,24 @@ func HandleHealthmonitorCreationSuccessfully(t *testing.T, response string) { w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) } // HandleHealthmonitorGetSuccessfully sets up the test server to respond to a healthmonitor Get request. -func HandleHealthmonitorGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/healthmonitors/5d4b5228-33b0-4e60-b225-9b727c1a20e7", func(w http.ResponseWriter, r *http.Request) { +func HandleHealthmonitorGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/healthmonitors/5d4b5228-33b0-4e60-b225-9b727c1a20e7", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleHealthmonitorBody) + fmt.Fprint(w, SingleHealthmonitorBody) }) } // HandleHealthmonitorDeletionSuccessfully sets up the test server to respond to a healthmonitor deletion request. -func HandleHealthmonitorDeletionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/healthmonitors/5d4b5228-33b0-4e60-b225-9b727c1a20e7", func(w http.ResponseWriter, r *http.Request) { +func HandleHealthmonitorDeletionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/healthmonitors/5d4b5228-33b0-4e60-b225-9b727c1a20e7", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -210,8 +222,8 @@ func HandleHealthmonitorDeletionSuccessfully(t *testing.T) { } // HandleHealthmonitorUpdateSuccessfully sets up the test server to respond to a healthmonitor Update request. -func HandleHealthmonitorUpdateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/healthmonitors/5d4b5228-33b0-4e60-b225-9b727c1a20e7", func(w http.ResponseWriter, r *http.Request) { +func HandleHealthmonitorUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/healthmonitors/5d4b5228-33b0-4e60-b225-9b727c1a20e7", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") @@ -228,6 +240,6 @@ func HandleHealthmonitorUpdateSuccessfully(t *testing.T) { } }`) - fmt.Fprintf(w, PostUpdateHealthmonitorBody) + fmt.Fprint(w, PostUpdateHealthmonitorBody) }) } diff --git a/openstack/loadbalancer/v2/monitors/testing/requests_test.go b/openstack/loadbalancer/v2/monitors/testing/requests_test.go index 0ca13f360e..25f2c96c53 100644 --- a/openstack/loadbalancer/v2/monitors/testing/requests_test.go +++ b/openstack/loadbalancer/v2/monitors/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestListHealthmonitors(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleHealthmonitorListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleHealthmonitorListSuccessfully(t, fakeServer) pages := 0 - err := monitors.List(fake.ServiceClient(), monitors.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := monitors.List(fake.ServiceClient(fakeServer), monitors.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := monitors.ExtractMonitors(page) @@ -41,11 +41,11 @@ func TestListHealthmonitors(t *testing.T) { } func TestListAllHealthmonitors(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleHealthmonitorListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleHealthmonitorListSuccessfully(t, fakeServer) - allPages, err := monitors.List(fake.ServiceClient(), monitors.ListOpts{}).AllPages(context.TODO()) + allPages, err := monitors.List(fake.ServiceClient(fakeServer), monitors.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := monitors.ExtractMonitors(allPages) th.AssertNoErr(t, err) @@ -54,12 +54,13 @@ func TestListAllHealthmonitors(t *testing.T) { } func TestCreateHealthmonitor(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleHealthmonitorCreationSuccessfully(t, SingleHealthmonitorBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleHealthmonitorCreationSuccessfully(t, fakeServer, SingleHealthmonitorBody) - actual, err := monitors.Create(context.TODO(), fake.ServiceClient(), monitors.CreateOpts{ + actual, err := monitors.Create(context.TODO(), fake.ServiceClient(fakeServer), monitors.CreateOpts{ Type: "HTTP", + DomainName: "www.example.com", Name: "db", PoolID: "84f1b61f-58c4-45bf-a8a9-2dafb9e5214d", ProjectID: "453105b9-1754-413f-aab1-55f1af620750", @@ -67,6 +68,7 @@ func TestCreateHealthmonitor(t *testing.T) { Timeout: 10, MaxRetries: 5, MaxRetriesDown: 4, + HTTPVersion: "1.1", Tags: []string{}, URLPath: "/check", ExpectedCodes: "200-299", @@ -77,22 +79,25 @@ func TestCreateHealthmonitor(t *testing.T) { } func TestRequiredCreateOpts(t *testing.T) { - res := monitors.Create(context.TODO(), fake.ServiceClient(), monitors.CreateOpts{}) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + res := monitors.Create(context.TODO(), fake.ServiceClient(fakeServer), monitors.CreateOpts{}) if res.Err == nil { t.Fatalf("Expected error, got none") } - res = monitors.Create(context.TODO(), fake.ServiceClient(), monitors.CreateOpts{Type: monitors.TypeHTTP}) + res = monitors.Create(context.TODO(), fake.ServiceClient(fakeServer), monitors.CreateOpts{Type: monitors.TypeHTTP}) if res.Err == nil { t.Fatalf("Expected error, got none") } } func TestGetHealthmonitor(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleHealthmonitorGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleHealthmonitorGetSuccessfully(t, fakeServer) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) actual, err := monitors.Get(context.TODO(), client, "5d4b5228-33b0-4e60-b225-9b727c1a20e7").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) @@ -102,20 +107,20 @@ func TestGetHealthmonitor(t *testing.T) { } func TestDeleteHealthmonitor(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleHealthmonitorDeletionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleHealthmonitorDeletionSuccessfully(t, fakeServer) - res := monitors.Delete(context.TODO(), fake.ServiceClient(), "5d4b5228-33b0-4e60-b225-9b727c1a20e7") + res := monitors.Delete(context.TODO(), fake.ServiceClient(fakeServer), "5d4b5228-33b0-4e60-b225-9b727c1a20e7") th.AssertNoErr(t, res.Err) } func TestUpdateHealthmonitor(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleHealthmonitorUpdateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleHealthmonitorUpdateSuccessfully(t, fakeServer) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) name := "NewHealthmonitorName" actual, err := monitors.Update(context.TODO(), client, "5d4b5228-33b0-4e60-b225-9b727c1a20e7", monitors.UpdateOpts{ Name: &name, @@ -134,7 +139,10 @@ func TestUpdateHealthmonitor(t *testing.T) { } func TestDelayMustBeGreaterOrEqualThanTimeout(t *testing.T) { - _, err := monitors.Create(context.TODO(), fake.ServiceClient(), monitors.CreateOpts{ + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + _, err := monitors.Create(context.TODO(), fake.ServiceClient(fakeServer), monitors.CreateOpts{ Type: "HTTP", PoolID: "d459f7d8-c6ee-439d-8713-d3fc08aeed8d", Delay: 1, @@ -148,7 +156,7 @@ func TestDelayMustBeGreaterOrEqualThanTimeout(t *testing.T) { t.Fatalf("Expected error, got none") } - _, err = monitors.Update(context.TODO(), fake.ServiceClient(), "453105b9-1754-413f-aab1-55f1af620750", monitors.UpdateOpts{ + _, err = monitors.Update(context.TODO(), fake.ServiceClient(fakeServer), "453105b9-1754-413f-aab1-55f1af620750", monitors.UpdateOpts{ Delay: 1, Timeout: 10, }).Extract() diff --git a/openstack/loadbalancer/v2/pools/requests.go b/openstack/loadbalancer/v2/pools/requests.go index 452bfaddb9..3443686370 100644 --- a/openstack/loadbalancer/v2/pools/requests.go +++ b/openstack/loadbalancer/v2/pools/requests.go @@ -8,6 +8,17 @@ import ( "github.com/gophercloud/gophercloud/v2/pagination" ) +// Type TLSVersion represents a tls version +type TLSVersion string + +const ( + TLSVersionSSLv3 TLSVersion = "SSLv3" + TLSVersionTLSv1 TLSVersion = "TLSv1" + TLSVersionTLSv1_1 TLSVersion = "TLSv1.1" + TLSVersionTLSv1_2 TLSVersion = "TLSv1.2" + TLSVersionTLSv1_3 TLSVersion = "TLSv1.3" +) + // ListOptsBuilder allows extensions to add additional parameters to the // List request. type ListOptsBuilder interface { @@ -122,6 +133,37 @@ type CreateOpts struct { // Omit this field to prevent session persistence. Persistence *SessionPersistence `json:"session_persistence,omitempty"` + // A list of ALPN protocols. Available protocols: http/1.0, http/1.1, + // h2. Available from microversion 2.24. + ALPNProtocols []string `json:"alpn_protocols,omitempty"` + + // The reference of the key manager service secret containing a PEM + // format CA certificate bundle for tls_enabled pools. Available from + // microversion 2.8. + CATLSContainerRef string `json:"ca_tls_container_ref,omitempty"` + + // The reference of the key manager service secret containing a PEM + // format CA revocation list file for tls_enabled pools. Available from + // microversion 2.8. + CRLContainerRef string `json:"crl_container_ref,omitempty"` + + // When true connections to backend member servers will use TLS + // encryption. Default is false. Available from microversion 2.8. + TLSEnabled bool `json:"tls_enabled,omitempty"` + + // List of ciphers in OpenSSL format (colon-separated). Available from + // microversion 2.15. + TLSCiphers string `json:"tls_ciphers,omitempty"` + + // The reference to the key manager service secret containing a PKCS12 + // format certificate/key bundle for tls_enabled pools for TLS client + // authentication to the member servers. Available from microversion 2.8. + TLSContainerRef string `json:"tls_container_ref,omitempty"` + + // A list of TLS protocol versions. Available versions: SSLv3, TLSv1, + // TLSv1.1, TLSv1.2, TLSv1.3. Available from microversion 2.17. + TLSVersions []TLSVersion `json:"tls_versions,omitempty"` + // The administrative state of the Pool. A valid value is true (UP) // or false (DOWN). AdminStateUp *bool `json:"admin_state_up,omitempty"` @@ -196,13 +238,66 @@ type UpdateOpts struct { // Persistence is the session persistence of the pool. Persistence *SessionPersistence `json:"session_persistence,omitempty"` + // A list of ALPN protocols. Available protocols: http/1.0, http/1.1, + // h2. Available from microversion 2.24. + ALPNProtocols *[]string `json:"alpn_protocols,omitempty"` + + // The reference of the key manager service secret containing a PEM + // format CA certificate bundle for tls_enabled pools. Available from + // microversion 2.8. + CATLSContainerRef *string `json:"ca_tls_container_ref,omitempty"` + + // The reference of the key manager service secret containing a PEM + // format CA revocation list file for tls_enabled pools. Available from + // microversion 2.8. + CRLContainerRef *string `json:"crl_container_ref,omitempty"` + + // When true connections to backend member servers will use TLS + // encryption. Default is false. Available from microversion 2.8. + TLSEnabled *bool `json:"tls_enabled,omitempty"` + + // List of ciphers in OpenSSL format (colon-separated). Available from + // microversion 2.15. + TLSCiphers *string `json:"tls_ciphers,omitempty"` + + // The reference to the key manager service secret containing a PKCS12 + // format certificate/key bundle for tls_enabled pools for TLS client + // authentication to the member servers. Available from microversion 2.8. + TLSContainerRef *string `json:"tls_container_ref,omitempty"` + + // A list of TLS protocol versions. Available versions: SSLv3, TLSv1, + // TLSv1.1, TLSv1.2, TLSv1.3. Available from microversion 2.17. + TLSVersions *[]TLSVersion `json:"tls_versions,omitempty"` + // Tags is a set of resource tags. New in version 2.5 Tags *[]string `json:"tags,omitempty"` } // ToPoolUpdateMap builds a request body from UpdateOpts. func (opts UpdateOpts) ToPoolUpdateMap() (map[string]any, error) { - return gophercloud.BuildRequestBody(opts, "pool") + b, err := gophercloud.BuildRequestBody(opts, "pool") + if err != nil { + return nil, err + } + + m := b["pool"].(map[string]any) + + // allow to unset session_persistence on empty SessionPersistence struct + if opts.Persistence != nil && *opts.Persistence == (SessionPersistence{}) { + m["session_persistence"] = nil + } + + // allow to unset alpn_protocols on empty slice + if opts.ALPNProtocols != nil && len(*opts.ALPNProtocols) == 0 { + m["alpn_protocols"] = nil + } + + // allow to unset tls_versions on empty slice + if opts.TLSVersions != nil && len(*opts.TLSVersions) == 0 { + m["tls_versions"] = nil + } + + return b, nil } // Update allows pools to be updated. diff --git a/openstack/loadbalancer/v2/pools/results.go b/openstack/loadbalancer/v2/pools/results.go index 1f27752e0d..88f92a0993 100644 --- a/openstack/loadbalancer/v2/pools/results.go +++ b/openstack/loadbalancer/v2/pools/results.go @@ -95,6 +95,37 @@ type Pool struct { // same Pool member or not. Persistence SessionPersistence `json:"session_persistence"` + // A list of ALPN protocols. Available protocols: http/1.0, http/1.1, + // h2. Available from microversion 2.24. + ALPNProtocols []string `json:"alpn_protocols"` + + // The reference of the key manager service secret containing a PEM + // format CA certificate bundle for tls_enabled pools. Available from + // microversion 2.8. + CATLSContainerRef string `json:"ca_tls_container_ref"` + + // The reference of the key manager service secret containing a PEM + // format CA revocation list file for tls_enabled pools. Available from + // microversion 2.8. + CRLContainerRef string `json:"crl_container_ref"` + + // When true connections to backend member servers will use TLS + // encryption. Default is false. Available from microversion 2.8. + TLSEnabled bool `json:"tls_enabled"` + + // List of ciphers in OpenSSL format (colon-separated). Available from + // microversion 2.15. + TLSCiphers string `json:"tls_ciphers"` + + // The reference to the key manager service secret containing a PKCS12 + // format certificate/key bundle for tls_enabled pools for TLS client + // authentication to the member servers. Available from microversion 2.8. + TLSContainerRef string `json:"tls_container_ref"` + + // A list of TLS protocol versions. Available versions: SSLv3, TLSv1, + // TLSv1.1, TLSv1.2, TLSv1.3. Available from microversion 2.17. + TLSVersions []string `json:"tls_versions"` + // The load balancer provider. Provider string `json:"provider"` @@ -122,7 +153,7 @@ type PoolPage struct { // NextPageURL is invoked when a paginated collection of pools has reached // the end of a page and the pager seeks to traverse over a new one. In order // to do this, it needs to construct the next page's URL. -func (r PoolPage) NextPageURL() (string, error) { +func (r PoolPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"pools_links"` } @@ -256,7 +287,7 @@ type MemberPage struct { // NextPageURL is invoked when a paginated collection of members has reached // the end of a page and the pager seeks to traverse over a new one. In order // to do this, it needs to construct the next page's URL. -func (r MemberPage) NextPageURL() (string, error) { +func (r MemberPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"members_links"` } diff --git a/openstack/loadbalancer/v2/pools/testing/fixtures_test.go b/openstack/loadbalancer/v2/pools/testing/fixtures_test.go index 47d7c373f8..a2073952bb 100644 --- a/openstack/loadbalancer/v2/pools/testing/fixtures_test.go +++ b/openstack/loadbalancer/v2/pools/testing/fixtures_test.go @@ -133,8 +133,8 @@ var ( ) // HandlePoolListSuccessfully sets up the test server to respond to a pool List request. -func HandlePoolListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/pools", func(w http.ResponseWriter, r *http.Request) { +func HandlePoolListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/pools", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -145,9 +145,9 @@ func HandlePoolListSuccessfully(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, PoolsListBody) + fmt.Fprint(w, PoolsListBody) case "45e08a3e-a78f-4b40-a229-1e7e23eee1ab": - fmt.Fprintf(w, `{ "pools": [] }`) + fmt.Fprint(w, `{ "pools": [] }`) default: t.Fatalf("/v2.0/lbaas/pools invoked with unexpected marker=[%s]", marker) } @@ -156,8 +156,8 @@ func HandlePoolListSuccessfully(t *testing.T) { // HandlePoolCreationSuccessfully sets up the test server to respond to a pool creation request // with a given response. -func HandlePoolCreationSuccessfully(t *testing.T, response string) { - th.Mux.HandleFunc("/v2.0/lbaas/pools", func(w http.ResponseWriter, r *http.Request) { +func HandlePoolCreationSuccessfully(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/pools", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -172,24 +172,24 @@ func HandlePoolCreationSuccessfully(t *testing.T, response string) { w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) } // HandlePoolGetSuccessfully sets up the test server to respond to a pool Get request. -func HandlePoolGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/pools/c3741b06-df4d-4715-b142-276b6bce75ab", func(w http.ResponseWriter, r *http.Request) { +func HandlePoolGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/pools/c3741b06-df4d-4715-b142-276b6bce75ab", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SinglePoolBody) + fmt.Fprint(w, SinglePoolBody) }) } // HandlePoolDeletionSuccessfully sets up the test server to respond to a pool deletion request. -func HandlePoolDeletionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/pools/c3741b06-df4d-4715-b142-276b6bce75ab", func(w http.ResponseWriter, r *http.Request) { +func HandlePoolDeletionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/pools/c3741b06-df4d-4715-b142-276b6bce75ab", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -198,8 +198,8 @@ func HandlePoolDeletionSuccessfully(t *testing.T) { } // HandlePoolUpdateSuccessfully sets up the test server to respond to a pool Update request. -func HandlePoolUpdateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/pools/c3741b06-df4d-4715-b142-276b6bce75ab", func(w http.ResponseWriter, r *http.Request) { +func HandlePoolUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/pools/c3741b06-df4d-4715-b142-276b6bce75ab", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") @@ -211,7 +211,7 @@ func HandlePoolUpdateSuccessfully(t *testing.T) { } }`) - fmt.Fprintf(w, PostUpdatePoolBody) + fmt.Fprint(w, PostUpdatePoolBody) }) } @@ -330,8 +330,8 @@ var ( ) // HandleMemberListSuccessfully sets up the test server to respond to a member List request. -func HandleMemberListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members", func(w http.ResponseWriter, r *http.Request) { +func HandleMemberListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -342,9 +342,9 @@ func HandleMemberListSuccessfully(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, MembersListBody) + fmt.Fprint(w, MembersListBody) case "45e08a3e-a78f-4b40-a229-1e7e23eee1ab": - fmt.Fprintf(w, `{ "members": [] }`) + fmt.Fprint(w, `{ "members": [] }`) default: t.Fatalf("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members invoked with unexpected marker=[%s]", marker) } @@ -353,8 +353,8 @@ func HandleMemberListSuccessfully(t *testing.T) { // HandleMemberCreationSuccessfully sets up the test server to respond to a member creation request // with a given response. -func HandleMemberCreationSuccessfully(t *testing.T, response string) { - th.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members", func(w http.ResponseWriter, r *http.Request) { +func HandleMemberCreationSuccessfully(t *testing.T, fakeServer th.FakeServer, response string) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, `{ @@ -370,24 +370,24 @@ func HandleMemberCreationSuccessfully(t *testing.T, response string) { w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) } // HandleMemberGetSuccessfully sets up the test server to respond to a member Get request. -func HandleMemberGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members/2a280670-c202-4b0b-a562-34077415aabf", func(w http.ResponseWriter, r *http.Request) { +func HandleMemberGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members/2a280670-c202-4b0b-a562-34077415aabf", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleMemberBody) + fmt.Fprint(w, SingleMemberBody) }) } // HandleMemberDeletionSuccessfully sets up the test server to respond to a member deletion request. -func HandleMemberDeletionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members/2a280670-c202-4b0b-a562-34077415aabf", func(w http.ResponseWriter, r *http.Request) { +func HandleMemberDeletionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members/2a280670-c202-4b0b-a562-34077415aabf", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -396,8 +396,8 @@ func HandleMemberDeletionSuccessfully(t *testing.T) { } // HandleMemberUpdateSuccessfully sets up the test server to respond to a member Update request. -func HandleMemberUpdateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members/2a280670-c202-4b0b-a562-34077415aabf", func(w http.ResponseWriter, r *http.Request) { +func HandleMemberUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members/2a280670-c202-4b0b-a562-34077415aabf", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") @@ -409,13 +409,13 @@ func HandleMemberUpdateSuccessfully(t *testing.T) { } }`) - fmt.Fprintf(w, PostUpdateMemberBody) + fmt.Fprint(w, PostUpdateMemberBody) }) } // HandleMembersUpdateSuccessfully sets up the test server to respond to a batch member Update request. -func HandleMembersUpdateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members", func(w http.ResponseWriter, r *http.Request) { +func HandleMembersUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") @@ -444,8 +444,8 @@ func HandleMembersUpdateSuccessfully(t *testing.T) { } // HandleEmptyMembersUpdateSuccessfully sets up the test server to respond to an empty batch member Update request. -func HandleEmptyMembersUpdateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members", func(w http.ResponseWriter, r *http.Request) { +func HandleEmptyMembersUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/pools/332abe93-f488-41ba-870b-2ac66be7f853/members", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") diff --git a/openstack/loadbalancer/v2/pools/testing/requests_test.go b/openstack/loadbalancer/v2/pools/testing/requests_test.go index 6e3126329a..17ae976617 100644 --- a/openstack/loadbalancer/v2/pools/testing/requests_test.go +++ b/openstack/loadbalancer/v2/pools/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestListPools(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandlePoolListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePoolListSuccessfully(t, fakeServer) pages := 0 - err := pools.List(fake.ServiceClient(), pools.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := pools.List(fake.ServiceClient(fakeServer), pools.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := pools.ExtractPools(page) @@ -41,11 +41,11 @@ func TestListPools(t *testing.T) { } func TestListAllPools(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandlePoolListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePoolListSuccessfully(t, fakeServer) - allPages, err := pools.List(fake.ServiceClient(), pools.ListOpts{}).AllPages(context.TODO()) + allPages, err := pools.List(fake.ServiceClient(fakeServer), pools.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := pools.ExtractPools(allPages) th.AssertNoErr(t, err) @@ -54,11 +54,11 @@ func TestListAllPools(t *testing.T) { } func TestCreatePool(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandlePoolCreationSuccessfully(t, SinglePoolBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePoolCreationSuccessfully(t, fakeServer, SinglePoolBody) - actual, err := pools.Create(context.TODO(), fake.ServiceClient(), pools.CreateOpts{ + actual, err := pools.Create(context.TODO(), fake.ServiceClient(fakeServer), pools.CreateOpts{ LBMethod: pools.LBMethodRoundRobin, Protocol: "HTTP", Name: "Example pool", @@ -71,11 +71,11 @@ func TestCreatePool(t *testing.T) { } func TestGetPool(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandlePoolGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePoolGetSuccessfully(t, fakeServer) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) actual, err := pools.Get(context.TODO(), client, "c3741b06-df4d-4715-b142-276b6bce75ab").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) @@ -85,20 +85,20 @@ func TestGetPool(t *testing.T) { } func TestDeletePool(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandlePoolDeletionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePoolDeletionSuccessfully(t, fakeServer) - res := pools.Delete(context.TODO(), fake.ServiceClient(), "c3741b06-df4d-4715-b142-276b6bce75ab") + res := pools.Delete(context.TODO(), fake.ServiceClient(fakeServer), "c3741b06-df4d-4715-b142-276b6bce75ab") th.AssertNoErr(t, res.Err) } func TestUpdatePool(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandlePoolUpdateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePoolUpdateSuccessfully(t, fakeServer) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) name := "NewPoolName" actual, err := pools.Update(context.TODO(), client, "c3741b06-df4d-4715-b142-276b6bce75ab", pools.UpdateOpts{ Name: &name, @@ -112,11 +112,14 @@ func TestUpdatePool(t *testing.T) { } func TestRequiredPoolCreateOpts(t *testing.T) { - res := pools.Create(context.TODO(), fake.ServiceClient(), pools.CreateOpts{}) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + res := pools.Create(context.TODO(), fake.ServiceClient(fakeServer), pools.CreateOpts{}) if res.Err == nil { t.Fatalf("Expected error, got none") } - res = pools.Create(context.TODO(), fake.ServiceClient(), pools.CreateOpts{ + res = pools.Create(context.TODO(), fake.ServiceClient(fakeServer), pools.CreateOpts{ LBMethod: pools.LBMethod("invalid"), Protocol: pools.ProtocolHTTPS, LoadbalancerID: "69055154-f603-4a28-8951-7cc2d9e54a9a", @@ -125,7 +128,7 @@ func TestRequiredPoolCreateOpts(t *testing.T) { t.Fatalf("Expected error, but got none") } - res = pools.Create(context.TODO(), fake.ServiceClient(), pools.CreateOpts{ + res = pools.Create(context.TODO(), fake.ServiceClient(fakeServer), pools.CreateOpts{ LBMethod: pools.LBMethodRoundRobin, Protocol: pools.Protocol("invalid"), LoadbalancerID: "69055154-f603-4a28-8951-7cc2d9e54a9a", @@ -134,7 +137,7 @@ func TestRequiredPoolCreateOpts(t *testing.T) { t.Fatalf("Expected error, but got none") } - res = pools.Create(context.TODO(), fake.ServiceClient(), pools.CreateOpts{ + res = pools.Create(context.TODO(), fake.ServiceClient(fakeServer), pools.CreateOpts{ LBMethod: pools.LBMethodRoundRobin, Protocol: pools.ProtocolHTTPS, }) @@ -144,12 +147,12 @@ func TestRequiredPoolCreateOpts(t *testing.T) { } func TestListMembers(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleMemberListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleMemberListSuccessfully(t, fakeServer) pages := 0 - err := pools.ListMembers(fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", pools.ListMembersOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := pools.ListMembers(fake.ServiceClient(fakeServer), "332abe93-f488-41ba-870b-2ac66be7f853", pools.ListMembersOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := pools.ExtractMembers(page) @@ -174,11 +177,11 @@ func TestListMembers(t *testing.T) { } func TestListAllMembers(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleMemberListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleMemberListSuccessfully(t, fakeServer) - allPages, err := pools.ListMembers(fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", pools.ListMembersOpts{}).AllPages(context.TODO()) + allPages, err := pools.ListMembers(fake.ServiceClient(fakeServer), "332abe93-f488-41ba-870b-2ac66be7f853", pools.ListMembersOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := pools.ExtractMembers(allPages) th.AssertNoErr(t, err) @@ -187,12 +190,12 @@ func TestListAllMembers(t *testing.T) { } func TestCreateMember(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleMemberCreationSuccessfully(t, SingleMemberBody) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleMemberCreationSuccessfully(t, fakeServer, SingleMemberBody) weight := 10 - actual, err := pools.CreateMember(context.TODO(), fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", pools.CreateMemberOpts{ + actual, err := pools.CreateMember(context.TODO(), fake.ServiceClient(fakeServer), "332abe93-f488-41ba-870b-2ac66be7f853", pools.CreateMemberOpts{ Name: "db", SubnetID: "1981f108-3c48-48d2-b908-30f7d28532c9", ProjectID: "2ffc6e22aae24e4795f87155d24c896f", @@ -206,30 +209,33 @@ func TestCreateMember(t *testing.T) { } func TestRequiredMemberCreateOpts(t *testing.T) { - res := pools.CreateMember(context.TODO(), fake.ServiceClient(), "", pools.CreateMemberOpts{}) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + res := pools.CreateMember(context.TODO(), fake.ServiceClient(fakeServer), "", pools.CreateMemberOpts{}) if res.Err == nil { t.Fatalf("Expected error, got none") } - res = pools.CreateMember(context.TODO(), fake.ServiceClient(), "", pools.CreateMemberOpts{Address: "1.2.3.4", ProtocolPort: 80}) + res = pools.CreateMember(context.TODO(), fake.ServiceClient(fakeServer), "", pools.CreateMemberOpts{Address: "1.2.3.4", ProtocolPort: 80}) if res.Err == nil { t.Fatalf("Expected error, but got none") } - res = pools.CreateMember(context.TODO(), fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", pools.CreateMemberOpts{ProtocolPort: 80}) + res = pools.CreateMember(context.TODO(), fake.ServiceClient(fakeServer), "332abe93-f488-41ba-870b-2ac66be7f853", pools.CreateMemberOpts{ProtocolPort: 80}) if res.Err == nil { t.Fatalf("Expected error, but got none") } - res = pools.CreateMember(context.TODO(), fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", pools.CreateMemberOpts{Address: "1.2.3.4"}) + res = pools.CreateMember(context.TODO(), fake.ServiceClient(fakeServer), "332abe93-f488-41ba-870b-2ac66be7f853", pools.CreateMemberOpts{Address: "1.2.3.4"}) if res.Err == nil { t.Fatalf("Expected error, but got none") } } func TestGetMember(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleMemberGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleMemberGetSuccessfully(t, fakeServer) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) actual, err := pools.GetMember(context.TODO(), client, "332abe93-f488-41ba-870b-2ac66be7f853", "2a280670-c202-4b0b-a562-34077415aabf").Extract() if err != nil { t.Fatalf("Unexpected Get error: %v", err) @@ -239,21 +245,21 @@ func TestGetMember(t *testing.T) { } func TestDeleteMember(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleMemberDeletionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleMemberDeletionSuccessfully(t, fakeServer) - res := pools.DeleteMember(context.TODO(), fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", "2a280670-c202-4b0b-a562-34077415aabf") + res := pools.DeleteMember(context.TODO(), fake.ServiceClient(fakeServer), "332abe93-f488-41ba-870b-2ac66be7f853", "2a280670-c202-4b0b-a562-34077415aabf") th.AssertNoErr(t, res.Err) } func TestUpdateMember(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleMemberUpdateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleMemberUpdateSuccessfully(t, fakeServer) weight := 4 - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) name := "newMemberName" actual, err := pools.UpdateMember(context.TODO(), client, "332abe93-f488-41ba-870b-2ac66be7f853", "2a280670-c202-4b0b-a562-34077415aabf", pools.UpdateMemberOpts{ Name: &name, @@ -267,9 +273,9 @@ func TestUpdateMember(t *testing.T) { } func TestBatchUpdateMembers(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleMembersUpdateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleMembersUpdateSuccessfully(t, fakeServer) name_1 := "web-server-1" weight_1 := 20 @@ -293,25 +299,25 @@ func TestBatchUpdateMembers(t *testing.T) { } members := []pools.BatchUpdateMemberOpts{member1, member2} - res := pools.BatchUpdateMembers(context.TODO(), fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", members) + res := pools.BatchUpdateMembers(context.TODO(), fake.ServiceClient(fakeServer), "332abe93-f488-41ba-870b-2ac66be7f853", members) th.AssertNoErr(t, res.Err) } func TestEmptyBatchUpdateMembers(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleEmptyMembersUpdateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleEmptyMembersUpdateSuccessfully(t, fakeServer) - res := pools.BatchUpdateMembers(context.TODO(), fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", []pools.BatchUpdateMemberOpts{}) + res := pools.BatchUpdateMembers(context.TODO(), fake.ServiceClient(fakeServer), "332abe93-f488-41ba-870b-2ac66be7f853", []pools.BatchUpdateMemberOpts{}) th.AssertNoErr(t, res.Err) } func TestRequiredBatchUpdateMemberOpts(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() name := "web-server-1" - res := pools.BatchUpdateMembers(context.TODO(), fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", []pools.BatchUpdateMemberOpts{ + res := pools.BatchUpdateMembers(context.TODO(), fake.ServiceClient(fakeServer), "332abe93-f488-41ba-870b-2ac66be7f853", []pools.BatchUpdateMemberOpts{ { Name: &name, }, @@ -320,7 +326,7 @@ func TestRequiredBatchUpdateMemberOpts(t *testing.T) { t.Fatalf("Expected error, but got none") } - res = pools.BatchUpdateMembers(context.TODO(), fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", []pools.BatchUpdateMemberOpts{ + res = pools.BatchUpdateMembers(context.TODO(), fake.ServiceClient(fakeServer), "332abe93-f488-41ba-870b-2ac66be7f853", []pools.BatchUpdateMemberOpts{ { Address: "192.0.2.17", Name: &name, @@ -330,7 +336,7 @@ func TestRequiredBatchUpdateMemberOpts(t *testing.T) { t.Fatalf("Expected error, but got none") } - res = pools.BatchUpdateMembers(context.TODO(), fake.ServiceClient(), "332abe93-f488-41ba-870b-2ac66be7f853", []pools.BatchUpdateMemberOpts{ + res = pools.BatchUpdateMembers(context.TODO(), fake.ServiceClient(fakeServer), "332abe93-f488-41ba-870b-2ac66be7f853", []pools.BatchUpdateMemberOpts{ { ProtocolPort: 80, Name: &name, diff --git a/openstack/loadbalancer/v2/providers/results.go b/openstack/loadbalancer/v2/providers/results.go index ec374faee6..49423f04d3 100644 --- a/openstack/loadbalancer/v2/providers/results.go +++ b/openstack/loadbalancer/v2/providers/results.go @@ -23,7 +23,7 @@ type ProviderPage struct { // NextPageURL is invoked when a paginated collection of providers has // reached the end of a page and the pager seeks to traverse over a new one. // In order to do this, it needs to construct the next page's URL. -func (r ProviderPage) NextPageURL() (string, error) { +func (r ProviderPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"providers_links"` } diff --git a/openstack/loadbalancer/v2/providers/testing/fixtures_test.go b/openstack/loadbalancer/v2/providers/testing/fixtures_test.go index 64db9a39bf..78c9f7a101 100644 --- a/openstack/loadbalancer/v2/providers/testing/fixtures_test.go +++ b/openstack/loadbalancer/v2/providers/testing/fixtures_test.go @@ -38,8 +38,8 @@ var ( ) // HandleProviderListSuccessfully sets up the test server to respond to a provider List request. -func HandleProviderListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/lbaas/providers", func(w http.ResponseWriter, r *http.Request) { +func HandleProviderListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/lbaas/providers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -50,7 +50,7 @@ func HandleProviderListSuccessfully(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, ProvidersListBody) + fmt.Fprint(w, ProvidersListBody) default: t.Fatalf("/v2.0/lbaas/providers invoked with unexpected marker=[%s]", marker) } diff --git a/openstack/loadbalancer/v2/providers/testing/requests_test.go b/openstack/loadbalancer/v2/providers/testing/requests_test.go index c5a5278aae..6046cde034 100644 --- a/openstack/loadbalancer/v2/providers/testing/requests_test.go +++ b/openstack/loadbalancer/v2/providers/testing/requests_test.go @@ -11,12 +11,12 @@ import ( ) func TestListProviders(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleProviderListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleProviderListSuccessfully(t, fakeServer) pages := 0 - err := providers.List(fake.ServiceClient(), providers.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := providers.List(fake.ServiceClient(fakeServer), providers.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := providers.ExtractProviders(page) @@ -41,11 +41,11 @@ func TestListProviders(t *testing.T) { } func TestListAllProviders(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleProviderListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleProviderListSuccessfully(t, fakeServer) - allPages, err := providers.List(fake.ServiceClient(), providers.ListOpts{}).AllPages(context.TODO()) + allPages, err := providers.List(fake.ServiceClient(fakeServer), providers.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := providers.ExtractProviders(allPages) th.AssertNoErr(t, err) diff --git a/openstack/loadbalancer/v2/quotas/doc.go b/openstack/loadbalancer/v2/quotas/doc.go index c2c63512c3..77c5600226 100644 --- a/openstack/loadbalancer/v2/quotas/doc.go +++ b/openstack/loadbalancer/v2/quotas/doc.go @@ -30,5 +30,15 @@ Example to Update project quotas } fmt.Printf("quotas: %#v\n", quotasInfo) + +Example to Delete project quotas + + projectID = "23d5d3f79dfa4f73b72b8b0b0063ec55" + err := quotas.Delete(context.TODO(), networkClient, projectID).ExtractErr() + if err != nil { + log.Fatal(err) + } + + fmt.Printf("Deleted quotas for project: %s\n", projectID) */ package quotas diff --git a/openstack/loadbalancer/v2/quotas/requests.go b/openstack/loadbalancer/v2/quotas/requests.go index 8b7771eea8..f07f9392e9 100644 --- a/openstack/loadbalancer/v2/quotas/requests.go +++ b/openstack/loadbalancer/v2/quotas/requests.go @@ -63,3 +63,9 @@ func Update(ctx context.Context, c *gophercloud.ServiceClient, projectID string, _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } + +func Delete(ctx context.Context, c *gophercloud.ServiceClient, projectID string) (r DeleteResult) { + resp, err := c.Delete(ctx, deleteURL(c, projectID), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/loadbalancer/v2/quotas/results.go b/openstack/loadbalancer/v2/quotas/results.go index e1ef385982..5487d7a84e 100644 --- a/openstack/loadbalancer/v2/quotas/results.go +++ b/openstack/loadbalancer/v2/quotas/results.go @@ -96,3 +96,9 @@ func (r *Quota) UnmarshalJSON(b []byte) error { return nil } + +// DeleteResult is the response from a Delete operation. Call its ExtractErr +// method to determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/openstack/loadbalancer/v2/quotas/testing/fixtures_test.go b/openstack/loadbalancer/v2/quotas/testing/fixtures_test.go index e5185b2012..e4609aa868 100644 --- a/openstack/loadbalancer/v2/quotas/testing/fixtures_test.go +++ b/openstack/loadbalancer/v2/quotas/testing/fixtures_test.go @@ -1,6 +1,13 @@ package testing -import "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/quotas" +import ( + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/quotas" + fake "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/common" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) const GetResponseRaw_1 = ` { @@ -77,3 +84,12 @@ var UpdateResponse = quotas.Quota{ L7Policy: 50, L7Rule: 100, } + +func MockDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/quotas/0a73845280574ad389c292f6a74afa76", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} diff --git a/openstack/loadbalancer/v2/quotas/testing/requests_test.go b/openstack/loadbalancer/v2/quotas/testing/requests_test.go index 64c819aecf..ab3ba6a177 100644 --- a/openstack/loadbalancer/v2/quotas/testing/requests_test.go +++ b/openstack/loadbalancer/v2/quotas/testing/requests_test.go @@ -13,58 +13,58 @@ import ( ) func TestGet_1(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/quotas/0a73845280574ad389c292f6a74afa76", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/quotas/0a73845280574ad389c292f6a74afa76", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponseRaw_1) + fmt.Fprint(w, GetResponseRaw_1) }) - q, err := quotas.Get(context.TODO(), fake.ServiceClient(), "0a73845280574ad389c292f6a74afa76").Extract() + q, err := quotas.Get(context.TODO(), fake.ServiceClient(fakeServer), "0a73845280574ad389c292f6a74afa76").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, q, &GetResponse) } func TestGet_2(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/quotas/0a73845280574ad389c292f6a74afa76", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/quotas/0a73845280574ad389c292f6a74afa76", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponseRaw_2) + fmt.Fprint(w, GetResponseRaw_2) }) - q, err := quotas.Get(context.TODO(), fake.ServiceClient(), "0a73845280574ad389c292f6a74afa76").Extract() + q, err := quotas.Get(context.TODO(), fake.ServiceClient(fakeServer), "0a73845280574ad389c292f6a74afa76").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, q, &GetResponse) } func TestUpdate_1(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/quotas/0a73845280574ad389c292f6a74afa76", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/quotas/0a73845280574ad389c292f6a74afa76", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, UpdateRequestResponseRaw_1) + fmt.Fprint(w, UpdateRequestResponseRaw_1) }) - q, err := quotas.Update(context.TODO(), fake.ServiceClient(), "0a73845280574ad389c292f6a74afa76", quotas.UpdateOpts{ + q, err := quotas.Update(context.TODO(), fake.ServiceClient(fakeServer), "0a73845280574ad389c292f6a74afa76", quotas.UpdateOpts{ Loadbalancer: gophercloud.IntToPointer(20), Listener: gophercloud.IntToPointer(40), Member: gophercloud.IntToPointer(200), @@ -79,20 +79,20 @@ func TestUpdate_1(t *testing.T) { } func TestUpdate_2(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/quotas/0a73845280574ad389c292f6a74afa76", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/quotas/0a73845280574ad389c292f6a74afa76", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, UpdateRequestResponseRaw_2) + fmt.Fprint(w, UpdateRequestResponseRaw_2) }) - q, err := quotas.Update(context.TODO(), fake.ServiceClient(), "0a73845280574ad389c292f6a74afa76", quotas.UpdateOpts{ + q, err := quotas.Update(context.TODO(), fake.ServiceClient(fakeServer), "0a73845280574ad389c292f6a74afa76", quotas.UpdateOpts{ Loadbalancer: gophercloud.IntToPointer(20), Listener: gophercloud.IntToPointer(40), Member: gophercloud.IntToPointer(200), @@ -105,3 +105,13 @@ func TestUpdate_2(t *testing.T) { th.AssertNoErr(t, err) th.AssertDeepEquals(t, q, &UpdateResponse) } + +func TestDelete(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + MockDeleteResponse(t, fakeServer) + + res := quotas.Delete(context.TODO(), fake.ServiceClient(fakeServer), "0a73845280574ad389c292f6a74afa76") + th.AssertNoErr(t, res.Err) +} diff --git a/openstack/loadbalancer/v2/quotas/urls.go b/openstack/loadbalancer/v2/quotas/urls.go index 0365d9c8ad..545e832307 100644 --- a/openstack/loadbalancer/v2/quotas/urls.go +++ b/openstack/loadbalancer/v2/quotas/urls.go @@ -15,3 +15,7 @@ func getURL(c *gophercloud.ServiceClient, projectID string) string { func updateURL(c *gophercloud.ServiceClient, projectID string) string { return resourceURL(c, projectID) } + +func deleteURL(c *gophercloud.ServiceClient, projectID string) string { + return getURL(c, projectID) +} diff --git a/openstack/loadbalancer/v2/testhelper/client.go b/openstack/loadbalancer/v2/testhelper/client.go index f32313a105..581c9803aa 100644 --- a/openstack/loadbalancer/v2/testhelper/client.go +++ b/openstack/loadbalancer/v2/testhelper/client.go @@ -2,13 +2,14 @@ package common import ( "github.com/gophercloud/gophercloud/v2" + th "github.com/gophercloud/gophercloud/v2/testhelper" "github.com/gophercloud/gophercloud/v2/testhelper/client" ) const TokenID = client.TokenID -func ServiceClient() *gophercloud.ServiceClient { - sc := client.ServiceClient() +func ServiceClient(fakeServer th.FakeServer) *gophercloud.ServiceClient { + sc := client.ServiceClient(fakeServer) sc.ResourceBase = sc.Endpoint + "v2.0/" return sc } diff --git a/openstack/messaging/v2/claims/testing/fixtures_test.go b/openstack/messaging/v2/claims/testing/fixtures_test.go index f713687892..b8087d663c 100644 --- a/openstack/messaging/v2/claims/testing/fixtures_test.go +++ b/openstack/messaging/v2/claims/testing/fixtures_test.go @@ -7,7 +7,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/messaging/v2/claims" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) // QueueName is the name of the queue @@ -84,25 +84,25 @@ var FirstClaim = claims.Claim{ } // HandleCreateSuccessfully configures the test server to respond to a Create request. -func HandleCreateSuccessfully(t *testing.T) { - th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/claims", QueueName), +func HandleCreateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/claims", QueueName), func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateClaimRequest) w.WriteHeader(http.StatusCreated) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, CreateClaimResponse) + fmt.Fprint(w, CreateClaimResponse) }) } // HandleCreateNoContent configures the test server to respond to a Create request with no content. -func HandleCreateNoContent(t *testing.T) { - th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/claims", QueueName), +func HandleCreateNoContent(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/claims", QueueName), func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateClaimRequest) w.WriteHeader(http.StatusNoContent) @@ -110,23 +110,23 @@ func HandleCreateNoContent(t *testing.T) { } // HandleGetSuccessfully configures the test server to respond to a Get request. -func HandleGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/claims/%s", QueueName, ClaimID), +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/claims/%s", QueueName, ClaimID), func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetClaimResponse) + fmt.Fprint(w, GetClaimResponse) }) } // HandleUpdateSuccessfully configures the test server to respond to a Update request. -func HandleUpdateSuccessfully(t *testing.T) { - th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/claims/%s", QueueName, ClaimID), +func HandleUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/claims/%s", QueueName, ClaimID), func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, UpdateClaimRequest) w.WriteHeader(http.StatusNoContent) @@ -134,11 +134,11 @@ func HandleUpdateSuccessfully(t *testing.T) { } // HandleDeleteSuccessfully configures the test server to respond to an Delete request. -func HandleDeleteSuccessfully(t *testing.T) { - th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/claims/%s", QueueName, ClaimID), +func HandleDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/claims/%s", QueueName, ClaimID), func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) diff --git a/openstack/messaging/v2/claims/testing/requests_test.go b/openstack/messaging/v2/claims/testing/requests_test.go index 8040796601..2a5bebd487 100644 --- a/openstack/messaging/v2/claims/testing/requests_test.go +++ b/openstack/messaging/v2/claims/testing/requests_test.go @@ -6,13 +6,13 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/messaging/v2/claims" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateSuccessfully(t, fakeServer) createOpts := claims.CreateOpts{ TTL: 3600, @@ -20,15 +20,15 @@ func TestCreate(t *testing.T) { Limit: 10, } - actual, err := claims.Create(context.TODO(), fake.ServiceClient(), QueueName, createOpts).Extract() + actual, err := claims.Create(context.TODO(), client.ServiceClient(fakeServer), QueueName, createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, CreatedClaim, actual) } func TestCreateNoContent(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateNoContent(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateNoContent(t, fakeServer) createOpts := claims.CreateOpts{ TTL: 3600, @@ -36,41 +36,41 @@ func TestCreateNoContent(t *testing.T) { Limit: 10, } - actual, err := claims.Create(context.TODO(), fake.ServiceClient(), QueueName, createOpts).Extract() + actual, err := claims.Create(context.TODO(), client.ServiceClient(fakeServer), QueueName, createOpts).Extract() th.AssertNoErr(t, err) var expected []claims.Messages th.CheckDeepEquals(t, expected, actual) } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSuccessfully(t, fakeServer) - actual, err := claims.Get(context.TODO(), fake.ServiceClient(), QueueName, ClaimID).Extract() + actual, err := claims.Get(context.TODO(), client.ServiceClient(fakeServer), QueueName, ClaimID).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &FirstClaim, actual) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateSuccessfully(t, fakeServer) updateOpts := claims.UpdateOpts{ Grace: 1600, TTL: 1200, } - err := claims.Update(context.TODO(), fake.ServiceClient(), QueueName, ClaimID, updateOpts).ExtractErr() + err := claims.Update(context.TODO(), client.ServiceClient(fakeServer), QueueName, ClaimID, updateOpts).ExtractErr() th.AssertNoErr(t, err) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteSuccessfully(t, fakeServer) - err := claims.Delete(context.TODO(), fake.ServiceClient(), QueueName, ClaimID).ExtractErr() + err := claims.Delete(context.TODO(), client.ServiceClient(fakeServer), QueueName, ClaimID).ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/messaging/v2/messages/results.go b/openstack/messaging/v2/messages/results.go index a0b76a7373..c700485e21 100644 --- a/openstack/messaging/v2/messages/results.go +++ b/openstack/messaging/v2/messages/results.go @@ -114,7 +114,7 @@ func (r MessagePage) IsEmpty() (bool, error) { // NextPageURL uses the response's embedded link reference to navigate to the // next page of results. -func (r MessagePage) NextPageURL() (string, error) { +func (r MessagePage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"links"` } @@ -127,5 +127,5 @@ func (r MessagePage) NextPageURL() (string, error) { if err != nil { return "", err } - return nextPageURL(r.URL.String(), next) + return nextPageURL(endpointURL, next) } diff --git a/openstack/messaging/v2/messages/testing/fixtures_test.go b/openstack/messaging/v2/messages/testing/fixtures_test.go index a5a82db253..b7f6d194a5 100644 --- a/openstack/messaging/v2/messages/testing/fixtures_test.go +++ b/openstack/messaging/v2/messages/testing/fixtures_test.go @@ -7,7 +7,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/messaging/v2/messages" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) // QueueName is the name of the queue @@ -220,32 +220,32 @@ var ExpectedPopMessage = []messages.PopMessage{{ }} // HandleCreateSuccessfully configures the test server to respond to a Create request. -func HandleCreateSuccessfully(t *testing.T) { - th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/messages", QueueName), +func HandleCreateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/messages", QueueName), func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateMessageRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateMessageResponse) + fmt.Fprint(w, CreateMessageResponse) }) } // HandleListSuccessfully configures the test server to respond to a List request. -func HandleListSuccessfully(t *testing.T) { - th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/messages", QueueName), +func HandleListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/messages", QueueName), func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") next := r.RequestURI switch next { case fmt.Sprintf("/v2/queues/%s/messages?limit=1", QueueName): - fmt.Fprintf(w, ListMessagesResponse1) + fmt.Fprint(w, ListMessagesResponse1) case fmt.Sprintf("/v2/queues/%s/messages?marker=1", QueueName): fmt.Fprint(w, ListMessagesResponse2) case fmt.Sprintf("/v2/queues/%s/messages?marker=2", QueueName): @@ -255,35 +255,35 @@ func HandleListSuccessfully(t *testing.T) { } // HandleGetMessagesSuccessfully configures the test server to respond to a GetMessages request. -func HandleGetMessagesSuccessfully(t *testing.T) { - th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/messages", QueueName), +func HandleGetMessagesSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/messages", QueueName), func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetMessagesResponse) + fmt.Fprint(w, GetMessagesResponse) }) } // HandleGetSuccessfully configures the test server to respond to a Get request. -func HandleGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/messages/%s", QueueName, MessageID), +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/messages/%s", QueueName, MessageID), func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetMessageResponse) + fmt.Fprint(w, GetMessageResponse) }) } // HandleDeleteMessagesSuccessfully configures the test server to respond to a Delete request. -func HandleDeleteMessagesSuccessfully(t *testing.T) { - th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/messages", QueueName), +func HandleDeleteMessagesSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/messages", QueueName), func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusNoContent) @@ -291,24 +291,24 @@ func HandleDeleteMessagesSuccessfully(t *testing.T) { } // HandlePopSuccessfully configures the test server to respond to a Pop request. -func HandlePopSuccessfully(t *testing.T) { - th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/messages", QueueName), +func HandlePopSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/messages", QueueName), func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, PopMessageResponse) + fmt.Fprint(w, PopMessageResponse) }) } // HandleGetSuccessfully configures the test server to respond to a Get request. -func HandleDeleteSuccessfully(t *testing.T) { - th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/messages/%s", QueueName, MessageID), +func HandleDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/messages/%s", QueueName, MessageID), func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusNoContent) diff --git a/openstack/messaging/v2/messages/testing/requests_test.go b/openstack/messaging/v2/messages/testing/requests_test.go index e2ee71be83..cd09f1b486 100644 --- a/openstack/messaging/v2/messages/testing/requests_test.go +++ b/openstack/messaging/v2/messages/testing/requests_test.go @@ -7,20 +7,20 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/messaging/v2/messages" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer) listOpts := messages.ListOpts{ Limit: 1, } count := 0 - err := messages.List(fake.ServiceClient(), QueueName, listOpts).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := messages.List(client.ServiceClient(fakeServer), QueueName, listOpts).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { actual, err := messages.ExtractMessages(page) th.AssertNoErr(t, err) @@ -35,9 +35,9 @@ func TestList(t *testing.T) { } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateSuccessfully(t, fakeServer) createOpts := messages.BatchCreateOpts{ messages.CreateOpts{ @@ -57,71 +57,71 @@ func TestCreate(t *testing.T) { }, } - actual, err := messages.Create(context.TODO(), fake.ServiceClient(), QueueName, createOpts).Extract() + actual, err := messages.Create(context.TODO(), client.ServiceClient(fakeServer), QueueName, createOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, ExpectedResources, actual) } func TestGetMessages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetMessagesSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetMessagesSuccessfully(t, fakeServer) getMessagesOpts := messages.GetMessagesOpts{ IDs: []string{"9988776655"}, } - actual, err := messages.GetMessages(context.TODO(), fake.ServiceClient(), QueueName, getMessagesOpts).Extract() + actual, err := messages.GetMessages(context.TODO(), client.ServiceClient(fakeServer), QueueName, getMessagesOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, ExpectedMessagesSet, actual) } func TestGetMessage(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSuccessfully(t, fakeServer) - actual, err := messages.Get(context.TODO(), fake.ServiceClient(), QueueName, MessageID).Extract() + actual, err := messages.Get(context.TODO(), client.ServiceClient(fakeServer), QueueName, MessageID).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, FirstMessage, actual) } func TestDeleteMessages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteMessagesSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteMessagesSuccessfully(t, fakeServer) deleteMessagesOpts := messages.DeleteMessagesOpts{ IDs: []string{"9988776655"}, } - err := messages.DeleteMessages(context.TODO(), fake.ServiceClient(), QueueName, deleteMessagesOpts).ExtractErr() + err := messages.DeleteMessages(context.TODO(), client.ServiceClient(fakeServer), QueueName, deleteMessagesOpts).ExtractErr() th.AssertNoErr(t, err) } func TestPopMessages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandlePopSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePopSuccessfully(t, fakeServer) popMessagesOpts := messages.PopMessagesOpts{ Pop: 1, } - actual, err := messages.PopMessages(context.TODO(), fake.ServiceClient(), QueueName, popMessagesOpts).Extract() + actual, err := messages.PopMessages(context.TODO(), client.ServiceClient(fakeServer), QueueName, popMessagesOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, ExpectedPopMessage, actual) } func TestDeleteMessage(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteSuccessfully(t, fakeServer) deleteOpts := messages.DeleteOpts{ ClaimID: "12345", } - err := messages.Delete(context.TODO(), fake.ServiceClient(), QueueName, MessageID, deleteOpts).ExtractErr() + err := messages.Delete(context.TODO(), client.ServiceClient(fakeServer), QueueName, MessageID, deleteOpts).ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/messaging/v2/messages/urls.go b/openstack/messaging/v2/messages/urls.go index b5ba4235b2..642efab905 100644 --- a/openstack/messaging/v2/messages/urls.go +++ b/openstack/messaging/v2/messages/urls.go @@ -35,9 +35,9 @@ func messageURL(client *gophercloud.ServiceClient, queueName string, messageID s return client.ServiceURL(apiVersion, apiName, queueName, "messages", messageID) } -// Builds next page full url based on current url. -func nextPageURL(currentURL string, next string) (string, error) { - base, err := url.Parse(currentURL) +// builds next page full url based on service endpoint +func nextPageURL(endpointURL, next string) (string, error) { + base, err := url.Parse(endpointURL) if err != nil { return "", err } @@ -45,5 +45,7 @@ func nextPageURL(currentURL string, next string) (string, error) { if err != nil { return "", err } - return base.ResolveReference(rel).String(), nil + combined := base.JoinPath(rel.Path) + combined.RawQuery = rel.RawQuery + return combined.String(), nil } diff --git a/openstack/messaging/v2/queues/requests.go b/openstack/messaging/v2/queues/requests.go index 2344921a89..ff4aa96760 100644 --- a/openstack/messaging/v2/queues/requests.go +++ b/openstack/messaging/v2/queues/requests.go @@ -50,7 +50,6 @@ func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pa pager := pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { return QueuePage{pagination.LinkedPageBase{PageResult: r}} - }) return pager } diff --git a/openstack/messaging/v2/queues/results.go b/openstack/messaging/v2/queues/results.go index 8190ddfa52..844eea6d07 100644 --- a/openstack/messaging/v2/queues/results.go +++ b/openstack/messaging/v2/queues/results.go @@ -160,7 +160,7 @@ func (r QueuePage) IsEmpty() (bool, error) { // NextPageURL uses the response's embedded link reference to navigate to the // next page of results. -func (r QueuePage) NextPageURL() (string, error) { +func (r QueuePage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"links"` } @@ -173,7 +173,7 @@ func (r QueuePage) NextPageURL() (string, error) { if err != nil { return "", err } - return nextPageURL(r.URL.String(), next) + return nextPageURL(endpointURL, next) } // GetCount value if it request was supplied `WithCount` param diff --git a/openstack/messaging/v2/queues/testing/fixtures_test.go b/openstack/messaging/v2/queues/testing/fixtures_test.go index 2e9cf5e752..c0d62f3e7b 100644 --- a/openstack/messaging/v2/queues/testing/fixtures_test.go +++ b/openstack/messaging/v2/queues/testing/fixtures_test.go @@ -7,7 +7,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/messaging/v2/queues" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) // QueueName is the name of the queue @@ -215,18 +215,18 @@ var ExpectedShare = queues.QueueShare{ } // HandleListSuccessfully configures the test server to respond to a List request. -func HandleListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2/queues", +func HandleListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2/queues", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") next := r.RequestURI switch next { case "/v2/queues?limit=1&with_count=true": - fmt.Fprintf(w, ListQueuesResponse1) + fmt.Fprint(w, ListQueuesResponse1) case "/v2/queues?marker=london": fmt.Fprint(w, ListQueuesResponse2) case "/v2/queues?marker=beijing": @@ -236,11 +236,11 @@ func HandleListSuccessfully(t *testing.T) { } // HandleCreateSuccessfully configures the test server to respond to a Create request. -func HandleCreateSuccessfully(t *testing.T) { - th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s", QueueName), +func HandleCreateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s", QueueName), func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateQueueRequest) w.WriteHeader(http.StatusNoContent) @@ -248,71 +248,71 @@ func HandleCreateSuccessfully(t *testing.T) { } // HandleUpdateSuccessfully configures the test server to respond to an Update request. -func HandleUpdateSuccessfully(t *testing.T) { - th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s", QueueName), +func HandleUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s", QueueName), func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, UpdateQueueRequest) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, UpdateQueueResponse) + fmt.Fprint(w, UpdateQueueResponse) }) } // HandleGetSuccessfully configures the test server to respond to a Get request. -func HandleGetSuccessfully(t *testing.T) { - th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s", QueueName), +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s", QueueName), func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetQueueResponse) + fmt.Fprint(w, GetQueueResponse) }) } // HandleDeleteSuccessfully configures the test server to respond to a Delete request. -func HandleDeleteSuccessfully(t *testing.T) { - th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s", QueueName), +func HandleDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s", QueueName), func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) } // HandleGetSuccessfully configures the test server to respond to a Get request. -func HandleGetStatsSuccessfully(t *testing.T) { - th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/stats", QueueName), +func HandleGetStatsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/stats", QueueName), func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetStatsResponse) + fmt.Fprint(w, GetStatsResponse) }) } // HandleShareSuccessfully configures the test server to respond to a Share request. -func HandleShareSuccessfully(t *testing.T) { - th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/share", QueueName), +func HandleShareSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/share", QueueName), func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreateShareRequest) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, CreateShareResponse) + fmt.Fprint(w, CreateShareResponse) }) } // HandlePurgeSuccessfully configures the test server to respond to a Purge request. -func HandlePurgeSuccessfully(t *testing.T) { - th.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/purge", QueueName), +func HandlePurgeSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(fmt.Sprintf("/v2/queues/%s/purge", QueueName), func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestJSONRequest(t, r, CreatePurgeRequest) w.WriteHeader(http.StatusNoContent) diff --git a/openstack/messaging/v2/queues/testing/requests_test.go b/openstack/messaging/v2/queues/testing/requests_test.go index eb83908d5d..839f5ec122 100644 --- a/openstack/messaging/v2/queues/testing/requests_test.go +++ b/openstack/messaging/v2/queues/testing/requests_test.go @@ -7,13 +7,13 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/messaging/v2/queues" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer) listOpts := queues.ListOpts{ Limit: 1, @@ -21,13 +21,13 @@ func TestList(t *testing.T) { } count := 0 - err := queues.List(fake.ServiceClient(), listOpts).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := queues.List(client.ServiceClient(fakeServer), listOpts).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { actual, err := queues.ExtractQueues(page) th.AssertNoErr(t, err) countField, err := page.(queues.QueuePage).GetCount() th.AssertNoErr(t, err) - th.AssertEquals(t, countField, 2) + th.AssertEquals(t, 2, countField) th.CheckDeepEquals(t, ExpectedQueueSlice[count], actual) count++ @@ -39,10 +39,10 @@ func TestList(t *testing.T) { } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateSuccessfully(t) - var enableEncrypted *bool = new(bool) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateSuccessfully(t, fakeServer) + var enableEncrypted = new(bool) createOpts := queues.CreateOpts{ QueueName: QueueName, @@ -56,14 +56,14 @@ func TestCreate(t *testing.T) { Extra: map[string]any{"description": "Queue for unit testing."}, } - err := queues.Create(context.TODO(), fake.ServiceClient(), createOpts).ExtractErr() + err := queues.Create(context.TODO(), client.ServiceClient(fakeServer), createOpts).ExtractErr() th.AssertNoErr(t, err) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateSuccessfully(t, fakeServer) updateOpts := queues.BatchUpdateOpts{ queues.UpdateOpts{ @@ -76,44 +76,44 @@ func TestUpdate(t *testing.T) { Extra: map[string]any{"description": "Update queue description"}, } - actual, err := queues.Update(context.TODO(), fake.ServiceClient(), QueueName, updateOpts).Extract() + actual, err := queues.Update(context.TODO(), client.ServiceClient(fakeServer), QueueName, updateOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, updatedQueueResult, actual) } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSuccessfully(t, fakeServer) - actual, err := queues.Get(context.TODO(), fake.ServiceClient(), QueueName).Extract() + actual, err := queues.Get(context.TODO(), client.ServiceClient(fakeServer), QueueName).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, QueueDetails, actual) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteSuccessfully(t, fakeServer) - err := queues.Delete(context.TODO(), fake.ServiceClient(), QueueName).ExtractErr() + err := queues.Delete(context.TODO(), client.ServiceClient(fakeServer), QueueName).ExtractErr() th.AssertNoErr(t, err) } func TestGetStat(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetStatsSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetStatsSuccessfully(t, fakeServer) - actual, err := queues.GetStats(context.TODO(), fake.ServiceClient(), QueueName).Extract() + actual, err := queues.GetStats(context.TODO(), client.ServiceClient(fakeServer), QueueName).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, ExpectedStats, actual) } func TestShare(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleShareSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleShareSuccessfully(t, fakeServer) shareOpts := queues.ShareOpts{ Paths: []queues.SharePath{queues.PathMessages, queues.PathClaims, queues.PathSubscriptions}, @@ -121,20 +121,20 @@ func TestShare(t *testing.T) { Expires: "2016-09-01T00:00:00", } - actual, err := queues.Share(context.TODO(), fake.ServiceClient(), QueueName, shareOpts).Extract() + actual, err := queues.Share(context.TODO(), client.ServiceClient(fakeServer), QueueName, shareOpts).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, ExpectedShare, actual) } func TestPurge(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandlePurgeSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePurgeSuccessfully(t, fakeServer) purgeOpts := queues.PurgeOpts{ ResourceTypes: []queues.PurgeResource{queues.ResourceMessages, queues.ResourceSubscriptions}, } - err := queues.Purge(context.TODO(), fake.ServiceClient(), QueueName, purgeOpts).ExtractErr() + err := queues.Purge(context.TODO(), client.ServiceClient(fakeServer), QueueName, purgeOpts).ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/messaging/v2/queues/urls.go b/openstack/messaging/v2/queues/urls.go index 9a89bfceab..9259d3f3f6 100644 --- a/openstack/messaging/v2/queues/urls.go +++ b/openstack/messaging/v2/queues/urls.go @@ -47,9 +47,9 @@ func purgeURL(client *gophercloud.ServiceClient, queueName string) string { return client.ServiceURL(apiVersion, apiName, queueName, "purge") } -// builds next page full url based on current url -func nextPageURL(currentURL string, next string) (string, error) { - base, err := url.Parse(currentURL) +// builds next page full url based on service endpoint +func nextPageURL(baseURL string, next string) (string, error) { + base, err := url.Parse(baseURL) if err != nil { return "", err } @@ -57,5 +57,7 @@ func nextPageURL(currentURL string, next string) (string, error) { if err != nil { return "", err } - return base.ResolveReference(rel).String(), nil + combined := base.JoinPath(rel.Path) + combined.RawQuery = rel.RawQuery + return combined.String(), nil } diff --git a/openstack/metric/v1/metrics/doc.go b/openstack/metric/v1/metrics/doc.go new file mode 100644 index 0000000000..410dc07735 --- /dev/null +++ b/openstack/metric/v1/metrics/doc.go @@ -0,0 +1,59 @@ +/* +Package metrics provides interaction with the Prometheus HTTP API through the +OpenStack Aetos service (metric-storage). + +Aetos acts as a Keystone-authenticated reverse proxy for Prometheus, enforcing +multi-tenant RBAC on all Prometheus queries. It exposes the Prometheus HTTP API +under /api/v1/ with OpenStack authentication. + +Example to Create a Metric Service Client + + metricClient, err := openstack.NewMetricV1(ctx, providerClient, gophercloud.EndpointOpts{ + Region: "RegionOne", + }) + if err != nil { + panic(err) + } + +Example to Query Metrics + + opts := metrics.QueryOpts{ + Query: "up", + } + + result, err := metrics.Query(ctx, metricClient, opts).Extract() + if err != nil { + panic(err) + } + + for _, v := range result.Result { + fmt.Printf("%s: %v\n", v.Metric["__name__"], v.Value) + } + +Example to List Label Names + + labels, err := metrics.Labels(ctx, metricClient, nil).Extract() + if err != nil { + panic(err) + } + + for _, label := range labels { + fmt.Println(label) + } + +Example to Find Series by Label Matchers + + opts := metrics.SeriesOpts{ + Match: []string{"up", `process_start_time_seconds{job="prometheus"}`}, + } + + series, err := metrics.Series(ctx, metricClient, opts).Extract() + if err != nil { + panic(err) + } + + for _, s := range series { + fmt.Printf("%v\n", s) + } +*/ +package metrics diff --git a/openstack/metric/v1/metrics/requests.go b/openstack/metric/v1/metrics/requests.go new file mode 100644 index 0000000000..0fdc9624fb --- /dev/null +++ b/openstack/metric/v1/metrics/requests.go @@ -0,0 +1,263 @@ +package metrics + +import ( + "context" + + "github.com/gophercloud/gophercloud/v2" +) + +// QueryOptsBuilder allows extensions to add parameters to the Query request. +type QueryOptsBuilder interface { + ToMetricQueryQuery() (string, error) +} + +// QueryOpts contains the options for a Prometheus instant query. +type QueryOpts struct { + // Query is the PromQL query string. + Query string `q:"query" required:"true"` + + // Time is the evaluation timestamp (RFC3339 or Unix timestamp). + Time string `q:"time"` + + // Timeout is the evaluation timeout. + Timeout string `q:"timeout"` +} + +// ToMetricQueryQuery formats QueryOpts into a query string. +func (opts QueryOpts) ToMetricQueryQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// Query performs a Prometheus instant query. +func Query(ctx context.Context, client *gophercloud.ServiceClient, opts QueryOptsBuilder) (r QueryResult) { + url := queryURL(client) + if opts != nil { + query, err := opts.ToMetricQueryQuery() + if err != nil { + r.Err = err + return + } + url += query + } + resp, err := client.Get(ctx, url, &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// LabelsOptsBuilder allows extensions to add parameters to the Labels request. +type LabelsOptsBuilder interface { + ToMetricLabelsQuery() (string, error) +} + +// LabelsOpts contains the options for listing label names. +type LabelsOpts struct { + // Match is a list of series selectors to filter labels by. + Match []string `q:"match[]"` + + // Start is the start timestamp (RFC3339 or Unix timestamp). + Start string `q:"start"` + + // End is the end timestamp (RFC3339 or Unix timestamp). + End string `q:"end"` +} + +// ToMetricLabelsQuery formats LabelsOpts into a query string. +func (opts LabelsOpts) ToMetricLabelsQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// Labels returns a list of label names. +func Labels(ctx context.Context, client *gophercloud.ServiceClient, opts LabelsOptsBuilder) (r LabelsResult) { + url := labelsURL(client) + if opts != nil { + query, err := opts.ToMetricLabelsQuery() + if err != nil { + r.Err = err + return + } + url += query + } + resp, err := client.Get(ctx, url, &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// LabelValuesOptsBuilder allows extensions to add parameters to the LabelValues request. +type LabelValuesOptsBuilder interface { + ToMetricLabelValuesQuery() (string, error) +} + +// LabelValuesOpts contains the options for listing label values. +type LabelValuesOpts struct { + // Match is a list of series selectors to filter label values by. + Match []string `q:"match[]"` + + // Start is the start timestamp (RFC3339 or Unix timestamp). + Start string `q:"start"` + + // End is the end timestamp (RFC3339 or Unix timestamp). + End string `q:"end"` +} + +// ToMetricLabelValuesQuery formats LabelValuesOpts into a query string. +func (opts LabelValuesOpts) ToMetricLabelValuesQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// LabelValues returns a list of label values for a given label name. +func LabelValues(ctx context.Context, client *gophercloud.ServiceClient, name string, opts LabelValuesOptsBuilder) (r LabelValuesResult) { + url := labelValuesURL(client, name) + if opts != nil { + query, err := opts.ToMetricLabelValuesQuery() + if err != nil { + r.Err = err + return + } + url += query + } + resp, err := client.Get(ctx, url, &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// SeriesOptsBuilder allows extensions to add parameters to the Series request. +type SeriesOptsBuilder interface { + ToMetricSeriesQuery() (string, error) +} + +// SeriesOpts contains the options for finding series by label matchers. +type SeriesOpts struct { + // Match is a list of series selectors. At least one must be provided. + Match []string `q:"match[]" required:"true"` + + // Start is the start timestamp (RFC3339 or Unix timestamp). + Start string `q:"start"` + + // End is the end timestamp (RFC3339 or Unix timestamp). + End string `q:"end"` +} + +// ToMetricSeriesQuery formats SeriesOpts into a query string. +func (opts SeriesOpts) ToMetricSeriesQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// Series returns the list of time series that match certain label sets. +func Series(ctx context.Context, client *gophercloud.ServiceClient, opts SeriesOptsBuilder) (r SeriesResult) { + url := seriesURL(client) + if opts != nil { + query, err := opts.ToMetricSeriesQuery() + if err != nil { + r.Err = err + return + } + url += query + } + resp, err := client.Get(ctx, url, &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// TargetsOptsBuilder allows extensions to add parameters to the Targets request. +type TargetsOptsBuilder interface { + ToMetricTargetsQuery() (string, error) +} + +// TargetsOpts contains the options for listing targets. +type TargetsOpts struct { + // State filters targets by state ("active", "dropped", "any"). + State string `q:"state"` +} + +// ToMetricTargetsQuery formats TargetsOpts into a query string. +func (opts TargetsOpts) ToMetricTargetsQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// Targets returns an overview of the current state of Prometheus target discovery. +func Targets(ctx context.Context, client *gophercloud.ServiceClient, opts TargetsOptsBuilder) (r TargetsResult) { + url := targetsURL(client) + if opts != nil { + query, err := opts.ToMetricTargetsQuery() + if err != nil { + r.Err = err + return + } + url += query + } + resp, err := client.Get(ctx, url, &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// RuntimeInfo returns runtime information about the Prometheus server. +func RuntimeInfo(ctx context.Context, client *gophercloud.ServiceClient) (r RuntimeInfoResult) { + resp, err := client.Get(ctx, runtimeInfoURL(client), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// CleanTombstones removes deleted data from disk and cleans up the existing tombstones. +func CleanTombstones(ctx context.Context, client *gophercloud.ServiceClient) (r CleanTombstonesResult) { + resp, err := client.Post(ctx, cleanTombstonesURL(client), nil, nil, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// DeleteSeriesOptsBuilder allows extensions to add parameters to the DeleteSeries request. +type DeleteSeriesOptsBuilder interface { + ToMetricDeleteSeriesQuery() (string, error) +} + +// DeleteSeriesOpts contains the options for deleting time series. +type DeleteSeriesOpts struct { + // Match is a list of series selectors. At least one must be provided. + Match []string `q:"match[]" required:"true"` + + // Start is the start timestamp (RFC3339 or Unix timestamp). + Start string `q:"start"` + + // End is the end timestamp (RFC3339 or Unix timestamp). + End string `q:"end"` +} + +// ToMetricDeleteSeriesQuery formats DeleteSeriesOpts into a query string. +func (opts DeleteSeriesOpts) ToMetricDeleteSeriesQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// DeleteSeries deletes data for a selection of series in a time range. +func DeleteSeries(ctx context.Context, client *gophercloud.ServiceClient, opts DeleteSeriesOptsBuilder) (r DeleteSeriesResult) { + url := deleteSeriesURL(client) + if opts != nil { + query, err := opts.ToMetricDeleteSeriesQuery() + if err != nil { + r.Err = err + return + } + url += query + } + resp, err := client.Post(ctx, url, nil, nil, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// Snapshot creates a snapshot of all current data into snapshots/- +// under the TSDB's data directory and returns the directory as response. +func Snapshot(ctx context.Context, client *gophercloud.ServiceClient) (r SnapshotResult) { + resp, err := client.Post(ctx, snapshotURL(client), nil, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/metric/v1/metrics/results.go b/openstack/metric/v1/metrics/results.go new file mode 100644 index 0000000000..c4c6463ecf --- /dev/null +++ b/openstack/metric/v1/metrics/results.go @@ -0,0 +1,298 @@ +package metrics + +import ( + "encoding/json" + "fmt" + + "github.com/gophercloud/gophercloud/v2" +) + +// prometheusResponse is the common envelope for all Prometheus HTTP API responses. +type prometheusResponse struct { + Status string `json:"status"` + Data json.RawMessage `json:"data"` + ErrorType string `json:"errorType"` + Error string `json:"error"` +} + +// checkResponse unmarshals the Prometheus envelope and returns an error if +// the response status is "error". +func checkResponse(body any) (*prometheusResponse, error) { + b, err := json.Marshal(body) + if err != nil { + return nil, err + } + var resp prometheusResponse + if err := json.Unmarshal(b, &resp); err != nil { + return nil, err + } + if resp.Status == "error" { + return &resp, fmt.Errorf("prometheus %s: %s", resp.ErrorType, resp.Error) + } + return &resp, nil +} + +// QueryResult is the result of a Query request. +type QueryResult struct { + gophercloud.Result +} + +// Extract interprets a QueryResult as QueryData. +func (r QueryResult) Extract() (*QueryData, error) { + if r.Err != nil { + return nil, r.Err + } + resp, err := checkResponse(r.Body) + if err != nil { + return nil, err + } + var data QueryData + if err := json.Unmarshal(resp.Data, &data); err != nil { + return nil, err + } + return &data, nil +} + +// LabelsResult is the result of a Labels request. +type LabelsResult struct { + gophercloud.Result +} + +// Extract interprets a LabelsResult as a slice of label name strings. +func (r LabelsResult) Extract() ([]string, error) { + if r.Err != nil { + return nil, r.Err + } + resp, err := checkResponse(r.Body) + if err != nil { + return nil, err + } + var data []string + if err := json.Unmarshal(resp.Data, &data); err != nil { + return nil, err + } + return data, nil +} + +// LabelValuesResult is the result of a LabelValues request. +type LabelValuesResult struct { + gophercloud.Result +} + +// Extract interprets a LabelValuesResult as a slice of label value strings. +func (r LabelValuesResult) Extract() ([]string, error) { + if r.Err != nil { + return nil, r.Err + } + resp, err := checkResponse(r.Body) + if err != nil { + return nil, err + } + var data []string + if err := json.Unmarshal(resp.Data, &data); err != nil { + return nil, err + } + return data, nil +} + +// SeriesResult is the result of a Series request. +type SeriesResult struct { + gophercloud.Result +} + +// Extract interprets a SeriesResult as a slice of label-set maps. +func (r SeriesResult) Extract() ([]map[string]string, error) { + if r.Err != nil { + return nil, r.Err + } + resp, err := checkResponse(r.Body) + if err != nil { + return nil, err + } + var data []map[string]string + if err := json.Unmarshal(resp.Data, &data); err != nil { + return nil, err + } + return data, nil +} + +// TargetsResult is the result of a Targets request. +type TargetsResult struct { + gophercloud.Result +} + +// Extract interprets a TargetsResult as TargetsData. +func (r TargetsResult) Extract() (*TargetsData, error) { + if r.Err != nil { + return nil, r.Err + } + resp, err := checkResponse(r.Body) + if err != nil { + return nil, err + } + var data TargetsData + if err := json.Unmarshal(resp.Data, &data); err != nil { + return nil, err + } + return &data, nil +} + +// RuntimeInfoResult is the result of a RuntimeInfo request. +type RuntimeInfoResult struct { + gophercloud.Result +} + +// Extract interprets a RuntimeInfoResult as RuntimeInfoData. +func (r RuntimeInfoResult) Extract() (*RuntimeInfoData, error) { + if r.Err != nil { + return nil, r.Err + } + resp, err := checkResponse(r.Body) + if err != nil { + return nil, err + } + var data RuntimeInfoData + if err := json.Unmarshal(resp.Data, &data); err != nil { + return nil, err + } + return &data, nil +} + +// CleanTombstonesResult is the result of a CleanTombstones request. +type CleanTombstonesResult struct { + gophercloud.ErrResult +} + +// DeleteSeriesResult is the result of a DeleteSeries request. +type DeleteSeriesResult struct { + gophercloud.ErrResult +} + +// SnapshotResult is the result of a Snapshot request. +type SnapshotResult struct { + gophercloud.Result +} + +// Extract interprets a SnapshotResult as SnapshotData. +func (r SnapshotResult) Extract() (*SnapshotData, error) { + if r.Err != nil { + return nil, r.Err + } + resp, err := checkResponse(r.Body) + if err != nil { + return nil, err + } + var data SnapshotData + if err := json.Unmarshal(resp.Data, &data); err != nil { + return nil, err + } + return &data, nil +} + +// QueryData represents the data field of a Prometheus instant query response. +type QueryData struct { + // ResultType is the type of result (e.g., "vector", "matrix", "scalar", "string"). + ResultType string `json:"resultType"` + + // Result is the list of metric values returned by the query. + Result []MetricValue `json:"result"` +} + +// MetricValue represents a single metric result with its labels and value. +type MetricValue struct { + // Metric contains the label set identifying this metric. + Metric map[string]string `json:"metric"` + + // Value contains a single sample value as [timestamp, "string_value"]. + Value []any `json:"value"` +} + +// TargetsData represents the data field of a Prometheus targets response. +type TargetsData struct { + // ActiveTargets is the list of currently active scrape targets. + ActiveTargets []ActiveTarget `json:"activeTargets"` + + // DroppedTargets is the list of targets that were dropped during relabeling. + DroppedTargets []DroppedTarget `json:"droppedTargets"` +} + +// ActiveTarget represents an active Prometheus scrape target. +type ActiveTarget struct { + // DiscoveredLabels are the unmodified labels from service discovery. + DiscoveredLabels map[string]string `json:"discoveredLabels"` + + // Labels are the labels after relabeling. + Labels map[string]string `json:"labels"` + + // ScrapePool is the name of the scrape configuration. + ScrapePool string `json:"scrapePool"` + + // ScrapeURL is the URL being scraped. + ScrapeURL string `json:"scrapeUrl"` + + // GlobalURL is the URL as available from other Prometheus instances. + GlobalURL string `json:"globalUrl"` + + // LastError is the last error encountered during scraping. + LastError string `json:"lastError"` + + // LastScrape is the time of the last scrape. + LastScrape string `json:"lastScrape"` + + // LastScrapeDuration is the duration of the last scrape in seconds. + LastScrapeDuration float64 `json:"lastScrapeDuration"` + + // Health is the health status of the target ("up", "down", "unknown"). + Health string `json:"health"` + + // ScrapeInterval is the configured scrape interval. + ScrapeInterval string `json:"scrapeInterval"` + + // ScrapeTimeout is the configured scrape timeout. + ScrapeTimeout string `json:"scrapeTimeout"` +} + +// DroppedTarget represents a target that was dropped during relabeling. +type DroppedTarget struct { + // DiscoveredLabels are the unmodified labels from service discovery. + DiscoveredLabels map[string]string `json:"discoveredLabels"` +} + +// RuntimeInfoData represents the data field of a Prometheus runtime info response. +type RuntimeInfoData struct { + // StartTime is when the Prometheus server started. + StartTime string `json:"startTime"` + + // CWD is the current working directory of the Prometheus server. + CWD string `json:"CWD"` + + // ReloadConfigSuccess indicates if the last config reload was successful. + ReloadConfigSuccess bool `json:"reloadConfigSuccess"` + + // LastConfigTime is the time of the last config reload. + LastConfigTime string `json:"lastConfigTime"` + + // CorruptionCount is the number of WAL corruptions encountered. + CorruptionCount int `json:"corruptionCount"` + + // GoroutineCount is the number of goroutines. + GoroutineCount int `json:"goroutineCount"` + + // GOMAXPROCS is the configured GOMAXPROCS value. + GOMAXPROCS int `json:"GOMAXPROCS"` + + // GOGC is the configured GOGC value. + GOGC string `json:"GOGC"` + + // GODEBUG is the configured GODEBUG value. + GODEBUG string `json:"GODEBUG"` + + // StorageRetention is the configured storage retention. + StorageRetention string `json:"storageRetention"` +} + +// SnapshotData represents the data field of a Prometheus snapshot response. +type SnapshotData struct { + // Name is the filename of the snapshot created. + Name string `json:"name"` +} diff --git a/openstack/metric/v1/metrics/testing/doc.go b/openstack/metric/v1/metrics/testing/doc.go new file mode 100644 index 0000000000..c1ca16c942 --- /dev/null +++ b/openstack/metric/v1/metrics/testing/doc.go @@ -0,0 +1,2 @@ +// metrics unit tests +package testing diff --git a/openstack/metric/v1/metrics/testing/fixtures_test.go b/openstack/metric/v1/metrics/testing/fixtures_test.go new file mode 100644 index 0000000000..7da259fc61 --- /dev/null +++ b/openstack/metric/v1/metrics/testing/fixtures_test.go @@ -0,0 +1,327 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack/metric/v1/metrics" + th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/client" +) + +// serviceClient returns a ServiceClient with ResourceBase set to include api/v1/. +func serviceClient(fakeServer th.FakeServer) *gophercloud.ServiceClient { + sc := client.ServiceClient(fakeServer) + sc.ResourceBase = sc.Endpoint + "api/v1/" + return sc +} + +// QueryOutput is a sample response to a Query call. +const QueryOutput = ` +{ + "status": "success", + "data": { + "resultType": "vector", + "result": [ + { + "metric": { + "__name__": "up", + "instance": "localhost:9090", + "job": "prometheus" + }, + "value": [1435781451.781, "1"] + } + ] + } +} +` + +// ExpectedQueryData is the expected result from QueryOutput. +var ExpectedQueryData = &metrics.QueryData{ + ResultType: "vector", + Result: []metrics.MetricValue{ + { + Metric: map[string]string{ + "__name__": "up", + "instance": "localhost:9090", + "job": "prometheus", + }, + Value: []any{1435781451.781, "1"}, + }, + }, +} + +// LabelsOutput is a sample response to a Labels call. +const LabelsOutput = ` +{ + "status": "success", + "data": ["__name__", "instance", "job"] +} +` + +// ExpectedLabels is the expected result from LabelsOutput. +var ExpectedLabels = []string{"__name__", "instance", "job"} + +// LabelValuesOutput is a sample response to a LabelValues call. +const LabelValuesOutput = ` +{ + "status": "success", + "data": ["node", "prometheus"] +} +` + +// ExpectedLabelValues is the expected result from LabelValuesOutput. +var ExpectedLabelValues = []string{"node", "prometheus"} + +// SeriesOutput is a sample response to a Series call. +const SeriesOutput = ` +{ + "status": "success", + "data": [ + { + "__name__": "up", + "instance": "localhost:9090", + "job": "prometheus" + }, + { + "__name__": "up", + "instance": "localhost:9100", + "job": "node" + } + ] +} +` + +// ExpectedSeries is the expected result from SeriesOutput. +var ExpectedSeries = []map[string]string{ + { + "__name__": "up", + "instance": "localhost:9090", + "job": "prometheus", + }, + { + "__name__": "up", + "instance": "localhost:9100", + "job": "node", + }, +} + +// TargetsOutput is a sample response to a Targets call. +const TargetsOutput = ` +{ + "status": "success", + "data": { + "activeTargets": [ + { + "discoveredLabels": { + "__address__": "localhost:9090", + "__scheme__": "http", + "job": "prometheus" + }, + "labels": { + "instance": "localhost:9090", + "job": "prometheus" + }, + "scrapePool": "prometheus", + "scrapeUrl": "http://localhost:9090/metrics", + "globalUrl": "http://localhost:9090/metrics", + "lastError": "", + "lastScrape": "2025-08-20T10:30:00.000Z", + "lastScrapeDuration": 0.003145, + "health": "up", + "scrapeInterval": "15s", + "scrapeTimeout": "10s" + } + ], + "droppedTargets": [ + { + "discoveredLabels": { + "__address__": "localhost:9091", + "job": "dropped" + } + } + ] + } +} +` + +// ExpectedTargetsData is the expected result from TargetsOutput. +var ExpectedTargetsData = &metrics.TargetsData{ + ActiveTargets: []metrics.ActiveTarget{ + { + DiscoveredLabels: map[string]string{ + "__address__": "localhost:9090", + "__scheme__": "http", + "job": "prometheus", + }, + Labels: map[string]string{ + "instance": "localhost:9090", + "job": "prometheus", + }, + ScrapePool: "prometheus", + ScrapeURL: "http://localhost:9090/metrics", + GlobalURL: "http://localhost:9090/metrics", + LastError: "", + LastScrape: "2025-08-20T10:30:00.000Z", + LastScrapeDuration: 0.003145, + Health: "up", + ScrapeInterval: "15s", + ScrapeTimeout: "10s", + }, + }, + DroppedTargets: []metrics.DroppedTarget{ + { + DiscoveredLabels: map[string]string{ + "__address__": "localhost:9091", + "job": "dropped", + }, + }, + }, +} + +// RuntimeInfoOutput is a sample response to a RuntimeInfo call. +const RuntimeInfoOutput = ` +{ + "status": "success", + "data": { + "startTime": "2025-08-20T10:00:00.000Z", + "CWD": "/prometheus", + "reloadConfigSuccess": true, + "lastConfigTime": "2025-08-20T10:00:00.000Z", + "corruptionCount": 0, + "goroutineCount": 42, + "GOMAXPROCS": 4, + "GOGC": "", + "GODEBUG": "", + "storageRetention": "15d" + } +} +` + +// ExpectedRuntimeInfoData is the expected result from RuntimeInfoOutput. +var ExpectedRuntimeInfoData = &metrics.RuntimeInfoData{ + StartTime: "2025-08-20T10:00:00.000Z", + CWD: "/prometheus", + ReloadConfigSuccess: true, + LastConfigTime: "2025-08-20T10:00:00.000Z", + CorruptionCount: 0, + GoroutineCount: 42, + GOMAXPROCS: 4, + GOGC: "", + GODEBUG: "", + StorageRetention: "15d", +} + +// SnapshotOutput is a sample response to a Snapshot call. +const SnapshotOutput = ` +{ + "status": "success", + "data": { + "name": "20250820T100000Z-abcdef1234567890" + } +} +` + +// ExpectedSnapshotData is the expected result from SnapshotOutput. +var ExpectedSnapshotData = &metrics.SnapshotData{ + Name: "20250820T100000Z-abcdef1234567890", +} + +// HandleQuerySuccessfully configures the test server to respond to a Query request. +func HandleQuerySuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/api/v1/query", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, QueryOutput) + }) +} + +// HandleLabelsSuccessfully configures the test server to respond to a Labels request. +func HandleLabelsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/api/v1/labels", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, LabelsOutput) + }) +} + +// HandleLabelValuesSuccessfully configures the test server to respond to a LabelValues request. +func HandleLabelValuesSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/api/v1/label/job/values", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, LabelValuesOutput) + }) +} + +// HandleSeriesSuccessfully configures the test server to respond to a Series request. +func HandleSeriesSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/api/v1/series", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, SeriesOutput) + }) +} + +// HandleTargetsSuccessfully configures the test server to respond to a Targets request. +func HandleTargetsSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/api/v1/targets", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, TargetsOutput) + }) +} + +// HandleRuntimeInfoSuccessfully configures the test server to respond to a RuntimeInfo request. +func HandleRuntimeInfoSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/api/v1/status/runtimeinfo", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, RuntimeInfoOutput) + }) +} + +// HandleCleanTombstonesSuccessfully configures the test server to respond to a CleanTombstones request. +func HandleCleanTombstonesSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/api/v1/admin/tsdb/clean_tombstones", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleDeleteSeriesSuccessfully configures the test server to respond to a DeleteSeries request. +func HandleDeleteSeriesSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/api/v1/admin/tsdb/delete_series", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleSnapshotSuccessfully configures the test server to respond to a Snapshot request. +func HandleSnapshotSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/api/v1/admin/tsdb/snapshot", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, SnapshotOutput) + }) +} diff --git a/openstack/metric/v1/metrics/testing/requests_test.go b/openstack/metric/v1/metrics/testing/requests_test.go new file mode 100644 index 0000000000..3311250643 --- /dev/null +++ b/openstack/metric/v1/metrics/testing/requests_test.go @@ -0,0 +1,109 @@ +package testing + +import ( + "context" + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/metric/v1/metrics" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func TestQuery(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleQuerySuccessfully(t, fakeServer) + + opts := metrics.QueryOpts{ + Query: "up", + } + + actual, err := metrics.Query(context.TODO(), serviceClient(fakeServer), opts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedQueryData, actual) +} + +func TestLabels(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleLabelsSuccessfully(t, fakeServer) + + actual, err := metrics.Labels(context.TODO(), serviceClient(fakeServer), nil).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedLabels, actual) +} + +func TestLabelValues(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleLabelValuesSuccessfully(t, fakeServer) + + actual, err := metrics.LabelValues(context.TODO(), serviceClient(fakeServer), "job", nil).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedLabelValues, actual) +} + +func TestSeries(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleSeriesSuccessfully(t, fakeServer) + + opts := metrics.SeriesOpts{ + Match: []string{"up"}, + } + + actual, err := metrics.Series(context.TODO(), serviceClient(fakeServer), opts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedSeries, actual) +} + +func TestTargets(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleTargetsSuccessfully(t, fakeServer) + + actual, err := metrics.Targets(context.TODO(), serviceClient(fakeServer), nil).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedTargetsData, actual) +} + +func TestRuntimeInfo(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleRuntimeInfoSuccessfully(t, fakeServer) + + actual, err := metrics.RuntimeInfo(context.TODO(), serviceClient(fakeServer)).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedRuntimeInfoData, actual) +} + +func TestCleanTombstones(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCleanTombstonesSuccessfully(t, fakeServer) + + err := metrics.CleanTombstones(context.TODO(), serviceClient(fakeServer)).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestDeleteSeries(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteSeriesSuccessfully(t, fakeServer) + + opts := metrics.DeleteSeriesOpts{ + Match: []string{"up"}, + } + + err := metrics.DeleteSeries(context.TODO(), serviceClient(fakeServer), opts).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestSnapshot(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleSnapshotSuccessfully(t, fakeServer) + + actual, err := metrics.Snapshot(context.TODO(), serviceClient(fakeServer)).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedSnapshotData, actual) +} diff --git a/openstack/metric/v1/metrics/urls.go b/openstack/metric/v1/metrics/urls.go new file mode 100644 index 0000000000..856f84da5b --- /dev/null +++ b/openstack/metric/v1/metrics/urls.go @@ -0,0 +1,39 @@ +package metrics + +import "github.com/gophercloud/gophercloud/v2" + +func queryURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("query") +} + +func labelsURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("labels") +} + +func labelValuesURL(c *gophercloud.ServiceClient, name string) string { + return c.ServiceURL("label", name, "values") +} + +func seriesURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("series") +} + +func targetsURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("targets") +} + +func runtimeInfoURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("status", "runtimeinfo") +} + +func cleanTombstonesURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("admin", "tsdb", "clean_tombstones") +} + +func deleteSeriesURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("admin", "tsdb", "delete_series") +} + +func snapshotURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("admin", "tsdb", "snapshot") +} diff --git a/openstack/networking/v2/apiversions/constants.go b/openstack/networking/v2/apiversions/constants.go new file mode 100644 index 0000000000..2ffd32aa0d --- /dev/null +++ b/openstack/networking/v2/apiversions/constants.go @@ -0,0 +1,7 @@ +package apiversions + +const ( + StatusCurrent = "CURRENT" + StatusDeprecated = "DEPRECATED" + StatusStable = "STABLE" +) diff --git a/openstack/networking/v2/apiversions/results.go b/openstack/networking/v2/apiversions/results.go index 89f30152b2..90fac7347d 100644 --- a/openstack/networking/v2/apiversions/results.go +++ b/openstack/networking/v2/apiversions/results.go @@ -7,7 +7,7 @@ import ( // APIVersion represents an API version for Neutron. It contains the status of // the API, and its unique ID. type APIVersion struct { - Status string `son:"status"` + Status string `json:"status"` ID string `json:"id"` } diff --git a/openstack/networking/v2/apiversions/testing/requests_test.go b/openstack/networking/v2/apiversions/testing/requests_test.go index c99c99488b..842b439c45 100644 --- a/openstack/networking/v2/apiversions/testing/requests_test.go +++ b/openstack/networking/v2/apiversions/testing/requests_test.go @@ -9,21 +9,21 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/apiversions" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestListVersions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "versions": [ { @@ -42,7 +42,7 @@ func TestListVersions(t *testing.T) { count := 0 - err := apiversions.ListVersions(fake.ServiceClient()).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := apiversions.ListVersions(client.ServiceClient(fakeServer)).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := apiversions.ExtractAPIVersions(page) if err != nil { @@ -69,14 +69,14 @@ func TestListVersions(t *testing.T) { } func TestNonJSONCannotBeExtractedIntoAPIVersions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) - err := apiversions.ListVersions(fake.ServiceClient()).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := apiversions.ListVersions(client.ServiceClient(fakeServer)).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { if _, err := apiversions.ExtractAPIVersions(page); err == nil { t.Fatalf("Expected error, got nil") } @@ -86,17 +86,17 @@ func TestNonJSONCannotBeExtractedIntoAPIVersions(t *testing.T) { } func TestAPIInfo(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "resources": [ { @@ -136,7 +136,7 @@ func TestAPIInfo(t *testing.T) { count := 0 - err := apiversions.ListVersionResources(fake.ServiceClient(), "v2.0").EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := apiversions.ListVersionResources(client.ServiceClient(fakeServer), "v2.0").EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := apiversions.ExtractVersionResources(page) if err != nil { @@ -171,14 +171,14 @@ func TestAPIInfo(t *testing.T) { } func TestNonJSONCannotBeExtractedIntoAPIVersionResources(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) - err := apiversions.ListVersionResources(fake.ServiceClient(), "v2.0").EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := apiversions.ListVersionResources(client.ServiceClient(fakeServer), "v2.0").EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { if _, err := apiversions.ExtractVersionResources(page); err == nil { t.Fatalf("Expected error, got nil") } diff --git a/openstack/networking/v2/common/common_tests.go b/openstack/networking/v2/common/common_tests.go index f32313a105..581c9803aa 100644 --- a/openstack/networking/v2/common/common_tests.go +++ b/openstack/networking/v2/common/common_tests.go @@ -2,13 +2,14 @@ package common import ( "github.com/gophercloud/gophercloud/v2" + th "github.com/gophercloud/gophercloud/v2/testhelper" "github.com/gophercloud/gophercloud/v2/testhelper/client" ) const TokenID = client.TokenID -func ServiceClient() *gophercloud.ServiceClient { - sc := client.ServiceClient() +func ServiceClient(fakeServer th.FakeServer) *gophercloud.ServiceClient { + sc := client.ServiceClient(fakeServer) sc.ResourceBase = sc.Endpoint + "v2.0/" return sc } diff --git a/openstack/networking/v2/extensions/agents/results.go b/openstack/networking/v2/extensions/agents/results.go index 166702009d..0fda2e8bc1 100644 --- a/openstack/networking/v2/extensions/agents/results.go +++ b/openstack/networking/v2/extensions/agents/results.go @@ -147,7 +147,7 @@ type AgentPage struct { // NextPageURL is invoked when a paginated collection of agent has // reached the end of a page and the pager seeks to traverse over a new one. // In order to do this, it needs to construct the next page's URL. -func (r AgentPage) NextPageURL() (string, error) { +func (r AgentPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"agents_links"` } @@ -205,7 +205,7 @@ func (r ListBGPSpeakersResult) IsEmpty() (bool, error) { } speakers, err := ExtractBGPSpeakers(r) - return 0 == len(speakers), err + return len(speakers) == 0, err } // ExtractBGPSpeakers inteprets the ListBGPSpeakersResult into an array of BGP speakers diff --git a/openstack/networking/v2/extensions/agents/testing/requests_test.go b/openstack/networking/v2/extensions/agents/testing/requests_test.go index 070ff1b184..c75e1a8106 100644 --- a/openstack/networking/v2/extensions/agents/testing/requests_test.go +++ b/openstack/networking/v2/extensions/agents/testing/requests_test.go @@ -15,22 +15,22 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/agents", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/agents", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, AgentsListResult) + fmt.Fprint(w, AgentsListResult) }) count := 0 - err := agents.List(fake.ServiceClient(), agents.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := agents.List(fake.ServiceClient(fakeServer), agents.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := agents.ExtractAgents(page) @@ -56,47 +56,47 @@ func TestList(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/agents/43583cf5-472e-4dc8-af5b-6aed4c94ee3a", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/agents/43583cf5-472e-4dc8-af5b-6aed4c94ee3a", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, AgentsGetResult) + fmt.Fprint(w, AgentsGetResult) }) - s, err := agents.Get(context.TODO(), fake.ServiceClient(), "43583cf5-472e-4dc8-af5b-6aed4c94ee3a").Extract() + s, err := agents.Get(context.TODO(), fake.ServiceClient(fakeServer), "43583cf5-472e-4dc8-af5b-6aed4c94ee3a").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.ID, "43583cf5-472e-4dc8-af5b-6aed4c94ee3a") - th.AssertEquals(t, s.Binary, "neutron-openvswitch-agent") - th.AssertEquals(t, s.AdminStateUp, true) - th.AssertEquals(t, s.Alive, true) - th.AssertEquals(t, s.Topic, "N/A") - th.AssertEquals(t, s.Host, "compute3") - th.AssertEquals(t, s.AgentType, "Open vSwitch agent") + th.AssertEquals(t, "43583cf5-472e-4dc8-af5b-6aed4c94ee3a", s.ID) + th.AssertEquals(t, "neutron-openvswitch-agent", s.Binary) + th.AssertTrue(t, s.AdminStateUp) + th.AssertTrue(t, s.Alive) + th.AssertEquals(t, "N/A", s.Topic) + th.AssertEquals(t, "compute3", s.Host) + th.AssertEquals(t, "Open vSwitch agent", s.AgentType) th.AssertEquals(t, s.HeartbeatTimestamp, time.Date(2019, 1, 9, 11, 43, 01, 0, time.UTC)) th.AssertEquals(t, s.StartedAt, time.Date(2018, 6, 26, 21, 46, 20, 0, time.UTC)) th.AssertEquals(t, s.CreatedAt, time.Date(2017, 7, 26, 23, 2, 5, 0, time.UTC)) - th.AssertDeepEquals(t, s.Configurations, map[string]any{ + th.AssertDeepEquals(t, map[string]any{ "ovs_hybrid_plug": false, "datapath_type": "system", "vhostuser_socket_dir": "/var/run/openvswitch", "log_agent_heartbeats": false, "l2_population": true, "enable_distributed_routing": false, - }) + }, s.Configurations) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/agents/43583cf5-472e-4dc8-af5b-6aed4c94ee3a", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/agents/43583cf5-472e-4dc8-af5b-6aed4c94ee3a", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -106,7 +106,7 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, AgentsUpdateResult) + fmt.Fprint(w, AgentsUpdateResult) }) iTrue := true @@ -115,17 +115,17 @@ func TestUpdate(t *testing.T) { Description: &description, AdminStateUp: &iTrue, } - s, err := agents.Update(context.TODO(), fake.ServiceClient(), "43583cf5-472e-4dc8-af5b-6aed4c94ee3a", updateOpts).Extract() + s, err := agents.Update(context.TODO(), fake.ServiceClient(fakeServer), "43583cf5-472e-4dc8-af5b-6aed4c94ee3a", updateOpts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, *s, Agent) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/agents/43583cf5-472e-4dc8-af5b-6aed4c94ee3a", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/agents/43583cf5-472e-4dc8-af5b-6aed4c94ee3a", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Accept", "application/json") @@ -134,47 +134,47 @@ func TestDelete(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - err := agents.Delete(context.TODO(), fake.ServiceClient(), "43583cf5-472e-4dc8-af5b-6aed4c94ee3a").ExtractErr() + err := agents.Delete(context.TODO(), fake.ServiceClient(fakeServer), "43583cf5-472e-4dc8-af5b-6aed4c94ee3a").ExtractErr() th.AssertNoErr(t, err) } func TestListDHCPNetworks(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/agents/43583cf5-472e-4dc8-af5b-6aed4c94ee3a/dhcp-networks", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/agents/43583cf5-472e-4dc8-af5b-6aed4c94ee3a/dhcp-networks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, AgentDHCPNetworksListResult) + fmt.Fprint(w, AgentDHCPNetworksListResult) }) - s, err := agents.ListDHCPNetworks(context.TODO(), fake.ServiceClient(), "43583cf5-472e-4dc8-af5b-6aed4c94ee3a").Extract() + s, err := agents.ListDHCPNetworks(context.TODO(), fake.ServiceClient(fakeServer), "43583cf5-472e-4dc8-af5b-6aed4c94ee3a").Extract() th.AssertNoErr(t, err) var nilSlice []string - th.AssertEquals(t, len(s), 1) - th.AssertEquals(t, s[0].ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") - th.AssertEquals(t, s[0].AdminStateUp, true) - th.AssertEquals(t, s[0].ProjectID, "4fd44f30292945e481c7b8a0c8908869") - th.AssertEquals(t, s[0].Shared, false) - th.AssertEquals(t, s[0].Name, "net1") - th.AssertEquals(t, s[0].Status, "ACTIVE") + th.AssertEquals(t, 1, len(s)) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", s[0].ID) + th.AssertTrue(t, s[0].AdminStateUp) + th.AssertEquals(t, "4fd44f30292945e481c7b8a0c8908869", s[0].ProjectID) + th.AssertFalse(t, s[0].Shared) + th.AssertEquals(t, "net1", s[0].Name) + th.AssertEquals(t, "ACTIVE", s[0].Status) th.AssertDeepEquals(t, s[0].Tags, nilSlice) - th.AssertEquals(t, s[0].TenantID, "4fd44f30292945e481c7b8a0c8908869") - th.AssertDeepEquals(t, s[0].AvailabilityZoneHints, []string{}) - th.AssertDeepEquals(t, s[0].Subnets, []string{"54d6f61d-db07-451c-9ab3-b9609b6b6f0b"}) + th.AssertEquals(t, "4fd44f30292945e481c7b8a0c8908869", s[0].TenantID) + th.AssertDeepEquals(t, []string{}, s[0].AvailabilityZoneHints) + th.AssertDeepEquals(t, []string{"54d6f61d-db07-451c-9ab3-b9609b6b6f0b"}, s[0].Subnets) } func TestScheduleDHCPNetwork(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/agents/43583cf5-472e-4dc8-af5b-6aed4c94ee3a/dhcp-networks", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/agents/43583cf5-472e-4dc8-af5b-6aed4c94ee3a/dhcp-networks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -188,15 +188,15 @@ func TestScheduleDHCPNetwork(t *testing.T) { opts := &agents.ScheduleDHCPNetworkOpts{ NetworkID: "1ae075ca-708b-4e66-b4a7-b7698632f05f", } - err := agents.ScheduleDHCPNetwork(context.TODO(), fake.ServiceClient(), "43583cf5-472e-4dc8-af5b-6aed4c94ee3a", opts).ExtractErr() + err := agents.ScheduleDHCPNetwork(context.TODO(), fake.ServiceClient(fakeServer), "43583cf5-472e-4dc8-af5b-6aed4c94ee3a", opts).ExtractErr() th.AssertNoErr(t, err) } func TestRemoveDHCPNetwork(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/agents/43583cf5-472e-4dc8-af5b-6aed4c94ee3a/dhcp-networks/1ae075ca-708b-4e66-b4a7-b7698632f05f", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/agents/43583cf5-472e-4dc8-af5b-6aed4c94ee3a/dhcp-networks/1ae075ca-708b-4e66-b4a7-b7698632f05f", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Accept", "application/json") @@ -205,17 +205,17 @@ func TestRemoveDHCPNetwork(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - err := agents.RemoveDHCPNetwork(context.TODO(), fake.ServiceClient(), "43583cf5-472e-4dc8-af5b-6aed4c94ee3a", "1ae075ca-708b-4e66-b4a7-b7698632f05f").ExtractErr() + err := agents.RemoveDHCPNetwork(context.TODO(), fake.ServiceClient(fakeServer), "43583cf5-472e-4dc8-af5b-6aed4c94ee3a", "1ae075ca-708b-4e66-b4a7-b7698632f05f").ExtractErr() th.AssertNoErr(t, err) } func TestListBGPSpeakers(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() agentID := "30d76012-46de-4215-aaa1-a1630d01d891" - th.Mux.HandleFunc("/v2.0/agents/"+agentID+"/bgp-drinstances", + fakeServer.Mux.HandleFunc("/v2.0/agents/"+agentID+"/bgp-drinstances", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) @@ -223,22 +223,22 @@ func TestListBGPSpeakers(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListBGPSpeakersResult) + fmt.Fprint(w, ListBGPSpeakersResult) }) count := 0 - err := agents.ListBGPSpeakers(fake.ServiceClient(), agentID).EachPage( + err := agents.ListBGPSpeakers(fake.ServiceClient(fakeServer), agentID).EachPage( context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := agents.ExtractBGPSpeakers(page) th.AssertNoErr(t, err) - th.AssertEquals(t, len(actual), 1) - th.AssertEquals(t, actual[0].ID, "cab00464-284d-4251-9798-2b27db7b1668") - th.AssertEquals(t, actual[0].Name, "gophercloud-testing-speaker") - th.AssertEquals(t, actual[0].LocalAS, 12345) - th.AssertEquals(t, actual[0].IPVersion, 4) + th.AssertEquals(t, 1, len(actual)) + th.AssertEquals(t, "cab00464-284d-4251-9798-2b27db7b1668", actual[0].ID) + th.AssertEquals(t, "gophercloud-testing-speaker", actual[0].Name) + th.AssertEquals(t, 12345, actual[0].LocalAS) + th.AssertEquals(t, 4, actual[0].IPVersion) return true, nil }) th.AssertNoErr(t, err) @@ -248,13 +248,13 @@ func TestListBGPSpeakers(t *testing.T) { } func TestScheduleBGPSpeaker(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() agentID := "30d76012-46de-4215-aaa1-a1630d01d891" speakerID := "8edb2c68-0654-49a9-b3fe-030f92e3ddf6" - th.Mux.HandleFunc("/v2.0/agents/"+agentID+"/bgp-drinstances", + fakeServer.Mux.HandleFunc("/v2.0/agents/"+agentID+"/bgp-drinstances", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) @@ -268,18 +268,18 @@ func TestScheduleBGPSpeaker(t *testing.T) { var opts agents.ScheduleBGPSpeakerOpts opts.SpeakerID = speakerID - err := agents.ScheduleBGPSpeaker(context.TODO(), fake.ServiceClient(), agentID, opts).ExtractErr() + err := agents.ScheduleBGPSpeaker(context.TODO(), fake.ServiceClient(fakeServer), agentID, opts).ExtractErr() th.AssertNoErr(t, err) } func TestRemoveBGPSpeaker(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() agentID := "30d76012-46de-4215-aaa1-a1630d01d891" speakerID := "8edb2c68-0654-49a9-b3fe-030f92e3ddf6" - th.Mux.HandleFunc("/v2.0/agents/"+agentID+"/bgp-drinstances/"+speakerID, + fakeServer.Mux.HandleFunc("/v2.0/agents/"+agentID+"/bgp-drinstances/"+speakerID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) @@ -289,27 +289,27 @@ func TestRemoveBGPSpeaker(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - err := agents.RemoveBGPSpeaker(context.TODO(), fake.ServiceClient(), agentID, speakerID).ExtractErr() + err := agents.RemoveBGPSpeaker(context.TODO(), fake.ServiceClient(fakeServer), agentID, speakerID).ExtractErr() th.AssertNoErr(t, err) } func TestListDRAgentHostingBGPSpeakers(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() speakerID := "3f511b1b-d541-45f1-aa98-2e44e8183d4c" - th.Mux.HandleFunc("/v2.0/bgp-speakers/"+speakerID+"/bgp-dragents", + fakeServer.Mux.HandleFunc("/v2.0/bgp-speakers/"+speakerID+"/bgp-dragents", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListDRAgentHostingBGPSpeakersResult) + fmt.Fprint(w, ListDRAgentHostingBGPSpeakersResult) }) count := 0 - err := agents.ListDRAgentHostingBGPSpeakers(fake.ServiceClient(), speakerID).EachPage( + err := agents.ListDRAgentHostingBGPSpeakers(fake.ServiceClient(fakeServer), speakerID).EachPage( context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ @@ -332,20 +332,20 @@ func TestListDRAgentHostingBGPSpeakers(t *testing.T) { } func TestListL3Routers(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/agents/43583cf5-472e-4dc8-af5b-6aed4c94ee3a/l3-routers", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/agents/43583cf5-472e-4dc8-af5b-6aed4c94ee3a/l3-routers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, AgentL3RoutersListResult) + fmt.Fprint(w, AgentL3RoutersListResult) }) - s, err := agents.ListL3Routers(context.TODO(), fake.ServiceClient(), "43583cf5-472e-4dc8-af5b-6aed4c94ee3a").Extract() + s, err := agents.ListL3Routers(context.TODO(), fake.ServiceClient(fakeServer), "43583cf5-472e-4dc8-af5b-6aed4c94ee3a").Extract() th.AssertNoErr(t, err) routes := []routers.Route{ @@ -355,7 +355,7 @@ func TestListL3Routers(t *testing.T) { }, } - var snat bool = true + var snat = true gw := routers.GatewayInfo{ EnableSNAT: &snat, NetworkID: "ae34051f-aa6c-4c75-abf5-50dc9ac99ef3", @@ -373,27 +373,27 @@ func TestListL3Routers(t *testing.T) { } var nilSlice []string - th.AssertEquals(t, len(s), 2) - th.AssertEquals(t, s[0].ID, "915a14a6-867b-4af7-83d1-70efceb146f9") - th.AssertEquals(t, s[0].AdminStateUp, true) - th.AssertEquals(t, s[0].ProjectID, "0bd18306d801447bb457a46252d82d13") - th.AssertEquals(t, s[0].Name, "router2") - th.AssertEquals(t, s[0].Status, "ACTIVE") - th.AssertEquals(t, s[0].TenantID, "0bd18306d801447bb457a46252d82d13") - th.AssertDeepEquals(t, s[0].AvailabilityZoneHints, []string{}) + th.AssertEquals(t, 2, len(s)) + th.AssertEquals(t, "915a14a6-867b-4af7-83d1-70efceb146f9", s[0].ID) + th.AssertTrue(t, s[0].AdminStateUp) + th.AssertEquals(t, "0bd18306d801447bb457a46252d82d13", s[0].ProjectID) + th.AssertEquals(t, "router2", s[0].Name) + th.AssertEquals(t, "ACTIVE", s[0].Status) + th.AssertEquals(t, "0bd18306d801447bb457a46252d82d13", s[0].TenantID) + th.AssertDeepEquals(t, []string{}, s[0].AvailabilityZoneHints) th.AssertDeepEquals(t, s[0].Routes, routes) th.AssertDeepEquals(t, s[0].GatewayInfo, gw) th.AssertDeepEquals(t, s[0].Tags, nilSlice) - th.AssertEquals(t, s[1].ID, "f8a44de0-fc8e-45df-93c7-f79bf3b01c95") - th.AssertEquals(t, s[1].Name, "router1") + th.AssertEquals(t, "f8a44de0-fc8e-45df-93c7-f79bf3b01c95", s[1].ID) + th.AssertEquals(t, "router1", s[1].Name) } func TestScheduleL3Router(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/agents/43583cf5-472e-4dc8-af5b-6aed4c94ee3a/l3-routers", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/agents/43583cf5-472e-4dc8-af5b-6aed4c94ee3a/l3-routers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -407,15 +407,15 @@ func TestScheduleL3Router(t *testing.T) { opts := &agents.ScheduleL3RouterOpts{ RouterID: "43e66290-79a4-415d-9eb9-7ff7919839e1", } - err := agents.ScheduleL3Router(context.TODO(), fake.ServiceClient(), "43583cf5-472e-4dc8-af5b-6aed4c94ee3a", opts).ExtractErr() + err := agents.ScheduleL3Router(context.TODO(), fake.ServiceClient(fakeServer), "43583cf5-472e-4dc8-af5b-6aed4c94ee3a", opts).ExtractErr() th.AssertNoErr(t, err) } func TestRemoveL3Router(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/agents/43583cf5-472e-4dc8-af5b-6aed4c94ee3a/l3-routers/43e66290-79a4-415d-9eb9-7ff7919839e1", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/agents/43583cf5-472e-4dc8-af5b-6aed4c94ee3a/l3-routers/43e66290-79a4-415d-9eb9-7ff7919839e1", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Accept", "application/json") @@ -424,6 +424,6 @@ func TestRemoveL3Router(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - err := agents.RemoveL3Router(context.TODO(), fake.ServiceClient(), "43583cf5-472e-4dc8-af5b-6aed4c94ee3a", "43e66290-79a4-415d-9eb9-7ff7919839e1").ExtractErr() + err := agents.RemoveL3Router(context.TODO(), fake.ServiceClient(fakeServer), "43583cf5-472e-4dc8-af5b-6aed4c94ee3a", "43e66290-79a4-415d-9eb9-7ff7919839e1").ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/networking/v2/extensions/attributestags/testing/requests_test.go b/openstack/networking/v2/extensions/attributestags/testing/requests_test.go index e9dc3498fb..7158693d02 100644 --- a/openstack/networking/v2/extensions/attributestags/testing/requests_test.go +++ b/openstack/networking/v2/extensions/attributestags/testing/requests_test.go @@ -12,10 +12,10 @@ import ( ) func TestReplaceAll(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks/fakeid/tags", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/fakeid/tags", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -25,43 +25,43 @@ func TestReplaceAll(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, attributestagsReplaceAllResult) + fmt.Fprint(w, attributestagsReplaceAllResult) }) opts := attributestags.ReplaceAllOpts{ Tags: []string{"abc", "xyz"}, } - res, err := attributestags.ReplaceAll(context.TODO(), fake.ServiceClient(), "networks", "fakeid", opts).Extract() + res, err := attributestags.ReplaceAll(context.TODO(), fake.ServiceClient(fakeServer), "networks", "fakeid", opts).Extract() th.AssertNoErr(t, err) - th.AssertDeepEquals(t, res, []string{"abc", "xyz"}) + th.AssertDeepEquals(t, []string{"abc", "xyz"}, res) } func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks/fakeid/tags", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/fakeid/tags", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, attributestagsListResult) + fmt.Fprint(w, attributestagsListResult) }) - res, err := attributestags.List(context.TODO(), fake.ServiceClient(), "networks", "fakeid").Extract() + res, err := attributestags.List(context.TODO(), fake.ServiceClient(fakeServer), "networks", "fakeid").Extract() th.AssertNoErr(t, err) - th.AssertDeepEquals(t, res, []string{"abc", "xyz"}) + th.AssertDeepEquals(t, []string{"abc", "xyz"}, res) } func TestDeleteAll(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks/fakeid/tags", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/fakeid/tags", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) @@ -69,15 +69,15 @@ func TestDeleteAll(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - err := attributestags.DeleteAll(context.TODO(), fake.ServiceClient(), "networks", "fakeid").ExtractErr() + err := attributestags.DeleteAll(context.TODO(), fake.ServiceClient(fakeServer), "networks", "fakeid").ExtractErr() th.AssertNoErr(t, err) } func TestAdd(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks/fakeid/tags/atag", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/fakeid/tags/atag", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) @@ -85,15 +85,15 @@ func TestAdd(t *testing.T) { w.WriteHeader(http.StatusCreated) }) - err := attributestags.Add(context.TODO(), fake.ServiceClient(), "networks", "fakeid", "atag").ExtractErr() + err := attributestags.Add(context.TODO(), fake.ServiceClient(fakeServer), "networks", "fakeid", "atag").ExtractErr() th.AssertNoErr(t, err) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks/fakeid/tags/atag", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/fakeid/tags/atag", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) @@ -101,15 +101,15 @@ func TestDelete(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - err := attributestags.Delete(context.TODO(), fake.ServiceClient(), "networks", "fakeid", "atag").ExtractErr() + err := attributestags.Delete(context.TODO(), fake.ServiceClient(fakeServer), "networks", "fakeid", "atag").ExtractErr() th.AssertNoErr(t, err) } func TestConfirmTrue(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks/fakeid/tags/atag", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/fakeid/tags/atag", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) @@ -117,16 +117,16 @@ func TestConfirmTrue(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - exists, err := attributestags.Confirm(context.TODO(), fake.ServiceClient(), "networks", "fakeid", "atag").Extract() + exists, err := attributestags.Confirm(context.TODO(), fake.ServiceClient(fakeServer), "networks", "fakeid", "atag").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, true, exists) + th.AssertTrue(t, exists) } func TestConfirmFalse(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks/fakeid/tags/atag", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/fakeid/tags/atag", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) @@ -134,6 +134,6 @@ func TestConfirmFalse(t *testing.T) { w.WriteHeader(http.StatusNotFound) }) - exists, _ := attributestags.Confirm(context.TODO(), fake.ServiceClient(), "networks", "fakeid", "atag").Extract() - th.AssertEquals(t, false, exists) + exists, _ := attributestags.Confirm(context.TODO(), fake.ServiceClient(fakeServer), "networks", "fakeid", "atag").Extract() + th.AssertFalse(t, exists) } diff --git a/openstack/networking/v2/extensions/bgp/peers/requests.go b/openstack/networking/v2/extensions/bgp/peers/requests.go index eef38c0b79..d9341f7cf5 100644 --- a/openstack/networking/v2/extensions/bgp/peers/requests.go +++ b/openstack/networking/v2/extensions/bgp/peers/requests.go @@ -32,9 +32,10 @@ type CreateOptsBuilder interface { type CreateOpts struct { AuthType string `json:"auth_type"` RemoteAS int `json:"remote_as"` - Name string `json:"name"` + Name string `json:"name,omitempty"` Password string `json:"password,omitempty"` PeerIP string `json:"peer_ip"` + TenantID string `json:"tenant_id,omitempty"` } // ToPeerCreateMap builds a request body from CreateOpts. @@ -43,7 +44,7 @@ func (opts CreateOpts) ToPeerCreateMap() (map[string]any, error) { } // Create a BGP Peer -func Create(ctx context.Context, c *gophercloud.ServiceClient, opts CreateOpts) (r CreateResult) { +func Create(ctx context.Context, c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { b, err := opts.ToPeerCreateMap() if err != nil { r.Err = err @@ -69,8 +70,8 @@ type UpdateOptsBuilder interface { // UpdateOpts represents options used to update a BGP Peer. type UpdateOpts struct { - Name string `json:"name,omitempty"` - Password string `json:"password,omitempty"` + Name *string `json:"name,omitempty"` + Password *string `json:"password,omitempty"` } // ToPeerUpdateMap builds a request body from UpdateOpts. @@ -79,7 +80,7 @@ func (opts UpdateOpts) ToPeerUpdateMap() (map[string]any, error) { } // Update accept a BGP Peer ID and an UpdateOpts and update the BGP Peer -func Update(ctx context.Context, c *gophercloud.ServiceClient, bgpPeerID string, opts UpdateOpts) (r UpdateResult) { +func Update(ctx context.Context, c *gophercloud.ServiceClient, bgpPeerID string, opts UpdateOptsBuilder) (r UpdateResult) { b, err := opts.ToPeerUpdateMap() if err != nil { r.Err = err diff --git a/openstack/networking/v2/extensions/bgp/peers/results.go b/openstack/networking/v2/extensions/bgp/peers/results.go index afd771b1e4..ac07c91c81 100644 --- a/openstack/networking/v2/extensions/bgp/peers/results.go +++ b/openstack/networking/v2/extensions/bgp/peers/results.go @@ -19,7 +19,7 @@ func (r commonResult) Extract() (*BGPPeer, error) { } func (r commonResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, jroot) + return r.ExtractIntoStructPtr(v, jroot) } // BGP peer @@ -72,7 +72,7 @@ func ExtractBGPPeers(r pagination.Page) ([]BGPPeer, error) { } func ExtractBGPPeersInto(r pagination.Page, v any) error { - return r.(BGPPeerPage).Result.ExtractIntoSlicePtr(v, "bgp_peers") + return r.(BGPPeerPage).ExtractIntoSlicePtr(v, "bgp_peers") } // GetResult represents the result of a get operation. Call its Extract diff --git a/openstack/networking/v2/extensions/bgp/peers/testing/requests_test.go b/openstack/networking/v2/extensions/bgp/peers/testing/requests_test.go index 3ec3faa391..d35e79da93 100644 --- a/openstack/networking/v2/extensions/bgp/peers/testing/requests_test.go +++ b/openstack/networking/v2/extensions/bgp/peers/testing/requests_test.go @@ -13,21 +13,21 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/bgp-peers", + fakeServer.Mux.HandleFunc("/v2.0/bgp-peers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListBGPPeersResult) + fmt.Fprint(w, ListBGPPeersResult) }) count := 0 - err := peers.List(fake.ServiceClient()).EachPage( + err := peers.List(fake.ServiceClient(fakeServer)).EachPage( context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ @@ -45,28 +45,28 @@ func TestList(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() bgpPeerID := "afacc0e8-6b66-44e4-be53-a1ef16033ceb" - th.Mux.HandleFunc("/v2.0/bgp-peers/"+bgpPeerID, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgp-peers/"+bgpPeerID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetBGPPeerResult) + fmt.Fprint(w, GetBGPPeerResult) }) - s, err := peers.Get(context.TODO(), fake.ServiceClient(), bgpPeerID).Extract() + s, err := peers.Get(context.TODO(), fake.ServiceClient(fakeServer), bgpPeerID).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, *s, BGPPeer1) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/bgp-peers", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgp-peers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -74,7 +74,7 @@ func TestCreate(t *testing.T) { th.TestJSONRequest(t, r, CreateRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) var opts peers.CreateOpts @@ -84,7 +84,7 @@ func TestCreate(t *testing.T) { opts.Name = "gophercloud-testing-bgp-peer" opts.PeerIP = "192.168.0.1" - r, err := peers.Create(context.TODO(), fake.ServiceClient(), opts).Extract() + r, err := peers.Create(context.TODO(), fake.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, r.AuthType, opts.AuthType) th.AssertEquals(t, r.RemoteAS, opts.RemoteAS) @@ -93,10 +93,10 @@ func TestCreate(t *testing.T) { func TestDelete(t *testing.T) { bgpPeerID := "afacc0e8-6b66-44e4-be53-a1ef16033ceb" - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/bgp-peers/"+bgpPeerID, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgp-peers/"+bgpPeerID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Accept", "application/json") @@ -105,16 +105,16 @@ func TestDelete(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - err := peers.Delete(context.TODO(), fake.ServiceClient(), bgpPeerID).ExtractErr() + err := peers.Delete(context.TODO(), fake.ServiceClient(fakeServer), bgpPeerID).ExtractErr() th.AssertNoErr(t, err) } func TestUpdate(t *testing.T) { bgpPeerID := "afacc0e8-6b66-44e4-be53-a1ef16033ceb" - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/bgp-peers/"+bgpPeerID, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgp-peers/"+bgpPeerID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -124,14 +124,17 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateBGPPeerResponse) + fmt.Fprint(w, UpdateBGPPeerResponse) }) - var opts peers.UpdateOpts - opts.Name = "test-rename-bgp-peer" - opts.Password = "superStrong" + name := "test-rename-bgp-peer" + password := "superStrong" + opts := peers.UpdateOpts{ + Name: &name, + Password: &password, + } - r, err := peers.Update(context.TODO(), fake.ServiceClient(), bgpPeerID, opts).Extract() + r, err := peers.Update(context.TODO(), fake.ServiceClient(fakeServer), bgpPeerID, opts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, r.Name, opts.Name) + th.AssertEquals(t, r.Name, *opts.Name) } diff --git a/openstack/networking/v2/extensions/bgp/speakers/requests.go b/openstack/networking/v2/extensions/bgp/speakers/requests.go index 4c352c2cc3..91694ec729 100644 --- a/openstack/networking/v2/extensions/bgp/speakers/requests.go +++ b/openstack/networking/v2/extensions/bgp/speakers/requests.go @@ -24,12 +24,12 @@ func Get(ctx context.Context, c *gophercloud.ServiceClient, id string) (r GetRes // CreateOpts represents options used to create a BGP Speaker. type CreateOpts struct { - Name string `json:"name"` - IPVersion int `json:"ip_version"` - AdvertiseFloatingIPHostRoutes bool `json:"advertise_floating_ip_host_routes"` - AdvertiseTenantNetworks bool `json:"advertise_tenant_networks"` - LocalAS string `json:"local_as"` - Networks []string `json:"networks,omitempty"` + Name string `json:"name,omitempty"` + IPVersion int `json:"ip_version,omitempty"` + AdvertiseFloatingIPHostRoutes *bool `json:"advertise_floating_ip_host_routes,omitempty"` + AdvertiseTenantNetworks *bool `json:"advertise_tenant_networks,omitempty"` + LocalAS int `json:"local_as"` + TenantID string `json:"tenant_id,omitempty"` } // CreateOptsBuilder declare a function that build CreateOpts into a Create request body. @@ -43,7 +43,7 @@ func (opts CreateOpts) ToSpeakerCreateMap() (map[string]any, error) { } // Create accepts a CreateOpts and create a BGP Speaker. -func Create(ctx context.Context, c *gophercloud.ServiceClient, opts CreateOpts) (r CreateResult) { +func Create(ctx context.Context, c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { b, err := opts.ToSpeakerCreateMap() if err != nil { r.Err = err @@ -63,9 +63,9 @@ func Delete(ctx context.Context, c *gophercloud.ServiceClient, speakerID string) // UpdateOpts represents options used to update a BGP Speaker. type UpdateOpts struct { - Name string `json:"name,omitempty"` - AdvertiseFloatingIPHostRoutes bool `json:"advertise_floating_ip_host_routes"` - AdvertiseTenantNetworks bool `json:"advertise_tenant_networks"` + Name *string `json:"name,omitempty"` + AdvertiseFloatingIPHostRoutes *bool `json:"advertise_floating_ip_host_routes,omitempty"` + AdvertiseTenantNetworks *bool `json:"advertise_tenant_networks,omitempty"` } // ToSpeakerUpdateMap build a request body from UpdateOpts @@ -142,7 +142,7 @@ func RemoveBGPPeer(ctx context.Context, c *gophercloud.ServiceClient, bgpSpeaker r.Err = err return } - resp, err := c.Put(ctx, removeBGPPeerURL(c, bgpSpeakerID), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := c.Put(ctx, removeBGPPeerURL(c, bgpSpeakerID), b, nil, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) @@ -207,7 +207,7 @@ func RemoveGatewayNetwork(ctx context.Context, c *gophercloud.ServiceClient, bgp r.Err = err return } - resp, err := c.Put(ctx, removeGatewayNetworkURL(c, bgpSpeakerID), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := c.Put(ctx, removeGatewayNetworkURL(c, bgpSpeakerID), b, nil, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) diff --git a/openstack/networking/v2/extensions/bgp/speakers/results.go b/openstack/networking/v2/extensions/bgp/speakers/results.go index 93c967ca47..3c8b312537 100644 --- a/openstack/networking/v2/extensions/bgp/speakers/results.go +++ b/openstack/networking/v2/extensions/bgp/speakers/results.go @@ -19,7 +19,7 @@ func (r commonResult) Extract() (*BGPSpeaker, error) { } func (r commonResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, jroot) + return r.ExtractIntoStructPtr(v, jroot) } // BGPSpeaker BGP Speaker @@ -84,7 +84,7 @@ func ExtractBGPSpeakers(r pagination.Page) ([]BGPSpeaker, error) { // a list of BGPSpeaker and the later should be used to store the result that would be // extracted from the former. func ExtractBGPSpeakersInto(r pagination.Page, v any) error { - return r.(BGPSpeakerPage).Result.ExtractIntoSlicePtr(v, "bgp_speakers") + return r.(BGPSpeakerPage).ExtractIntoSlicePtr(v, "bgp_speakers") } // GetResult represents the result of a get operation. Call its Extract @@ -124,7 +124,7 @@ func (r AddBGPPeerResult) Extract() (*AddBGPPeerOpts, error) { } func (r AddBGPPeerResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "") + return r.ExtractIntoStructPtr(v, "") } // RemoveBGPPeerResult represent the response of the PUT /v2.0/bgp-speakers/{bgp-speaker-id}/remove-bgp-peer @@ -167,7 +167,7 @@ func ExtractAdvertisedRoutes(r pagination.Page) ([]AdvertisedRoute, error) { // ExtractAdvertisedRoutesInto extract the advertised routes from the first param into the 2nd func ExtractAdvertisedRoutesInto(r pagination.Page, v any) error { - return r.(AdvertisedRoutePage).Result.ExtractIntoSlicePtr(v, "advertised_routes") + return r.(AdvertisedRoutePage).ExtractIntoSlicePtr(v, "advertised_routes") } // AddGatewayNetworkResult represents the data that would be PUT to diff --git a/openstack/networking/v2/extensions/bgp/speakers/testing/fixture.go b/openstack/networking/v2/extensions/bgp/speakers/testing/fixture.go index de346783c3..21c5deeef8 100644 --- a/openstack/networking/v2/extensions/bgp/speakers/testing/fixture.go +++ b/openstack/networking/v2/extensions/bgp/speakers/testing/fixture.go @@ -67,7 +67,7 @@ const CreateRequest = ` "advertise_floating_ip_host_routes": false, "advertise_tenant_networks": true, "ip_version": 6, - "local_as": "2000", + "local_as": 2000, "name": "gophercloud-testing-bgp-speaker" } } diff --git a/openstack/networking/v2/extensions/bgp/speakers/testing/requests_test.go b/openstack/networking/v2/extensions/bgp/speakers/testing/requests_test.go index 9e8df52b0c..74dd875410 100644 --- a/openstack/networking/v2/extensions/bgp/speakers/testing/requests_test.go +++ b/openstack/networking/v2/extensions/bgp/speakers/testing/requests_test.go @@ -3,7 +3,6 @@ package testing import ( "context" "fmt" - "io" "net/http" "testing" @@ -14,21 +13,21 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/bgp-speakers", + fakeServer.Mux.HandleFunc("/v2.0/bgp-speakers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListBGPSpeakerResult) + fmt.Fprint(w, ListBGPSpeakerResult) }) count := 0 - err := speakers.List(fake.ServiceClient()).EachPage( + err := speakers.List(fake.ServiceClient(fakeServer)).EachPage( context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ @@ -46,28 +45,28 @@ func TestList(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() bgpSpeakerID := "ab01ade1-ae62-43c9-8a1f-3c24225b96d8" - th.Mux.HandleFunc("/v2.0/bgp-speakers/"+bgpSpeakerID, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgp-speakers/"+bgpSpeakerID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetBGPSpeakerResult) + fmt.Fprint(w, GetBGPSpeakerResult) }) - s, err := speakers.Get(context.TODO(), fake.ServiceClient(), bgpSpeakerID).Extract() + s, err := speakers.Get(context.TODO(), fake.ServiceClient(fakeServer), bgpSpeakerID).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, *s, BGPSpeaker1) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/bgp-speakers", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgp-speakers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -75,33 +74,33 @@ func TestCreate(t *testing.T) { th.TestJSONRequest(t, r, CreateRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) + iTrue := true opts := speakers.CreateOpts{ IPVersion: 6, - AdvertiseFloatingIPHostRoutes: false, - AdvertiseTenantNetworks: true, + AdvertiseFloatingIPHostRoutes: new(bool), + AdvertiseTenantNetworks: &iTrue, Name: "gophercloud-testing-bgp-speaker", - LocalAS: "2000", - Networks: []string{}, + LocalAS: 2000, } - r, err := speakers.Create(context.TODO(), fake.ServiceClient(), opts).Extract() + r, err := speakers.Create(context.TODO(), fake.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, r.Name, opts.Name) - th.AssertEquals(t, r.LocalAS, 2000) - th.AssertEquals(t, len(r.Networks), 0) + th.AssertEquals(t, 2000, r.LocalAS) + th.AssertEquals(t, 0, len(r.Networks)) th.AssertEquals(t, r.IPVersion, opts.IPVersion) - th.AssertEquals(t, r.AdvertiseFloatingIPHostRoutes, opts.AdvertiseFloatingIPHostRoutes) - th.AssertEquals(t, r.AdvertiseTenantNetworks, opts.AdvertiseTenantNetworks) + th.AssertEquals(t, r.AdvertiseFloatingIPHostRoutes, *opts.AdvertiseFloatingIPHostRoutes) + th.AssertEquals(t, r.AdvertiseTenantNetworks, *opts.AdvertiseTenantNetworks) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() bgpSpeakerID := "ab01ade1-ae62-43c9-8a1f-3c24225b96d8" - th.Mux.HandleFunc("/v2.0/bgp-speakers/"+bgpSpeakerID, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgp-speakers/"+bgpSpeakerID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Accept", "application/json") @@ -110,23 +109,24 @@ func TestDelete(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - err := speakers.Delete(context.TODO(), fake.ServiceClient(), bgpSpeakerID).ExtractErr() + err := speakers.Delete(context.TODO(), fake.ServiceClient(fakeServer), bgpSpeakerID).ExtractErr() th.AssertNoErr(t, err) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() bgpSpeakerID := "ab01ade1-ae62-43c9-8a1f-3c24225b96d8" - th.Mux.HandleFunc("/v2.0/bgp-speakers/"+bgpSpeakerID, func(w http.ResponseWriter, r *http.Request) { - if r.Method == "GET" { + fakeServer.Mux.HandleFunc("/v2.0/bgp-speakers/"+bgpSpeakerID, func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "GET": th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetBGPSpeakerResult) - } else if r.Method == "PUT" { + fmt.Fprint(w, GetBGPSpeakerResult) + case "PUT": th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -135,32 +135,34 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateBGPSpeakerResponse) - } else { + fmt.Fprint(w, UpdateBGPSpeakerResponse) + default: panic("Unexpected Request") } }) + name := "testing-bgp-speaker" + iTrue := true opts := speakers.UpdateOpts{ - Name: "testing-bgp-speaker", - AdvertiseTenantNetworks: false, - AdvertiseFloatingIPHostRoutes: true, + Name: &name, + AdvertiseTenantNetworks: new(bool), + AdvertiseFloatingIPHostRoutes: &iTrue, } - r, err := speakers.Update(context.TODO(), fake.ServiceClient(), bgpSpeakerID, opts).Extract() + r, err := speakers.Update(context.TODO(), fake.ServiceClient(fakeServer), bgpSpeakerID, opts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, r.Name, opts.Name) - th.AssertEquals(t, r.AdvertiseTenantNetworks, opts.AdvertiseTenantNetworks) - th.AssertEquals(t, r.AdvertiseFloatingIPHostRoutes, opts.AdvertiseFloatingIPHostRoutes) + th.AssertEquals(t, r.Name, *opts.Name) + th.AssertEquals(t, r.AdvertiseTenantNetworks, *opts.AdvertiseTenantNetworks) + th.AssertEquals(t, r.AdvertiseFloatingIPHostRoutes, *opts.AdvertiseFloatingIPHostRoutes) } func TestAddBGPPeer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() bgpSpeakerID := "ab01ade1-ae62-43c9-8a1f-3c24225b96d8" bgpPeerID := "f5884c7c-71d5-43a3-88b4-1742e97674aa" - th.Mux.HandleFunc("/v2.0/bgp-speakers/"+bgpSpeakerID+"/add_bgp_peer", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgp-speakers/"+bgpSpeakerID+"/add_bgp_peer", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -169,22 +171,22 @@ func TestAddBGPPeer(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, AddRemoveBGPPeerJSON) + fmt.Fprint(w, AddRemoveBGPPeerJSON) }) opts := speakers.AddBGPPeerOpts{BGPPeerID: bgpPeerID} - r, err := speakers.AddBGPPeer(context.TODO(), fake.ServiceClient(), bgpSpeakerID, opts).Extract() + r, err := speakers.AddBGPPeer(context.TODO(), fake.ServiceClient(fakeServer), bgpSpeakerID, opts).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, bgpPeerID, r.BGPPeerID) } func TestRemoveBGPPeer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() bgpSpeakerID := "ab01ade1-ae62-43c9-8a1f-3c24225b96d8" bgpPeerID := "f5884c7c-71d5-43a3-88b4-1742e97674aa" - th.Mux.HandleFunc("/v2.0/bgp-speakers/"+bgpSpeakerID+"/remove_bgp_peer", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgp-speakers/"+bgpSpeakerID+"/remove_bgp_peer", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -194,25 +196,25 @@ func TestRemoveBGPPeer(t *testing.T) { }) opts := speakers.RemoveBGPPeerOpts{BGPPeerID: bgpPeerID} - err := speakers.RemoveBGPPeer(context.TODO(), fake.ServiceClient(), bgpSpeakerID, opts).ExtractErr() - th.AssertEquals(t, err, io.EOF) + err := speakers.RemoveBGPPeer(context.TODO(), fake.ServiceClient(fakeServer), bgpSpeakerID, opts).ExtractErr() + th.AssertNoErr(t, err) } func TestGetAdvertisedRoutes(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() bgpSpeakerID := "ab01ade1-ae62-43c9-8a1f-3c24225b96d8" - th.Mux.HandleFunc("/v2.0/bgp-speakers/"+bgpSpeakerID+"/get_advertised_routes", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgp-speakers/"+bgpSpeakerID+"/get_advertised_routes", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetAdvertisedRoutesResult) + fmt.Fprint(w, GetAdvertisedRoutesResult) }) count := 0 - err := speakers.GetAdvertisedRoutes(fake.ServiceClient(), bgpSpeakerID).EachPage( + err := speakers.GetAdvertisedRoutes(fake.ServiceClient(fakeServer), bgpSpeakerID).EachPage( context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ @@ -228,7 +230,7 @@ func TestGetAdvertisedRoutes(t *testing.T) { {NextHop: "172.17.128.218", Destination: "172.17.129.0/27"}, {NextHop: "172.17.128.231", Destination: "172.17.129.160/27"}, } - th.CheckDeepEquals(t, count, 1) + th.CheckDeepEquals(t, 1, count) th.CheckDeepEquals(t, expected, actual) return true, nil }) @@ -236,12 +238,12 @@ func TestGetAdvertisedRoutes(t *testing.T) { } func TestAddGatewayNetwork(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() bgpSpeakerID := "ab01ade1-ae62-43c9-8a1f-3c24225b96d8" networkID := "ac13bb26-6219-49c3-a880-08847f6830b7" - th.Mux.HandleFunc("/v2.0/bgp-speakers/"+bgpSpeakerID+"/add_gateway_network", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgp-speakers/"+bgpSpeakerID+"/add_gateway_network", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -250,22 +252,22 @@ func TestAddGatewayNetwork(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, AddRemoveGatewayNetworkJSON) + fmt.Fprint(w, AddRemoveGatewayNetworkJSON) }) opts := speakers.AddGatewayNetworkOpts{NetworkID: networkID} - r, err := speakers.AddGatewayNetwork(context.TODO(), fake.ServiceClient(), bgpSpeakerID, opts).Extract() + r, err := speakers.AddGatewayNetwork(context.TODO(), fake.ServiceClient(fakeServer), bgpSpeakerID, opts).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, r.NetworkID, networkID) } func TestRemoveGatewayNetwork(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() bgpSpeakerID := "ab01ade1-ae62-43c9-8a1f-3c24225b96d8" networkID := "ac13bb26-6219-49c3-a880-08847f6830b7" - th.Mux.HandleFunc("/v2.0/bgp-speakers/"+bgpSpeakerID+"/remove_gateway_network", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgp-speakers/"+bgpSpeakerID+"/remove_gateway_network", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -274,10 +276,10 @@ func TestRemoveGatewayNetwork(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, "") + fmt.Fprint(w, "") }) opts := speakers.RemoveGatewayNetworkOpts{NetworkID: networkID} - err := speakers.RemoveGatewayNetwork(context.TODO(), fake.ServiceClient(), bgpSpeakerID, opts).ExtractErr() - th.AssertEquals(t, err, io.EOF) + err := speakers.RemoveGatewayNetwork(context.TODO(), fake.ServiceClient(fakeServer), bgpSpeakerID, opts).ExtractErr() + th.AssertNoErr(t, err) } diff --git a/openstack/networking/v2/extensions/bgpvpns/requests.go b/openstack/networking/v2/extensions/bgpvpns/requests.go index 587961edee..9e6c02717f 100644 --- a/openstack/networking/v2/extensions/bgpvpns/requests.go +++ b/openstack/networking/v2/extensions/bgpvpns/requests.go @@ -43,7 +43,7 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { url += query return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { p := BGPVPNPage{pagination.MarkerPageBase{PageResult: r}} - p.MarkerPageBase.Owner = p + p.Owner = p return p }) } @@ -81,7 +81,7 @@ func (opts CreateOpts) ToBGPVPNCreateMap() (map[string]any, error) { } // Create a BGP VPN -func Create(ctx context.Context, c *gophercloud.ServiceClient, opts CreateOpts) (r CreateResult) { +func Create(ctx context.Context, c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { b, err := opts.ToBGPVPNCreateMap() if err != nil { r.Err = err @@ -121,7 +121,7 @@ func (opts UpdateOpts) ToBGPVPNUpdateMap() (map[string]any, error) { } // Update accept a BGP VPN ID and an UpdateOpts and update the BGP VPN -func Update(ctx context.Context, c *gophercloud.ServiceClient, id string, opts UpdateOpts) (r UpdateResult) { +func Update(ctx context.Context, c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { b, err := opts.ToBGPVPNUpdateMap() if err != nil { r.Err = err @@ -169,7 +169,7 @@ func ListNetworkAssociations(c *gophercloud.ServiceClient, id string, opts ListN url += query return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { p := NetworkAssociationPage{pagination.MarkerPageBase{PageResult: r}} - p.MarkerPageBase.Owner = p + p.Owner = p return p }) } @@ -258,7 +258,7 @@ func ListRouterAssociations(c *gophercloud.ServiceClient, id string, opts ListRo url += query return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { p := RouterAssociationPage{pagination.MarkerPageBase{PageResult: r}} - p.MarkerPageBase.Owner = p + p.Owner = p return p }) } @@ -380,7 +380,7 @@ func ListPortAssociations(c *gophercloud.ServiceClient, id string, opts ListPort url += query return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { p := PortAssociationPage{pagination.MarkerPageBase{PageResult: r}} - p.MarkerPageBase.Owner = p + p.Owner = p return p }) } diff --git a/openstack/networking/v2/extensions/bgpvpns/results.go b/openstack/networking/v2/extensions/bgpvpns/results.go index df0587568c..d0ff10aa87 100644 --- a/openstack/networking/v2/extensions/bgpvpns/results.go +++ b/openstack/networking/v2/extensions/bgpvpns/results.go @@ -23,7 +23,7 @@ func (r commonResult) Extract() (*BGPVPN, error) { } func (r commonResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "bgpvpn") + return r.ExtractIntoStructPtr(v, "bgpvpn") } // BGPVPN represents an MPLS network with which Neutron routers and/or networks @@ -89,7 +89,7 @@ type BGPVPNPage struct { } // NextPageURL generates the URL for the page of results after this one. -func (r BGPVPNPage) NextPageURL() (string, error) { +func (r BGPVPNPage) NextPageURL(endpointURL string) (string, error) { currentURL := r.URL mark, err := r.Owner.LastMarker() if err != nil { @@ -115,7 +115,7 @@ func (r BGPVPNPage) LastMarker() (string, error) { return invalidMarker, nil } - u, err := url.Parse(r.URL.String()) + u, err := url.Parse(r.String()) if err != nil { return invalidMarker, err } @@ -150,7 +150,7 @@ func ExtractBGPVPNs(r pagination.Page) ([]BGPVPN, error) { } func ExtractBGPVPNsInto(r pagination.Page, v any) error { - return r.(BGPVPNPage).Result.ExtractIntoSlicePtr(v, "bgpvpns") + return r.(BGPVPNPage).ExtractIntoSlicePtr(v, "bgpvpns") } // GetResult represents the result of a get operation. Call its Extract @@ -189,7 +189,7 @@ func (r commonNetworkAssociationResult) Extract() (*NetworkAssociation, error) { } func (r commonNetworkAssociationResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "network_association") + return r.ExtractIntoStructPtr(v, "network_association") } // NetworkAssociation represents a BGP VPN network association object. @@ -207,7 +207,7 @@ type NetworkAssociationPage struct { } // NextPageURL generates the URL for the page of results after this one. -func (r NetworkAssociationPage) NextPageURL() (string, error) { +func (r NetworkAssociationPage) NextPageURL(endpointURL string) (string, error) { currentURL := r.URL mark, err := r.Owner.LastMarker() if err != nil { @@ -233,7 +233,7 @@ func (r NetworkAssociationPage) LastMarker() (string, error) { return invalidMarker, nil } - u, err := url.Parse(r.URL.String()) + u, err := url.Parse(r.String()) if err != nil { return invalidMarker, err } @@ -264,7 +264,7 @@ func ExtractNetworkAssociations(r pagination.Page) ([]NetworkAssociation, error) } func ExtractNetworkAssociationsInto(r pagination.Page, v interface{}) error { - return r.(NetworkAssociationPage).Result.ExtractIntoSlicePtr(v, "network_associations") + return r.(NetworkAssociationPage).ExtractIntoSlicePtr(v, "network_associations") } // CreateNetworkAssociationResult represents the result of a create operation. Call its Extract @@ -297,7 +297,7 @@ func (r commonRouterAssociationResult) Extract() (*RouterAssociation, error) { } func (r commonRouterAssociationResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "router_association") + return r.ExtractIntoStructPtr(v, "router_association") } // RouterAssociation represents a BGP VPN router association object. @@ -316,7 +316,7 @@ type RouterAssociationPage struct { } // NextPageURL generates the URL for the page of results after this one. -func (r RouterAssociationPage) NextPageURL() (string, error) { +func (r RouterAssociationPage) NextPageURL(endpointURL string) (string, error) { currentURL := r.URL mark, err := r.Owner.LastMarker() if err != nil { @@ -342,7 +342,7 @@ func (r RouterAssociationPage) LastMarker() (string, error) { return invalidMarker, nil } - u, err := url.Parse(r.URL.String()) + u, err := url.Parse(r.String()) if err != nil { return invalidMarker, err } @@ -373,7 +373,7 @@ func ExtractRouterAssociations(r pagination.Page) ([]RouterAssociation, error) { } func ExtractRouterAssociationsInto(r pagination.Page, v interface{}) error { - return r.(RouterAssociationPage).Result.ExtractIntoSlicePtr(v, "router_associations") + return r.(RouterAssociationPage).ExtractIntoSlicePtr(v, "router_associations") } // CreateRouterAssociationResult represents the result of a create operation. Call its Extract @@ -412,7 +412,7 @@ func (r commonPortAssociationResult) Extract() (*PortAssociation, error) { } func (r commonPortAssociationResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "port_association") + return r.ExtractIntoStructPtr(v, "port_association") } // PortAssociation represents a BGP VPN port association object. @@ -432,7 +432,7 @@ type PortAssociationPage struct { } // NextPageURL generates the URL for the page of results after this one. -func (r PortAssociationPage) NextPageURL() (string, error) { +func (r PortAssociationPage) NextPageURL(endpointURL string) (string, error) { currentURL := r.URL mark, err := r.Owner.LastMarker() if err != nil { @@ -458,7 +458,7 @@ func (r PortAssociationPage) LastMarker() (string, error) { return invalidMarker, nil } - u, err := url.Parse(r.URL.String()) + u, err := url.Parse(r.String()) if err != nil { return invalidMarker, err } @@ -489,7 +489,7 @@ func ExtractPortAssociations(r pagination.Page) ([]PortAssociation, error) { } func ExtractPortAssociationsInto(r pagination.Page, v interface{}) error { - return r.(PortAssociationPage).Result.ExtractIntoSlicePtr(v, "port_associations") + return r.(PortAssociationPage).ExtractIntoSlicePtr(v, "port_associations") } // CreatePortAssociationResult represents the result of a create operation. Call its Extract diff --git a/openstack/networking/v2/extensions/bgpvpns/testing/requests_test.go b/openstack/networking/v2/extensions/bgpvpns/testing/requests_test.go index 859441f6f4..095fc5056c 100644 --- a/openstack/networking/v2/extensions/bgpvpns/testing/requests_test.go +++ b/openstack/networking/v2/extensions/bgpvpns/testing/requests_test.go @@ -13,8 +13,8 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() filterProjectID := []string{"b7549121395844bea941bb92feb3fad9"} fields := []string{"id", "name"} @@ -22,22 +22,24 @@ func TestList(t *testing.T) { Fields: fields, ProjectID: filterProjectID[0], } - th.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns", + fakeServer.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - r.ParseForm() + if err := r.ParseForm(); err != nil { + t.Errorf("Failed to parse request form %v", err) + } th.AssertDeepEquals(t, r.Form["fields"], fields) th.AssertDeepEquals(t, r.Form["project_id"], filterProjectID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListBGPVPNsResult) + fmt.Fprint(w, ListBGPVPNsResult) }) count := 0 - err := bgpvpns.List(fake.ServiceClient(), listOpts).EachPage( + err := bgpvpns.List(fake.ServiceClient(fakeServer), listOpts).EachPage( context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ @@ -56,28 +58,28 @@ func TestList(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() bgpVpnID := "460ac411-3dfb-45bb-8116-ed1a7233d143" - th.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetBGPVPNResult) + fmt.Fprint(w, GetBGPVPNResult) }) - r, err := bgpvpns.Get(context.TODO(), fake.ServiceClient(), bgpVpnID).Extract() + r, err := bgpvpns.Get(context.TODO(), fake.ServiceClient(fakeServer), bgpVpnID).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, GetBGPVPN, *r) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -85,7 +87,7 @@ func TestCreate(t *testing.T) { th.TestJSONRequest(t, r, CreateRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) opts := bgpvpns.CreateOpts{ @@ -108,17 +110,17 @@ func TestCreate(t *testing.T) { VNI: 1000, } - r, err := bgpvpns.Create(context.TODO(), fake.ServiceClient(), opts).Extract() + r, err := bgpvpns.Create(context.TODO(), fake.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, CreateBGPVPN, *r) } func TestDelete(t *testing.T) { bgpVpnID := "0f9d472a-908f-40f5-8574-b4e8a63ccbf0" - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Accept", "application/json") @@ -127,16 +129,16 @@ func TestDelete(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - err := bgpvpns.Delete(context.TODO(), fake.ServiceClient(), bgpVpnID).ExtractErr() + err := bgpvpns.Delete(context.TODO(), fake.ServiceClient(fakeServer), bgpVpnID).ExtractErr() th.AssertNoErr(t, err) } func TestUpdate(t *testing.T) { bgpVpnID := "4d627abf-06dd-45ab-920b-8e61422bb984" - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -146,7 +148,7 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateBGPVPNResponse) + fmt.Fprint(w, UpdateBGPVPNResponse) }) name := "foo" @@ -159,34 +161,36 @@ func TestUpdate(t *testing.T) { ExportTargets: &emptyTarget, } - r, err := bgpvpns.Update(context.TODO(), fake.ServiceClient(), bgpVpnID, opts).Extract() + r, err := bgpvpns.Update(context.TODO(), fake.ServiceClient(fakeServer), bgpVpnID, opts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, *opts.Name, r.Name) } func TestListNetworkAssociations(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() bgpVpnID := "460ac411-3dfb-45bb-8116-ed1a7233d143" fields := []string{"id", "name"} listOpts := bgpvpns.ListNetworkAssociationsOpts{ Fields: fields, } - th.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/network_associations", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/network_associations", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - r.ParseForm() + if err := r.ParseForm(); err != nil { + t.Errorf("Failed to parse request form %v", err) + } th.AssertDeepEquals(t, fields, r.Form["fields"]) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListNetworkAssociationsResult) + fmt.Fprint(w, ListNetworkAssociationsResult) }) count := 0 - err := bgpvpns.ListNetworkAssociations(fake.ServiceClient(), bgpVpnID, listOpts).EachPage( + err := bgpvpns.ListNetworkAssociations(fake.ServiceClient(fakeServer), bgpVpnID, listOpts).EachPage( context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ @@ -207,11 +211,11 @@ func TestListNetworkAssociations(t *testing.T) { } func TestCreateNetworkAssociation(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() bgpVpnID := "460ac411-3dfb-45bb-8116-ed1a7233d143" - th.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/network_associations", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/network_associations", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -219,75 +223,77 @@ func TestCreateNetworkAssociation(t *testing.T) { th.TestJSONRequest(t, r, CreateNetworkAssociationRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateNetworkAssociationResponse) + fmt.Fprint(w, CreateNetworkAssociationResponse) }) opts := bgpvpns.CreateNetworkAssociationOpts{ NetworkID: "8c5d88dc-60ac-4b02-a65a-36b65888ddcd", } - r, err := bgpvpns.CreateNetworkAssociation(context.TODO(), fake.ServiceClient(), bgpVpnID, opts).Extract() + r, err := bgpvpns.CreateNetworkAssociation(context.TODO(), fake.ServiceClient(fakeServer), bgpVpnID, opts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, CreateNetworkAssociation, *r) } func TestGetNetworkAssociation(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() bgpVpnID := "460ac411-3dfb-45bb-8116-ed1a7233d143" networkAssociationID := "73238ca1-e05d-4c7a-b4d4-70407b4b8730" - th.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/network_associations/"+networkAssociationID, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/network_associations/"+networkAssociationID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetNetworkAssociationResult) + fmt.Fprint(w, GetNetworkAssociationResult) }) - r, err := bgpvpns.GetNetworkAssociation(context.TODO(), fake.ServiceClient(), bgpVpnID, networkAssociationID).Extract() + r, err := bgpvpns.GetNetworkAssociation(context.TODO(), fake.ServiceClient(fakeServer), bgpVpnID, networkAssociationID).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, GetNetworkAssociation, *r) } func TestDeleteNetworkAssociation(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() bgpVpnID := "460ac411-3dfb-45bb-8116-ed1a7233d143" networkAssociationID := "73238ca1-e05d-4c7a-b4d4-70407b4b8730" - th.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/network_associations/"+networkAssociationID, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/network_associations/"+networkAssociationID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - err := bgpvpns.DeleteNetworkAssociation(context.TODO(), fake.ServiceClient(), bgpVpnID, networkAssociationID).ExtractErr() + err := bgpvpns.DeleteNetworkAssociation(context.TODO(), fake.ServiceClient(fakeServer), bgpVpnID, networkAssociationID).ExtractErr() th.AssertNoErr(t, err) } func TestListRouterAssociations(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() bgpVpnID := "460ac411-3dfb-45bb-8116-ed1a7233d143" fields := []string{"id", "name"} listOpts := bgpvpns.ListRouterAssociationsOpts{ Fields: fields, } - th.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/router_associations", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/router_associations", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - r.ParseForm() + if err := r.ParseForm(); err != nil { + t.Errorf("Failed to parse request form %v", err) + } th.AssertDeepEquals(t, fields, r.Form["fields"]) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListRouterAssociationsResult) + fmt.Fprint(w, ListRouterAssociationsResult) }) count := 0 - err := bgpvpns.ListRouterAssociations(fake.ServiceClient(), bgpVpnID, listOpts).EachPage( + err := bgpvpns.ListRouterAssociations(fake.ServiceClient(fakeServer), bgpVpnID, listOpts).EachPage( context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ @@ -308,11 +314,11 @@ func TestListRouterAssociations(t *testing.T) { } func TestCreateRouterAssociation(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() bgpVpnID := "460ac411-3dfb-45bb-8116-ed1a7233d143" - th.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/router_associations", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/router_associations", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -320,32 +326,32 @@ func TestCreateRouterAssociation(t *testing.T) { th.TestJSONRequest(t, r, CreateRouterAssociationRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateRouterAssociationResponse) + fmt.Fprint(w, CreateRouterAssociationResponse) }) opts := bgpvpns.CreateRouterAssociationOpts{ RouterID: "8c5d88dc-60ac-4b02-a65a-36b65888ddcd", } - r, err := bgpvpns.CreateRouterAssociation(context.TODO(), fake.ServiceClient(), bgpVpnID, opts).Extract() + r, err := bgpvpns.CreateRouterAssociation(context.TODO(), fake.ServiceClient(fakeServer), bgpVpnID, opts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, CreateRouterAssociation, *r) } func TestGetRouterAssociation(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() bgpVpnID := "460ac411-3dfb-45bb-8116-ed1a7233d143" routerAssociationID := "73238ca1-e05d-4c7a-b4d4-70407b4b8730" - th.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/router_associations/"+routerAssociationID, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/router_associations/"+routerAssociationID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetRouterAssociationResult) + fmt.Fprint(w, GetRouterAssociationResult) }) - r, err := bgpvpns.GetRouterAssociation(context.TODO(), fake.ServiceClient(), bgpVpnID, routerAssociationID).Extract() + r, err := bgpvpns.GetRouterAssociation(context.TODO(), fake.ServiceClient(fakeServer), bgpVpnID, routerAssociationID).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, GetRouterAssociation, *r) } @@ -353,10 +359,10 @@ func TestGetRouterAssociation(t *testing.T) { func TestUpdateRouterAssociation(t *testing.T) { bgpVpnID := "4d627abf-06dd-45ab-920b-8e61422bb984" routerAssociationID := "73238ca1-e05d-4c7a-b4d4-70407b4b8730" - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/router_associations/"+routerAssociationID, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/router_associations/"+routerAssociationID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -364,56 +370,58 @@ func TestUpdateRouterAssociation(t *testing.T) { th.TestJSONRequest(t, r, UpdateRouterAssociationRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateRouterAssociationResponse) + fmt.Fprint(w, UpdateRouterAssociationResponse) }) opts := bgpvpns.UpdateRouterAssociationOpts{ AdvertiseExtraRoutes: new(bool), } - r, err := bgpvpns.UpdateRouterAssociation(context.TODO(), fake.ServiceClient(), bgpVpnID, routerAssociationID, opts).Extract() + r, err := bgpvpns.UpdateRouterAssociation(context.TODO(), fake.ServiceClient(fakeServer), bgpVpnID, routerAssociationID, opts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, UpdateRouterAssociation, *r) } func TestDeleteRouterAssociation(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() bgpVpnID := "460ac411-3dfb-45bb-8116-ed1a7233d143" routerAssociationID := "73238ca1-e05d-4c7a-b4d4-70407b4b8730" - th.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/router_associations/"+routerAssociationID, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/router_associations/"+routerAssociationID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - err := bgpvpns.DeleteRouterAssociation(context.TODO(), fake.ServiceClient(), bgpVpnID, routerAssociationID).ExtractErr() + err := bgpvpns.DeleteRouterAssociation(context.TODO(), fake.ServiceClient(fakeServer), bgpVpnID, routerAssociationID).ExtractErr() th.AssertNoErr(t, err) } func TestListPortAssociations(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() bgpVpnID := "460ac411-3dfb-45bb-8116-ed1a7233d143" fields := []string{"id", "name"} listOpts := bgpvpns.ListPortAssociationsOpts{ Fields: fields, } - th.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/port_associations", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/port_associations", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - r.ParseForm() + if err := r.ParseForm(); err != nil { + t.Errorf("Failed to parse request form %v", err) + } th.AssertDeepEquals(t, fields, r.Form["fields"]) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListPortAssociationsResult) + fmt.Fprint(w, ListPortAssociationsResult) }) count := 0 - err := bgpvpns.ListPortAssociations(fake.ServiceClient(), bgpVpnID, listOpts).EachPage( + err := bgpvpns.ListPortAssociations(fake.ServiceClient(fakeServer), bgpVpnID, listOpts).EachPage( context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ @@ -434,11 +442,11 @@ func TestListPortAssociations(t *testing.T) { } func TestCreatePortAssociation(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() bgpVpnID := "460ac411-3dfb-45bb-8116-ed1a7233d143" - th.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/port_associations", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/port_associations", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -446,32 +454,32 @@ func TestCreatePortAssociation(t *testing.T) { th.TestJSONRequest(t, r, CreatePortAssociationRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreatePortAssociationResponse) + fmt.Fprint(w, CreatePortAssociationResponse) }) opts := bgpvpns.CreatePortAssociationOpts{ PortID: "8c5d88dc-60ac-4b02-a65a-36b65888ddcd", } - r, err := bgpvpns.CreatePortAssociation(context.TODO(), fake.ServiceClient(), bgpVpnID, opts).Extract() + r, err := bgpvpns.CreatePortAssociation(context.TODO(), fake.ServiceClient(fakeServer), bgpVpnID, opts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, CreatePortAssociation, *r) } func TestGetPortAssociation(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() bgpVpnID := "460ac411-3dfb-45bb-8116-ed1a7233d143" portAssociationID := "73238ca1-e05d-4c7a-b4d4-70407b4b8730" - th.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/port_associations/"+portAssociationID, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/port_associations/"+portAssociationID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetPortAssociationResult) + fmt.Fprint(w, GetPortAssociationResult) }) - r, err := bgpvpns.GetPortAssociation(context.TODO(), fake.ServiceClient(), bgpVpnID, portAssociationID).Extract() + r, err := bgpvpns.GetPortAssociation(context.TODO(), fake.ServiceClient(fakeServer), bgpVpnID, portAssociationID).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, GetPortAssociation, *r) } @@ -479,10 +487,10 @@ func TestGetPortAssociation(t *testing.T) { func TestUpdatePortAssociation(t *testing.T) { bgpVpnID := "4d627abf-06dd-45ab-920b-8e61422bb984" portAssociationID := "73238ca1-e05d-4c7a-b4d4-70407b4b8730" - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/port_associations/"+portAssociationID, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/port_associations/"+portAssociationID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -490,29 +498,29 @@ func TestUpdatePortAssociation(t *testing.T) { th.TestJSONRequest(t, r, UpdatePortAssociationRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdatePortAssociationResponse) + fmt.Fprint(w, UpdatePortAssociationResponse) }) opts := bgpvpns.UpdatePortAssociationOpts{ AdvertiseFixedIPs: new(bool), } - r, err := bgpvpns.UpdatePortAssociation(context.TODO(), fake.ServiceClient(), bgpVpnID, portAssociationID, opts).Extract() + r, err := bgpvpns.UpdatePortAssociation(context.TODO(), fake.ServiceClient(fakeServer), bgpVpnID, portAssociationID, opts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, UpdatePortAssociation, *r) } func TestDeletePortAssociation(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() bgpVpnID := "460ac411-3dfb-45bb-8116-ed1a7233d143" portAssociationID := "73238ca1-e05d-4c7a-b4d4-70407b4b8730" - th.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/port_associations/"+portAssociationID, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/bgpvpn/bgpvpns/"+bgpVpnID+"/port_associations/"+portAssociationID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - err := bgpvpns.DeletePortAssociation(context.TODO(), fake.ServiceClient(), bgpVpnID, portAssociationID).ExtractErr() + err := bgpvpns.DeletePortAssociation(context.TODO(), fake.ServiceClient(fakeServer), bgpVpnID, portAssociationID).ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/networking/v2/extensions/dns/testing/fixtures_test.go b/openstack/networking/v2/extensions/dns/testing/fixtures_test.go index 340c90083c..3af9bac7eb 100644 --- a/openstack/networking/v2/extensions/dns/testing/fixtures_test.go +++ b/openstack/networking/v2/extensions/dns/testing/fixtures_test.go @@ -68,34 +68,34 @@ const NetworkUpdateResponse = ` } }` -func PortHandleListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { +func PortHandleListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - th.AssertEquals(t, r.RequestURI, "/v2.0/ports?dns_name=test-port") + th.AssertEquals(t, "/v2.0/ports?dns_name=test-port", r.RequestURI) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, porttest.ListResponse) + fmt.Fprint(w, porttest.ListResponse) }) } -func PortHandleGet(t *testing.T) { - th.Mux.HandleFunc("/v2.0/ports/46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", func(w http.ResponseWriter, r *http.Request) { +func PortHandleGet(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/ports/46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, porttest.GetResponse) + fmt.Fprint(w, porttest.GetResponse) }) } -func PortHandleCreate(t *testing.T) { - th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { +func PortHandleCreate(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -121,7 +121,7 @@ func PortHandleCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "port": { "status": "DOWN", @@ -157,8 +157,8 @@ func PortHandleCreate(t *testing.T) { }) } -func PortHandleUpdate(t *testing.T) { - th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { +func PortHandleUpdate(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -184,7 +184,7 @@ func PortHandleUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "port": { "status": "DOWN", @@ -219,34 +219,34 @@ func PortHandleUpdate(t *testing.T) { }) } -func FloatingIPHandleList(t *testing.T) { - th.Mux.HandleFunc("/v2.0/floatingips", func(w http.ResponseWriter, r *http.Request) { +func FloatingIPHandleList(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/floatingips", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - th.AssertEquals(t, r.RequestURI, "/v2.0/floatingips?dns_domain=local.&dns_name=test-fip") + th.AssertEquals(t, "/v2.0/floatingips?dns_domain=local.&dns_name=test-fip", r.RequestURI) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, floatingiptest.ListResponseDNS) + fmt.Fprint(w, floatingiptest.ListResponseDNS) }) } -func FloatingIPHandleGet(t *testing.T) { - th.Mux.HandleFunc("/v2.0/floatingips/2f95fd2b-9f6a-4e8e-9e9a-2cbe286cbf9e", func(w http.ResponseWriter, r *http.Request) { +func FloatingIPHandleGet(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/floatingips/2f95fd2b-9f6a-4e8e-9e9a-2cbe286cbf9e", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, fmt.Sprintf(`{"floatingip": %s}`, floatingiptest.FipDNS)) + fmt.Fprintf(w, `{"floatingip": %s}`, floatingiptest.FipDNS) }) } -func FloatingIPHandleCreate(t *testing.T) { - th.Mux.HandleFunc("/v2.0/floatingips", func(w http.ResponseWriter, r *http.Request) { +func FloatingIPHandleCreate(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/floatingips", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -264,38 +264,38 @@ func FloatingIPHandleCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, fmt.Sprintf(`{"floatingip": %s}`, floatingiptest.FipDNS)) + fmt.Fprintf(w, `{"floatingip": %s}`, floatingiptest.FipDNS) }) } -func NetworkHandleList(t *testing.T) { - th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { +func NetworkHandleList(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - th.AssertEquals(t, r.RequestURI, "/v2.0/networks?dns_domain=local.") + th.AssertEquals(t, "/v2.0/networks?dns_domain=local.", r.RequestURI) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, networktest.ListResponse) + fmt.Fprint(w, networktest.ListResponse) }) } -func NetworkHandleGet(t *testing.T) { - th.Mux.HandleFunc("/v2.0/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { +func NetworkHandleGet(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, networktest.GetResponse) + fmt.Fprint(w, networktest.GetResponse) }) } -func NetworkHandleCreate(t *testing.T) { - th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { +func NetworkHandleCreate(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -304,12 +304,12 @@ func NetworkHandleCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, NetworkCreateResponse) + fmt.Fprint(w, NetworkCreateResponse) }) } -func NetworkHandleUpdate(t *testing.T) { - th.Mux.HandleFunc("/v2.0/networks/db193ab3-96e3-4cb3-8fc5-05f4296d0324", func(w http.ResponseWriter, r *http.Request) { +func NetworkHandleUpdate(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/networks/db193ab3-96e3-4cb3-8fc5-05f4296d0324", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -319,6 +319,6 @@ func NetworkHandleUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, NetworkUpdateResponse) + fmt.Fprint(w, NetworkUpdateResponse) }) } diff --git a/openstack/networking/v2/extensions/dns/testing/requests_test.go b/openstack/networking/v2/extensions/dns/testing/requests_test.go index 645edf7d32..55e9b1664b 100644 --- a/openstack/networking/v2/extensions/dns/testing/requests_test.go +++ b/openstack/networking/v2/extensions/dns/testing/requests_test.go @@ -32,10 +32,10 @@ var createdTime, _ = time.Parse(time.RFC3339, "2019-06-30T04:15:37Z") var updatedTime, _ = time.Parse(time.RFC3339, "2019-06-30T05:18:49Z") func TestPortList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - PortHandleListSuccessfully(t) + PortHandleListSuccessfully(t, fakeServer) var actual []PortDNS @@ -79,7 +79,7 @@ func TestPortList(t *testing.T) { DNSName: "test-port", } - allPages, err := ports.List(fake.ServiceClient(), listOptsBuilder).AllPages(context.TODO()) + allPages, err := ports.List(fake.ServiceClient(fakeServer), listOptsBuilder).AllPages(context.TODO()) th.AssertNoErr(t, err) err = ports.ExtractPortsInto(allPages, &actual) @@ -89,45 +89,45 @@ func TestPortList(t *testing.T) { } func TestPortGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - PortHandleGet(t) + PortHandleGet(t, fakeServer) var s PortDNS - err := ports.Get(context.TODO(), fake.ServiceClient(), "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2").ExtractInto(&s) + err := ports.Get(context.TODO(), fake.ServiceClient(fakeServer), "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2").ExtractInto(&s) th.AssertNoErr(t, err) - th.AssertEquals(t, s.Status, "ACTIVE") - th.AssertEquals(t, s.Name, "") - th.AssertEquals(t, s.AdminStateUp, true) - th.AssertEquals(t, s.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7") - th.AssertEquals(t, s.TenantID, "7e02058126cc4950b75f9970368ba177") - th.AssertEquals(t, s.DeviceOwner, "network:router_interface") - th.AssertEquals(t, s.MACAddress, "fa:16:3e:23:fd:d7") - th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{ + th.AssertEquals(t, "ACTIVE", s.Status) + th.AssertEquals(t, "", s.Name) + th.AssertTrue(t, s.AdminStateUp) + th.AssertEquals(t, "a87cc70a-3e15-4acf-8205-9b711a3531b7", s.NetworkID) + th.AssertEquals(t, "7e02058126cc4950b75f9970368ba177", s.TenantID) + th.AssertEquals(t, "network:router_interface", s.DeviceOwner) + th.AssertEquals(t, "fa:16:3e:23:fd:d7", s.MACAddress) + th.AssertDeepEquals(t, []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.1"}, - }) - th.AssertEquals(t, s.ID, "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2") - th.AssertDeepEquals(t, s.SecurityGroups, []string{}) - th.AssertEquals(t, s.DeviceID, "5e3898d7-11be-483e-9732-b2f5eccd2b2e") + }, s.FixedIPs) + th.AssertEquals(t, "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", s.ID) + th.AssertDeepEquals(t, []string{}, s.SecurityGroups) + th.AssertEquals(t, "5e3898d7-11be-483e-9732-b2f5eccd2b2e", s.DeviceID) - th.AssertEquals(t, s.DNSName, "test-port") - th.AssertDeepEquals(t, s.DNSAssignment, []map[string]string{ + th.AssertEquals(t, "test-port", s.DNSName) + th.AssertDeepEquals(t, []map[string]string{ { "hostname": "test-port", "ip_address": "172.24.4.2", "fqdn": "test-port.openstack.local.", }, - }) + }, s.DNSAssignment) } func TestPortCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - PortHandleCreate(t) + PortHandleCreate(t, fakeServer) var s PortDNS @@ -147,44 +147,47 @@ func TestPortCreate(t *testing.T) { DNSName: "test-port", } - err := ports.Create(context.TODO(), fake.ServiceClient(), createOpts).ExtractInto(&s) + err := ports.Create(context.TODO(), fake.ServiceClient(fakeServer), createOpts).ExtractInto(&s) th.AssertNoErr(t, err) - th.AssertEquals(t, s.Status, "DOWN") - th.AssertEquals(t, s.Name, "private-port") - th.AssertEquals(t, s.AdminStateUp, true) - th.AssertEquals(t, s.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7") - th.AssertEquals(t, s.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa") - th.AssertEquals(t, s.DeviceOwner, "") - th.AssertEquals(t, s.MACAddress, "fa:16:3e:c9:cb:f0") - th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{ + th.AssertEquals(t, "DOWN", s.Status) + th.AssertEquals(t, "private-port", s.Name) + th.AssertTrue(t, s.AdminStateUp) + th.AssertEquals(t, "a87cc70a-3e15-4acf-8205-9b711a3531b7", s.NetworkID) + th.AssertEquals(t, "d6700c0c9ffa4f1cb322cd4a1f3906fa", s.TenantID) + th.AssertEquals(t, "", s.DeviceOwner) + th.AssertEquals(t, "fa:16:3e:c9:cb:f0", s.MACAddress) + th.AssertDeepEquals(t, []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"}, - }) - th.AssertEquals(t, s.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") - th.AssertDeepEquals(t, s.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}) + }, s.FixedIPs) + th.AssertEquals(t, "65c0ee9f-d634-4522-8954-51021b570b0d", s.ID) + th.AssertDeepEquals(t, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}, s.SecurityGroups) - th.AssertEquals(t, s.DNSName, "test-port") - th.AssertDeepEquals(t, s.DNSAssignment, []map[string]string{ + th.AssertEquals(t, "test-port", s.DNSName) + th.AssertDeepEquals(t, []map[string]string{ { "hostname": "test-port", "ip_address": "172.24.4.2", "fqdn": "test-port.openstack.local.", }, - }) + }, s.DNSAssignment) } func TestPortRequiredCreateOpts(t *testing.T) { - res := ports.Create(context.TODO(), fake.ServiceClient(), dns.PortCreateOptsExt{CreateOptsBuilder: ports.CreateOpts{}}) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + res := ports.Create(context.TODO(), fake.ServiceClient(fakeServer), dns.PortCreateOptsExt{CreateOptsBuilder: ports.CreateOpts{}}) if res.Err == nil { t.Fatalf("Expected error, got none") } } func TestPortUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - PortHandleUpdate(t) + PortHandleUpdate(t, fakeServer) var s PortDNS @@ -203,32 +206,32 @@ func TestPortUpdate(t *testing.T) { DNSName: &dnsName, } - err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&s) + err := ports.Update(context.TODO(), fake.ServiceClient(fakeServer), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&s) th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "new_port_name") - th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{ + th.AssertEquals(t, "new_port_name", s.Name) + th.AssertDeepEquals(t, []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"}, - }) - th.AssertDeepEquals(t, s.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}) - th.AssertEquals(t, s.DNSName, "test-port1") - th.AssertDeepEquals(t, s.DNSAssignment, []map[string]string{ + }, s.FixedIPs) + th.AssertDeepEquals(t, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}, s.SecurityGroups) + th.AssertEquals(t, "test-port1", s.DNSName) + th.AssertDeepEquals(t, []map[string]string{ { "hostname": "test-port1", "ip_address": "172.24.4.2", "fqdn": "test-port1.openstack.local.", }, - }) + }, s.DNSAssignment) } func TestFloatingIPGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - FloatingIPHandleGet(t) + FloatingIPHandleGet(t, fakeServer) var actual FloatingIPDNS - err := floatingips.Get(context.TODO(), fake.ServiceClient(), "2f95fd2b-9f6a-4e8e-9e9a-2cbe286cbf9e").ExtractInto(&actual) + err := floatingips.Get(context.TODO(), fake.ServiceClient(fakeServer), "2f95fd2b-9f6a-4e8e-9e9a-2cbe286cbf9e").ExtractInto(&actual) th.AssertNoErr(t, err) expected := FloatingIPDNS{ @@ -254,10 +257,10 @@ func TestFloatingIPGet(t *testing.T) { } func TestFloatingIPCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - FloatingIPHandleCreate(t) + FloatingIPHandleCreate(t, fakeServer) var actual FloatingIPDNS @@ -271,7 +274,7 @@ func TestFloatingIPCreate(t *testing.T) { DNSDomain: "local.", } - err := floatingips.Create(context.TODO(), fake.ServiceClient(), options).ExtractInto(&actual) + err := floatingips.Create(context.TODO(), fake.ServiceClient(fakeServer), options).ExtractInto(&actual) th.AssertNoErr(t, err) expected := FloatingIPDNS{ @@ -297,14 +300,14 @@ func TestFloatingIPCreate(t *testing.T) { } func TestNetworkGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - NetworkHandleGet(t) + NetworkHandleGet(t, fakeServer) var actual NetworkDNS - err := networks.Get(context.TODO(), fake.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(&actual) + err := networks.Get(context.TODO(), fake.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(&actual) th.AssertNoErr(t, err) expected := NetworkDNS{ @@ -328,10 +331,10 @@ func TestNetworkGet(t *testing.T) { } func TestNetworkCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - NetworkHandleCreate(t) + NetworkHandleCreate(t, fakeServer) var actual NetworkDNS @@ -342,7 +345,7 @@ func TestNetworkCreate(t *testing.T) { DNSDomain: "local.", } - err := networks.Create(context.TODO(), fake.ServiceClient(), createOpts).ExtractInto(&actual) + err := networks.Create(context.TODO(), fake.ServiceClient(fakeServer), createOpts).ExtractInto(&actual) th.AssertNoErr(t, err) expected := NetworkDNS{ @@ -366,10 +369,10 @@ func TestNetworkCreate(t *testing.T) { } func TestNetworkUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - NetworkHandleUpdate(t) + NetworkHandleUpdate(t, fakeServer) var actual NetworkDNS @@ -380,7 +383,7 @@ func TestNetworkUpdate(t *testing.T) { DNSDomain: new(string), } - err := networks.Update(context.TODO(), fake.ServiceClient(), "db193ab3-96e3-4cb3-8fc5-05f4296d0324", updateOpts).ExtractInto(&actual) + err := networks.Update(context.TODO(), fake.ServiceClient(fakeServer), "db193ab3-96e3-4cb3-8fc5-05f4296d0324", updateOpts).ExtractInto(&actual) th.AssertNoErr(t, err) expected := NetworkDNS{ diff --git a/openstack/networking/v2/extensions/external/testing/requests_test.go b/openstack/networking/v2/extensions/external/testing/requests_test.go index aa60ee4424..6b6c3017dc 100644 --- a/openstack/networking/v2/extensions/external/testing/requests_test.go +++ b/openstack/networking/v2/extensions/external/testing/requests_test.go @@ -9,7 +9,7 @@ import ( ) func TestListExternal(t *testing.T) { - var iTrue bool = true + var iTrue = true networkListOpts := networks.ListOpts{ ID: "d32019d3-bc6e-4319-9c1d-6722fc136a22", diff --git a/openstack/networking/v2/extensions/external/testing/results_test.go b/openstack/networking/v2/extensions/external/testing/results_test.go index 12062e53d8..e0701a0e1e 100644 --- a/openstack/networking/v2/extensions/external/testing/results_test.go +++ b/openstack/networking/v2/extensions/external/testing/results_test.go @@ -14,17 +14,17 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, nettest.ListResponse) + fmt.Fprint(w, nettest.ListResponse) }) type NetworkWithExternalExt struct { @@ -33,28 +33,28 @@ func TestList(t *testing.T) { } var actual []NetworkWithExternalExt - allPages, err := networks.List(fake.ServiceClient(), networks.ListOpts{}).AllPages(context.TODO()) + allPages, err := networks.List(fake.ServiceClient(fakeServer), networks.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) err = networks.ExtractNetworksInto(allPages, &actual) th.AssertNoErr(t, err) th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", actual[0].ID) - th.AssertEquals(t, true, actual[0].External) + th.AssertTrue(t, actual[0].External) } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, nettest.GetResponse) + fmt.Fprint(w, nettest.GetResponse) }) var s struct { @@ -62,18 +62,18 @@ func TestGet(t *testing.T) { external.NetworkExternalExt } - err := networks.Get(context.TODO(), fake.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(&s) + err := networks.Get(context.TODO(), fake.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(&s) th.AssertNoErr(t, err) th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", s.ID) - th.AssertEquals(t, true, s.External) + th.AssertTrue(t, s.External) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -83,7 +83,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) iTrue := true @@ -98,17 +98,17 @@ func TestCreate(t *testing.T) { External: &iFalse, } - _, err := networks.Create(context.TODO(), fake.ServiceClient(), externalCreateOpts).Extract() + _, err := networks.Create(context.TODO(), fake.ServiceClient(fakeServer), externalCreateOpts).Extract() th.AssertNoErr(t, err) th.AssertNoErr(t, err) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -118,7 +118,7 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateResponse) + fmt.Fprint(w, UpdateResponse) }) iTrue := true @@ -135,6 +135,6 @@ func TestUpdate(t *testing.T) { External: &iFalse, } - _, err := networks.Update(context.TODO(), fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", externalUpdateOpts).Extract() + _, err := networks.Update(context.TODO(), fake.ServiceClient(fakeServer), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", externalUpdateOpts).Extract() th.AssertNoErr(t, err) } diff --git a/openstack/networking/v2/extensions/fwaas_v2/groups/requests.go b/openstack/networking/v2/extensions/fwaas_v2/groups/requests.go index 9b12c62842..856e21548f 100644 --- a/openstack/networking/v2/extensions/fwaas_v2/groups/requests.go +++ b/openstack/networking/v2/extensions/fwaas_v2/groups/requests.go @@ -69,7 +69,8 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { // Get retrieves a particular firewall group based on its unique ID. func Get(ctx context.Context, c *gophercloud.ServiceClient, id string) (r GetResult) { - _, r.Err = c.Get(ctx, resourceURL(c, id), &r.Body, nil) + resp, err := c.Get(ctx, resourceURL(c, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -107,7 +108,8 @@ func Create(ctx context.Context, c *gophercloud.ServiceClient, opts CreateOptsBu r.Err = err return } - _, r.Err = c.Post(ctx, rootURL(c), b, &r.Body, nil) + resp, err := c.Post(ctx, rootURL(c), b, &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -142,9 +144,10 @@ func Update(ctx context.Context, c *gophercloud.ServiceClient, id string, opts U r.Err = err return } - _, r.Err = c.Put(ctx, resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := c.Put(ctx, resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -165,9 +168,10 @@ func RemoveIngressPolicy(ctx context.Context, c *gophercloud.ServiceClient, id s r.Err = err return } - _, r.Err = c.Put(ctx, resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := c.Put(ctx, resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -181,14 +185,16 @@ func RemoveEgressPolicy(ctx context.Context, c *gophercloud.ServiceClient, id st r.Err = err return } - _, r.Err = c.Put(ctx, resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := c.Put(ctx, resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Delete will permanently delete a particular firewall group based on its unique ID. func Delete(ctx context.Context, c *gophercloud.ServiceClient, id string) (r DeleteResult) { - _, r.Err = c.Delete(ctx, resourceURL(c, id), nil) + resp, err := c.Delete(ctx, resourceURL(c, id), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } diff --git a/openstack/networking/v2/extensions/fwaas_v2/groups/results.go b/openstack/networking/v2/extensions/fwaas_v2/groups/results.go index 622986c2e5..b24e7d1cac 100644 --- a/openstack/networking/v2/extensions/fwaas_v2/groups/results.go +++ b/openstack/networking/v2/extensions/fwaas_v2/groups/results.go @@ -42,7 +42,7 @@ type GroupPage struct { // NextPageURL is invoked when a paginated collection of firewall groups has // reached the end of a page and the pager seeks to traverse over a new one. // In order to do this, it needs to construct the next page's URL. -func (r GroupPage) NextPageURL() (string, error) { +func (r GroupPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"firewall_groups_links"` } diff --git a/openstack/networking/v2/extensions/fwaas_v2/groups/testing/requests_test.go b/openstack/networking/v2/extensions/fwaas_v2/groups/testing/requests_test.go index 59768a756d..fabed6d4be 100644 --- a/openstack/networking/v2/extensions/fwaas_v2/groups/testing/requests_test.go +++ b/openstack/networking/v2/extensions/fwaas_v2/groups/testing/requests_test.go @@ -13,17 +13,17 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/fwaas/firewall_groups", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/fwaas/firewall_groups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "firewall_groups": [ { @@ -68,7 +68,7 @@ func TestList(t *testing.T) { count := 0 - err := groups.List(fake.ServiceClient(), groups.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := groups.List(fake.ServiceClient(fakeServer), groups.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := groups.ExtractGroups(page) if err != nil { @@ -126,17 +126,17 @@ func TestList(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/fwaas/firewall_groups/6bfb0f10-07f7-4a40-b534-bad4b4ca3428", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/fwaas/firewall_groups/6bfb0f10-07f7-4a40-b534-bad4b4ca3428", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "firewall_group": { "id": "6bfb0f10-07f7-4a40-b534-bad4b4ca3428", @@ -157,7 +157,7 @@ func TestGet(t *testing.T) { `) }) - group, err := groups.Get(context.TODO(), fake.ServiceClient(), "6bfb0f10-07f7-4a40-b534-bad4b4ca3428").Extract() + group, err := groups.Get(context.TODO(), fake.ServiceClient(fakeServer), "6bfb0f10-07f7-4a40-b534-bad4b4ca3428").Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "6bfb0f10-07f7-4a40-b534-bad4b4ca3428", group.ID) @@ -166,19 +166,19 @@ func TestGet(t *testing.T) { th.AssertEquals(t, "some information", group.Description) th.AssertEquals(t, "e3f11142-3792-454b-8d3e-91ac1bf127b4", group.IngressFirewallPolicyID) th.AssertEquals(t, "", group.EgressFirewallPolicyID) - th.AssertEquals(t, true, group.AdminStateUp) + th.AssertTrue(t, group.AdminStateUp) th.AssertEquals(t, 1, len(group.Ports)) th.AssertEquals(t, "a6af1e56-b12b-4733-8f77-49166afd5719", group.Ports[0]) th.AssertEquals(t, "ACTIVE", group.Status) - th.AssertEquals(t, false, group.Shared) + th.AssertFalse(t, group.Shared) th.AssertEquals(t, "9f98fc0e5f944cd1b51798b668dc8778", group.TenantID) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/fwaas/firewall_groups", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/fwaas/firewall_groups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -199,7 +199,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "firewall_group": { "id": "6bfb0f10-07f7-4a40-b534-bad4b4ca3428", @@ -230,15 +230,15 @@ func TestCreate(t *testing.T) { }, } - _, err := groups.Create(context.TODO(), fake.ServiceClient(), options).Extract() + _, err := groups.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/fwaas/firewall_groups/6bfb0f10-07f7-4a40-b534-bad4b4ca3428", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/fwaas/firewall_groups/6bfb0f10-07f7-4a40-b534-bad4b4ca3428", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -260,7 +260,7 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "firewall_group": { "id": "6bfb0f10-07f7-4a40-b534-bad4b4ca3428", @@ -295,15 +295,15 @@ func TestUpdate(t *testing.T) { AdminStateUp: &adminStateUp, } - _, err := groups.Update(context.TODO(), fake.ServiceClient(), "6bfb0f10-07f7-4a40-b534-bad4b4ca3428", options).Extract() + _, err := groups.Update(context.TODO(), fake.ServiceClient(fakeServer), "6bfb0f10-07f7-4a40-b534-bad4b4ca3428", options).Extract() th.AssertNoErr(t, err) } func TestRemoveIngressPolicy(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/fwaas/firewall_groups/6bfb0f10-07f7-4a40-b534-bad4b4ca3428", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/fwaas/firewall_groups/6bfb0f10-07f7-4a40-b534-bad4b4ca3428", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -319,7 +319,7 @@ func TestRemoveIngressPolicy(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "firewall_group": { "id": "6bfb0f10-07f7-4a40-b534-bad4b4ca3428", @@ -341,17 +341,17 @@ func TestRemoveIngressPolicy(t *testing.T) { `) }) - removeIngressPolicy, err := groups.RemoveIngressPolicy(context.TODO(), fake.ServiceClient(), "6bfb0f10-07f7-4a40-b534-bad4b4ca3428").Extract() + removeIngressPolicy, err := groups.RemoveIngressPolicy(context.TODO(), fake.ServiceClient(fakeServer), "6bfb0f10-07f7-4a40-b534-bad4b4ca3428").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, removeIngressPolicy.IngressFirewallPolicyID, "") - th.AssertEquals(t, removeIngressPolicy.EgressFirewallPolicyID, "43a11f3a-ddac-4129-9469-02b9df26548e") + th.AssertEquals(t, "", removeIngressPolicy.IngressFirewallPolicyID) + th.AssertEquals(t, "43a11f3a-ddac-4129-9469-02b9df26548e", removeIngressPolicy.EgressFirewallPolicyID) } func TestRemoveEgressPolicy(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/fwaas/firewall_groups/6bfb0f10-07f7-4a40-b534-bad4b4ca3428", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/fwaas/firewall_groups/6bfb0f10-07f7-4a40-b534-bad4b4ca3428", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -367,7 +367,7 @@ func TestRemoveEgressPolicy(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "firewall_group": { "id": "6bfb0f10-07f7-4a40-b534-bad4b4ca3428", @@ -389,22 +389,22 @@ func TestRemoveEgressPolicy(t *testing.T) { `) }) - removeEgressPolicy, err := groups.RemoveEgressPolicy(context.TODO(), fake.ServiceClient(), "6bfb0f10-07f7-4a40-b534-bad4b4ca3428").Extract() + removeEgressPolicy, err := groups.RemoveEgressPolicy(context.TODO(), fake.ServiceClient(fakeServer), "6bfb0f10-07f7-4a40-b534-bad4b4ca3428").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, removeEgressPolicy.IngressFirewallPolicyID, "e3f11142-3792-454b-8d3e-91ac1bf127b4") - th.AssertEquals(t, removeEgressPolicy.EgressFirewallPolicyID, "") + th.AssertEquals(t, "e3f11142-3792-454b-8d3e-91ac1bf127b4", removeEgressPolicy.IngressFirewallPolicyID) + th.AssertEquals(t, "", removeEgressPolicy.EgressFirewallPolicyID) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/fwaas/firewall_groups/4ec89077-d057-4a2b-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/fwaas/firewall_groups/4ec89077-d057-4a2b-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := groups.Delete(context.TODO(), fake.ServiceClient(), "4ec89077-d057-4a2b-911f-60a3b47ee304") + res := groups.Delete(context.TODO(), fake.ServiceClient(fakeServer), "4ec89077-d057-4a2b-911f-60a3b47ee304") th.AssertNoErr(t, res.Err) } diff --git a/openstack/networking/v2/extensions/fwaas_v2/policies/requests.go b/openstack/networking/v2/extensions/fwaas_v2/policies/requests.go index 57d25a6726..476360dbb3 100644 --- a/openstack/networking/v2/extensions/fwaas_v2/policies/requests.go +++ b/openstack/networking/v2/extensions/fwaas_v2/policies/requests.go @@ -91,13 +91,15 @@ func Create(ctx context.Context, c *gophercloud.ServiceClient, opts CreateOptsBu r.Err = err return } - _, r.Err = c.Post(ctx, rootURL(c), b, &r.Body, nil) + resp, err := c.Post(ctx, rootURL(c), b, &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Get retrieves a particular firewall policy based on its unique ID. func Get(ctx context.Context, c *gophercloud.ServiceClient, id string) (r GetResult) { - _, r.Err = c.Get(ctx, resourceURL(c, id), &r.Body, nil) + resp, err := c.Get(ctx, resourceURL(c, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -130,15 +132,17 @@ func Update(ctx context.Context, c *gophercloud.ServiceClient, id string, opts U r.Err = err return } - _, r.Err = c.Put(ctx, resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := c.Put(ctx, resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Delete will permanently delete a particular firewall policy based on its unique ID. func Delete(ctx context.Context, c *gophercloud.ServiceClient, id string) (r DeleteResult) { - _, r.Err = c.Delete(ctx, resourceURL(c, id), nil) + resp, err := c.Delete(ctx, resourceURL(c, id), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -162,16 +166,18 @@ func InsertRule(ctx context.Context, c *gophercloud.ServiceClient, id string, op r.Err = err return } - _, r.Err = c.Put(ctx, insertURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := c.Put(ctx, insertURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } func RemoveRule(ctx context.Context, c *gophercloud.ServiceClient, id, ruleID string) (r RemoveRuleResult) { b := map[string]any{"firewall_rule_id": ruleID} - _, r.Err = c.Put(ctx, removeURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := c.Put(ctx, removeURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } diff --git a/openstack/networking/v2/extensions/fwaas_v2/policies/results.go b/openstack/networking/v2/extensions/fwaas_v2/policies/results.go index 88731d0788..bdaae61823 100644 --- a/openstack/networking/v2/extensions/fwaas_v2/policies/results.go +++ b/openstack/networking/v2/extensions/fwaas_v2/policies/results.go @@ -50,7 +50,7 @@ type PolicyPage struct { // NextPageURL is invoked when a paginated collection of firewall policies has // reached the end of a page and the pager seeks to traverse over a new one. // In order to do this, it needs to construct the next page's URL. -func (r PolicyPage) NextPageURL() (string, error) { +func (r PolicyPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"firewall_policies_links"` } diff --git a/openstack/networking/v2/extensions/fwaas_v2/policies/testing/requests_test.go b/openstack/networking/v2/extensions/fwaas_v2/policies/testing/requests_test.go index 3141f19795..6fb8318214 100644 --- a/openstack/networking/v2/extensions/fwaas_v2/policies/testing/requests_test.go +++ b/openstack/networking/v2/extensions/fwaas_v2/policies/testing/requests_test.go @@ -14,17 +14,17 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/fwaas/firewall_policies", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/fwaas/firewall_policies", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "firewall_policies": [ { @@ -59,7 +59,7 @@ func TestList(t *testing.T) { count := 0 - err := policies.List(fake.ServiceClient(), policies.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := policies.List(fake.ServiceClient(fakeServer), policies.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := policies.ExtractPolicies(page) if err != nil { @@ -107,10 +107,10 @@ func TestList(t *testing.T) { } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/fwaas/firewall_policies", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/fwaas/firewall_policies", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -135,7 +135,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "firewall_policy":{ "name": "policy", @@ -166,15 +166,15 @@ func TestCreate(t *testing.T) { }, } - _, err := policies.Create(context.TODO(), fake.ServiceClient(), options).Extract() + _, err := policies.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) } func TestInsertRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/fwaas/firewall_policies/e3c78ab6-e827-4297-8d68-739063865a8b/insert_rule", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/fwaas/firewall_policies/e3c78ab6-e827-4297-8d68-739063865a8b/insert_rule", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -189,7 +189,7 @@ func TestInsertRule(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "audited": false, "description": "TESTACC-DESC-8P12aLfW", @@ -211,7 +211,7 @@ func TestInsertRule(t *testing.T) { InsertBefore: "3062ed90-1fb0-4c25-af3d-318dff2143ae", } - policy, err := policies.InsertRule(context.TODO(), fake.ServiceClient(), "e3c78ab6-e827-4297-8d68-739063865a8b", options).Extract() + policy, err := policies.InsertRule(context.TODO(), fake.ServiceClient(fakeServer), "e3c78ab6-e827-4297-8d68-739063865a8b", options).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "TESTACC-2LnMayeG", policy.Name) th.AssertEquals(t, 2, len(policy.Rules)) @@ -224,8 +224,8 @@ func TestInsertRule(t *testing.T) { } func TestInsertRuleWithInvalidParameters(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() //invalid opts, its not allowed to specify InsertBefore and InsertAfter together options := policies.InsertRuleOpts{ @@ -234,7 +234,7 @@ func TestInsertRuleWithInvalidParameters(t *testing.T) { InsertAfter: "2", } - _, err := policies.InsertRule(context.TODO(), fake.ServiceClient(), "0", options).Extract() + _, err := policies.InsertRule(context.TODO(), fake.ServiceClient(fakeServer), "0", options).Extract() // expect to fail with an gophercloud error th.AssertErr(t, err) @@ -242,17 +242,17 @@ func TestInsertRuleWithInvalidParameters(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/fwaas/firewall_policies/f2b08c1e-aa81-4668-8ae1-1401bcb0576c", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/fwaas/firewall_policies/f2b08c1e-aa81-4668-8ae1-1401bcb0576c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "firewall_policy":{ "name": "www", @@ -271,7 +271,7 @@ func TestGet(t *testing.T) { `) }) - policy, err := policies.Get(context.TODO(), fake.ServiceClient(), "f2b08c1e-aa81-4668-8ae1-1401bcb0576c").Extract() + policy, err := policies.Get(context.TODO(), fake.ServiceClient(fakeServer), "f2b08c1e-aa81-4668-8ae1-1401bcb0576c").Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "www", policy.Name) @@ -286,10 +286,10 @@ func TestGet(t *testing.T) { } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/fwaas/firewall_policies/f2b08c1e-aa81-4668-8ae1-1401bcb0576c", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/fwaas/firewall_policies/f2b08c1e-aa81-4668-8ae1-1401bcb0576c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -310,7 +310,7 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "firewall_policy":{ "name": "policy", @@ -341,29 +341,29 @@ func TestUpdate(t *testing.T) { }, } - _, err := policies.Update(context.TODO(), fake.ServiceClient(), "f2b08c1e-aa81-4668-8ae1-1401bcb0576c", options).Extract() + _, err := policies.Update(context.TODO(), fake.ServiceClient(fakeServer), "f2b08c1e-aa81-4668-8ae1-1401bcb0576c", options).Extract() th.AssertNoErr(t, err) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/fwaas/firewall_policies/4ec89077-d057-4a2b-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/fwaas/firewall_policies/4ec89077-d057-4a2b-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := policies.Delete(context.TODO(), fake.ServiceClient(), "4ec89077-d057-4a2b-911f-60a3b47ee304") + res := policies.Delete(context.TODO(), fake.ServiceClient(fakeServer), "4ec89077-d057-4a2b-911f-60a3b47ee304") th.AssertNoErr(t, res.Err) } func TestRemoveRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/fwaas/firewall_policies/9fed8075-06ee-463f-83a6-d4118791b02f/remove_rule", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/fwaas/firewall_policies/9fed8075-06ee-463f-83a6-d4118791b02f/remove_rule", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -377,7 +377,7 @@ func TestRemoveRule(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "audited": false, "description": "TESTACC-DESC-skno2e52", @@ -393,7 +393,7 @@ func TestRemoveRule(t *testing.T) { `) }) - policy, err := policies.RemoveRule(context.TODO(), fake.ServiceClient(), "9fed8075-06ee-463f-83a6-d4118791b02f", "9fed8075-06ee-463f-83a6-d4118791b02f").Extract() + policy, err := policies.RemoveRule(context.TODO(), fake.ServiceClient(fakeServer), "9fed8075-06ee-463f-83a6-d4118791b02f", "9fed8075-06ee-463f-83a6-d4118791b02f").Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "9fed8075-06ee-463f-83a6-d4118791b02f", policy.ID) diff --git a/openstack/networking/v2/extensions/fwaas_v2/rules/results.go b/openstack/networking/v2/extensions/fwaas_v2/rules/results.go index 601b9dd2ce..a2b95c9a73 100644 --- a/openstack/networking/v2/extensions/fwaas_v2/rules/results.go +++ b/openstack/networking/v2/extensions/fwaas_v2/rules/results.go @@ -33,7 +33,7 @@ type RulePage struct { // NextPageURL is invoked when a paginated collection of firewall rules has // reached the end of a page and the pager seeks to traverse over a new one. // In order to do this, it needs to construct the next page's URL. -func (r RulePage) NextPageURL() (string, error) { +func (r RulePage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"firewall_rules_links"` } diff --git a/openstack/networking/v2/extensions/fwaas_v2/rules/testing/requests_test.go b/openstack/networking/v2/extensions/fwaas_v2/rules/testing/requests_test.go index 36469967d8..4f9ce33792 100644 --- a/openstack/networking/v2/extensions/fwaas_v2/rules/testing/requests_test.go +++ b/openstack/networking/v2/extensions/fwaas_v2/rules/testing/requests_test.go @@ -14,17 +14,17 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/fwaas/firewall_rules", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/fwaas/firewall_rules", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "firewall_rules": [ { @@ -68,7 +68,7 @@ func TestList(t *testing.T) { count := 0 - err := rules.List(fake.ServiceClient(), rules.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := rules.List(fake.ServiceClient(fakeServer), rules.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := rules.ExtractRules(page) if err != nil { @@ -124,10 +124,10 @@ func TestList(t *testing.T) { } } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/fwaas/firewall_rules", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/fwaas/firewall_rules", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -150,7 +150,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "firewall_rule":{ "protocol": "tcp", @@ -185,15 +185,15 @@ func TestCreate(t *testing.T) { Action: "allow", } - _, err := rules.Create(context.TODO(), fake.ServiceClient(), options).Extract() + _, err := rules.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) } func TestCreateAnyProtocol(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/fwaas/firewall_rules", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/fwaas/firewall_rules", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -215,7 +215,7 @@ func TestCreateAnyProtocol(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "firewall_rule":{ "protocol": null, @@ -249,22 +249,22 @@ func TestCreateAnyProtocol(t *testing.T) { Action: "allow", } - _, err := rules.Create(context.TODO(), fake.ServiceClient(), options).Extract() + _, err := rules.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/fwaas/firewall_rules/f03bd950-6c56-4f5e-a307-45967078f507", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/fwaas/firewall_rules/f03bd950-6c56-4f5e-a307-45967078f507", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "firewall_rule":{ "protocol": "tcp", @@ -288,7 +288,7 @@ func TestGet(t *testing.T) { `) }) - rule, err := rules.Get(context.TODO(), fake.ServiceClient(), "f03bd950-6c56-4f5e-a307-45967078f507").Extract() + rule, err := rules.Get(context.TODO(), fake.ServiceClient(fakeServer), "f03bd950-6c56-4f5e-a307-45967078f507").Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "tcp", rule.Protocol) @@ -301,17 +301,17 @@ func TestGet(t *testing.T) { th.AssertEquals(t, "ssh_form_any", rule.Name) th.AssertEquals(t, "80cf934d6ffb4ef5b244f1c512ad1e61", rule.TenantID) th.AssertEquals(t, "80cf934d6ffb4ef5b244f1c512ad1e61", rule.ProjectID) - th.AssertEquals(t, true, rule.Enabled) + th.AssertTrue(t, rule.Enabled) th.AssertEquals(t, "allow", rule.Action) th.AssertEquals(t, 4, rule.IPVersion) - th.AssertEquals(t, false, rule.Shared) + th.AssertFalse(t, rule.Shared) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/fwaas/firewall_rules/f03bd950-6c56-4f5e-a307-45967078f507", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/fwaas/firewall_rules/f03bd950-6c56-4f5e-a307-45967078f507", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -333,7 +333,7 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "firewall_rule":{ "protocol": "tcp", @@ -372,20 +372,20 @@ func TestUpdate(t *testing.T) { Enabled: gophercloud.Disabled, } - _, err := rules.Update(context.TODO(), fake.ServiceClient(), "f03bd950-6c56-4f5e-a307-45967078f507", options).Extract() + _, err := rules.Update(context.TODO(), fake.ServiceClient(fakeServer), "f03bd950-6c56-4f5e-a307-45967078f507", options).Extract() th.AssertNoErr(t, err) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/fwaas/firewall_rules/4ec89077-d057-4a2b-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/fwaas/firewall_rules/4ec89077-d057-4a2b-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := rules.Delete(context.TODO(), fake.ServiceClient(), "4ec89077-d057-4a2b-911f-60a3b47ee304") + res := rules.Delete(context.TODO(), fake.ServiceClient(fakeServer), "4ec89077-d057-4a2b-911f-60a3b47ee304") th.AssertNoErr(t, res.Err) } diff --git a/openstack/networking/v2/extensions/layer3/addressscopes/requests.go b/openstack/networking/v2/extensions/layer3/addressscopes/requests.go index 22e4df39de..d75a3f0581 100644 --- a/openstack/networking/v2/extensions/layer3/addressscopes/requests.go +++ b/openstack/networking/v2/extensions/layer3/addressscopes/requests.go @@ -20,17 +20,16 @@ type ListOptsBuilder interface { // SortDir sets the direction, and is either `asc' or `desc'. // Marker and Limit are used for the pagination. type ListOpts struct { - ID string `q:"id"` - Name string `q:"name"` - TenantID string `q:"tenant_id"` - ProjectID string `q:"project_id"` - IPVersion int `q:"ip_version"` - Shared *bool `q:"shared"` - Description string `q:"description"` - Limit int `q:"limit"` - Marker string `q:"marker"` - SortKey string `q:"sort_key"` - SortDir string `q:"sort_dir"` + ID string `q:"id"` + Name string `q:"name"` + TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` + IPVersion int `q:"ip_version"` + Shared *bool `q:"shared"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` } // ToAddressScopeListQuery formats a ListOpts into a query string. diff --git a/openstack/networking/v2/extensions/layer3/addressscopes/results.go b/openstack/networking/v2/extensions/layer3/addressscopes/results.go index 8664edd6ea..ecd7c919b4 100644 --- a/openstack/networking/v2/extensions/layer3/addressscopes/results.go +++ b/openstack/networking/v2/extensions/layer3/addressscopes/results.go @@ -71,7 +71,7 @@ type AddressScopePage struct { // NextPageURL is invoked when a paginated collection of address-scope has // reached the end of a page and the pager seeks to traverse over a new one. // In order to do this, it needs to construct the next page's URL. -func (r AddressScopePage) NextPageURL() (string, error) { +func (r AddressScopePage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"address_scopes_links"` } diff --git a/openstack/networking/v2/extensions/layer3/addressscopes/testing/requests_test.go b/openstack/networking/v2/extensions/layer3/addressscopes/testing/requests_test.go index b59bd3893d..f8bb9108ae 100644 --- a/openstack/networking/v2/extensions/layer3/addressscopes/testing/requests_test.go +++ b/openstack/networking/v2/extensions/layer3/addressscopes/testing/requests_test.go @@ -13,22 +13,22 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/address-scopes", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/address-scopes", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, AddressScopesListResult) + fmt.Fprint(w, AddressScopesListResult) }) count := 0 - err := addressscopes.List(fake.ServiceClient(), addressscopes.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := addressscopes.List(fake.ServiceClient(fakeServer), addressscopes.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := addressscopes.ExtractAddressScopes(page) if err != nil { @@ -53,35 +53,35 @@ func TestList(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/address-scopes/9cc35860-522a-4d35-974d-51d4b011801e", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/address-scopes/9cc35860-522a-4d35-974d-51d4b011801e", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, AddressScopesGetResult) + fmt.Fprint(w, AddressScopesGetResult) }) - s, err := addressscopes.Get(context.TODO(), fake.ServiceClient(), "9cc35860-522a-4d35-974d-51d4b011801e").Extract() + s, err := addressscopes.Get(context.TODO(), fake.ServiceClient(fakeServer), "9cc35860-522a-4d35-974d-51d4b011801e").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.ID, "9cc35860-522a-4d35-974d-51d4b011801e") - th.AssertEquals(t, s.Name, "scopev4") - th.AssertEquals(t, s.TenantID, "4a9807b773404e979b19633f38370643") - th.AssertEquals(t, s.ProjectID, "4a9807b773404e979b19633f38370643") - th.AssertEquals(t, s.IPVersion, 4) - th.AssertEquals(t, s.Shared, false) + th.AssertEquals(t, "9cc35860-522a-4d35-974d-51d4b011801e", s.ID) + th.AssertEquals(t, "scopev4", s.Name) + th.AssertEquals(t, "4a9807b773404e979b19633f38370643", s.TenantID) + th.AssertEquals(t, "4a9807b773404e979b19633f38370643", s.ProjectID) + th.AssertEquals(t, 4, s.IPVersion) + th.AssertFalse(t, s.Shared) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/address-scopes", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/address-scopes", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -91,7 +91,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, AddressScopeCreateResult) + fmt.Fprint(w, AddressScopeCreateResult) }) opts := addressscopes.CreateOpts{ @@ -99,22 +99,22 @@ func TestCreate(t *testing.T) { Shared: true, Name: "test0", } - s, err := addressscopes.Create(context.TODO(), fake.ServiceClient(), opts).Extract() + s, err := addressscopes.Create(context.TODO(), fake.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "test0") - th.AssertEquals(t, s.Shared, true) - th.AssertEquals(t, s.IPVersion, 4) - th.AssertEquals(t, s.TenantID, "4a9807b773404e979b19633f38370643") - th.AssertEquals(t, s.ProjectID, "4a9807b773404e979b19633f38370643") - th.AssertEquals(t, s.ID, "9cc35860-522a-4d35-974d-51d4b011801e") + th.AssertEquals(t, "test0", s.Name) + th.AssertTrue(t, s.Shared) + th.AssertEquals(t, 4, s.IPVersion) + th.AssertEquals(t, "4a9807b773404e979b19633f38370643", s.TenantID) + th.AssertEquals(t, "4a9807b773404e979b19633f38370643", s.ProjectID) + th.AssertEquals(t, "9cc35860-522a-4d35-974d-51d4b011801e", s.ID) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/address-scopes/9cc35860-522a-4d35-974d-51d4b011801e", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/address-scopes/9cc35860-522a-4d35-974d-51d4b011801e", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -124,7 +124,7 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, AddressScopeUpdateResult) + fmt.Fprint(w, AddressScopeUpdateResult) }) shared := true @@ -133,23 +133,23 @@ func TestUpdate(t *testing.T) { Name: &newName, Shared: &shared, } - s, err := addressscopes.Update(context.TODO(), fake.ServiceClient(), "9cc35860-522a-4d35-974d-51d4b011801e", updateOpts).Extract() + s, err := addressscopes.Update(context.TODO(), fake.ServiceClient(fakeServer), "9cc35860-522a-4d35-974d-51d4b011801e", updateOpts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "test1") - th.AssertEquals(t, s.Shared, true) + th.AssertEquals(t, "test1", s.Name) + th.AssertTrue(t, s.Shared) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/address-scopes/9cc35860-522a-4d35-974d-51d4b011801e", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/address-scopes/9cc35860-522a-4d35-974d-51d4b011801e", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := addressscopes.Delete(context.TODO(), fake.ServiceClient(), "9cc35860-522a-4d35-974d-51d4b011801e") + res := addressscopes.Delete(context.TODO(), fake.ServiceClient(fakeServer), "9cc35860-522a-4d35-974d-51d4b011801e") th.AssertNoErr(t, res.Err) } diff --git a/openstack/networking/v2/extensions/layer3/extraroutes/testing/requests_test.go b/openstack/networking/v2/extensions/layer3/extraroutes/testing/requests_test.go index 66d08c8898..c9aa0864a4 100644 --- a/openstack/networking/v2/extensions/layer3/extraroutes/testing/requests_test.go +++ b/openstack/networking/v2/extensions/layer3/extraroutes/testing/requests_test.go @@ -13,10 +13,10 @@ import ( ) func TestAddExtraRoutes(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/routers/4e8e5957-649f-477b-9e5b-f1f75b21c03c/add_extraroutes", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/routers/4e8e5957-649f-477b-9e5b-f1f75b21c03c/add_extraroutes", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -35,7 +35,7 @@ func TestAddExtraRoutes(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "router": { "name": "name", @@ -63,10 +63,10 @@ func TestAddExtraRoutes(t *testing.T) { } options := extraroutes.Opts{Routes: &r} - n, err := extraroutes.Add(context.TODO(), fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", options).Extract() + n, err := extraroutes.Add(context.TODO(), fake.ServiceClient(fakeServer), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", options).Extract() th.AssertNoErr(t, err) - th.AssertDeepEquals(t, n.Routes, []routers.Route{ + th.AssertDeepEquals(t, []routers.Route{ { DestinationCIDR: "10.0.1.0/24", NextHop: "10.0.0.11", @@ -83,14 +83,14 @@ func TestAddExtraRoutes(t *testing.T) { DestinationCIDR: "10.0.4.0/24", NextHop: "10.0.0.14", }, - }) + }, n.Routes) } func TestRemoveExtraRoutes(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/routers/4e8e5957-649f-477b-9e5b-f1f75b21c03c/remove_extraroutes", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/routers/4e8e5957-649f-477b-9e5b-f1f75b21c03c/remove_extraroutes", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -109,7 +109,7 @@ func TestRemoveExtraRoutes(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "router": { "name": "name", @@ -135,10 +135,10 @@ func TestRemoveExtraRoutes(t *testing.T) { } options := extraroutes.Opts{Routes: &r} - n, err := extraroutes.Remove(context.TODO(), fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", options).Extract() + n, err := extraroutes.Remove(context.TODO(), fake.ServiceClient(fakeServer), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", options).Extract() th.AssertNoErr(t, err) - th.AssertDeepEquals(t, n.Routes, []routers.Route{ + th.AssertDeepEquals(t, []routers.Route{ { DestinationCIDR: "10.0.1.0/24", NextHop: "10.0.0.11", @@ -147,5 +147,5 @@ func TestRemoveExtraRoutes(t *testing.T) { DestinationCIDR: "10.0.2.0/24", NextHop: "10.0.0.12", }, - }) + }, n.Routes) } diff --git a/openstack/networking/v2/extensions/layer3/floatingips/constants.go b/openstack/networking/v2/extensions/layer3/floatingips/constants.go new file mode 100644 index 0000000000..85dff7818c --- /dev/null +++ b/openstack/networking/v2/extensions/layer3/floatingips/constants.go @@ -0,0 +1,7 @@ +package floatingips + +const ( + StatusActive = "ACTIVE" + StatusDown = "DOWN" + StatusError = "ERROR" +) diff --git a/openstack/networking/v2/extensions/layer3/floatingips/requests.go b/openstack/networking/v2/extensions/layer3/floatingips/requests.go index a3afb0403c..be8949d693 100644 --- a/openstack/networking/v2/extensions/layer3/floatingips/requests.go +++ b/openstack/networking/v2/extensions/layer3/floatingips/requests.go @@ -2,6 +2,7 @@ package floatingips import ( "context" + "fmt" "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/pagination" @@ -37,6 +38,7 @@ type ListOpts struct { TagsAny string `q:"tags-any"` NotTags string `q:"not-tags"` NotTagsAny string `q:"not-tags-any"` + RevisionNumber *int `q:"revision_number"` } // ToNetworkListQuery formats a ListOpts into a query string. @@ -144,6 +146,11 @@ type UpdateOpts struct { Description *string `json:"description,omitempty"` PortID *string `json:"port_id,omitempty"` FixedIP string `json:"fixed_ip_address,omitempty"` + + // RevisionNumber implements extension:standard-attr-revisions. If != "" it + // will set revision_number=%s. If the revision number does not match, the + // update will fail. + RevisionNumber *int `json:"-" h:"If-Match"` } // ToFloatingIPUpdateMap allows UpdateOpts to satisfy the UpdateOptsBuilder @@ -171,8 +178,19 @@ func Update(ctx context.Context, c *gophercloud.ServiceClient, id string, opts U r.Err = err return } + h, err := gophercloud.BuildHeaders(opts) + if err != nil { + r.Err = err + return + } + for k := range h { + if k == "If-Match" { + h[k] = fmt.Sprintf("revision_number=%s", h[k]) + } + } resp, err := c.Put(ctx, resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, + MoreHeaders: h, + OkCodes: []int{200}, }) _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return diff --git a/openstack/networking/v2/extensions/layer3/floatingips/results.go b/openstack/networking/v2/extensions/layer3/floatingips/results.go index 50740ebf30..474146ffe9 100644 --- a/openstack/networking/v2/extensions/layer3/floatingips/results.go +++ b/openstack/networking/v2/extensions/layer3/floatingips/results.go @@ -56,6 +56,9 @@ type FloatingIP struct { // Tags optionally set via extensions/attributestags Tags []string `json:"tags"` + + // RevisionNumber optionally set via extensions/standard-attr-revisions + RevisionNumber int `json:"revision_number"` } func (r *FloatingIP) UnmarshalJSON(b []byte) error { @@ -108,7 +111,7 @@ func (r commonResult) Extract() (*FloatingIP, error) { } func (r commonResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "floatingip") + return r.ExtractIntoStructPtr(v, "floatingip") } // CreateResult represents the result of a create operation. Call its Extract @@ -144,7 +147,7 @@ type FloatingIPPage struct { // NextPageURL is invoked when a paginated collection of floating IPs has // reached the end of a page and the pager seeks to traverse over a new one. // In order to do this, it needs to construct the next page's URL. -func (r FloatingIPPage) NextPageURL() (string, error) { +func (r FloatingIPPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"floatingips_links"` } @@ -177,5 +180,5 @@ func ExtractFloatingIPs(r pagination.Page) ([]FloatingIP, error) { } func ExtractFloatingIPsInto(r pagination.Page, v any) error { - return r.(FloatingIPPage).Result.ExtractIntoSlicePtr(v, "floatingips") + return r.(FloatingIPPage).ExtractIntoSlicePtr(v, "floatingips") } diff --git a/openstack/networking/v2/extensions/layer3/floatingips/testing/requests_test.go b/openstack/networking/v2/extensions/layer3/floatingips/testing/requests_test.go index 92830dabb4..4447b04ad4 100644 --- a/openstack/networking/v2/extensions/layer3/floatingips/testing/requests_test.go +++ b/openstack/networking/v2/extensions/layer3/floatingips/testing/requests_test.go @@ -14,22 +14,22 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/floatingips", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/floatingips", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) count := 0 - err := floatingips.List(fake.ServiceClient(), floatingips.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := floatingips.List(fake.ServiceClient(fakeServer), floatingips.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := floatingips.ExtractFloatingIPs(page) if err != nil { @@ -82,16 +82,16 @@ func TestList(t *testing.T) { } func TestInvalidNextPageURLs(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/floatingips", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/floatingips", func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, `{"floatingips": [{}], "floatingips_links": {}}`) + fmt.Fprint(w, `{"floatingips": [{}], "floatingips_links": {}}`) }) - err := floatingips.List(fake.ServiceClient(), floatingips.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := floatingips.List(fake.ServiceClient(fakeServer), floatingips.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { _, err := floatingips.ExtractFloatingIPs(page) if err != nil { return false, err @@ -101,23 +101,26 @@ func TestInvalidNextPageURLs(t *testing.T) { th.AssertErr(t, err) } -func TestRequiredFieldsForCreate(t *testing.T) { - res1 := floatingips.Create(context.TODO(), fake.ServiceClient(), floatingips.CreateOpts{FloatingNetworkID: ""}) +func TestRequiredCreateOpts(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + res1 := floatingips.Create(context.TODO(), fake.ServiceClient(fakeServer), floatingips.CreateOpts{FloatingNetworkID: ""}) if res1.Err == nil { t.Fatalf("Expected error, got none") } - res2 := floatingips.Create(context.TODO(), fake.ServiceClient(), floatingips.CreateOpts{FloatingNetworkID: "foo", PortID: ""}) + res2 := floatingips.Create(context.TODO(), fake.ServiceClient(fakeServer), floatingips.CreateOpts{FloatingNetworkID: "foo", PortID: ""}) if res2.Err == nil { t.Fatalf("Expected error, got none") } } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/floatingips", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/floatingips", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -134,7 +137,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "floatingip": { "router_id": "d23abc8d-2991-4a55-ba98-2aaea84cc72f", @@ -156,7 +159,7 @@ func TestCreate(t *testing.T) { PortID: "ce705c24-c1ef-408a-bda3-7bbd946164ab", } - ip, err := floatingips.Create(context.TODO(), fake.ServiceClient(), options).Extract() + ip, err := floatingips.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "2f245a7b-796b-4f26-9cf9-9e82d248fda7", ip.ID) @@ -170,10 +173,10 @@ func TestCreate(t *testing.T) { } func TestCreateEmptyPort(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/floatingips", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/floatingips", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -189,7 +192,7 @@ func TestCreateEmptyPort(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "floatingip": { "router_id": "d23abc8d-2991-4a55-ba98-2aaea84cc72f", @@ -209,7 +212,7 @@ func TestCreateEmptyPort(t *testing.T) { FloatingNetworkID: "376da547-b977-4cfe-9cba-275c80debf57", } - ip, err := floatingips.Create(context.TODO(), fake.ServiceClient(), options).Extract() + ip, err := floatingips.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "2f245a7b-796b-4f26-9cf9-9e82d248fda7", ip.ID) @@ -223,10 +226,10 @@ func TestCreateEmptyPort(t *testing.T) { } func TestCreateWithSubnetID(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/floatingips", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/floatingips", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -243,7 +246,7 @@ func TestCreateWithSubnetID(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "floatingip": { "router_id": null, @@ -265,7 +268,7 @@ func TestCreateWithSubnetID(t *testing.T) { SubnetID: "37adf01c-24db-467a-b845-7ab1e8216c01", } - ip, err := floatingips.Create(context.TODO(), fake.ServiceClient(), options).Extract() + ip, err := floatingips.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "2f245a7b-796b-4f26-9cf9-9e82d248fda7", ip.ID) @@ -279,17 +282,17 @@ func TestCreateWithSubnetID(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/floatingips/2f245a7b-796b-4f26-9cf9-9e82d248fda7", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/floatingips/2f245a7b-796b-4f26-9cf9-9e82d248fda7", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "floatingip": { "floating_network_id": "90f742b1-6d17-487b-ba95-71881dbc0b64", @@ -307,7 +310,7 @@ func TestGet(t *testing.T) { `) }) - ip, err := floatingips.Get(context.TODO(), fake.ServiceClient(), "2f245a7b-796b-4f26-9cf9-9e82d248fda7").Extract() + ip, err := floatingips.Get(context.TODO(), fake.ServiceClient(fakeServer), "2f245a7b-796b-4f26-9cf9-9e82d248fda7").Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "90f742b1-6d17-487b-ba95-71881dbc0b64", ip.FloatingNetworkID) @@ -323,10 +326,10 @@ func TestGet(t *testing.T) { } func TestAssociate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/floatingips/2f245a7b-796b-4f26-9cf9-9e82d248fda7", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/floatingips/2f245a7b-796b-4f26-9cf9-9e82d248fda7", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -342,7 +345,7 @@ func TestAssociate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "floatingip": { "router_id": "d23abc8d-2991-4a55-ba98-2aaea84cc72f", @@ -358,17 +361,17 @@ func TestAssociate(t *testing.T) { }) portID := "423abc8d-2991-4a55-ba98-2aaea84cc72e" - ip, err := floatingips.Update(context.TODO(), fake.ServiceClient(), "2f245a7b-796b-4f26-9cf9-9e82d248fda7", floatingips.UpdateOpts{PortID: &portID}).Extract() + ip, err := floatingips.Update(context.TODO(), fake.ServiceClient(fakeServer), "2f245a7b-796b-4f26-9cf9-9e82d248fda7", floatingips.UpdateOpts{PortID: &portID}).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, portID, ip.PortID) } func TestDisassociate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/floatingips/2f245a7b-796b-4f26-9cf9-9e82d248fda7", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/floatingips/2f245a7b-796b-4f26-9cf9-9e82d248fda7", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -384,7 +387,7 @@ func TestDisassociate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "floatingip": { "router_id": "d23abc8d-2991-4a55-ba98-2aaea84cc72f", @@ -399,7 +402,7 @@ func TestDisassociate(t *testing.T) { `) }) - ip, err := floatingips.Update(context.TODO(), fake.ServiceClient(), "2f245a7b-796b-4f26-9cf9-9e82d248fda7", floatingips.UpdateOpts{PortID: new(string)}).Extract() + ip, err := floatingips.Update(context.TODO(), fake.ServiceClient(fakeServer), "2f245a7b-796b-4f26-9cf9-9e82d248fda7", floatingips.UpdateOpts{PortID: new(string)}).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, "", ip.FixedIP) @@ -407,15 +410,15 @@ func TestDisassociate(t *testing.T) { } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/floatingips/2f245a7b-796b-4f26-9cf9-9e82d248fda7", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/floatingips/2f245a7b-796b-4f26-9cf9-9e82d248fda7", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := floatingips.Delete(context.TODO(), fake.ServiceClient(), "2f245a7b-796b-4f26-9cf9-9e82d248fda7") + res := floatingips.Delete(context.TODO(), fake.ServiceClient(fakeServer), "2f245a7b-796b-4f26-9cf9-9e82d248fda7") th.AssertNoErr(t, res.Err) } diff --git a/openstack/networking/v2/extensions/layer3/portforwarding/doc.go b/openstack/networking/v2/extensions/layer3/portforwarding/doc.go index bf67b92b6b..b010f62177 100644 --- a/openstack/networking/v2/extensions/layer3/portforwarding/doc.go +++ b/openstack/networking/v2/extensions/layer3/portforwarding/doc.go @@ -44,6 +44,22 @@ Example to Create a Port Forwarding for a floating IP panic(err) } +or, for port ranges + + createOpts := &portforwarding.CreateOpts{ + Protocol: "tcp", + InternalPortRange: "100:199", + ExternalPortRange: "1500:1599", + InternalIPAddress: internalIP, + InternalPortID: portID, + } + + pf, err := portforwarding.Create(context.TODO(), networkingClient, floatingIPID, createOpts).Extract() + + if err != nil { + panic(err) + } + Example to Update a Port Forwarding updateOpts := portforwarding.UpdateOpts{ diff --git a/openstack/networking/v2/extensions/layer3/portforwarding/requests.go b/openstack/networking/v2/extensions/layer3/portforwarding/requests.go index 1b07b6b7c9..49b7842389 100644 --- a/openstack/networking/v2/extensions/layer3/portforwarding/requests.go +++ b/openstack/networking/v2/extensions/layer3/portforwarding/requests.go @@ -21,6 +21,7 @@ type ListOpts struct { Description string `q:"description"` InternalPortID string `q:"internal_port_id"` ExternalPort string `q:"external_port"` + ExternalPortRange string `q:"external_port_range"` InternalIPAddress string `q:"internal_ip_address"` Protocol string `q:"protocol"` InternalPort string `q:"internal_port"` @@ -62,13 +63,15 @@ func Get(ctx context.Context, c *gophercloud.ServiceClient, floatingIpId string, } // CreateOpts contains all the values needed to create a new port forwarding -// resource. All attributes are required. +// resource. Internal/External ports and port ranges are mutually exclusive. type CreateOpts struct { Description string `json:"description,omitempty"` InternalPortID string `json:"internal_port_id"` InternalIPAddress string `json:"internal_ip_address"` - InternalPort int `json:"internal_port"` - ExternalPort int `json:"external_port"` + InternalPort int `json:"internal_port,omitempty"` + InternalPortRange string `json:"internal_port_range,omitempty"` + ExternalPort int `json:"external_port,omitempty"` + ExternalPortRange string `json:"external_port_range,omitempty"` Protocol string `json:"protocol"` } @@ -98,12 +101,15 @@ func Create(ctx context.Context, c *gophercloud.ServiceClient, floatingIpId stri } // UpdateOpts contains the values used when updating a port forwarding resource. +// Only Internal/External port values OR range values should be specified, not both or mixed. type UpdateOpts struct { Description *string `json:"description,omitempty"` InternalPortID string `json:"internal_port_id,omitempty"` InternalIPAddress string `json:"internal_ip_address,omitempty"` InternalPort int `json:"internal_port,omitempty"` + InternalPortRange string `json:"internal_port_range,omitempty"` ExternalPort int `json:"external_port,omitempty"` + ExternalPortRange string `json:"external_port_range,omitempty"` Protocol string `json:"protocol,omitempty"` } diff --git a/openstack/networking/v2/extensions/layer3/portforwarding/results.go b/openstack/networking/v2/extensions/layer3/portforwarding/results.go index 89e71efd0e..0c31b84528 100644 --- a/openstack/networking/v2/extensions/layer3/portforwarding/results.go +++ b/openstack/networking/v2/extensions/layer3/portforwarding/results.go @@ -19,6 +19,9 @@ type PortForwarding struct { // The TCP/UDP/other protocol port number of the port forwarding’s floating IP address. ExternalPort int `json:"external_port"` + // The TCP/UDP/other protocol port range of the port forwarding’s floating IP address. + ExternalPortRange string `json:"external_port_range"` + // The IP protocol used in the floating IP port forwarding. Protocol string `json:"protocol"` @@ -26,6 +29,10 @@ type PortForwarding struct { // IP address associated to the floating ip port forwarding. InternalPort int `json:"internal_port"` + // The TCP/UDP/other protocol port range of the Neutron port fixed + // IP address associated to the floating ip port forwarding. + InternalPortRange string `json:"internal_port_range"` + // The fixed IPv4 address of the Neutron port associated // to the floating IP port forwarding. InternalIPAddress string `json:"internal_ip_address"` @@ -67,7 +74,7 @@ func (r commonResult) Extract() (*PortForwarding, error) { } func (r commonResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "port_forwarding") + return r.ExtractIntoStructPtr(v, "port_forwarding") } // PortForwardingPage is the page returned by a pager when traversing over a @@ -79,7 +86,7 @@ type PortForwardingPage struct { // NextPageURL is invoked when a paginated collection of port forwardings has // reached the end of a page and the pager seeks to traverse over a new one. // In order to do this, it needs to construct the next page's URL. -func (r PortForwardingPage) NextPageURL() (string, error) { +func (r PortForwardingPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"port_forwarding_links"` } diff --git a/openstack/networking/v2/extensions/layer3/portforwarding/testing/fixtures_test.go b/openstack/networking/v2/extensions/layer3/portforwarding/testing/fixtures_test.go index b9362c4859..d479e0577f 100644 --- a/openstack/networking/v2/extensions/layer3/portforwarding/testing/fixtures_test.go +++ b/openstack/networking/v2/extensions/layer3/portforwarding/testing/fixtures_test.go @@ -20,11 +20,21 @@ const PoFw_second = `{ "id": "e0a0274e-4d19-4eab-9e12-9e77a8caf3ea" }` +const PoFw_third = `{ + "protocol": "tcp", + "internal_ip_address": "10.0.0.19", + "internal_port_range": "1200:1299", + "internal_port_id": "dba563d2-aa9e-4a21-8cc2-3e0bdec9015a", + "external_port_range": "1100:1199", + "id": "f3a9f921-6bed-492b-a3fa-32a76fdd0159" + }` + var ListResponse = fmt.Sprintf(` { "port_forwardings": [ %s, +%s, %s ] } -`, PoFw, PoFw_second) +`, PoFw, PoFw_second, PoFw_third) diff --git a/openstack/networking/v2/extensions/layer3/portforwarding/testing/requests_test.go b/openstack/networking/v2/extensions/layer3/portforwarding/testing/requests_test.go index c4ad108146..384354c3f5 100644 --- a/openstack/networking/v2/extensions/layer3/portforwarding/testing/requests_test.go +++ b/openstack/networking/v2/extensions/layer3/portforwarding/testing/requests_test.go @@ -13,22 +13,22 @@ import ( ) func TestPortForwardingList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/floatingips/2f95fd2b-9f6a-4e8e-9e9a-2cbe286cbf9e/port_forwardings", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/floatingips/2f95fd2b-9f6a-4e8e-9e9a-2cbe286cbf9e/port_forwardings", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) count := 0 - err := portforwarding.List(fake.ServiceClient(), portforwarding.ListOpts{}, "2f95fd2b-9f6a-4e8e-9e9a-2cbe286cbf9e").EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := portforwarding.List(fake.ServiceClient(fakeServer), portforwarding.ListOpts{}, "2f95fd2b-9f6a-4e8e-9e9a-2cbe286cbf9e").EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := portforwarding.ExtractPortForwardings(page) if err != nil { @@ -53,6 +53,14 @@ func TestPortForwardingList(t *testing.T) { ExternalPort: 2230, ID: "e0a0274e-4d19-4eab-9e12-9e77a8caf3ea", }, + { + Protocol: "tcp", + InternalIPAddress: "10.0.0.19", + InternalPortRange: "1200:1299", + InternalPortID: "dba563d2-aa9e-4a21-8cc2-3e0bdec9015a", + ExternalPortRange: "1100:1199", + ID: "f3a9f921-6bed-492b-a3fa-32a76fdd0159", + }, } th.CheckDeepEquals(t, expected, actual) @@ -67,17 +75,17 @@ func TestPortForwardingList(t *testing.T) { } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/floatingips/2f95fd2b-9f6a-4e8e-9e9a-2cbe286cbf9e/port_forwardings", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/floatingips/2f95fd2b-9f6a-4e8e-9e9a-2cbe286cbf9e/port_forwardings", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` { - "port_forwarding": { + "port_forwarding": { "protocol": "tcp", "internal_ip_address": "10.0.0.11", "internal_port": 25, @@ -85,21 +93,20 @@ func TestCreate(t *testing.T) { "external_port": 2230 } } - - `) +`) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { - "port_forwarding": { - "protocol": "tcp", - "internal_ip_address": "10.0.0.11", - "internal_port": 25, - "internal_port_id": "1238be08-a2a8-4b8d-addf-fb5e2250e480", - "external_port": 2230, - "id": "725ade3c-9760-4880-8080-8fc2dbab9acc" + "port_forwarding": { + "protocol": "tcp", + "internal_ip_address": "10.0.0.11", + "internal_port": 25, + "internal_port_id": "1238be08-a2a8-4b8d-addf-fb5e2250e480", + "external_port": 2230, + "id": "725ade3c-9760-4880-8080-8fc2dbab9acc" } }`) }) @@ -112,7 +119,7 @@ func TestCreate(t *testing.T) { InternalPortID: "1238be08-a2a8-4b8d-addf-fb5e2250e480", } - pf, err := portforwarding.Create(context.TODO(), fake.ServiceClient(), "2f95fd2b-9f6a-4e8e-9e9a-2cbe286cbf9e", options).Extract() + pf, err := portforwarding.Create(context.TODO(), fake.ServiceClient(fakeServer), "2f95fd2b-9f6a-4e8e-9e9a-2cbe286cbf9e", options).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "725ade3c-9760-4880-8080-8fc2dbab9acc", pf.ID) @@ -123,18 +130,74 @@ func TestCreate(t *testing.T) { th.AssertEquals(t, "tcp", pf.Protocol) } +func TestCreateRange(t *testing.T) { + // Test port range + fakeServerRange := th.SetupHTTP() + defer fakeServerRange.Teardown() + fakeServerRange.Mux.HandleFunc("/v2.0/floatingips/f40600c2-f240-4bb0-aa76-f6f18b2aaab1/port_forwardings", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "port_forwarding": { + "protocol": "tcp", + "internal_ip_address": "10.0.0.19", + "internal_port_range": "1200:1299", + "internal_port_id": "dba563d2-aa9e-4a21-8cc2-3e0bdec9015a", + "external_port_range": "1100:1199" + } +} +`) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprint(w, ` +{ + "port_forwarding": { + "protocol": "tcp", + "internal_ip_address": "10.0.0.19", + "internal_port_range": "1200:1299", + "internal_port_id": "dba563d2-aa9e-4a21-8cc2-3e0bdec9015a", + "external_port_range": "1100:1199", + "id": "f3a9f921-6bed-492b-a3fa-32a76fdd0159" + } +}`) + }) + + options := portforwarding.CreateOpts{ + Protocol: "tcp", + InternalIPAddress: "10.0.0.19", + InternalPortRange: "1200:1299", + ExternalPortRange: "1100:1199", + InternalPortID: "dba563d2-aa9e-4a21-8cc2-3e0bdec9015a", + } + + pf, err := portforwarding.Create(context.TODO(), fake.ServiceClient(fakeServerRange), "f40600c2-f240-4bb0-aa76-f6f18b2aaab1", options).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, "f3a9f921-6bed-492b-a3fa-32a76fdd0159", pf.ID) + th.AssertEquals(t, "10.0.0.19", pf.InternalIPAddress) + th.AssertEquals(t, "1200:1299", pf.InternalPortRange) + th.AssertEquals(t, "dba563d2-aa9e-4a21-8cc2-3e0bdec9015a", pf.InternalPortID) + th.AssertEquals(t, "1100:1199", pf.ExternalPortRange) + th.AssertEquals(t, "tcp", pf.Protocol) +} + func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/floatingips/2f245a7b-796b-4f26-9cf9-9e82d248fda7/port_forwardings/725ade3c-9760-4880-8080-8fc2dbab9acc", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/floatingips/2f245a7b-796b-4f26-9cf9-9e82d248fda7/port_forwardings/725ade3c-9760-4880-8080-8fc2dbab9acc", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "port_forwarding": { "protocol": "tcp", @@ -148,7 +211,7 @@ func TestGet(t *testing.T) { `) }) - pf, err := portforwarding.Get(context.TODO(), fake.ServiceClient(), "2f245a7b-796b-4f26-9cf9-9e82d248fda7", "725ade3c-9760-4880-8080-8fc2dbab9acc").Extract() + pf, err := portforwarding.Get(context.TODO(), fake.ServiceClient(fakeServer), "2f245a7b-796b-4f26-9cf9-9e82d248fda7", "725ade3c-9760-4880-8080-8fc2dbab9acc").Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "tcp", pf.Protocol) @@ -159,25 +222,61 @@ func TestGet(t *testing.T) { th.AssertEquals(t, 2230, pf.ExternalPort) } +func TestGetRange(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/floatingips/2f245a7b-796b-4f26-9cf9-9e82d248fda7/port_forwardings/725ade3c-9760-4880-8080-8fc2dbab9acc", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ` +{ + "port_forwarding": { + "protocol": "tcp", + "internal_ip_address": "10.0.0.11", + "internal_port_range": "1200:1299", + "internal_port_id": "1238be08-a2a8-4b8d-addf-fb5e2250e480", + "external_port_range": "1100:1199", + "id": "725ade3c-9760-4880-8080-8fc2dbab9acc" + } +} + `) + }) + + pf, err := portforwarding.Get(context.TODO(), fake.ServiceClient(fakeServer), "2f245a7b-796b-4f26-9cf9-9e82d248fda7", "725ade3c-9760-4880-8080-8fc2dbab9acc").Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, "tcp", pf.Protocol) + th.AssertEquals(t, "725ade3c-9760-4880-8080-8fc2dbab9acc", pf.ID) + th.AssertEquals(t, "10.0.0.11", pf.InternalIPAddress) + th.AssertEquals(t, "1200:1299", pf.InternalPortRange) + th.AssertEquals(t, "1238be08-a2a8-4b8d-addf-fb5e2250e480", pf.InternalPortID) + th.AssertEquals(t, "1100:1199", pf.ExternalPortRange) +} + func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/floatingips/2f245a7b-796b-4f26-9cf9-9e82d248fda7/port_forwardings/725ade3c-9760-4880-8080-8fc2dbab9acc", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/floatingips/2f245a7b-796b-4f26-9cf9-9e82d248fda7/port_forwardings/725ade3c-9760-4880-8080-8fc2dbab9acc", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := portforwarding.Delete(context.TODO(), fake.ServiceClient(), "2f245a7b-796b-4f26-9cf9-9e82d248fda7", "725ade3c-9760-4880-8080-8fc2dbab9acc") + res := portforwarding.Delete(context.TODO(), fake.ServiceClient(fakeServer), "2f245a7b-796b-4f26-9cf9-9e82d248fda7", "725ade3c-9760-4880-8080-8fc2dbab9acc") th.AssertNoErr(t, res.Err) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/floatingips/2f245a7b-796b-4f26-9cf9-9e82d248fda7/port_forwardings/725ade3c-9760-4880-8080-8fc2dbab9acc", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/floatingips/2f245a7b-796b-4f26-9cf9-9e82d248fda7/port_forwardings/725ade3c-9760-4880-8080-8fc2dbab9acc", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -196,7 +295,7 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "port_forwarding": { "protocol": "udp", @@ -221,7 +320,7 @@ func TestUpdate(t *testing.T) { ExternalPort: updatedExternalPort, } - actual, err := portforwarding.Update(context.TODO(), fake.ServiceClient(), "2f245a7b-796b-4f26-9cf9-9e82d248fda7", "725ade3c-9760-4880-8080-8fc2dbab9acc", options).Extract() + actual, err := portforwarding.Update(context.TODO(), fake.ServiceClient(fakeServer), "2f245a7b-796b-4f26-9cf9-9e82d248fda7", "725ade3c-9760-4880-8080-8fc2dbab9acc", options).Extract() th.AssertNoErr(t, err) expected := portforwarding.PortForwarding{ Protocol: "udp", @@ -233,3 +332,64 @@ func TestUpdate(t *testing.T) { } th.AssertDeepEquals(t, expected, *actual) } + +func TestUpdateRange(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/floatingips/2f245a7b-796b-4f26-9cf9-9e82d248fda7/port_forwardings/f3a9f921-6bed-492b-a3fa-32a76fdd0159", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "port_forwarding": { + "protocol": "udp", + "internal_port_range": "1500:1599", + "internal_port_id": "99889dc2-19a7-4edb-b9d0-d2ace8d1e144", + "external_port_range": "23000:23099" + } +} +`) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ` +{ + "port_forwarding": { + "protocol": "udp", + "internal_ip_address": "10.0.0.14", + "internal_port_range": "1500:1599", + "internal_port_id": "99889dc2-19a7-4edb-b9d0-d2ace8d1e144", + "external_port_range": "23000:23099", + "id": "f3a9f921-6bed-492b-a3fa-32a76fdd0159" + } +} +`) + }) + + updatedProtocol := "udp" + updatedInternalPortRange := "1500:1599" + updatedInternalPortID := "99889dc2-19a7-4edb-b9d0-d2ace8d1e144" + updatedExternalPortRange := "23000:23099" + options := portforwarding.UpdateOpts{ + Protocol: updatedProtocol, + InternalPortRange: updatedInternalPortRange, + InternalPortID: updatedInternalPortID, + ExternalPortRange: updatedExternalPortRange, + } + + actual, err := portforwarding.Update(context.TODO(), fake.ServiceClient(fakeServer), "2f245a7b-796b-4f26-9cf9-9e82d248fda7", "f3a9f921-6bed-492b-a3fa-32a76fdd0159", options).Extract() + th.AssertNoErr(t, err) + expected := portforwarding.PortForwarding{ + Protocol: "udp", + InternalIPAddress: "10.0.0.14", + InternalPortRange: "1500:1599", + ID: "f3a9f921-6bed-492b-a3fa-32a76fdd0159", + InternalPortID: "99889dc2-19a7-4edb-b9d0-d2ace8d1e144", + ExternalPortRange: "23000:23099", + } + th.AssertDeepEquals(t, expected, *actual) +} diff --git a/openstack/networking/v2/extensions/layer3/routers/doc.go b/openstack/networking/v2/extensions/layer3/routers/doc.go index 42ccbcf7cf..772c9e3bbf 100644 --- a/openstack/networking/v2/extensions/layer3/routers/doc.go +++ b/openstack/networking/v2/extensions/layer3/routers/doc.go @@ -135,5 +135,67 @@ Example to List an L3 agents for a Router for _, agent := range allL3Agents { fmt.Printf("%+v\n", agent) } + +Example to Add External Gateways to a Router: This requires the external-gateway-multihoming extension. + + routerID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + + addOpts := routers.AddExternalGatewaysOpts{ + ExternalGateways: []routers.GatewayInfo{ + { + NetworkID: "8ca37218-28ff-41cb-9b10-039601ea7e6b", + ExternalFixedIPs: []routers.ExternalFixedIP{ + {SubnetID: "ab561bc4-1a8e-48f2-9fbd-376fcb1a1def"}, + }, + }, + }, + } + + router, err := routers.AddExternalGateways(context.TODO(), networkClient, routerID, addOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update External Gateways of a Router: This requires the external-gateway-multihoming extension. + + routerID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + + enableSNAT := true + updateOpts := routers.UpdateExternalGatewaysOpts{ + ExternalGateways: []routers.GatewayInfo{ + { + NetworkID: "8ca37218-28ff-41cb-9b10-039601ea7e6b", + EnableSNAT: &enableSNAT, + ExternalFixedIPs: []routers.ExternalFixedIP{ + {IPAddress: "192.0.2.17", SubnetID: "ab561bc4-1a8e-48f2-9fbd-376fcb1a1def"}, + }, + }, + }, + } + + router, err := routers.UpdateExternalGateways(context.TODO(), networkClient, routerID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Remove External Gateways from a Router: This requires the external-gateway-multihoming extension. + + routerID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + + removeOpts := routers.RemoveExternalGatewaysOpts{ + ExternalGateways: []routers.GatewayInfo{ + { + NetworkID: "8ca37218-28ff-41cb-9b10-039601ea7e6b", + ExternalFixedIPs: []routers.ExternalFixedIP{ + {IPAddress: "192.0.2.17", SubnetID: "ab561bc4-1a8e-48f2-9fbd-376fcb1a1def"}, + }, + }, + }, + } + + router, err := routers.RemoveExternalGateways(context.TODO(), networkClient, routerID, removeOpts).Extract() + if err != nil { + panic(err) + } */ package routers diff --git a/openstack/networking/v2/extensions/layer3/routers/requests.go b/openstack/networking/v2/extensions/layer3/routers/requests.go index dcc7977a46..3e49a30f10 100644 --- a/openstack/networking/v2/extensions/layer3/routers/requests.go +++ b/openstack/networking/v2/extensions/layer3/routers/requests.go @@ -2,33 +2,50 @@ package routers import ( "context" + "fmt" "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/pagination" ) +// ListOptsBuilder allows extensions to add additional parameters to the List +// request. +type ListOptsBuilder interface { + ToRouterListQuery() (string, error) +} + // ListOpts allows the filtering and sorting of paginated collections through // the API. Filtering is achieved by passing in struct field values that map to // the floating IP attributes you want to see returned. SortKey allows you to // sort by a particular network attribute. SortDir sets the direction, and is // either `asc' or `desc'. Marker and Limit are used for pagination. type ListOpts struct { - ID string `q:"id"` - Name string `q:"name"` - Description string `q:"description"` - AdminStateUp *bool `q:"admin_state_up"` - Distributed *bool `q:"distributed"` - Status string `q:"status"` - TenantID string `q:"tenant_id"` - ProjectID string `q:"project_id"` - Limit int `q:"limit"` - Marker string `q:"marker"` - SortKey string `q:"sort_key"` - SortDir string `q:"sort_dir"` - Tags string `q:"tags"` - TagsAny string `q:"tags-any"` - NotTags string `q:"not-tags"` - NotTagsAny string `q:"not-tags-any"` + ID string `q:"id"` + Name string `q:"name"` + Description string `q:"description"` + AdminStateUp *bool `q:"admin_state_up"` + Distributed *bool `q:"distributed"` + Status string `q:"status"` + TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` + Tags string `q:"tags"` + TagsAny string `q:"tags-any"` + NotTags string `q:"not-tags"` + NotTagsAny string `q:"not-tags-any"` + RevisionNumber *int `q:"revision_number"` +} + +// ToRouterListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToRouterListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(&opts) + if err != nil { + return "", err + } + return q.String(), nil } // List returns a Pager which allows you to iterate over a collection of @@ -37,13 +54,16 @@ type ListOpts struct { // // Default policy settings return only those routers that are owned by the // tenant who submits the request, unless an admin user submits the request. -func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { - q, err := gophercloud.BuildQueryString(&opts) - if err != nil { - return pagination.Pager{Err: err} +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToRouterListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query } - u := rootURL(c) + q.String() - return pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page { + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { return RouterPage{pagination.LinkedPageBase{PageResult: r}} }) } @@ -112,6 +132,11 @@ type UpdateOpts struct { Distributed *bool `json:"distributed,omitempty"` GatewayInfo *GatewayInfo `json:"external_gateway_info,omitempty"` Routes *[]Route `json:"routes,omitempty"` + + // RevisionNumber implements extension:standard-attr-revisions. If != "" it + // will set revision_number=%s. If the revision number does not match, the + // update will fail. + RevisionNumber *int `json:"-" h:"If-Match"` } // ToRouterUpdateMap builds an update body based on UpdateOpts. @@ -130,8 +155,19 @@ func Update(ctx context.Context, c *gophercloud.ServiceClient, id string, opts U r.Err = err return } + h, err := gophercloud.BuildHeaders(opts) + if err != nil { + r.Err = err + return + } + for k := range h { + if k == "If-Match" { + h[k] = fmt.Sprintf("revision_number=%s", h[k]) + } + } resp, err := c.Put(ctx, resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, + MoreHeaders: h, + OkCodes: []int{200}, }) _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return @@ -246,3 +282,99 @@ func ListL3Agents(c *gophercloud.ServiceClient, id string) (result pagination.Pa return ListL3AgentsPage{pagination.SinglePageBase(r)} }) } + +// AddExternalGatewaysOptsBuilder allows extensions to add additional parameters +// to the AddExternalGateways request. +type AddExternalGatewaysOptsBuilder interface { + ToRouterAddExternalGatewaysMap() (map[string]any, error) +} + +// AddExternalGatewaysOpts represents the options for adding external gateways +// to a router. +type AddExternalGatewaysOpts struct { + ExternalGateways []GatewayInfo `json:"external_gateways" required:"true"` +} + +// ToRouterAddExternalGatewaysMap builds a request body from AddExternalGatewaysOpts. +func (opts AddExternalGatewaysOpts) ToRouterAddExternalGatewaysMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "router") +} + +// AddExternalGateways adds external gateways to a router. +// This requires the external-gateway-multihoming extension. +func AddExternalGateways(ctx context.Context, c *gophercloud.ServiceClient, id string, opts AddExternalGatewaysOptsBuilder) (r UpdateResult) { + b, err := opts.ToRouterAddExternalGatewaysMap() + if err != nil { + r.Err = err + return + } + resp, err := c.Put(ctx, addExternalGatewaysURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// UpdateExternalGatewaysOptsBuilder allows extensions to add additional +// parameters to the UpdateExternalGateways request. +type UpdateExternalGatewaysOptsBuilder interface { + ToRouterUpdateExternalGatewaysMap() (map[string]any, error) +} + +// UpdateExternalGatewaysOpts represents the options for updating external +// gateways of a router. +type UpdateExternalGatewaysOpts struct { + ExternalGateways []GatewayInfo `json:"external_gateways" required:"true"` +} + +// ToRouterUpdateExternalGatewaysMap builds a request body from UpdateExternalGatewaysOpts. +func (opts UpdateExternalGatewaysOpts) ToRouterUpdateExternalGatewaysMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "router") +} + +// UpdateExternalGateways updates external gateways of a router. +// This requires the external-gateway-multihoming extension. +func UpdateExternalGateways(ctx context.Context, c *gophercloud.ServiceClient, id string, opts UpdateExternalGatewaysOptsBuilder) (r UpdateResult) { + b, err := opts.ToRouterUpdateExternalGatewaysMap() + if err != nil { + r.Err = err + return + } + resp, err := c.Put(ctx, updateExternalGatewaysURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// RemoveExternalGatewaysOptsBuilder allows extensions to add additional +// parameters to the RemoveExternalGateways request. +type RemoveExternalGatewaysOptsBuilder interface { + ToRouterRemoveExternalGatewaysMap() (map[string]any, error) +} + +// RemoveExternalGatewaysOpts represents the options for removing external +// gateways from a router. +type RemoveExternalGatewaysOpts struct { + ExternalGateways []GatewayInfo `json:"external_gateways" required:"true"` +} + +// ToRouterRemoveExternalGatewaysMap builds a request body from RemoveExternalGatewaysOpts. +func (opts RemoveExternalGatewaysOpts) ToRouterRemoveExternalGatewaysMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "router") +} + +// RemoveExternalGateways removes external gateways from a router. +// This requires the external-gateway-multihoming extension. +func RemoveExternalGateways(ctx context.Context, c *gophercloud.ServiceClient, id string, opts RemoveExternalGatewaysOptsBuilder) (r UpdateResult) { + b, err := opts.ToRouterRemoveExternalGatewaysMap() + if err != nil { + r.Err = err + return + } + resp, err := c.Put(ctx, removeExternalGatewaysURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/networking/v2/extensions/layer3/routers/results.go b/openstack/networking/v2/extensions/layer3/routers/results.go index 25e8ab7b59..73f3b93134 100644 --- a/openstack/networking/v2/extensions/layer3/routers/results.go +++ b/openstack/networking/v2/extensions/layer3/routers/results.go @@ -21,7 +21,7 @@ type GatewayInfo struct { // router. type ExternalFixedIP struct { IPAddress string `json:"ip_address,omitempty"` - SubnetID string `json:"subnet_id"` + SubnetID string `json:"subnet_id,omitempty"` } // Route is a possible route in a router. @@ -77,6 +77,53 @@ type Router struct { // Tags optionally set via extensions/attributestags Tags []string `json:"tags"` + + // RevisionNumber optionally set via extensions/standard-attr-revisions + RevisionNumber int `json:"revision_number"` + + // Timestamp when the router was created + CreatedAt time.Time `json:"-"` + + // Timestamp when the router was last updated + UpdatedAt time.Time `json:"-"` +} + +func (r *Router) UnmarshalJSON(b []byte) error { + type tmp Router + + // Support for older neutron time format + var s1 struct { + tmp + CreatedAt gophercloud.JSONRFC3339NoZ `json:"created_at"` + UpdatedAt gophercloud.JSONRFC3339NoZ `json:"updated_at"` + } + + err := json.Unmarshal(b, &s1) + if err == nil { + *r = Router(s1.tmp) + r.CreatedAt = time.Time(s1.CreatedAt) + r.UpdatedAt = time.Time(s1.UpdatedAt) + + return nil + } + + // Support for newer neutron time format + var s2 struct { + tmp + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + } + + err = json.Unmarshal(b, &s2) + if err != nil { + return err + } + + *r = Router(s2.tmp) + r.CreatedAt = time.Time(s2.CreatedAt) + r.UpdatedAt = time.Time(s2.UpdatedAt) + + return nil } // RouterPage is the page returned by a pager when traversing over a @@ -88,7 +135,7 @@ type RouterPage struct { // NextPageURL is invoked when a paginated collection of routers has reached // the end of a page and the pager seeks to traverse over a new one. In order // to do this, it needs to construct the next page's URL. -func (r RouterPage) NextPageURL() (string, error) { +func (r RouterPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"routers_links"` } @@ -113,11 +160,14 @@ func (r RouterPage) IsEmpty() (bool, error) { // and extracts the elements into a slice of Router structs. In other words, // a generic collection is mapped into a relevant slice. func ExtractRouters(r pagination.Page) ([]Router, error) { - var s struct { - Routers []Router `json:"routers"` - } - err := (r.(RouterPage)).ExtractInto(&s) - return s.Routers, err + var s []Router + err := ExtractRoutersInto(r, &s) + return s, err +} + +// ExtractRoutersInto extracts the elements into a slice of Router structs. +func ExtractRoutersInto(r pagination.Page, v any) error { + return r.(RouterPage).ExtractIntoSlicePtr(v, "routers") } type commonResult struct { diff --git a/openstack/networking/v2/extensions/layer3/routers/testing/requests_test.go b/openstack/networking/v2/extensions/layer3/routers/testing/requests_test.go index 541dd656b5..1d3eb13444 100644 --- a/openstack/networking/v2/extensions/layer3/routers/testing/requests_test.go +++ b/openstack/networking/v2/extensions/layer3/routers/testing/requests_test.go @@ -14,17 +14,17 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/routers", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/routers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "routers": [ { @@ -34,6 +34,8 @@ func TestList(t *testing.T) { "admin_state_up": true, "tenant_id": "6b96ff0cb17a4b859e1e575d221683d3", "distributed": false, + "created_at": "2017-12-28T07:21:40Z", + "updated_at": "2017-12-28T07:21:40Z", "id": "7177abc4-5ae9-4bb7-b0d4-89e94a4abf3b" }, { @@ -45,6 +47,8 @@ func TestList(t *testing.T) { "admin_state_up": true, "tenant_id": "33a40233088643acb66ff6eb0ebea679", "distributed": false, + "created_at": "2017-12-28T07:21:40", + "updated_at": "2017-12-28T07:21:40", "id": "a9254bdb-2613-4a13-ac4c-adc581fba50d" }, { @@ -70,7 +74,7 @@ func TestList(t *testing.T) { count := 0 - err := routers.List(fake.ServiceClient(), routers.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := routers.List(fake.ServiceClient(fakeServer), routers.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := routers.ExtractRouters(page) if err != nil { @@ -86,6 +90,8 @@ func TestList(t *testing.T) { Distributed: false, Name: "second_routers", ID: "7177abc4-5ae9-4bb7-b0d4-89e94a4abf3b", + CreatedAt: time.Date(2017, 12, 28, 07, 21, 40, 0, time.UTC), + UpdatedAt: time.Date(2017, 12, 28, 07, 21, 40, 0, time.UTC), TenantID: "6b96ff0cb17a4b859e1e575d221683d3", }, { @@ -95,6 +101,8 @@ func TestList(t *testing.T) { Distributed: false, Name: "router1", ID: "a9254bdb-2613-4a13-ac4c-adc581fba50d", + CreatedAt: time.Date(2017, 12, 28, 07, 21, 40, 0, time.UTC), + UpdatedAt: time.Date(2017, 12, 28, 07, 21, 40, 0, time.UTC), TenantID: "33a40233088643acb66ff6eb0ebea679", }, { @@ -127,10 +135,10 @@ func TestList(t *testing.T) { } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/routers", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/routers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -156,7 +164,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "router": { "status": "ACTIVE", @@ -199,7 +207,7 @@ func TestCreate(t *testing.T) { GatewayInfo: &gwi, AvailabilityZoneHints: []string{"zone1", "zone2"}, } - r, err := routers.Create(context.TODO(), fake.ServiceClient(), options).Extract() + r, err := routers.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) gwi.ExternalFixedIPs = []routers.ExternalFixedIP{{ @@ -208,23 +216,23 @@ func TestCreate(t *testing.T) { }} th.AssertEquals(t, "foo_router", r.Name) - th.AssertEquals(t, false, r.AdminStateUp) + th.AssertFalse(t, r.AdminStateUp) th.AssertDeepEquals(t, gwi, r.GatewayInfo) th.AssertDeepEquals(t, []string{"zone1", "zone2"}, r.AvailabilityZoneHints) } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/routers/a07eea83-7710-4860-931b-5fe220fae533", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/routers/a07eea83-7710-4860-931b-5fe220fae533", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "router": { "status": "ACTIVE", @@ -252,30 +260,30 @@ func TestGet(t *testing.T) { `) }) - n, err := routers.Get(context.TODO(), fake.ServiceClient(), "a07eea83-7710-4860-931b-5fe220fae533").Extract() + n, err := routers.Get(context.TODO(), fake.ServiceClient(fakeServer), "a07eea83-7710-4860-931b-5fe220fae533").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.Status, "ACTIVE") - th.AssertDeepEquals(t, n.GatewayInfo, routers.GatewayInfo{ + th.AssertEquals(t, "ACTIVE", n.Status) + th.AssertDeepEquals(t, routers.GatewayInfo{ NetworkID: "85d76829-6415-48ff-9c63-5c5ca8c61ac6", ExternalFixedIPs: []routers.ExternalFixedIP{ {IPAddress: "198.51.100.33", SubnetID: "1d699529-bdfd-43f8-bcaa-bff00c547af2"}, }, QoSPolicyID: "6601bae5-f15a-4687-8be9-ddec9a2f8a8b", - }) - th.AssertEquals(t, n.Name, "router1") - th.AssertEquals(t, n.AdminStateUp, true) - th.AssertEquals(t, n.TenantID, "d6554fe62e2f41efbb6e026fad5c1542") - th.AssertEquals(t, n.ID, "a07eea83-7710-4860-931b-5fe220fae533") - th.AssertDeepEquals(t, n.Routes, []routers.Route{{DestinationCIDR: "40.0.1.0/24", NextHop: "10.1.0.10"}}) - th.AssertDeepEquals(t, n.AvailabilityZoneHints, []string{"zone1", "zone2"}) + }, n.GatewayInfo) + th.AssertEquals(t, "router1", n.Name) + th.AssertTrue(t, n.AdminStateUp) + th.AssertEquals(t, "d6554fe62e2f41efbb6e026fad5c1542", n.TenantID) + th.AssertEquals(t, "a07eea83-7710-4860-931b-5fe220fae533", n.ID) + th.AssertDeepEquals(t, []routers.Route{{DestinationCIDR: "40.0.1.0/24", NextHop: "10.1.0.10"}}, n.Routes) + th.AssertDeepEquals(t, []string{"zone1", "zone2"}, n.AvailabilityZoneHints) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/routers/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/routers/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -301,7 +309,7 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "router": { "status": "ACTIVE", @@ -335,23 +343,23 @@ func TestUpdate(t *testing.T) { r := []routers.Route{{DestinationCIDR: "40.0.1.0/24", NextHop: "10.1.0.10"}} options := routers.UpdateOpts{Name: "new_name", GatewayInfo: &gwi, Routes: &r} - n, err := routers.Update(context.TODO(), fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", options).Extract() + n, err := routers.Update(context.TODO(), fake.ServiceClient(fakeServer), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", options).Extract() th.AssertNoErr(t, err) gwi.ExternalFixedIPs = []routers.ExternalFixedIP{ {IPAddress: "192.0.2.17", SubnetID: "ab561bc4-1a8e-48f2-9fbd-376fcb1a1def"}, } - th.AssertEquals(t, n.Name, "new_name") + th.AssertEquals(t, "new_name", n.Name) th.AssertDeepEquals(t, n.GatewayInfo, gwi) - th.AssertDeepEquals(t, n.Routes, []routers.Route{{DestinationCIDR: "40.0.1.0/24", NextHop: "10.1.0.10"}}) + th.AssertDeepEquals(t, []routers.Route{{DestinationCIDR: "40.0.1.0/24", NextHop: "10.1.0.10"}}, n.Routes) } func TestUpdateWithoutRoutes(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/routers/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/routers/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -367,7 +375,7 @@ func TestUpdateWithoutRoutes(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "router": { "status": "ACTIVE", @@ -395,18 +403,18 @@ func TestUpdateWithoutRoutes(t *testing.T) { options := routers.UpdateOpts{Name: "new_name"} - n, err := routers.Update(context.TODO(), fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", options).Extract() + n, err := routers.Update(context.TODO(), fake.ServiceClient(fakeServer), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.Name, "new_name") - th.AssertDeepEquals(t, n.Routes, []routers.Route{{DestinationCIDR: "40.0.1.0/24", NextHop: "10.1.0.10"}}) + th.AssertEquals(t, "new_name", n.Name) + th.AssertDeepEquals(t, []routers.Route{{DestinationCIDR: "40.0.1.0/24", NextHop: "10.1.0.10"}}, n.Routes) } func TestAllRoutesRemoved(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/routers/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/routers/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -422,7 +430,7 @@ func TestAllRoutesRemoved(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "router": { "status": "ACTIVE", @@ -443,31 +451,31 @@ func TestAllRoutesRemoved(t *testing.T) { r := []routers.Route{} options := routers.UpdateOpts{Routes: &r} - n, err := routers.Update(context.TODO(), fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", options).Extract() + n, err := routers.Update(context.TODO(), fake.ServiceClient(fakeServer), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", options).Extract() th.AssertNoErr(t, err) - th.AssertDeepEquals(t, n.Routes, []routers.Route{}) + th.AssertDeepEquals(t, []routers.Route{}, n.Routes) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/routers/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/routers/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := routers.Delete(context.TODO(), fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c") + res := routers.Delete(context.TODO(), fake.ServiceClient(fakeServer), "4e8e5957-649f-477b-9e5b-f1f75b21c03c") th.AssertNoErr(t, res.Err) } func TestAddInterface(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/routers/4e8e5957-649f-477b-9e5b-f1f75b21c03c/add_router_interface", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/routers/4e8e5957-649f-477b-9e5b-f1f75b21c03c/add_router_interface", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -481,7 +489,7 @@ func TestAddInterface(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "subnet_id": "0d32a837-8069-4ec3-84c4-3eef3e10b188", "tenant_id": "017d8de156df4177889f31a9bd6edc00", @@ -492,7 +500,7 @@ func TestAddInterface(t *testing.T) { }) opts := routers.AddInterfaceOpts{SubnetID: "a2f1f29d-571b-4533-907f-5803ab96ead1"} - res, err := routers.AddInterface(context.TODO(), fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", opts).Extract() + res, err := routers.AddInterface(context.TODO(), fake.ServiceClient(fakeServer), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", opts).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "0d32a837-8069-4ec3-84c4-3eef3e10b188", res.SubnetID) @@ -502,21 +510,24 @@ func TestAddInterface(t *testing.T) { } func TestAddInterfaceRequiredOpts(t *testing.T) { - _, err := routers.AddInterface(context.TODO(), fake.ServiceClient(), "foo", routers.AddInterfaceOpts{}).Extract() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + _, err := routers.AddInterface(context.TODO(), fake.ServiceClient(fakeServer), "foo", routers.AddInterfaceOpts{}).Extract() if err == nil { t.Fatalf("Expected error, got none") } - _, err = routers.AddInterface(context.TODO(), fake.ServiceClient(), "foo", routers.AddInterfaceOpts{SubnetID: "bar", PortID: "baz"}).Extract() + _, err = routers.AddInterface(context.TODO(), fake.ServiceClient(fakeServer), "foo", routers.AddInterfaceOpts{SubnetID: "bar", PortID: "baz"}).Extract() if err == nil { t.Fatalf("Expected error, got none") } } func TestRemoveInterface(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/routers/4e8e5957-649f-477b-9e5b-f1f75b21c03c/remove_router_interface", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/routers/4e8e5957-649f-477b-9e5b-f1f75b21c03c/remove_router_interface", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -530,7 +541,7 @@ func TestRemoveInterface(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "subnet_id": "0d32a837-8069-4ec3-84c4-3eef3e10b188", "tenant_id": "017d8de156df4177889f31a9bd6edc00", @@ -541,7 +552,7 @@ func TestRemoveInterface(t *testing.T) { }) opts := routers.RemoveInterfaceOpts{SubnetID: "a2f1f29d-571b-4533-907f-5803ab96ead1"} - res, err := routers.RemoveInterface(context.TODO(), fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", opts).Extract() + res, err := routers.RemoveInterface(context.TODO(), fake.ServiceClient(fakeServer), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", opts).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "0d32a837-8069-4ec3-84c4-3eef3e10b188", res.SubnetID) @@ -551,17 +562,17 @@ func TestRemoveInterface(t *testing.T) { } func TestListL3Agents(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/routers/fa3a4aaa-c73f-48aa-a603-8c8bf642b7c0/l3-agents", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/routers/fa3a4aaa-c73f-48aa-a603-8c8bf642b7c0/l3-agents", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "agents": [ { @@ -623,7 +634,7 @@ func TestListL3Agents(t *testing.T) { `) }) - l3AgentsPages, err := routers.ListL3Agents(fake.ServiceClient(), "fa3a4aaa-c73f-48aa-a603-8c8bf642b7c0").AllPages(context.TODO()) + l3AgentsPages, err := routers.ListL3Agents(fake.ServiceClient(fakeServer), "fa3a4aaa-c73f-48aa-a603-8c8bf642b7c0").AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := routers.ExtractL3Agents(l3AgentsPages) th.AssertNoErr(t, err) @@ -686,3 +697,203 @@ func TestListL3Agents(t *testing.T) { } th.CheckDeepEquals(t, expected, actual) } + +func TestAddExternalGateways(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/routers/4e8e5957-649f-477b-9e5b-f1f75b21c03c/add_external_gateways", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "router": { + "external_gateways": [ + { + "network_id": "8ca37218-28ff-41cb-9b10-039601ea7e6b", + "external_fixed_ips": [ + {"subnet_id": "ab561bc4-1a8e-48f2-9fbd-376fcb1a1def"} + ] + } + ] + } +} + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ` +{ + "router": { + "status": "ACTIVE", + "external_gateway_info": { + "network_id": "8ca37218-28ff-41cb-9b10-039601ea7e6b", + "external_fixed_ips": [ + {"ip_address": "192.0.2.17", "subnet_id": "ab561bc4-1a8e-48f2-9fbd-376fcb1a1def"} + ] + }, + "name": "router1", + "admin_state_up": true, + "tenant_id": "6b96ff0cb17a4b859e1e575d221683d3", + "distributed": false, + "id": "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + } +} + `) + }) + + efi := []routers.ExternalFixedIP{ + {SubnetID: "ab561bc4-1a8e-48f2-9fbd-376fcb1a1def"}, + } + opts := routers.AddExternalGatewaysOpts{ + ExternalGateways: []routers.GatewayInfo{ + { + NetworkID: "8ca37218-28ff-41cb-9b10-039601ea7e6b", + ExternalFixedIPs: efi, + }, + }, + } + + n, err := routers.AddExternalGateways(context.TODO(), fake.ServiceClient(fakeServer), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", opts).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, "router1", n.Name) + th.AssertEquals(t, "4e8e5957-649f-477b-9e5b-f1f75b21c03c", n.ID) + th.AssertEquals(t, "8ca37218-28ff-41cb-9b10-039601ea7e6b", n.GatewayInfo.NetworkID) +} + +func TestUpdateExternalGateways(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/routers/4e8e5957-649f-477b-9e5b-f1f75b21c03c/update_external_gateways", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "router": { + "external_gateways": [ + { + "network_id": "8ca37218-28ff-41cb-9b10-039601ea7e6b", + "enable_snat": true, + "external_fixed_ips": [ + {"ip_address": "192.0.2.17", "subnet_id": "ab561bc4-1a8e-48f2-9fbd-376fcb1a1def"} + ] + } + ] + } +} + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ` +{ + "router": { + "status": "ACTIVE", + "external_gateway_info": { + "network_id": "8ca37218-28ff-41cb-9b10-039601ea7e6b", + "enable_snat": true, + "external_fixed_ips": [ + {"ip_address": "192.0.2.17", "subnet_id": "ab561bc4-1a8e-48f2-9fbd-376fcb1a1def"} + ] + }, + "name": "router1", + "admin_state_up": true, + "tenant_id": "6b96ff0cb17a4b859e1e575d221683d3", + "distributed": false, + "id": "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + } +} + `) + }) + + enableSNAT := true + efi := []routers.ExternalFixedIP{ + {IPAddress: "192.0.2.17", SubnetID: "ab561bc4-1a8e-48f2-9fbd-376fcb1a1def"}, + } + opts := routers.UpdateExternalGatewaysOpts{ + ExternalGateways: []routers.GatewayInfo{ + { + NetworkID: "8ca37218-28ff-41cb-9b10-039601ea7e6b", + EnableSNAT: &enableSNAT, + ExternalFixedIPs: efi, + }, + }, + } + + n, err := routers.UpdateExternalGateways(context.TODO(), fake.ServiceClient(fakeServer), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", opts).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, "router1", n.Name) + th.AssertEquals(t, "4e8e5957-649f-477b-9e5b-f1f75b21c03c", n.ID) + th.AssertTrue(t, *n.GatewayInfo.EnableSNAT) +} + +func TestRemoveExternalGateways(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/routers/4e8e5957-649f-477b-9e5b-f1f75b21c03c/remove_external_gateways", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "router": { + "external_gateways": [ + { + "network_id": "8ca37218-28ff-41cb-9b10-039601ea7e6b", + "external_fixed_ips": [ + {"ip_address": "192.0.2.17", "subnet_id": "ab561bc4-1a8e-48f2-9fbd-376fcb1a1def"} + ] + } + ] + } +} + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ` +{ + "router": { + "status": "ACTIVE", + "external_gateway_info": null, + "name": "router1", + "admin_state_up": true, + "tenant_id": "6b96ff0cb17a4b859e1e575d221683d3", + "distributed": false, + "id": "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + } +} + `) + }) + + efi := []routers.ExternalFixedIP{ + {IPAddress: "192.0.2.17", SubnetID: "ab561bc4-1a8e-48f2-9fbd-376fcb1a1def"}, + } + opts := routers.RemoveExternalGatewaysOpts{ + ExternalGateways: []routers.GatewayInfo{ + { + NetworkID: "8ca37218-28ff-41cb-9b10-039601ea7e6b", + ExternalFixedIPs: efi, + }, + }, + } + + n, err := routers.RemoveExternalGateways(context.TODO(), fake.ServiceClient(fakeServer), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", opts).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, "router1", n.Name) + th.AssertEquals(t, "4e8e5957-649f-477b-9e5b-f1f75b21c03c", n.ID) + th.AssertEquals(t, "", n.GatewayInfo.NetworkID) +} diff --git a/openstack/networking/v2/extensions/layer3/routers/urls.go b/openstack/networking/v2/extensions/layer3/routers/urls.go index 87815aa6ec..b07c2fdc7b 100644 --- a/openstack/networking/v2/extensions/layer3/routers/urls.go +++ b/openstack/networking/v2/extensions/layer3/routers/urls.go @@ -23,3 +23,15 @@ func removeInterfaceURL(c *gophercloud.ServiceClient, id string) string { func listl3AgentsURL(c *gophercloud.ServiceClient, id string) string { return c.ServiceURL(resourcePath, id, "l3-agents") } + +func addExternalGatewaysURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id, "add_external_gateways") +} + +func updateExternalGatewaysURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id, "update_external_gateways") +} + +func removeExternalGatewaysURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id, "remove_external_gateways") +} diff --git a/openstack/networking/v2/extensions/mtu/testing/results_test.go b/openstack/networking/v2/extensions/mtu/testing/results_test.go index 273bdb5166..99b2b2e745 100644 --- a/openstack/networking/v2/extensions/mtu/testing/results_test.go +++ b/openstack/networking/v2/extensions/mtu/testing/results_test.go @@ -19,17 +19,17 @@ type NetworkMTU struct { } func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, nettest.ListResponse) + fmt.Fprint(w, nettest.ListResponse) }) type NetworkWithMTUExt struct { @@ -38,7 +38,7 @@ func TestList(t *testing.T) { } var actual []NetworkWithMTUExt - allPages, err := networks.List(fake.ServiceClient(), networks.ListOpts{}).AllPages(context.TODO()) + allPages, err := networks.List(fake.ServiceClient(fakeServer), networks.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) err = networks.ExtractNetworksInto(allPages, &actual) @@ -49,22 +49,22 @@ func TestList(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, nettest.GetResponse) + fmt.Fprint(w, nettest.GetResponse) }) var s NetworkMTU - err := networks.Get(context.TODO(), fake.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(&s) + err := networks.Get(context.TODO(), fake.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(&s) th.AssertNoErr(t, err) th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", s.ID) @@ -72,10 +72,10 @@ func TestGet(t *testing.T) { } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -85,7 +85,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) iTrue := true @@ -101,7 +101,7 @@ func TestCreate(t *testing.T) { var s NetworkMTU - err := networks.Create(context.TODO(), fake.ServiceClient(), mtuCreateOpts).ExtractInto(&s) + err := networks.Create(context.TODO(), fake.ServiceClient(fakeServer), mtuCreateOpts).ExtractInto(&s) th.AssertNoErr(t, err) th.AssertEquals(t, "db193ab3-96e3-4cb3-8fc5-05f4296d0324", s.ID) @@ -110,10 +110,10 @@ func TestCreate(t *testing.T) { } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -123,7 +123,7 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateResponse) + fmt.Fprint(w, UpdateResponse) }) iTrue := true @@ -142,7 +142,7 @@ func TestUpdate(t *testing.T) { var s NetworkMTU - err := networks.Update(context.TODO(), fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", mtuUpdateOpts).ExtractInto(&s) + err := networks.Update(context.TODO(), fake.ServiceClient(fakeServer), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", mtuUpdateOpts).ExtractInto(&s) th.AssertNoErr(t, err) th.AssertEquals(t, "4e8e5957-649f-477b-9e5b-f1f75b21c03c", s.ID) diff --git a/openstack/networking/v2/extensions/networkipavailabilities/results.go b/openstack/networking/v2/extensions/networkipavailabilities/results.go index b37617f9d1..e29810972a 100644 --- a/openstack/networking/v2/extensions/networkipavailabilities/results.go +++ b/openstack/networking/v2/extensions/networkipavailabilities/results.go @@ -2,6 +2,7 @@ package networkipavailabilities import ( "encoding/json" + "fmt" "math/big" "github.com/gophercloud/gophercloud/v2" @@ -52,12 +53,35 @@ type NetworkIPAvailability struct { UsedIPs string `json:"-"` } +// Go's encoding/json decodes all JSON numbers into float64 when the target is +// interface{}. For large integers (abs >= 1e21), re-encoding that float64 +// produces scientific notation (e.g. "1.1805916207174113e+21"), which +// big.Int.UnmarshalJSON cannot parse. This function handles both plain integer +// and scientific notation forms. +func parseBigIntFromNumber(n json.Number) (*big.Int, error) { + s := string(n) + + // Fast path: plain integer notation + bi := new(big.Int) + if _, ok := bi.SetString(s, 10); ok { + return bi, nil + } + + // Slow path: scientific notation from float64 round-trip + bf := new(big.Float).SetPrec(256) + if _, _, err := bf.Parse(s, 10); err != nil { + return nil, fmt.Errorf("networkipavailabilities: cannot parse %q as an integer: %w", s, err) + } + result, _ := bf.Int(nil) + return result, nil +} + func (r *NetworkIPAvailability) UnmarshalJSON(b []byte) error { type tmp NetworkIPAvailability var s struct { tmp - TotalIPs big.Int `json:"total_ips"` - UsedIPs big.Int `json:"used_ips"` + TotalIPs json.Number `json:"total_ips"` + UsedIPs json.Number `json:"used_ips"` } err := json.Unmarshal(b, &s) @@ -66,10 +90,19 @@ func (r *NetworkIPAvailability) UnmarshalJSON(b []byte) error { } *r = NetworkIPAvailability(s.tmp) - r.TotalIPs = s.TotalIPs.String() - r.UsedIPs = s.UsedIPs.String() + totalIPs, err := parseBigIntFromNumber(s.TotalIPs) + if err != nil { + return err + } + r.TotalIPs = totalIPs.String() - return err + usedIPs, err := parseBigIntFromNumber(s.UsedIPs) + if err != nil { + return err + } + r.UsedIPs = usedIPs.String() + + return nil } // SubnetIPAvailability represents availability details for a single subnet. @@ -97,8 +130,8 @@ func (r *SubnetIPAvailability) UnmarshalJSON(b []byte) error { type tmp SubnetIPAvailability var s struct { tmp - TotalIPs big.Int `json:"total_ips"` - UsedIPs big.Int `json:"used_ips"` + TotalIPs json.Number `json:"total_ips"` + UsedIPs json.Number `json:"used_ips"` } err := json.Unmarshal(b, &s) @@ -107,10 +140,19 @@ func (r *SubnetIPAvailability) UnmarshalJSON(b []byte) error { } *r = SubnetIPAvailability(s.tmp) - r.TotalIPs = s.TotalIPs.String() - r.UsedIPs = s.UsedIPs.String() + totalIPs, err := parseBigIntFromNumber(s.TotalIPs) + if err != nil { + return err + } + r.TotalIPs = totalIPs.String() + + usedIPs, err := parseBigIntFromNumber(s.UsedIPs) + if err != nil { + return err + } + r.UsedIPs = usedIPs.String() - return err + return nil } // NetworkIPAvailabilityPage stores a single page of NetworkIPAvailabilities diff --git a/openstack/networking/v2/extensions/networkipavailabilities/testing/fixtures_test.go b/openstack/networking/v2/extensions/networkipavailabilities/testing/fixtures_test.go index 58900db23c..475511cce2 100644 --- a/openstack/networking/v2/extensions/networkipavailabilities/testing/fixtures_test.go +++ b/openstack/networking/v2/extensions/networkipavailabilities/testing/fixtures_test.go @@ -15,11 +15,11 @@ const NetworkIPAvailabilityListResult = ` "project_id": "fb57277ef2f84a0e85b9018ec2dedbf7", "subnet_ip_availability": [ { - "cidr": "fdbc:bf53:567e::/64", + "cidr": "fdbc:bf53:567e::/56", "ip_version": 6, "subnet_id": "497ac4d3-0b92-42cf-82de-71302ab2b656", "subnet_name": "ipv6-private-subnet", - "total_ips": 18446744073709552000, + "total_ips": 4722366482869645213696, "used_ips": 2 }, { @@ -69,10 +69,14 @@ var NetworkIPAvailability1 = networkipavailabilities.NetworkIPAvailability{ { SubnetID: "497ac4d3-0b92-42cf-82de-71302ab2b656", SubnetName: "ipv6-private-subnet", - CIDR: "fdbc:bf53:567e::/64", + CIDR: "fdbc:bf53:567e::/56", IPVersion: int(gophercloud.IPv6), - TotalIPs: "18446744073709552000", - UsedIPs: "2", + // 4722366482869645213696 is 2^72 (a /56 IPv6 subnet). + // After gophercloud's float64 round-trip (numbers >= 1e21 are + // re-encoded in scientific notation), the value loses the last + // few digits of precision but no longer crashes. + TotalIPs: "4722366482869645000000", + UsedIPs: "2", }, { SubnetID: "521f47e7-c4fb-452c-b71a-851da38cc571", diff --git a/openstack/networking/v2/extensions/networkipavailabilities/testing/requests_test.go b/openstack/networking/v2/extensions/networkipavailabilities/testing/requests_test.go index 86166b5e33..cd1c55694b 100644 --- a/openstack/networking/v2/extensions/networkipavailabilities/testing/requests_test.go +++ b/openstack/networking/v2/extensions/networkipavailabilities/testing/requests_test.go @@ -14,22 +14,22 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/network-ip-availabilities", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/network-ip-availabilities", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, NetworkIPAvailabilityListResult) + fmt.Fprint(w, NetworkIPAvailabilityListResult) }) count := 0 - err := networkipavailabilities.List(fake.ServiceClient(), networkipavailabilities.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := networkipavailabilities.List(fake.ServiceClient(fakeServer), networkipavailabilities.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := networkipavailabilities.ExtractNetworkIPAvailabilities(page) if err != nil { @@ -55,29 +55,29 @@ func TestList(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/network-ip-availabilities/cf11ab78-2302-49fa-870f-851a08c7afb8", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/network-ip-availabilities/cf11ab78-2302-49fa-870f-851a08c7afb8", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, NetworkIPAvailabilityGetResult) + fmt.Fprint(w, NetworkIPAvailabilityGetResult) }) - s, err := networkipavailabilities.Get(context.TODO(), fake.ServiceClient(), "cf11ab78-2302-49fa-870f-851a08c7afb8").Extract() + s, err := networkipavailabilities.Get(context.TODO(), fake.ServiceClient(fakeServer), "cf11ab78-2302-49fa-870f-851a08c7afb8").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.NetworkID, "cf11ab78-2302-49fa-870f-851a08c7afb8") - th.AssertEquals(t, s.NetworkName, "public") - th.AssertEquals(t, s.ProjectID, "424e7cf0243c468ca61732ba45973b3e") - th.AssertEquals(t, s.TenantID, "424e7cf0243c468ca61732ba45973b3e") - th.AssertEquals(t, s.TotalIPs, "253") - th.AssertEquals(t, s.UsedIPs, "3") - th.AssertDeepEquals(t, s.SubnetIPAvailabilities, []networkipavailabilities.SubnetIPAvailability{ + th.AssertEquals(t, "cf11ab78-2302-49fa-870f-851a08c7afb8", s.NetworkID) + th.AssertEquals(t, "public", s.NetworkName) + th.AssertEquals(t, "424e7cf0243c468ca61732ba45973b3e", s.ProjectID) + th.AssertEquals(t, "424e7cf0243c468ca61732ba45973b3e", s.TenantID) + th.AssertEquals(t, "253", s.TotalIPs) + th.AssertEquals(t, "3", s.UsedIPs) + th.AssertDeepEquals(t, []networkipavailabilities.SubnetIPAvailability{ { SubnetID: "4afe6e5f-9649-40db-b18f-64c7ead942bd", SubnetName: "public-subnet", @@ -86,5 +86,5 @@ func TestGet(t *testing.T) { TotalIPs: "253", UsedIPs: "3", }, - }) + }, s.SubnetIPAvailabilities) } diff --git a/openstack/networking/v2/extensions/portsbinding/testing/fixtures_test.go b/openstack/networking/v2/extensions/portsbinding/testing/fixtures_test.go index 494fe49091..2a639214dd 100644 --- a/openstack/networking/v2/extensions/portsbinding/testing/fixtures_test.go +++ b/openstack/networking/v2/extensions/portsbinding/testing/fixtures_test.go @@ -10,32 +10,32 @@ import ( th "github.com/gophercloud/gophercloud/v2/testhelper" ) -func HandleListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { +func HandleListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, porttest.ListResponse) + fmt.Fprint(w, porttest.ListResponse) }) } -func HandleGet(t *testing.T) { - th.Mux.HandleFunc("/v2.0/ports/46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", func(w http.ResponseWriter, r *http.Request) { +func HandleGet(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/ports/46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, porttest.GetResponse) + fmt.Fprint(w, porttest.GetResponse) }) } -func HandleCreate(t *testing.T) { - th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { +func HandleCreate(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -62,7 +62,7 @@ func HandleCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "port": { "status": "DOWN", @@ -92,8 +92,8 @@ func HandleCreate(t *testing.T) { }) } -func HandleUpdate(t *testing.T) { - th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { +func HandleUpdate(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -120,7 +120,7 @@ func HandleUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "port": { "status": "DOWN", diff --git a/openstack/networking/v2/extensions/portsbinding/testing/requests_test.go b/openstack/networking/v2/extensions/portsbinding/testing/requests_test.go index 4f0073b4a5..9efd2f22a7 100644 --- a/openstack/networking/v2/extensions/portsbinding/testing/requests_test.go +++ b/openstack/networking/v2/extensions/portsbinding/testing/requests_test.go @@ -12,10 +12,10 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleListSuccessfully(t) + HandleListSuccessfully(t, fakeServer) type PortWithExt struct { ports.Port @@ -52,7 +52,7 @@ func TestList(t *testing.T) { }, } - allPages, err := ports.List(fake.ServiceClient(), ports.ListOpts{}).AllPages(context.TODO()) + allPages, err := ports.List(fake.ServiceClient(fakeServer), ports.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) err = ports.ExtractPortsInto(allPages, &actual) @@ -62,44 +62,44 @@ func TestList(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleGet(t) + HandleGet(t, fakeServer) var s struct { ports.Port portsbinding.PortsBindingExt } - err := ports.Get(context.TODO(), fake.ServiceClient(), "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2").ExtractInto(&s) + err := ports.Get(context.TODO(), fake.ServiceClient(fakeServer), "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2").ExtractInto(&s) th.AssertNoErr(t, err) - th.AssertEquals(t, s.Status, "ACTIVE") - th.AssertEquals(t, s.Name, "") - th.AssertEquals(t, s.AdminStateUp, true) - th.AssertEquals(t, s.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7") - th.AssertEquals(t, s.TenantID, "7e02058126cc4950b75f9970368ba177") - th.AssertEquals(t, s.DeviceOwner, "network:router_interface") - th.AssertEquals(t, s.MACAddress, "fa:16:3e:23:fd:d7") - th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{ + th.AssertEquals(t, "ACTIVE", s.Status) + th.AssertEquals(t, "", s.Name) + th.AssertTrue(t, s.AdminStateUp) + th.AssertEquals(t, "a87cc70a-3e15-4acf-8205-9b711a3531b7", s.NetworkID) + th.AssertEquals(t, "7e02058126cc4950b75f9970368ba177", s.TenantID) + th.AssertEquals(t, "network:router_interface", s.DeviceOwner) + th.AssertEquals(t, "fa:16:3e:23:fd:d7", s.MACAddress) + th.AssertDeepEquals(t, []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.1"}, - }) - th.AssertEquals(t, s.ID, "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2") - th.AssertDeepEquals(t, s.SecurityGroups, []string{}) - th.AssertEquals(t, s.DeviceID, "5e3898d7-11be-483e-9732-b2f5eccd2b2e") - - th.AssertEquals(t, s.HostID, "devstack") - th.AssertEquals(t, s.VNICType, "normal") - th.AssertEquals(t, s.VIFType, "ovs") - th.AssertDeepEquals(t, s.VIFDetails, map[string]any{"port_filter": true, "ovs_hybrid_plug": true}) + }, s.FixedIPs) + th.AssertEquals(t, "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", s.ID) + th.AssertDeepEquals(t, []string{}, s.SecurityGroups) + th.AssertEquals(t, "5e3898d7-11be-483e-9732-b2f5eccd2b2e", s.DeviceID) + + th.AssertEquals(t, "devstack", s.HostID) + th.AssertEquals(t, "normal", s.VNICType) + th.AssertEquals(t, "ovs", s.VIFType) + th.AssertDeepEquals(t, map[string]any{"port_filter": true, "ovs_hybrid_plug": true}, s.VIFDetails) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleCreate(t) + HandleCreate(t, fakeServer) var s struct { ports.Port @@ -123,37 +123,40 @@ func TestCreate(t *testing.T) { VNICType: "normal", } - err := ports.Create(context.TODO(), fake.ServiceClient(), createOpts).ExtractInto(&s) + err := ports.Create(context.TODO(), fake.ServiceClient(fakeServer), createOpts).ExtractInto(&s) th.AssertNoErr(t, err) - th.AssertEquals(t, s.Status, "DOWN") - th.AssertEquals(t, s.Name, "private-port") - th.AssertEquals(t, s.AdminStateUp, true) - th.AssertEquals(t, s.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7") - th.AssertEquals(t, s.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa") - th.AssertEquals(t, s.DeviceOwner, "") - th.AssertEquals(t, s.MACAddress, "fa:16:3e:c9:cb:f0") - th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{ + th.AssertEquals(t, "DOWN", s.Status) + th.AssertEquals(t, "private-port", s.Name) + th.AssertTrue(t, s.AdminStateUp) + th.AssertEquals(t, "a87cc70a-3e15-4acf-8205-9b711a3531b7", s.NetworkID) + th.AssertEquals(t, "d6700c0c9ffa4f1cb322cd4a1f3906fa", s.TenantID) + th.AssertEquals(t, "", s.DeviceOwner) + th.AssertEquals(t, "fa:16:3e:c9:cb:f0", s.MACAddress) + th.AssertDeepEquals(t, []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"}, - }) - th.AssertEquals(t, s.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") - th.AssertDeepEquals(t, s.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}) - th.AssertEquals(t, s.HostID, "HOST1") - th.AssertEquals(t, s.VNICType, "normal") + }, s.FixedIPs) + th.AssertEquals(t, "65c0ee9f-d634-4522-8954-51021b570b0d", s.ID) + th.AssertDeepEquals(t, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}, s.SecurityGroups) + th.AssertEquals(t, "HOST1", s.HostID) + th.AssertEquals(t, "normal", s.VNICType) } func TestRequiredCreateOpts(t *testing.T) { - res := ports.Create(context.TODO(), fake.ServiceClient(), portsbinding.CreateOptsExt{CreateOptsBuilder: ports.CreateOpts{}}) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + res := ports.Create(context.TODO(), fake.ServiceClient(fakeServer), portsbinding.CreateOptsExt{CreateOptsBuilder: ports.CreateOpts{}}) if res.Err == nil { t.Fatalf("Expected error, got none") } } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleUpdate(t) + HandleUpdate(t, fakeServer) var s struct { ports.Port @@ -176,14 +179,14 @@ func TestUpdate(t *testing.T) { VNICType: "normal", } - err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&s) + err := ports.Update(context.TODO(), fake.ServiceClient(fakeServer), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&s) th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "new_port_name") - th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{ + th.AssertEquals(t, "new_port_name", s.Name) + th.AssertDeepEquals(t, []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"}, - }) - th.AssertDeepEquals(t, s.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}) - th.AssertEquals(t, s.HostID, "HOST1") - th.AssertEquals(t, s.VNICType, "normal") + }, s.FixedIPs) + th.AssertDeepEquals(t, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}, s.SecurityGroups) + th.AssertEquals(t, "HOST1", s.HostID) + th.AssertEquals(t, "normal", s.VNICType) } diff --git a/openstack/networking/v2/extensions/portstrustedvif/doc.go b/openstack/networking/v2/extensions/portstrustedvif/doc.go new file mode 100644 index 0000000000..9efedf04ad --- /dev/null +++ b/openstack/networking/v2/extensions/portstrustedvif/doc.go @@ -0,0 +1,74 @@ +/* +Package portstrustedvif provides information and interaction with the port +trusted vif extension which adds trusted attributes to the port resource +for the OpenStack Networking service. + +Example to Get a Port with a Port Trusted VIF + + var portWithPortTrustedVIFExtension struct { + ports.Port + portstrustedvif.PortTrustedVIFExt + } + + portID := "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2" + + err := ports.Get(context.TODO(), networkingClient, portID).ExtractInto(&portWithPortTrustedVIFExtension) + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", portWithPortTrustedVIFExtension) + +Example to Create a Port With Port Trusted VIF set as true + + var portWithPortTrustedVIFExtension struct { + ports.Port + portstrustedvif.PortTrustedVIFExt + } + + iTrue := true + networkID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + subnetID := "a87cc70a-3e15-4acf-8205-9b711a3531b7" + + portCreateOpts := ports.CreateOpts{ + NetworkID: networkID, + FixedIPs: []ports.IP{ports.IP{SubnetID: subnetID}}, + } + + createOpts := portstrustedvif.PortCreateOptsExt{ + CreateOptsBuilder: portCreateOpts, + PortTrustedVIF: &iTrue, + } + + err := ports.Create(context.TODO(), networkingClient, createOpts).ExtractInto(&portWithPortTrustedVIFExtension) + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", portWithPortTrustedVIFExtension) + +Example to Update the status of the Port Trusted VIF to false on an Existing Port + + var portWithPortTrustedVIDExtension struct { + ports.Port + portstrustedvif.PortTrustedVIFExt + } + + iFalse := false + portID := "65c0ee9f-d634-4522-8954-51021b570b0d" + + portUpdateOpts := ports.UpdateOpts{} + updateOpts := portstrustedvif.PortUpdateOptsExt{ + UpdateOptsBuilder: portUpdateOpts, + PortTrustedVIF: &iFalse, + } + + err := ports.Update(context.TODO(), networkingClient, portID, updateOpts).ExtractInto(&portWithPortTrustedVIFExtension) + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", portWithPortTrustedVIFExtension) +*/ + +package portstrustedvif diff --git a/openstack/networking/v2/extensions/portstrustedvif/requests.go b/openstack/networking/v2/extensions/portstrustedvif/requests.go new file mode 100644 index 0000000000..c4166ce1bd --- /dev/null +++ b/openstack/networking/v2/extensions/portstrustedvif/requests.go @@ -0,0 +1,53 @@ +package portstrustedvif + +import ( + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/ports" +) + +// PortCreateOptsExt adds port trusted VIF options to the base ports.CreateOpts. +type PortCreateOptsExt struct { + ports.CreateOptsBuilder + + // PortTrustedVIF toggles the port's trusted VIF status. + PortTrustedVIF *bool `json:"trusted,omitempty"` +} + +// To PortCreateMap casts a CreateOpts struct to a map +func (opts PortCreateOptsExt) ToPortCreateMap() (map[string]any, error) { + base, err := opts.CreateOptsBuilder.ToPortCreateMap() + if err != nil { + return nil, err + } + + port := base["port"].(map[string]any) + + if opts.PortTrustedVIF != nil { + port["trusted"] = *opts.PortTrustedVIF + } + + return base, nil +} + +// PortUpdateOptsExt adds port trusted VIF options to the base ports.UpdateOpts. +type PortUpdateOptsExt struct { + ports.UpdateOptsBuilder + + // PortTrustedVIF updates the port's trusted VIF status. + PortTrustedVIF *bool `json:"trusted,omitempty"` +} + +// ToPortUpdateMap casts a UpdateOpts struct to a map. +func (opts PortUpdateOptsExt) ToPortUpdateMap() (map[string]any, error) { + base, err := opts.UpdateOptsBuilder.ToPortUpdateMap() + if err != nil { + return nil, err + } + + port := base["port"].(map[string]any) + + if opts.PortTrustedVIF != nil { + port["trusted"] = *opts.PortTrustedVIF + } + + return base, nil +} diff --git a/openstack/networking/v2/extensions/portstrustedvif/results.go b/openstack/networking/v2/extensions/portstrustedvif/results.go new file mode 100644 index 0000000000..39cfeacfaa --- /dev/null +++ b/openstack/networking/v2/extensions/portstrustedvif/results.go @@ -0,0 +1,6 @@ +package portstrustedvif + +type PortTrustedVIFExt struct { + // PortTrustedVIF stores information about whether a SR-IOV port should be trusted + PortTrustedVIF *bool `json:"trusted"` +} diff --git a/openstack/networking/v2/extensions/portstrustedvif/testing/fixtures_test.go b/openstack/networking/v2/extensions/portstrustedvif/testing/fixtures_test.go new file mode 100644 index 0000000000..0a9df88bbe --- /dev/null +++ b/openstack/networking/v2/extensions/portstrustedvif/testing/fixtures_test.go @@ -0,0 +1,161 @@ +package testing + +const ListResponse = ` +{ + "ports": [ + { + "status": "ACTIVE", + "binding:host_id": "devstack", + "name": "", + "admin_state_up": true, + "network_id": "70c1db1f-b701-45bd-96e0-a313ee3430b3", + "tenant_id": "", + "device_owner": "network:router_gateway", + "mac_address": "fa:16:3e:58:42:ed", + "binding:vnic_type": "normal", + "fixed_ips": [ + { + "subnet_id": "008ba151-0b8c-4a67-98b5-0d2b87666062", + "ip_address": "172.24.4.2" + } + ], + "id": "d80b1a3b-4fc1-49f3-952e-1e2ab7081d8b", + "security_groups": [], + "dns_name": "test-port", + "dns_assignment": [ + { + "hostname": "test-port", + "ip_address": "172.24.4.2", + "fqdn": "test-port.openstack.local." + } + ], + "device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824", + "port_security_enabled": false, + "trusted": true, + "created_at": "2019-06-30T04:15:37", + "updated_at": "2019-06-30T05:18:49" + } + ] +} +` + +const GetPortTrustedVIFStatusResponse = ` +{ + "port": { + "status": "DOWN", + "name": "private-port", + "admin_state_up": true, + "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7", + "tenant_id": "d6700c0c9ffa4f1cb322cd4a1f3906fa", + "device_owner": "", + "mac_address": "fa:16:3e:c9:cb:f0", + "fixed_ips": [ + { + "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2", + "ip_address": "10.0.0.1" + } + ], + "id": "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", + "trusted": false, + "device_id": "" + } +} +` +const GetPortTrustedVIFUnsetResponse = ` +{ + "port": { + "status": "DOWN", + "name": "private-port", + "admin_state_up": true, + "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7", + "tenant_id": "d6700c0c9ffa4f1cb322cd4a1f3906fa", + "device_owner": "", + "mac_address": "fa:16:3e:c9:cb:f0", + "fixed_ips": [ + { + "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2", + "ip_address": "10.0.0.1" + } + ], + "id": "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", + "device_id": "" + } +} +` + +const CreatePortTrustedVIFRequest = ` +{ + "port": { + "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7", + "name": "private-port", + "admin_state_up": true, + "fixed_ips": [ + { + "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2", + "ip_address": "10.0.0.2" + } + ], + "trusted": true + } +}` + +const CreatePortTrustedVIFResponse = ` +{ + "port": { + "status": "DOWN", + "name": "private-port", + "admin_state_up": true, + "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7", + "tenant_id": "d6700c0c9ffa4f1cb322cd4a1f3906fa", + "device_owner": "", + "mac_address": "fa:16:3e:c9:cb:f0", + "fixed_ips": [ + { + "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2", + "ip_address": "10.0.0.2" + } + ], + "id": "65c0ee9f-d634-4522-8954-51021b570b0d", + "trusted": true, + "device_id": "" + } +}` + +const UpdatePortTrustedVIFRequest = ` +{ + "port": { + "trusted": false + } +}` + +const UpdatePortTrustedVIFResponse = ` +{ + "port": { + "status": "DOWN", + "name": "private-port", + "admin_state_up": true, + "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7", + "tenant_id": "d6700c0c9ffa4f1cb322cd4a1f3906fa", + "device_owner": "", + "mac_address": "fa:16:3e:c9:cb:f0", + "fixed_ips": [ + { + "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2", + "ip_address": "10.0.0.3" + } + ], + "allowed_address_pairs": [ + { + "ip_address": "10.0.0.4", + "mac_address": "fa:16:3e:c9:cb:f0" + } + ], + "id": "65c0ee9f-d634-4522-8954-51021b570b0d", + "security_groups": [ + "f0ac4394-7e4a-4409-9701-ba8be283dbc3" + ], + "trusted": false, + "device_id": "" + } +} +` diff --git a/openstack/networking/v2/extensions/portstrustedvif/testing/requests_test.go b/openstack/networking/v2/extensions/portstrustedvif/testing/requests_test.go new file mode 100644 index 0000000000..a8a0e1966f --- /dev/null +++ b/openstack/networking/v2/extensions/portstrustedvif/testing/requests_test.go @@ -0,0 +1,242 @@ +package testing + +import ( + "context" + "fmt" + "net/http" + "testing" + "time" + + fake "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/common" + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/portstrustedvif" + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/ports" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func TestList(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ListResponse) + }) + + type PortWithExt struct { + ports.Port + portstrustedvif.PortTrustedVIFExt + } + var actual []PortWithExt + + iTrue := true + expected := []PortWithExt{ + { + Port: ports.Port{ + Status: "ACTIVE", + Name: "", + AdminStateUp: true, + NetworkID: "70c1db1f-b701-45bd-96e0-a313ee3430b3", + TenantID: "", + DeviceOwner: "network:router_gateway", + MACAddress: "fa:16:3e:58:42:ed", + FixedIPs: []ports.IP{ + { + SubnetID: "008ba151-0b8c-4a67-98b5-0d2b87666062", + IPAddress: "172.24.4.2", + }, + }, + ID: "d80b1a3b-4fc1-49f3-952e-1e2ab7081d8b", + SecurityGroups: []string{}, + DeviceID: "9ae135f4-b6e0-4dad-9e91-3c223e385824", + CreatedAt: time.Date(2019, time.June, 30, 4, 15, 37, 0, time.UTC), + UpdatedAt: time.Date(2019, time.June, 30, 5, 18, 49, 0, time.UTC), + }, + PortTrustedVIFExt: portstrustedvif.PortTrustedVIFExt{ + PortTrustedVIF: &iTrue, + }, + }, + } + + allPages, err := ports.List(fake.ServiceClient(fakeServer), ports.ListOpts{}).AllPages(context.TODO()) + th.AssertNoErr(t, err) + + err = ports.ExtractPortsInto(allPages, &actual) + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, expected, actual) +} + +func TestGet(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/ports/46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, GetPortTrustedVIFStatusResponse) + }) + + var s struct { + ports.Port + portstrustedvif.PortTrustedVIFExt + } + + err := ports.Get(context.TODO(), fake.ServiceClient(fakeServer), "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2").ExtractInto(&s) + th.AssertNoErr(t, err) + + th.AssertEquals(t, "DOWN", s.Status) + th.AssertEquals(t, "private-port", s.Name) + th.AssertTrue(t, s.AdminStateUp) + th.AssertEquals(t, "a87cc70a-3e15-4acf-8205-9b711a3531b7", s.NetworkID) + th.AssertEquals(t, "d6700c0c9ffa4f1cb322cd4a1f3906fa", s.TenantID) + th.AssertEquals(t, "", s.DeviceOwner) + th.AssertEquals(t, "fa:16:3e:c9:cb:f0", s.MACAddress) + th.AssertDeepEquals(t, []ports.IP{ + {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.1"}, + }, s.FixedIPs) + th.AssertEquals(t, "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", s.ID) + th.AssertEquals(t, "", s.DeviceID) + + if s.PortTrustedVIF == nil { + t.Fatalf("Expected s.PortTrustedVIF to be not nil") + } + th.AssertFalse(t, *s.PortTrustedVIF) +} + +func TestGetUnset(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/ports/46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, GetPortTrustedVIFUnsetResponse) + }) + + var s struct { + ports.Port + portstrustedvif.PortTrustedVIFExt + } + + err := ports.Get(context.TODO(), fake.ServiceClient(fakeServer), "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2").ExtractInto(&s) + th.AssertNoErr(t, err) + + th.AssertEquals(t, "DOWN", s.Status) + th.AssertEquals(t, "private-port", s.Name) + th.AssertTrue(t, s.AdminStateUp) + th.AssertEquals(t, "a87cc70a-3e15-4acf-8205-9b711a3531b7", s.NetworkID) + th.AssertEquals(t, "d6700c0c9ffa4f1cb322cd4a1f3906fa", s.TenantID) + th.AssertEquals(t, "", s.DeviceOwner) + th.AssertEquals(t, "fa:16:3e:c9:cb:f0", s.MACAddress) + th.AssertDeepEquals(t, []ports.IP{ + {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.1"}, + }, s.FixedIPs) + th.AssertEquals(t, "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", s.ID) + th.AssertEquals(t, "", s.DeviceID) + + if s.PortTrustedVIF != nil { + t.Fatalf("Expected s.PortTrustedVIF to be nil") + } +} + +func TestCreateWithPortTrustedVIF(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, CreatePortTrustedVIFRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprint(w, CreatePortTrustedVIFResponse) + }) + + var portWithExt struct { + ports.Port + portstrustedvif.PortTrustedVIFExt + } + + asu := true + portTrustedVIFStatus := true + options := ports.CreateOpts{ + Name: "private-port", + AdminStateUp: &asu, + NetworkID: "a87cc70a-3e15-4acf-8205-9b711a3531b7", + FixedIPs: []ports.IP{ + {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"}, + }, + } + createOpts := portstrustedvif.PortCreateOptsExt{ + CreateOptsBuilder: options, + PortTrustedVIF: &portTrustedVIFStatus, + } + err := ports.Create(context.TODO(), fake.ServiceClient(fakeServer), createOpts).ExtractInto(&portWithExt) + th.AssertNoErr(t, err) + + th.AssertEquals(t, "DOWN", portWithExt.Status) + th.AssertEquals(t, "private-port", portWithExt.Name) + th.AssertTrue(t, portWithExt.AdminStateUp) + th.AssertEquals(t, "a87cc70a-3e15-4acf-8205-9b711a3531b7", portWithExt.NetworkID) + th.AssertEquals(t, "d6700c0c9ffa4f1cb322cd4a1f3906fa", portWithExt.TenantID) + th.AssertEquals(t, "", portWithExt.DeviceOwner) + th.AssertEquals(t, "fa:16:3e:c9:cb:f0", portWithExt.MACAddress) + th.AssertDeepEquals(t, []ports.IP{ + {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"}, + }, portWithExt.FixedIPs) + th.AssertEquals(t, "65c0ee9f-d634-4522-8954-51021b570b0d", portWithExt.ID) + th.AssertTrue(t, *portWithExt.PortTrustedVIF) +} + +func TestUpdatePortTrustedVIF(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, UpdatePortTrustedVIFRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, UpdatePortTrustedVIFResponse) + }) + + var portWithExt struct { + ports.Port + portstrustedvif.PortTrustedVIFExt + } + + portTrustedVIFStatus := false + portUpdateOpts := ports.UpdateOpts{} + updateOpts := portstrustedvif.PortUpdateOptsExt{ + UpdateOptsBuilder: portUpdateOpts, + PortTrustedVIF: &portTrustedVIFStatus, + } + + err := ports.Update(context.TODO(), fake.ServiceClient(fakeServer), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&portWithExt) + th.AssertNoErr(t, err) + + th.AssertEquals(t, "private-port", portWithExt.Name) + th.AssertDeepEquals(t, false, *portWithExt.PortTrustedVIF) +} diff --git a/openstack/networking/v2/extensions/provider/testing/results_test.go b/openstack/networking/v2/extensions/provider/testing/results_test.go index 4e21481de1..74dda0dcb0 100644 --- a/openstack/networking/v2/extensions/provider/testing/results_test.go +++ b/openstack/networking/v2/extensions/provider/testing/results_test.go @@ -15,17 +15,17 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, nettest.ListResponse) + fmt.Fprint(w, nettest.ListResponse) }) type NetworkWithExt struct { @@ -34,7 +34,7 @@ func TestList(t *testing.T) { } var actual []NetworkWithExt - allPages, err := networks.List(fake.ServiceClient(), networks.ListOpts{}).AllPages(context.TODO()) + allPages, err := networks.List(fake.ServiceClient(fakeServer), networks.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) err = networks.ExtractNetworksInto(allPages, &actual) @@ -44,23 +44,23 @@ func TestList(t *testing.T) { th.AssertEquals(t, "db193ab3-96e3-4cb3-8fc5-05f4296d0324", actual[1].ID) th.AssertEquals(t, "local", actual[1].NetworkType) th.AssertEquals(t, "1234567890", actual[1].SegmentationID) - th.AssertEquals(t, actual[0].Subnets[0], "54d6f61d-db07-451c-9ab3-b9609b6b6f0b") - th.AssertEquals(t, actual[1].Subnets[0], "08eae331-0402-425a-923c-34f7cfe39c1b") + th.AssertEquals(t, "54d6f61d-db07-451c-9ab3-b9609b6b6f0b", actual[0].Subnets[0]) + th.AssertEquals(t, "08eae331-0402-425a-923c-34f7cfe39c1b", actual[1].Subnets[0]) } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, nettest.GetResponse) + fmt.Fprint(w, nettest.GetResponse) }) var s struct { @@ -68,7 +68,7 @@ func TestGet(t *testing.T) { provider.NetworkProviderExt } - err := networks.Get(context.TODO(), fake.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(&s) + err := networks.Get(context.TODO(), fake.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(&s) th.AssertNoErr(t, err) th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", s.ID) @@ -78,10 +78,10 @@ func TestGet(t *testing.T) { } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -91,7 +91,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, nettest.CreateResponse) + fmt.Fprint(w, nettest.CreateResponse) }) var s struct { @@ -100,7 +100,7 @@ func TestCreate(t *testing.T) { } options := networks.CreateOpts{Name: "private", AdminStateUp: gophercloud.Enabled} - err := networks.Create(context.TODO(), fake.ServiceClient(), options).ExtractInto(&s) + err := networks.Create(context.TODO(), fake.ServiceClient(fakeServer), options).ExtractInto(&s) th.AssertNoErr(t, err) th.AssertEquals(t, "db193ab3-96e3-4cb3-8fc5-05f4296d0324", s.ID) @@ -110,10 +110,10 @@ func TestCreate(t *testing.T) { } func TestCreateWithMultipleProvider(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -142,7 +142,7 @@ func TestCreateWithMultipleProvider(t *testing.T) { `) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "network": { "status": "ACTIVE", @@ -185,13 +185,13 @@ func TestCreateWithMultipleProvider(t *testing.T) { Segments: segments, } - _, err := networks.Create(context.TODO(), fake.ServiceClient(), providerCreateOpts).Extract() + _, err := networks.Create(context.TODO(), fake.ServiceClient(fakeServer), providerCreateOpts).Extract() th.AssertNoErr(t, err) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() iTrue := true name := "new_network_name" @@ -204,7 +204,7 @@ func TestUpdate(t *testing.T) { Segments: &segments, } - th.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -227,7 +227,7 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, nettest.UpdateResponse) + fmt.Fprint(w, nettest.UpdateResponse) }) var s struct { @@ -235,7 +235,7 @@ func TestUpdate(t *testing.T) { provider.NetworkProviderExt } - err := networks.Update(context.TODO(), fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", providerUpdateOpts).ExtractInto(&s) + err := networks.Update(context.TODO(), fake.ServiceClient(fakeServer), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", providerUpdateOpts).ExtractInto(&s) th.AssertNoErr(t, err) th.AssertEquals(t, "4e8e5957-649f-477b-9e5b-f1f75b21c03c", s.ID) diff --git a/openstack/networking/v2/extensions/qos/policies/requests.go b/openstack/networking/v2/extensions/qos/policies/requests.go index 88d9f85fb8..832ea2d73e 100644 --- a/openstack/networking/v2/extensions/qos/policies/requests.go +++ b/openstack/networking/v2/extensions/qos/policies/requests.go @@ -2,6 +2,7 @@ package policies import ( "context" + "fmt" "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/networks" @@ -134,7 +135,6 @@ type ListOpts struct { ProjectID string `q:"project_id"` Name string `q:"name"` Description string `q:"description"` - RevisionNumber *int `q:"revision_number"` IsDefault *bool `q:"is_default"` Shared *bool `q:"shared"` Limit int `q:"limit"` @@ -145,6 +145,7 @@ type ListOpts struct { TagsAny string `q:"tags-any"` NotTags string `q:"not-tags"` NotTagsAny string `q:"not-tags-any"` + RevisionNumber *int `q:"revision_number"` } // ToPolicyListQuery formats a ListOpts into a query string. @@ -243,6 +244,11 @@ type UpdateOpts struct { // IsDefault indicates if this QoS policy is default policy or not. IsDefault *bool `json:"is_default,omitempty"` + + // RevisionNumber implements extension:standard-attr-revisions. If != "" it + // will set revision_number=%s. If the revision number does not match, the + // update will fail. + RevisionNumber *int `json:"-" h:"If-Match"` } // ToPolicyUpdateMap builds a request body from UpdateOpts. @@ -258,8 +264,19 @@ func Update(ctx context.Context, c *gophercloud.ServiceClient, policyID string, r.Err = err return } + h, err := gophercloud.BuildHeaders(opts) + if err != nil { + r.Err = err + return + } + for k := range h { + if k == "If-Match" { + h[k] = fmt.Sprintf("revision_number=%s", h[k]) + } + } resp, err := c.Put(ctx, updateURL(c, policyID), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, + MoreHeaders: h, + OkCodes: []int{200}, }) _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return diff --git a/openstack/networking/v2/extensions/qos/policies/results.go b/openstack/networking/v2/extensions/qos/policies/results.go index 70c70379d7..886de88729 100644 --- a/openstack/networking/v2/extensions/qos/policies/results.go +++ b/openstack/networking/v2/extensions/qos/policies/results.go @@ -79,7 +79,7 @@ type Policy struct { // Shared indicates whether this policy is shared across all projects. Shared bool `json:"shared"` - // RevisionNumber represents revision number of the policy. + // RevisionNumber optionally set via extensions/standard-attr-revisions RevisionNumber int `json:"revision_number"` // Rules represents QoS rules of the policy. @@ -97,7 +97,7 @@ type PolicyPage struct { // NextPageURL is invoked when a paginated collection of policies has reached // the end of a page and the pager seeks to traverse over a new one. // In order to do this, it needs to construct the next page's URL. -func (r PolicyPage) NextPageURL() (string, error) { +func (r PolicyPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"policies_links"` } @@ -127,5 +127,5 @@ func ExtractPolicies(r pagination.Page) ([]Policy, error) { // ExtractPoliciesInto extracts the elements into a slice of RBAC Policy structs. func ExtractPolicysInto(r pagination.Page, v any) error { - return r.(PolicyPage).Result.ExtractIntoSlicePtr(v, "policies") + return r.(PolicyPage).ExtractIntoSlicePtr(v, "policies") } diff --git a/openstack/networking/v2/extensions/qos/policies/testing/requests_test.go b/openstack/networking/v2/extensions/qos/policies/testing/requests_test.go index 72b17e5330..767babf95d 100644 --- a/openstack/networking/v2/extensions/qos/policies/testing/requests_test.go +++ b/openstack/networking/v2/extensions/qos/policies/testing/requests_test.go @@ -16,17 +16,17 @@ import ( ) func TestGetPort(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - _, err := fmt.Fprintf(w, GetPortResponse) + _, err := fmt.Fprint(w, GetPortResponse) th.AssertNoErr(t, err) }) @@ -34,18 +34,18 @@ func TestGetPort(t *testing.T) { ports.Port policies.QoSPolicyExt } - err := ports.Get(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d").ExtractInto(&p) + err := ports.Get(context.TODO(), fake.ServiceClient(fakeServer), "65c0ee9f-d634-4522-8954-51021b570b0d").ExtractInto(&p) th.AssertNoErr(t, err) - th.AssertEquals(t, p.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") - th.AssertEquals(t, p.QoSPolicyID, "591e0597-39a6-4665-8149-2111d8de9a08") + th.AssertEquals(t, "65c0ee9f-d634-4522-8954-51021b570b0d", p.ID) + th.AssertEquals(t, "591e0597-39a6-4665-8149-2111d8de9a08", p.QoSPolicyID) } func TestCreatePort(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -55,7 +55,7 @@ func TestCreatePort(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - _, err := fmt.Fprintf(w, CreatePortResponse) + _, err := fmt.Fprint(w, CreatePortResponse) th.AssertNoErr(t, err) }) @@ -70,20 +70,20 @@ func TestCreatePort(t *testing.T) { CreateOptsBuilder: portCreateOpts, QoSPolicyID: "591e0597-39a6-4665-8149-2111d8de9a08", } - err := ports.Create(context.TODO(), fake.ServiceClient(), createOpts).ExtractInto(&p) + err := ports.Create(context.TODO(), fake.ServiceClient(fakeServer), createOpts).ExtractInto(&p) th.AssertNoErr(t, err) - th.AssertEquals(t, p.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7") - th.AssertEquals(t, p.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa") - th.AssertEquals(t, p.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") - th.AssertEquals(t, p.QoSPolicyID, "591e0597-39a6-4665-8149-2111d8de9a08") + th.AssertEquals(t, "a87cc70a-3e15-4acf-8205-9b711a3531b7", p.NetworkID) + th.AssertEquals(t, "d6700c0c9ffa4f1cb322cd4a1f3906fa", p.TenantID) + th.AssertEquals(t, "65c0ee9f-d634-4522-8954-51021b570b0d", p.ID) + th.AssertEquals(t, "591e0597-39a6-4665-8149-2111d8de9a08", p.QoSPolicyID) } func TestUpdatePortWithPolicy(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -93,7 +93,7 @@ func TestUpdatePortWithPolicy(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - _, err := fmt.Fprintf(w, UpdatePortWithPolicyResponse) + _, err := fmt.Fprint(w, UpdatePortWithPolicyResponse) th.AssertNoErr(t, err) }) @@ -108,20 +108,20 @@ func TestUpdatePortWithPolicy(t *testing.T) { UpdateOptsBuilder: portUpdateOpts, QoSPolicyID: &policyID, } - err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&p) + err := ports.Update(context.TODO(), fake.ServiceClient(fakeServer), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&p) th.AssertNoErr(t, err) - th.AssertEquals(t, p.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7") - th.AssertEquals(t, p.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa") - th.AssertEquals(t, p.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") - th.AssertEquals(t, p.QoSPolicyID, "591e0597-39a6-4665-8149-2111d8de9a08") + th.AssertEquals(t, "a87cc70a-3e15-4acf-8205-9b711a3531b7", p.NetworkID) + th.AssertEquals(t, "d6700c0c9ffa4f1cb322cd4a1f3906fa", p.TenantID) + th.AssertEquals(t, "65c0ee9f-d634-4522-8954-51021b570b0d", p.ID) + th.AssertEquals(t, "591e0597-39a6-4665-8149-2111d8de9a08", p.QoSPolicyID) } func TestUpdatePortWithoutPolicy(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -131,7 +131,7 @@ func TestUpdatePortWithoutPolicy(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - _, err := fmt.Fprintf(w, UpdatePortWithoutPolicyResponse) + _, err := fmt.Fprint(w, UpdatePortWithoutPolicyResponse) th.AssertNoErr(t, err) }) @@ -146,27 +146,27 @@ func TestUpdatePortWithoutPolicy(t *testing.T) { UpdateOptsBuilder: portUpdateOpts, QoSPolicyID: &policyID, } - err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&p) + err := ports.Update(context.TODO(), fake.ServiceClient(fakeServer), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&p) th.AssertNoErr(t, err) - th.AssertEquals(t, p.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7") - th.AssertEquals(t, p.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa") - th.AssertEquals(t, p.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") - th.AssertEquals(t, p.QoSPolicyID, "") + th.AssertEquals(t, "a87cc70a-3e15-4acf-8205-9b711a3531b7", p.NetworkID) + th.AssertEquals(t, "d6700c0c9ffa4f1cb322cd4a1f3906fa", p.TenantID) + th.AssertEquals(t, "65c0ee9f-d634-4522-8954-51021b570b0d", p.ID) + th.AssertEquals(t, "", p.QoSPolicyID) } func TestGetNetwork(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - _, err := fmt.Fprintf(w, GetNetworkResponse) + _, err := fmt.Fprint(w, GetNetworkResponse) th.AssertNoErr(t, err) }) @@ -174,18 +174,18 @@ func TestGetNetwork(t *testing.T) { networks.Network policies.QoSPolicyExt } - err := networks.Get(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d").ExtractInto(&n) + err := networks.Get(context.TODO(), fake.ServiceClient(fakeServer), "65c0ee9f-d634-4522-8954-51021b570b0d").ExtractInto(&n) th.AssertNoErr(t, err) - th.AssertEquals(t, n.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") - th.AssertEquals(t, n.QoSPolicyID, "591e0597-39a6-4665-8149-2111d8de9a08") + th.AssertEquals(t, "65c0ee9f-d634-4522-8954-51021b570b0d", n.ID) + th.AssertEquals(t, "591e0597-39a6-4665-8149-2111d8de9a08", n.QoSPolicyID) } func TestCreateNetwork(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -195,7 +195,7 @@ func TestCreateNetwork(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - _, err := fmt.Fprintf(w, CreateNetworkResponse) + _, err := fmt.Fprint(w, CreateNetworkResponse) th.AssertNoErr(t, err) }) @@ -210,19 +210,19 @@ func TestCreateNetwork(t *testing.T) { CreateOptsBuilder: networkCreateOpts, QoSPolicyID: "591e0597-39a6-4665-8149-2111d8de9a08", } - err := networks.Create(context.TODO(), fake.ServiceClient(), createOpts).ExtractInto(&n) + err := networks.Create(context.TODO(), fake.ServiceClient(fakeServer), createOpts).ExtractInto(&n) th.AssertNoErr(t, err) - th.AssertEquals(t, n.TenantID, "4fd44f30292945e481c7b8a0c8908869") - th.AssertEquals(t, n.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") - th.AssertEquals(t, n.QoSPolicyID, "591e0597-39a6-4665-8149-2111d8de9a08") + th.AssertEquals(t, "4fd44f30292945e481c7b8a0c8908869", n.TenantID) + th.AssertEquals(t, "65c0ee9f-d634-4522-8954-51021b570b0d", n.ID) + th.AssertEquals(t, "591e0597-39a6-4665-8149-2111d8de9a08", n.QoSPolicyID) } func TestUpdateNetworkWithPolicy(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -232,7 +232,7 @@ func TestUpdateNetworkWithPolicy(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - _, err := fmt.Fprintf(w, UpdateNetworkWithPolicyResponse) + _, err := fmt.Fprint(w, UpdateNetworkWithPolicyResponse) th.AssertNoErr(t, err) }) @@ -250,20 +250,20 @@ func TestUpdateNetworkWithPolicy(t *testing.T) { UpdateOptsBuilder: networkUpdateOpts, QoSPolicyID: &policyID, } - err := networks.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&n) + err := networks.Update(context.TODO(), fake.ServiceClient(fakeServer), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&n) th.AssertNoErr(t, err) - th.AssertEquals(t, n.TenantID, "4fd44f30292945e481c7b8a0c8908869") - th.AssertEquals(t, n.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") - th.AssertEquals(t, n.Name, "updated") - th.AssertEquals(t, n.QoSPolicyID, "591e0597-39a6-4665-8149-2111d8de9a08") + th.AssertEquals(t, "4fd44f30292945e481c7b8a0c8908869", n.TenantID) + th.AssertEquals(t, "65c0ee9f-d634-4522-8954-51021b570b0d", n.ID) + th.AssertEquals(t, "updated", n.Name) + th.AssertEquals(t, "591e0597-39a6-4665-8149-2111d8de9a08", n.QoSPolicyID) } func TestUpdateNetworkWithoutPolicy(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -273,7 +273,7 @@ func TestUpdateNetworkWithoutPolicy(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - _, err := fmt.Fprintf(w, UpdateNetworkWithoutPolicyResponse) + _, err := fmt.Fprint(w, UpdateNetworkWithoutPolicyResponse) th.AssertNoErr(t, err) }) @@ -288,31 +288,31 @@ func TestUpdateNetworkWithoutPolicy(t *testing.T) { UpdateOptsBuilder: networkUpdateOpts, QoSPolicyID: &policyID, } - err := networks.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&n) + err := networks.Update(context.TODO(), fake.ServiceClient(fakeServer), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&n) th.AssertNoErr(t, err) - th.AssertEquals(t, n.TenantID, "4fd44f30292945e481c7b8a0c8908869") - th.AssertEquals(t, n.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") - th.AssertEquals(t, n.QoSPolicyID, "") + th.AssertEquals(t, "4fd44f30292945e481c7b8a0c8908869", n.TenantID) + th.AssertEquals(t, "65c0ee9f-d634-4522-8954-51021b570b0d", n.ID) + th.AssertEquals(t, "", n.QoSPolicyID) } func TestListPolicies(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/qos/policies", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/qos/policies", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListPoliciesResponse) + fmt.Fprint(w, ListPoliciesResponse) }) count := 0 - err := policies.List(fake.ServiceClient(), policies.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := policies.List(fake.ServiceClient(fakeServer), policies.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := policies.ExtractPolicies(page) if err != nil { @@ -337,20 +337,20 @@ func TestListPolicies(t *testing.T) { } func TestGetPolicy(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/qos/policies/30a57f4a-336b-4382-8275-d708babd2241", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/qos/policies/30a57f4a-336b-4382-8275-d708babd2241", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetPolicyResponse) + fmt.Fprint(w, GetPolicyResponse) }) - p, err := policies.Get(context.TODO(), fake.ServiceClient(), "30a57f4a-336b-4382-8275-d708babd2241").Extract() + p, err := policies.Get(context.TODO(), fake.ServiceClient(fakeServer), "30a57f4a-336b-4382-8275-d708babd2241").Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "bw-limiter", p.Name) @@ -374,10 +374,10 @@ func TestGetPolicy(t *testing.T) { } func TestCreatePolicy(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/qos/policies", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/qos/policies", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -387,7 +387,7 @@ func TestCreatePolicy(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreatePolicyResponse) + fmt.Fprint(w, CreatePolicyResponse) }) opts := policies.CreateOpts{ @@ -396,12 +396,12 @@ func TestCreatePolicy(t *testing.T) { IsDefault: true, Description: "use-me", } - p, err := policies.Create(context.TODO(), fake.ServiceClient(), opts).Extract() + p, err := policies.Create(context.TODO(), fake.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "shared-default-policy", p.Name) - th.AssertEquals(t, true, p.Shared) - th.AssertEquals(t, true, p.IsDefault) + th.AssertTrue(t, p.Shared) + th.AssertTrue(t, p.IsDefault) th.AssertEquals(t, "use-me", p.Description) th.AssertEquals(t, "a77cbe0998374aed9a6798ad6c61677e", p.TenantID) th.AssertEquals(t, "a77cbe0998374aed9a6798ad6c61677e", p.ProjectID) @@ -412,10 +412,10 @@ func TestCreatePolicy(t *testing.T) { } func TestUpdatePolicy(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/qos/policies/d6ae28ce-fcb5-4180-aa62-d260a27e09ae", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/qos/policies/d6ae28ce-fcb5-4180-aa62-d260a27e09ae", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -425,7 +425,7 @@ func TestUpdatePolicy(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdatePolicyResponse) + fmt.Fprint(w, UpdatePolicyResponse) }) shared := true @@ -435,12 +435,12 @@ func TestUpdatePolicy(t *testing.T) { Shared: &shared, Description: &description, } - p, err := policies.Update(context.TODO(), fake.ServiceClient(), "d6ae28ce-fcb5-4180-aa62-d260a27e09ae", opts).Extract() + p, err := policies.Update(context.TODO(), fake.ServiceClient(fakeServer), "d6ae28ce-fcb5-4180-aa62-d260a27e09ae", opts).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "new-name", p.Name) - th.AssertEquals(t, true, p.Shared) - th.AssertEquals(t, false, p.IsDefault) + th.AssertTrue(t, p.Shared) + th.AssertFalse(t, p.IsDefault) th.AssertEquals(t, "", p.Description) th.AssertEquals(t, "a77cbe0998374aed9a6798ad6c61677e", p.TenantID) th.AssertEquals(t, "a77cbe0998374aed9a6798ad6c61677e", p.ProjectID) @@ -451,15 +451,15 @@ func TestUpdatePolicy(t *testing.T) { } func TestDeletePolicy(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/qos/policies/d6ae28ce-fcb5-4180-aa62-d260a27e09ae", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/qos/policies/d6ae28ce-fcb5-4180-aa62-d260a27e09ae", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := policies.Delete(context.TODO(), fake.ServiceClient(), "d6ae28ce-fcb5-4180-aa62-d260a27e09ae") + res := policies.Delete(context.TODO(), fake.ServiceClient(fakeServer), "d6ae28ce-fcb5-4180-aa62-d260a27e09ae") th.AssertNoErr(t, res.Err) } diff --git a/openstack/networking/v2/extensions/qos/rules/doc.go b/openstack/networking/v2/extensions/qos/rules/doc.go index f1c2d2672a..f7d3b744ad 100644 --- a/openstack/networking/v2/extensions/qos/rules/doc.go +++ b/openstack/networking/v2/extensions/qos/rules/doc.go @@ -76,7 +76,7 @@ Example of Deleting a single BandwidthLimitRule policyID := "501005fa-3b56-4061-aaca-3f24995112e1" ruleID := "30a57f4a-336b-4382-8275-d708babd2241" - err := rules.DeleteBandwidthLimitRule(fake.ServiceClient(), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241").ExtractErr() + err := rules.DeleteBandwidthLimitRule(fake.ServiceClient(fakeServer), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241").ExtractErr() if err != nil { panic(err) } @@ -151,7 +151,7 @@ Example of Deleting a single DSCPMarkingRule policyID := "501005fa-3b56-4061-aaca-3f24995112e1" ruleID := "30a57f4a-336b-4382-8275-d708babd2241" - err := rules.DeleteDSCPMarkingRule(fake.ServiceClient(), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241").ExtractErr() + err := rules.DeleteDSCPMarkingRule(fake.ServiceClient(fakeServer), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241").ExtractErr() if err != nil { panic(err) } @@ -228,7 +228,7 @@ Example of Deleting a single MinimumBandwidthRule policyID := "501005fa-3b56-4061-aaca-3f24995112e1" ruleID := "30a57f4a-336b-4382-8275-d708babd2241" - err := rules.DeleteMinimumBandwidthRule(fake.ServiceClient(), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241").ExtractErr() + err := rules.DeleteMinimumBandwidthRule(fake.ServiceClient(fakeServer), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241").ExtractErr() if err != nil { panic(err) } diff --git a/openstack/networking/v2/extensions/qos/rules/results.go b/openstack/networking/v2/extensions/qos/rules/results.go index cb2ee24887..241e8f800b 100644 --- a/openstack/networking/v2/extensions/qos/rules/results.go +++ b/openstack/networking/v2/extensions/qos/rules/results.go @@ -88,7 +88,7 @@ func ExtractBandwidthLimitRules(r pagination.Page) ([]BandwidthLimitRule, error) // ExtractBandwidthLimitRulesInto extracts the elements into a slice of RBAC Policy structs. func ExtractBandwidthLimitRulesInto(r pagination.Page, v any) error { - return r.(BandwidthLimitRulePage).Result.ExtractIntoSlicePtr(v, "bandwidth_limit_rules") + return r.(BandwidthLimitRulePage).ExtractIntoSlicePtr(v, "bandwidth_limit_rules") } // Extract is a function that accepts a result and extracts a DSCPMarkingRule. @@ -164,7 +164,7 @@ func ExtractDSCPMarkingRules(r pagination.Page) ([]DSCPMarkingRule, error) { // ExtractDSCPMarkingRulesInto extracts the elements into a slice of RBAC Policy structs. func ExtractDSCPMarkingRulesInto(r pagination.Page, v any) error { - return r.(DSCPMarkingRulePage).Result.ExtractIntoSlicePtr(v, "dscp_marking_rules") + return r.(DSCPMarkingRulePage).ExtractIntoSlicePtr(v, "dscp_marking_rules") } // Extract is a function that accepts a result and extracts a BandwidthLimitRule. @@ -243,5 +243,5 @@ func ExtractMinimumBandwidthRules(r pagination.Page) ([]MinimumBandwidthRule, er // ExtractMinimumBandwidthRulesInto extracts the elements into a slice of RBAC Policy structs. func ExtractMinimumBandwidthRulesInto(r pagination.Page, v any) error { - return r.(MinimumBandwidthRulePage).Result.ExtractIntoSlicePtr(v, "minimum_bandwidth_rules") + return r.(MinimumBandwidthRulePage).ExtractIntoSlicePtr(v, "minimum_bandwidth_rules") } diff --git a/openstack/networking/v2/extensions/qos/rules/testing/requests_test.go b/openstack/networking/v2/extensions/qos/rules/testing/requests_test.go index 4ee49605ea..06d42908cf 100644 --- a/openstack/networking/v2/extensions/qos/rules/testing/requests_test.go +++ b/openstack/networking/v2/extensions/qos/rules/testing/requests_test.go @@ -13,23 +13,23 @@ import ( ) func TestListBandwidthLimitRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/bandwidth_limit_rules", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/bandwidth_limit_rules", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, BandwidthLimitRulesListResult) + fmt.Fprint(w, BandwidthLimitRulesListResult) }) count := 0 err := rules.ListBandwidthLimitRules( - fake.ServiceClient(), + fake.ServiceClient(fakeServer), "501005fa-3b56-4061-aaca-3f24995112e1", rules.BandwidthLimitRulesListOpts{}, ).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { @@ -61,33 +61,33 @@ func TestListBandwidthLimitRule(t *testing.T) { } func TestGetBandwidthLimitRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/bandwidth_limit_rules/30a57f4a-336b-4382-8275-d708babd2241", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/bandwidth_limit_rules/30a57f4a-336b-4382-8275-d708babd2241", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, BandwidthLimitRulesGetResult) + fmt.Fprint(w, BandwidthLimitRulesGetResult) }) - r, err := rules.GetBandwidthLimitRule(context.TODO(), fake.ServiceClient(), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241").ExtractBandwidthLimitRule() + r, err := rules.GetBandwidthLimitRule(context.TODO(), fake.ServiceClient(fakeServer), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241").ExtractBandwidthLimitRule() th.AssertNoErr(t, err) - th.AssertEquals(t, r.ID, "30a57f4a-336b-4382-8275-d708babd2241") - th.AssertEquals(t, r.Direction, "egress") - th.AssertEquals(t, r.MaxBurstKBps, 300) - th.AssertEquals(t, r.MaxKBps, 3000) + th.AssertEquals(t, "30a57f4a-336b-4382-8275-d708babd2241", r.ID) + th.AssertEquals(t, "egress", r.Direction) + th.AssertEquals(t, 300, r.MaxBurstKBps) + th.AssertEquals(t, 3000, r.MaxKBps) } func TestCreateBandwidthLimitRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/bandwidth_limit_rules", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/bandwidth_limit_rules", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -97,14 +97,14 @@ func TestCreateBandwidthLimitRule(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, BandwidthLimitRulesCreateResult) + fmt.Fprint(w, BandwidthLimitRulesCreateResult) }) opts := rules.CreateBandwidthLimitRuleOpts{ MaxKBps: 2000, MaxBurstKBps: 200, } - r, err := rules.CreateBandwidthLimitRule(context.TODO(), fake.ServiceClient(), "501005fa-3b56-4061-aaca-3f24995112e1", opts).ExtractBandwidthLimitRule() + r, err := rules.CreateBandwidthLimitRule(context.TODO(), fake.ServiceClient(fakeServer), "501005fa-3b56-4061-aaca-3f24995112e1", opts).ExtractBandwidthLimitRule() th.AssertNoErr(t, err) th.AssertEquals(t, 200, r.MaxBurstKBps) @@ -112,10 +112,10 @@ func TestCreateBandwidthLimitRule(t *testing.T) { } func TestUpdateBandwidthLimitRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/bandwidth_limit_rules/30a57f4a-336b-4382-8275-d708babd2241", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/bandwidth_limit_rules/30a57f4a-336b-4382-8275-d708babd2241", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -125,7 +125,7 @@ func TestUpdateBandwidthLimitRule(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, BandwidthLimitRulesUpdateResult) + fmt.Fprint(w, BandwidthLimitRulesUpdateResult) }) maxKBps := 500 @@ -134,7 +134,7 @@ func TestUpdateBandwidthLimitRule(t *testing.T) { MaxKBps: &maxKBps, MaxBurstKBps: &maxBurstKBps, } - r, err := rules.UpdateBandwidthLimitRule(context.TODO(), fake.ServiceClient(), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241", opts).ExtractBandwidthLimitRule() + r, err := rules.UpdateBandwidthLimitRule(context.TODO(), fake.ServiceClient(fakeServer), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241", opts).ExtractBandwidthLimitRule() th.AssertNoErr(t, err) th.AssertEquals(t, 0, r.MaxBurstKBps) @@ -142,37 +142,37 @@ func TestUpdateBandwidthLimitRule(t *testing.T) { } func TestDeleteBandwidthLimitRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/bandwidth_limit_rules/30a57f4a-336b-4382-8275-d708babd2241", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/bandwidth_limit_rules/30a57f4a-336b-4382-8275-d708babd2241", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := rules.DeleteBandwidthLimitRule(context.TODO(), fake.ServiceClient(), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241") + res := rules.DeleteBandwidthLimitRule(context.TODO(), fake.ServiceClient(fakeServer), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241") th.AssertNoErr(t, res.Err) } func TestListDSCPMarkingRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/dscp_marking_rules", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/dscp_marking_rules", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, DSCPMarkingRulesListResult) + fmt.Fprint(w, DSCPMarkingRulesListResult) }) count := 0 err := rules.ListDSCPMarkingRules( - fake.ServiceClient(), + fake.ServiceClient(fakeServer), "501005fa-3b56-4061-aaca-3f24995112e1", rules.DSCPMarkingRulesListOpts{}, ).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { @@ -202,31 +202,31 @@ func TestListDSCPMarkingRule(t *testing.T) { } func TestGetDSCPMarkingRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/dscp_marking_rules/30a57f4a-336b-4382-8275-d708babd2241", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/dscp_marking_rules/30a57f4a-336b-4382-8275-d708babd2241", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, DSCPMarkingRuleGetResult) + fmt.Fprint(w, DSCPMarkingRuleGetResult) }) - r, err := rules.GetDSCPMarkingRule(context.TODO(), fake.ServiceClient(), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241").ExtractDSCPMarkingRule() + r, err := rules.GetDSCPMarkingRule(context.TODO(), fake.ServiceClient(fakeServer), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241").ExtractDSCPMarkingRule() th.AssertNoErr(t, err) - th.AssertEquals(t, r.ID, "30a57f4a-336b-4382-8275-d708babd2241") + th.AssertEquals(t, "30a57f4a-336b-4382-8275-d708babd2241", r.ID) th.AssertEquals(t, 26, r.DSCPMark) } func TestCreateDSCPMarkingRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/dscp_marking_rules", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/dscp_marking_rules", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -236,13 +236,13 @@ func TestCreateDSCPMarkingRule(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, DSCPMarkingRuleCreateResult) + fmt.Fprint(w, DSCPMarkingRuleCreateResult) }) opts := rules.CreateDSCPMarkingRuleOpts{ DSCPMark: 20, } - r, err := rules.CreateDSCPMarkingRule(context.TODO(), fake.ServiceClient(), "501005fa-3b56-4061-aaca-3f24995112e1", opts).ExtractDSCPMarkingRule() + r, err := rules.CreateDSCPMarkingRule(context.TODO(), fake.ServiceClient(fakeServer), "501005fa-3b56-4061-aaca-3f24995112e1", opts).ExtractDSCPMarkingRule() th.AssertNoErr(t, err) th.AssertEquals(t, "30a57f4a-336b-4382-8275-d708babd2241", r.ID) @@ -250,10 +250,10 @@ func TestCreateDSCPMarkingRule(t *testing.T) { } func TestUpdateDSCPMarkingRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/dscp_marking_rules/30a57f4a-336b-4382-8275-d708babd2241", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/dscp_marking_rules/30a57f4a-336b-4382-8275-d708babd2241", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -263,14 +263,14 @@ func TestUpdateDSCPMarkingRule(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, DSCPMarkingRuleUpdateResult) + fmt.Fprint(w, DSCPMarkingRuleUpdateResult) }) dscpMark := 26 opts := rules.UpdateDSCPMarkingRuleOpts{ DSCPMark: &dscpMark, } - r, err := rules.UpdateDSCPMarkingRule(context.TODO(), fake.ServiceClient(), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241", opts).ExtractDSCPMarkingRule() + r, err := rules.UpdateDSCPMarkingRule(context.TODO(), fake.ServiceClient(fakeServer), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241", opts).ExtractDSCPMarkingRule() th.AssertNoErr(t, err) th.AssertEquals(t, "30a57f4a-336b-4382-8275-d708babd2241", r.ID) @@ -278,37 +278,37 @@ func TestUpdateDSCPMarkingRule(t *testing.T) { } func TestDeleteDSCPMarkingRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/dscp_marking_rules/30a57f4a-336b-4382-8275-d708babd2241", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/dscp_marking_rules/30a57f4a-336b-4382-8275-d708babd2241", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := rules.DeleteDSCPMarkingRule(context.TODO(), fake.ServiceClient(), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241") + res := rules.DeleteDSCPMarkingRule(context.TODO(), fake.ServiceClient(fakeServer), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241") th.AssertNoErr(t, res.Err) } func TestListMinimumBandwidthRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/minimum_bandwidth_rules", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/minimum_bandwidth_rules", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, MinimumBandwidthRulesListResult) + fmt.Fprint(w, MinimumBandwidthRulesListResult) }) count := 0 err := rules.ListMinimumBandwidthRules( - fake.ServiceClient(), + fake.ServiceClient(fakeServer), "501005fa-3b56-4061-aaca-3f24995112e1", rules.MinimumBandwidthRulesListOpts{}, ).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { @@ -339,32 +339,32 @@ func TestListMinimumBandwidthRule(t *testing.T) { } func TestGetMinimumBandwidthRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/minimum_bandwidth_rules/30a57f4a-336b-4382-8275-d708babd2241", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/minimum_bandwidth_rules/30a57f4a-336b-4382-8275-d708babd2241", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, MinimumBandwidthRulesGetResult) + fmt.Fprint(w, MinimumBandwidthRulesGetResult) }) - r, err := rules.GetMinimumBandwidthRule(context.TODO(), fake.ServiceClient(), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241").ExtractMinimumBandwidthRule() + r, err := rules.GetMinimumBandwidthRule(context.TODO(), fake.ServiceClient(fakeServer), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241").ExtractMinimumBandwidthRule() th.AssertNoErr(t, err) - th.AssertEquals(t, r.ID, "30a57f4a-336b-4382-8275-d708babd2241") - th.AssertEquals(t, r.Direction, "egress") - th.AssertEquals(t, r.MinKBps, 3000) + th.AssertEquals(t, "30a57f4a-336b-4382-8275-d708babd2241", r.ID) + th.AssertEquals(t, "egress", r.Direction) + th.AssertEquals(t, 3000, r.MinKBps) } func TestCreateMinimumBandwidthRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/minimum_bandwidth_rules", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/minimum_bandwidth_rules", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -374,23 +374,23 @@ func TestCreateMinimumBandwidthRule(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, MinimumBandwidthRulesCreateResult) + fmt.Fprint(w, MinimumBandwidthRulesCreateResult) }) opts := rules.CreateMinimumBandwidthRuleOpts{ MinKBps: 2000, } - r, err := rules.CreateMinimumBandwidthRule(context.TODO(), fake.ServiceClient(), "501005fa-3b56-4061-aaca-3f24995112e1", opts).ExtractMinimumBandwidthRule() + r, err := rules.CreateMinimumBandwidthRule(context.TODO(), fake.ServiceClient(fakeServer), "501005fa-3b56-4061-aaca-3f24995112e1", opts).ExtractMinimumBandwidthRule() th.AssertNoErr(t, err) th.AssertEquals(t, 2000, r.MinKBps) } func TestUpdateMinimumBandwidthRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/minimum_bandwidth_rules/30a57f4a-336b-4382-8275-d708babd2241", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/minimum_bandwidth_rules/30a57f4a-336b-4382-8275-d708babd2241", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -400,29 +400,29 @@ func TestUpdateMinimumBandwidthRule(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, MinimumBandwidthRulesUpdateResult) + fmt.Fprint(w, MinimumBandwidthRulesUpdateResult) }) minKBps := 500 opts := rules.UpdateMinimumBandwidthRuleOpts{ MinKBps: &minKBps, } - r, err := rules.UpdateMinimumBandwidthRule(context.TODO(), fake.ServiceClient(), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241", opts).ExtractMinimumBandwidthRule() + r, err := rules.UpdateMinimumBandwidthRule(context.TODO(), fake.ServiceClient(fakeServer), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241", opts).ExtractMinimumBandwidthRule() th.AssertNoErr(t, err) th.AssertEquals(t, 500, r.MinKBps) } func TestDeleteMinimumBandwidthRule(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/minimum_bandwidth_rules/30a57f4a-336b-4382-8275-d708babd2241", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/qos/policies/501005fa-3b56-4061-aaca-3f24995112e1/minimum_bandwidth_rules/30a57f4a-336b-4382-8275-d708babd2241", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := rules.DeleteMinimumBandwidthRule(context.TODO(), fake.ServiceClient(), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241") + res := rules.DeleteMinimumBandwidthRule(context.TODO(), fake.ServiceClient(fakeServer), "501005fa-3b56-4061-aaca-3f24995112e1", "30a57f4a-336b-4382-8275-d708babd2241") th.AssertNoErr(t, res.Err) } diff --git a/openstack/networking/v2/extensions/qos/ruletypes/testing/requests_test.go b/openstack/networking/v2/extensions/qos/ruletypes/testing/requests_test.go index 74eeb49aa4..7d2bfc5835 100644 --- a/openstack/networking/v2/extensions/qos/ruletypes/testing/requests_test.go +++ b/openstack/networking/v2/extensions/qos/ruletypes/testing/requests_test.go @@ -8,16 +8,16 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/qos/ruletypes" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestListRuleTypes(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -25,7 +25,7 @@ func TestListRuleTypes(t *testing.T) { fmt.Fprint(w, ListRuleTypesResponse) }) - page, err := ruletypes.ListRuleTypes(fake.ServiceClient()).AllPages(context.TODO()) + page, err := ruletypes.ListRuleTypes(client.ServiceClient(fakeServer)).AllPages(context.TODO()) if err != nil { t.Errorf("Failed to list rule types pages: %v", err) return @@ -42,21 +42,21 @@ func TestListRuleTypes(t *testing.T) { } func TestGetRuleType(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/qos/rule-types/bandwidth_limit", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/qos/rule-types/bandwidth_limit", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - _, err := fmt.Fprintf(w, GetRuleTypeResponse) + _, err := fmt.Fprint(w, GetRuleTypeResponse) th.AssertNoErr(t, err) }) - r, err := ruletypes.GetRuleType(context.TODO(), fake.ServiceClient(), "bandwidth_limit").Extract() + r, err := ruletypes.GetRuleType(context.TODO(), client.ServiceClient(fakeServer), "bandwidth_limit").Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "bandwidth_limit", r.Type) diff --git a/openstack/networking/v2/extensions/quotas/doc.go b/openstack/networking/v2/extensions/quotas/doc.go index fe1cc26311..5e5ec3e58a 100644 --- a/openstack/networking/v2/extensions/quotas/doc.go +++ b/openstack/networking/v2/extensions/quotas/doc.go @@ -43,5 +43,15 @@ Example to Update project quotas } fmt.Printf("quotas: %#v\n", quotasInfo) + +Example to Delete project quotas + + projectID = "23d5d3f79dfa4f73b72b8b0b0063ec55" + err := quotas.Delete(context.TODO(), networkClient, projectID).ExtractErr() + if err != nil { + log.Fatal(err) + } + + fmt.Printf("Deleted quotas for project: %s\n", projectID) */ package quotas diff --git a/openstack/networking/v2/extensions/quotas/requests.go b/openstack/networking/v2/extensions/quotas/requests.go index 859c552cef..6289368841 100644 --- a/openstack/networking/v2/extensions/quotas/requests.go +++ b/openstack/networking/v2/extensions/quotas/requests.go @@ -78,3 +78,9 @@ func Update(ctx context.Context, c *gophercloud.ServiceClient, projectID string, _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } + +func Delete(ctx context.Context, c *gophercloud.ServiceClient, projectID string) (r DeleteResult) { + resp, err := c.Delete(ctx, deleteURL(c, projectID), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/networking/v2/extensions/quotas/results.go b/openstack/networking/v2/extensions/quotas/results.go index 8b1ae8a95d..5fa10cf21d 100644 --- a/openstack/networking/v2/extensions/quotas/results.go +++ b/openstack/networking/v2/extensions/quotas/results.go @@ -166,8 +166,14 @@ func (q *QuotaDetail) UnmarshalJSON(b []byte) error { return err } default: - return fmt.Errorf("reserved has unexpected type: %T", t) + return fmt.Errorf("Reserved has unexpected type: %T", t) //nolint:staticcheck } return nil } + +// DeleteResult is the response from a Delete operation. Call its ExtractErr +// method to determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/openstack/networking/v2/extensions/quotas/testing/fixtures_test.go b/openstack/networking/v2/extensions/quotas/testing/fixtures_test.go index a4a7703c89..1f34dead15 100644 --- a/openstack/networking/v2/extensions/quotas/testing/fixtures_test.go +++ b/openstack/networking/v2/extensions/quotas/testing/fixtures_test.go @@ -1,7 +1,12 @@ package testing import ( + "net/http" + "testing" + + fake "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/common" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/quotas" + th "github.com/gophercloud/gophercloud/v2/testhelper" ) const GetResponseRaw = ` @@ -140,3 +145,12 @@ var UpdateResponse = quotas.Quota{ SubnetPool: 0, Trunk: 5, } + +func MockDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2.0/quotas/0a73845280574ad389c292f6a74afa76", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} diff --git a/openstack/networking/v2/extensions/quotas/testing/requests_test.go b/openstack/networking/v2/extensions/quotas/testing/requests_test.go index 7ed3100242..e4550c11fd 100644 --- a/openstack/networking/v2/extensions/quotas/testing/requests_test.go +++ b/openstack/networking/v2/extensions/quotas/testing/requests_test.go @@ -13,58 +13,58 @@ import ( ) func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/quotas/0a73845280574ad389c292f6a74afa76", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/quotas/0a73845280574ad389c292f6a74afa76", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponseRaw) + fmt.Fprint(w, GetResponseRaw) }) - q, err := quotas.Get(context.TODO(), fake.ServiceClient(), "0a73845280574ad389c292f6a74afa76").Extract() + q, err := quotas.Get(context.TODO(), fake.ServiceClient(fakeServer), "0a73845280574ad389c292f6a74afa76").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, q, &GetResponse) } func TestGetDetail(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/quotas/0a73845280574ad389c292f6a74afa76/details.json", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/quotas/0a73845280574ad389c292f6a74afa76/details.json", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetDetailedResponseRaw) + fmt.Fprint(w, GetDetailedResponseRaw) }) - q, err := quotas.GetDetail(context.TODO(), fake.ServiceClient(), "0a73845280574ad389c292f6a74afa76").Extract() + q, err := quotas.GetDetail(context.TODO(), fake.ServiceClient(fakeServer), "0a73845280574ad389c292f6a74afa76").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, q, &GetDetailResponse) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/quotas/0a73845280574ad389c292f6a74afa76", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/quotas/0a73845280574ad389c292f6a74afa76", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateRequestResponseRaw) + fmt.Fprint(w, UpdateRequestResponseRaw) }) - q, err := quotas.Update(context.TODO(), fake.ServiceClient(), "0a73845280574ad389c292f6a74afa76", quotas.UpdateOpts{ + q, err := quotas.Update(context.TODO(), fake.ServiceClient(fakeServer), "0a73845280574ad389c292f6a74afa76", quotas.UpdateOpts{ FloatingIP: gophercloud.IntToPointer(0), Network: gophercloud.IntToPointer(-1), Port: gophercloud.IntToPointer(5), @@ -80,3 +80,13 @@ func TestUpdate(t *testing.T) { th.AssertNoErr(t, err) th.AssertDeepEquals(t, q, &UpdateResponse) } + +func TestDelete(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + MockDeleteResponse(t, fakeServer) + + res := quotas.Delete(context.TODO(), fake.ServiceClient(fakeServer), "0a73845280574ad389c292f6a74afa76") + th.AssertNoErr(t, res.Err) +} diff --git a/openstack/networking/v2/extensions/quotas/urls.go b/openstack/networking/v2/extensions/quotas/urls.go index 94cbe23880..726c4da8eb 100644 --- a/openstack/networking/v2/extensions/quotas/urls.go +++ b/openstack/networking/v2/extensions/quotas/urls.go @@ -24,3 +24,7 @@ func getDetailURL(c *gophercloud.ServiceClient, projectID string) string { func updateURL(c *gophercloud.ServiceClient, projectID string) string { return resourceURL(c, projectID) } + +func deleteURL(c *gophercloud.ServiceClient, projectID string) string { + return getURL(c, projectID) +} diff --git a/openstack/networking/v2/extensions/rbacpolicies/results.go b/openstack/networking/v2/extensions/rbacpolicies/results.go index abbd044b91..9550b8d15b 100644 --- a/openstack/networking/v2/extensions/rbacpolicies/results.go +++ b/openstack/networking/v2/extensions/rbacpolicies/results.go @@ -17,7 +17,7 @@ func (r commonResult) Extract() (*RBACPolicy, error) { } func (r commonResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "rbac_policy") + return r.ExtractIntoStructPtr(v, "rbac_policy") } // CreateResult represents the result of a create operation. Call its Extract @@ -101,5 +101,5 @@ func ExtractRBACPolicies(r pagination.Page) ([]RBACPolicy, error) { // ExtractRBACPolicesInto extracts the elements into a slice of RBAC Policy structs. func ExtractRBACPolicesInto(r pagination.Page, v any) error { - return r.(RBACPolicyPage).Result.ExtractIntoSlicePtr(v, "rbac_policies") + return r.(RBACPolicyPage).ExtractIntoSlicePtr(v, "rbac_policies") } diff --git a/openstack/networking/v2/extensions/rbacpolicies/testing/requests_test.go b/openstack/networking/v2/extensions/rbacpolicies/testing/requests_test.go index 7503bc7a74..e4f9558521 100644 --- a/openstack/networking/v2/extensions/rbacpolicies/testing/requests_test.go +++ b/openstack/networking/v2/extensions/rbacpolicies/testing/requests_test.go @@ -13,10 +13,10 @@ import ( ) func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/rbac-policies", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/rbac-policies", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -25,7 +25,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) options := rbacpolicies.CreateOpts{ @@ -34,46 +34,46 @@ func TestCreate(t *testing.T) { TargetTenant: "6e547a3bcfe44702889fdeff3c3520c3", ObjectID: "240d22bf-bd17-4238-9758-25f72610ecdc", } - rbacResult, err := rbacpolicies.Create(context.TODO(), fake.ServiceClient(), options).Extract() + rbacResult, err := rbacpolicies.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, &rbacPolicy1, rbacResult) } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/rbac-policies/2cf7523a-93b5-4e69-9360-6c6bf986bb7c", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/rbac-policies/2cf7523a-93b5-4e69-9360-6c6bf986bb7c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) - n, err := rbacpolicies.Get(context.TODO(), fake.ServiceClient(), "2cf7523a-93b5-4e69-9360-6c6bf986bb7c").Extract() + n, err := rbacpolicies.Get(context.TODO(), fake.ServiceClient(fakeServer), "2cf7523a-93b5-4e69-9360-6c6bf986bb7c").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &rbacPolicy1, n) } func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/rbac-policies", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/rbac-policies", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) count := 0 err := rbacpolicies.List(client, rbacpolicies.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { @@ -96,20 +96,20 @@ func TestList(t *testing.T) { } func TestListWithAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/rbac-policies", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/rbac-policies", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) type newRBACPolicy struct { rbacpolicies.RBACPolicy @@ -123,33 +123,33 @@ func TestListWithAllPages(t *testing.T) { err = rbacpolicies.ExtractRBACPolicesInto(allPages, &allRBACpolicies) th.AssertNoErr(t, err) - th.AssertEquals(t, allRBACpolicies[0].ObjectType, "network") + th.AssertEquals(t, "network", allRBACpolicies[0].ObjectType) th.AssertEquals(t, allRBACpolicies[0].Action, rbacpolicies.ActionAccessShared) - th.AssertEquals(t, allRBACpolicies[1].ProjectID, "1ae27ce0a2a54cc6ae06dc62dd0ec832") - th.AssertEquals(t, allRBACpolicies[1].TargetTenant, "1a547a3bcfe44702889fdeff3c3520c3") + th.AssertEquals(t, "1ae27ce0a2a54cc6ae06dc62dd0ec832", allRBACpolicies[1].ProjectID) + th.AssertEquals(t, "1a547a3bcfe44702889fdeff3c3520c3", allRBACpolicies[1].TargetTenant) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/rbac-policies/71d55b18-d2f8-4c76-a5e6-e0a3dd114361", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/rbac-policies/71d55b18-d2f8-4c76-a5e6-e0a3dd114361", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := rbacpolicies.Delete(context.TODO(), fake.ServiceClient(), "71d55b18-d2f8-4c76-a5e6-e0a3dd114361").ExtractErr() + res := rbacpolicies.Delete(context.TODO(), fake.ServiceClient(fakeServer), "71d55b18-d2f8-4c76-a5e6-e0a3dd114361").ExtractErr() th.AssertNoErr(t, res) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/rbac-policies/2cf7523a-93b5-4e69-9360-6c6bf986bb7c", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/rbac-policies/2cf7523a-93b5-4e69-9360-6c6bf986bb7c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -159,13 +159,13 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateResponse) + fmt.Fprint(w, UpdateResponse) }) options := rbacpolicies.UpdateOpts{TargetTenant: "9d766060b6354c9e8e2da44cab0e8f38"} - rbacResult, err := rbacpolicies.Update(context.TODO(), fake.ServiceClient(), "2cf7523a-93b5-4e69-9360-6c6bf986bb7c", options).Extract() + rbacResult, err := rbacpolicies.Update(context.TODO(), fake.ServiceClient(fakeServer), "2cf7523a-93b5-4e69-9360-6c6bf986bb7c", options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, rbacResult.TargetTenant, "9d766060b6354c9e8e2da44cab0e8f38") - th.AssertEquals(t, rbacResult.ID, "2cf7523a-93b5-4e69-9360-6c6bf986bb7c") + th.AssertEquals(t, "9d766060b6354c9e8e2da44cab0e8f38", rbacResult.TargetTenant) + th.AssertEquals(t, "2cf7523a-93b5-4e69-9360-6c6bf986bb7c", rbacResult.ID) } diff --git a/openstack/networking/v2/extensions/security/addressgroups/doc.go b/openstack/networking/v2/extensions/security/addressgroups/doc.go new file mode 100644 index 0000000000..e1921f4306 --- /dev/null +++ b/openstack/networking/v2/extensions/security/addressgroups/doc.go @@ -0,0 +1,91 @@ +/* +Package addressgroups provides information and interaction with Address Groups +for the OpenStack Networking services. + +Example to List Address Groups + + listOpts := addressgroups.ListOpts{ + } + + allPages, err := addressgroups.List(networkClient, listOpts).AllPages(context.TODO()) + if err != nil { + panic(err) + } + + allAddressGroups, err := addressgroups.ExtractGroups(allPages) + if err != nil { + panic(err) + } + + for _, addressGroup := range allAddressGroups { + fmt.Printf("%+v\n", addressGroup) + } + +Example to Get an Address Group + + groupID := "37d94f8a-d136-465c-ae46-144f0d8ef141" + addressGroup, err := addressgroups.Get(context.TODO(), networkClient, groupID).Extract() + if err != nil { + panic(err) + } + +Example to Create an Address Group + + createOpts := addressgroups.CreateOpts{ + Name: "addressGroupName", + Addresses: []string{"10.2.30.4/32", "10.2.30.6/32"}, + Description: "Created address group", + } + + addressGroup, err := addressgroups.Create(context.TODO(), networkClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Address Group + + groupID := "37d94f8a-d136-465c-ae46-144f0d8ef141" + err := addressgroups.Delete(context.TODO(), computeClient, groupID).ExtractErr() + if err != nil { + panic(err) + } + +Example to update an existing Address Group + + groupID := "37d94f8a-d136-465c-ae46-144f0d8ef141" + name := "ADDR_GP_2" + description := "new description" + updateOpts := addressgroups.UpdateOpts{ + Name: &name, + Description: &description, + } + addressGroup, err := addressgroups.UpdateAddressGroup(context.TODO(), networkClient, groupID, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to add addresses to an existing Address Group + + groupID := "37d94f8a-d136-465c-ae46-144f0d8ef141" + createOpts := addressgroups.UpdateAddressesOpts{ + Addresses: []string{"10.2.30.4/32", "10.2.30.6/32"}, + } + addressGroup, err := addressgroups.AddAddresses(context.TODO(), networkClient, groupID, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to remove addresses from an existing Address Group + + groupID := "37d94f8a-d136-465c-ae46-144f0d8ef141" + createOpts := addressgroups.UpdateAddressesOpts{ + Addresses: []string{"10.2.30.4/32", "10.2.30.6/32"}, + } + addressGroup, err := addressgroups.RemoveAddresses(context.TODO(), networkClient, groupID, createOpts).Extract() + if err != nil { + panic(err) + } + +*/ + +package addressgroups diff --git a/openstack/networking/v2/extensions/security/addressgroups/requests.go b/openstack/networking/v2/extensions/security/addressgroups/requests.go new file mode 100644 index 0000000000..8d2d6fedb3 --- /dev/null +++ b/openstack/networking/v2/extensions/security/addressgroups/requests.go @@ -0,0 +1,197 @@ +package addressgroups + +import ( + "context" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToAddressGroupListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the address group attributes you want to see returned. SortKey allows +// you to sort by a particular network attribute. SortDir sets the direction, +// and is either `asc' or `desc'. Marker and Limit are used for pagination. +type ListOpts struct { + ID string `q:"id"` + Name string `q:"name"` + Description string `q:"description"` + ProjectID string `q:"project_id"` + Addresses []string `q:"addresses"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// ToAddressGroupListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToAddressGroupListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// address groups. It accepts a ListOpts struct, which allows you to filter +// and sort the returned collection for greater efficiency. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToAddressGroupListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return AddressGroupPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToAddressGroupCreateMap() (map[string]any, error) +} + +// CreateOpts contains all the values needed to create a new address group. +type CreateOpts struct { + // The address group ID to associate with this address group. + ID string `json:"id,omitempty"` + + // Human readable name for the address group (255 characters limit). Does not have to be unique. + Name string `json:"name,omitempty"` + + // Human readable description for the address group (255 characters limit). + Description string `json:"description,omitempty"` + + // Owner of the address group. + // Only administrative users can specify a project UUID other than their own. + ProjectID string `json:"project_id,omitempty"` + + // Array of address. It supports both CIDR and IP range objects. + // An example of addresses: [“132.168.4.12/24”, “132.168.5.12-132.168.5.24”, “2001::db8::f00/64”] + Addresses []string `json:"addresses" required:"true"` +} + +// ToAddressGroupCreateMap builds a request body from CreateOpts. +func (opts CreateOpts) ToAddressGroupCreateMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "address_group") +} + +// ToAddressesCreateMap builds a request body from CreateOpts. +func (opts CreateOpts) ToAddressesCreateMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// Create is an operation which creates a new address group and associates it +// with an existing address group (whose ID is specified in CreateOpts). +func Create(ctx context.Context, c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToAddressGroupCreateMap() + if err != nil { + r.Err = err + return + } + resp, err := c.Post(ctx, rootURL(c), b, &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// Get retrieves a particular address group based on its unique ID. +func Get(ctx context.Context, c *gophercloud.ServiceClient, id string) (r GetResult) { + resp, err := c.Get(ctx, resourceURL(c, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// Delete will permanently delete a particular address group based on its +// unique ID. +func Delete(ctx context.Context, c *gophercloud.ServiceClient, id string) (r DeleteResult) { + resp, err := c.Delete(ctx, resourceURL(c, id), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update requests. +type UpdateOptsBuilder interface { + ToAddressGroupUpdateMap() (map[string]any, error) +} + +// UpdateOpts contains all the values needed to update an address group. +type UpdateOpts struct { + // Human readable name for the address group (255 characters limit). Does not have to be unique. + Name *string `json:"name,omitempty"` + // Human readable description for the address group (255 characters limit). + Description *string `json:"description,omitempty"` +} + +// ToAddressGroupUpdateMap builds a request body from UpdateOpts. +func (opts UpdateOpts) ToAddressGroupUpdateMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "address_group") +} + +// Update will update a particular address group with a complete new set of data. +func Update(ctx context.Context, c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToAddressGroupUpdateMap() + if err != nil { + r.Err = err + return + } + resp, err := c.Put(ctx, resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// UpdateAddressesOpts will add or remove a list of particular addresses from +// an address group. It is used by both AddAddresses and RemoveAddresses +// requests. +type UpdateAddressesOpts struct { + Addresses []string `json:"addresses" required:"true"` +} + +// AddressesBuilder allows extensions to add additional parameters to the +// AddAddresses or RemoveAddresses requests. +type UpdateAddressesBuilder interface { + ToUpdateAddressesMap() (map[string]any, error) +} + +// ToAddressesCreateMap builds a request body from CreateOpts. +func (opts UpdateAddressesOpts) ToUpdateAddressesMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// AddAddresses will add IP addresses to a particular address group. +func AddAddresses(ctx context.Context, c *gophercloud.ServiceClient, id string, opts UpdateAddressesBuilder) (r AddAddressesResult) { + b, err := opts.ToUpdateAddressesMap() + if err != nil { + r.Err = err + return + } + resp, err := c.Put(ctx, resourceAddAddressesURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// RemoveAddresses will remove particular IP addresses from a particular address group. +func RemoveAddresses(ctx context.Context, c *gophercloud.ServiceClient, id string, opts UpdateAddressesBuilder) (r RemoveAddressesResult) { + b, err := opts.ToUpdateAddressesMap() + if err != nil { + r.Err = err + return + } + resp, err := c.Put(ctx, resourceRemoveAddressesURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/networking/v2/extensions/security/addressgroups/results.go b/openstack/networking/v2/extensions/security/addressgroups/results.go new file mode 100644 index 0000000000..f1d21250cd --- /dev/null +++ b/openstack/networking/v2/extensions/security/addressgroups/results.go @@ -0,0 +1,115 @@ +package addressgroups + +import ( + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/pagination" +) + +// AddressGroup represents a container for address groups. +type AddressGroup struct { + // Unique identifier for the address_group object. + ID string `json:"id"` + + // Human readable name for the address group (255 characters limit). Does not have to be unique. + Name string `json:"name"` + + // Human readable description for the address group (255 characters limit). + Description string `json:"description"` + + // ProjectID is the project owner of this address group. + ProjectID string `json:"project_id"` + + // Array of address. It supports both CIDR and IP range objects. + // An example of addresses: [“132.168.4.12/24”, “132.168.5.12-132.168.5.24”, “2001::db8::f00/64”] + Addresses []string `json:"addresses"` +} + +// AddressGroupPage is the page returned by a pager when traversing over a +// collection of address group addresses. +type AddressGroupPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of address groups has +// reached the end of a page and the pager seeks to traverse over a new one. In +// order to do this, it needs to construct the next page's URL. +func (r AddressGroupPage) NextPageURL(endpointURL string) (string, error) { + var s struct { + Links []gophercloud.Link `json:"address_groups_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a AddressGroupPage struct is empty. +func (r AddressGroupPage) IsEmpty() (bool, error) { + if r.StatusCode == 204 { + return true, nil + } + + is, err := ExtractGroups(r) + return len(is) == 0, err +} + +// ExtractGroups accepts a Page struct, specifically a AddressGroupPage struct, +// and extracts the elements into a slice of AddressGroup structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractGroups(r pagination.Page) ([]AddressGroup, error) { + var s struct { + AddressGroups []AddressGroup `json:"address_groups"` + } + err := (r.(AddressGroupPage)).ExtractInto(&s) + return s.AddressGroups, err +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a address group. +func (r commonResult) Extract() (*AddressGroup, error) { + var s struct { + AddressGroup *AddressGroup `json:"address_group"` + } + err := r.ExtractInto(&s) + return s.AddressGroup, err +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a AddressGroup. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a AddressGroup. +type GetResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// UpdateResult represents the result of an update address group operation. Call its Extract +// method to interpret it as a AddressGroup. +type UpdateResult struct { + commonResult +} + +// AddAddressesResult represents the result of an add addresses operation. Call its Extract +// method to interpret it as a AddressGroup. +type AddAddressesResult struct { + commonResult +} + +// RemoveAddressesResult represents the result of a remove addresses operation. Call its Extract +// method to interpret it as a AddressGroup. +type RemoveAddressesResult struct { + commonResult +} diff --git a/openstack/networking/v2/extensions/security/addressgroups/testing/fixtures_test.go b/openstack/networking/v2/extensions/security/addressgroups/testing/fixtures_test.go new file mode 100644 index 0000000000..5375868636 --- /dev/null +++ b/openstack/networking/v2/extensions/security/addressgroups/testing/fixtures_test.go @@ -0,0 +1,119 @@ +package testing + +const AddressGroupListResponse = ` +{ + "address_groups": [ + { + "id": "8722e0e0-9cc9-4490-9660-8c9a5732fbb0", + "project_id": "45977fa2dbd7482098dd68d0d8970117", + "name": "ADDR_GP_1", + "addresses": [ + "132.168.4.12/24" + ] + } + ] +} +` + +const AddressGroupGetResponse = ` +{ + "address_group": { + "description": "", + "id": "8722e0e0-9cc9-4490-9660-8c9a5732fbb0", + "name": "ADDR_GP_1", + "project_id": "45977fa2dbd7482098dd68d0d8970117", + "addresses": [ + "132.168.4.12/24" + ] + } +} +` + +const AddressGroupCreateRequest = ` +{ + "address_group": { + "name": "ADDR_GP_1", + "addresses": [ + "132.168.4.12/24" + ] + } +} +` + +const AddressGroupCreateResponse = ` +{ + "address_group": { + "description": "", + "id": "8722e0e0-9cc9-4490-9660-8c9a5732fbb0", + "name": "ADDR_GP_1", + "project_id": "45977fa2dbd7482098dd68d0d8970117", + "addresses": [ + "132.168.4.12/24" + ] + } +} +` + +const AddressGroupUpdateRequest = ` +{ + "address_group": { + "description": "new description", + "name": "ADDR_GP_2" + } +} +` + +const AddressGroupUpdateResponse = ` +{ + "address_group": { + "description": "new description", + "id": "8722e0e0-9cc9-4490-9660-8c9a5732fbb0", + "name": "ADDR_GP_2", + "project_id": "45977fa2dbd7482098dd68d0d8970117", + "addresses": [ + "192.168.4.1/32" + ] + } +} +` + +const AddressGroupAddAddressesRequest = ` +{ + "addresses": ["192.168.4.1/32"] +} +` + +const AddressGroupAddAddressesResponse = ` +{ + "address_group": { + "description": "original description", + "id": "8722e0e0-9cc9-4490-9660-8c9a5732fbb0", + "name": "ADDR_GP_1", + "project_id": "45977fa2dbd7482098dd68d0d8970117", + "addresses": [ + "132.168.4.12/24", + "192.168.4.1/32" + ] + } +} +` + +const AddressGroupRemoveAddressesRequest = ` +{ + "addresses": ["192.168.4.1/32"] +} +` + +const AddressGroupRemoveAddressesResponse = ` +{ + "address_group": { + "description": "original description", + "id": "8722e0e0-9cc9-4490-9660-8c9a5732fbb0", + "name": "ADDR_GP_1", + "project_id": "45977fa2dbd7482098dd68d0d8970117", + "addresses": [ + "132.168.4.12/24" + ] + } +} +` diff --git a/openstack/networking/v2/extensions/security/addressgroups/testing/requests_test.go b/openstack/networking/v2/extensions/security/addressgroups/testing/requests_test.go new file mode 100644 index 0000000000..37f8184dc2 --- /dev/null +++ b/openstack/networking/v2/extensions/security/addressgroups/testing/requests_test.go @@ -0,0 +1,227 @@ +package testing + +import ( + "context" + "fmt" + "net/http" + "testing" + + fake "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/common" + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/security/addressgroups" + "github.com/gophercloud/gophercloud/v2/pagination" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func TestList(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/address-groups", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, AddressGroupListResponse) + }) + + count := 0 + + err := addressgroups.List(fake.ServiceClient(fakeServer), addressgroups.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + count++ + actual, err := addressgroups.ExtractGroups(page) + if err != nil { + t.Errorf("Failed to extract address groups: %v", err) + return false, err + } + + expected := []addressgroups.AddressGroup{ + { + Description: "", + ID: "8722e0e0-9cc9-4490-9660-8c9a5732fbb0", + Name: "ADDR_GP_1", + ProjectID: "45977fa2dbd7482098dd68d0d8970117", + Addresses: []string{ + "132.168.4.12/24", + }, + }, + } + + th.CheckDeepEquals(t, expected, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + + if count != 1 { + t.Errorf("Expected 1 page, got %d", count) + } +} + +func TestCreate(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/address-groups", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, AddressGroupCreateRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprint(w, AddressGroupCreateResponse) + }) + + opts := addressgroups.CreateOpts{ + Name: "ADDR_GP_1", + Addresses: []string{ + "132.168.4.12/24", + }, + } + _, err := addressgroups.Create(context.TODO(), fake.ServiceClient(fakeServer), opts).Extract() + th.AssertNoErr(t, err) +} + +func TestRequiredCreateOpts(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + _, err := addressgroups.Create(context.TODO(), fake.ServiceClient(fakeServer), addressgroups.CreateOpts{Name: "ADDR_GP_1"}).Extract() + if err == nil { + t.Fatalf("Expected error, got none") + } +} + +func TestGet(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/address-groups/8722e0e0-9cc9-4490-9660-8c9a5732fbb0", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, AddressGroupGetResponse) + }) + + sr, err := addressgroups.Get(context.TODO(), fake.ServiceClient(fakeServer), "8722e0e0-9cc9-4490-9660-8c9a5732fbb0").Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, "", sr.Description) + th.AssertEquals(t, "8722e0e0-9cc9-4490-9660-8c9a5732fbb0", sr.ID) + th.AssertEquals(t, "45977fa2dbd7482098dd68d0d8970117", sr.ProjectID) + th.CheckDeepEquals(t, []string{"132.168.4.12/24"}, sr.Addresses) + th.AssertEquals(t, "ADDR_GP_1", sr.Name) +} + +func TestUpdate(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/address-groups/8722e0e0-9cc9-4490-9660-8c9a5732fbb0", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, AddressGroupUpdateRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, AddressGroupUpdateResponse) + }) + + name := "ADDR_GP_2" + description := "new description" + opts := addressgroups.UpdateOpts{ + Name: &name, + Description: &description, + } + ag, err := addressgroups.Update(context.TODO(), fake.ServiceClient(fakeServer), "8722e0e0-9cc9-4490-9660-8c9a5732fbb0", opts).Extract() + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, []string{"192.168.4.1/32"}, ag.Addresses) + th.AssertEquals(t, "new description", ag.Description) + th.AssertEquals(t, "8722e0e0-9cc9-4490-9660-8c9a5732fbb0", ag.ID) + th.AssertEquals(t, "45977fa2dbd7482098dd68d0d8970117", ag.ProjectID) + th.AssertEquals(t, "ADDR_GP_2", ag.Name) +} + +func TestAddAddresses(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/address-groups/8722e0e0-9cc9-4490-9660-8c9a5732fbb0/add_addresses", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, AddressGroupAddAddressesRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, AddressGroupAddAddressesResponse) + }) + + opts := addressgroups.UpdateAddressesOpts{ + Addresses: []string{"192.168.4.1/32"}, + } + ag, err := addressgroups.AddAddresses(context.TODO(), fake.ServiceClient(fakeServer), "8722e0e0-9cc9-4490-9660-8c9a5732fbb0", opts).Extract() + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, []string{"132.168.4.12/24", "192.168.4.1/32"}, ag.Addresses) + th.AssertEquals(t, "original description", ag.Description) + th.AssertEquals(t, "8722e0e0-9cc9-4490-9660-8c9a5732fbb0", ag.ID) +} + +func TestRemoveAddresses(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/address-groups/8722e0e0-9cc9-4490-9660-8c9a5732fbb0/remove_addresses", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, AddressGroupRemoveAddressesRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, AddressGroupRemoveAddressesResponse) + }) + + opts := addressgroups.UpdateAddressesOpts{ + Addresses: []string{"192.168.4.1/32"}, + } + ag, err := addressgroups.RemoveAddresses(context.TODO(), fake.ServiceClient(fakeServer), "8722e0e0-9cc9-4490-9660-8c9a5732fbb0", opts).Extract() + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, []string{"132.168.4.12/24"}, ag.Addresses) + th.AssertEquals(t, "original description", ag.Description) + th.AssertEquals(t, "8722e0e0-9cc9-4490-9660-8c9a5732fbb0", ag.ID) +} + +func TestDelete(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/address-groups/4ec89087-d057-4e2c-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) + + err := addressgroups.Delete(context.TODO(), fake.ServiceClient(fakeServer), "4ec89087-d057-4e2c-911f-60a3b47ee304").ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/openstack/networking/v2/extensions/security/addressgroups/urls.go b/openstack/networking/v2/extensions/security/addressgroups/urls.go new file mode 100644 index 0000000000..60525743ed --- /dev/null +++ b/openstack/networking/v2/extensions/security/addressgroups/urls.go @@ -0,0 +1,21 @@ +package addressgroups + +import "github.com/gophercloud/gophercloud/v2" + +const rootPath = "address-groups" + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, id) +} + +func resourceAddAddressesURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, id, "add_addresses") +} + +func resourceRemoveAddressesURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, id, "remove_addresses") +} diff --git a/openstack/networking/v2/extensions/security/groups/requests.go b/openstack/networking/v2/extensions/security/groups/requests.go index a0e509173f..f6d8a7570a 100644 --- a/openstack/networking/v2/extensions/security/groups/requests.go +++ b/openstack/networking/v2/extensions/security/groups/requests.go @@ -2,6 +2,7 @@ package groups import ( "context" + "fmt" "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/pagination" @@ -19,20 +20,21 @@ type ListOptsBuilder interface { // sort by a particular network attribute. SortDir sets the direction, and is // either `asc' or `desc'. Marker and Limit are used for pagination. type ListOpts struct { - ID string `q:"id"` - Name string `q:"name"` - Description string `q:"description"` - Stateful *bool `q:"stateful"` - TenantID string `q:"tenant_id"` - ProjectID string `q:"project_id"` - Limit int `q:"limit"` - Marker string `q:"marker"` - SortKey string `q:"sort_key"` - SortDir string `q:"sort_dir"` - Tags string `q:"tags"` - TagsAny string `q:"tags-any"` - NotTags string `q:"not-tags"` - NotTagsAny string `q:"not-tags-any"` + ID string `q:"id"` + Name string `q:"name"` + Description string `q:"description"` + Stateful *bool `q:"stateful"` + TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` + Tags string `q:"tags"` + TagsAny string `q:"tags-any"` + NotTags string `q:"not-tags"` + NotTagsAny string `q:"not-tags-any"` + RevisionNumber *int `q:"revision_number"` } // ToPortListQuery formats a ListOpts into a query string. @@ -112,13 +114,18 @@ type UpdateOptsBuilder interface { // group. type UpdateOpts struct { // Human-readable name for the Security Group. Does not have to be unique. - Name string `json:"name,omitempty"` + Name *string `json:"name,omitempty"` // Describes the security group. Description *string `json:"description,omitempty"` // Stateful indicates if the security group is stateful or stateless. Stateful *bool `json:"stateful,omitempty"` + + // RevisionNumber implements extension:standard-attr-revisions. If != "" it + // will set revision_number=%s. If the revision number does not match, the + // update will fail. + RevisionNumber *int `json:"-" h:"If-Match"` } // ToSecGroupUpdateMap builds a request body from UpdateOpts. @@ -133,9 +140,20 @@ func Update(ctx context.Context, c *gophercloud.ServiceClient, id string, opts U r.Err = err return } + h, err := gophercloud.BuildHeaders(opts) + if err != nil { + r.Err = err + return + } + for k := range h { + if k == "If-Match" { + h[k] = fmt.Sprintf("revision_number=%s", h[k]) + } + } resp, err := c.Put(ctx, resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, + MoreHeaders: h, + OkCodes: []int{200}, }) _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return diff --git a/openstack/networking/v2/extensions/security/groups/results.go b/openstack/networking/v2/extensions/security/groups/results.go index b3aa2efb48..9c5244989d 100644 --- a/openstack/networking/v2/extensions/security/groups/results.go +++ b/openstack/networking/v2/extensions/security/groups/results.go @@ -41,6 +41,9 @@ type SecGroup struct { // Tags optionally set via extensions/attributestags Tags []string `json:"tags"` + + // RevisionNumber optionally set via extensions/standard-attr-revisions + RevisionNumber int `json:"revision_number"` } func (r *SecGroup) UnmarshalJSON(b []byte) error { @@ -90,7 +93,7 @@ type SecGroupPage struct { // NextPageURL is invoked when a paginated collection of security groups has // reached the end of a page and the pager seeks to traverse over a new one. In // order to do this, it needs to construct the next page's URL. -func (r SecGroupPage) NextPageURL() (string, error) { +func (r SecGroupPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"security_groups_links"` } diff --git a/openstack/networking/v2/extensions/security/groups/testing/requests_test.go b/openstack/networking/v2/extensions/security/groups/testing/requests_test.go index 30c3ab6359..f05cbf05dd 100644 --- a/openstack/networking/v2/extensions/security/groups/testing/requests_test.go +++ b/openstack/networking/v2/extensions/security/groups/testing/requests_test.go @@ -14,22 +14,22 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/security-groups", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/security-groups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, SecurityGroupListResponse) + fmt.Fprint(w, SecurityGroupListResponse) }) count := 0 - err := groups.List(fake.ServiceClient(), groups.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := groups.List(fake.ServiceClient(fakeServer), groups.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := groups.ExtractGroups(page) if err != nil { @@ -51,10 +51,10 @@ func TestList(t *testing.T) { } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/security-groups", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/security-groups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -64,19 +64,19 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, SecurityGroupCreateResponse) + fmt.Fprint(w, SecurityGroupCreateResponse) }) opts := groups.CreateOpts{Name: "new-webservers", Description: "security group for webservers"} - _, err := groups.Create(context.TODO(), fake.ServiceClient(), opts).Extract() + _, err := groups.Create(context.TODO(), fake.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/security-groups/2076db17-a522-4506-91de-c6dd8e837028", + fakeServer.Mux.HandleFunc("/v2.0/security-groups/2076db17-a522-4506-91de-c6dd8e837028", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) @@ -87,11 +87,12 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, SecurityGroupUpdateResponse) + fmt.Fprint(w, SecurityGroupUpdateResponse) }) - opts := groups.UpdateOpts{Name: "newer-webservers"} - sg, err := groups.Update(context.TODO(), fake.ServiceClient(), "2076db17-a522-4506-91de-c6dd8e837028", opts).Extract() + name := "newer-webservers" + opts := groups.UpdateOpts{Name: &name} + sg, err := groups.Update(context.TODO(), fake.ServiceClient(fakeServer), "2076db17-a522-4506-91de-c6dd8e837028", opts).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "newer-webservers", sg.Name) @@ -102,20 +103,20 @@ func TestUpdate(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/security-groups/85cc3048-abc3-43cc-89b3-377341426ac5", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/security-groups/85cc3048-abc3-43cc-89b3-377341426ac5", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, SecurityGroupGetResponse) + fmt.Fprint(w, SecurityGroupGetResponse) }) - sg, err := groups.Get(context.TODO(), fake.ServiceClient(), "85cc3048-abc3-43cc-89b3-377341426ac5").Extract() + sg, err := groups.Get(context.TODO(), fake.ServiceClient(fakeServer), "85cc3048-abc3-43cc-89b3-377341426ac5").Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "default", sg.Description) @@ -128,15 +129,15 @@ func TestGet(t *testing.T) { } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/security-groups/4ec89087-d057-4e2c-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/security-groups/4ec89087-d057-4e2c-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := groups.Delete(context.TODO(), fake.ServiceClient(), "4ec89087-d057-4e2c-911f-60a3b47ee304") + res := groups.Delete(context.TODO(), fake.ServiceClient(fakeServer), "4ec89087-d057-4e2c-911f-60a3b47ee304") th.AssertNoErr(t, res.Err) } diff --git a/openstack/networking/v2/extensions/security/rules/requests.go b/openstack/networking/v2/extensions/security/rules/requests.go index 78b67b0df0..c691059b42 100644 --- a/openstack/networking/v2/extensions/security/rules/requests.go +++ b/openstack/networking/v2/extensions/security/rules/requests.go @@ -7,40 +7,60 @@ import ( "github.com/gophercloud/gophercloud/v2/pagination" ) +// ListOptsBuilder allows extensions to add additional parameters to the List +// request. +type ListOptsBuilder interface { + ToSecGroupListQuery() (string, error) +} + // ListOpts allows the filtering and sorting of paginated collections through // the API. Filtering is achieved by passing in struct field values that map to // the security group rule attributes you want to see returned. SortKey allows // you to sort by a particular network attribute. SortDir sets the direction, // and is either `asc' or `desc'. Marker and Limit are used for pagination. type ListOpts struct { - Direction string `q:"direction"` - EtherType string `q:"ethertype"` - ID string `q:"id"` - Description string `q:"description"` - PortRangeMax int `q:"port_range_max"` - PortRangeMin int `q:"port_range_min"` - Protocol string `q:"protocol"` - RemoteGroupID string `q:"remote_group_id"` - RemoteIPPrefix string `q:"remote_ip_prefix"` - SecGroupID string `q:"security_group_id"` - TenantID string `q:"tenant_id"` - ProjectID string `q:"project_id"` - Limit int `q:"limit"` - Marker string `q:"marker"` - SortKey string `q:"sort_key"` - SortDir string `q:"sort_dir"` + Direction string `q:"direction"` + EtherType string `q:"ethertype"` + ID string `q:"id"` + Description string `q:"description"` + PortRangeMax int `q:"port_range_max"` + PortRangeMin int `q:"port_range_min"` + Protocol string `q:"protocol"` + RemoteAddressGroupID string `q:"remote_address_group_id"` + RemoteGroupID string `q:"remote_group_id"` + RemoteIPPrefix string `q:"remote_ip_prefix"` + SecGroupID string `q:"security_group_id"` + TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` + RevisionNumber *int `q:"revision_number"` +} + +// ToSecGroupListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToSecGroupListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(&opts) + if err != nil { + return "", err + } + return q.String(), nil } // List returns a Pager which allows you to iterate over a collection of // security group rules. It accepts a ListOpts struct, which allows you to filter // and sort the returned collection for greater efficiency. -func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { - q, err := gophercloud.BuildQueryString(&opts) - if err != nil { - return pagination.Pager{Err: err} +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToSecGroupListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query } - u := rootURL(c) + q.String() - return pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page { + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { return SecGroupRulePage{pagination.LinkedPageBase{PageResult: r}} }) } @@ -77,7 +97,7 @@ const ( ProtocolUDP RuleProtocol = "udp" ProtocolUDPLite RuleProtocol = "udplite" ProtocolVRRP RuleProtocol = "vrrp" - ProtocolAny RuleProtocol = "any" + ProtocolAny RuleProtocol = "" ) // CreateOptsBuilder allows extensions to add additional parameters to the @@ -105,7 +125,7 @@ type CreateOpts struct { // The maximum port number in the range that is matched by the security group // rule. The PortRangeMin attribute constrains the PortRangeMax attribute. If - // the protocol is ICMP, this value must be an ICMP type. + // the protocol is ICMP, this value must be an ICMP code. PortRangeMax int `json:"port_range_max,omitempty"` // The minimum port number in the range that is matched by the security group @@ -118,12 +138,16 @@ type CreateOpts struct { // "tcp", "udp", "icmp" or an empty string. Protocol RuleProtocol `json:"protocol,omitempty"` + // The remote address group ID to be associated with this security group rule. + // You can specify either RemoteAddressGroupID, RemoteGroupID, or RemoteIPPrefix + RemoteAddressGroupID string `json:"remote_address_group_id,omitempty"` + // The remote group ID to be associated with this security group rule. You can - // specify either RemoteGroupID or RemoteIPPrefix. + // specify either RemoteAddressGroupID,RemoteGroupID or RemoteIPPrefix. RemoteGroupID string `json:"remote_group_id,omitempty"` // The remote IP prefix to be associated with this security group rule. You can - // specify either RemoteGroupID or RemoteIPPrefix. This attribute matches the + // specify either RemoteAddressGroupID,RemoteGroupID or RemoteIPPrefix. This attribute matches the // specified IP prefix as the source IP address of the IP packet. RemoteIPPrefix string `json:"remote_ip_prefix,omitempty"` @@ -150,6 +174,23 @@ func Create(ctx context.Context, c *gophercloud.ServiceClient, opts CreateOptsBu return } +// CreateBulk is an operation which adds new security group rules and associates them +// with an existing security group (whose ID is specified in CreateOpts). +// As of Dalmatian (2024.2) neutron only allows bulk creation of rules when +// they all belong to the same tenant and security group. +// https://github.com/openstack/neutron/blob/6183792/neutron/db/securitygroups_db.py#L814-L828 +func CreateBulk[createOpts CreateOptsBuilder](ctx context.Context, c *gophercloud.ServiceClient, opts []createOpts) (r CreateBulkResult) { + body, err := gophercloud.BuildRequestBody(opts, "security_group_rules") + if err != nil { + r.Err = err + return + } + + resp, err := c.Post(ctx, rootURL(c), body, &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + // Get retrieves a particular security group rule based on its unique ID. func Get(ctx context.Context, c *gophercloud.ServiceClient, id string) (r GetResult) { resp, err := c.Get(ctx, resourceURL(c, id), &r.Body, nil) diff --git a/openstack/networking/v2/extensions/security/rules/results.go b/openstack/networking/v2/extensions/security/rules/results.go index cfdb27fa17..ddc7c5b7c7 100644 --- a/openstack/networking/v2/extensions/security/rules/results.go +++ b/openstack/networking/v2/extensions/security/rules/results.go @@ -1,6 +1,9 @@ package rules import ( + "encoding/json" + "time" + "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/pagination" ) @@ -42,6 +45,10 @@ type SecGroupRule struct { // "tcp", "udp", "icmp" or an empty string. Protocol string + // The remote address group ID to be associated with this security group rule. + // You can specify either RemoteAddressGroupID, RemoteGroupID, or RemoteIPPrefix + RemoteAddressGroupID string `json:"remote_address_group_id"` + // The remote group ID to be associated with this security group rule. You // can specify either RemoteGroupID or RemoteIPPrefix. RemoteGroupID string `json:"remote_group_id"` @@ -56,6 +63,53 @@ type SecGroupRule struct { // ProjectID is the project owner of this security group rule. ProjectID string `json:"project_id"` + + // RevisionNumber optionally set via extensions/standard-attr-revisions + RevisionNumber int `json:"revision_number"` + + // Timestamp when the rule was created + CreatedAt time.Time `json:"-"` + + // Timestamp when the rule was last updated + UpdatedAt time.Time `json:"-"` +} + +func (r *SecGroupRule) UnmarshalJSON(b []byte) error { + type tmp SecGroupRule + + // Support for older neutron time format + var s1 struct { + tmp + CreatedAt gophercloud.JSONRFC3339NoZ `json:"created_at"` + UpdatedAt gophercloud.JSONRFC3339NoZ `json:"updated_at"` + } + + err := json.Unmarshal(b, &s1) + if err == nil { + *r = SecGroupRule(s1.tmp) + r.CreatedAt = time.Time(s1.CreatedAt) + r.UpdatedAt = time.Time(s1.UpdatedAt) + + return nil + } + + // Support for newer neutron time format + var s2 struct { + tmp + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + } + + err = json.Unmarshal(b, &s2) + if err != nil { + return err + } + + *r = SecGroupRule(s2.tmp) + r.CreatedAt = time.Time(s2.CreatedAt) + r.UpdatedAt = time.Time(s2.UpdatedAt) + + return nil } // SecGroupRulePage is the page returned by a pager when traversing over a @@ -67,7 +121,7 @@ type SecGroupRulePage struct { // NextPageURL is invoked when a paginated collection of security group rules has // reached the end of a page and the pager seeks to traverse over a new one. In // order to do this, it needs to construct the next page's URL. -func (r SecGroupRulePage) NextPageURL() (string, error) { +func (r SecGroupRulePage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"security_group_rules_links"` } @@ -103,6 +157,10 @@ type commonResult struct { gophercloud.Result } +type bulkResult struct { + gophercloud.Result +} + // Extract is a function that accepts a result and extracts a security rule. func (r commonResult) Extract() (*SecGroupRule, error) { var s struct { @@ -112,12 +170,27 @@ func (r commonResult) Extract() (*SecGroupRule, error) { return s.SecGroupRule, err } +// Extract is a function that accepts a result and extracts security rules. +func (r bulkResult) Extract() ([]SecGroupRule, error) { + var s struct { + SecGroupRules []SecGroupRule `json:"security_group_rules"` + } + err := r.ExtractInto(&s) + return s.SecGroupRules, err +} + // CreateResult represents the result of a create operation. Call its Extract // method to interpret it as a SecGroupRule. type CreateResult struct { commonResult } +// CreateBulkResult represents the result of a bulk create operation. Call its +// Extract method to interpret it as a slice of SecGroupRules. +type CreateBulkResult struct { + bulkResult +} + // GetResult represents the result of a get operation. Call its Extract // method to interpret it as a SecGroupRule. type GetResult struct { diff --git a/openstack/networking/v2/extensions/security/rules/testing/requests_test.go b/openstack/networking/v2/extensions/security/rules/testing/requests_test.go index 454399f306..4e1938984b 100644 --- a/openstack/networking/v2/extensions/security/rules/testing/requests_test.go +++ b/openstack/networking/v2/extensions/security/rules/testing/requests_test.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "testing" + "time" fake "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/common" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/security/rules" @@ -13,17 +14,17 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/security-group-rules", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/security-group-rules", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "security_group_rules": [ { @@ -35,6 +36,8 @@ func TestList(t *testing.T) { "protocol": null, "remote_group_id": null, "remote_ip_prefix": null, + "created_at": "2017-12-28T07:21:40Z", + "updated_at": "2017-12-28T07:21:40Z", "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550" }, @@ -47,6 +50,8 @@ func TestList(t *testing.T) { "protocol": null, "remote_group_id": null, "remote_ip_prefix": null, + "created_at": "2017-12-28T07:21:40", + "updated_at": "2017-12-28T07:21:40", "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550" } @@ -57,7 +62,7 @@ func TestList(t *testing.T) { count := 0 - err := rules.List(fake.ServiceClient(), rules.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := rules.List(fake.ServiceClient(fakeServer), rules.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := rules.ExtractRules(page) if err != nil { @@ -76,6 +81,8 @@ func TestList(t *testing.T) { Protocol: "", RemoteGroupID: "", RemoteIPPrefix: "", + CreatedAt: time.Date(2017, 12, 28, 07, 21, 40, 0, time.UTC), + UpdatedAt: time.Date(2017, 12, 28, 07, 21, 40, 0, time.UTC), SecGroupID: "85cc3048-abc3-43cc-89b3-377341426ac5", TenantID: "e4f50856753b4dc6afee5fa6b9b6c550", }, @@ -88,6 +95,8 @@ func TestList(t *testing.T) { Protocol: "", RemoteGroupID: "", RemoteIPPrefix: "", + CreatedAt: time.Date(2017, 12, 28, 07, 21, 40, 0, time.UTC), + UpdatedAt: time.Date(2017, 12, 28, 07, 21, 40, 0, time.UTC), SecGroupID: "85cc3048-abc3-43cc-89b3-377341426ac5", TenantID: "e4f50856753b4dc6afee5fa6b9b6c550", }, @@ -105,10 +114,10 @@ func TestList(t *testing.T) { } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/security-group-rules", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/security-group-rules", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -131,7 +140,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "security_group_rule": { "description": "test description of rule", @@ -160,41 +169,208 @@ func TestCreate(t *testing.T) { RemoteGroupID: "85cc3048-abc3-43cc-89b3-377341426ac5", SecGroupID: "a7734e61-b545-452d-a3cd-0189cbd9747a", } - _, err := rules.Create(context.TODO(), fake.ServiceClient(), opts).Extract() + _, err := rules.Create(context.TODO(), fake.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) } +func TestCreateAnyProtocol(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/security-group-rules", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "security_group_rule": { + "description": "test description of rule", + "direction": "ingress", + "port_range_min": 80, + "ethertype": "IPv4", + "port_range_max": 80, + "remote_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "security_group_id": "a7734e61-b545-452d-a3cd-0189cbd9747a" + } +} + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprint(w, ` +{ + "security_group_rule": { + "description": "test description of rule", + "direction": "ingress", + "ethertype": "IPv4", + "id": "2bc0accf-312e-429a-956e-e4407625eb62", + "port_range_max": 80, + "port_range_min": 80, + "remote_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "remote_ip_prefix": null, + "security_group_id": "a7734e61-b545-452d-a3cd-0189cbd9747a", + "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550" + } +} + `) + }) + + opts := rules.CreateOpts{ + Description: "test description of rule", + Direction: "ingress", + PortRangeMin: 80, + EtherType: rules.EtherType4, + PortRangeMax: 80, + Protocol: rules.ProtocolAny, + RemoteGroupID: "85cc3048-abc3-43cc-89b3-377341426ac5", + SecGroupID: "a7734e61-b545-452d-a3cd-0189cbd9747a", + } + _, err := rules.Create(context.TODO(), fake.ServiceClient(fakeServer), opts).Extract() + th.AssertNoErr(t, err) +} + +func TestCreateBulk(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/security-group-rules", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "security_group_rules": [ + { + "description": "test description of rule", + "direction": "ingress", + "port_range_min": 80, + "ethertype": "IPv4", + "port_range_max": 80, + "protocol": "tcp", + "remote_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "security_group_id": "a7734e61-b545-452d-a3cd-0189cbd9747a" + }, + { + "description": "test description of rule", + "direction": "ingress", + "port_range_min": 443, + "ethertype": "IPv4", + "port_range_max": 443, + "protocol": "tcp", + "security_group_id": "a7734e61-b545-452d-a3cd-0189cbd9747a" + } + ] +} + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprint(w, ` +{ + "security_group_rules": [ + { + "description": "test description of rule", + "direction": "ingress", + "ethertype": "IPv4", + "port_range_max": 80, + "port_range_min": 80, + "protocol": "tcp", + "remote_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "remote_ip_prefix": null, + "security_group_id": "a7734e61-b545-452d-a3cd-0189cbd9747a", + "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550" + }, + { + "description": "test description of rule", + "direction": "ingress", + "ethertype": "IPv4", + "port_range_max": 443, + "port_range_min": 443, + "protocol": "tcp", + "remote_group_id": null, + "remote_ip_prefix": null, + "security_group_id": "a7734e61-b545-452d-a3cd-0189cbd9747a", + "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550" + } + ] +} + `) + }) + + opts := []rules.CreateOpts{ + { + Description: "test description of rule", + Direction: "ingress", + PortRangeMin: 80, + EtherType: rules.EtherType4, + PortRangeMax: 80, + Protocol: "tcp", + RemoteGroupID: "85cc3048-abc3-43cc-89b3-377341426ac5", + SecGroupID: "a7734e61-b545-452d-a3cd-0189cbd9747a", + }, + { + Description: "test description of rule", + Direction: "ingress", + PortRangeMin: 443, + EtherType: rules.EtherType4, + PortRangeMax: 443, + Protocol: "tcp", + SecGroupID: "a7734e61-b545-452d-a3cd-0189cbd9747a", + }, + } + { + _, err := rules.CreateBulk(context.TODO(), fake.ServiceClient(fakeServer), opts).Extract() + th.AssertNoErr(t, err) + } + + { + optsBuilder := make([]rules.CreateOptsBuilder, len(opts)) + for i := range opts { + optsBuilder[i] = opts[i] + } + _, err := rules.CreateBulk(context.TODO(), fake.ServiceClient(fakeServer), optsBuilder).Extract() + th.AssertNoErr(t, err) + } +} + func TestRequiredCreateOpts(t *testing.T) { - res := rules.Create(context.TODO(), fake.ServiceClient(), rules.CreateOpts{Direction: rules.DirIngress}) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + res := rules.Create(context.TODO(), fake.ServiceClient(fakeServer), rules.CreateOpts{Direction: rules.DirIngress}) if res.Err == nil { t.Fatalf("Expected error, got none") } - res = rules.Create(context.TODO(), fake.ServiceClient(), rules.CreateOpts{Direction: rules.DirIngress, EtherType: rules.EtherType4}) + res = rules.Create(context.TODO(), fake.ServiceClient(fakeServer), rules.CreateOpts{Direction: rules.DirIngress, EtherType: rules.EtherType4}) if res.Err == nil { t.Fatalf("Expected error, got none") } - res = rules.Create(context.TODO(), fake.ServiceClient(), rules.CreateOpts{Direction: rules.DirIngress, EtherType: rules.EtherType4}) + res = rules.Create(context.TODO(), fake.ServiceClient(fakeServer), rules.CreateOpts{Direction: rules.DirIngress, EtherType: rules.EtherType4}) if res.Err == nil { t.Fatalf("Expected error, got none") } - res = rules.Create(context.TODO(), fake.ServiceClient(), rules.CreateOpts{Direction: rules.DirIngress, EtherType: rules.EtherType4, SecGroupID: "something", Protocol: "foo"}) + res = rules.Create(context.TODO(), fake.ServiceClient(fakeServer), rules.CreateOpts{Direction: rules.DirIngress, EtherType: rules.EtherType4, SecGroupID: "something", Protocol: "foo"}) if res.Err == nil { t.Fatalf("Expected error, got none") } } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/security-group-rules/3c0e45ff-adaf-4124-b083-bf390e5482ff", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/security-group-rules/3c0e45ff-adaf-4124-b083-bf390e5482ff", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "security_group_rule": { "direction": "egress", @@ -205,6 +381,8 @@ func TestGet(t *testing.T) { "protocol": null, "remote_group_id": null, "remote_ip_prefix": null, + "created_at": "2017-12-28T07:21:40Z", + "updated_at": "2017-12-28T07:21:40Z", "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550" } @@ -212,7 +390,7 @@ func TestGet(t *testing.T) { `) }) - sr, err := rules.Get(context.TODO(), fake.ServiceClient(), "3c0e45ff-adaf-4124-b083-bf390e5482ff").Extract() + sr, err := rules.Get(context.TODO(), fake.ServiceClient(fakeServer), "3c0e45ff-adaf-4124-b083-bf390e5482ff").Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "egress", sr.Direction) @@ -223,20 +401,22 @@ func TestGet(t *testing.T) { th.AssertEquals(t, "", sr.Protocol) th.AssertEquals(t, "", sr.RemoteGroupID) th.AssertEquals(t, "", sr.RemoteIPPrefix) + th.AssertEquals(t, time.Date(2017, 12, 28, 07, 21, 40, 0, time.UTC), sr.UpdatedAt) + th.AssertEquals(t, time.Date(2017, 12, 28, 07, 21, 40, 0, time.UTC), sr.CreatedAt) th.AssertEquals(t, "85cc3048-abc3-43cc-89b3-377341426ac5", sr.SecGroupID) th.AssertEquals(t, "e4f50856753b4dc6afee5fa6b9b6c550", sr.TenantID) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/security-group-rules/4ec89087-d057-4e2c-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/security-group-rules/4ec89087-d057-4e2c-911f-60a3b47ee304", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := rules.Delete(context.TODO(), fake.ServiceClient(), "4ec89087-d057-4e2c-911f-60a3b47ee304") + res := rules.Delete(context.TODO(), fake.ServiceClient(fakeServer), "4ec89087-d057-4e2c-911f-60a3b47ee304") th.AssertNoErr(t, res.Err) } diff --git a/openstack/networking/v2/extensions/segments/requests.go b/openstack/networking/v2/extensions/segments/requests.go new file mode 100644 index 0000000000..926c5d854d --- /dev/null +++ b/openstack/networking/v2/extensions/segments/requests.go @@ -0,0 +1,125 @@ +package segments + +import ( + "context" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/pagination" +) + +// ListOpts allows filtering when listing segments. +type ListOpts struct { + Name string `q:"name"` + Description string `q:"description"` + NetworkID string `q:"network_id"` + PhysicalNetwork string `q:"physical_network"` + NetworkType string `q:"network_type"` + SegmentationID int `q:"segmentation_id"` + RevisionNumber int `q:"revision_number"` + SortDir string `q:"sort_dir"` + SortKey string `q:"sort_key"` + Fields string `q:"fields"` +} + +// ListOptsBuilder interface for listing. +type ListOptsBuilder interface { + ToSegmentListQuery() (string, error) +} + +func (opts ListOpts) ToSegmentListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + return q.String(), nil +} + +// List all segments. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToSegmentListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return SegmentPage{LinkedPageBase: pagination.LinkedPageBase{PageResult: r}} + }) +} + +// CreateOptsBuilder allows extensions to add additional parameters. +type CreateOptsBuilder interface { + ToSegmentCreateMap() (map[string]any, error) +} + +// CreateOpts contains the fields needed for creating a segment. +type CreateOpts struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + NetworkID string `json:"network_id" required:"true"` + NetworkType string `json:"network_type" required:"true"` + PhysicalNetwork string `json:"physical_network,omitempty"` + SegmentationID int `json:"segmentation_id,omitempty"` +} + +func (opts CreateOpts) ToSegmentCreateMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "segment") +} + +// Create a new segment. +func Create(ctx context.Context, c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToSegmentCreateMap() + if err != nil { + r.Err = err + return + } + resp, err := c.Post(ctx, rootURL(c), b, &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// Get retrieves a segment by ID. +func Get(ctx context.Context, c *gophercloud.ServiceClient, id string) (r GetResult) { + resp, err := c.Get(ctx, resourceURL(c, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// Delete removes a segment by ID. +func Delete(ctx context.Context, c *gophercloud.ServiceClient, id string) (r DeleteResult) { + resp, err := c.Delete(ctx, resourceURL(c, id), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// UpdateOpts contains fields to update a segment. +type UpdateOpts struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + SegmentationID *int `json:"segmentation_id,omitempty"` +} + +// UpdateOptsBuilder is the interface for update options. +type UpdateOptsBuilder interface { + ToSegmentUpdateMap() (map[string]any, error) +} + +func (opts UpdateOpts) ToSegmentUpdateMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "segment") +} + +// Update a segment. +func Update(ctx context.Context, c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToSegmentUpdateMap() + if err != nil { + r.Err = err + return + } + resp, err := c.Put(ctx, resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/networking/v2/extensions/segments/results.go b/openstack/networking/v2/extensions/segments/results.go new file mode 100644 index 0000000000..8e940c60a3 --- /dev/null +++ b/openstack/networking/v2/extensions/segments/results.go @@ -0,0 +1,78 @@ +package segments + +import ( + "time" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/pagination" +) + +// Segment model +type Segment struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + NetworkID string `json:"network_id"` + NetworkType string `json:"network_type"` + PhysicalNetwork string `json:"physical_network"` + SegmentationID int `json:"segmentation_id"` + RevisionNumber int `json:"revision_number"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// SegmentPage wraps a page of segments. +type SegmentPage struct { + pagination.LinkedPageBase +} + +func (r SegmentPage) IsEmpty() (bool, error) { + if r.StatusCode == 204 { + return true, nil + } + + is, err := ExtractSegments(r) + return len(is) == 0, err +} + +func ExtractSegments(r pagination.Page) ([]Segment, error) { + var s []Segment + err := ExtractSegmentsInto(r, &s) + return s, err +} + +// ExtractSegmentsInto extracts the elements into a slice of Segment structs. +func ExtractSegmentsInto(r pagination.Page, v any) error { + return r.(SegmentPage).ExtractIntoSlicePtr(v, "segments") +} + +// Segment results +type commonResult struct { + gophercloud.Result +} + +func (r commonResult) Extract() (*Segment, error) { + var s Segment + err := r.ExtractInto(&s) + return &s, err +} + +func (r commonResult) ExtractInto(v any) error { + return r.ExtractIntoStructPtr(v, "segment") +} + +type GetResult struct { + commonResult +} + +type CreateResult struct { + commonResult +} + +type UpdateResult struct { + commonResult +} + +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/openstack/networking/v2/extensions/segments/testing/fixtures.go b/openstack/networking/v2/extensions/segments/testing/fixtures.go new file mode 100644 index 0000000000..1394fa9aea --- /dev/null +++ b/openstack/networking/v2/extensions/segments/testing/fixtures.go @@ -0,0 +1,116 @@ +package testing + +import ( + "time" + + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/segments" +) + +var ( + SegmentID1 = "a5e3a494-26ee-4fde-ad26-2d846c47072e" + SegmentID2 = "e75ff709-fd25-47f8-ad89-aa6404d74fa8" + + Segment1 = segments.Segment{ + ID: "a5e3a494-26ee-4fde-ad26-2d846c47072e", + NetworkID: "f35f300f-8c26-4d51-a0b0-84e2fff14307", + Name: "seg1", + Description: "desc", + PhysicalNetwork: "public", + NetworkType: "flat", + SegmentationID: 0, + RevisionNumber: 1, + CreatedAt: time.Date(2025, 6, 13, 10, 36, 52, 0, time.UTC), + UpdatedAt: time.Date(2025, 6, 13, 10, 36, 52, 0, time.UTC), + } + + Segment2 = segments.Segment{ + ID: "e75ff709-fd25-47f8-ad89-aa6404d74fa8", + NetworkID: "7a7f34de-4dcc-4211-85da-a3afefc8f990", + Name: "", + Description: "", + PhysicalNetwork: "", + NetworkType: "geneve", + SegmentationID: 35745, + RevisionNumber: 0, + CreatedAt: time.Date(2025, 6, 13, 10, 36, 45, 0, time.UTC), + UpdatedAt: time.Date(2025, 6, 13, 10, 36, 45, 0, time.UTC), + } + + SegmentsListBody = ` +{ + "segments": [ + { + "id": "a5e3a494-26ee-4fde-ad26-2d846c47072e", + "network_id": "f35f300f-8c26-4d51-a0b0-84e2fff14307", + "name": "seg1", + "description": "desc", + "physical_network": "public", + "network_type": "flat", + "segmentation_id": null, + "created_at": "2025-06-13T10:36:52Z", + "updated_at": "2025-06-13T10:36:52Z", + "revision_number": 1 + }, + { + "id": "e75ff709-fd25-47f8-ad89-aa6404d74fa8", + "network_id": "7a7f34de-4dcc-4211-85da-a3afefc8f990", + "name": null, + "description": null, + "physical_network": null, + "network_type": "geneve", + "segmentation_id": 35745, + "created_at": "2025-06-13T10:36:45Z", + "updated_at": "2025-06-13T10:36:45Z", + "revision_number": 0 + } + ] +}` + + createRequest = `{ + "segment": { + "network_id":"f35f300f-8c26-4d51-a0b0-84e2fff14307", + "network_type":"flat", + "physical_network":"public", + "name":"seg1", + "description":"desc" + } +}` + + createResponse = `{ + "segment": { + "id":"a5e3a494-26ee-4fde-ad26-2d846c47072e", + "network_id":"f35f300f-8c26-4d51-a0b0-84e2fff14307", + "name":"seg1", + "description":"desc", + "physical_network":"public", + "network_type":"flat", + "segmentation_id":null, + "created_at": "2025-06-13T10:36:52Z", + "updated_at": "2025-06-13T10:36:52Z", + "revision_number":1 + } +} +` + + updateRequest = `{ + "segment": { + "name":"new-name", + "description":"new-desc" + } +}` + + updateResponse = `{ + "segment": { + "id":"a5e3a494-26ee-4fde-ad26-2d846c47072e", + "network_id":"f35f300f-8c26-4d51-a0b0-84e2fff14307", + "name":"new-name", + "description":"new-desc", + "physical_network":"public", + "network_type":"flat", + "segmentation_id":null, + "created_at": "2025-06-13T10:36:52Z", + "updated_at": "2025-06-13T10:36:52Z", + "revision_number":1 + } +}` +) diff --git a/openstack/networking/v2/extensions/segments/testing/requests_test.go b/openstack/networking/v2/extensions/segments/testing/requests_test.go new file mode 100644 index 0000000000..08388a72cc --- /dev/null +++ b/openstack/networking/v2/extensions/segments/testing/requests_test.go @@ -0,0 +1,127 @@ +package testing + +import ( + "context" + "net/http" + "testing" + + fake "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/common" + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/segments" + "github.com/gophercloud/gophercloud/v2/pagination" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func TestGetSegment(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/segments/"+SegmentID1, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(createResponse)) + th.AssertNoErr(t, err) + }) + + res, err := segments.Get(context.TODO(), fake.ServiceClient(fakeServer), SegmentID1).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, Segment1, *res) +} + +func TestListSegments(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/segments", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(SegmentsListBody)) + th.AssertNoErr(t, err) + }) + + count := 0 + pager := segments.List(fake.ServiceClient(fakeServer), nil) + err := pager.EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + count++ + actual, err := segments.ExtractSegments(page) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, []segments.Segment{Segment1, Segment2}, actual) + return true, nil + }) + th.AssertNoErr(t, err) + if count != 1 { + t.Errorf("Expected 1 page, got %d", count) + } +} + +func TestCreateSegment(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/segments", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, createRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + _, err := w.Write([]byte(createResponse)) + th.AssertNoErr(t, err) + }) + + opts := segments.CreateOpts{ + NetworkID: Segment1.NetworkID, + NetworkType: "flat", + PhysicalNetwork: "public", + Name: "seg1", + Description: "desc", + } + actual, err := segments.Create(context.TODO(), fake.ServiceClient(fakeServer), opts).Extract() + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, Segment1, *actual) +} + +func TestUpdateSegment(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/segments/"+SegmentID1, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestJSONRequest(t, r, updateRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(updateResponse)) + th.AssertNoErr(t, err) + }) + + newName := "new-name" + newDesc := "new-desc" + opts := segments.UpdateOpts{ + Name: &newName, + Description: &newDesc, + } + actual, err := segments.Update(context.TODO(), fake.ServiceClient(fakeServer), SegmentID1, opts).Extract() + th.AssertNoErr(t, err) + + expected := Segment1 + expected.Name = newName + expected.Description = newDesc + + th.CheckDeepEquals(t, expected, *actual) +} + +func TestDeleteSegment(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/segments/"+SegmentID1, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + err := segments.Delete(context.TODO(), fake.ServiceClient(fakeServer), SegmentID1).ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/openstack/networking/v2/extensions/segments/urls.go b/openstack/networking/v2/extensions/segments/urls.go new file mode 100644 index 0000000000..6694927c58 --- /dev/null +++ b/openstack/networking/v2/extensions/segments/urls.go @@ -0,0 +1,13 @@ +package segments + +import "github.com/gophercloud/gophercloud/v2" + +const urlBaase = "segments" + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(urlBaase) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(urlBaase, id) +} diff --git a/openstack/networking/v2/extensions/subnetpools/requests.go b/openstack/networking/v2/extensions/subnetpools/requests.go index 8823e94b2f..b18427a6e7 100644 --- a/openstack/networking/v2/extensions/subnetpools/requests.go +++ b/openstack/networking/v2/extensions/subnetpools/requests.go @@ -2,6 +2,7 @@ package subnetpools import ( "context" + "fmt" "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/pagination" @@ -33,7 +34,6 @@ type ListOpts struct { Shared *bool `q:"shared"` Description string `q:"description"` IsDefault *bool `q:"is_default"` - RevisionNumber int `q:"revision_number"` Limit int `q:"limit"` Marker string `q:"marker"` SortKey string `q:"sort_key"` @@ -42,6 +42,8 @@ type ListOpts struct { TagsAny string `q:"tags-any"` NotTags string `q:"not-tags"` NotTagsAny string `q:"not-tags-any"` + // type int does not allow to filter with revision_number=0 + RevisionNumber int `q:"revision_number"` } // ToSubnetPoolListQuery formats a ListOpts into a query string. @@ -201,6 +203,11 @@ type UpdateOpts struct { // IsDefault indicates if the subnetpool is default pool or not. IsDefault *bool `json:"is_default,omitempty"` + + // RevisionNumber implements extension:standard-attr-revisions. If != "" it + // will set revision_number=%s. If the revision number does not match, the + // update will fail. + RevisionNumber *int `json:"-" h:"If-Match"` } // ToSubnetPoolUpdateMap builds a request body from UpdateOpts. @@ -216,8 +223,19 @@ func Update(ctx context.Context, c *gophercloud.ServiceClient, subnetPoolID stri r.Err = err return } + h, err := gophercloud.BuildHeaders(opts) + if err != nil { + r.Err = err + return + } + for k := range h { + if k == "If-Match" { + h[k] = fmt.Sprintf("revision_number=%s", h[k]) + } + } resp, err := c.Put(ctx, updateURL(c, subnetPoolID), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, + MoreHeaders: h, + OkCodes: []int{200}, }) _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return diff --git a/openstack/networking/v2/extensions/subnetpools/results.go b/openstack/networking/v2/extensions/subnetpools/results.go index e68fbfa9f7..69335d5350 100644 --- a/openstack/networking/v2/extensions/subnetpools/results.go +++ b/openstack/networking/v2/extensions/subnetpools/results.go @@ -238,7 +238,7 @@ type SubnetPoolPage struct { // NextPageURL is invoked when a paginated collection of subnetpools has reached // the end of a page and the pager seeks to traverse over a new one. // In order to do this, it needs to construct the next page's URL. -func (r SubnetPoolPage) NextPageURL() (string, error) { +func (r SubnetPoolPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"subnetpools_links"` } diff --git a/openstack/networking/v2/extensions/subnetpools/testing/requests_test.go b/openstack/networking/v2/extensions/subnetpools/testing/requests_test.go index 50f479b9d1..f13ef69ca2 100644 --- a/openstack/networking/v2/extensions/subnetpools/testing/requests_test.go +++ b/openstack/networking/v2/extensions/subnetpools/testing/requests_test.go @@ -14,22 +14,22 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/subnetpools", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/subnetpools", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, SubnetPoolsListResult) + fmt.Fprint(w, SubnetPoolsListResult) }) count := 0 - err := subnetpools.List(fake.ServiceClient(), subnetpools.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := subnetpools.List(fake.ServiceClient(fakeServer), subnetpools.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := subnetpools.ExtractSubnetPools(page) if err != nil { @@ -56,47 +56,47 @@ func TestList(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/subnetpools/0a738452-8057-4ad3-89c2-92f6a74afa76", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/subnetpools/0a738452-8057-4ad3-89c2-92f6a74afa76", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, SubnetPoolGetResult) + fmt.Fprint(w, SubnetPoolGetResult) }) - s, err := subnetpools.Get(context.TODO(), fake.ServiceClient(), "0a738452-8057-4ad3-89c2-92f6a74afa76").Extract() + s, err := subnetpools.Get(context.TODO(), fake.ServiceClient(fakeServer), "0a738452-8057-4ad3-89c2-92f6a74afa76").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.ID, "0a738452-8057-4ad3-89c2-92f6a74afa76") - th.AssertEquals(t, s.Name, "my-ipv6-pool") - th.AssertEquals(t, s.DefaultQuota, 2) - th.AssertEquals(t, s.TenantID, "1e2b9857295a4a3e841809ef492812c5") - th.AssertEquals(t, s.ProjectID, "1e2b9857295a4a3e841809ef492812c5") + th.AssertEquals(t, "0a738452-8057-4ad3-89c2-92f6a74afa76", s.ID) + th.AssertEquals(t, "my-ipv6-pool", s.Name) + th.AssertEquals(t, 2, s.DefaultQuota) + th.AssertEquals(t, "1e2b9857295a4a3e841809ef492812c5", s.TenantID) + th.AssertEquals(t, "1e2b9857295a4a3e841809ef492812c5", s.ProjectID) th.AssertEquals(t, s.CreatedAt, time.Date(2018, 1, 1, 0, 0, 1, 0, time.UTC)) th.AssertEquals(t, s.UpdatedAt, time.Date(2018, 1, 1, 0, 10, 10, 0, time.UTC)) - th.AssertDeepEquals(t, s.Prefixes, []string{ + th.AssertDeepEquals(t, []string{ "2001:db8::a3/64", - }) - th.AssertEquals(t, s.DefaultPrefixLen, 64) - th.AssertEquals(t, s.MinPrefixLen, 64) - th.AssertEquals(t, s.MaxPrefixLen, 128) - th.AssertEquals(t, s.AddressScopeID, "") - th.AssertEquals(t, s.IPversion, 6) - th.AssertEquals(t, s.Shared, false) - th.AssertEquals(t, s.Description, "ipv6 prefixes") - th.AssertEquals(t, s.IsDefault, true) - th.AssertEquals(t, s.RevisionNumber, 2) + }, s.Prefixes) + th.AssertEquals(t, 64, s.DefaultPrefixLen) + th.AssertEquals(t, 64, s.MinPrefixLen) + th.AssertEquals(t, 128, s.MaxPrefixLen) + th.AssertEquals(t, "", s.AddressScopeID) + th.AssertEquals(t, 6, s.IPversion) + th.AssertFalse(t, s.Shared) + th.AssertEquals(t, "ipv6 prefixes", s.Description) + th.AssertTrue(t, s.IsDefault) + th.AssertEquals(t, 2, s.RevisionNumber) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/subnetpools", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/subnetpools", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -106,7 +106,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, SubnetPoolCreateResult) + fmt.Fprint(w, SubnetPoolCreateResult) }) opts := subnetpools.CreateOpts{ @@ -120,25 +120,25 @@ func TestCreate(t *testing.T) { AddressScopeID: "3d4e2e2a-552b-42ad-a16d-820bbf3edaf3", Description: "ipv4 prefixes", } - s, err := subnetpools.Create(context.TODO(), fake.ServiceClient(), opts).Extract() + s, err := subnetpools.Create(context.TODO(), fake.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "my_ipv4_pool") - th.AssertDeepEquals(t, s.Prefixes, []string{ + th.AssertEquals(t, "my_ipv4_pool", s.Name) + th.AssertDeepEquals(t, []string{ "10.10.0.0/16", "10.11.11.0/24", - }) - th.AssertEquals(t, s.MinPrefixLen, 25) - th.AssertEquals(t, s.MaxPrefixLen, 30) - th.AssertEquals(t, s.AddressScopeID, "3d4e2e2a-552b-42ad-a16d-820bbf3edaf3") - th.AssertEquals(t, s.Description, "ipv4 prefixes") + }, s.Prefixes) + th.AssertEquals(t, 25, s.MinPrefixLen) + th.AssertEquals(t, 30, s.MaxPrefixLen) + th.AssertEquals(t, "3d4e2e2a-552b-42ad-a16d-820bbf3edaf3", s.AddressScopeID) + th.AssertEquals(t, "ipv4 prefixes", s.Description) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/subnetpools/099546ca-788d-41e5-a76d-17d8cd282d3e", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/subnetpools/099546ca-788d-41e5-a76d-17d8cd282d3e", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -148,7 +148,7 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, SubnetPoolUpdateResponse) + fmt.Fprint(w, SubnetPoolUpdateResponse) }) nullString := "" @@ -164,32 +164,32 @@ func TestUpdate(t *testing.T) { DefaultQuota: &nullInt, Description: &nullString, } - n, err := subnetpools.Update(context.TODO(), fake.ServiceClient(), "099546ca-788d-41e5-a76d-17d8cd282d3e", updateOpts).Extract() + n, err := subnetpools.Update(context.TODO(), fake.ServiceClient(fakeServer), "099546ca-788d-41e5-a76d-17d8cd282d3e", updateOpts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.Name, "new_subnetpool_name") - th.AssertDeepEquals(t, n.Prefixes, []string{ + th.AssertEquals(t, "new_subnetpool_name", n.Name) + th.AssertDeepEquals(t, []string{ "10.8.0.0/16", "10.11.12.0/24", "10.24.0.0/16", - }) - th.AssertEquals(t, n.MaxPrefixLen, 16) - th.AssertEquals(t, n.ID, "099546ca-788d-41e5-a76d-17d8cd282d3e") - th.AssertEquals(t, n.AddressScopeID, "") - th.AssertEquals(t, n.DefaultQuota, 0) - th.AssertEquals(t, n.Description, "") + }, n.Prefixes) + th.AssertEquals(t, 16, n.MaxPrefixLen) + th.AssertEquals(t, "099546ca-788d-41e5-a76d-17d8cd282d3e", n.ID) + th.AssertEquals(t, "", n.AddressScopeID) + th.AssertEquals(t, 0, n.DefaultQuota) + th.AssertEquals(t, "", n.Description) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/subnetpools/099546ca-788d-41e5-a76d-17d8cd282d3e", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/subnetpools/099546ca-788d-41e5-a76d-17d8cd282d3e", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := subnetpools.Delete(context.TODO(), fake.ServiceClient(), "099546ca-788d-41e5-a76d-17d8cd282d3e") + res := subnetpools.Delete(context.TODO(), fake.ServiceClient(fakeServer), "099546ca-788d-41e5-a76d-17d8cd282d3e") th.AssertNoErr(t, res.Err) } diff --git a/openstack/networking/v2/extensions/taas/tapmirrors/doc.go b/openstack/networking/v2/extensions/taas/tapmirrors/doc.go new file mode 100644 index 0000000000..46e1d9966b --- /dev/null +++ b/openstack/networking/v2/extensions/taas/tapmirrors/doc.go @@ -0,0 +1,63 @@ +/* +Package tapmirrors manages and retrieves Tap Mirrors in the OpenStack Networking Service. + +Example to Create a Tap Mirror + + createopts := tapmirrors.CreateOpts{ + Name: "tapmirror1", + Description: "Description of tapmirror1", + PortID: "a25290e9-1a54-4c26-a5b3-34458d122acc", + MirrorType: tapmirrors.MirrorTypeErspanv1, + RemoteIP: "192.168.54.217", + Directions: tapmirrors.Directions{ + In: "1", + Out: "2", + }, + } + + mirror, err := tapmirrors.Create(context.TODO(), networkClient, createopts).Extract() + if err != nil { + panic(err) + } + +Example to Show the details of a specific Tap Mirror by ID + + tapMirror, err := tapmirrors.Get(context.TODO(), networkClient, "f2b08c1e-aa81-4668-8ae1-1401bcb0576c").Extract() + if err != nil { + panic(err) + } + +Example to Delete a Tap Mirror + + err = tapmirrors.Delete(context.TODO(), networkClient, "5291b189-fd84-46e5-84bd-78f40c05d69c").ExtractErr() + if err != nil { + panic(err) + } + +Example to Update an Tap Mirror + + name := "updated name" + description := "updated description" + updateOps := tapmirrors.UpdateOpts{ + Description: &description, + Name: &name, + } + + updatedTapMirror, err := tapmirrors.Update(context.TODO(), networkClient, "5c561d9d-eaea-45f6-ae3e-08d1a7080828", updateOps).Extract() + if err != nil { + panic(err) + } + +Example to List Tap Mirrors + + allPages, err := tapmirrors.List(networkClient, nil).AllPages(context.TODO()) + if err != nil { + panic(err) + } + + allTapMirrors, err := tapmirrors.ExtractTapMirrors(allPages) + if err != nil { + panic(err) + } +*/ +package tapmirrors diff --git a/openstack/networking/v2/extensions/taas/tapmirrors/requests.go b/openstack/networking/v2/extensions/taas/tapmirrors/requests.go new file mode 100644 index 0000000000..7b236374bd --- /dev/null +++ b/openstack/networking/v2/extensions/taas/tapmirrors/requests.go @@ -0,0 +1,156 @@ +package tapmirrors + +import ( + "context" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/pagination" +) + +type MirrorType string + +const ( + MirrorTypeErspanv1 MirrorType = "erspanv1" + MirrorTypeGre MirrorType = "gre" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToTapMirrorCreateMap() (map[string]any, error) +} + +// CreateOpts contains all the values needed to create a new tap mirror +type CreateOpts struct { + // The name of the Tap Mirror. + Name string `json:"name"` + + // A human-readable description of the Tap Mirror. + Description string `json:"description,omitempty"` + + // The ID of the project. The caller must have an admin role in + // order to set this. Otherwise, this field is left unset + // and the caller will be the owner. + TenantID string `json:"tenant_id,omitempty"` + + // The Port ID of the Tap Mirror, this will be the source of the mirrored traffic, + // and this traffic will be tunneled into the GRE or ERSPAN v1 tunnel. + // The tunnel itself is not starting from this port. + PortID string `json:"port_id"` + + // The type of the mirroring, it can be gre or erspanv1. + MirrorType MirrorType `json:"mirror_type"` + + // The remote IP of the Tap Mirror, this will be the remote end of the GRE or ERSPAN v1 tunnel. + RemoteIP string `json:"remote_ip"` + + // A dictionary of direction and tunnel_id. Directions are In and Out. In specifies + // ingress traffic to the port will be mirrored, Out specifies egress traffic will be mirrored. + // The values of the directions are the identifiers of the ERSPAN or GRE session between + // the source and destination, these must be unique within the project. + Directions Directions `json:"directions"` +} + +// ToTapMirrorCreateMap casts a CreateOpts struct to a map. +func (opts CreateOpts) ToTapMirrorCreateMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "tap_mirror") +} + +// Create accepts a CreateOpts struct and uses the values to create a new Tap Mirror. +func Create(ctx context.Context, c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToTapMirrorCreateMap() + if err != nil { + r.Err = err + return + } + resp, err := c.Post(ctx, rootURL(c), b, &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// Get retrieves a particular Tap Mirror on its ID. +func Get(ctx context.Context, c *gophercloud.ServiceClient, id string) (r GetResult) { + resp, err := c.Get(ctx, resourceURL(c, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToTapMirrorListQuery() (string, error) +} + +// ListOpts allows the filtering of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the Endpoint group attributes you want to see returned. +type ListOpts struct { + ProjectID string `q:"project_id"` + Name string `q:"name"` + Description string `q:"description"` + TenantID string `q:"tenant_id"` + PortID string `q:"port_id"` + MirrorType MirrorType `q:"mirror_type"` + RemoteIP string `q:"remote_ip"` +} + +// ToTapMirrorListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToTapMirrorListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// Tap Mirrors. It accepts a ListOpts struct, which allows you to filter +// the returned collection for greater efficiency. +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToTapMirrorListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return TapMirrorPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Delete will permanently delete a Tap Mirror based on its ID. +func Delete(ctx context.Context, c *gophercloud.ServiceClient, id string) (r DeleteResult) { + resp, err := c.Delete(ctx, resourceURL(c, id), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToTapMirrorUpdateMap() (map[string]any, error) +} + +// UpdateOpts contains the values used when updating a Tap Mirror. +type UpdateOpts struct { + Description *string `json:"description,omitempty"` + Name *string `json:"name,omitempty"` +} + +// ToTapMirrorUpdateMap casts an UpdateOpts struct to a map. +func (opts UpdateOpts) ToTapMirrorUpdateMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "tap_mirror") +} + +// Update allows Tap Mirrors to be updated. +func Update(ctx context.Context, c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToTapMirrorUpdateMap() + if err != nil { + r.Err = err + return + } + resp, err := c.Put(ctx, resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/networking/v2/extensions/taas/tapmirrors/results.go b/openstack/networking/v2/extensions/taas/tapmirrors/results.go new file mode 100644 index 0000000000..f6ca8b1aa0 --- /dev/null +++ b/openstack/networking/v2/extensions/taas/tapmirrors/results.go @@ -0,0 +1,129 @@ +package tapmirrors + +import ( + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/pagination" +) + +// TapMirror represents a Tap Mirror of the networking service taas extension +type TapMirror struct { + // The ID of the Tap Mirror. + ID string `json:"id"` + + // The name of the Tap Mirror. + Name string `json:"name"` + + // A human-readable description of the Tap Mirror. + Description string `json:"description"` + + // The ID of the tenant. + TenantID string `json:"tenant_id"` + + // The ID of the project. + ProjectID string `json:"project_id"` + + // The Port ID of the Tap Mirror, this will be the source of the mirrored traffic, + // and this traffic will be tunneled into the GRE or ERSPAN v1 tunnel. + // The tunnel itself is not starting from this port. + PortID string `json:"port_id"` + + // The type of the mirroring, it can be gre or erspanv1. + MirrorType string `json:"mirror_type"` + + // The remote IP of the Tap Mirror, this will be the remote end of the GRE or ERSPAN v1 tunnel. + RemoteIP string `json:"remote_ip"` + + // A dictionary of direction and tunnel_id. Directions are In and Out. In specifies + // ingress traffic to the port will be mirrored, Out specifies egress traffic will be mirrored. + // The values of the directions are the identifiers of the ERSPAN or GRE session between + // the source and destination, these must be unique within the project. + Directions Directions `json:"directions"` +} + +type Directions struct { + // Unique identifier of the tunnel with ingress traffic. Omit to not capture ingress traffic. + // Encoded as JSON string to be compatible with python tap-as-a-service client. + In int `json:"IN,omitempty,string"` + + // Unique identifier of the tunnel with egress traffic. Omit to not capture egress traffic. + // Encoded as JSON string to be compatible with python tap-as-a-service client. + Out int `json:"OUT,omitempty,string"` +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a Tap Mirror. +func (r commonResult) Extract() (*TapMirror, error) { + var s struct { + TapMirror *TapMirror `json:"tap_mirror"` + } + err := r.ExtractInto(&s) + return s.TapMirror, err +} + +// TapMirrorPage is the page returned by a pager when traversing over a +// collection of Policies. +type TapMirrorPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of Endpoint groups has +// reached the end of a page and the pager seeks to traverse over a new one. +// In order to do this, it needs to construct the next page's URL. +func (r TapMirrorPage) NextPageURL(endpointURL string) (string, error) { + var s struct { + Links []gophercloud.Link `json:"tap_mirrors_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether an TapMirrorPage struct is empty. +func (r TapMirrorPage) IsEmpty() (bool, error) { + if r.StatusCode == 204 { + return true, nil + } + + is, err := ExtractTapMirrors(r) + return len(is) == 0, err +} + +// ExtractTapMirrors accepts a Page struct, specifically an TapMirrorPage struct, +// and extracts the elements into a slice of Tap Mirror structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractTapMirrors(r pagination.Page) ([]TapMirror, error) { + var s struct { + TapMirrors []TapMirror `json:"tap_mirrors"` + } + err := (r.(TapMirrorPage)).ExtractInto(&s) + return s.TapMirrors, err +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Tap Mirror. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a TapMirror. +type GetResult struct { + commonResult +} + +// DeleteResult represents the results of a Delete operation. Call its ExtractErr method +// to determine whether the operation succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// UpdateResult represents the result of an update operation. Call its Extract method +// to interpret it as a TapMirror. +type UpdateResult struct { + commonResult +} diff --git a/openstack/networking/v2/extensions/taas/tapmirrors/testing/requests_test.go b/openstack/networking/v2/extensions/taas/tapmirrors/testing/requests_test.go new file mode 100644 index 0000000000..7d98c403b5 --- /dev/null +++ b/openstack/networking/v2/extensions/taas/tapmirrors/testing/requests_test.go @@ -0,0 +1,291 @@ +package testing + +import ( + "context" + "fmt" + "net/http" + "testing" + + fake "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/common" + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/taas/tapmirrors" + "github.com/gophercloud/gophercloud/v2/pagination" + + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func TestCreate(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/taas/tap_mirrors", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "tap_mirror": { + "description": "description", + "directions": { + "IN": "1", + "OUT": "2" + }, + "mirror_type": "erspanv1", + "name": "test", + "port_id": "a25290e9-1a54-4c26-a5b3-34458d122acc", + "remote_ip": "192.168.54.217" + } +} + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprint(w, ` +{ + "tap_mirror": { + "id": "bd64a6e3-12b8-4092-a348-6fc7e27c298a", + "project_id": "6776f022d64443a898ee3fab89dc8c05", + "name": "test", + "description": "description", + "port_id": "a25290e9-1a54-4c26-a5b3-34458d122acc", + "directions": { + "IN": "1", + "OUT": "2" + }, + "remote_ip": "192.168.54.217", + "mirror_type": "erspanv1", + "tenant_id": "6776f022d64443a898ee3fab89dc8c05" + } +} + `) + }) + + options := tapmirrors.CreateOpts{ + Name: "test", + Description: "description", + PortID: "a25290e9-1a54-4c26-a5b3-34458d122acc", + MirrorType: tapmirrors.MirrorTypeErspanv1, + RemoteIP: "192.168.54.217", + Directions: tapmirrors.Directions{ + In: 1, + Out: 2, + }, + } + actual, err := tapmirrors.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() + th.AssertNoErr(t, err) + expected := tapmirrors.TapMirror{ + ID: "bd64a6e3-12b8-4092-a348-6fc7e27c298a", + TenantID: "6776f022d64443a898ee3fab89dc8c05", + ProjectID: "6776f022d64443a898ee3fab89dc8c05", + Name: "test", + Description: "description", + PortID: "a25290e9-1a54-4c26-a5b3-34458d122acc", + MirrorType: "erspanv1", + RemoteIP: "192.168.54.217", + Directions: tapmirrors.Directions{ + In: 1, + Out: 2, + }, + } + th.AssertDeepEquals(t, expected, *actual) +} + +func TestGet(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/taas/tap_mirrors/0837b488-f0e2-4689-99b3-e3ed531f9b10", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ` +{ + "tap_mirror": { + "id": "0837b488-f0e2-4689-99b3-e3ed531f9b10", + "project_id": "6776f022d64443a898ee3fab89dc8c05", + "name": "test", + "description": "description", + "port_id": "a25290e9-1a54-4c26-a5b3-34458d122acc", + "directions": { + "IN": "1", + "OUT": "2" + }, + "remote_ip": "192.168.54.217", + "mirror_type": "erspanv1", + "tenant_id": "6776f022d64443a898ee3fab89dc8c05" + } +} + `) + }) + + actual, err := tapmirrors.Get(context.TODO(), fake.ServiceClient(fakeServer), "0837b488-f0e2-4689-99b3-e3ed531f9b10").Extract() + th.AssertNoErr(t, err) + expected := tapmirrors.TapMirror{ + ID: "0837b488-f0e2-4689-99b3-e3ed531f9b10", + TenantID: "6776f022d64443a898ee3fab89dc8c05", + ProjectID: "6776f022d64443a898ee3fab89dc8c05", + Name: "test", + Description: "description", + PortID: "a25290e9-1a54-4c26-a5b3-34458d122acc", + MirrorType: "erspanv1", + RemoteIP: "192.168.54.217", + Directions: tapmirrors.Directions{ + In: 1, + Out: 2, + }, + } + th.AssertDeepEquals(t, expected, *actual) +} + +func TestDelete(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/taas/tap_mirrors/0837b488-f0e2-4689-99b3-e3ed531f9b10", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) + + res := tapmirrors.Delete(context.TODO(), fake.ServiceClient(fakeServer), "0837b488-f0e2-4689-99b3-e3ed531f9b10") + th.AssertNoErr(t, res.Err) +} + +func TestList(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/taas/tap_mirrors", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ` +{ + "tap_mirrors": [ + { + "id": "0837b488-f0e2-4689-99b3-e3ed531f9b10", + "project_id": "6776f022d64443a898ee3fab89dc8c05", + "name": "test", + "description": "description", + "port_id": "a25290e9-1a54-4c26-a5b3-34458d122acc", + "directions": { + "IN": "1", + "OUT": "2" + }, + "remote_ip": "192.168.54.217", + "mirror_type": "erspanv1", + "tenant_id": "6776f022d64443a898ee3fab89dc8c05" + } + ] +} + `) + }) + + count := 0 + + err := tapmirrors.List(fake.ServiceClient(fakeServer), tapmirrors.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + count++ + actual, err := tapmirrors.ExtractTapMirrors(page) + if err != nil { + t.Errorf("Failed to extract members: %v", err) + return false, err + } + expected := []tapmirrors.TapMirror{ + { + ID: "0837b488-f0e2-4689-99b3-e3ed531f9b10", + TenantID: "6776f022d64443a898ee3fab89dc8c05", + ProjectID: "6776f022d64443a898ee3fab89dc8c05", + Name: "test", + Description: "description", + PortID: "a25290e9-1a54-4c26-a5b3-34458d122acc", + MirrorType: "erspanv1", + RemoteIP: "192.168.54.217", + Directions: tapmirrors.Directions{ + In: 1, + Out: 2, + }, + }, + } + th.CheckDeepEquals(t, expected, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + + if count != 1 { + t.Errorf("Expected 1 page, got %d", count) + } +} + +func TestUpdate(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + fakeServer.Mux.HandleFunc("/v2.0/taas/tap_mirrors/d031da31-fb9b-4bd9-8d37-aaf04a12d45f", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "tap_mirror": { + "name": "new name", + "description": "new description" + } +} + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ` +{ + "tap_mirror": { + "id": "d031da31-fb9b-4bd9-8d37-aaf04a12d45f", + "project_id": "6776f022d64443a898ee3fab89dc8c05", + "name": "new name", + "description": "new description", + "port_id": "a25290e9-1a54-4c26-a5b3-34458d122acc", + "directions": { + "IN": "1", + "OUT": "2" + }, + "remote_ip": "192.168.54.217", + "mirror_type": "erspanv1", + "tenant_id": "6776f022d64443a898ee3fab89dc8c05" + } +} +`) + }) + + updatedName := "new name" + updatedDescription := "new description" + options := tapmirrors.UpdateOpts{ + Name: &updatedName, + Description: &updatedDescription, + } + + actual, err := tapmirrors.Update(context.TODO(), fake.ServiceClient(fakeServer), "d031da31-fb9b-4bd9-8d37-aaf04a12d45f", options).Extract() + th.AssertNoErr(t, err) + expected := tapmirrors.TapMirror{ + ID: "d031da31-fb9b-4bd9-8d37-aaf04a12d45f", + TenantID: "6776f022d64443a898ee3fab89dc8c05", + ProjectID: "6776f022d64443a898ee3fab89dc8c05", + Name: "new name", + Description: "new description", + PortID: "a25290e9-1a54-4c26-a5b3-34458d122acc", + MirrorType: "erspanv1", + RemoteIP: "192.168.54.217", + Directions: tapmirrors.Directions{ + In: 1, + Out: 2, + }, + } + th.AssertDeepEquals(t, expected, *actual) +} diff --git a/openstack/networking/v2/extensions/taas/tapmirrors/urls.go b/openstack/networking/v2/extensions/taas/tapmirrors/urls.go new file mode 100644 index 0000000000..1b38a7cec3 --- /dev/null +++ b/openstack/networking/v2/extensions/taas/tapmirrors/urls.go @@ -0,0 +1,16 @@ +package tapmirrors + +import "github.com/gophercloud/gophercloud/v2" + +const ( + rootPath = "taas" + resourcePath = "tap_mirrors" +) + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +} diff --git a/openstack/networking/v2/extensions/testing/delegate_test.go b/openstack/networking/v2/extensions/testing/delegate_test.go index eb130cd4b0..5f54a099f0 100644 --- a/openstack/networking/v2/extensions/testing/delegate_test.go +++ b/openstack/networking/v2/extensions/testing/delegate_test.go @@ -14,16 +14,16 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/extensions", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/extensions", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "extensions": [ { @@ -41,7 +41,7 @@ func TestList(t *testing.T) { count := 0 - err := extensions.List(fake.ServiceClient()).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := extensions.List(fake.ServiceClient(fakeServer)).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := extensions.ExtractExtensions(page) if err != nil { @@ -73,17 +73,17 @@ func TestList(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/extensions/agent", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/extensions/agent", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "extension": { "updated": "2013-02-03T10:00:00-00:00", @@ -97,12 +97,12 @@ func TestGet(t *testing.T) { `) }) - ext, err := extensions.Get(context.TODO(), fake.ServiceClient(), "agent").Extract() + ext, err := extensions.Get(context.TODO(), fake.ServiceClient(fakeServer), "agent").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, ext.Updated, "2013-02-03T10:00:00-00:00") - th.AssertEquals(t, ext.Name, "agent") - th.AssertEquals(t, ext.Namespace, "http://docs.openstack.org/ext/agent/api/v2.0") - th.AssertEquals(t, ext.Alias, "agent") - th.AssertEquals(t, ext.Description, "The agent management extension.") + th.AssertEquals(t, "2013-02-03T10:00:00-00:00", ext.Updated) + th.AssertEquals(t, "agent", ext.Name) + th.AssertEquals(t, "http://docs.openstack.org/ext/agent/api/v2.0", ext.Namespace) + th.AssertEquals(t, "agent", ext.Alias) + th.AssertEquals(t, "The agent management extension.", ext.Description) } diff --git a/openstack/networking/v2/extensions/trunk_details/testing/requests_test.go b/openstack/networking/v2/extensions/trunk_details/testing/requests_test.go index b99e711b1b..fee71b7641 100644 --- a/openstack/networking/v2/extensions/trunk_details/testing/requests_test.go +++ b/openstack/networking/v2/extensions/trunk_details/testing/requests_test.go @@ -9,18 +9,18 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/trunk_details" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/ports" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestServerWithUsageExt(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() const portIDFixture = "dc3e8758-ee96-402d-94b0-4be5e9396c82" - th.Mux.HandleFunc("/ports/"+portIDFixture, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/ports/"+portIDFixture, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") fmt.Fprint(w, PortWithTrunkDetailsResult) @@ -32,14 +32,14 @@ func TestServerWithUsageExt(t *testing.T) { } // Extract basic fields. - err := ports.Get(context.TODO(), fake.ServiceClient(), portIDFixture).ExtractInto(&portExt) + err := ports.Get(context.TODO(), client.ServiceClient(fakeServer), portIDFixture).ExtractInto(&portExt) th.AssertNoErr(t, err) - th.AssertEquals(t, portExt.TrunkDetails.TrunkID, "f170c831-8c55-4ceb-ad13-75eab4a121e5") - th.AssertEquals(t, len(portExt.TrunkDetails.SubPorts), 1) - subPort := portExt.TrunkDetails.SubPorts[0] - th.AssertEquals(t, subPort.SegmentationID, 100) - th.AssertEquals(t, subPort.SegmentationType, "vlan") - th.AssertEquals(t, subPort.PortID, "20c673d8-7f9d-4570-b662-148d9ddcc5bd") - th.AssertEquals(t, subPort.MACAddress, "fa:16:3e:88:29:a0") + th.AssertEquals(t, "f170c831-8c55-4ceb-ad13-75eab4a121e5", portExt.TrunkID) + th.AssertEquals(t, 1, len(portExt.SubPorts)) + subPort := portExt.SubPorts[0] + th.AssertEquals(t, 100, subPort.SegmentationID) + th.AssertEquals(t, "vlan", subPort.SegmentationType) + th.AssertEquals(t, "20c673d8-7f9d-4570-b662-148d9ddcc5bd", subPort.PortID) + th.AssertEquals(t, "fa:16:3e:88:29:a0", subPort.MACAddress) } diff --git a/openstack/networking/v2/extensions/trunks/constants.go b/openstack/networking/v2/extensions/trunks/constants.go new file mode 100644 index 0000000000..6bec77fa79 --- /dev/null +++ b/openstack/networking/v2/extensions/trunks/constants.go @@ -0,0 +1,9 @@ +package trunks + +const ( + StatusActive = "ACTIVE" + StatusBuild = "BUILD" + StatusDegraded = "DEGRADED" + StatusDown = "DOWN" + StatusError = "ERROR" +) diff --git a/openstack/networking/v2/extensions/trunks/requests.go b/openstack/networking/v2/extensions/trunks/requests.go index a58d81ac17..ea4dba2507 100644 --- a/openstack/networking/v2/extensions/trunks/requests.go +++ b/openstack/networking/v2/extensions/trunks/requests.go @@ -2,6 +2,7 @@ package trunks import ( "context" + "fmt" "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/pagination" @@ -63,21 +64,22 @@ type ListOptsBuilder interface { // by a particular trunk attribute. SortDir sets the direction, and is either // `asc' or `desc'. Marker and Limit are used for pagination. type ListOpts struct { - AdminStateUp *bool `q:"admin_state_up"` - Description string `q:"description"` - ID string `q:"id"` - Name string `q:"name"` - PortID string `q:"port_id"` + AdminStateUp *bool `q:"admin_state_up"` + Description string `q:"description"` + ID string `q:"id"` + Name string `q:"name"` + PortID string `q:"port_id"` + Status string `q:"status"` + TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` + SortDir string `q:"sort_dir"` + SortKey string `q:"sort_key"` + Tags string `q:"tags"` + TagsAny string `q:"tags-any"` + NotTags string `q:"not-tags"` + NotTagsAny string `q:"not-tags-any"` + // TODO change type to *int for consistency RevisionNumber string `q:"revision_number"` - Status string `q:"status"` - TenantID string `q:"tenant_id"` - ProjectID string `q:"project_id"` - SortDir string `q:"sort_dir"` - SortKey string `q:"sort_key"` - Tags string `q:"tags"` - TagsAny string `q:"tags-any"` - NotTags string `q:"not-tags"` - NotTagsAny string `q:"not-tags-any"` } // ToTrunkListQuery formats a ListOpts into a query string. @@ -122,6 +124,11 @@ type UpdateOpts struct { AdminStateUp *bool `json:"admin_state_up,omitempty"` Name *string `json:"name,omitempty"` Description *string `json:"description,omitempty"` + + // RevisionNumber implements extension:standard-attr-revisions. If != "" it + // will set revision_number=%s. If the revision number does not match, the + // update will fail. + RevisionNumber *int `json:"-" h:"If-Match"` } func (opts UpdateOpts) ToTrunkUpdateMap() (map[string]any, error) { @@ -134,8 +141,19 @@ func Update(ctx context.Context, c *gophercloud.ServiceClient, id string, opts U r.Err = err return } + h, err := gophercloud.BuildHeaders(opts) + if err != nil { + r.Err = err + return + } + for k := range h { + if k == "If-Match" { + h[k] = fmt.Sprintf("revision_number=%s", h[k]) + } + } resp, err := c.Put(ctx, updateURL(c, id), body, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200}, + MoreHeaders: h, + OkCodes: []int{200}, }) _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return diff --git a/openstack/networking/v2/extensions/trunks/results.go b/openstack/networking/v2/extensions/trunks/results.go index 20edcf9a88..72efd636fb 100644 --- a/openstack/networking/v2/extensions/trunks/results.go +++ b/openstack/networking/v2/extensions/trunks/results.go @@ -81,6 +81,7 @@ type Trunk struct { // if the resource has not been updated, this field will show as null. UpdatedAt time.Time `json:"updated_at"` + // RevisionNumber optionally set via extensions/standard-attr-revisions RevisionNumber int `json:"revision_number"` // UUID of the trunk's parent port diff --git a/openstack/networking/v2/extensions/trunks/testing/requests_test.go b/openstack/networking/v2/extensions/trunks/testing/requests_test.go index bc4a2341ab..162226dc2a 100644 --- a/openstack/networking/v2/extensions/trunks/testing/requests_test.go +++ b/openstack/networking/v2/extensions/trunks/testing/requests_test.go @@ -13,10 +13,10 @@ import ( ) func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/trunks", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/trunks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -25,7 +25,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) iTrue := true @@ -46,25 +46,25 @@ func TestCreate(t *testing.T) { }, }, } - _, err := trunks.Create(context.TODO(), fake.ServiceClient(), options).Extract() + _, err := trunks.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() if err == nil { t.Fatalf("Failed to detect missing parent PortID field") } options.PortID = "c373d2fa-3d3b-4492-924c-aff54dea19b6" - n, err := trunks.Create(context.TODO(), fake.ServiceClient(), options).Extract() + n, err := trunks.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.Status, "ACTIVE") + th.AssertEquals(t, "ACTIVE", n.Status) expectedTrunks, err := ExpectedTrunkSlice() th.AssertNoErr(t, err) th.AssertDeepEquals(t, &expectedTrunks[1], n) } func TestCreateNoSubports(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/trunks", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/trunks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -73,7 +73,7 @@ func TestCreateNoSubports(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateNoSubportsResponse) + fmt.Fprint(w, CreateNoSubportsResponse) }) iTrue := true @@ -83,42 +83,42 @@ func TestCreateNoSubports(t *testing.T) { AdminStateUp: &iTrue, PortID: "c373d2fa-3d3b-4492-924c-aff54dea19b6", } - n, err := trunks.Create(context.TODO(), fake.ServiceClient(), options).Extract() + n, err := trunks.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.Status, "ACTIVE") + th.AssertEquals(t, "ACTIVE", n.Status) th.AssertEquals(t, 0, len(n.Subports)) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/trunks/f6a9718c-5a64-43e3-944f-4deccad8e78c", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/trunks/f6a9718c-5a64-43e3-944f-4deccad8e78c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := trunks.Delete(context.TODO(), fake.ServiceClient(), "f6a9718c-5a64-43e3-944f-4deccad8e78c") + res := trunks.Delete(context.TODO(), fake.ServiceClient(fakeServer), "f6a9718c-5a64-43e3-944f-4deccad8e78c") th.AssertNoErr(t, res.Err) } func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/trunks", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/trunks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) count := 0 err := trunks.List(client, trunks.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { @@ -143,20 +143,20 @@ func TestList(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/trunks/f6a9718c-5a64-43e3-944f-4deccad8e78c", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/trunks/f6a9718c-5a64-43e3-944f-4deccad8e78c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) - n, err := trunks.Get(context.TODO(), fake.ServiceClient(), "f6a9718c-5a64-43e3-944f-4deccad8e78c").Extract() + n, err := trunks.Get(context.TODO(), fake.ServiceClient(fakeServer), "f6a9718c-5a64-43e3-944f-4deccad8e78c").Extract() th.AssertNoErr(t, err) expectedTrunks, err := ExpectedTrunkSlice() th.AssertNoErr(t, err) @@ -164,10 +164,10 @@ func TestGet(t *testing.T) { } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/trunks/f6a9718c-5a64-43e3-944f-4deccad8e78c", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/trunks/f6a9718c-5a64-43e3-944f-4deccad8e78c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -177,7 +177,7 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateResponse) + fmt.Fprint(w, UpdateResponse) }) iFalse := false @@ -188,7 +188,7 @@ func TestUpdate(t *testing.T) { AdminStateUp: &iFalse, Description: &description, } - n, err := trunks.Update(context.TODO(), fake.ServiceClient(), "f6a9718c-5a64-43e3-944f-4deccad8e78c", options).Extract() + n, err := trunks.Update(context.TODO(), fake.ServiceClient(fakeServer), "f6a9718c-5a64-43e3-944f-4deccad8e78c", options).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, n.Name, name) @@ -197,20 +197,20 @@ func TestUpdate(t *testing.T) { } func TestGetSubports(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/trunks/f6a9718c-5a64-43e3-944f-4deccad8e78c/get_subports", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/trunks/f6a9718c-5a64-43e3-944f-4deccad8e78c/get_subports", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListSubportsResponse) + fmt.Fprint(w, ListSubportsResponse) }) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) subports, err := trunks.GetSubports(context.TODO(), client, "f6a9718c-5a64-43e3-944f-4deccad8e78c").Extract() th.AssertNoErr(t, err) @@ -248,10 +248,10 @@ func TestMissingFields(t *testing.T) { } func TestAddSubports(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/trunks/f6a9718c-5a64-43e3-944f-4deccad8e78c/add_subports", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/trunks/f6a9718c-5a64-43e3-944f-4deccad8e78c/add_subports", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -259,10 +259,10 @@ func TestAddSubports(t *testing.T) { th.TestJSONRequest(t, r, AddSubportsRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, AddSubportsResponse) + fmt.Fprint(w, AddSubportsResponse) }) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) opts := trunks.AddSubportsOpts{ Subports: ExpectedSubports, @@ -276,10 +276,10 @@ func TestAddSubports(t *testing.T) { } func TestRemoveSubports(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/trunks/f6a9718c-5a64-43e3-944f-4deccad8e78c/remove_subports", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/trunks/f6a9718c-5a64-43e3-944f-4deccad8e78c/remove_subports", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -287,10 +287,10 @@ func TestRemoveSubports(t *testing.T) { th.TestJSONRequest(t, r, RemoveSubportsRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, RemoveSubportsResponse) + fmt.Fprint(w, RemoveSubportsResponse) }) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) opts := trunks.RemoveSubportsOpts{ Subports: []trunks.RemoveSubport{ diff --git a/openstack/networking/v2/extensions/vlantransparent/testing/requests_test.go b/openstack/networking/v2/extensions/vlantransparent/testing/requests_test.go index d298894de7..834a8b8d5a 100644 --- a/openstack/networking/v2/extensions/vlantransparent/testing/requests_test.go +++ b/openstack/networking/v2/extensions/vlantransparent/testing/requests_test.go @@ -13,17 +13,17 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, NetworksVLANTransparentListResult) + fmt.Fprint(w, NetworksVLANTransparentListResult) }) type networkVLANTransparentExt struct { @@ -32,7 +32,7 @@ func TestList(t *testing.T) { } var actual []networkVLANTransparentExt - allPages, err := networks.List(fake.ServiceClient(), networks.ListOpts{}).AllPages(context.TODO()) + allPages, err := networks.List(fake.ServiceClient(fakeServer), networks.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) err = networks.ExtractNetworksInto(allPages, &actual) @@ -40,26 +40,26 @@ func TestList(t *testing.T) { th.AssertEquals(t, "db193ab3-96e3-4cb3-8fc5-05f4296d0324", actual[0].ID) th.AssertEquals(t, "private", actual[0].Name) - th.AssertEquals(t, true, actual[0].AdminStateUp) + th.AssertTrue(t, actual[0].AdminStateUp) th.AssertEquals(t, "ACTIVE", actual[0].Status) th.AssertDeepEquals(t, []string{"08eae331-0402-425a-923c-34f7cfe39c1b"}, actual[0].Subnets) th.AssertEquals(t, "26a7980765d0414dbc1fc1f88cdb7e6e", actual[0].TenantID) - th.AssertEquals(t, false, actual[0].Shared) - th.AssertEquals(t, true, actual[0].VLANTransparent) + th.AssertFalse(t, actual[0].Shared) + th.AssertTrue(t, actual[0].VLANTransparent) } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks/db193ab3-96e3-4cb3-8fc5-05f4296d0324", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/db193ab3-96e3-4cb3-8fc5-05f4296d0324", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, NetworksVLANTransparentGetResult) + fmt.Fprint(w, NetworksVLANTransparentGetResult) }) var s struct { @@ -67,24 +67,24 @@ func TestGet(t *testing.T) { vlantransparent.TransparentExt } - err := networks.Get(context.TODO(), fake.ServiceClient(), "db193ab3-96e3-4cb3-8fc5-05f4296d0324").ExtractInto(&s) + err := networks.Get(context.TODO(), fake.ServiceClient(fakeServer), "db193ab3-96e3-4cb3-8fc5-05f4296d0324").ExtractInto(&s) th.AssertNoErr(t, err) th.AssertEquals(t, "db193ab3-96e3-4cb3-8fc5-05f4296d0324", s.ID) th.AssertEquals(t, "private", s.Name) - th.AssertEquals(t, true, s.AdminStateUp) + th.AssertTrue(t, s.AdminStateUp) th.AssertEquals(t, "ACTIVE", s.Status) th.AssertDeepEquals(t, []string{"08eae331-0402-425a-923c-34f7cfe39c1b"}, s.Subnets) th.AssertEquals(t, "26a7980765d0414dbc1fc1f88cdb7e6e", s.TenantID) - th.AssertEquals(t, false, s.Shared) - th.AssertEquals(t, true, s.VLANTransparent) + th.AssertFalse(t, s.Shared) + th.AssertTrue(t, s.VLANTransparent) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -94,7 +94,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, NetworksVLANTransparentCreateResult) + fmt.Fprint(w, NetworksVLANTransparentCreateResult) }) iTrue := true @@ -112,24 +112,24 @@ func TestCreate(t *testing.T) { vlantransparent.TransparentExt } - err := networks.Create(context.TODO(), fake.ServiceClient(), vlanTransparentCreateOpts).ExtractInto(&s) + err := networks.Create(context.TODO(), fake.ServiceClient(fakeServer), vlanTransparentCreateOpts).ExtractInto(&s) th.AssertNoErr(t, err) th.AssertEquals(t, "db193ab3-96e3-4cb3-8fc5-05f4296d0324", s.ID) th.AssertEquals(t, "private", s.Name) - th.AssertEquals(t, true, s.AdminStateUp) + th.AssertTrue(t, s.AdminStateUp) th.AssertEquals(t, "ACTIVE", s.Status) th.AssertDeepEquals(t, []string{"08eae331-0402-425a-923c-34f7cfe39c1b"}, s.Subnets) th.AssertEquals(t, "26a7980765d0414dbc1fc1f88cdb7e6e", s.TenantID) - th.AssertEquals(t, false, s.Shared) - th.AssertEquals(t, true, s.VLANTransparent) + th.AssertFalse(t, s.Shared) + th.AssertTrue(t, s.VLANTransparent) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -139,7 +139,7 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, NetworksVLANTransparentUpdateResult) + fmt.Fprint(w, NetworksVLANTransparentUpdateResult) }) iFalse := false @@ -159,15 +159,15 @@ func TestUpdate(t *testing.T) { vlantransparent.TransparentExt } - err := networks.Update(context.TODO(), fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", vlanTransparentUpdateOpts).ExtractInto(&s) + err := networks.Update(context.TODO(), fake.ServiceClient(fakeServer), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", vlanTransparentUpdateOpts).ExtractInto(&s) th.AssertNoErr(t, err) th.AssertEquals(t, "db193ab3-96e3-4cb3-8fc5-05f4296d0324", s.ID) th.AssertEquals(t, "new_network_name", s.Name) - th.AssertEquals(t, false, s.AdminStateUp) + th.AssertFalse(t, s.AdminStateUp) th.AssertEquals(t, "ACTIVE", s.Status) th.AssertDeepEquals(t, []string{"08eae331-0402-425a-923c-34f7cfe39c1b"}, s.Subnets) th.AssertEquals(t, "26a7980765d0414dbc1fc1f88cdb7e6e", s.TenantID) - th.AssertEquals(t, false, s.Shared) - th.AssertEquals(t, false, s.VLANTransparent) + th.AssertFalse(t, s.Shared) + th.AssertFalse(t, s.VLANTransparent) } diff --git a/openstack/networking/v2/extensions/vpnaas/endpointgroups/results.go b/openstack/networking/v2/extensions/vpnaas/endpointgroups/results.go index 2fa97f5c8a..1cc1015a15 100644 --- a/openstack/networking/v2/extensions/vpnaas/endpointgroups/results.go +++ b/openstack/networking/v2/extensions/vpnaas/endpointgroups/results.go @@ -51,7 +51,7 @@ type EndpointGroupPage struct { // NextPageURL is invoked when a paginated collection of Endpoint groups has // reached the end of a page and the pager seeks to traverse over a new one. // In order to do this, it needs to construct the next page's URL. -func (r EndpointGroupPage) NextPageURL() (string, error) { +func (r EndpointGroupPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"endpoint_groups_links"` } diff --git a/openstack/networking/v2/extensions/vpnaas/endpointgroups/testing/requests_test.go b/openstack/networking/v2/extensions/vpnaas/endpointgroups/testing/requests_test.go index 806580dd4e..c7620500e1 100644 --- a/openstack/networking/v2/extensions/vpnaas/endpointgroups/testing/requests_test.go +++ b/openstack/networking/v2/extensions/vpnaas/endpointgroups/testing/requests_test.go @@ -13,10 +13,10 @@ import ( ) func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/endpoint-groups", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/endpoint-groups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -36,7 +36,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "endpoint_group": { "description": "", @@ -62,7 +62,7 @@ func TestCreate(t *testing.T) { "10.3.0.0/24", }, } - actual, err := endpointgroups.Create(context.TODO(), fake.ServiceClient(), options).Extract() + actual, err := endpointgroups.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) expected := endpointgroups.EndpointGroup{ Name: "peers", @@ -80,17 +80,17 @@ func TestCreate(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/endpoint-groups/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/endpoint-groups/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "endpoint_group": { "description": "", @@ -108,7 +108,7 @@ func TestGet(t *testing.T) { `) }) - actual, err := endpointgroups.Get(context.TODO(), fake.ServiceClient(), "5c561d9d-eaea-45f6-ae3e-08d1a7080828").Extract() + actual, err := endpointgroups.Get(context.TODO(), fake.ServiceClient(fakeServer), "5c561d9d-eaea-45f6-ae3e-08d1a7080828").Extract() th.AssertNoErr(t, err) expected := endpointgroups.EndpointGroup{ Name: "peers", @@ -126,16 +126,16 @@ func TestGet(t *testing.T) { } func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/endpoint-groups", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/endpoint-groups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "endpoint_groups": [ { @@ -157,7 +157,7 @@ func TestList(t *testing.T) { count := 0 - err := endpointgroups.List(fake.ServiceClient(), endpointgroups.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := endpointgroups.List(fake.ServiceClient(fakeServer), endpointgroups.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := endpointgroups.ExtractEndpointGroups(page) if err != nil { @@ -190,24 +190,24 @@ func TestList(t *testing.T) { } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/endpoint-groups/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/endpoint-groups/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := endpointgroups.Delete(context.TODO(), fake.ServiceClient(), "5c561d9d-eaea-45f6-ae3e-08d1a7080828") + res := endpointgroups.Delete(context.TODO(), fake.ServiceClient(fakeServer), "5c561d9d-eaea-45f6-ae3e-08d1a7080828") th.AssertNoErr(t, res.Err) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/endpoint-groups/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/endpoint-groups/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -224,7 +224,7 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "endpoint_group": { "description": "updated description", @@ -249,7 +249,7 @@ func TestUpdate(t *testing.T) { Description: &updatedDescription, } - actual, err := endpointgroups.Update(context.TODO(), fake.ServiceClient(), "5c561d9d-eaea-45f6-ae3e-08d1a7080828", options).Extract() + actual, err := endpointgroups.Update(context.TODO(), fake.ServiceClient(fakeServer), "5c561d9d-eaea-45f6-ae3e-08d1a7080828", options).Extract() th.AssertNoErr(t, err) expected := endpointgroups.EndpointGroup{ Name: "updatedname", diff --git a/openstack/networking/v2/extensions/vpnaas/ikepolicies/requests.go b/openstack/networking/v2/extensions/vpnaas/ikepolicies/requests.go index 7d5c81e13b..f0845d6a60 100644 --- a/openstack/networking/v2/extensions/vpnaas/ikepolicies/requests.go +++ b/openstack/networking/v2/extensions/vpnaas/ikepolicies/requests.go @@ -15,22 +15,62 @@ type IKEVersion string type Phase1NegotiationMode string const ( - AuthAlgorithmSHA1 AuthAlgorithm = "sha1" - AuthAlgorithmSHA256 AuthAlgorithm = "sha256" - AuthAlgorithmSHA384 AuthAlgorithm = "sha384" - AuthAlgorithmSHA512 AuthAlgorithm = "sha512" - EncryptionAlgorithm3DES EncryptionAlgorithm = "3des" - EncryptionAlgorithmAES128 EncryptionAlgorithm = "aes-128" - EncryptionAlgorithmAES256 EncryptionAlgorithm = "aes-256" - EncryptionAlgorithmAES192 EncryptionAlgorithm = "aes-192" - UnitSeconds Unit = "seconds" - UnitKilobytes Unit = "kilobytes" - PFSGroup2 PFS = "group2" - PFSGroup5 PFS = "group5" - PFSGroup14 PFS = "group14" - IKEVersionv1 IKEVersion = "v1" - IKEVersionv2 IKEVersion = "v2" - Phase1NegotiationModeMain Phase1NegotiationMode = "main" + AuthAlgorithmSHA1 AuthAlgorithm = "sha1" + AuthAlgorithmSHA256 AuthAlgorithm = "sha256" + AuthAlgorithmSHA384 AuthAlgorithm = "sha384" + AuthAlgorithmSHA512 AuthAlgorithm = "sha512" + AuthAlgorithmAESXCBC AuthAlgorithm = "aes-xcbc" + AuthAlgorithmAESCMAC AuthAlgorithm = "aes-cmac" + EncryptionAlgorithm3DES EncryptionAlgorithm = "3des" + EncryptionAlgorithmAES128 EncryptionAlgorithm = "aes-128" + EncryptionAlgorithmAES192 EncryptionAlgorithm = "aes-192" + EncryptionAlgorithmAES256 EncryptionAlgorithm = "aes-256" + EncryptionAlgorithmAES128CTR EncryptionAlgorithm = "aes-128-ctr" + EncryptionAlgorithmAES192CTR EncryptionAlgorithm = "aes-192-ctr" + EncryptionAlgorithmAES256CTR EncryptionAlgorithm = "aes-256-ctr" + EncryptionAlgorithmAES128CCM8 EncryptionAlgorithm = "aes-128-ccm-8" + EncryptionAlgorithmAES128CCM12 EncryptionAlgorithm = "aes-128-ccm-12" + EncryptionAlgorithmAES128CCM16 EncryptionAlgorithm = "aes-128-ccm-16" + EncryptionAlgorithmAES192CCM8 EncryptionAlgorithm = "aes-192-ccm-8" + EncryptionAlgorithmAES192CCM12 EncryptionAlgorithm = "aes-192-ccm-12" + EncryptionAlgorithmAES192CCM16 EncryptionAlgorithm = "aes-192-ccm-16" + EncryptionAlgorithmAES256CCM8 EncryptionAlgorithm = "aes-256-ccm-8" + EncryptionAlgorithmAES256CCM12 EncryptionAlgorithm = "aes-256-ccm-12" + EncryptionAlgorithmAES256CCM16 EncryptionAlgorithm = "aes-256-ccm-16" + EncryptionAlgorithmAES128GCM8 EncryptionAlgorithm = "aes-128-gcm-8" + EncryptionAlgorithmAES128GCM12 EncryptionAlgorithm = "aes-128-gcm-12" + EncryptionAlgorithmAES128GCM16 EncryptionAlgorithm = "aes-128-gcm-16" + EncryptionAlgorithmAES192GCM8 EncryptionAlgorithm = "aes-192-gcm-8" + EncryptionAlgorithmAES192GCM12 EncryptionAlgorithm = "aes-192-gcm-12" + EncryptionAlgorithmAES192GCM16 EncryptionAlgorithm = "aes-192-gcm-16" + EncryptionAlgorithmAES256GCM8 EncryptionAlgorithm = "aes-256-gcm-8" + EncryptionAlgorithmAES256GCM12 EncryptionAlgorithm = "aes-256-gcm-12" + EncryptionAlgorithmAES256GCM16 EncryptionAlgorithm = "aes-256-gcm-16" + UnitSeconds Unit = "seconds" + UnitKilobytes Unit = "kilobytes" + PFSGroup2 PFS = "group2" + PFSGroup5 PFS = "group5" + PFSGroup14 PFS = "group14" + PFSGroup15 PFS = "group15" + PFSGroup16 PFS = "group16" + PFSGroup17 PFS = "group17" + PFSGroup18 PFS = "group18" + PFSGroup19 PFS = "group19" + PFSGroup20 PFS = "group20" + PFSGroup21 PFS = "group21" + PFSGroup22 PFS = "group22" + PFSGroup23 PFS = "group23" + PFSGroup24 PFS = "group24" + PFSGroup25 PFS = "group25" + PFSGroup26 PFS = "group26" + PFSGroup27 PFS = "group27" + PFSGroup28 PFS = "group28" + PFSGroup29 PFS = "group29" + PFSGroup30 PFS = "group30" + PFSGroup31 PFS = "group31" + IKEVersionv1 IKEVersion = "v1" + IKEVersionv2 IKEVersion = "v2" + Phase1NegotiationModeMain Phase1NegotiationMode = "main" ) // CreateOptsBuilder allows extensions to add additional parameters to the diff --git a/openstack/networking/v2/extensions/vpnaas/ikepolicies/results.go b/openstack/networking/v2/extensions/vpnaas/ikepolicies/results.go index 551d0af7a2..3c5c1d8b9a 100644 --- a/openstack/networking/v2/extensions/vpnaas/ikepolicies/results.go +++ b/openstack/networking/v2/extensions/vpnaas/ikepolicies/results.go @@ -72,7 +72,7 @@ type PolicyPage struct { // NextPageURL is invoked when a paginated collection of IKE policies has // reached the end of a page and the pager seeks to traverse over a new one. // In order to do this, it needs to construct the next page's URL. -func (r PolicyPage) NextPageURL() (string, error) { +func (r PolicyPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"ikepolicies_links"` } diff --git a/openstack/networking/v2/extensions/vpnaas/ikepolicies/testing/requests_test.go b/openstack/networking/v2/extensions/vpnaas/ikepolicies/testing/requests_test.go index 74b9427cc1..60b8939a60 100644 --- a/openstack/networking/v2/extensions/vpnaas/ikepolicies/testing/requests_test.go +++ b/openstack/networking/v2/extensions/vpnaas/ikepolicies/testing/requests_test.go @@ -13,10 +13,10 @@ import ( ) func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/ikepolicies", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/ikepolicies", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -35,7 +35,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "ikepolicy":{ "name": "policy", @@ -64,7 +64,7 @@ func TestCreate(t *testing.T) { IKEVersion: ikepolicies.IKEVersionv2, } - actual, err := ikepolicies.Create(context.TODO(), fake.ServiceClient(), options).Extract() + actual, err := ikepolicies.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) expectedLifetime := ikepolicies.Lifetime{ Units: "seconds", @@ -87,17 +87,17 @@ func TestCreate(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/ikepolicies/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/ikepolicies/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "ikepolicy":{ "name": "policy", @@ -119,7 +119,7 @@ func TestGet(t *testing.T) { `) }) - actual, err := ikepolicies.Get(context.TODO(), fake.ServiceClient(), "5c561d9d-eaea-45f6-ae3e-08d1a7080828").Extract() + actual, err := ikepolicies.Get(context.TODO(), fake.ServiceClient(fakeServer), "5c561d9d-eaea-45f6-ae3e-08d1a7080828").Extract() th.AssertNoErr(t, err) expectedLifetime := ikepolicies.Lifetime{ Units: "seconds", @@ -142,30 +142,30 @@ func TestGet(t *testing.T) { } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/ikepolicies/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/ikepolicies/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := ikepolicies.Delete(context.TODO(), fake.ServiceClient(), "5c561d9d-eaea-45f6-ae3e-08d1a7080828") + res := ikepolicies.Delete(context.TODO(), fake.ServiceClient(fakeServer), "5c561d9d-eaea-45f6-ae3e-08d1a7080828") th.AssertNoErr(t, res.Err) } func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/ikepolicies", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/ikepolicies", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "ikepolicies": [ { @@ -191,7 +191,7 @@ func TestList(t *testing.T) { count := 0 - err := ikepolicies.List(fake.ServiceClient(), ikepolicies.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := ikepolicies.List(fake.ServiceClient(fakeServer), ikepolicies.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := ikepolicies.ExtractPolicies(page) if err != nil { @@ -230,10 +230,10 @@ func TestList(t *testing.T) { } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/ikepolicies/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/ikepolicies/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -253,7 +253,7 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "ikepolicy": { "name": "updatedname", @@ -285,7 +285,7 @@ func TestUpdate(t *testing.T) { }, } - actual, err := ikepolicies.Update(context.TODO(), fake.ServiceClient(), "5c561d9d-eaea-45f6-ae3e-08d1a7080828", options).Extract() + actual, err := ikepolicies.Update(context.TODO(), fake.ServiceClient(fakeServer), "5c561d9d-eaea-45f6-ae3e-08d1a7080828", options).Extract() th.AssertNoErr(t, err) expectedLifetime := ikepolicies.Lifetime{ Units: "seconds", diff --git a/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/requests.go b/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/requests.go index 4397c0587b..9f34895c15 100644 --- a/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/requests.go +++ b/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/requests.go @@ -15,24 +15,64 @@ type PFS string type Unit string const ( - TransformProtocolESP TransformProtocol = "esp" - TransformProtocolAH TransformProtocol = "ah" - TransformProtocolAHESP TransformProtocol = "ah-esp" - AuthAlgorithmSHA1 AuthAlgorithm = "sha1" - AuthAlgorithmSHA256 AuthAlgorithm = "sha256" - AuthAlgorithmSHA384 AuthAlgorithm = "sha384" - AuthAlgorithmSHA512 AuthAlgorithm = "sha512" - EncryptionAlgorithm3DES EncryptionAlgorithm = "3des" - EncryptionAlgorithmAES128 EncryptionAlgorithm = "aes-128" - EncryptionAlgorithmAES256 EncryptionAlgorithm = "aes-256" - EncryptionAlgorithmAES192 EncryptionAlgorithm = "aes-192" - EncapsulationModeTunnel EncapsulationMode = "tunnel" - EncapsulationModeTransport EncapsulationMode = "transport" - UnitSeconds Unit = "seconds" - UnitKilobytes Unit = "kilobytes" - PFSGroup2 PFS = "group2" - PFSGroup5 PFS = "group5" - PFSGroup14 PFS = "group14" + TransformProtocolESP TransformProtocol = "esp" + TransformProtocolAH TransformProtocol = "ah" + TransformProtocolAHESP TransformProtocol = "ah-esp" + AuthAlgorithmSHA1 AuthAlgorithm = "sha1" + AuthAlgorithmSHA256 AuthAlgorithm = "sha256" + AuthAlgorithmSHA384 AuthAlgorithm = "sha384" + AuthAlgorithmSHA512 AuthAlgorithm = "sha512" + AuthAlgorithmAESXCBC AuthAlgorithm = "aes-xcbc" + AuthAlgorithmAESCMAC AuthAlgorithm = "aes-cmac" + EncryptionAlgorithm3DES EncryptionAlgorithm = "3des" + EncryptionAlgorithmAES128 EncryptionAlgorithm = "aes-128" + EncryptionAlgorithmAES192 EncryptionAlgorithm = "aes-192" + EncryptionAlgorithmAES256 EncryptionAlgorithm = "aes-256" + EncryptionAlgorithmAES128CTR EncryptionAlgorithm = "aes-128-ctr" + EncryptionAlgorithmAES192CTR EncryptionAlgorithm = "aes-192-ctr" + EncryptionAlgorithmAES256CTR EncryptionAlgorithm = "aes-256-ctr" + EncryptionAlgorithmAES128CCM8 EncryptionAlgorithm = "aes-128-ccm-8" + EncryptionAlgorithmAES128CCM12 EncryptionAlgorithm = "aes-128-ccm-12" + EncryptionAlgorithmAES128CCM16 EncryptionAlgorithm = "aes-128-ccm-16" + EncryptionAlgorithmAES192CCM8 EncryptionAlgorithm = "aes-192-ccm-8" + EncryptionAlgorithmAES192CCM12 EncryptionAlgorithm = "aes-192-ccm-12" + EncryptionAlgorithmAES192CCM16 EncryptionAlgorithm = "aes-192-ccm-16" + EncryptionAlgorithmAES256CCM8 EncryptionAlgorithm = "aes-256-ccm-8" + EncryptionAlgorithmAES256CCM12 EncryptionAlgorithm = "aes-256-ccm-12" + EncryptionAlgorithmAES256CCM16 EncryptionAlgorithm = "aes-256-ccm-16" + EncryptionAlgorithmAES128GCM8 EncryptionAlgorithm = "aes-128-gcm-8" + EncryptionAlgorithmAES128GCM12 EncryptionAlgorithm = "aes-128-gcm-12" + EncryptionAlgorithmAES128GCM16 EncryptionAlgorithm = "aes-128-gcm-16" + EncryptionAlgorithmAES192GCM8 EncryptionAlgorithm = "aes-192-gcm-8" + EncryptionAlgorithmAES192GCM12 EncryptionAlgorithm = "aes-192-gcm-12" + EncryptionAlgorithmAES192GCM16 EncryptionAlgorithm = "aes-192-gcm-16" + EncryptionAlgorithmAES256GCM8 EncryptionAlgorithm = "aes-256-gcm-8" + EncryptionAlgorithmAES256GCM12 EncryptionAlgorithm = "aes-256-gcm-12" + EncryptionAlgorithmAES256GCM16 EncryptionAlgorithm = "aes-256-gcm-16" + EncapsulationModeTunnel EncapsulationMode = "tunnel" + EncapsulationModeTransport EncapsulationMode = "transport" + UnitSeconds Unit = "seconds" + UnitKilobytes Unit = "kilobytes" + PFSGroup2 PFS = "group2" + PFSGroup5 PFS = "group5" + PFSGroup14 PFS = "group14" + PFSGroup15 PFS = "group15" + PFSGroup16 PFS = "group16" + PFSGroup17 PFS = "group17" + PFSGroup18 PFS = "group18" + PFSGroup19 PFS = "group19" + PFSGroup20 PFS = "group20" + PFSGroup21 PFS = "group21" + PFSGroup22 PFS = "group22" + PFSGroup23 PFS = "group23" + PFSGroup24 PFS = "group24" + PFSGroup25 PFS = "group25" + PFSGroup26 PFS = "group26" + PFSGroup27 PFS = "group27" + PFSGroup28 PFS = "group28" + PFSGroup29 PFS = "group29" + PFSGroup30 PFS = "group30" + PFSGroup31 PFS = "group31" ) // CreateOptsBuilder allows extensions to add additional parameters to the diff --git a/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/results.go b/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/results.go index 6170a2a9a7..bedcdd3f3f 100644 --- a/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/results.go +++ b/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/results.go @@ -91,7 +91,7 @@ type PolicyPage struct { // NextPageURL is invoked when a paginated collection of IPSec policies has // reached the end of a page and the pager seeks to traverse over a new one. // In order to do this, it needs to construct the next page's URL. -func (r PolicyPage) NextPageURL() (string, error) { +func (r PolicyPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"ipsecpolicies_links"` } diff --git a/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/testing/requests_test.go b/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/testing/requests_test.go index 0363c7c63e..27435c51a6 100644 --- a/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/testing/requests_test.go +++ b/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/testing/requests_test.go @@ -13,10 +13,10 @@ import ( ) func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/ipsecpolicies", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/ipsecpolicies", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -41,7 +41,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "ipsecpolicy": { "name": "ipsecpolicy1", @@ -78,7 +78,7 @@ func TestCreate(t *testing.T) { Lifetime: &lifetime, Description: "", } - actual, err := ipsecpolicies.Create(context.TODO(), fake.ServiceClient(), options).Extract() + actual, err := ipsecpolicies.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) expectedLifetime := ipsecpolicies.Lifetime{ Units: "seconds", @@ -101,17 +101,17 @@ func TestCreate(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/ipsecpolicies/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/ipsecpolicies/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "ipsecpolicy": { "name": "ipsecpolicy1", @@ -133,7 +133,7 @@ func TestGet(t *testing.T) { `) }) - actual, err := ipsecpolicies.Get(context.TODO(), fake.ServiceClient(), "5c561d9d-eaea-45f6-ae3e-08d1a7080828").Extract() + actual, err := ipsecpolicies.Get(context.TODO(), fake.ServiceClient(fakeServer), "5c561d9d-eaea-45f6-ae3e-08d1a7080828").Extract() th.AssertNoErr(t, err) expectedLifetime := ipsecpolicies.Lifetime{ Units: "seconds", @@ -156,31 +156,31 @@ func TestGet(t *testing.T) { } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/ipsecpolicies/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/ipsecpolicies/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := ipsecpolicies.Delete(context.TODO(), fake.ServiceClient(), "5c561d9d-eaea-45f6-ae3e-08d1a7080828") + res := ipsecpolicies.Delete(context.TODO(), fake.ServiceClient(fakeServer), "5c561d9d-eaea-45f6-ae3e-08d1a7080828") th.AssertNoErr(t, res.Err) } func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/ipsecpolicies", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/ipsecpolicies", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "ipsecpolicies": [ { @@ -206,7 +206,7 @@ func TestList(t *testing.T) { count := 0 - err := ipsecpolicies.List(fake.ServiceClient(), ipsecpolicies.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := ipsecpolicies.List(fake.ServiceClient(fakeServer), ipsecpolicies.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := ipsecpolicies.ExtractPolicies(page) if err != nil { @@ -245,10 +245,10 @@ func TestList(t *testing.T) { } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/ipsecpolicies/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/ipsecpolicies/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -268,7 +268,7 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "ipsecpolicy": { @@ -300,7 +300,7 @@ func TestUpdate(t *testing.T) { }, } - actual, err := ipsecpolicies.Update(context.TODO(), fake.ServiceClient(), "5c561d9d-eaea-45f6-ae3e-08d1a7080828", options).Extract() + actual, err := ipsecpolicies.Update(context.TODO(), fake.ServiceClient(fakeServer), "5c561d9d-eaea-45f6-ae3e-08d1a7080828", options).Extract() th.AssertNoErr(t, err) expectedLifetime := ipsecpolicies.Lifetime{ Units: "seconds", diff --git a/openstack/networking/v2/extensions/vpnaas/services/constants.go b/openstack/networking/v2/extensions/vpnaas/services/constants.go new file mode 100644 index 0000000000..4917083923 --- /dev/null +++ b/openstack/networking/v2/extensions/vpnaas/services/constants.go @@ -0,0 +1,11 @@ +package services + +const ( + StatusActive = "ACTIVE" + StatusBuild = "BUILD" + StatusDown = "DOWN" + StatusError = "ERROR" + StatusPendingCreate = "PENDING_CREATE" + StatusPendingDelete = "PENDING_DELETE" + StatusPendingUpdate = "PENDING_UPDATE" +) diff --git a/openstack/networking/v2/extensions/vpnaas/services/results.go b/openstack/networking/v2/extensions/vpnaas/services/results.go index 1af204b157..70eec1f25f 100644 --- a/openstack/networking/v2/extensions/vpnaas/services/results.go +++ b/openstack/networking/v2/extensions/vpnaas/services/results.go @@ -59,7 +59,7 @@ type ServicePage struct { // NextPageURL is invoked when a paginated collection of VPN services has // reached the end of a page and the pager seeks to traverse over a new one. // In order to do this, it needs to construct the next page's URL. -func (r ServicePage) NextPageURL() (string, error) { +func (r ServicePage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"vpnservices_links"` } diff --git a/openstack/networking/v2/extensions/vpnaas/services/testing/requests_test.go b/openstack/networking/v2/extensions/vpnaas/services/testing/requests_test.go index ce9e50171e..84c14a05f8 100644 --- a/openstack/networking/v2/extensions/vpnaas/services/testing/requests_test.go +++ b/openstack/networking/v2/extensions/vpnaas/services/testing/requests_test.go @@ -14,10 +14,10 @@ import ( ) func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/vpnservices", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/vpnservices", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -36,7 +36,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "vpnservice": { "router_id": "66e3b16c-8ce5-40fb-bb49-ab6d8dc3f2aa", @@ -62,7 +62,7 @@ func TestCreate(t *testing.T) { AdminStateUp: gophercloud.Enabled, RouterID: "66e3b16c-8ce5-40fb-bb49-ab6d8dc3f2aa", } - actual, err := services.Create(context.TODO(), fake.ServiceClient(), options).Extract() + actual, err := services.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) expected := services.Service{ RouterID: "66e3b16c-8ce5-40fb-bb49-ab6d8dc3f2aa", @@ -81,17 +81,17 @@ func TestCreate(t *testing.T) { } func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/vpnservices", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/vpnservices", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "vpnservices":[ { @@ -111,7 +111,7 @@ func TestList(t *testing.T) { count := 0 - err := services.List(fake.ServiceClient(), services.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := services.List(fake.ServiceClient(fakeServer), services.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := services.ExtractServices(page) if err != nil { @@ -144,17 +144,17 @@ func TestList(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/vpnservices/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/vpnservices/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "vpnservice": { "router_id": "66e3b16c-8ce5-40fb-bb49-ab6d8dc3f2aa", @@ -171,7 +171,7 @@ func TestGet(t *testing.T) { `) }) - actual, err := services.Get(context.TODO(), fake.ServiceClient(), "5c561d9d-eaea-45f6-ae3e-08d1a7080828").Extract() + actual, err := services.Get(context.TODO(), fake.ServiceClient(fakeServer), "5c561d9d-eaea-45f6-ae3e-08d1a7080828").Extract() th.AssertNoErr(t, err) expected := services.Service{ Status: "PENDING_CREATE", @@ -188,23 +188,23 @@ func TestGet(t *testing.T) { } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/vpnservices/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/vpnservices/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := services.Delete(context.TODO(), fake.ServiceClient(), "5c561d9d-eaea-45f6-ae3e-08d1a7080828") + res := services.Delete(context.TODO(), fake.ServiceClient(fakeServer), "5c561d9d-eaea-45f6-ae3e-08d1a7080828") th.AssertNoErr(t, res.Err) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/vpnservices/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/vpnservices/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) @@ -223,7 +223,7 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "vpnservice": { "router_id": "66e3b16c-8ce5-40fb-bb49-ab6d8dc3f2aa", @@ -249,7 +249,7 @@ func TestUpdate(t *testing.T) { AdminStateUp: gophercloud.Disabled, } - actual, err := services.Update(context.TODO(), fake.ServiceClient(), "5c561d9d-eaea-45f6-ae3e-08d1a7080828", options).Extract() + actual, err := services.Update(context.TODO(), fake.ServiceClient(fakeServer), "5c561d9d-eaea-45f6-ae3e-08d1a7080828", options).Extract() th.AssertNoErr(t, err) expected := services.Service{ RouterID: "66e3b16c-8ce5-40fb-bb49-ab6d8dc3f2aa", diff --git a/openstack/networking/v2/extensions/vpnaas/siteconnections/constants.go b/openstack/networking/v2/extensions/vpnaas/siteconnections/constants.go new file mode 100644 index 0000000000..d4f4e0f448 --- /dev/null +++ b/openstack/networking/v2/extensions/vpnaas/siteconnections/constants.go @@ -0,0 +1,11 @@ +package siteconnections + +const ( + StatusActive = "ACTIVE" + StatusBuild = "BUILD" + StatusDown = "DOWN" + StatusError = "ERROR" + StatusPendingCreate = "PENDING_CREATE" + StatusPendingDelete = "PENDING_DELETE" + StatusPendingUpdate = "PENDING_UPDATE" +) diff --git a/openstack/networking/v2/extensions/vpnaas/siteconnections/results.go b/openstack/networking/v2/extensions/vpnaas/siteconnections/results.go index 59840e8df9..88e57c5142 100644 --- a/openstack/networking/v2/extensions/vpnaas/siteconnections/results.go +++ b/openstack/networking/v2/extensions/vpnaas/siteconnections/results.go @@ -101,7 +101,7 @@ type ConnectionPage struct { // NextPageURL is invoked when a paginated collection of IPSec site connections has // reached the end of a page and the pager seeks to traverse over a new one. // In order to do this, it needs to construct the next page's URL. -func (r ConnectionPage) NextPageURL() (string, error) { +func (r ConnectionPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"ipsec_site_connections_links"` } diff --git a/openstack/networking/v2/extensions/vpnaas/siteconnections/testing/requests_test.go b/openstack/networking/v2/extensions/vpnaas/siteconnections/testing/requests_test.go index 68977acee4..689e153ad1 100644 --- a/openstack/networking/v2/extensions/vpnaas/siteconnections/testing/requests_test.go +++ b/openstack/networking/v2/extensions/vpnaas/siteconnections/testing/requests_test.go @@ -14,10 +14,10 @@ import ( ) func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/ipsec-site-connections", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/ipsec-site-connections", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -45,7 +45,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "ipsec_site_connection": { "status": "PENDING_CREATE", @@ -92,7 +92,7 @@ func TestCreate(t *testing.T) { PeerAddress: "172.24.4.233", PeerID: "172.24.4.233", } - actual, err := siteconnections.Create(context.TODO(), fake.ServiceClient(), options).Extract() + actual, err := siteconnections.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) expectedDPD := siteconnections.DPD{ Action: "hold", @@ -126,31 +126,31 @@ func TestCreate(t *testing.T) { } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/ipsec-site-connections/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/ipsec-site-connections/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := siteconnections.Delete(context.TODO(), fake.ServiceClient(), "5c561d9d-eaea-45f6-ae3e-08d1a7080828") + res := siteconnections.Delete(context.TODO(), fake.ServiceClient(fakeServer), "5c561d9d-eaea-45f6-ae3e-08d1a7080828") th.AssertNoErr(t, res.Err) } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/ipsec-site-connections/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/ipsec-site-connections/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "ipsec_site_connection": { "status": "PENDING_CREATE", @@ -183,7 +183,7 @@ func TestGet(t *testing.T) { `) }) - actual, err := siteconnections.Get(context.TODO(), fake.ServiceClient(), "5c561d9d-eaea-45f6-ae3e-08d1a7080828").Extract() + actual, err := siteconnections.Get(context.TODO(), fake.ServiceClient(fakeServer), "5c561d9d-eaea-45f6-ae3e-08d1a7080828").Extract() th.AssertNoErr(t, err) expectedDPD := siteconnections.DPD{ Action: "hold", @@ -217,17 +217,17 @@ func TestGet(t *testing.T) { } func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/ipsec-site-connections", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/ipsec-site-connections", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "ipsec_site_connections":[ { @@ -263,7 +263,7 @@ func TestList(t *testing.T) { count := 0 - err := siteconnections.List(fake.ServiceClient(), siteconnections.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := siteconnections.List(fake.ServiceClient(fakeServer), siteconnections.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := siteconnections.ExtractConnections(page) if err != nil { @@ -314,10 +314,10 @@ func TestList(t *testing.T) { } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/vpn/ipsec-site-connections/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/vpn/ipsec-site-connections/5c561d9d-eaea-45f6-ae3e-08d1a7080828", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -336,7 +336,7 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "ipsec_site_connection": { @@ -379,7 +379,7 @@ func TestUpdate(t *testing.T) { PSK: "updatedsecret", } - actual, err := siteconnections.Update(context.TODO(), fake.ServiceClient(), "5c561d9d-eaea-45f6-ae3e-08d1a7080828", options).Extract() + actual, err := siteconnections.Update(context.TODO(), fake.ServiceClient(fakeServer), "5c561d9d-eaea-45f6-ae3e-08d1a7080828", options).Extract() th.AssertNoErr(t, err) expectedDPD := siteconnections.DPD{ diff --git a/openstack/networking/v2/networks/constants.go b/openstack/networking/v2/networks/constants.go new file mode 100644 index 0000000000..1214ce9deb --- /dev/null +++ b/openstack/networking/v2/networks/constants.go @@ -0,0 +1,8 @@ +package networks + +const ( + StatusActive = "ACTIVE" + StatusBuild = "BUILD" + StatusDown = "DOWN" + StatusError = "ERROR" +) diff --git a/openstack/networking/v2/networks/requests.go b/openstack/networking/v2/networks/requests.go index 29d14187ab..d4dd64ff93 100644 --- a/openstack/networking/v2/networks/requests.go +++ b/openstack/networking/v2/networks/requests.go @@ -20,22 +20,23 @@ type ListOptsBuilder interface { // by a particular network attribute. SortDir sets the direction, and is either // `asc' or `desc'. Marker and Limit are used for pagination. type ListOpts struct { - Status string `q:"status"` - Name string `q:"name"` - Description string `q:"description"` - AdminStateUp *bool `q:"admin_state_up"` - TenantID string `q:"tenant_id"` - ProjectID string `q:"project_id"` - Shared *bool `q:"shared"` - ID string `q:"id"` - Marker string `q:"marker"` - Limit int `q:"limit"` - SortKey string `q:"sort_key"` - SortDir string `q:"sort_dir"` - Tags string `q:"tags"` - TagsAny string `q:"tags-any"` - NotTags string `q:"not-tags"` - NotTagsAny string `q:"not-tags-any"` + Status string `q:"status"` + Name string `q:"name"` + Description string `q:"description"` + AdminStateUp *bool `q:"admin_state_up"` + TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` + Shared *bool `q:"shared"` + ID string `q:"id"` + Marker string `q:"marker"` + Limit int `q:"limit"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` + Tags string `q:"tags"` + TagsAny string `q:"tags-any"` + NotTags string `q:"not-tags"` + NotTagsAny string `q:"not-tags-any"` + RevisionNumber *int `q:"revision_number"` } // ToNetworkListQuery formats a ListOpts into a query string. diff --git a/openstack/networking/v2/networks/results.go b/openstack/networking/v2/networks/results.go index 53927bf0c0..60ef21d116 100644 --- a/openstack/networking/v2/networks/results.go +++ b/openstack/networking/v2/networks/results.go @@ -20,7 +20,7 @@ func (r commonResult) Extract() (*Network, error) { } func (r commonResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "network") + return r.ExtractIntoStructPtr(v, "network") } // CreateResult represents the result of a create operation. Call its Extract @@ -142,7 +142,7 @@ type NetworkPage struct { // NextPageURL is invoked when a paginated collection of networks has reached // the end of a page and the pager seeks to traverse over a new one. In order // to do this, it needs to construct the next page's URL. -func (r NetworkPage) NextPageURL() (string, error) { +func (r NetworkPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"networks_links"` } @@ -173,5 +173,5 @@ func ExtractNetworks(r pagination.Page) ([]Network, error) { } func ExtractNetworksInto(r pagination.Page, v any) error { - return r.(NetworkPage).Result.ExtractIntoSlicePtr(v, "networks") + return r.(NetworkPage).ExtractIntoSlicePtr(v, "networks") } diff --git a/openstack/networking/v2/networks/testing/requests_test.go b/openstack/networking/v2/networks/testing/requests_test.go index c28dd2ed0f..0e913d4a74 100644 --- a/openstack/networking/v2/networks/testing/requests_test.go +++ b/openstack/networking/v2/networks/testing/requests_test.go @@ -15,20 +15,20 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) count := 0 err := networks.List(client, networks.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { @@ -52,20 +52,20 @@ func TestList(t *testing.T) { } func TestListWithExtensions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) - client := fake.ServiceClient() + client := fake.ServiceClient(fakeServer) type networkWithExt struct { networks.Network @@ -80,49 +80,49 @@ func TestListWithExtensions(t *testing.T) { err = networks.ExtractNetworksInto(allPages, &allNetworks) th.AssertNoErr(t, err) - th.AssertEquals(t, allNetworks[0].Status, "ACTIVE") - th.AssertEquals(t, allNetworks[0].PortSecurityEnabled, true) - th.AssertEquals(t, allNetworks[0].Subnets[0], "54d6f61d-db07-451c-9ab3-b9609b6b6f0b") - th.AssertEquals(t, allNetworks[1].Subnets[0], "08eae331-0402-425a-923c-34f7cfe39c1b") - th.AssertEquals(t, allNetworks[0].CreatedAt.Format(time.RFC3339), "2019-06-30T04:15:37Z") - th.AssertEquals(t, allNetworks[0].UpdatedAt.Format(time.RFC3339), "2019-06-30T05:18:49Z") - th.AssertEquals(t, allNetworks[1].CreatedAt.Format(time.RFC3339), "2019-06-30T04:15:37Z") - th.AssertEquals(t, allNetworks[1].UpdatedAt.Format(time.RFC3339), "2019-06-30T05:18:49Z") + th.AssertEquals(t, "ACTIVE", allNetworks[0].Status) + th.AssertTrue(t, allNetworks[0].PortSecurityEnabled) + th.AssertEquals(t, "54d6f61d-db07-451c-9ab3-b9609b6b6f0b", allNetworks[0].Subnets[0]) + th.AssertEquals(t, "08eae331-0402-425a-923c-34f7cfe39c1b", allNetworks[1].Subnets[0]) + th.AssertEquals(t, "2019-06-30T04:15:37Z", allNetworks[0].CreatedAt.Format(time.RFC3339)) + th.AssertEquals(t, "2019-06-30T05:18:49Z", allNetworks[0].UpdatedAt.Format(time.RFC3339)) + th.AssertEquals(t, "2019-06-30T04:15:37Z", allNetworks[1].CreatedAt.Format(time.RFC3339)) + th.AssertEquals(t, "2019-06-30T05:18:49Z", allNetworks[1].UpdatedAt.Format(time.RFC3339)) } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) - n, err := networks.Get(context.TODO(), fake.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() + n, err := networks.Get(context.TODO(), fake.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &Network1, n) - th.AssertEquals(t, n.CreatedAt.Format(time.RFC3339), "2019-06-30T04:15:37Z") - th.AssertEquals(t, n.UpdatedAt.Format(time.RFC3339), "2019-06-30T05:18:49Z") + th.AssertEquals(t, "2019-06-30T04:15:37Z", n.CreatedAt.Format(time.RFC3339)) + th.AssertEquals(t, "2019-06-30T05:18:49Z", n.UpdatedAt.Format(time.RFC3339)) } func TestGetWithExtensions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) var networkWithExtensions struct { @@ -130,18 +130,18 @@ func TestGetWithExtensions(t *testing.T) { portsecurity.PortSecurityExt } - err := networks.Get(context.TODO(), fake.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(&networkWithExtensions) + err := networks.Get(context.TODO(), fake.ServiceClient(fakeServer), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(&networkWithExtensions) th.AssertNoErr(t, err) - th.AssertEquals(t, networkWithExtensions.Status, "ACTIVE") - th.AssertEquals(t, networkWithExtensions.PortSecurityEnabled, true) + th.AssertEquals(t, "ACTIVE", networkWithExtensions.Status) + th.AssertTrue(t, networkWithExtensions.PortSecurityEnabled) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -150,25 +150,25 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) iTrue := true options := networks.CreateOpts{Name: "private", AdminStateUp: &iTrue} - n, err := networks.Create(context.TODO(), fake.ServiceClient(), options).Extract() + n, err := networks.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.Status, "ACTIVE") + th.AssertEquals(t, "ACTIVE", n.Status) th.AssertDeepEquals(t, &Network2, n) - th.AssertEquals(t, n.CreatedAt.Format(time.RFC3339), "2019-06-30T04:15:37Z") - th.AssertEquals(t, n.UpdatedAt.Format(time.RFC3339), "2019-06-30T05:18:49Z") + th.AssertEquals(t, "2019-06-30T04:15:37Z", n.CreatedAt.Format(time.RFC3339)) + th.AssertEquals(t, "2019-06-30T05:18:49Z", n.UpdatedAt.Format(time.RFC3339)) } func TestCreateWithOptionalFields(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -176,7 +176,7 @@ func TestCreateWithOptionalFields(t *testing.T) { th.TestJSONRequest(t, r, CreateOptionalFieldsRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) iTrue := true @@ -187,15 +187,15 @@ func TestCreateWithOptionalFields(t *testing.T) { TenantID: "12345", AvailabilityZoneHints: []string{"zone1", "zone2"}, } - _, err := networks.Create(context.TODO(), fake.ServiceClient(), options).Extract() + _, err := networks.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -205,28 +205,28 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateResponse) + fmt.Fprint(w, UpdateResponse) }) iTrue, iFalse := true, false name := "new_network_name" options := networks.UpdateOpts{Name: &name, AdminStateUp: &iFalse, Shared: &iTrue} - n, err := networks.Update(context.TODO(), fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", options).Extract() + n, err := networks.Update(context.TODO(), fake.ServiceClient(fakeServer), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.Name, "new_network_name") - th.AssertEquals(t, n.AdminStateUp, false) - th.AssertEquals(t, n.Shared, true) - th.AssertEquals(t, n.ID, "4e8e5957-649f-477b-9e5b-f1f75b21c03c") - th.AssertEquals(t, n.CreatedAt.Format(time.RFC3339), "2019-06-30T04:15:37Z") - th.AssertEquals(t, n.UpdatedAt.Format(time.RFC3339), "2019-06-30T05:18:49Z") + th.AssertEquals(t, "new_network_name", n.Name) + th.AssertFalse(t, n.AdminStateUp) + th.AssertTrue(t, n.Shared) + th.AssertEquals(t, "4e8e5957-649f-477b-9e5b-f1f75b21c03c", n.ID) + th.AssertEquals(t, "2019-06-30T04:15:37Z", n.CreatedAt.Format(time.RFC3339)) + th.AssertEquals(t, "2019-06-30T05:18:49Z", n.UpdatedAt.Format(time.RFC3339)) } func TestUpdateRevision(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -237,10 +237,10 @@ func TestUpdateRevision(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateResponse) + fmt.Fprint(w, UpdateResponse) }) - th.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03d", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03d", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -251,40 +251,40 @@ func TestUpdateRevision(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateResponse) + fmt.Fprint(w, UpdateResponse) }) iTrue, iFalse := true, false name := "new_network_name" options := networks.UpdateOpts{Name: &name, AdminStateUp: &iFalse, Shared: &iTrue} - _, err := networks.Update(context.TODO(), fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", options).Extract() + _, err := networks.Update(context.TODO(), fake.ServiceClient(fakeServer), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", options).Extract() th.AssertNoErr(t, err) revisionNumber := 42 options.RevisionNumber = &revisionNumber - _, err = networks.Update(context.TODO(), fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03d", options).Extract() + _, err = networks.Update(context.TODO(), fake.ServiceClient(fakeServer), "4e8e5957-649f-477b-9e5b-f1f75b21c03d", options).Extract() th.AssertNoErr(t, err) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := networks.Delete(context.TODO(), fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c") + res := networks.Delete(context.TODO(), fake.ServiceClient(fakeServer), "4e8e5957-649f-477b-9e5b-f1f75b21c03c") th.AssertNoErr(t, res.Err) } func TestCreatePortSecurity(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -293,7 +293,7 @@ func TestCreatePortSecurity(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreatePortSecurityResponse) + fmt.Fprint(w, CreatePortSecurityResponse) }) var networkWithExtensions struct { @@ -309,18 +309,18 @@ func TestCreatePortSecurity(t *testing.T) { PortSecurityEnabled: &iFalse, } - err := networks.Create(context.TODO(), fake.ServiceClient(), createOpts).ExtractInto(&networkWithExtensions) + err := networks.Create(context.TODO(), fake.ServiceClient(fakeServer), createOpts).ExtractInto(&networkWithExtensions) th.AssertNoErr(t, err) - th.AssertEquals(t, networkWithExtensions.Status, "ACTIVE") - th.AssertEquals(t, networkWithExtensions.PortSecurityEnabled, false) + th.AssertEquals(t, "ACTIVE", networkWithExtensions.Status) + th.AssertFalse(t, networkWithExtensions.PortSecurityEnabled) } func TestUpdatePortSecurity(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -330,7 +330,7 @@ func TestUpdatePortSecurity(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdatePortSecurityResponse) + fmt.Fprint(w, UpdatePortSecurityResponse) }) var networkWithExtensions struct { @@ -345,12 +345,12 @@ func TestUpdatePortSecurity(t *testing.T) { PortSecurityEnabled: &iFalse, } - err := networks.Update(context.TODO(), fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", updateOpts).ExtractInto(&networkWithExtensions) + err := networks.Update(context.TODO(), fake.ServiceClient(fakeServer), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", updateOpts).ExtractInto(&networkWithExtensions) th.AssertNoErr(t, err) - th.AssertEquals(t, networkWithExtensions.Name, "private") - th.AssertEquals(t, networkWithExtensions.AdminStateUp, true) - th.AssertEquals(t, networkWithExtensions.Shared, false) - th.AssertEquals(t, networkWithExtensions.ID, "4e8e5957-649f-477b-9e5b-f1f75b21c03c") - th.AssertEquals(t, networkWithExtensions.PortSecurityEnabled, false) + th.AssertEquals(t, "private", networkWithExtensions.Name) + th.AssertTrue(t, networkWithExtensions.AdminStateUp) + th.AssertFalse(t, networkWithExtensions.Shared) + th.AssertEquals(t, "4e8e5957-649f-477b-9e5b-f1f75b21c03c", networkWithExtensions.ID) + th.AssertFalse(t, networkWithExtensions.PortSecurityEnabled) } diff --git a/openstack/networking/v2/ports/constants.go b/openstack/networking/v2/ports/constants.go new file mode 100644 index 0000000000..6275839bf4 --- /dev/null +++ b/openstack/networking/v2/ports/constants.go @@ -0,0 +1,8 @@ +package ports + +const ( + StatusActive = "ACTIVE" + StatusBuild = "BUILD" + StatusDown = "DOWN" + StatusError = "ERROR" +) diff --git a/openstack/networking/v2/ports/requests.go b/openstack/networking/v2/ports/requests.go index 218c2897f7..a9c5c37535 100644 --- a/openstack/networking/v2/ports/requests.go +++ b/openstack/networking/v2/ports/requests.go @@ -41,6 +41,7 @@ type ListOpts struct { TagsAny string `q:"tags-any"` NotTags string `q:"not-tags"` NotTagsAny string `q:"not-tags-any"` + RevisionNumber *int `q:"revision_number"` SecurityGroups []string `q:"security_groups"` FixedIPs []FixedIPOpts } @@ -196,6 +197,7 @@ type UpdateOpts struct { AllowedAddressPairs *[]AddressPair `json:"allowed_address_pairs,omitempty"` PropagateUplinkStatus *bool `json:"propagate_uplink_status,omitempty"` ValueSpecs *map[string]string `json:"value_specs,omitempty"` + MACAddress *string `json:"mac_address,omitempty"` // RevisionNumber implements extension:standard-attr-revisions. If != "" it // will set revision_number=%s. If the revision number does not match, the diff --git a/openstack/networking/v2/ports/results.go b/openstack/networking/v2/ports/results.go index 74a0fa3b49..6b5a2a2654 100644 --- a/openstack/networking/v2/ports/results.go +++ b/openstack/networking/v2/ports/results.go @@ -20,7 +20,7 @@ func (r commonResult) Extract() (*Port, error) { } func (r commonResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "port") + return r.ExtractIntoStructPtr(v, "port") } // CreateResult represents the result of a create operation. Call its Extract @@ -49,7 +49,7 @@ type DeleteResult struct { // IP is a sub-struct that represents an individual IP. type IP struct { - SubnetID string `json:"subnet_id"` + SubnetID string `json:"subnet_id,omitempty"` IPAddress string `json:"ip_address,omitempty"` } @@ -171,7 +171,7 @@ type PortPage struct { // NextPageURL is invoked when a paginated collection of ports has reached // the end of a page and the pager seeks to traverse over a new one. In order // to do this, it needs to construct the next page's URL. -func (r PortPage) NextPageURL() (string, error) { +func (r PortPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"ports_links"` } @@ -202,5 +202,5 @@ func ExtractPorts(r pagination.Page) ([]Port, error) { } func ExtractPortsInto(r pagination.Page, v any) error { - return r.(PortPage).Result.ExtractIntoSlicePtr(v, "ports") + return r.(PortPage).ExtractIntoSlicePtr(v, "ports") } diff --git a/openstack/networking/v2/ports/testing/fixtures.go b/openstack/networking/v2/ports/testing/fixtures.go index b4c03fd618..b11650863f 100644 --- a/openstack/networking/v2/ports/testing/fixtures.go +++ b/openstack/networking/v2/ports/testing/fixtures.go @@ -409,7 +409,8 @@ const UpdateRequest = ` ], "security_groups": [ "f0ac4394-7e4a-4409-9701-ba8be283dbc3" - ] + ], + "mac_address": "fa:16:3e:c9:cb:f4" } } ` @@ -423,7 +424,7 @@ const UpdateResponse = ` "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7", "tenant_id": "d6700c0c9ffa4f1cb322cd4a1f3906fa", "device_owner": "", - "mac_address": "fa:16:3e:c9:cb:f0", + "mac_address": "fa:16:3e:c9:cb:f4", "fixed_ips": [ { "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2", diff --git a/openstack/networking/v2/ports/testing/requests_test.go b/openstack/networking/v2/ports/testing/requests_test.go index 7c3569b70b..a6812d7f73 100644 --- a/openstack/networking/v2/ports/testing/requests_test.go +++ b/openstack/networking/v2/ports/testing/requests_test.go @@ -17,22 +17,22 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) count := 0 - err := ports.List(fake.ServiceClient(), ports.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := ports.List(fake.ServiceClient(fakeServer), ports.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := ports.ExtractPorts(page) if err != nil { @@ -76,17 +76,17 @@ func TestList(t *testing.T) { } func TestListWithExtensions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) type portWithExt struct { @@ -96,63 +96,63 @@ func TestListWithExtensions(t *testing.T) { var allPorts []portWithExt - allPages, err := ports.List(fake.ServiceClient(), ports.ListOpts{}).AllPages(context.TODO()) + allPages, err := ports.List(fake.ServiceClient(fakeServer), ports.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) err = ports.ExtractPortsInto(allPages, &allPorts) th.AssertNoErr(t, err) - th.AssertEquals(t, allPorts[0].Status, "ACTIVE") - th.AssertEquals(t, allPorts[0].PortSecurityEnabled, false) + th.AssertEquals(t, "ACTIVE", allPorts[0].Status) + th.AssertFalse(t, allPorts[0].PortSecurityEnabled) } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports/46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports/46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) - n, err := ports.Get(context.TODO(), fake.ServiceClient(), "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2").Extract() + n, err := ports.Get(context.TODO(), fake.ServiceClient(fakeServer), "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.Status, "ACTIVE") - th.AssertEquals(t, n.Name, "") - th.AssertEquals(t, n.AdminStateUp, true) - th.AssertEquals(t, n.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7") - th.AssertEquals(t, n.TenantID, "7e02058126cc4950b75f9970368ba177") - th.AssertEquals(t, n.DeviceOwner, "network:router_interface") - th.AssertEquals(t, n.MACAddress, "fa:16:3e:23:fd:d7") - th.AssertDeepEquals(t, n.FixedIPs, []ports.IP{ + th.AssertEquals(t, "ACTIVE", n.Status) + th.AssertEquals(t, "", n.Name) + th.AssertTrue(t, n.AdminStateUp) + th.AssertEquals(t, "a87cc70a-3e15-4acf-8205-9b711a3531b7", n.NetworkID) + th.AssertEquals(t, "7e02058126cc4950b75f9970368ba177", n.TenantID) + th.AssertEquals(t, "network:router_interface", n.DeviceOwner) + th.AssertEquals(t, "fa:16:3e:23:fd:d7", n.MACAddress) + th.AssertDeepEquals(t, []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.1"}, - }) - th.AssertEquals(t, n.ID, "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2") - th.AssertDeepEquals(t, n.SecurityGroups, []string{}) - th.AssertEquals(t, n.Status, "ACTIVE") - th.AssertEquals(t, n.DeviceID, "5e3898d7-11be-483e-9732-b2f5eccd2b2e") + }, n.FixedIPs) + th.AssertEquals(t, "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", n.ID) + th.AssertDeepEquals(t, []string{}, n.SecurityGroups) + th.AssertEquals(t, "ACTIVE", n.Status) + th.AssertEquals(t, "5e3898d7-11be-483e-9732-b2f5eccd2b2e", n.DeviceID) th.AssertEquals(t, n.CreatedAt, time.Date(2019, time.June, 30, 4, 15, 37, 0, time.UTC)) th.AssertEquals(t, n.UpdatedAt, time.Date(2019, time.June, 30, 5, 18, 49, 0, time.UTC)) } func TestGetWithExtensions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports/46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports/46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) var portWithExtensions struct { @@ -160,18 +160,18 @@ func TestGetWithExtensions(t *testing.T) { portsecurity.PortSecurityExt } - err := ports.Get(context.TODO(), fake.ServiceClient(), "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2").ExtractInto(&portWithExtensions) + err := ports.Get(context.TODO(), fake.ServiceClient(fakeServer), "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2").ExtractInto(&portWithExtensions) th.AssertNoErr(t, err) - th.AssertEquals(t, portWithExtensions.Status, "ACTIVE") - th.AssertEquals(t, portWithExtensions.PortSecurityEnabled, false) + th.AssertEquals(t, "ACTIVE", portWithExtensions.Status) + th.AssertFalse(t, portWithExtensions.PortSecurityEnabled) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -181,7 +181,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) asu := true @@ -197,31 +197,31 @@ func TestCreate(t *testing.T) { {IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"}, }, } - n, err := ports.Create(context.TODO(), fake.ServiceClient(), options).Extract() + n, err := ports.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.Status, "DOWN") - th.AssertEquals(t, n.Name, "private-port") - th.AssertEquals(t, n.AdminStateUp, true) - th.AssertEquals(t, n.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7") - th.AssertEquals(t, n.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa") - th.AssertEquals(t, n.DeviceOwner, "") - th.AssertEquals(t, n.MACAddress, "fa:16:3e:c9:cb:f0") - th.AssertDeepEquals(t, n.FixedIPs, []ports.IP{ + th.AssertEquals(t, "DOWN", n.Status) + th.AssertEquals(t, "private-port", n.Name) + th.AssertTrue(t, n.AdminStateUp) + th.AssertEquals(t, "a87cc70a-3e15-4acf-8205-9b711a3531b7", n.NetworkID) + th.AssertEquals(t, "d6700c0c9ffa4f1cb322cd4a1f3906fa", n.TenantID) + th.AssertEquals(t, "", n.DeviceOwner) + th.AssertEquals(t, "fa:16:3e:c9:cb:f0", n.MACAddress) + th.AssertDeepEquals(t, []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"}, - }) - th.AssertEquals(t, n.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") - th.AssertDeepEquals(t, n.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}) - th.AssertDeepEquals(t, n.AllowedAddressPairs, []ports.AddressPair{ + }, n.FixedIPs) + th.AssertEquals(t, "65c0ee9f-d634-4522-8954-51021b570b0d", n.ID) + th.AssertDeepEquals(t, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}, n.SecurityGroups) + th.AssertDeepEquals(t, []ports.AddressPair{ {IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"}, - }) + }, n.AllowedAddressPairs) } func TestCreateOmitSecurityGroups(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -231,7 +231,7 @@ func TestCreateOmitSecurityGroups(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateOmitSecurityGroupsResponse) + fmt.Fprint(w, CreateOmitSecurityGroupsResponse) }) asu := true @@ -246,31 +246,31 @@ func TestCreateOmitSecurityGroups(t *testing.T) { {IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"}, }, } - n, err := ports.Create(context.TODO(), fake.ServiceClient(), options).Extract() + n, err := ports.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.Status, "DOWN") - th.AssertEquals(t, n.Name, "private-port") - th.AssertEquals(t, n.AdminStateUp, true) - th.AssertEquals(t, n.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7") - th.AssertEquals(t, n.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa") - th.AssertEquals(t, n.DeviceOwner, "") - th.AssertEquals(t, n.MACAddress, "fa:16:3e:c9:cb:f0") - th.AssertDeepEquals(t, n.FixedIPs, []ports.IP{ + th.AssertEquals(t, "DOWN", n.Status) + th.AssertEquals(t, "private-port", n.Name) + th.AssertTrue(t, n.AdminStateUp) + th.AssertEquals(t, "a87cc70a-3e15-4acf-8205-9b711a3531b7", n.NetworkID) + th.AssertEquals(t, "d6700c0c9ffa4f1cb322cd4a1f3906fa", n.TenantID) + th.AssertEquals(t, "", n.DeviceOwner) + th.AssertEquals(t, "fa:16:3e:c9:cb:f0", n.MACAddress) + th.AssertDeepEquals(t, []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"}, - }) - th.AssertEquals(t, n.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") - th.AssertDeepEquals(t, n.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}) - th.AssertDeepEquals(t, n.AllowedAddressPairs, []ports.AddressPair{ + }, n.FixedIPs) + th.AssertEquals(t, "65c0ee9f-d634-4522-8954-51021b570b0d", n.ID) + th.AssertDeepEquals(t, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}, n.SecurityGroups) + th.AssertDeepEquals(t, []ports.AddressPair{ {IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"}, - }) + }, n.AllowedAddressPairs) } func TestCreateWithNoSecurityGroup(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -280,7 +280,7 @@ func TestCreateWithNoSecurityGroup(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateWithNoSecurityGroupsResponse) + fmt.Fprint(w, CreateWithNoSecurityGroupsResponse) }) asu := true @@ -296,30 +296,30 @@ func TestCreateWithNoSecurityGroup(t *testing.T) { {IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"}, }, } - n, err := ports.Create(context.TODO(), fake.ServiceClient(), options).Extract() + n, err := ports.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.Status, "DOWN") - th.AssertEquals(t, n.Name, "private-port") - th.AssertEquals(t, n.AdminStateUp, true) - th.AssertEquals(t, n.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7") - th.AssertEquals(t, n.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa") - th.AssertEquals(t, n.DeviceOwner, "") - th.AssertEquals(t, n.MACAddress, "fa:16:3e:c9:cb:f0") - th.AssertDeepEquals(t, n.FixedIPs, []ports.IP{ + th.AssertEquals(t, "DOWN", n.Status) + th.AssertEquals(t, "private-port", n.Name) + th.AssertTrue(t, n.AdminStateUp) + th.AssertEquals(t, "a87cc70a-3e15-4acf-8205-9b711a3531b7", n.NetworkID) + th.AssertEquals(t, "d6700c0c9ffa4f1cb322cd4a1f3906fa", n.TenantID) + th.AssertEquals(t, "", n.DeviceOwner) + th.AssertEquals(t, "fa:16:3e:c9:cb:f0", n.MACAddress) + th.AssertDeepEquals(t, []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"}, - }) - th.AssertEquals(t, n.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") - th.AssertDeepEquals(t, n.AllowedAddressPairs, []ports.AddressPair{ + }, n.FixedIPs) + th.AssertEquals(t, "65c0ee9f-d634-4522-8954-51021b570b0d", n.ID) + th.AssertDeepEquals(t, []ports.AddressPair{ {IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"}, - }) + }, n.AllowedAddressPairs) } func TestCreateWithPropagateUplinkStatus(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -329,7 +329,7 @@ func TestCreateWithPropagateUplinkStatus(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreatePropagateUplinkStatusResponse) + fmt.Fprint(w, CreatePropagateUplinkStatusResponse) }) asu := true @@ -343,28 +343,28 @@ func TestCreateWithPropagateUplinkStatus(t *testing.T) { }, PropagateUplinkStatus: &propagateUplinkStatus, } - n, err := ports.Create(context.TODO(), fake.ServiceClient(), options).Extract() + n, err := ports.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.Status, "DOWN") - th.AssertEquals(t, n.Name, "private-port") - th.AssertEquals(t, n.AdminStateUp, true) - th.AssertEquals(t, n.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7") - th.AssertEquals(t, n.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa") - th.AssertEquals(t, n.DeviceOwner, "") - th.AssertEquals(t, n.MACAddress, "fa:16:3e:c9:cb:f0") - th.AssertDeepEquals(t, n.FixedIPs, []ports.IP{ + th.AssertEquals(t, "DOWN", n.Status) + th.AssertEquals(t, "private-port", n.Name) + th.AssertTrue(t, n.AdminStateUp) + th.AssertEquals(t, "a87cc70a-3e15-4acf-8205-9b711a3531b7", n.NetworkID) + th.AssertEquals(t, "d6700c0c9ffa4f1cb322cd4a1f3906fa", n.TenantID) + th.AssertEquals(t, "", n.DeviceOwner) + th.AssertEquals(t, "fa:16:3e:c9:cb:f0", n.MACAddress) + th.AssertDeepEquals(t, []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"}, - }) - th.AssertEquals(t, n.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") + }, n.FixedIPs) + th.AssertEquals(t, "65c0ee9f-d634-4522-8954-51021b570b0d", n.ID) th.AssertEquals(t, n.PropagateUplinkStatus, propagateUplinkStatus) } func TestCreateWithValueSpecs(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -374,7 +374,7 @@ func TestCreateWithValueSpecs(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateValueSpecResponse) + fmt.Fprint(w, CreateValueSpecResponse) }) asu := true @@ -393,31 +393,31 @@ func TestCreateWithValueSpecs(t *testing.T) { "test": "value", }, } - n, err := ports.Create(context.TODO(), fake.ServiceClient(), options).Extract() + n, err := ports.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.Status, "DOWN") - th.AssertEquals(t, n.Name, "private-port") - th.AssertEquals(t, n.AdminStateUp, true) - th.AssertEquals(t, n.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7") - th.AssertEquals(t, n.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa") - th.AssertEquals(t, n.DeviceOwner, "") - th.AssertEquals(t, n.MACAddress, "fa:16:3e:c9:cb:f0") - th.AssertDeepEquals(t, n.FixedIPs, []ports.IP{ + th.AssertEquals(t, "DOWN", n.Status) + th.AssertEquals(t, "private-port", n.Name) + th.AssertTrue(t, n.AdminStateUp) + th.AssertEquals(t, "a87cc70a-3e15-4acf-8205-9b711a3531b7", n.NetworkID) + th.AssertEquals(t, "d6700c0c9ffa4f1cb322cd4a1f3906fa", n.TenantID) + th.AssertEquals(t, "", n.DeviceOwner) + th.AssertEquals(t, "fa:16:3e:c9:cb:f0", n.MACAddress) + th.AssertDeepEquals(t, []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"}, - }) - th.AssertEquals(t, n.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") - th.AssertDeepEquals(t, n.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}) - th.AssertDeepEquals(t, n.AllowedAddressPairs, []ports.AddressPair{ + }, n.FixedIPs) + th.AssertEquals(t, "65c0ee9f-d634-4522-8954-51021b570b0d", n.ID) + th.AssertDeepEquals(t, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}, n.SecurityGroups) + th.AssertDeepEquals(t, []ports.AddressPair{ {IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"}, - }) + }, n.AllowedAddressPairs) } func TestCreateWithInvalidValueSpecs(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -427,7 +427,7 @@ func TestCreateWithInvalidValueSpecs(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateValueSpecResponse) + fmt.Fprint(w, CreateValueSpecResponse) }) asu := true @@ -449,7 +449,7 @@ func TestCreateWithInvalidValueSpecs(t *testing.T) { } // We expect an error here since we used a fobidden key in the value specs. - _, err := ports.Create(context.TODO(), fake.ServiceClient(), options).Extract() + _, err := ports.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertErr(t, err) options.ValueSpecs = &map[string]string{ @@ -458,22 +458,25 @@ func TestCreateWithInvalidValueSpecs(t *testing.T) { } // We expect an error here since the value specs would overwrite an existing field. - _, err = ports.Create(context.TODO(), fake.ServiceClient(), options).Extract() + _, err = ports.Create(context.TODO(), fake.ServiceClient(fakeServer), options).Extract() th.AssertErr(t, err) } func TestRequiredCreateOpts(t *testing.T) { - res := ports.Create(context.TODO(), fake.ServiceClient(), ports.CreateOpts{}) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + res := ports.Create(context.TODO(), fake.ServiceClient(fakeServer), ports.CreateOpts{}) if res.Err == nil { t.Fatalf("Expected error, got none") } } func TestCreatePortSecurity(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -483,7 +486,7 @@ func TestCreatePortSecurity(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreatePortSecurityResponse) + fmt.Fprint(w, CreatePortSecurityResponse) }) var portWithExt struct { @@ -510,18 +513,18 @@ func TestCreatePortSecurity(t *testing.T) { PortSecurityEnabled: &iFalse, } - err := ports.Create(context.TODO(), fake.ServiceClient(), createOpts).ExtractInto(&portWithExt) + err := ports.Create(context.TODO(), fake.ServiceClient(fakeServer), createOpts).ExtractInto(&portWithExt) th.AssertNoErr(t, err) - th.AssertEquals(t, portWithExt.Status, "DOWN") - th.AssertEquals(t, portWithExt.PortSecurityEnabled, false) + th.AssertEquals(t, "DOWN", portWithExt.Status) + th.AssertFalse(t, portWithExt.PortSecurityEnabled) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -531,10 +534,11 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateResponse) + fmt.Fprint(w, UpdateResponse) }) name := "new_port_name" + newMACAddress := "fa:16:3e:c9:cb:f4" options := ports.UpdateOpts{ Name: &name, FixedIPs: []ports.IP{ @@ -544,26 +548,27 @@ func TestUpdate(t *testing.T) { AllowedAddressPairs: &[]ports.AddressPair{ {IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"}, }, + MACAddress: &newMACAddress, } - s, err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract() + s, err := ports.Update(context.TODO(), fake.ServiceClient(fakeServer), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "new_port_name") - th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{ + th.AssertEquals(t, "new_port_name", s.Name) + th.AssertDeepEquals(t, []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"}, - }) - th.AssertDeepEquals(t, s.AllowedAddressPairs, []ports.AddressPair{ + }, s.FixedIPs) + th.AssertDeepEquals(t, []ports.AddressPair{ {IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"}, - }) - th.AssertDeepEquals(t, s.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}) + }, s.AllowedAddressPairs) + th.AssertDeepEquals(t, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}, s.SecurityGroups) } func TestUpdateOmitSecurityGroups(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -573,7 +578,7 @@ func TestUpdateOmitSecurityGroups(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateOmitSecurityGroupsResponse) + fmt.Fprint(w, UpdateOmitSecurityGroupsResponse) }) name := "new_port_name" @@ -587,24 +592,24 @@ func TestUpdateOmitSecurityGroups(t *testing.T) { }, } - s, err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract() + s, err := ports.Update(context.TODO(), fake.ServiceClient(fakeServer), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "new_port_name") - th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{ + th.AssertEquals(t, "new_port_name", s.Name) + th.AssertDeepEquals(t, []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"}, - }) - th.AssertDeepEquals(t, s.AllowedAddressPairs, []ports.AddressPair{ + }, s.FixedIPs) + th.AssertDeepEquals(t, []ports.AddressPair{ {IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"}, - }) - th.AssertDeepEquals(t, s.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}) + }, s.AllowedAddressPairs) + th.AssertDeepEquals(t, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}, s.SecurityGroups) } func TestUpdatePropagateUplinkStatus(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -614,7 +619,7 @@ func TestUpdatePropagateUplinkStatus(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdatePropagateUplinkStatusResponse) + fmt.Fprint(w, UpdatePropagateUplinkStatusResponse) }) propagateUplinkStatus := true @@ -622,17 +627,17 @@ func TestUpdatePropagateUplinkStatus(t *testing.T) { PropagateUplinkStatus: &propagateUplinkStatus, } - s, err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract() + s, err := ports.Update(context.TODO(), fake.ServiceClient(fakeServer), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, s.PropagateUplinkStatus, propagateUplinkStatus) } func TestUpdateValueSpecs(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -642,7 +647,7 @@ func TestUpdateValueSpecs(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateValueSpecsResponse) + fmt.Fprint(w, UpdateValueSpecsResponse) }) options := ports.UpdateOpts{ @@ -651,15 +656,15 @@ func TestUpdateValueSpecs(t *testing.T) { }, } - _, err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract() + _, err := ports.Update(context.TODO(), fake.ServiceClient(fakeServer), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract() th.AssertNoErr(t, err) } func TestUpdatePortSecurity(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -669,7 +674,7 @@ func TestUpdatePortSecurity(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdatePortSecurityResponse) + fmt.Fprint(w, UpdatePortSecurityResponse) }) var portWithExt struct { @@ -684,19 +689,19 @@ func TestUpdatePortSecurity(t *testing.T) { PortSecurityEnabled: &iFalse, } - err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&portWithExt) + err := ports.Update(context.TODO(), fake.ServiceClient(fakeServer), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&portWithExt) th.AssertNoErr(t, err) - th.AssertEquals(t, portWithExt.Status, "DOWN") - th.AssertEquals(t, portWithExt.Name, "private-port") - th.AssertEquals(t, portWithExt.PortSecurityEnabled, false) + th.AssertEquals(t, "DOWN", portWithExt.Status) + th.AssertEquals(t, "private-port", portWithExt.Name) + th.AssertFalse(t, portWithExt.PortSecurityEnabled) } func TestUpdateRevision(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -707,9 +712,9 @@ func TestUpdateRevision(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateResponse) + fmt.Fprint(w, UpdateResponse) }) - th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0e", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0e", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -720,10 +725,11 @@ func TestUpdateRevision(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateResponse) + fmt.Fprint(w, UpdateResponse) }) name := "new_port_name" + newMACAddress := "fa:16:3e:c9:cb:f4" options := ports.UpdateOpts{ Name: &name, FixedIPs: []ports.IP{ @@ -733,21 +739,22 @@ func TestUpdateRevision(t *testing.T) { AllowedAddressPairs: &[]ports.AddressPair{ {IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"}, }, + MACAddress: &newMACAddress, } - _, err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract() + _, err := ports.Update(context.TODO(), fake.ServiceClient(fakeServer), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract() th.AssertNoErr(t, err) revisionNumber := 42 options.RevisionNumber = &revisionNumber - _, err = ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0e", options).Extract() + _, err = ports.Update(context.TODO(), fake.ServiceClient(fakeServer), "65c0ee9f-d634-4522-8954-51021b570b0e", options).Extract() th.AssertNoErr(t, err) } func TestRemoveSecurityGroups(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -757,7 +764,7 @@ func TestRemoveSecurityGroups(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, RemoveSecurityGroupResponse) + fmt.Fprint(w, RemoveSecurityGroupResponse) }) name := "new_port_name" @@ -772,24 +779,24 @@ func TestRemoveSecurityGroups(t *testing.T) { }, } - s, err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract() + s, err := ports.Update(context.TODO(), fake.ServiceClient(fakeServer), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "new_port_name") - th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{ + th.AssertEquals(t, "new_port_name", s.Name) + th.AssertDeepEquals(t, []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"}, - }) - th.AssertDeepEquals(t, s.AllowedAddressPairs, []ports.AddressPair{ + }, s.FixedIPs) + th.AssertDeepEquals(t, []ports.AddressPair{ {IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"}, - }) + }, s.AllowedAddressPairs) th.AssertDeepEquals(t, s.SecurityGroups, []string(nil)) } func TestRemoveAllowedAddressPairs(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -799,7 +806,7 @@ func TestRemoveAllowedAddressPairs(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, RemoveAllowedAddressPairsResponse) + fmt.Fprint(w, RemoveAllowedAddressPairsResponse) }) name := "new_port_name" @@ -812,22 +819,22 @@ func TestRemoveAllowedAddressPairs(t *testing.T) { AllowedAddressPairs: &[]ports.AddressPair{}, } - s, err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract() + s, err := ports.Update(context.TODO(), fake.ServiceClient(fakeServer), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "new_port_name") - th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{ + th.AssertEquals(t, "new_port_name", s.Name) + th.AssertDeepEquals(t, []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"}, - }) + }, s.FixedIPs) th.AssertDeepEquals(t, s.AllowedAddressPairs, []ports.AddressPair(nil)) - th.AssertDeepEquals(t, s.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}) + th.AssertDeepEquals(t, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}, s.SecurityGroups) } func TestDontUpdateAllowedAddressPairs(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -837,7 +844,7 @@ func TestDontUpdateAllowedAddressPairs(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, DontUpdateAllowedAddressPairsResponse) + fmt.Fprint(w, DontUpdateAllowedAddressPairsResponse) }) name := "new_port_name" @@ -849,45 +856,45 @@ func TestDontUpdateAllowedAddressPairs(t *testing.T) { SecurityGroups: &[]string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}, } - s, err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract() + s, err := ports.Update(context.TODO(), fake.ServiceClient(fakeServer), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "new_port_name") - th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{ + th.AssertEquals(t, "new_port_name", s.Name) + th.AssertDeepEquals(t, []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"}, - }) - th.AssertDeepEquals(t, s.AllowedAddressPairs, []ports.AddressPair{ + }, s.FixedIPs) + th.AssertDeepEquals(t, []ports.AddressPair{ {IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"}, - }) - th.AssertDeepEquals(t, s.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}) + }, s.AllowedAddressPairs) + th.AssertDeepEquals(t, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"}, s.SecurityGroups) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := ports.Delete(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d") + res := ports.Delete(context.TODO(), fake.ServiceClient(fakeServer), "65c0ee9f-d634-4522-8954-51021b570b0d") th.AssertNoErr(t, res.Err) } func TestGetWithExtraDHCPOpts(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports/46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports/46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetWithExtraDHCPOptsResponse) + fmt.Fprint(w, GetWithExtraDHCPOptsResponse) }) var s struct { @@ -895,35 +902,35 @@ func TestGetWithExtraDHCPOpts(t *testing.T) { extradhcpopts.ExtraDHCPOptsExt } - err := ports.Get(context.TODO(), fake.ServiceClient(), "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2").ExtractInto(&s) + err := ports.Get(context.TODO(), fake.ServiceClient(fakeServer), "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2").ExtractInto(&s) th.AssertNoErr(t, err) - th.AssertEquals(t, s.Status, "ACTIVE") - th.AssertEquals(t, s.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7") - th.AssertEquals(t, s.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa") - th.AssertEquals(t, s.AdminStateUp, true) - th.AssertEquals(t, s.Name, "port-with-extra-dhcp-opts") - th.AssertEquals(t, s.DeviceOwner, "") - th.AssertEquals(t, s.MACAddress, "fa:16:3e:c9:cb:f0") - th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{ + th.AssertEquals(t, "ACTIVE", s.Status) + th.AssertEquals(t, "a87cc70a-3e15-4acf-8205-9b711a3531b7", s.NetworkID) + th.AssertEquals(t, "d6700c0c9ffa4f1cb322cd4a1f3906fa", s.TenantID) + th.AssertTrue(t, s.AdminStateUp) + th.AssertEquals(t, "port-with-extra-dhcp-opts", s.Name) + th.AssertEquals(t, "", s.DeviceOwner) + th.AssertEquals(t, "fa:16:3e:c9:cb:f0", s.MACAddress) + th.AssertDeepEquals(t, []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.4"}, - }) - th.AssertEquals(t, s.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") - th.AssertEquals(t, s.DeviceID, "") - - th.AssertDeepEquals(t, s.ExtraDHCPOpts[0].OptName, "option1") - th.AssertDeepEquals(t, s.ExtraDHCPOpts[0].OptValue, "value1") - th.AssertDeepEquals(t, s.ExtraDHCPOpts[0].IPVersion, 4) - th.AssertDeepEquals(t, s.ExtraDHCPOpts[1].OptName, "option2") - th.AssertDeepEquals(t, s.ExtraDHCPOpts[1].OptValue, "value2") - th.AssertDeepEquals(t, s.ExtraDHCPOpts[1].IPVersion, 4) + }, s.FixedIPs) + th.AssertEquals(t, "65c0ee9f-d634-4522-8954-51021b570b0d", s.ID) + th.AssertEquals(t, "", s.DeviceID) + + th.AssertDeepEquals(t, "option1", s.ExtraDHCPOpts[0].OptName) + th.AssertDeepEquals(t, "value1", s.ExtraDHCPOpts[0].OptValue) + th.AssertDeepEquals(t, 4, s.ExtraDHCPOpts[0].IPVersion) + th.AssertDeepEquals(t, "option2", s.ExtraDHCPOpts[1].OptName) + th.AssertDeepEquals(t, "value2", s.ExtraDHCPOpts[1].OptValue) + th.AssertDeepEquals(t, 4, s.ExtraDHCPOpts[1].IPVersion) } func TestCreateWithExtraDHCPOpts(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -933,7 +940,7 @@ func TestCreateWithExtraDHCPOpts(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateWithExtraDHCPOptsResponse) + fmt.Fprint(w, CreateWithExtraDHCPOptsResponse) }) adminStateUp := true @@ -961,32 +968,32 @@ func TestCreateWithExtraDHCPOpts(t *testing.T) { extradhcpopts.ExtraDHCPOptsExt } - err := ports.Create(context.TODO(), fake.ServiceClient(), createOpts).ExtractInto(&s) + err := ports.Create(context.TODO(), fake.ServiceClient(fakeServer), createOpts).ExtractInto(&s) th.AssertNoErr(t, err) - th.AssertEquals(t, s.Status, "DOWN") - th.AssertEquals(t, s.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7") - th.AssertEquals(t, s.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa") - th.AssertEquals(t, s.AdminStateUp, true) - th.AssertEquals(t, s.Name, "port-with-extra-dhcp-opts") - th.AssertEquals(t, s.DeviceOwner, "") - th.AssertEquals(t, s.MACAddress, "fa:16:3e:c9:cb:f0") - th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{ + th.AssertEquals(t, "DOWN", s.Status) + th.AssertEquals(t, "a87cc70a-3e15-4acf-8205-9b711a3531b7", s.NetworkID) + th.AssertEquals(t, "d6700c0c9ffa4f1cb322cd4a1f3906fa", s.TenantID) + th.AssertTrue(t, s.AdminStateUp) + th.AssertEquals(t, "port-with-extra-dhcp-opts", s.Name) + th.AssertEquals(t, "", s.DeviceOwner) + th.AssertEquals(t, "fa:16:3e:c9:cb:f0", s.MACAddress) + th.AssertDeepEquals(t, []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"}, - }) - th.AssertEquals(t, s.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") - th.AssertEquals(t, s.DeviceID, "") + }, s.FixedIPs) + th.AssertEquals(t, "65c0ee9f-d634-4522-8954-51021b570b0d", s.ID) + th.AssertEquals(t, "", s.DeviceID) - th.AssertDeepEquals(t, s.ExtraDHCPOpts[0].OptName, "option1") - th.AssertDeepEquals(t, s.ExtraDHCPOpts[0].OptValue, "value1") - th.AssertDeepEquals(t, s.ExtraDHCPOpts[0].IPVersion, 4) + th.AssertDeepEquals(t, "option1", s.ExtraDHCPOpts[0].OptName) + th.AssertDeepEquals(t, "value1", s.ExtraDHCPOpts[0].OptValue) + th.AssertDeepEquals(t, 4, s.ExtraDHCPOpts[0].IPVersion) } func TestUpdateWithExtraDHCPOpts(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -996,7 +1003,7 @@ func TestUpdateWithExtraDHCPOpts(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateWithExtraDHCPOptsResponse) + fmt.Fprint(w, UpdateWithExtraDHCPOptsResponse) }) name := "updated-port-with-dhcp-opts" @@ -1026,25 +1033,25 @@ func TestUpdateWithExtraDHCPOpts(t *testing.T) { extradhcpopts.ExtraDHCPOptsExt } - err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&s) + err := ports.Update(context.TODO(), fake.ServiceClient(fakeServer), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&s) th.AssertNoErr(t, err) - th.AssertEquals(t, s.Status, "DOWN") - th.AssertEquals(t, s.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7") - th.AssertEquals(t, s.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa") - th.AssertEquals(t, s.AdminStateUp, true) - th.AssertEquals(t, s.Name, "updated-port-with-dhcp-opts") - th.AssertEquals(t, s.DeviceOwner, "") - th.AssertEquals(t, s.MACAddress, "fa:16:3e:c9:cb:f0") - th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{ + th.AssertEquals(t, "DOWN", s.Status) + th.AssertEquals(t, "a87cc70a-3e15-4acf-8205-9b711a3531b7", s.NetworkID) + th.AssertEquals(t, "d6700c0c9ffa4f1cb322cd4a1f3906fa", s.TenantID) + th.AssertTrue(t, s.AdminStateUp) + th.AssertEquals(t, "updated-port-with-dhcp-opts", s.Name) + th.AssertEquals(t, "", s.DeviceOwner) + th.AssertEquals(t, "fa:16:3e:c9:cb:f0", s.MACAddress) + th.AssertDeepEquals(t, []ports.IP{ {SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"}, - }) - th.AssertEquals(t, s.ID, "65c0ee9f-d634-4522-8954-51021b570b0d") - th.AssertEquals(t, s.DeviceID, "") + }, s.FixedIPs) + th.AssertEquals(t, "65c0ee9f-d634-4522-8954-51021b570b0d", s.ID) + th.AssertEquals(t, "", s.DeviceID) - th.AssertDeepEquals(t, s.ExtraDHCPOpts[0].OptName, "option2") - th.AssertDeepEquals(t, s.ExtraDHCPOpts[0].OptValue, "value2") - th.AssertDeepEquals(t, s.ExtraDHCPOpts[0].IPVersion, 4) + th.AssertDeepEquals(t, "option2", s.ExtraDHCPOpts[0].OptName) + th.AssertDeepEquals(t, "value2", s.ExtraDHCPOpts[0].OptValue) + th.AssertDeepEquals(t, 4, s.ExtraDHCPOpts[0].IPVersion) } func TestPortsListOpts(t *testing.T) { diff --git a/openstack/networking/v2/subnets/requests.go b/openstack/networking/v2/subnets/requests.go index db597d6864..85c5d2b402 100644 --- a/openstack/networking/v2/subnets/requests.go +++ b/openstack/networking/v2/subnets/requests.go @@ -42,6 +42,8 @@ type ListOpts struct { TagsAny string `q:"tags-any"` NotTags string `q:"not-tags"` NotTagsAny string `q:"not-tags-any"` + RevisionNumber *int `q:"revision_number"` + SegmentID string `q:"segment_id"` } // ToSubnetListQuery formats a ListOpts into a query string. @@ -146,6 +148,10 @@ type CreateOpts struct { // Prefixlen is used when user creates a subnet from the subnetpool. It will // overwrite the "default_prefixlen" value of the referenced subnetpool. Prefixlen int `json:"prefixlen,omitempty"` + + // SegmentID is a network segment the subnet is associated with. It is + // available when segment extension is enabled. + SegmentID string `json:"segment_id,omitempty"` } // ToSubnetCreateMap builds a request body from CreateOpts. @@ -193,9 +199,8 @@ type UpdateOpts struct { // AllocationPools are IP Address pools that will be available for DHCP. AllocationPools []AllocationPool `json:"allocation_pools,omitempty"` - // GatewayIP sets gateway information for the subnet. Setting to nil will - // cause a default gateway to automatically be created. Setting to an empty - // string will cause the subnet to be created with no gateway. Setting to + // GatewayIP sets gateway information for the subnet. Setting to an empty + // string will cause the subnet to not have a gateway. Setting to // an explicit address will set that address as the gateway. GatewayIP *string `json:"gateway_ip,omitempty"` @@ -218,6 +223,10 @@ type UpdateOpts struct { // will set revision_number=%s. If the revision number does not match, the // update will fail. RevisionNumber *int `json:"-" h:"If-Match"` + + // SegmentID is a network segment the subnet is associated with. It is + // available when segment extension is enabled. + SegmentID *string `json:"segment_id,omitempty"` } // ToSubnetUpdateMap builds a request body from UpdateOpts. diff --git a/openstack/networking/v2/subnets/results.go b/openstack/networking/v2/subnets/results.go index 7d5ba13cc5..cb5831e9e6 100644 --- a/openstack/networking/v2/subnets/results.go +++ b/openstack/networking/v2/subnets/results.go @@ -1,6 +1,9 @@ package subnets import ( + "encoding/json" + "time" + "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/pagination" ) @@ -121,6 +124,54 @@ type Subnet struct { // RevisionNumber optionally set via extensions/standard-attr-revisions RevisionNumber int `json:"revision_number"` + + // SegmentID of a network segment the subnet is associated with. It is + // available when segment extension is enabled. + SegmentID string `json:"segment_id"` + + // Timestamp when the subnet was created + CreatedAt time.Time `json:"-"` + + // Timestamp when the subnet was last updated + UpdatedAt time.Time `json:"-"` +} + +func (r *Subnet) UnmarshalJSON(b []byte) error { + type tmp Subnet + + // Support for older neutron time format + var s1 struct { + tmp + CreatedAt gophercloud.JSONRFC3339NoZ `json:"created_at"` + UpdatedAt gophercloud.JSONRFC3339NoZ `json:"updated_at"` + } + + err := json.Unmarshal(b, &s1) + if err == nil { + *r = Subnet(s1.tmp) + r.CreatedAt = time.Time(s1.CreatedAt) + r.UpdatedAt = time.Time(s1.UpdatedAt) + + return nil + } + + // Support for newer neutron time format + var s2 struct { + tmp + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + } + + err = json.Unmarshal(b, &s2) + if err != nil { + return err + } + + *r = Subnet(s2.tmp) + r.CreatedAt = time.Time(s2.CreatedAt) + r.UpdatedAt = time.Time(s2.UpdatedAt) + + return nil } // SubnetPage is the page returned by a pager when traversing over a collection @@ -132,7 +183,7 @@ type SubnetPage struct { // NextPageURL is invoked when a paginated collection of subnets has reached // the end of a page and the pager seeks to traverse over a new one. In order // to do this, it needs to construct the next page's URL. -func (r SubnetPage) NextPageURL() (string, error) { +func (r SubnetPage) NextPageURL(endpointURL string) (string, error) { var s struct { Links []gophercloud.Link `json:"subnets_links"` } diff --git a/openstack/networking/v2/subnets/testing/fixtures_test.go b/openstack/networking/v2/subnets/testing/fixtures_test.go index a44f1cc77e..ebb005f62d 100644 --- a/openstack/networking/v2/subnets/testing/fixtures_test.go +++ b/openstack/networking/v2/subnets/testing/fixtures_test.go @@ -1,6 +1,8 @@ package testing import ( + "time" + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/subnets" ) @@ -24,6 +26,8 @@ const SubnetListResult = ` "ip_version": 4, "gateway_ip": "10.0.0.1", "cidr": "10.0.0.0/24", + "created_at": "2017-12-28T07:21:40Z", + "updated_at": "2017-12-28T07:21:40Z", "id": "08eae331-0402-425a-923c-34f7cfe39c1b" }, { @@ -43,6 +47,8 @@ const SubnetListResult = ` "ip_version": 4, "gateway_ip": "192.0.0.1", "cidr": "192.0.0.0/8", + "created_at": "2017-12-28T07:21:40", + "updated_at": "2017-12-28T07:21:40", "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0b" }, { @@ -104,6 +110,8 @@ var Subnet1 = subnets.Subnet{ IPVersion: 4, GatewayIP: "10.0.0.1", CIDR: "10.0.0.0/24", + CreatedAt: time.Date(2017, 12, 28, 07, 21, 40, 0, time.UTC), + UpdatedAt: time.Date(2017, 12, 28, 07, 21, 40, 0, time.UTC), ID: "08eae331-0402-425a-923c-34f7cfe39c1b", } @@ -125,6 +133,8 @@ var Subnet2 = subnets.Subnet{ IPVersion: 4, GatewayIP: "192.0.0.1", CIDR: "192.0.0.0/8", + CreatedAt: time.Date(2017, 12, 28, 07, 21, 40, 0, time.UTC), + UpdatedAt: time.Date(2017, 12, 28, 07, 21, 40, 0, time.UTC), ID: "54d6f61d-db07-451c-9ab3-b9609b6b6f0b", } diff --git a/openstack/networking/v2/subnets/testing/requests_test.go b/openstack/networking/v2/subnets/testing/requests_test.go index 1df6fd1ce3..66d3ce7570 100644 --- a/openstack/networking/v2/subnets/testing/requests_test.go +++ b/openstack/networking/v2/subnets/testing/requests_test.go @@ -13,22 +13,22 @@ import ( ) func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/subnets", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/subnets", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, SubnetListResult) + fmt.Fprint(w, SubnetListResult) }) count := 0 - err := subnets.List(fake.ServiceClient(), subnets.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := subnets.List(fake.ServiceClient(fakeServer), subnets.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := subnets.ExtractSubnets(page) if err != nil { @@ -55,46 +55,46 @@ func TestList(t *testing.T) { } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/subnets/54d6f61d-db07-451c-9ab3-b9609b6b6f0b", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/subnets/54d6f61d-db07-451c-9ab3-b9609b6b6f0b", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, SubnetGetResult) + fmt.Fprint(w, SubnetGetResult) }) - s, err := subnets.Get(context.TODO(), fake.ServiceClient(), "54d6f61d-db07-451c-9ab3-b9609b6b6f0b").Extract() + s, err := subnets.Get(context.TODO(), fake.ServiceClient(fakeServer), "54d6f61d-db07-451c-9ab3-b9609b6b6f0b").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "my_subnet") - th.AssertEquals(t, s.EnableDHCP, true) - th.AssertEquals(t, s.NetworkID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") - th.AssertEquals(t, s.TenantID, "4fd44f30292945e481c7b8a0c8908869") - th.AssertDeepEquals(t, s.DNSNameservers, []string{}) - th.AssertDeepEquals(t, s.AllocationPools, []subnets.AllocationPool{ + th.AssertEquals(t, "my_subnet", s.Name) + th.AssertTrue(t, s.EnableDHCP) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", s.NetworkID) + th.AssertEquals(t, "4fd44f30292945e481c7b8a0c8908869", s.TenantID) + th.AssertDeepEquals(t, []string{}, s.DNSNameservers) + th.AssertDeepEquals(t, []subnets.AllocationPool{ { Start: "192.0.0.2", End: "192.255.255.254", }, - }) - th.AssertDeepEquals(t, s.HostRoutes, []subnets.HostRoute{}) - th.AssertEquals(t, s.IPVersion, 4) - th.AssertEquals(t, s.GatewayIP, "192.0.0.1") - th.AssertEquals(t, s.CIDR, "192.0.0.0/8") - th.AssertEquals(t, s.ID, "54d6f61d-db07-451c-9ab3-b9609b6b6f0b") - th.AssertEquals(t, s.SubnetPoolID, "b80340c7-9960-4f67-a99c-02501656284b") + }, s.AllocationPools) + th.AssertDeepEquals(t, []subnets.HostRoute{}, s.HostRoutes) + th.AssertEquals(t, 4, s.IPVersion) + th.AssertEquals(t, "192.0.0.1", s.GatewayIP) + th.AssertEquals(t, "192.0.0.0/8", s.CIDR) + th.AssertEquals(t, "54d6f61d-db07-451c-9ab3-b9609b6b6f0b", s.ID) + th.AssertEquals(t, "b80340c7-9960-4f67-a99c-02501656284b", s.SubnetPoolID) } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/subnets", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/subnets", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -104,7 +104,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, SubnetCreateResult) + fmt.Fprint(w, SubnetCreateResult) }) var gatewayIP = "192.168.199.1" @@ -128,35 +128,35 @@ func TestCreate(t *testing.T) { }, SubnetPoolID: "b80340c7-9960-4f67-a99c-02501656284b", } - s, err := subnets.Create(context.TODO(), fake.ServiceClient(), opts).Extract() + s, err := subnets.Create(context.TODO(), fake.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "") - th.AssertEquals(t, s.DNSPublishFixedIP, true) - th.AssertEquals(t, s.EnableDHCP, true) - th.AssertEquals(t, s.NetworkID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") - th.AssertEquals(t, s.TenantID, "4fd44f30292945e481c7b8a0c8908869") - th.AssertDeepEquals(t, s.DNSNameservers, []string{"foo"}) - th.AssertDeepEquals(t, s.ServiceTypes, []string{"network:routed"}) - th.AssertDeepEquals(t, s.AllocationPools, []subnets.AllocationPool{ + th.AssertEquals(t, "", s.Name) + th.AssertTrue(t, s.DNSPublishFixedIP) + th.AssertTrue(t, s.EnableDHCP) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", s.NetworkID) + th.AssertEquals(t, "4fd44f30292945e481c7b8a0c8908869", s.TenantID) + th.AssertDeepEquals(t, []string{"foo"}, s.DNSNameservers) + th.AssertDeepEquals(t, []string{"network:routed"}, s.ServiceTypes) + th.AssertDeepEquals(t, []subnets.AllocationPool{ { Start: "192.168.199.2", End: "192.168.199.254", }, - }) - th.AssertDeepEquals(t, s.HostRoutes, []subnets.HostRoute{}) - th.AssertEquals(t, s.IPVersion, 4) - th.AssertEquals(t, s.GatewayIP, "192.168.199.1") - th.AssertEquals(t, s.CIDR, "192.168.199.0/24") - th.AssertEquals(t, s.ID, "3b80198d-4f7b-4f77-9ef5-774d54e17126") - th.AssertEquals(t, s.SubnetPoolID, "b80340c7-9960-4f67-a99c-02501656284b") + }, s.AllocationPools) + th.AssertDeepEquals(t, []subnets.HostRoute{}, s.HostRoutes) + th.AssertEquals(t, 4, s.IPVersion) + th.AssertEquals(t, "192.168.199.1", s.GatewayIP) + th.AssertEquals(t, "192.168.199.0/24", s.CIDR) + th.AssertEquals(t, "3b80198d-4f7b-4f77-9ef5-774d54e17126", s.ID) + th.AssertEquals(t, "b80340c7-9960-4f67-a99c-02501656284b", s.SubnetPoolID) } func TestCreateNoGateway(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/subnets", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/subnets", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -166,7 +166,7 @@ func TestCreateNoGateway(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, SubnetCreateWithNoGatewayResponse) + fmt.Fprint(w, SubnetCreateWithNoGatewayResponse) }) var noGateway = "" @@ -183,31 +183,31 @@ func TestCreateNoGateway(t *testing.T) { }, DNSNameservers: []string{}, } - s, err := subnets.Create(context.TODO(), fake.ServiceClient(), opts).Extract() + s, err := subnets.Create(context.TODO(), fake.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "") - th.AssertEquals(t, s.EnableDHCP, true) - th.AssertEquals(t, s.NetworkID, "d32019d3-bc6e-4319-9c1d-6722fc136a23") - th.AssertEquals(t, s.TenantID, "4fd44f30292945e481c7b8a0c8908869") - th.AssertDeepEquals(t, s.AllocationPools, []subnets.AllocationPool{ + th.AssertEquals(t, "", s.Name) + th.AssertTrue(t, s.EnableDHCP) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a23", s.NetworkID) + th.AssertEquals(t, "4fd44f30292945e481c7b8a0c8908869", s.TenantID) + th.AssertDeepEquals(t, []subnets.AllocationPool{ { Start: "192.168.1.2", End: "192.168.1.254", }, - }) - th.AssertDeepEquals(t, s.HostRoutes, []subnets.HostRoute{}) - th.AssertEquals(t, s.IPVersion, 4) - th.AssertEquals(t, s.GatewayIP, "") - th.AssertEquals(t, s.CIDR, "192.168.1.0/24") - th.AssertEquals(t, s.ID, "54d6f61d-db07-451c-9ab3-b9609b6b6f0c") + }, s.AllocationPools) + th.AssertDeepEquals(t, []subnets.HostRoute{}, s.HostRoutes) + th.AssertEquals(t, 4, s.IPVersion) + th.AssertEquals(t, "", s.GatewayIP) + th.AssertEquals(t, "192.168.1.0/24", s.CIDR) + th.AssertEquals(t, "54d6f61d-db07-451c-9ab3-b9609b6b6f0c", s.ID) } func TestCreateDefaultGateway(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/subnets", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/subnets", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -217,7 +217,7 @@ func TestCreateDefaultGateway(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, SubnetCreateWithDefaultGatewayResponse) + fmt.Fprint(w, SubnetCreateWithDefaultGatewayResponse) }) opts := subnets.CreateOpts{ @@ -232,31 +232,31 @@ func TestCreateDefaultGateway(t *testing.T) { }, DNSNameservers: []string{}, } - s, err := subnets.Create(context.TODO(), fake.ServiceClient(), opts).Extract() + s, err := subnets.Create(context.TODO(), fake.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "") - th.AssertEquals(t, s.EnableDHCP, true) - th.AssertEquals(t, s.NetworkID, "d32019d3-bc6e-4319-9c1d-6722fc136a23") - th.AssertEquals(t, s.TenantID, "4fd44f30292945e481c7b8a0c8908869") - th.AssertDeepEquals(t, s.AllocationPools, []subnets.AllocationPool{ + th.AssertEquals(t, "", s.Name) + th.AssertTrue(t, s.EnableDHCP) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a23", s.NetworkID) + th.AssertEquals(t, "4fd44f30292945e481c7b8a0c8908869", s.TenantID) + th.AssertDeepEquals(t, []subnets.AllocationPool{ { Start: "192.168.1.2", End: "192.168.1.254", }, - }) - th.AssertDeepEquals(t, s.HostRoutes, []subnets.HostRoute{}) - th.AssertEquals(t, s.IPVersion, 4) - th.AssertEquals(t, s.GatewayIP, "192.168.1.1") - th.AssertEquals(t, s.CIDR, "192.168.1.0/24") - th.AssertEquals(t, s.ID, "54d6f61d-db07-451c-9ab3-b9609b6b6f0c") + }, s.AllocationPools) + th.AssertDeepEquals(t, []subnets.HostRoute{}, s.HostRoutes) + th.AssertEquals(t, 4, s.IPVersion) + th.AssertEquals(t, "192.168.1.1", s.GatewayIP) + th.AssertEquals(t, "192.168.1.0/24", s.CIDR) + th.AssertEquals(t, "54d6f61d-db07-451c-9ab3-b9609b6b6f0c", s.ID) } func TestCreateIPv6RaAddressMode(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/subnets", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/subnets", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -266,7 +266,7 @@ func TestCreateIPv6RaAddressMode(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, SubnetCreateWithIPv6RaAddressModeResponse) + fmt.Fprint(w, SubnetCreateWithIPv6RaAddressModeResponse) }) var gatewayIP = "2001:db8:0:a::1" @@ -278,26 +278,26 @@ func TestCreateIPv6RaAddressMode(t *testing.T) { IPv6AddressMode: "slaac", IPv6RAMode: "slaac", } - s, err := subnets.Create(context.TODO(), fake.ServiceClient(), opts).Extract() + s, err := subnets.Create(context.TODO(), fake.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "") - th.AssertEquals(t, s.EnableDHCP, true) - th.AssertEquals(t, s.NetworkID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") - th.AssertEquals(t, s.TenantID, "4fd44f30292945e481c7b8a0c8908869") - th.AssertEquals(t, s.IPVersion, 6) - th.AssertEquals(t, s.GatewayIP, "2001:db8:0:a::1") - th.AssertEquals(t, s.CIDR, "2001:db8:0:a:0:0:0:0/64") - th.AssertEquals(t, s.ID, "3b80198d-4f7b-4f77-9ef5-774d54e17126") - th.AssertEquals(t, s.IPv6AddressMode, "slaac") - th.AssertEquals(t, s.IPv6RAMode, "slaac") + th.AssertEquals(t, "", s.Name) + th.AssertTrue(t, s.EnableDHCP) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", s.NetworkID) + th.AssertEquals(t, "4fd44f30292945e481c7b8a0c8908869", s.TenantID) + th.AssertEquals(t, 6, s.IPVersion) + th.AssertEquals(t, "2001:db8:0:a::1", s.GatewayIP) + th.AssertEquals(t, "2001:db8:0:a:0:0:0:0/64", s.CIDR) + th.AssertEquals(t, "3b80198d-4f7b-4f77-9ef5-774d54e17126", s.ID) + th.AssertEquals(t, "slaac", s.IPv6AddressMode) + th.AssertEquals(t, "slaac", s.IPv6RAMode) } func TestCreateWithNoCIDR(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/subnets", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/subnets", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -307,7 +307,7 @@ func TestCreateWithNoCIDR(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, SubnetCreateResult) + fmt.Fprint(w, SubnetCreateResult) }) opts := subnets.CreateOpts{ @@ -319,34 +319,34 @@ func TestCreateWithNoCIDR(t *testing.T) { }, SubnetPoolID: "b80340c7-9960-4f67-a99c-02501656284b", } - s, err := subnets.Create(context.TODO(), fake.ServiceClient(), opts).Extract() + s, err := subnets.Create(context.TODO(), fake.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "") - th.AssertEquals(t, s.DNSPublishFixedIP, true) - th.AssertEquals(t, s.EnableDHCP, true) - th.AssertEquals(t, s.NetworkID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") - th.AssertEquals(t, s.TenantID, "4fd44f30292945e481c7b8a0c8908869") - th.AssertDeepEquals(t, s.DNSNameservers, []string{"foo"}) - th.AssertDeepEquals(t, s.AllocationPools, []subnets.AllocationPool{ + th.AssertEquals(t, "", s.Name) + th.AssertTrue(t, s.DNSPublishFixedIP) + th.AssertTrue(t, s.EnableDHCP) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", s.NetworkID) + th.AssertEquals(t, "4fd44f30292945e481c7b8a0c8908869", s.TenantID) + th.AssertDeepEquals(t, []string{"foo"}, s.DNSNameservers) + th.AssertDeepEquals(t, []subnets.AllocationPool{ { Start: "192.168.199.2", End: "192.168.199.254", }, - }) - th.AssertDeepEquals(t, s.HostRoutes, []subnets.HostRoute{}) - th.AssertEquals(t, s.IPVersion, 4) - th.AssertEquals(t, s.GatewayIP, "192.168.199.1") - th.AssertEquals(t, s.CIDR, "192.168.199.0/24") - th.AssertEquals(t, s.ID, "3b80198d-4f7b-4f77-9ef5-774d54e17126") - th.AssertEquals(t, s.SubnetPoolID, "b80340c7-9960-4f67-a99c-02501656284b") + }, s.AllocationPools) + th.AssertDeepEquals(t, []subnets.HostRoute{}, s.HostRoutes) + th.AssertEquals(t, 4, s.IPVersion) + th.AssertEquals(t, "192.168.199.1", s.GatewayIP) + th.AssertEquals(t, "192.168.199.0/24", s.CIDR) + th.AssertEquals(t, "3b80198d-4f7b-4f77-9ef5-774d54e17126", s.ID) + th.AssertEquals(t, "b80340c7-9960-4f67-a99c-02501656284b", s.SubnetPoolID) } func TestCreateWithPrefixlen(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/subnets", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/subnets", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -356,7 +356,7 @@ func TestCreateWithPrefixlen(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, SubnetCreateResult) + fmt.Fprint(w, SubnetCreateResult) }) opts := subnets.CreateOpts{ @@ -369,51 +369,54 @@ func TestCreateWithPrefixlen(t *testing.T) { SubnetPoolID: "b80340c7-9960-4f67-a99c-02501656284b", Prefixlen: 12, } - s, err := subnets.Create(context.TODO(), fake.ServiceClient(), opts).Extract() + s, err := subnets.Create(context.TODO(), fake.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "") - th.AssertEquals(t, s.DNSPublishFixedIP, true) - th.AssertEquals(t, s.EnableDHCP, true) - th.AssertEquals(t, s.NetworkID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") - th.AssertEquals(t, s.TenantID, "4fd44f30292945e481c7b8a0c8908869") - th.AssertDeepEquals(t, s.DNSNameservers, []string{"foo"}) - th.AssertDeepEquals(t, s.AllocationPools, []subnets.AllocationPool{ + th.AssertEquals(t, "", s.Name) + th.AssertTrue(t, s.DNSPublishFixedIP) + th.AssertTrue(t, s.EnableDHCP) + th.AssertEquals(t, "d32019d3-bc6e-4319-9c1d-6722fc136a22", s.NetworkID) + th.AssertEquals(t, "4fd44f30292945e481c7b8a0c8908869", s.TenantID) + th.AssertDeepEquals(t, []string{"foo"}, s.DNSNameservers) + th.AssertDeepEquals(t, []subnets.AllocationPool{ { Start: "192.168.199.2", End: "192.168.199.254", }, - }) - th.AssertDeepEquals(t, s.HostRoutes, []subnets.HostRoute{}) - th.AssertEquals(t, s.IPVersion, 4) - th.AssertEquals(t, s.GatewayIP, "192.168.199.1") - th.AssertEquals(t, s.CIDR, "192.168.199.0/24") - th.AssertEquals(t, s.ID, "3b80198d-4f7b-4f77-9ef5-774d54e17126") - th.AssertEquals(t, s.SubnetPoolID, "b80340c7-9960-4f67-a99c-02501656284b") + }, s.AllocationPools) + th.AssertDeepEquals(t, []subnets.HostRoute{}, s.HostRoutes) + th.AssertEquals(t, 4, s.IPVersion) + th.AssertEquals(t, "192.168.199.1", s.GatewayIP) + th.AssertEquals(t, "192.168.199.0/24", s.CIDR) + th.AssertEquals(t, "3b80198d-4f7b-4f77-9ef5-774d54e17126", s.ID) + th.AssertEquals(t, "b80340c7-9960-4f67-a99c-02501656284b", s.SubnetPoolID) } func TestRequiredCreateOpts(t *testing.T) { - res := subnets.Create(context.TODO(), fake.ServiceClient(), subnets.CreateOpts{}) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + res := subnets.Create(context.TODO(), fake.ServiceClient(fakeServer), subnets.CreateOpts{}) if res.Err == nil { t.Fatalf("Expected error, got none") } - res = subnets.Create(context.TODO(), fake.ServiceClient(), subnets.CreateOpts{NetworkID: "foo"}) + res = subnets.Create(context.TODO(), fake.ServiceClient(fakeServer), subnets.CreateOpts{NetworkID: "foo"}) if res.Err == nil { t.Fatalf("Expected error, got none") } - res = subnets.Create(context.TODO(), fake.ServiceClient(), subnets.CreateOpts{NetworkID: "foo", CIDR: "bar", IPVersion: 40}) + res = subnets.Create(context.TODO(), fake.ServiceClient(fakeServer), subnets.CreateOpts{NetworkID: "foo", CIDR: "bar", IPVersion: 40}) if res.Err == nil { t.Fatalf("Expected error, got none") } } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/subnets/08eae331-0402-425a-923c-34f7cfe39c1b", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/subnets/08eae331-0402-425a-923c-34f7cfe39c1b", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -423,7 +426,7 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, SubnetUpdateResponse) + fmt.Fprint(w, SubnetUpdateResponse) }) dnsNameservers := []string{"foo"} @@ -435,18 +438,18 @@ func TestUpdate(t *testing.T) { {NextHop: "bar"}, }, } - s, err := subnets.Update(context.TODO(), fake.ServiceClient(), "08eae331-0402-425a-923c-34f7cfe39c1b", opts).Extract() + s, err := subnets.Update(context.TODO(), fake.ServiceClient(fakeServer), "08eae331-0402-425a-923c-34f7cfe39c1b", opts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "my_new_subnet") - th.AssertEquals(t, s.ID, "08eae331-0402-425a-923c-34f7cfe39c1b") + th.AssertEquals(t, "my_new_subnet", s.Name) + th.AssertEquals(t, "08eae331-0402-425a-923c-34f7cfe39c1b", s.ID) } func TestUpdateGateway(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/subnets/08eae331-0402-425a-923c-34f7cfe39c1b", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/subnets/08eae331-0402-425a-923c-34f7cfe39c1b", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -456,7 +459,7 @@ func TestUpdateGateway(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, SubnetUpdateGatewayResponse) + fmt.Fprint(w, SubnetUpdateGatewayResponse) }) var gatewayIP = "10.0.0.1" @@ -465,19 +468,19 @@ func TestUpdateGateway(t *testing.T) { Name: &name, GatewayIP: &gatewayIP, } - s, err := subnets.Update(context.TODO(), fake.ServiceClient(), "08eae331-0402-425a-923c-34f7cfe39c1b", opts).Extract() + s, err := subnets.Update(context.TODO(), fake.ServiceClient(fakeServer), "08eae331-0402-425a-923c-34f7cfe39c1b", opts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "my_new_subnet") - th.AssertEquals(t, s.ID, "08eae331-0402-425a-923c-34f7cfe39c1b") - th.AssertEquals(t, s.GatewayIP, "10.0.0.1") + th.AssertEquals(t, "my_new_subnet", s.Name) + th.AssertEquals(t, "08eae331-0402-425a-923c-34f7cfe39c1b", s.ID) + th.AssertEquals(t, "10.0.0.1", s.GatewayIP) } func TestUpdateRemoveGateway(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/subnets/08eae331-0402-425a-923c-34f7cfe39c1b", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/subnets/08eae331-0402-425a-923c-34f7cfe39c1b", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -487,7 +490,7 @@ func TestUpdateRemoveGateway(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, SubnetUpdateRemoveGatewayResponse) + fmt.Fprint(w, SubnetUpdateRemoveGatewayResponse) }) var noGateway = "" @@ -496,19 +499,19 @@ func TestUpdateRemoveGateway(t *testing.T) { Name: &name, GatewayIP: &noGateway, } - s, err := subnets.Update(context.TODO(), fake.ServiceClient(), "08eae331-0402-425a-923c-34f7cfe39c1b", opts).Extract() + s, err := subnets.Update(context.TODO(), fake.ServiceClient(fakeServer), "08eae331-0402-425a-923c-34f7cfe39c1b", opts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "my_new_subnet") - th.AssertEquals(t, s.ID, "08eae331-0402-425a-923c-34f7cfe39c1b") - th.AssertEquals(t, s.GatewayIP, "") + th.AssertEquals(t, "my_new_subnet", s.Name) + th.AssertEquals(t, "08eae331-0402-425a-923c-34f7cfe39c1b", s.ID) + th.AssertEquals(t, "", s.GatewayIP) } func TestUpdateHostRoutes(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/subnets/08eae331-0402-425a-923c-34f7cfe39c1b", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/subnets/08eae331-0402-425a-923c-34f7cfe39c1b", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -518,7 +521,7 @@ func TestUpdateHostRoutes(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, SubnetUpdateHostRoutesResponse) + fmt.Fprint(w, SubnetUpdateHostRoutesResponse) }) HostRoutes := []subnets.HostRoute{ @@ -533,19 +536,19 @@ func TestUpdateHostRoutes(t *testing.T) { Name: &name, HostRoutes: &HostRoutes, } - s, err := subnets.Update(context.TODO(), fake.ServiceClient(), "08eae331-0402-425a-923c-34f7cfe39c1b", opts).Extract() + s, err := subnets.Update(context.TODO(), fake.ServiceClient(fakeServer), "08eae331-0402-425a-923c-34f7cfe39c1b", opts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "my_new_subnet") - th.AssertEquals(t, s.ID, "08eae331-0402-425a-923c-34f7cfe39c1b") + th.AssertEquals(t, "my_new_subnet", s.Name) + th.AssertEquals(t, "08eae331-0402-425a-923c-34f7cfe39c1b", s.ID) th.AssertDeepEquals(t, s.HostRoutes, HostRoutes) } func TestUpdateRemoveHostRoutes(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/subnets/08eae331-0402-425a-923c-34f7cfe39c1b", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/subnets/08eae331-0402-425a-923c-34f7cfe39c1b", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -555,26 +558,26 @@ func TestUpdateRemoveHostRoutes(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, SubnetUpdateRemoveHostRoutesResponse) + fmt.Fprint(w, SubnetUpdateRemoveHostRoutesResponse) }) noHostRoutes := []subnets.HostRoute{} opts := subnets.UpdateOpts{ HostRoutes: &noHostRoutes, } - s, err := subnets.Update(context.TODO(), fake.ServiceClient(), "08eae331-0402-425a-923c-34f7cfe39c1b", opts).Extract() + s, err := subnets.Update(context.TODO(), fake.ServiceClient(fakeServer), "08eae331-0402-425a-923c-34f7cfe39c1b", opts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "my_new_subnet") - th.AssertEquals(t, s.ID, "08eae331-0402-425a-923c-34f7cfe39c1b") + th.AssertEquals(t, "my_new_subnet", s.Name) + th.AssertEquals(t, "08eae331-0402-425a-923c-34f7cfe39c1b", s.ID) th.AssertDeepEquals(t, s.HostRoutes, noHostRoutes) } func TestUpdateAllocationPool(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/subnets/08eae331-0402-425a-923c-34f7cfe39c1b", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/subnets/08eae331-0402-425a-923c-34f7cfe39c1b", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -584,7 +587,7 @@ func TestUpdateAllocationPool(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, SubnetUpdateAllocationPoolResponse) + fmt.Fprint(w, SubnetUpdateAllocationPoolResponse) }) name := "my_new_subnet" @@ -597,24 +600,24 @@ func TestUpdateAllocationPool(t *testing.T) { }, }, } - s, err := subnets.Update(context.TODO(), fake.ServiceClient(), "08eae331-0402-425a-923c-34f7cfe39c1b", opts).Extract() + s, err := subnets.Update(context.TODO(), fake.ServiceClient(fakeServer), "08eae331-0402-425a-923c-34f7cfe39c1b", opts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "my_new_subnet") - th.AssertEquals(t, s.ID, "08eae331-0402-425a-923c-34f7cfe39c1b") - th.AssertDeepEquals(t, s.AllocationPools, []subnets.AllocationPool{ + th.AssertEquals(t, "my_new_subnet", s.Name) + th.AssertEquals(t, "08eae331-0402-425a-923c-34f7cfe39c1b", s.ID) + th.AssertDeepEquals(t, []subnets.AllocationPool{ { Start: "10.1.0.2", End: "10.1.0.254", }, - }) + }, s.AllocationPools) } func TestUpdateRevision(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/subnets/08eae331-0402-425a-923c-34f7cfe39c1b", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/subnets/08eae331-0402-425a-923c-34f7cfe39c1b", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -625,10 +628,10 @@ func TestUpdateRevision(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, SubnetUpdateResponse) + fmt.Fprint(w, SubnetUpdateResponse) }) - th.Mux.HandleFunc("/v2.0/subnets/08eae331-0402-425a-923c-34f7cfe39c1c", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/subnets/08eae331-0402-425a-923c-34f7cfe39c1c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") @@ -639,7 +642,7 @@ func TestUpdateRevision(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, SubnetUpdateResponse) + fmt.Fprint(w, SubnetUpdateResponse) }) dnsNameservers := []string{"foo"} @@ -651,25 +654,25 @@ func TestUpdateRevision(t *testing.T) { {NextHop: "bar"}, }, } - _, err := subnets.Update(context.TODO(), fake.ServiceClient(), "08eae331-0402-425a-923c-34f7cfe39c1b", opts).Extract() + _, err := subnets.Update(context.TODO(), fake.ServiceClient(fakeServer), "08eae331-0402-425a-923c-34f7cfe39c1b", opts).Extract() th.AssertNoErr(t, err) revisionNumber := 42 opts.RevisionNumber = &revisionNumber - _, err = subnets.Update(context.TODO(), fake.ServiceClient(), "08eae331-0402-425a-923c-34f7cfe39c1c", opts).Extract() + _, err = subnets.Update(context.TODO(), fake.ServiceClient(fakeServer), "08eae331-0402-425a-923c-34f7cfe39c1c", opts).Extract() th.AssertNoErr(t, err) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/v2.0/subnets/08eae331-0402-425a-923c-34f7cfe39c1b", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v2.0/subnets/08eae331-0402-425a-923c-34f7cfe39c1b", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) - res := subnets.Delete(context.TODO(), fake.ServiceClient(), "08eae331-0402-425a-923c-34f7cfe39c1b") + res := subnets.Delete(context.TODO(), fake.ServiceClient(fakeServer), "08eae331-0402-425a-923c-34f7cfe39c1b") th.AssertNoErr(t, res.Err) } diff --git a/openstack/networking/v2/subnets/testing/results_test.go b/openstack/networking/v2/subnets/testing/results_test.go index 273607bb51..0cef30bc36 100644 --- a/openstack/networking/v2/subnets/testing/results_test.go +++ b/openstack/networking/v2/subnets/testing/results_test.go @@ -54,6 +54,6 @@ func TestHostRoute(t *testing.T) { t.Fatalf("%s", err) } route := subnetWrapper.Subnet.HostRoutes[0] - th.AssertEquals(t, route.NextHop, "172.16.0.2") - th.AssertEquals(t, route.DestinationCIDR, "172.20.1.0/24") + th.AssertEquals(t, "172.16.0.2", route.NextHop) + th.AssertEquals(t, "172.20.1.0/24", route.DestinationCIDR) } diff --git a/openstack/objectstorage/v1/accounts/testing/fixtures.go b/openstack/objectstorage/v1/accounts/testing/fixtures.go index 42284933e8..5e97d2de64 100644 --- a/openstack/objectstorage/v1/accounts/testing/fixtures.go +++ b/openstack/objectstorage/v1/accounts/testing/fixtures.go @@ -5,15 +5,15 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) // HandleGetAccountSuccessfully creates an HTTP handler at `/` on the test handler mux that // responds with a `Get` response. -func HandleGetAccountSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { +func HandleGetAccountSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "HEAD") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("X-Account-Container-Count", "2") w.Header().Set("X-Account-Object-Count", "5") @@ -29,10 +29,10 @@ func HandleGetAccountSuccessfully(t *testing.T) { // HandleGetAccountNoQuotaSuccessfully creates an HTTP handler at `/` on the // test handler mux that responds with a `Get` response. -func HandleGetAccountNoQuotaSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { +func HandleGetAccountNoQuotaSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "HEAD") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Set("X-Account-Container-Count", "2") w.Header().Set("X-Account-Object-Count", "5") @@ -46,10 +46,10 @@ func HandleGetAccountNoQuotaSuccessfully(t *testing.T) { // HandleUpdateAccountSuccessfully creates an HTTP handler at `/` on the test handler mux that // responds with a `Update` response. -func HandleUpdateAccountSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { +func HandleUpdateAccountSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "X-Account-Meta-Gophercloud-Test", "accounts") th.TestHeader(t, r, "X-Remove-Account-Meta-Gophercloud-Test-Remove", "remove") th.TestHeader(t, r, "Content-Type", "") diff --git a/openstack/objectstorage/v1/accounts/testing/requests_test.go b/openstack/objectstorage/v1/accounts/testing/requests_test.go index 6ed29c1ae5..19067efbb7 100644 --- a/openstack/objectstorage/v1/accounts/testing/requests_test.go +++ b/openstack/objectstorage/v1/accounts/testing/requests_test.go @@ -7,13 +7,13 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/objectstorage/v1/accounts" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestUpdateAccount(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateAccountSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateAccountSuccessfully(t, fakeServer) options := &accounts.UpdateOpts{ Metadata: map[string]string{"gophercloud-test": "accounts"}, @@ -21,7 +21,7 @@ func TestUpdateAccount(t *testing.T) { ContentType: new(string), DetectContentType: new(bool), } - res := accounts.Update(context.TODO(), fake.ServiceClient(), options) + res := accounts.Update(context.TODO(), client.ServiceClient(fakeServer), options) th.AssertNoErr(t, res.Err) expected := &accounts.UpdateHeader{ @@ -33,12 +33,12 @@ func TestUpdateAccount(t *testing.T) { } func TestGetAccount(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetAccountSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetAccountSuccessfully(t, fakeServer) expectedMetadata := map[string]string{"Subject": "books", "Quota-Bytes": "42", "Temp-Url-Key": "testsecret"} - res := accounts.Get(context.TODO(), fake.ServiceClient(), &accounts.GetOpts{}) + res := accounts.Get(context.TODO(), client.ServiceClient(fakeServer), &accounts.GetOpts{}) th.AssertNoErr(t, res.Err) actualMetadata, _ := res.ExtractMetadata() th.CheckDeepEquals(t, expectedMetadata, actualMetadata) @@ -60,12 +60,12 @@ func TestGetAccount(t *testing.T) { } func TestGetAccountNoQuota(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetAccountNoQuotaSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetAccountNoQuotaSuccessfully(t, fakeServer) expectedMetadata := map[string]string{"Subject": "books"} - res := accounts.Get(context.TODO(), fake.ServiceClient(), &accounts.GetOpts{}) + res := accounts.Get(context.TODO(), client.ServiceClient(fakeServer), &accounts.GetOpts{}) th.AssertNoErr(t, res.Err) actualMetadata, _ := res.ExtractMetadata() th.CheckDeepEquals(t, expectedMetadata, actualMetadata) diff --git a/openstack/objectstorage/v1/containers/requests.go b/openstack/objectstorage/v1/containers/requests.go index f4714d6ff6..2d00b659ca 100644 --- a/openstack/objectstorage/v1/containers/requests.go +++ b/openstack/objectstorage/v1/containers/requests.go @@ -54,7 +54,7 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { pager := pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { p := ContainerPage{pagination.MarkerPageBase{PageResult: r}} - p.MarkerPageBase.Owner = p + p.Owner = p return p }) pager.Headers = headers diff --git a/openstack/objectstorage/v1/containers/results.go b/openstack/objectstorage/v1/containers/results.go index 8e71c15063..ddbdfca623 100644 --- a/openstack/objectstorage/v1/containers/results.go +++ b/openstack/objectstorage/v1/containers/results.go @@ -89,7 +89,7 @@ func ExtractNames(page pagination.Page) ([]string, error) { return names, nil default: - return nil, fmt.Errorf("Cannot extract names from response with content-type: [%s]", ct) + return nil, fmt.Errorf("cannot extract names from response with content-type: [%s]", ct) } } @@ -111,6 +111,8 @@ type GetHeader struct { TempURLKey2 string `json:"X-Container-Meta-Temp-URL-Key-2"` Timestamp float64 `json:"X-Timestamp,string"` VersionsEnabled bool `json:"-"` + SyncKey string `json:"X-Sync-Key"` + SyncTo string `json:"X-Sync-To"` } func (r *GetHeader) UnmarshalJSON(b []byte) error { diff --git a/openstack/objectstorage/v1/containers/testing/fixtures.go b/openstack/objectstorage/v1/containers/testing/fixtures.go index 0f440472ac..8e35743a7c 100644 --- a/openstack/objectstorage/v1/containers/testing/fixtures.go +++ b/openstack/objectstorage/v1/containers/testing/fixtures.go @@ -7,7 +7,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/objectstorage/v1/containers" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) type handlerOptions struct { @@ -43,10 +43,10 @@ var ExpectedListNames = []string{"janeausten", "marktwain"} // HandleListContainerInfoSuccessfully creates an HTTP handler at `/` on the test handler mux that // responds with a `List` response when full info is requested. -func HandleListContainerInfoSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { +func HandleListContainerInfoSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") @@ -56,7 +56,7 @@ func HandleListContainerInfoSuccessfully(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, `[ + fmt.Fprint(w, `[ { "count": 0, "bytes": 0, @@ -69,7 +69,7 @@ func HandleListContainerInfoSuccessfully(t *testing.T) { } ]`) case "janeausten": - fmt.Fprintf(w, `[ + fmt.Fprint(w, `[ { "count": 1, "bytes": 14, @@ -77,7 +77,7 @@ func HandleListContainerInfoSuccessfully(t *testing.T) { } ]`) case "marktwain": - fmt.Fprintf(w, `[]`) + fmt.Fprint(w, `[]`) default: t.Fatalf("Unexpected marker: [%s]", marker) } @@ -87,10 +87,10 @@ func HandleListContainerInfoSuccessfully(t *testing.T) { // HandleListZeroContainerNames204 creates an HTTP handler at `/` on the test handler mux that // responds with "204 No Content" when container names are requested. This happens on some, but not all, // objectstorage instances. This case is peculiar in that the server sends no `content-type` header. -func HandleListZeroContainerNames204(t *testing.T) { - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { +func HandleListZeroContainerNames204(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.WriteHeader(http.StatusNoContent) @@ -99,10 +99,10 @@ func HandleListZeroContainerNames204(t *testing.T) { // HandleCreateContainerSuccessfully creates an HTTP handler at `/testContainer` on the test handler mux that // responds with a `Create` response. -func HandleCreateContainerSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateContainerSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Add("X-Container-Meta-Foo", "bar") @@ -117,7 +117,7 @@ func HandleCreateContainerSuccessfully(t *testing.T) { // HandleDeleteContainerSuccessfully creates an HTTP handler at `/testContainer` on the test handler mux that // responds with a `Delete` response. -func HandleDeleteContainerSuccessfully(t *testing.T, options ...option) { +func HandleDeleteContainerSuccessfully(t *testing.T, fakeServer th.FakeServer, options ...option) { ho := handlerOptions{ path: "/testContainer", } @@ -125,9 +125,9 @@ func HandleDeleteContainerSuccessfully(t *testing.T, options ...option) { apply(&ho) } - th.Mux.HandleFunc(ho.path, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(ho.path, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.WriteHeader(http.StatusNoContent) }) @@ -145,10 +145,10 @@ const bulkDeleteResponse = ` // HandleBulkDeleteSuccessfully creates an HTTP handler at `/` on the test // handler mux that responds with a `Delete` response. -func HandleBulkDeleteSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { +func HandleBulkDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "Content-Type", "text/plain") th.TestFormValues(t, r, map[string]string{ @@ -158,13 +158,13 @@ func HandleBulkDeleteSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, bulkDeleteResponse) + fmt.Fprint(w, bulkDeleteResponse) }) } // HandleUpdateContainerSuccessfully creates an HTTP handler at `/testContainer` on the test handler mux that // responds with a `Update` response. -func HandleUpdateContainerSuccessfully(t *testing.T, options ...option) { +func HandleUpdateContainerSuccessfully(t *testing.T, fakeServer th.FakeServer, options ...option) { ho := handlerOptions{ path: "/testContainer", } @@ -172,9 +172,9 @@ func HandleUpdateContainerSuccessfully(t *testing.T, options ...option) { apply(&ho) } - th.Mux.HandleFunc(ho.path, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(ho.path, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Container-Write", "") th.TestHeader(t, r, "X-Container-Read", "") @@ -187,7 +187,7 @@ func HandleUpdateContainerSuccessfully(t *testing.T, options ...option) { // HandleUpdateContainerVersioningOn creates an HTTP handler at `/testVersioning` on the test handler mux that // responds with a `Update` response. -func HandleUpdateContainerVersioningOn(t *testing.T, options ...option) { +func HandleUpdateContainerVersioningOn(t *testing.T, fakeServer th.FakeServer, options ...option) { ho := handlerOptions{ path: "/testVersioning", } @@ -195,9 +195,9 @@ func HandleUpdateContainerVersioningOn(t *testing.T, options ...option) { apply(&ho) } - th.Mux.HandleFunc(ho.path, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(ho.path, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Container-Write", "") th.TestHeader(t, r, "X-Container-Read", "") @@ -211,7 +211,7 @@ func HandleUpdateContainerVersioningOn(t *testing.T, options ...option) { // HandleUpdateContainerVersioningOff creates an HTTP handler at `/testVersioning` on the test handler mux that // responds with a `Update` response. -func HandleUpdateContainerVersioningOff(t *testing.T, options ...option) { +func HandleUpdateContainerVersioningOff(t *testing.T, fakeServer th.FakeServer, options ...option) { ho := handlerOptions{ path: "/testVersioning", } @@ -219,9 +219,9 @@ func HandleUpdateContainerVersioningOff(t *testing.T, options ...option) { apply(&ho) } - th.Mux.HandleFunc(ho.path, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(ho.path, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Container-Write", "") th.TestHeader(t, r, "X-Container-Read", "") @@ -235,7 +235,7 @@ func HandleUpdateContainerVersioningOff(t *testing.T, options ...option) { // HandleGetContainerSuccessfully creates an HTTP handler at `/testContainer` on the test handler mux that // responds with a `Get` response. -func HandleGetContainerSuccessfully(t *testing.T, options ...option) { +func HandleGetContainerSuccessfully(t *testing.T, fakeServer th.FakeServer, options ...option) { ho := handlerOptions{ path: "/testContainer", } @@ -243,9 +243,9 @@ func HandleGetContainerSuccessfully(t *testing.T, options ...option) { apply(&ho) } - th.Mux.HandleFunc(ho.path, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(ho.path, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "HEAD") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Accept-Ranges", "bytes") w.Header().Set("Content-Type", "application/json; charset=utf-8") @@ -258,6 +258,8 @@ func HandleGetContainerSuccessfully(t *testing.T, options ...option) { w.Header().Set("X-Trans-Id", "tx554ed59667a64c61866f1-0057b4ba37") w.Header().Set("X-Storage-Policy", "test_policy") w.Header().Set("X-Versions-Enabled", "True") + w.Header().Set("X-Sync-Key", "272465181849") + w.Header().Set("X-Sync-To", "anotherContainer") w.WriteHeader(http.StatusNoContent) }) } diff --git a/openstack/objectstorage/v1/containers/testing/requests_test.go b/openstack/objectstorage/v1/containers/testing/requests_test.go index 9a2074d510..5fcc669ba6 100644 --- a/openstack/objectstorage/v1/containers/testing/requests_test.go +++ b/openstack/objectstorage/v1/containers/testing/requests_test.go @@ -9,7 +9,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/objectstorage/v1/containers" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestContainerNames(t *testing.T) { @@ -31,25 +31,25 @@ func TestContainerNames(t *testing.T) { } { t.Run(tc.name, func(t *testing.T) { t.Run("create", func(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateContainerSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateContainerSuccessfully(t, fakeServer) - _, err := containers.Create(context.TODO(), fake.ServiceClient(), tc.containerName, nil).Extract() + _, err := containers.Create(context.TODO(), client.ServiceClient(fakeServer), tc.containerName, nil).Extract() th.CheckErr(t, err, &tc.expectedError) }) t.Run("delete", func(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteContainerSuccessfully(t, WithPath("/")) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteContainerSuccessfully(t, fakeServer, WithPath("/")) - res := containers.Delete(context.TODO(), fake.ServiceClient(), tc.containerName) + res := containers.Delete(context.TODO(), client.ServiceClient(fakeServer), tc.containerName) th.CheckErr(t, res.Err, &tc.expectedError) }) t.Run("update", func(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateContainerSuccessfully(t, WithPath("/")) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateContainerSuccessfully(t, fakeServer, WithPath("/")) contentType := "text/plain" options := &containers.UpdateOpts{ @@ -60,15 +60,15 @@ func TestContainerNames(t *testing.T) { ContainerSyncKey: new(string), ContentType: &contentType, } - res := containers.Update(context.TODO(), fake.ServiceClient(), tc.containerName, options) + res := containers.Update(context.TODO(), client.ServiceClient(fakeServer), tc.containerName, options) th.CheckErr(t, res.Err, &tc.expectedError) }) t.Run("get", func(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetContainerSuccessfully(t, WithPath("/")) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetContainerSuccessfully(t, fakeServer, WithPath("/")) - res := containers.Get(context.TODO(), fake.ServiceClient(), tc.containerName, nil) + res := containers.Get(context.TODO(), client.ServiceClient(fakeServer), tc.containerName, nil) _, err := res.ExtractMetadata() th.CheckErr(t, err, &tc.expectedError) @@ -80,12 +80,12 @@ func TestContainerNames(t *testing.T) { } func TestListContainerInfo(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListContainerInfoSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListContainerInfoSuccessfully(t, fakeServer) count := 0 - err := containers.List(fake.ServiceClient(), &containers.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := containers.List(client.ServiceClient(fakeServer), &containers.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := containers.ExtractInfo(page) th.AssertNoErr(t, err) @@ -99,11 +99,11 @@ func TestListContainerInfo(t *testing.T) { } func TestListAllContainerInfo(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListContainerInfoSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListContainerInfoSuccessfully(t, fakeServer) - allPages, err := containers.List(fake.ServiceClient(), &containers.ListOpts{}).AllPages(context.TODO()) + allPages, err := containers.List(client.ServiceClient(fakeServer), &containers.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := containers.ExtractInfo(allPages) th.AssertNoErr(t, err) @@ -111,12 +111,12 @@ func TestListAllContainerInfo(t *testing.T) { } func TestListContainerNames(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListContainerInfoSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListContainerInfoSuccessfully(t, fakeServer) count := 0 - err := containers.List(fake.ServiceClient(), &containers.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := containers.List(client.ServiceClient(fakeServer), &containers.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := containers.ExtractNames(page) if err != nil { @@ -133,11 +133,11 @@ func TestListContainerNames(t *testing.T) { } func TestListAllContainerNames(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListContainerInfoSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListContainerInfoSuccessfully(t, fakeServer) - allPages, err := containers.List(fake.ServiceClient(), &containers.ListOpts{}).AllPages(context.TODO()) + allPages, err := containers.List(client.ServiceClient(fakeServer), &containers.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := containers.ExtractNames(allPages) th.AssertNoErr(t, err) @@ -145,11 +145,11 @@ func TestListAllContainerNames(t *testing.T) { } func TestListZeroContainerNames(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListZeroContainerNames204(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListZeroContainerNames204(t, fakeServer) - allPages, err := containers.List(fake.ServiceClient(), &containers.ListOpts{}).AllPages(context.TODO()) + allPages, err := containers.List(client.ServiceClient(fakeServer), &containers.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := containers.ExtractNames(allPages) th.AssertNoErr(t, err) @@ -157,12 +157,12 @@ func TestListZeroContainerNames(t *testing.T) { } func TestCreateContainer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateContainerSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateContainerSuccessfully(t, fakeServer) options := containers.CreateOpts{ContentType: "application/json", Metadata: map[string]string{"foo": "bar"}} - res := containers.Create(context.TODO(), fake.ServiceClient(), "testContainer", options) + res := containers.Create(context.TODO(), client.ServiceClient(fakeServer), "testContainer", options) th.CheckEquals(t, "bar", res.Header["X-Container-Meta-Foo"][0]) expected := &containers.CreateHeader{ @@ -177,18 +177,18 @@ func TestCreateContainer(t *testing.T) { } func TestDeleteContainer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteContainerSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteContainerSuccessfully(t, fakeServer) - res := containers.Delete(context.TODO(), fake.ServiceClient(), "testContainer") + res := containers.Delete(context.TODO(), client.ServiceClient(fakeServer), "testContainer") th.AssertNoErr(t, res.Err) } func TestBulkDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleBulkDeleteSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleBulkDeleteSuccessfully(t, fakeServer) expected := containers.BulkDeleteResponse{ ResponseStatus: "foo", @@ -197,15 +197,15 @@ func TestBulkDelete(t *testing.T) { Errors: [][]string{}, } - resp, err := containers.BulkDelete(context.TODO(), fake.ServiceClient(), []string{"testContainer1", "testContainer2"}).Extract() + resp, err := containers.BulkDelete(context.TODO(), client.ServiceClient(fakeServer), []string{"testContainer1", "testContainer2"}).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, expected, *resp) } func TestUpdateContainer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateContainerSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateContainerSuccessfully(t, fakeServer) contentType := "text/plain" options := &containers.UpdateOpts{ @@ -216,19 +216,19 @@ func TestUpdateContainer(t *testing.T) { ContainerSyncKey: new(string), ContentType: &contentType, } - res := containers.Update(context.TODO(), fake.ServiceClient(), "testContainer", options) + res := containers.Update(context.TODO(), client.ServiceClient(fakeServer), "testContainer", options) th.AssertNoErr(t, res.Err) } func TestGetContainer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetContainerSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetContainerSuccessfully(t, fakeServer) getOpts := containers.GetOpts{ Newest: true, } - res := containers.Get(context.TODO(), fake.ServiceClient(), "testContainer", getOpts) + res := containers.Get(context.TODO(), client.ServiceClient(fakeServer), "testContainer", getOpts) _, err := res.ExtractMetadata() th.AssertNoErr(t, err) @@ -244,6 +244,8 @@ func TestGetContainer(t *testing.T) { StoragePolicy: "test_policy", Timestamp: 1471298837.95721, VersionsEnabled: true, + SyncKey: "272465181849", + SyncTo: "anotherContainer", } actual, err := res.Extract() th.AssertNoErr(t, err) @@ -251,9 +253,9 @@ func TestGetContainer(t *testing.T) { } func TestUpdateContainerVersioningOff(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateContainerVersioningOff(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateContainerVersioningOff(t, fakeServer) contentType := "text/plain" options := &containers.UpdateOpts{ @@ -265,14 +267,14 @@ func TestUpdateContainerVersioningOff(t *testing.T) { ContentType: &contentType, VersionsEnabled: new(bool), } - _, err := containers.Update(context.TODO(), fake.ServiceClient(), "testVersioning", options).Extract() + _, err := containers.Update(context.TODO(), client.ServiceClient(fakeServer), "testVersioning", options).Extract() th.AssertNoErr(t, err) } func TestUpdateContainerVersioningOn(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateContainerVersioningOn(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateContainerVersioningOn(t, fakeServer) iTrue := true contentType := "text/plain" @@ -285,6 +287,6 @@ func TestUpdateContainerVersioningOn(t *testing.T) { ContentType: &contentType, VersionsEnabled: &iTrue, } - _, err := containers.Update(context.TODO(), fake.ServiceClient(), "testVersioning", options).Extract() + _, err := containers.Update(context.TODO(), client.ServiceClient(fakeServer), "testVersioning", options).Extract() th.AssertNoErr(t, err) } diff --git a/openstack/objectstorage/v1/objects/requests.go b/openstack/objectstorage/v1/objects/requests.go index abc56f4773..464f94eeb7 100644 --- a/openstack/objectstorage/v1/objects/requests.go +++ b/openstack/objectstorage/v1/objects/requests.go @@ -91,7 +91,7 @@ func List(c *gophercloud.ServiceClient, containerName string, opts ListOptsBuild pager := pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { p := ObjectPage{pagination.MarkerPageBase{PageResult: r}} - p.MarkerPageBase.Owner = p + p.Owner = p return p }) pager.Headers = headers diff --git a/openstack/objectstorage/v1/objects/results.go b/openstack/objectstorage/v1/objects/results.go index e98c542960..387516994b 100644 --- a/openstack/objectstorage/v1/objects/results.go +++ b/openstack/objectstorage/v1/objects/results.go @@ -133,7 +133,7 @@ func ExtractNames(r pagination.Page) ([]string, error) { case strings.HasPrefix(ct, "text/html"): return []string{}, nil default: - return nil, fmt.Errorf("Cannot extract names from response with content-type: [%s]", ct) + return nil, fmt.Errorf("cannot extract names from response with content-type: [%s]", ct) } } @@ -496,7 +496,7 @@ func extractLastMarker(r pagination.Page) (string, error) { casted := r.(ObjectPage) // If a delimiter was requested, check if a subdir exists. - queryParams, err := url.ParseQuery(casted.URL.RawQuery) + queryParams, err := url.ParseQuery(casted.RawQuery) if err != nil { return "", err } @@ -542,6 +542,6 @@ func extractLastMarker(r pagination.Page) (string, error) { case strings.HasPrefix(ct, "text/html"): return "", nil default: - return "", fmt.Errorf("Cannot extract names from response with content-type: [%s]", ct) + return "", fmt.Errorf("cannot extract names from response with content-type: [%s]", ct) } } diff --git a/openstack/objectstorage/v1/objects/testing/fixtures_test.go b/openstack/objectstorage/v1/objects/testing/fixtures_test.go index fd24937f6c..b6c6f78144 100644 --- a/openstack/objectstorage/v1/objects/testing/fixtures_test.go +++ b/openstack/objectstorage/v1/objects/testing/fixtures_test.go @@ -10,7 +10,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/objectstorage/v1/objects" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) type handlerOptions struct { @@ -27,7 +27,7 @@ func WithPath(s string) option { // HandleDownloadObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux that // responds with a `Download` response. -func HandleDownloadObjectSuccessfully(t *testing.T, options ...option) { +func HandleDownloadObjectSuccessfully(t *testing.T, fakeServer th.FakeServer, options ...option) { ho := handlerOptions{ path: "/testContainer/testObject", } @@ -35,10 +35,10 @@ func HandleDownloadObjectSuccessfully(t *testing.T, options ...option) { apply(&ho) } - th.Mux.HandleFunc(ho.path, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(ho.path, func(w http.ResponseWriter, r *http.Request) { date := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Date", date.Format(time.RFC1123)) w.Header().Set("X-Static-Large-Object", "True") @@ -61,7 +61,7 @@ func HandleDownloadObjectSuccessfully(t *testing.T, options ...option) { } w.Header().Set("Last-Modified", date.Format(time.RFC1123)) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, "Successful download with Gophercloud") + fmt.Fprint(w, "Successful download with Gophercloud") }) } @@ -98,7 +98,7 @@ var ExpectedListNames = []string{"goodbye", "hello"} // HandleListObjectsInfoSuccessfully creates an HTTP handler at `/testContainer` on the test handler mux that // responds with a `List` response when full info is requested. -func HandleListObjectsInfoSuccessfully(t *testing.T, options ...option) { +func HandleListObjectsInfoSuccessfully(t *testing.T, fakeServer th.FakeServer, options ...option) { ho := handlerOptions{ path: "/testContainer", } @@ -106,9 +106,9 @@ func HandleListObjectsInfoSuccessfully(t *testing.T, options ...option) { apply(&ho) } - th.Mux.HandleFunc(ho.path, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(ho.path, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") @@ -118,7 +118,7 @@ func HandleListObjectsInfoSuccessfully(t *testing.T, options ...option) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, `[ + fmt.Fprint(w, `[ { "hash": "451e372e48e0f6b1114fa0724aa79fa1", "last_modified": "2016-08-17T22:11:58.602650", @@ -135,7 +135,7 @@ func HandleListObjectsInfoSuccessfully(t *testing.T, options ...option) { } ]`) case "hello": - fmt.Fprintf(w, `[]`) + fmt.Fprint(w, `[]`) default: t.Fatalf("Unexpected marker: [%s]", marker) } @@ -144,10 +144,10 @@ func HandleListObjectsInfoSuccessfully(t *testing.T, options ...option) { // HandleListSubdirSuccessfully creates an HTTP handler at `/testContainer` on the test handler mux that // responds with a `List` response when full info is requested. -func HandleListSubdirSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) { +func HandleListSubdirSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") @@ -157,13 +157,13 @@ func HandleListSubdirSuccessfully(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, `[ + fmt.Fprint(w, `[ { "subdir": "directory/" } ]`) case "directory/": - fmt.Fprintf(w, `[]`) + fmt.Fprint(w, `[]`) default: t.Fatalf("Unexpected marker: [%s]", marker) } @@ -173,10 +173,10 @@ func HandleListSubdirSuccessfully(t *testing.T) { // HandleListZeroObjectNames204 creates an HTTP handler at `/testContainer` on the test handler mux that // responds with "204 No Content" when object names are requested. This happens on some, but not all, objectstorage // instances. This case is peculiar in that the server sends no `content-type` header. -func HandleListZeroObjectNames204(t *testing.T) { - th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) { +func HandleListZeroObjectNames204(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.WriteHeader(http.StatusNoContent) @@ -185,7 +185,7 @@ func HandleListZeroObjectNames204(t *testing.T) { // HandleCreateTextObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux // that responds with a `Create` response. A Content-Type of "text/plain" is expected. -func HandleCreateTextObjectSuccessfully(t *testing.T, content string, options ...option) { +func HandleCreateTextObjectSuccessfully(t *testing.T, fakeServer th.FakeServer, content string, options ...option) { ho := handlerOptions{ path: "/testContainer/testObject", } @@ -193,9 +193,9 @@ func HandleCreateTextObjectSuccessfully(t *testing.T, content string, options .. apply(&ho) } - th.Mux.HandleFunc(ho.path, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(ho.path, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "text/plain") th.TestHeader(t, r, "Accept", "application/json") th.TestBody(t, r, `Did gyre and gimble in the wabe`) @@ -212,10 +212,10 @@ func HandleCreateTextObjectSuccessfully(t *testing.T, content string, options .. // HandleCreateTextWithCacheControlSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler // mux that responds with a `Create` response. A Cache-Control of `max-age="3600", public` is expected. -func HandleCreateTextWithCacheControlSuccessfully(t *testing.T, content string) { - th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateTextWithCacheControlSuccessfully(t *testing.T, fakeServer th.FakeServer, content string) { + fakeServer.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Cache-Control", `max-age="3600", public`) th.TestHeader(t, r, "Accept", "application/json") th.TestBody(t, r, `All mimsy were the borogoves`) @@ -233,10 +233,10 @@ func HandleCreateTextWithCacheControlSuccessfully(t *testing.T, content string) // HandleCreateTypelessObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler // mux that responds with a `Create` response. No Content-Type header may be present in the request, so that server- // side content-type detection will be triggered properly. -func HandleCreateTypelessObjectSuccessfully(t *testing.T, content string) { - th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateTypelessObjectSuccessfully(t *testing.T, fakeServer th.FakeServer, content string) { + fakeServer.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") th.TestBody(t, r, `The sky was the color of television, tuned to a dead channel.`) @@ -256,10 +256,10 @@ func HandleCreateTypelessObjectSuccessfully(t *testing.T, content string) { // HandleCopyObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux that // responds with a `Copy` response. -func HandleCopyObjectSuccessfully(t *testing.T, destination string) { - th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) { +func HandleCopyObjectSuccessfully(t *testing.T, fakeServer th.FakeServer, destination string) { + fakeServer.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "COPY") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "Destination", destination) w.WriteHeader(http.StatusCreated) @@ -268,10 +268,10 @@ func HandleCopyObjectSuccessfully(t *testing.T, destination string) { // HandleCopyObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux that // responds with a `Copy` response. -func HandleCopyObjectVersionSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) { +func HandleCopyObjectVersionSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "COPY") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "Destination", "/newTestContainer/newTestObject") th.TestFormValues(t, r, map[string]string{"version-id": "123456788"}) @@ -282,7 +282,7 @@ func HandleCopyObjectVersionSuccessfully(t *testing.T) { // HandleDeleteObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux that // responds with a `Delete` response. -func HandleDeleteObjectSuccessfully(t *testing.T, options ...option) { +func HandleDeleteObjectSuccessfully(t *testing.T, fakeServer th.FakeServer, options ...option) { ho := handlerOptions{ path: "/testContainer/testObject", } @@ -290,9 +290,9 @@ func HandleDeleteObjectSuccessfully(t *testing.T, options ...option) { apply(&ho) } - th.Mux.HandleFunc(ho.path, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(ho.path, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.WriteHeader(http.StatusNoContent) }) @@ -310,10 +310,10 @@ const bulkDeleteResponse = ` // HandleBulkDeleteSuccessfully creates an HTTP handler at `/` on the test // handler mux that responds with a `BulkDelete` response. -func HandleBulkDeleteSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { +func HandleBulkDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "Content-Type", "text/plain") th.TestFormValues(t, r, map[string]string{ @@ -323,13 +323,13 @@ func HandleBulkDeleteSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, bulkDeleteResponse) + fmt.Fprint(w, bulkDeleteResponse) }) } // HandleUpdateObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux that // responds with a `Update` response. -func HandleUpdateObjectSuccessfully(t *testing.T, options ...option) { +func HandleUpdateObjectSuccessfully(t *testing.T, fakeServer th.FakeServer, options ...option) { ho := handlerOptions{ path: "/testContainer/testObject", } @@ -337,9 +337,9 @@ func HandleUpdateObjectSuccessfully(t *testing.T, options ...option) { apply(&ho) } - th.Mux.HandleFunc(ho.path, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(ho.path, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-Object-Meta-Gophercloud-Test", "objects") th.TestHeader(t, r, "X-Remove-Object-Meta-Gophercloud-Test-Remove", "remove") @@ -355,7 +355,7 @@ func HandleUpdateObjectSuccessfully(t *testing.T, options ...option) { // HandleGetObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux that // responds with a `Get` response. -func HandleGetObjectSuccessfully(t *testing.T, options ...option) { +func HandleGetObjectSuccessfully(t *testing.T, fakeServer th.FakeServer, options ...option) { ho := handlerOptions{ path: "/testContainer/testObject", } @@ -363,9 +363,9 @@ func HandleGetObjectSuccessfully(t *testing.T, options ...option) { apply(&ho) } - th.Mux.HandleFunc(ho.path, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(ho.path, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "HEAD") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Add("X-Object-Meta-Gophercloud-Test", "objects") w.Header().Add("X-Static-Large-Object", "true") diff --git a/openstack/objectstorage/v1/objects/testing/requests_test.go b/openstack/objectstorage/v1/objects/testing/requests_test.go index 60747a2efc..a410c0206f 100644 --- a/openstack/objectstorage/v1/objects/testing/requests_test.go +++ b/openstack/objectstorage/v1/objects/testing/requests_test.go @@ -17,7 +17,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/objectstorage/v1/objects" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestContainerNames(t *testing.T) { @@ -39,69 +39,67 @@ func TestContainerNames(t *testing.T) { } { t.Run(tc.name, func(t *testing.T) { t.Run("list", func(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListObjectsInfoSuccessfully(t, WithPath("/")) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListObjectsInfoSuccessfully(t, fakeServer, WithPath("/")) - _, err := objects.List(fake.ServiceClient(), tc.containerName, nil).AllPages(context.TODO()) + _, err := objects.List(client.ServiceClient(fakeServer), tc.containerName, nil).AllPages(context.TODO()) th.CheckErr(t, err, &tc.expectedError) }) t.Run("download", func(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDownloadObjectSuccessfully(t, WithPath("/")) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDownloadObjectSuccessfully(t, fakeServer, WithPath("/")) - _, err := objects.Download(context.TODO(), fake.ServiceClient(), tc.containerName, "testObject", nil).Extract() + _, err := objects.Download(context.TODO(), client.ServiceClient(fakeServer), tc.containerName, "testObject", nil).Extract() th.CheckErr(t, err, &tc.expectedError) }) t.Run("create", func(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() content := "Ceci n'est pas une pipe" - HandleCreateTextObjectSuccessfully(t, content, WithPath("/")) + HandleCreateTextObjectSuccessfully(t, fakeServer, content, WithPath("/")) - res := objects.Create(context.TODO(), fake.ServiceClient(), tc.containerName, "testObject", &objects.CreateOpts{ + res := objects.Create(context.TODO(), client.ServiceClient(fakeServer), tc.containerName, "testObject", &objects.CreateOpts{ ContentType: "text/plain", Content: strings.NewReader(content), }) th.CheckErr(t, res.Err, &tc.expectedError) }) t.Run("delete", func(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteObjectSuccessfully(t, WithPath("/")) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteObjectSuccessfully(t, fakeServer, WithPath("/")) - res := objects.Delete(context.TODO(), fake.ServiceClient(), tc.containerName, "testObject", nil) + res := objects.Delete(context.TODO(), client.ServiceClient(fakeServer), tc.containerName, "testObject", nil) th.CheckErr(t, res.Err, &tc.expectedError) }) t.Run("get", func(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetObjectSuccessfully(t, WithPath("/")) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetObjectSuccessfully(t, fakeServer, WithPath("/")) - _, err := objects.Get(context.TODO(), fake.ServiceClient(), tc.containerName, "testObject", nil).ExtractMetadata() + _, err := objects.Get(context.TODO(), client.ServiceClient(fakeServer), tc.containerName, "testObject", nil).ExtractMetadata() th.CheckErr(t, err, &tc.expectedError) }) t.Run("update", func(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateObjectSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateObjectSuccessfully(t, fakeServer) - res := objects.Update(context.TODO(), fake.ServiceClient(), tc.containerName, "testObject", &objects.UpdateOpts{ + res := objects.Update(context.TODO(), client.ServiceClient(fakeServer), tc.containerName, "testObject", &objects.UpdateOpts{ Metadata: map[string]string{"Gophercloud-Test": "objects"}, }) th.CheckErr(t, res.Err, &tc.expectedError) }) t.Run("createTempURL", func(t *testing.T) { - port := 33200 - th.SetupHTTP() - th.SetupPersistentPortHTTP(t, port) - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() // Handle fetching of secret key inside of CreateTempURL - containerTesting.HandleGetContainerSuccessfully(t) - accountTesting.HandleGetAccountSuccessfully(t) - client := fake.ServiceClient() + containerTesting.HandleGetContainerSuccessfully(t, fakeServer) + accountTesting.HandleGetAccountSuccessfully(t, fakeServer) + client := client.ServiceClient(fakeServer) // Append v1/ to client endpoint URL to be compliant with tempURL generator client.Endpoint = client.Endpoint + "v1/" @@ -114,11 +112,11 @@ func TestContainerNames(t *testing.T) { th.CheckErr(t, err, &tc.expectedError) }) t.Run("bulk-delete", func(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleBulkDeleteSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleBulkDeleteSuccessfully(t, fakeServer) - res := objects.BulkDelete(context.TODO(), fake.ServiceClient(), tc.containerName, []string{"testObject"}) + res := objects.BulkDelete(context.TODO(), client.ServiceClient(fakeServer), tc.containerName, []string{"testObject"}) th.CheckErr(t, res.Err, &tc.expectedError) }) }) @@ -126,11 +124,11 @@ func TestContainerNames(t *testing.T) { } func TestDownloadReader(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDownloadObjectSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDownloadObjectSuccessfully(t, fakeServer) - response := objects.Download(context.TODO(), fake.ServiceClient(), "testContainer", "testObject", nil) + response := objects.Download(context.TODO(), client.ServiceClient(fakeServer), "testContainer", "testObject", nil) defer response.Body.Close() // Check reader @@ -141,11 +139,11 @@ func TestDownloadReader(t *testing.T) { } func TestDownloadExtraction(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDownloadObjectSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDownloadObjectSuccessfully(t, fakeServer) - response := objects.Download(context.TODO(), fake.ServiceClient(), "testContainer", "testObject", nil) + response := objects.Download(context.TODO(), client.ServiceClient(fakeServer), "testContainer", "testObject", nil) // Check []byte extraction bytes, err := response.ExtractContent() @@ -165,34 +163,34 @@ func TestDownloadExtraction(t *testing.T) { } func TestDownloadWithLastModified(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDownloadObjectSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDownloadObjectSuccessfully(t, fakeServer) options1 := &objects.DownloadOpts{ IfUnmodifiedSince: time.Date(2009, time.November, 10, 22, 59, 59, 0, time.UTC), } - response1 := objects.Download(context.TODO(), fake.ServiceClient(), "testContainer", "testObject", options1) + response1 := objects.Download(context.TODO(), client.ServiceClient(fakeServer), "testContainer", "testObject", options1) _, err1 := response1.Extract() th.AssertErr(t, err1) options2 := &objects.DownloadOpts{ IfModifiedSince: time.Date(2009, time.November, 10, 23, 0, 1, 0, time.UTC), } - response2 := objects.Download(context.TODO(), fake.ServiceClient(), "testContainer", "testObject", options2) + response2 := objects.Download(context.TODO(), client.ServiceClient(fakeServer), "testContainer", "testObject", options2) content, err2 := response2.ExtractContent() th.AssertNoErr(t, err2) th.AssertEquals(t, 0, len(content)) } func TestListObjectInfo(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListObjectsInfoSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListObjectsInfoSuccessfully(t, fakeServer) count := 0 options := &objects.ListOpts{} - err := objects.List(fake.ServiceClient(), "testContainer", options).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := objects.List(client.ServiceClient(fakeServer), "testContainer", options).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := objects.ExtractInfo(page) th.AssertNoErr(t, err) @@ -206,13 +204,13 @@ func TestListObjectInfo(t *testing.T) { } func TestListObjectSubdir(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListSubdirSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSubdirSuccessfully(t, fakeServer) count := 0 options := &objects.ListOpts{Prefix: "", Delimiter: "/"} - err := objects.List(fake.ServiceClient(), "testContainer", options).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := objects.List(client.ServiceClient(fakeServer), "testContainer", options).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := objects.ExtractInfo(page) th.AssertNoErr(t, err) @@ -226,14 +224,14 @@ func TestListObjectSubdir(t *testing.T) { } func TestListObjectNames(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListObjectsInfoSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListObjectsInfoSuccessfully(t, fakeServer) // Check without delimiter. count := 0 options := &objects.ListOpts{} - err := objects.List(fake.ServiceClient(), "testContainer", options).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := objects.List(client.ServiceClient(fakeServer), "testContainer", options).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := objects.ExtractNames(page) if err != nil { @@ -251,7 +249,7 @@ func TestListObjectNames(t *testing.T) { // Check with delimiter. count = 0 options = &objects.ListOpts{Delimiter: "/"} - err = objects.List(fake.ServiceClient(), "testContainer", options).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err = objects.List(client.ServiceClient(fakeServer), "testContainer", options).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := objects.ExtractNames(page) if err != nil { @@ -268,13 +266,13 @@ func TestListObjectNames(t *testing.T) { } func TestListZeroObjectNames204(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListZeroObjectNames204(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListZeroObjectNames204(t, fakeServer) count := 0 options := &objects.ListOpts{} - err := objects.List(fake.ServiceClient(), "testContainer", options).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := objects.List(client.ServiceClient(fakeServer), "testContainer", options).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := objects.ExtractNames(page) if err != nil { @@ -291,100 +289,100 @@ func TestListZeroObjectNames204(t *testing.T) { } func TestCreateObject(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() content := "Did gyre and gimble in the wabe" - HandleCreateTextObjectSuccessfully(t, content) + HandleCreateTextObjectSuccessfully(t, fakeServer, content) options := &objects.CreateOpts{ContentType: "text/plain", Content: strings.NewReader(content)} - res := objects.Create(context.TODO(), fake.ServiceClient(), "testContainer", "testObject", options) + res := objects.Create(context.TODO(), client.ServiceClient(fakeServer), "testContainer", "testObject", options) th.AssertNoErr(t, res.Err) } func TestCreateObjectWithCacheControl(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() content := "All mimsy were the borogoves" - HandleCreateTextWithCacheControlSuccessfully(t, content) + HandleCreateTextWithCacheControlSuccessfully(t, fakeServer, content) options := &objects.CreateOpts{ CacheControl: `max-age="3600", public`, Content: strings.NewReader(content), } - res := objects.Create(context.TODO(), fake.ServiceClient(), "testContainer", "testObject", options) + res := objects.Create(context.TODO(), client.ServiceClient(fakeServer), "testContainer", "testObject", options) th.AssertNoErr(t, res.Err) } func TestCreateObjectWithoutContentType(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() content := "The sky was the color of television, tuned to a dead channel." - HandleCreateTypelessObjectSuccessfully(t, content) + HandleCreateTypelessObjectSuccessfully(t, fakeServer, content) - res := objects.Create(context.TODO(), fake.ServiceClient(), "testContainer", "testObject", &objects.CreateOpts{Content: strings.NewReader(content)}) + res := objects.Create(context.TODO(), client.ServiceClient(fakeServer), "testContainer", "testObject", &objects.CreateOpts{Content: strings.NewReader(content)}) th.AssertNoErr(t, res.Err) } func TestCopyObject(t *testing.T) { t.Run("simple", func(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCopyObjectSuccessfully(t, "/newTestContainer/newTestObject") + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCopyObjectSuccessfully(t, fakeServer, "/newTestContainer/newTestObject") options := &objects.CopyOpts{Destination: "/newTestContainer/newTestObject"} - res := objects.Copy(context.TODO(), fake.ServiceClient(), "testContainer", "testObject", options) + res := objects.Copy(context.TODO(), client.ServiceClient(fakeServer), "testContainer", "testObject", options) th.AssertNoErr(t, res.Err) }) t.Run("slash", func(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCopyObjectSuccessfully(t, "/newTestContainer/path%2Fto%2FnewTestObject") + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCopyObjectSuccessfully(t, fakeServer, "/newTestContainer/path%2Fto%2FnewTestObject") options := &objects.CopyOpts{Destination: "/newTestContainer/path/to/newTestObject"} - res := objects.Copy(context.TODO(), fake.ServiceClient(), "testContainer", "testObject", options) + res := objects.Copy(context.TODO(), client.ServiceClient(fakeServer), "testContainer", "testObject", options) th.AssertNoErr(t, res.Err) }) t.Run("emojis", func(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCopyObjectSuccessfully(t, "/newTestContainer/new%F0%9F%98%8ATest%2C%3B%22O%28bject%21_%E7%AF%84") + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCopyObjectSuccessfully(t, fakeServer, "/newTestContainer/new%F0%9F%98%8ATest%2C%3B%22O%28bject%21_%E7%AF%84") options := &objects.CopyOpts{Destination: "/newTestContainer/new😊Test,;\"O(bject!_範"} - res := objects.Copy(context.TODO(), fake.ServiceClient(), "testContainer", "testObject", options) + res := objects.Copy(context.TODO(), client.ServiceClient(fakeServer), "testContainer", "testObject", options) th.AssertNoErr(t, res.Err) }) } func TestCopyObjectVersion(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCopyObjectVersionSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCopyObjectVersionSuccessfully(t, fakeServer) options := &objects.CopyOpts{Destination: "/newTestContainer/newTestObject", ObjectVersionID: "123456788"} - res, err := objects.Copy(context.TODO(), fake.ServiceClient(), "testContainer", "testObject", options).Extract() + res, err := objects.Copy(context.TODO(), client.ServiceClient(fakeServer), "testContainer", "testObject", options).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, "123456789", res.ObjectVersionID) } func TestDeleteObject(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteObjectSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteObjectSuccessfully(t, fakeServer) - res := objects.Delete(context.TODO(), fake.ServiceClient(), "testContainer", "testObject", nil) + res := objects.Delete(context.TODO(), client.ServiceClient(fakeServer), "testContainer", "testObject", nil) th.AssertNoErr(t, res.Err) } func TestBulkDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleBulkDeleteSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleBulkDeleteSuccessfully(t, fakeServer) expected := objects.BulkDeleteResponse{ ResponseStatus: "foo", @@ -393,15 +391,15 @@ func TestBulkDelete(t *testing.T) { Errors: [][]string{}, } - resp, err := objects.BulkDelete(context.TODO(), fake.ServiceClient(), "testContainer", []string{"testObject1", "testObject2"}).Extract() + resp, err := objects.BulkDelete(context.TODO(), client.ServiceClient(fakeServer), "testContainer", []string{"testObject1", "testObject2"}).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, expected, *resp) } func TestUpateObjectMetadata(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateObjectSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateObjectSuccessfully(t, fakeServer) s := new(string) i := new(int64) @@ -414,26 +412,26 @@ func TestUpateObjectMetadata(t *testing.T) { DeleteAt: i, DetectContentType: new(bool), } - res := objects.Update(context.TODO(), fake.ServiceClient(), "testContainer", "testObject", options) + res := objects.Update(context.TODO(), client.ServiceClient(fakeServer), "testContainer", "testObject", options) th.AssertNoErr(t, res.Err) } func TestGetObject(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetObjectSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetObjectSuccessfully(t, fakeServer) expected := map[string]string{"Gophercloud-Test": "objects"} - actual, err := objects.Get(context.TODO(), fake.ServiceClient(), "testContainer", "testObject", nil).ExtractMetadata() + actual, err := objects.Get(context.TODO(), client.ServiceClient(fakeServer), "testContainer", "testObject", nil).ExtractMetadata() th.AssertNoErr(t, err) th.CheckDeepEquals(t, expected, actual) getOpts := objects.GetOpts{ Newest: true, } - actualHeaders, err := objects.Get(context.TODO(), fake.ServiceClient(), "testContainer", "testObject", getOpts).Extract() + actualHeaders, err := objects.Get(context.TODO(), client.ServiceClient(fakeServer), "testContainer", "testObject", getOpts).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, true, actualHeaders.StaticLargeObject) + th.AssertTrue(t, actualHeaders.StaticLargeObject) } func TestETag(t *testing.T) { @@ -446,7 +444,7 @@ func TestETag(t *testing.T) { _, headers, _, err := createOpts.ToObjectCreateParams() th.AssertNoErr(t, err) _, ok := headers["ETag"] - th.AssertEquals(t, false, ok) + th.AssertFalse(t, ok) hash := md5.New() _, err = io.WriteString(hash, content) @@ -473,7 +471,7 @@ func TestObjectCreateParamsWithoutSeek(t *testing.T) { th.AssertNoErr(t, err) _, ok := reader.(io.ReadSeeker) - th.AssertEquals(t, true, ok) + th.AssertTrue(t, ok) c, err := io.ReadAll(reader) th.AssertNoErr(t, err) @@ -481,7 +479,7 @@ func TestObjectCreateParamsWithoutSeek(t *testing.T) { th.AssertEquals(t, content, string(c)) _, ok = headers["ETag"] - th.AssertEquals(t, true, ok) + th.AssertTrue(t, ok) } func TestObjectCreateParamsWithSeek(t *testing.T) { @@ -492,7 +490,7 @@ func TestObjectCreateParamsWithSeek(t *testing.T) { th.AssertNoErr(t, err) _, ok := reader.(io.ReadSeeker) - th.AssertEquals(t, ok, true) + th.AssertTrue(t, ok) c, err := io.ReadAll(reader) th.AssertNoErr(t, err) @@ -500,19 +498,17 @@ func TestObjectCreateParamsWithSeek(t *testing.T) { th.AssertEquals(t, content, string(c)) _, ok = headers["ETag"] - th.AssertEquals(t, true, ok) + th.AssertTrue(t, ok) } func TestCreateTempURL(t *testing.T) { - port := 33200 - th.SetupHTTP() - th.SetupPersistentPortHTTP(t, port) - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() // Handle fetching of secret key inside of CreateTempURL - containerTesting.HandleGetContainerSuccessfully(t) - accountTesting.HandleGetAccountSuccessfully(t) - client := fake.ServiceClient() + containerTesting.HandleGetContainerSuccessfully(t, fakeServer) + accountTesting.HandleGetAccountSuccessfully(t, fakeServer) + client := client.ServiceClient(fakeServer) // Append v1/ to client endpoint URL to be compliant with tempURL generator client.Endpoint = client.Endpoint + "v1/" @@ -524,7 +520,7 @@ func TestCreateTempURL(t *testing.T) { sig := "89be454a9c7e2e9f3f50a8441815e0b5801cba5b" expiry := "1593565980" - expectedURL := fmt.Sprintf("http://127.0.0.1:%v/v1/testContainer/testObject%%2FtestFile.txt?temp_url_sig=%v&temp_url_expires=%v", port, sig, expiry) + expectedURL := fmt.Sprintf("%sv1/testContainer/testObject%%2FtestFile.txt?temp_url_sig=%v&temp_url_expires=%v", fakeServer.Endpoint(), sig, expiry) th.AssertNoErr(t, err) th.AssertEquals(t, expectedURL, tempURL) diff --git a/openstack/objectstorage/v1/swauth/testing/fixtures_test.go b/openstack/objectstorage/v1/swauth/testing/fixtures_test.go index b8e0b33f24..796132da3e 100644 --- a/openstack/objectstorage/v1/swauth/testing/fixtures_test.go +++ b/openstack/objectstorage/v1/swauth/testing/fixtures_test.go @@ -16,14 +16,14 @@ var AuthResult = swauth.AuthResult{ } // HandleAuthSuccessfully configures the test server to respond to an Auth request. -func HandleAuthSuccessfully(t *testing.T, authOpts swauth.AuthOpts) { - th.Mux.HandleFunc("/auth/v1.0", func(w http.ResponseWriter, r *http.Request) { +func HandleAuthSuccessfully(t *testing.T, fakeServer th.FakeServer, authOpts swauth.AuthOpts) { + fakeServer.Mux.HandleFunc("/auth/v1.0", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-User", authOpts.User) th.TestHeader(t, r, "X-Auth-Key", authOpts.Key) w.Header().Add("X-Auth-Token", AuthResult.Token) w.Header().Add("X-Storage-Url", AuthResult.StorageURL) - fmt.Fprintf(w, "") + fmt.Fprint(w, "") }) } diff --git a/openstack/objectstorage/v1/swauth/testing/requests_test.go b/openstack/objectstorage/v1/swauth/testing/requests_test.go index cd095d0c17..066da28498 100644 --- a/openstack/objectstorage/v1/swauth/testing/requests_test.go +++ b/openstack/objectstorage/v1/swauth/testing/requests_test.go @@ -15,11 +15,11 @@ func TestAuth(t *testing.T) { Key: "testing", } - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAuthSuccessfully(t, authOpts) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAuthSuccessfully(t, fakeServer, authOpts) - providerClient, err := openstack.NewClient(th.Endpoint()) + providerClient, err := openstack.NewClient(fakeServer.Endpoint()) th.AssertNoErr(t, err) swiftClient, err := swauth.NewObjectStorageV1(context.TODO(), providerClient, authOpts) diff --git a/openstack/orchestration/v1/apiversions/testing/requests_test.go b/openstack/orchestration/v1/apiversions/testing/requests_test.go index ec4ad6d228..80613508c1 100644 --- a/openstack/orchestration/v1/apiversions/testing/requests_test.go +++ b/openstack/orchestration/v1/apiversions/testing/requests_test.go @@ -10,21 +10,21 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/orchestration/v1/apiversions" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestListVersions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "versions": [ { @@ -43,7 +43,7 @@ func TestListVersions(t *testing.T) { count := 0 - err := apiversions.ListVersions(fake.ServiceClient()).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := apiversions.ListVersions(client.ServiceClient(fakeServer)).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := apiversions.ExtractAPIVersions(page) if err != nil { @@ -76,14 +76,14 @@ func TestListVersions(t *testing.T) { } func TestNonJSONCannotBeExtractedIntoAPIVersions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) - err := apiversions.ListVersions(fake.ServiceClient()).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := apiversions.ListVersions(client.ServiceClient(fakeServer)).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { if _, err := apiversions.ExtractAPIVersions(page); err == nil { t.Fatalf("Expected error, got nil") } diff --git a/openstack/orchestration/v1/buildinfo/testing/fixtures_test.go b/openstack/orchestration/v1/buildinfo/testing/fixtures_test.go index 72527032d8..8a89455e5f 100644 --- a/openstack/orchestration/v1/buildinfo/testing/fixtures_test.go +++ b/openstack/orchestration/v1/buildinfo/testing/fixtures_test.go @@ -7,7 +7,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/orchestration/v1/buildinfo" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) // GetExpected represents the expected object from a Get request. @@ -33,14 +33,14 @@ const GetOutput = ` // HandleGetSuccessfully creates an HTTP handler at `/build_info` // on the test handler mux that responds with a `Get` response. -func HandleGetSuccessfully(t *testing.T, output string) { - th.Mux.HandleFunc("/build_info", func(w http.ResponseWriter, r *http.Request) { +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer, output string) { + fakeServer.Mux.HandleFunc("/build_info", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } diff --git a/openstack/orchestration/v1/buildinfo/testing/requests_test.go b/openstack/orchestration/v1/buildinfo/testing/requests_test.go index 5177e7d409..9780033255 100644 --- a/openstack/orchestration/v1/buildinfo/testing/requests_test.go +++ b/openstack/orchestration/v1/buildinfo/testing/requests_test.go @@ -6,15 +6,15 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/orchestration/v1/buildinfo" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestGetTemplate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSuccessfully(t, GetOutput) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSuccessfully(t, fakeServer, GetOutput) - actual, err := buildinfo.Get(context.TODO(), fake.ServiceClient()).Extract() + actual, err := buildinfo.Get(context.TODO(), client.ServiceClient(fakeServer)).Extract() th.AssertNoErr(t, err) expected := GetExpected diff --git a/openstack/orchestration/v1/resourcetypes/testing/fixtures_test.go b/openstack/orchestration/v1/resourcetypes/testing/fixtures_test.go index 37a91b0901..4883edd8d8 100644 --- a/openstack/orchestration/v1/resourcetypes/testing/fixtures_test.go +++ b/openstack/orchestration/v1/resourcetypes/testing/fixtures_test.go @@ -7,7 +7,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/orchestration/v1/resourcetypes" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) const BasicListOutput = ` @@ -75,11 +75,11 @@ var FilteredListExpected = []resourcetypes.ResourceTypeSummary{ // HandleListSuccessfully creates an HTTP handler at `/resource_types` // on the test handler mux that responds with a `List` response. -func HandleListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/resource_types", +func HandleListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/resource_types", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") @@ -324,11 +324,11 @@ const GetSchemaOutput = ` // HandleGetSchemaSuccessfully creates an HTTP handler at // `/resource_types/OS::Test::TestServer` on the test handler mux that // responds with a `GetSchema` response. -func HandleGetSchemaSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/resource_types/OS::Test::TestServer", +func HandleGetSchemaSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/resource_types/OS::Test::TestServer", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") @@ -370,11 +370,11 @@ const GenerateTemplateOutput = ` // HandleGenerateTemplateSuccessfully creates an HTTP handler at // `/resource_types/OS::Heat::None/template` on the test handler mux that // responds with a template. -func HandleGenerateTemplateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/resource_types/OS::Heat::None/template", +func HandleGenerateTemplateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/resource_types/OS::Heat::None/template", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") diff --git a/openstack/orchestration/v1/resourcetypes/testing/requests_test.go b/openstack/orchestration/v1/resourcetypes/testing/requests_test.go index b426d55f6a..6a0fb4356d 100644 --- a/openstack/orchestration/v1/resourcetypes/testing/requests_test.go +++ b/openstack/orchestration/v1/resourcetypes/testing/requests_test.go @@ -6,15 +6,15 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/orchestration/v1/resourcetypes" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestBasicListResourceTypes(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer) - result := resourcetypes.List(context.TODO(), fake.ServiceClient(), nil) + result := resourcetypes.List(context.TODO(), client.ServiceClient(fakeServer), nil) th.AssertNoErr(t, result.Err) actual, err := result.Extract() @@ -24,11 +24,11 @@ func TestBasicListResourceTypes(t *testing.T) { } func TestFullListResourceTypes(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer) - result := resourcetypes.List(context.TODO(), fake.ServiceClient(), resourcetypes.ListOpts{ + result := resourcetypes.List(context.TODO(), client.ServiceClient(fakeServer), resourcetypes.ListOpts{ WithDescription: true, }) th.AssertNoErr(t, result.Err) @@ -40,11 +40,11 @@ func TestFullListResourceTypes(t *testing.T) { } func TestFilteredListResourceTypes(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer) - result := resourcetypes.List(context.TODO(), fake.ServiceClient(), resourcetypes.ListOpts{ + result := resourcetypes.List(context.TODO(), client.ServiceClient(fakeServer), resourcetypes.ListOpts{ NameRegex: listFilterRegex, WithDescription: true, }) @@ -57,11 +57,11 @@ func TestFilteredListResourceTypes(t *testing.T) { } func TestGetSchema(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSchemaSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSchemaSuccessfully(t, fakeServer) - result := resourcetypes.GetSchema(context.TODO(), fake.ServiceClient(), "OS::Test::TestServer") + result := resourcetypes.GetSchema(context.TODO(), client.ServiceClient(fakeServer), "OS::Test::TestServer") th.AssertNoErr(t, result.Err) actual, err := result.Extract() @@ -71,11 +71,11 @@ func TestGetSchema(t *testing.T) { } func TestGenerateTemplate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGenerateTemplateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGenerateTemplateSuccessfully(t, fakeServer) - result := resourcetypes.GenerateTemplate(context.TODO(), fake.ServiceClient(), "OS::Heat::None", nil) + result := resourcetypes.GenerateTemplate(context.TODO(), client.ServiceClient(fakeServer), "OS::Heat::None", nil) th.AssertNoErr(t, result.Err) actual, err := result.Extract() diff --git a/openstack/orchestration/v1/stackevents/requests.go b/openstack/orchestration/v1/stackevents/requests.go index 71836e0541..7166387ec0 100644 --- a/openstack/orchestration/v1/stackevents/requests.go +++ b/openstack/orchestration/v1/stackevents/requests.go @@ -116,7 +116,7 @@ func List(client *gophercloud.ServiceClient, stackName, stackID string, opts Lis } return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { p := EventPage{pagination.MarkerPageBase{PageResult: r}} - p.MarkerPageBase.Owner = p + p.Owner = p return p }) } @@ -173,7 +173,7 @@ func ListResourceEvents(client *gophercloud.ServiceClient, stackName, stackID, r } return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { p := EventPage{pagination.MarkerPageBase{PageResult: r}} - p.MarkerPageBase.Owner = p + p.Owner = p return p }) } diff --git a/openstack/orchestration/v1/stackevents/testing/fixtures_test.go b/openstack/orchestration/v1/stackevents/testing/fixtures_test.go index 4c22428a9a..d450ca559e 100644 --- a/openstack/orchestration/v1/stackevents/testing/fixtures_test.go +++ b/openstack/orchestration/v1/stackevents/testing/fixtures_test.go @@ -9,7 +9,7 @@ import ( "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/openstack/orchestration/v1/stackevents" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) var Timestamp1, _ = time.Parse(time.RFC3339, "2018-06-26T07:58:17Z") @@ -120,15 +120,15 @@ const FindOutput = ` // HandleFindSuccessfully creates an HTTP handler at `/stacks/postman_stack/events` // on the test handler mux that responds with a `Find` response. -func HandleFindSuccessfully(t *testing.T, output string) { - th.Mux.HandleFunc("/stacks/postman_stack/events", func(w http.ResponseWriter, r *http.Request) { +func HandleFindSuccessfully(t *testing.T, fakeServer th.FakeServer, output string) { + fakeServer.Mux.HandleFunc("/stacks/postman_stack/events", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } @@ -237,10 +237,10 @@ const ListOutput = ` // HandleListSuccessfully creates an HTTP handler at `/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/events` // on the test handler mux that responds with a `List` response. -func HandleListSuccessfully(t *testing.T, output string) { - th.Mux.HandleFunc("/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/events", func(w http.ResponseWriter, r *http.Request) { +func HandleListSuccessfully(t *testing.T, fakeServer th.FakeServer, output string) { + fakeServer.Mux.HandleFunc("/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/events", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") @@ -250,9 +250,9 @@ func HandleListSuccessfully(t *testing.T, output string) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, output) + fmt.Fprint(w, output) case "93940999-7d40-44ae-8de4-19624e7b8d18": - fmt.Fprintf(w, `{"events":[]}`) + fmt.Fprint(w, `{"events":[]}`) default: t.Fatalf("Unexpected marker: [%s]", marker) } @@ -364,10 +364,10 @@ const ListResourceEventsOutput = ` // HandleListResourceEventsSuccessfully creates an HTTP handler at `/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/resources/my_resource/events` // on the test handler mux that responds with a `ListResourceEvents` response. -func HandleListResourceEventsSuccessfully(t *testing.T, output string) { - th.Mux.HandleFunc("/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/resources/my_resource/events", func(w http.ResponseWriter, r *http.Request) { +func HandleListResourceEventsSuccessfully(t *testing.T, fakeServer th.FakeServer, output string) { + fakeServer.Mux.HandleFunc("/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/resources/my_resource/events", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") @@ -377,9 +377,9 @@ func HandleListResourceEventsSuccessfully(t *testing.T, output string) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, output) + fmt.Fprint(w, output) case "93940999-7d40-44ae-8de4-19624e7b8d18": - fmt.Fprintf(w, `{"events":[]}`) + fmt.Fprint(w, `{"events":[]}`) default: t.Fatalf("Unexpected marker: [%s]", marker) } @@ -441,14 +441,14 @@ const GetOutput = ` // HandleGetSuccessfully creates an HTTP handler at `/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/resources/my_resource/events/93940999-7d40-44ae-8de4-19624e7b8d18` // on the test handler mux that responds with a `Get` response. -func HandleGetSuccessfully(t *testing.T, output string) { - th.Mux.HandleFunc("/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/resources/my_resource/events/93940999-7d40-44ae-8de4-19624e7b8d18", func(w http.ResponseWriter, r *http.Request) { +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer, output string) { + fakeServer.Mux.HandleFunc("/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/resources/my_resource/events/93940999-7d40-44ae-8de4-19624e7b8d18", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } diff --git a/openstack/orchestration/v1/stackevents/testing/requests_test.go b/openstack/orchestration/v1/stackevents/testing/requests_test.go index bed2d8a2bf..f378916881 100644 --- a/openstack/orchestration/v1/stackevents/testing/requests_test.go +++ b/openstack/orchestration/v1/stackevents/testing/requests_test.go @@ -7,15 +7,15 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/orchestration/v1/stackevents" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestFindEvents(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleFindSuccessfully(t, FindOutput) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleFindSuccessfully(t, fakeServer, FindOutput) - actual, err := stackevents.Find(context.TODO(), fake.ServiceClient(), "postman_stack").Extract() + actual, err := stackevents.Find(context.TODO(), client.ServiceClient(fakeServer), "postman_stack").Extract() th.AssertNoErr(t, err) expected := FindExpected @@ -23,12 +23,12 @@ func TestFindEvents(t *testing.T) { } func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListSuccessfully(t, ListOutput) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer, ListOutput) count := 0 - err := stackevents.List(fake.ServiceClient(), "hello_world", "49181cd6-169a-4130-9455-31185bbfc5bf", nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := stackevents.List(client.ServiceClient(fakeServer), "hello_world", "49181cd6-169a-4130-9455-31185bbfc5bf", nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := stackevents.ExtractEvents(page) th.AssertNoErr(t, err) @@ -38,16 +38,16 @@ func TestList(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListResourceEvents(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListResourceEventsSuccessfully(t, ListResourceEventsOutput) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListResourceEventsSuccessfully(t, fakeServer, ListResourceEventsOutput) count := 0 - err := stackevents.ListResourceEvents(fake.ServiceClient(), "hello_world", "49181cd6-169a-4130-9455-31185bbfc5bf", "my_resource", nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := stackevents.ListResourceEvents(client.ServiceClient(fakeServer), "hello_world", "49181cd6-169a-4130-9455-31185bbfc5bf", "my_resource", nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := stackevents.ExtractResourceEvents(page) th.AssertNoErr(t, err) @@ -57,15 +57,15 @@ func TestListResourceEvents(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestGetEvent(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSuccessfully(t, GetOutput) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSuccessfully(t, fakeServer, GetOutput) - actual, err := stackevents.Get(context.TODO(), fake.ServiceClient(), "hello_world", "49181cd6-169a-4130-9455-31185bbfc5bf", "my_resource", "93940999-7d40-44ae-8de4-19624e7b8d18").Extract() + actual, err := stackevents.Get(context.TODO(), client.ServiceClient(fakeServer), "hello_world", "49181cd6-169a-4130-9455-31185bbfc5bf", "my_resource", "93940999-7d40-44ae-8de4-19624e7b8d18").Extract() th.AssertNoErr(t, err) expected := GetExpected diff --git a/openstack/orchestration/v1/stackresources/testing/fixtures_test.go b/openstack/orchestration/v1/stackresources/testing/fixtures_test.go index a306d8de8b..45800807f3 100644 --- a/openstack/orchestration/v1/stackresources/testing/fixtures_test.go +++ b/openstack/orchestration/v1/stackresources/testing/fixtures_test.go @@ -9,7 +9,7 @@ import ( "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/openstack/orchestration/v1/stackresources" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) var Create_time, _ = time.Parse(time.RFC3339, "2018-06-26T07:57:17Z") @@ -74,15 +74,15 @@ const FindOutput = ` // HandleFindSuccessfully creates an HTTP handler at `/stacks/hello_world/resources` // on the test handler mux that responds with a `Find` response. -func HandleFindSuccessfully(t *testing.T, output string) { - th.Mux.HandleFunc("/stacks/hello_world/resources", func(w http.ResponseWriter, r *http.Request) { +func HandleFindSuccessfully(t *testing.T, fakeServer th.FakeServer, output string) { + fakeServer.Mux.HandleFunc("/stacks/hello_world/resources", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } @@ -144,10 +144,10 @@ const ListOutput = `{ // HandleListSuccessfully creates an HTTP handler at `/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/resources` // on the test handler mux that responds with a `List` response. -func HandleListSuccessfully(t *testing.T, output string) { - th.Mux.HandleFunc("/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/resources", func(w http.ResponseWriter, r *http.Request) { +func HandleListSuccessfully(t *testing.T, fakeServer th.FakeServer, output string) { + fakeServer.Mux.HandleFunc("/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/resources", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") @@ -157,9 +157,9 @@ func HandleListSuccessfully(t *testing.T, output string) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, output) + fmt.Fprint(w, output) case "49181cd6-169a-4130-9455-31185bbfc5bf": - fmt.Fprintf(w, `{"resources":[]}`) + fmt.Fprint(w, `{"resources":[]}`) default: t.Fatalf("Unexpected marker: [%s]", marker) } @@ -219,15 +219,15 @@ const GetOutput = ` // HandleGetSuccessfully creates an HTTP handler at `/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance` // on the test handler mux that responds with a `Get` response. -func HandleGetSuccessfully(t *testing.T, output string) { - th.Mux.HandleFunc("/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance", func(w http.ResponseWriter, r *http.Request) { +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer, output string) { + fakeServer.Mux.HandleFunc("/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } @@ -248,15 +248,15 @@ const MetadataOutput = ` // HandleMetadataSuccessfully creates an HTTP handler at `/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance/metadata` // on the test handler mux that responds with a `Metadata` response. -func HandleMetadataSuccessfully(t *testing.T, output string) { - th.Mux.HandleFunc("/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance/metadata", func(w http.ResponseWriter, r *http.Request) { +func HandleMetadataSuccessfully(t *testing.T, fakeServer th.FakeServer, output string) { + fakeServer.Mux.HandleFunc("/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance/metadata", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } @@ -301,15 +301,15 @@ const ListTypesOutput = ` // HandleListTypesSuccessfully creates an HTTP handler at `/resource_types` // on the test handler mux that responds with a `ListTypes` response. -func HandleListTypesSuccessfully(t *testing.T, output string) { - th.Mux.HandleFunc("/resource_types", func(w http.ResponseWriter, r *http.Request) { +func HandleListTypesSuccessfully(t *testing.T, fakeServer th.FakeServer, output string) { + fakeServer.Mux.HandleFunc("/resource_types", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } @@ -362,15 +362,15 @@ const GetSchemaOutput = ` // HandleGetSchemaSuccessfully creates an HTTP handler at `/resource_types/OS::Heat::AResourceName` // on the test handler mux that responds with a `Schema` response. -func HandleGetSchemaSuccessfully(t *testing.T, output string) { - th.Mux.HandleFunc("/resource_types/OS::Heat::AResourceName", func(w http.ResponseWriter, r *http.Request) { +func HandleGetSchemaSuccessfully(t *testing.T, fakeServer th.FakeServer, output string) { + fakeServer.Mux.HandleFunc("/resource_types/OS::Heat::AResourceName", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } @@ -432,24 +432,24 @@ const GetTemplateOutput = ` // HandleGetTemplateSuccessfully creates an HTTP handler at `/resource_types/OS::Heat::AResourceName/template` // on the test handler mux that responds with a `Template` response. -func HandleGetTemplateSuccessfully(t *testing.T, output string) { - th.Mux.HandleFunc("/resource_types/OS::Heat::AResourceName/template", func(w http.ResponseWriter, r *http.Request) { +func HandleGetTemplateSuccessfully(t *testing.T, fakeServer th.FakeServer, output string) { + fakeServer.Mux.HandleFunc("/resource_types/OS::Heat::AResourceName/template", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } // HandleMarkUnhealthySuccessfully creates an HTTP handler at `/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance` // on the test handler mux that responds with a `MarkUnhealthy` response. -func HandleMarkUnhealthySuccessfully(t *testing.T) { - th.Mux.HandleFunc("/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance", func(w http.ResponseWriter, r *http.Request) { +func HandleMarkUnhealthySuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") diff --git a/openstack/orchestration/v1/stackresources/testing/requests_test.go b/openstack/orchestration/v1/stackresources/testing/requests_test.go index 358b30621b..4f0afa8ccf 100644 --- a/openstack/orchestration/v1/stackresources/testing/requests_test.go +++ b/openstack/orchestration/v1/stackresources/testing/requests_test.go @@ -8,15 +8,15 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/orchestration/v1/stackresources" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestFindResources(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleFindSuccessfully(t, FindOutput) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleFindSuccessfully(t, fakeServer, FindOutput) - actual, err := stackresources.Find(context.TODO(), fake.ServiceClient(), "hello_world").Extract() + actual, err := stackresources.Find(context.TODO(), client.ServiceClient(fakeServer), "hello_world").Extract() th.AssertNoErr(t, err) expected := FindExpected @@ -24,12 +24,12 @@ func TestFindResources(t *testing.T) { } func TestListResources(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListSuccessfully(t, ListOutput) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer, ListOutput) count := 0 - err := stackresources.List(fake.ServiceClient(), "hello_world", "49181cd6-169a-4130-9455-31185bbfc5bf", nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := stackresources.List(client.ServiceClient(fakeServer), "hello_world", "49181cd6-169a-4130-9455-31185bbfc5bf", nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := stackresources.ExtractResources(page) th.AssertNoErr(t, err) @@ -39,15 +39,15 @@ func TestListResources(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestGetResource(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSuccessfully(t, GetOutput) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSuccessfully(t, fakeServer, GetOutput) - actual, err := stackresources.Get(context.TODO(), fake.ServiceClient(), "teststack", "0b1771bd-9336-4f2b-ae86-a80f971faf1e", "wordpress_instance").Extract() + actual, err := stackresources.Get(context.TODO(), client.ServiceClient(fakeServer), "teststack", "0b1771bd-9336-4f2b-ae86-a80f971faf1e", "wordpress_instance").Extract() th.AssertNoErr(t, err) expected := GetExpected @@ -55,11 +55,11 @@ func TestGetResource(t *testing.T) { } func TestResourceMetadata(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleMetadataSuccessfully(t, MetadataOutput) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleMetadataSuccessfully(t, fakeServer, MetadataOutput) - actual, err := stackresources.Metadata(context.TODO(), fake.ServiceClient(), "teststack", "0b1771bd-9336-4f2b-ae86-a80f971faf1e", "wordpress_instance").Extract() + actual, err := stackresources.Metadata(context.TODO(), client.ServiceClient(fakeServer), "teststack", "0b1771bd-9336-4f2b-ae86-a80f971faf1e", "wordpress_instance").Extract() th.AssertNoErr(t, err) expected := MetadataExpected @@ -67,12 +67,12 @@ func TestResourceMetadata(t *testing.T) { } func TestListResourceTypes(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListTypesSuccessfully(t, ListTypesOutput) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListTypesSuccessfully(t, fakeServer, ListTypesOutput) count := 0 - err := stackresources.ListTypes(fake.ServiceClient()).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := stackresources.ListTypes(client.ServiceClient(fakeServer)).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := stackresources.ExtractResourceTypes(page) th.AssertNoErr(t, err) @@ -89,11 +89,11 @@ func TestListResourceTypes(t *testing.T) { } func TestGetResourceSchema(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSchemaSuccessfully(t, GetSchemaOutput) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSchemaSuccessfully(t, fakeServer, GetSchemaOutput) - actual, err := stackresources.Schema(context.TODO(), fake.ServiceClient(), "OS::Heat::AResourceName").Extract() + actual, err := stackresources.Schema(context.TODO(), client.ServiceClient(fakeServer), "OS::Heat::AResourceName").Extract() th.AssertNoErr(t, err) expected := GetSchemaExpected @@ -101,11 +101,11 @@ func TestGetResourceSchema(t *testing.T) { } func TestGetResourceTemplate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetTemplateSuccessfully(t, GetTemplateOutput) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetTemplateSuccessfully(t, fakeServer, GetTemplateOutput) - actual, err := stackresources.Template(context.TODO(), fake.ServiceClient(), "OS::Heat::AResourceName").Extract() + actual, err := stackresources.Template(context.TODO(), client.ServiceClient(fakeServer), "OS::Heat::AResourceName").Extract() th.AssertNoErr(t, err) expected := GetTemplateExpected @@ -113,14 +113,14 @@ func TestGetResourceTemplate(t *testing.T) { } func TestMarkUnhealthyResource(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleMarkUnhealthySuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleMarkUnhealthySuccessfully(t, fakeServer) markUnhealthyOpts := &stackresources.MarkUnhealthyOpts{ MarkUnhealthy: true, ResourceStatusReason: "Kubelet.Ready is Unknown more than 10 mins.", } - err := stackresources.MarkUnhealthy(context.TODO(), fake.ServiceClient(), "teststack", "0b1771bd-9336-4f2b-ae86-a80f971faf1e", "wordpress_instance", markUnhealthyOpts).ExtractErr() + err := stackresources.MarkUnhealthy(context.TODO(), client.ServiceClient(fakeServer), "teststack", "0b1771bd-9336-4f2b-ae86-a80f971faf1e", "wordpress_instance", markUnhealthyOpts).ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/orchestration/v1/stacks/doc.go b/openstack/orchestration/v1/stacks/doc.go index 772c0f4450..ddf54d20ab 100644 --- a/openstack/orchestration/v1/stacks/doc.go +++ b/openstack/orchestration/v1/stacks/doc.go @@ -20,7 +20,7 @@ import ( Example of Preparing Orchestration client: - client, err := openstack.NewOrchestrationV1(provider, gophercloud.EndpointOpts{Region: "RegionOne"}) + client, err := openstack.NewOrchestrationV1(context.TODO(), provider, gophercloud.EndpointOpts{Region: "RegionOne"}) Example of List Stack: diff --git a/openstack/orchestration/v1/stacks/environment.go b/openstack/orchestration/v1/stacks/environment.go index bb9004a9f8..30fdcc00a6 100644 --- a/openstack/orchestration/v1/stacks/environment.go +++ b/openstack/orchestration/v1/stacks/environment.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - yaml "gopkg.in/yaml.v2" + yaml "go.yaml.in/yaml/v3" ) // Environment is a structure that represents stack environments @@ -46,87 +46,68 @@ func (e *Environment) getRRFileContents(ignoreIf igFunc) error { e.fileMaps = make(map[string]string) } - // get the resource registry - rr := e.Parsed["resource_registry"] + // get the resource registry, only process further if it is a map + rr, ok := e.Parsed["resource_registry"].(map[string]any) + if !ok { + return nil + } // search the resource registry for URLs - switch rr.(type) { - // process further only if the resource registry is a map - case map[string]any, map[any]any: - rrMap, err := toStringKeys(rr) - if err != nil { - return err - } - // the resource registry might contain a base URL for the resource. If - // such a field is present, use it. Otherwise, use the default base URL. - var baseURL string - if val, ok := rrMap["base_url"]; ok { - baseURL = val.(string) - } else { - baseURL = e.baseURL - } + // + // the resource registry might contain a base URL for the resource. If + // such a field is present, use it. Otherwise, use the default base URL. + var baseURL string + if val, ok := rr["base_url"]; ok { + baseURL = val.(string) + } else { + baseURL = e.baseURL + } - // The contents of the resource may be located in a remote file, which - // will be a template. Instantiate a temporary template to manage the - // contents. - tempTemplate := new(Template) - tempTemplate.baseURL = baseURL - tempTemplate.client = e.client + // The contents of the resource may be located in a remote file, which + // will be a template. Instantiate a temporary template to manage the + // contents. + tempTemplate := new(Template) + tempTemplate.baseURL = baseURL + tempTemplate.client = e.client - // Fetch the contents of remote resource URL's - if err = tempTemplate.getFileContents(rr, ignoreIf, false); err != nil { - return err - } - // check the `resources` section (if it exists) for more URL's. Note that - // the previous call to GetFileContents was (deliberately) not recursive - // as we want more control over where to look for URL's - if val, ok := rrMap["resources"]; ok { - switch val.(type) { - // process further only if the contents are a map - case map[string]any, map[any]any: - resourcesMap, err := toStringKeys(val) - if err != nil { - return err + // Fetch the contents of remote resource URL's + if err := tempTemplate.getFileContents(rr, ignoreIf, false); err != nil { + return err + } + // check the `resources` section (if it exists) for more URL's. Note that + // the previous call to GetFileContents was (deliberately) not recursive + // as we want more control over where to look for URL's + if resourcesMap, ok := rr["resources"].(map[string]any); ok { + for _, v := range resourcesMap { + if resourceMap, ok := v.(map[string]any); ok { + var resourceBaseURL string + // if base_url for the resource type is defined, use it + if val, ok := resourceMap["base_url"]; ok { + resourceBaseURL = val.(string) + } else { + resourceBaseURL = baseURL } - for _, v := range resourcesMap { - switch v.(type) { - case map[string]any, map[any]any: - resourceMap, err := toStringKeys(v) - if err != nil { - return err - } - var resourceBaseURL string - // if base_url for the resource type is defined, use it - if val, ok := resourceMap["base_url"]; ok { - resourceBaseURL = val.(string) - } else { - resourceBaseURL = baseURL - } - tempTemplate.baseURL = resourceBaseURL - if err := tempTemplate.getFileContents(v, ignoreIf, false); err != nil { - return err - } - } + tempTemplate.baseURL = resourceBaseURL + if err := tempTemplate.getFileContents(v, ignoreIf, false); err != nil { + return err } } } - // if the resource registry contained any URL's, store them. This can - // then be passed as parameter to api calls to Heat api. - e.Files = tempTemplate.Files + } + // if the resource registry contained any URL's, store them. This can + // then be passed as parameter to api calls to Heat api. + e.Files = tempTemplate.Files - // In case some element was updated, regenerate the string representation - if len(e.Files) > 0 { - var err error - e.Bin, err = yaml.Marshal(&e.Parsed) - if err != nil { - return fmt.Errorf("failed to marshal updated environment: %w", err) - } + // In case some element was updated, regenerate the string representation + if len(e.Files) > 0 { + var err error + e.Bin, err = yaml.Marshal(&e.Parsed) + if err != nil { + return fmt.Errorf("failed to marshal updated environment: %w", err) } - - return nil - default: - return nil } + + return nil } // function to choose keys whose values are other environment files diff --git a/openstack/orchestration/v1/stacks/environment_test.go b/openstack/orchestration/v1/stacks/environment_test.go index b69e527050..c3b900543a 100644 --- a/openstack/orchestration/v1/stacks/environment_test.go +++ b/openstack/orchestration/v1/stacks/environment_test.go @@ -71,8 +71,8 @@ func TestIgnoreIfEnvironment(t *testing.T) { } func TestGetRRFileContents(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() environmentContent := ` heat_template_version: 2013-05-23 @@ -129,10 +129,10 @@ service_db: th.AssertNoErr(t, err) // Serve "my_env.yaml" and "my_db.yaml" - fakeEnvURL := th.ServeFile(t, baseurl, "my_env.yaml", "application/json", environmentContent) - fakeDBURL := th.ServeFile(t, baseurl, "my_db.yaml", "application/json", dbContent) + fakeEnvURL := fakeServer.ServeFile(t, baseurl, "my_env.yaml", "application/json", environmentContent) + fakeDBURL := fakeServer.ServeFile(t, baseurl, "my_db.yaml", "application/json", dbContent) - client := fakeClient{BaseClient: getHTTPClient()} + client := fakeClient{BaseClient: getHTTPClient(), FakeServer: fakeServer} env := new(Environment) env.Bin = []byte(`{"resource_registry": {"My::WP::Server": "my_env.yaml", "resources": {"my_db_server": {"OS::DBInstance": "my_db.yaml"}}}}`) env.client = client diff --git a/openstack/orchestration/v1/stacks/template.go b/openstack/orchestration/v1/stacks/template.go index 75eb6a97cf..0943aae978 100644 --- a/openstack/orchestration/v1/stacks/template.go +++ b/openstack/orchestration/v1/stacks/template.go @@ -8,7 +8,7 @@ import ( "strings" "github.com/gophercloud/gophercloud/v2" - yaml "gopkg.in/yaml.v2" + yaml "go.yaml.in/yaml/v3" ) // Template is a structure that represents OpenStack Heat templates @@ -84,12 +84,7 @@ func (t *Template) makeChildTemplate(childURL string, ignoreIf igFunc, recurse b // Applies the transformation for getFileContents() to just one element of a map. // In case the element requires transforming, the function returns its new value. -func (t *Template) mapElemFileContents(k any, v any, ignoreIf igFunc, recurse bool) (any, error) { - key, ok := k.(string) - if !ok { - return nil, fmt.Errorf("can't convert map key to string: %v", k) - } - +func (t *Template) mapElemFileContents(key string, v any, ignoreIf igFunc, recurse bool) (any, error) { value, ok := v.(string) if !ok { // if the value is not a string, recursively parse that value @@ -153,18 +148,6 @@ func (t *Template) getFileContents(te any, ignoreIf igFunc, recurse bool) error updated = true } } - // same if te is a map[non-string] (can't group with above case because we - // can't range over and update 'te' without knowing its key type) - case map[any]any: - for k, v := range teTyped { - newVal, err := t.mapElemFileContents(k, v, ignoreIf, recurse) - if err != nil { - return err - } else if newVal != nil { - teTyped[k] = newVal - updated = true - } - } // if te is a slice, call the function on each element of the slice. case []any: for i := range teTyped { @@ -202,7 +185,7 @@ func ignoreIfTemplate(key string, value any) bool { return true } // `.template` and `.yaml` are allowed suffixes for template URLs when referred to by `type` - if key == "type" && !(strings.HasSuffix(valueString, ".template") || strings.HasSuffix(valueString, ".yaml")) { + if key == "type" && !strings.HasSuffix(valueString, ".template") && !strings.HasSuffix(valueString, ".yaml") { return true } return false diff --git a/openstack/orchestration/v1/stacks/template_test.go b/openstack/orchestration/v1/stacks/template_test.go index 9ab257d70d..1f304e0cb5 100644 --- a/openstack/orchestration/v1/stacks/template_test.go +++ b/openstack/orchestration/v1/stacks/template_test.go @@ -111,14 +111,14 @@ var myNovaExpected = map[string]any{ } func TestGetFileContentsWithType(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() baseurl, err := getBasePath() th.AssertNoErr(t, err) - fakeURL := th.ServeFile(t, baseurl, "my_nova.yaml", "application/json", myNovaContent) + fakeURL := fakeServer.ServeFile(t, baseurl, "my_nova.yaml", "application/json", myNovaContent) - client := fakeClient{BaseClient: getHTTPClient()} + client := fakeClient{BaseClient: getHTTPClient(), FakeServer: fakeServer} te := new(Template) te.Bin = []byte(`heat_template_version: 2015-04-30 resources: @@ -148,15 +148,15 @@ resources: } func TestGetFileContentsWithFile(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() baseurl, err := getBasePath() th.AssertNoErr(t, err) somefile := `Welcome!` - fakeURL := th.ServeFile(t, baseurl, "somefile", "text/plain", somefile) + fakeURL := fakeServer.ServeFile(t, baseurl, "somefile", "text/plain", somefile) - client := fakeClient{BaseClient: getHTTPClient()} + client := fakeClient{BaseClient: getHTTPClient(), FakeServer: fakeServer} te := new(Template) // Note: We include the path that should be replaced also as a not-to-be-replaced // keyword ("path: somefile" below) to validate that no updates happen outside of @@ -195,13 +195,13 @@ resources: } func TestGetFileContentsComposeRelativePath(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() baseurl, err := getBasePath() th.AssertNoErr(t, err) novaPath := strings.Join([]string{"templates", "my_nova.yaml"}, "/") - novaURL := th.ServeFile(t, baseurl, novaPath, "application/json", myNovaContent) + novaURL := fakeServer.ServeFile(t, baseurl, novaPath, "application/json", myNovaContent) mySubStackContent := `heat_template_version: 2015-04-30 resources: @@ -237,9 +237,9 @@ resources: }, } subStacksPath := strings.Join([]string{"substacks", "my_substack.yaml"}, "/") - subStackURL := th.ServeFile(t, baseurl, subStacksPath, "application/json", mySubStackContent) + subStackURL := fakeServer.ServeFile(t, baseurl, subStacksPath, "application/json", mySubStackContent) - client := fakeClient{BaseClient: getHTTPClient()} + client := fakeClient{BaseClient: getHTTPClient(), FakeServer: fakeServer} te := new(Template) te.Bin = []byte(`heat_template_version: 2015-04-30 resources: diff --git a/openstack/orchestration/v1/stacks/testing/fixtures_test.go b/openstack/orchestration/v1/stacks/testing/fixtures_test.go index 2244b7d89d..4b293dbf89 100644 --- a/openstack/orchestration/v1/stacks/testing/fixtures_test.go +++ b/openstack/orchestration/v1/stacks/testing/fixtures_test.go @@ -9,7 +9,7 @@ import ( "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/openstack/orchestration/v1/stacks" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) var Create_time, _ = time.Parse(time.RFC3339, "2018-06-26T07:58:17Z") @@ -42,13 +42,13 @@ const CreateOutput = ` // HandleCreateSuccessfully creates an HTTP handler at `/stacks` on the test handler mux // that responds with a `Create` response. -func HandleCreateSuccessfully(t *testing.T, output string) { - th.Mux.HandleFunc("/stacks", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateSuccessfully(t *testing.T, fakeServer th.FakeServer, output string) { + fakeServer.Mux.HandleFunc("/stacks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } @@ -129,10 +129,10 @@ const FullListOutput = ` // HandleListSuccessfully creates an HTTP handler at `/stacks` on the test handler mux // that responds with a `List` response. -func HandleListSuccessfully(t *testing.T, output string) { - th.Mux.HandleFunc("/stacks", func(w http.ResponseWriter, r *http.Request) { +func HandleListSuccessfully(t *testing.T, fakeServer th.FakeServer, output string) { + fakeServer.Mux.HandleFunc("/stacks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") @@ -142,9 +142,9 @@ func HandleListSuccessfully(t *testing.T, output string) { marker := r.Form.Get("marker") switch marker { case "": - fmt.Fprintf(w, output) + fmt.Fprint(w, output) case "db6977b2-27aa-4775-9ae7-6213212d4ada": - fmt.Fprintf(w, `[]`) + fmt.Fprint(w, `[]`) default: t.Fatalf("Unexpected marker: [%s]", marker) } @@ -213,36 +213,36 @@ const GetOutput = ` // HandleGetSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87` // on the test handler mux that responds with a `Get` response. -func HandleGetSuccessfully(t *testing.T, output string) { - th.Mux.HandleFunc("/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87", func(w http.ResponseWriter, r *http.Request) { +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer, output string) { + fakeServer.Mux.HandleFunc("/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } -func HandleFindSuccessfully(t *testing.T, output string) { - th.Mux.HandleFunc("/stacks/16ef0584-4458-41eb-87c8-0dc8d5f66c87", func(w http.ResponseWriter, r *http.Request) { +func HandleFindSuccessfully(t *testing.T, fakeServer th.FakeServer, output string) { + fakeServer.Mux.HandleFunc("/stacks/16ef0584-4458-41eb-87c8-0dc8d5f66c87", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } // HandleUpdateSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87` // on the test handler mux that responds with an `Update` response. -func HandleUpdateSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/stacks/gophercloud-test-stack-2/db6977b2-27aa-4775-9ae7-6213212d4ada", func(w http.ResponseWriter, r *http.Request) { +func HandleUpdateSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/stacks/gophercloud-test-stack-2/db6977b2-27aa-4775-9ae7-6213212d4ada", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") @@ -252,10 +252,10 @@ func HandleUpdateSuccessfully(t *testing.T) { // HandleUpdatePatchSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87` // on the test handler mux that responds with an `Update` response. -func HandleUpdatePatchSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/stacks/gophercloud-test-stack-2/db6977b2-27aa-4775-9ae7-6213212d4ada", func(w http.ResponseWriter, r *http.Request) { +func HandleUpdatePatchSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/stacks/gophercloud-test-stack-2/db6977b2-27aa-4775-9ae7-6213212d4ada", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") @@ -265,10 +265,10 @@ func HandleUpdatePatchSuccessfully(t *testing.T) { // HandleDeleteSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87` // on the test handler mux that responds with a `Delete` response. -func HandleDeleteSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/stacks/gophercloud-test-stack-2/db6977b2-27aa-4775-9ae7-6213212d4ada", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/stacks/gophercloud-test-stack-2/db6977b2-27aa-4775-9ae7-6213212d4ada", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") @@ -301,15 +301,15 @@ var PreviewExpected = &stacks.PreviewedStack{ // HandlePreviewSuccessfully creates an HTTP handler at `/stacks/preview` // on the test handler mux that responds with a `Preview` response. -func HandlePreviewSuccessfully(t *testing.T, output string) { - th.Mux.HandleFunc("/stacks/preview", func(w http.ResponseWriter, r *http.Request) { +func HandlePreviewSuccessfully(t *testing.T, fakeServer th.FakeServer, output string) { + fakeServer.Mux.HandleFunc("/stacks/preview", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } @@ -424,14 +424,14 @@ const AbandonOutput = ` // HandleAbandonSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87/abandon` // on the test handler mux that responds with an `Abandon` response. -func HandleAbandonSuccessfully(t *testing.T, output string) { - th.Mux.HandleFunc("/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c8/abandon", func(w http.ResponseWriter, r *http.Request) { +func HandleAbandonSuccessfully(t *testing.T, fakeServer th.FakeServer, output string) { + fakeServer.Mux.HandleFunc("/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c8/abandon", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } diff --git a/openstack/orchestration/v1/stacks/testing/requests_test.go b/openstack/orchestration/v1/stacks/testing/requests_test.go index 224893f5d0..75a4f3d47c 100644 --- a/openstack/orchestration/v1/stacks/testing/requests_test.go +++ b/openstack/orchestration/v1/stacks/testing/requests_test.go @@ -8,13 +8,13 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/orchestration/v1/stacks" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestCreateStack(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateSuccessfully(t, CreateOutput) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateSuccessfully(t, fakeServer, CreateOutput) template := new(stacks.Template) template.Bin = []byte(` { @@ -33,7 +33,7 @@ func TestCreateStack(t *testing.T) { TemplateOpts: template, DisableRollback: gophercloud.Disabled, } - actual, err := stacks.Create(context.TODO(), fake.ServiceClient(), createOpts).Extract() + actual, err := stacks.Create(context.TODO(), client.ServiceClient(fakeServer), createOpts).Extract() th.AssertNoErr(t, err) expected := CreateExpected @@ -41,9 +41,9 @@ func TestCreateStack(t *testing.T) { } func TestCreateStackMissingRequiredInOpts(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateSuccessfully(t, CreateOutput) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateSuccessfully(t, fakeServer, CreateOutput) template := new(stacks.Template) template.Bin = []byte(` { @@ -59,14 +59,14 @@ func TestCreateStackMissingRequiredInOpts(t *testing.T) { createOpts := stacks.CreateOpts{ DisableRollback: gophercloud.Disabled, } - r := stacks.Create(context.TODO(), fake.ServiceClient(), createOpts) + r := stacks.Create(context.TODO(), client.ServiceClient(fakeServer), createOpts) th.AssertEquals(t, "error creating the options map: Missing input for argument [Name]", r.Err.Error()) } func TestAdoptStack(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateSuccessfully(t, CreateOutput) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateSuccessfully(t, fakeServer, CreateOutput) template := new(stacks.Template) template.Bin = []byte(` { @@ -102,7 +102,7 @@ func TestAdoptStack(t *testing.T) { TemplateOpts: template, DisableRollback: gophercloud.Disabled, } - actual, err := stacks.Adopt(context.TODO(), fake.ServiceClient(), adoptOpts).Extract() + actual, err := stacks.Adopt(context.TODO(), client.ServiceClient(fakeServer), adoptOpts).Extract() th.AssertNoErr(t, err) expected := CreateExpected @@ -110,12 +110,12 @@ func TestAdoptStack(t *testing.T) { } func TestListStack(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListSuccessfully(t, FullListOutput) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer, FullListOutput) count := 0 - err := stacks.List(fake.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := stacks.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := stacks.ExtractStacks(page) th.AssertNoErr(t, err) @@ -125,15 +125,15 @@ func TestListStack(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestGetStack(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSuccessfully(t, GetOutput) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSuccessfully(t, fakeServer, GetOutput) - actual, err := stacks.Get(context.TODO(), fake.ServiceClient(), "postman_stack", "16ef0584-4458-41eb-87c8-0dc8d5f66c87").Extract() + actual, err := stacks.Get(context.TODO(), client.ServiceClient(fakeServer), "postman_stack", "16ef0584-4458-41eb-87c8-0dc8d5f66c87").Extract() th.AssertNoErr(t, err) expected := GetExpected @@ -141,11 +141,11 @@ func TestGetStack(t *testing.T) { } func TestFindStack(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleFindSuccessfully(t, GetOutput) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleFindSuccessfully(t, fakeServer, GetOutput) - actual, err := stacks.Find(context.TODO(), fake.ServiceClient(), "16ef0584-4458-41eb-87c8-0dc8d5f66c87").Extract() + actual, err := stacks.Find(context.TODO(), client.ServiceClient(fakeServer), "16ef0584-4458-41eb-87c8-0dc8d5f66c87").Extract() th.AssertNoErr(t, err) expected := GetExpected @@ -153,9 +153,9 @@ func TestFindStack(t *testing.T) { } func TestUpdateStack(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateSuccessfully(t, fakeServer) template := new(stacks.Template) template.Bin = []byte(` @@ -172,14 +172,14 @@ func TestUpdateStack(t *testing.T) { updateOpts := &stacks.UpdateOpts{ TemplateOpts: template, } - err := stacks.Update(context.TODO(), fake.ServiceClient(), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada", updateOpts).ExtractErr() + err := stacks.Update(context.TODO(), client.ServiceClient(fakeServer), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada", updateOpts).ExtractErr() th.AssertNoErr(t, err) } func TestUpdateStackNoTemplate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdateSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdateSuccessfully(t, fakeServer) parameters := make(map[string]any) parameters["flavor"] = "m1.tiny" @@ -189,14 +189,14 @@ func TestUpdateStackNoTemplate(t *testing.T) { } expected := stacks.ErrTemplateRequired{} - err := stacks.Update(context.TODO(), fake.ServiceClient(), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada", updateOpts).ExtractErr() + err := stacks.Update(context.TODO(), client.ServiceClient(fakeServer), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada", updateOpts).ExtractErr() th.AssertEquals(t, expected, err) } func TestUpdatePatchStack(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleUpdatePatchSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleUpdatePatchSuccessfully(t, fakeServer) parameters := make(map[string]any) parameters["flavor"] = "m1.tiny" @@ -204,23 +204,23 @@ func TestUpdatePatchStack(t *testing.T) { updateOpts := &stacks.UpdateOpts{ Parameters: parameters, } - err := stacks.UpdatePatch(context.TODO(), fake.ServiceClient(), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada", updateOpts).ExtractErr() + err := stacks.UpdatePatch(context.TODO(), client.ServiceClient(fakeServer), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada", updateOpts).ExtractErr() th.AssertNoErr(t, err) } func TestDeleteStack(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteSuccessfully(t, fakeServer) - err := stacks.Delete(context.TODO(), fake.ServiceClient(), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada").ExtractErr() + err := stacks.Delete(context.TODO(), client.ServiceClient(fakeServer), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada").ExtractErr() th.AssertNoErr(t, err) } func TestPreviewStack(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandlePreviewSuccessfully(t, GetOutput) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePreviewSuccessfully(t, fakeServer, GetOutput) template := new(stacks.Template) template.Bin = []byte(` @@ -240,7 +240,7 @@ func TestPreviewStack(t *testing.T) { TemplateOpts: template, DisableRollback: gophercloud.Disabled, } - actual, err := stacks.Preview(context.TODO(), fake.ServiceClient(), previewOpts).Extract() + actual, err := stacks.Preview(context.TODO(), client.ServiceClient(fakeServer), previewOpts).Extract() th.AssertNoErr(t, err) expected := PreviewExpected @@ -248,11 +248,11 @@ func TestPreviewStack(t *testing.T) { } func TestAbandonStack(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAbandonSuccessfully(t, AbandonOutput) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAbandonSuccessfully(t, fakeServer, AbandonOutput) - actual, err := stacks.Abandon(context.TODO(), fake.ServiceClient(), "postman_stack", "16ef0584-4458-41eb-87c8-0dc8d5f66c8").Extract() + actual, err := stacks.Abandon(context.TODO(), client.ServiceClient(fakeServer), "postman_stack", "16ef0584-4458-41eb-87c8-0dc8d5f66c8").Extract() th.AssertNoErr(t, err) expected := AbandonExpected diff --git a/openstack/orchestration/v1/stacks/utils.go b/openstack/orchestration/v1/stacks/utils.go index 56bc48d041..bb936ae1b3 100644 --- a/openstack/orchestration/v1/stacks/utils.go +++ b/openstack/orchestration/v1/stacks/utils.go @@ -6,10 +6,10 @@ import ( "io" "net/http" "path/filepath" - "reflect" + "time" "github.com/gophercloud/gophercloud/v2" - yaml "gopkg.in/yaml.v2" + yaml "go.yaml.in/yaml/v3" ) // Client is an interface that expects a Get method similar to http.Get. This @@ -80,7 +80,7 @@ func (t *TE) Fetch() error { if err != nil { return err } - if !(resp.StatusCode >= 200 && resp.StatusCode < 300) { + if resp.StatusCode < 200 || resp.StatusCode >= 300 { return fmt.Errorf("error fetching %s: %s", t.URL, resp.Status) } t.Bin = body @@ -113,10 +113,20 @@ func (t *TE) Parse() error { if err := t.Fetch(); err != nil { return err } - if jerr := json.Unmarshal(t.Bin, &t.Parsed); jerr != nil { - if yerr := yaml.Unmarshal(t.Bin, &t.Parsed); yerr != nil { - return ErrInvalidDataFormat{} - } + + // either parse as JSON... + if jerr := json.Unmarshal(t.Bin, &t.Parsed); jerr == nil { + return nil + } + + // ... or as YAML (but in this case, take extra care because yaml.Unmarshal() + // might incorrectly decode the `heat_template_version` attribute as a + // time.Time instead of as a string because it looks like "YYYY-MM-DD") + if yerr := yaml.Unmarshal(t.Bin, &t.Parsed); yerr != nil { + return ErrInvalidDataFormat{} + } + if versionAsTime, ok := t.Parsed["heat_template_version"].(time.Time); ok { + t.Parsed["heat_template_version"] = versionAsTime.Format(time.DateOnly) } return nil } @@ -124,21 +134,3 @@ func (t *TE) Parse() error { // igfunc is a parameter used by GetFileContents and GetRRFileContents to check // for valid URL's. type igFunc func(string, any) bool - -// convert map[any]any to map[string]any -func toStringKeys(m any) (map[string]any, error) { - switch m.(type) { - case map[string]any, map[any]any: - typedMap := make(map[string]any) - if _, ok := m.(map[any]any); ok { - for k, v := range m.(map[any]any) { - typedMap[k.(string)] = v - } - } else { - typedMap = m.(map[string]any) - } - return typedMap, nil - default: - return nil, gophercloud.ErrUnexpectedType{Expected: "map[string]any/map[any]any", Actual: fmt.Sprintf("%v", reflect.TypeOf(m))} - } -} diff --git a/openstack/orchestration/v1/stacks/utils_test.go b/openstack/orchestration/v1/stacks/utils_test.go index 07fbb90cba..22b36ba19a 100644 --- a/openstack/orchestration/v1/stacks/utils_test.go +++ b/openstack/orchestration/v1/stacks/utils_test.go @@ -10,21 +10,6 @@ import ( th "github.com/gophercloud/gophercloud/v2/testhelper" ) -func TestToStringKeys(t *testing.T) { - var test1 any = map[any]any{ - "Adam": "Smith", - "Isaac": "Newton", - } - result1, err := toStringKeys(test1) - th.AssertNoErr(t, err) - - expected := map[string]any{ - "Adam": "Smith", - "Isaac": "Newton", - } - th.AssertDeepEquals(t, result1, expected) -} - func TestGetBasePath(t *testing.T) { _, err := getBasePath() th.AssertNoErr(t, err) @@ -38,40 +23,41 @@ func TestGetHTTPClient(t *testing.T) { th.AssertNoErr(t, err) resp, err := client.Get(baseurl) th.AssertNoErr(t, err) - th.AssertEquals(t, resp.StatusCode, 200) + th.AssertEquals(t, 200, resp.StatusCode) } // Implement a fakeclient that can be used to mock out HTTP requests type fakeClient struct { BaseClient Client + FakeServer th.FakeServer } // this client's Get method first changes the URL given to point to // testhelper's (th) endpoints. This is done because the http Mux does not seem // to work for fqdns with the `file` scheme func (c fakeClient) Get(url string) (*http.Response, error) { - newurl := strings.Replace(url, "file://", th.Endpoint(), 1) + newurl := strings.Replace(url, "file://", c.FakeServer.Endpoint(), 1) return c.BaseClient.Get(newurl) } // test the fetch function func TestFetch(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() baseurl, err := getBasePath() th.AssertNoErr(t, err) fakeURL := strings.Join([]string{baseurl, "file.yaml"}, "/") urlparsed, err := url.Parse(fakeURL) th.AssertNoErr(t, err) - th.Mux.HandleFunc(urlparsed.Path, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(urlparsed.Path, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") w.Header().Set("Content-Type", "application/jason") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, "Fee-fi-fo-fum") + fmt.Fprint(w, "Fee-fi-fo-fum") }) - client := fakeClient{BaseClient: getHTTPClient()} + client := fakeClient{BaseClient: getHTTPClient(), FakeServer: fakeServer} te := TE{ URL: "file.yaml", client: client, diff --git a/openstack/orchestration/v1/stacktemplates/testing/fixtures_test.go b/openstack/orchestration/v1/stacktemplates/testing/fixtures_test.go index 5b9c9ad345..7ee957aeb6 100644 --- a/openstack/orchestration/v1/stacktemplates/testing/fixtures_test.go +++ b/openstack/orchestration/v1/stacktemplates/testing/fixtures_test.go @@ -7,7 +7,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/orchestration/v1/stacktemplates" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) // GetExpected represents the expected object from a Get request. @@ -40,15 +40,15 @@ const GetOutput = ` // HandleGetSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87/template` // on the test handler mux that responds with a `Get` response. -func HandleGetSuccessfully(t *testing.T, output string) { - th.Mux.HandleFunc("/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87/template", func(w http.ResponseWriter, r *http.Request) { +func HandleGetSuccessfully(t *testing.T, fakeServer th.FakeServer, output string) { + fakeServer.Mux.HandleFunc("/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87/template", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } @@ -83,14 +83,14 @@ const ValidateOutput = ` // HandleValidateSuccessfully creates an HTTP handler at `/validate` // on the test handler mux that responds with a `Validate` response. -func HandleValidateSuccessfully(t *testing.T, output string) { - th.Mux.HandleFunc("/validate", func(w http.ResponseWriter, r *http.Request) { +func HandleValidateSuccessfully(t *testing.T, fakeServer th.FakeServer, output string) { + fakeServer.Mux.HandleFunc("/validate", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } diff --git a/openstack/orchestration/v1/stacktemplates/testing/requests_test.go b/openstack/orchestration/v1/stacktemplates/testing/requests_test.go index f52f4f75d7..3e2eae944b 100644 --- a/openstack/orchestration/v1/stacktemplates/testing/requests_test.go +++ b/openstack/orchestration/v1/stacktemplates/testing/requests_test.go @@ -6,15 +6,15 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/orchestration/v1/stacktemplates" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestGetTemplate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetSuccessfully(t, GetOutput) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetSuccessfully(t, fakeServer, GetOutput) - actual, err := stacktemplates.Get(context.TODO(), fake.ServiceClient(), "postman_stack", "16ef0584-4458-41eb-87c8-0dc8d5f66c87").Extract() + actual, err := stacktemplates.Get(context.TODO(), client.ServiceClient(fakeServer), "postman_stack", "16ef0584-4458-41eb-87c8-0dc8d5f66c87").Extract() th.AssertNoErr(t, err) expected := GetExpected @@ -22,9 +22,9 @@ func TestGetTemplate(t *testing.T) { } func TestValidateTemplate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleValidateSuccessfully(t, ValidateOutput) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleValidateSuccessfully(t, fakeServer, ValidateOutput) opts := stacktemplates.ValidateOpts{ Template: `{ @@ -51,7 +51,7 @@ func TestValidateTemplate(t *testing.T) { } }`, } - actual, err := stacktemplates.Validate(context.TODO(), fake.ServiceClient(), opts).Extract() + actual, err := stacktemplates.Validate(context.TODO(), client.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) expected := ValidateExpected diff --git a/openstack/placement/v1/allocationcandidates/doc.go b/openstack/placement/v1/allocationcandidates/doc.go new file mode 100644 index 0000000000..ae9ceb284c --- /dev/null +++ b/openstack/placement/v1/allocationcandidates/doc.go @@ -0,0 +1,58 @@ +/* +Package allocationcandidates queries allocation candidates from the +OpenStack Placement service. + +Allocation candidates API requests are available starting from version 1.10. + +The response format changed in version 1.12: the allocations field in +allocation_requests changed from an array to a dictionary keyed by +resource provider UUID. Use the microversions.go types for 1.10-1.11. + +Example to list allocation candidates + + listOpts := allocationcandidates.ListOpts{ + Resources: "VCPU:1,MEMORY_MB:1024", + Required: []string{"HW_CPU_X86_SSE"}, + } + + page, err := allocationcandidates.List(placementClient, listOpts).AllPages(context.TODO()) + if err != nil { + panic(err) + } + allocationCandidates, err := allocationcandidates.ExtractAllocationCandidates(page) + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", allocationCandidates) + +Example to list allocation candidates with granular resource groups (microversion >= 1.33) + + listOpts := allocationcandidates.ListOpts{ + Resources: "VCPU:1,MEMORY_MB:1024", + GroupPolicy: "isolate", + ResourceGroups: map[string]allocationcandidates.ResourceGroup{ + "1": { + Resources: "SRIOV_NET_VF:1", + Required: []string{"CUSTOM_PHYSNET1"}, + }, + "_NET": { + Resources: "NET_BW_EGR_KILOBIT_PER_SEC:10", + MemberOf: "in:42896e0d-205d-4fe3-bd1e-100924931787,5e08ea53-c4c6-448e-9334-ac4953de3cfa", + }, + }, + } + + placementClient.Microversion = "1.33" + page, err := allocationcandidates.List(placementClient, listOpts).AllPages(context.TODO()) + if err != nil { + panic(err) + } + allocationCandidates, err := allocationcandidates.ExtractAllocationCandidates(page) + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", allocationCandidates) +*/ +package allocationcandidates diff --git a/openstack/placement/v1/allocationcandidates/microversions.go b/openstack/placement/v1/allocationcandidates/microversions.go new file mode 100644 index 0000000000..56b3c3c41b --- /dev/null +++ b/openstack/placement/v1/allocationcandidates/microversions.go @@ -0,0 +1,52 @@ +package allocationcandidates + +import "github.com/gophercloud/gophercloud/v2/pagination" + +// AllocationCandidates110 represents the response from a List allocation +// candidates request for microversions 1.10-1.11. +// In these versions the allocations field is an array rather than a dictionary. +type AllocationCandidates110 struct { + // AllocationRequests is a list of objects that contain information + // for creating a later allocation claim request. + AllocationRequests []AllocationRequest110 `json:"allocation_requests"` + + // ProviderSummaries is a dictionary keyed by resource provider UUID of + // inventory/capacity information for providers in the allocation_requests. + ProviderSummaries map[string]ProviderSummary110 `json:"provider_summaries"` +} + +// AllocationRequest110 represents a single allocation request for +// microversions 1.10-1.11 where allocations is an array. +type AllocationRequest110 struct { + // Allocations is a list of allocation resources per provider. + Allocations []AllocationRequest110Resource `json:"allocations"` +} + +// AllocationRequest110Resource represents a single provider allocation +// within a 1.10-1.11 allocation request. +type AllocationRequest110Resource struct { + // ResourceProvider contains the UUID of the resource provider. + ResourceProvider AllocationRequest110ResourceProvider `json:"resource_provider"` + + // Resources is a dictionary of resource class names to the amount requested. + Resources map[string]int `json:"resources"` +} + +// AllocationRequest110ResourceProvider contains the UUID of a resource provider. +type AllocationRequest110ResourceProvider struct { + UUID string `json:"uuid"` +} + +// ProviderSummary110 represents a provider summary for microversions 1.10-1.11 +// which only includes resources (no traits or parent/root UUIDs). +type ProviderSummary110 struct { + // Resources is a dictionary of resource class names to capacity/usage info. + Resources map[string]ProviderSummaryResource `json:"resources"` +} + +// ExtractAllocationCandidates110 interprets an AllocationCandidatesPage as AllocationCandidates110 (microversions 1.10-1.11). +func ExtractAllocationCandidates110(r pagination.Page) (*AllocationCandidates110, error) { + var s AllocationCandidates110 + err := (r.(AllocationCandidatesPage)).ExtractInto(&s) + return &s, err +} diff --git a/openstack/placement/v1/allocationcandidates/requests.go b/openstack/placement/v1/allocationcandidates/requests.go new file mode 100644 index 0000000000..02530a7cf2 --- /dev/null +++ b/openstack/placement/v1/allocationcandidates/requests.go @@ -0,0 +1,140 @@ +package allocationcandidates + +import ( + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToAllocationCandidatesListQuery() (string, error) +} + +// ResourceGroup represents a granular resource request group. +// It is not a standalone request type, but becomes part of ListOpts, +// keyed by a user-defined suffix string. +// Available in version >= 1.25. +type ResourceGroup struct { + // Resources is a comma-separated list of resource amounts, e.g. "VCPU:1,MEMORY_MB:1024". + // Becomes the resourcesN query parameter. + Resources string + + // Required is a list of trait expressions for this group. + // Each entry becomes one requiredN query parameter. + // Available in version >= 1.39: can be repeated; supports in: syntax. + Required []string + + // MemberOf is an aggregate UUID or the prefix in: followed by a + // comma-separated list of aggregate UUIDs for this group. + // Becomes the member_ofN query parameter. + // Available in version >= 1.32: forbidden aggregates can be expressed + // with a ! prefix or the !in: prefix. + MemberOf string + + // InTree filters results to include only providers in the same tree as + // the specified provider UUID for this group. + // Becomes the in_treeN query parameter. + // Available in version >= 1.31. + InTree string +} + +// ListOpts allows filtering of allocation candidates. +type ListOpts struct { + // Resources is a comma-separated list of resource amounts that providers + // must collectively have capacity to serve, e.g. "VCPU:4,DISK_GB:64,MEMORY_MB:2048". + Resources string `q:"resources"` + + // Required is a comma-separated list of traits that a provider must have. + // Available in version >= 1.17. + // Available in version >= 1.22: prefix with ! for forbidden traits. + // Available in version >= 1.39: can be repeated and supports in: syntax. + Required []string `q:"required"` + + // MemberOf is a string representing an aggregate UUID, or the prefix in: + // followed by a comma-separated list of aggregate UUIDs. + // Available in version >= 1.21. + // Available in version >= 1.24: can be specified multiple times. + MemberOf []string `q:"member_of"` + + // InTree is a resource provider UUID. When supplied, filters candidates to + // only those providers that are in the same tree. + // Available in version >= 1.31. + InTree string `q:"in_tree"` + + // GroupPolicy indicates how the groups should interact when more than one + // resourcesN parameter is supplied. Valid values are "none" and "isolate". + // Available in version >= 1.25. + GroupPolicy string `q:"group_policy"` + + // Limit is a positive integer used to limit the maximum number of + // allocation candidates returned. + // Available in version >= 1.16. + Limit int `q:"limit"` + + // RootRequired is a comma-separated list of trait requirements that the + // root provider of the (non-sharing) tree must satisfy. + // Available in version >= 1.35. + RootRequired string `q:"root_required"` + + // SameSubtree is a comma-separated list of request group suffix strings. + // At least one of the resource providers satisfying a specified request group + // must be an ancestor of the rest. + // Available in version >= 1.36. + SameSubtree []string `q:"same_subtree"` + + // ResourceGroups allows specifying suffixed granular resource request groups. + // The map key is the non-empty group suffix (e.g. "1", "_NET1", "_STORAGE"). + // Use the top-level Resources/Required/MemberOf/InTree fields for the + // unsuffixed (default) group; do not use an empty string key here. + // In microversions 1.25-1.32 the suffix must be a numeric string. + // Starting from microversion 1.33 it can be 1-64 characters [a-zA-Z0-9_-]. + // Available in version >= 1.25. + ResourceGroups map[string]ResourceGroup `q:"-"` +} + +// ToAllocationCandidatesListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToAllocationCandidatesListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + + params := q.Query() + for suffix, group := range opts.ResourceGroups { + if group.Resources != "" { + params.Add("resources"+suffix, group.Resources) + } + for _, required := range group.Required { + if required != "" { + params.Add("required"+suffix, required) + } + } + if group.MemberOf != "" { + params.Add("member_of"+suffix, group.MemberOf) + } + if group.InTree != "" { + params.Add("in_tree"+suffix, group.InTree) + } + } + q.RawQuery = params.Encode() + + return q.String(), nil +} + +// List makes a request against the API to list allocation candidates. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + + if opts != nil { + query, err := opts.ToAllocationCandidatesListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return AllocationCandidatesPage{pagination.SinglePageBase(r)} + }) +} diff --git a/openstack/placement/v1/allocationcandidates/results.go b/openstack/placement/v1/allocationcandidates/results.go new file mode 100644 index 0000000000..8b94a0a7f5 --- /dev/null +++ b/openstack/placement/v1/allocationcandidates/results.go @@ -0,0 +1,93 @@ +package allocationcandidates + +import ( + "encoding/json" + + "github.com/gophercloud/gophercloud/v2/pagination" +) + +// AllocationCandidates represents the response from a List allocation +// candidates request for microversions 1.12 and above. +type AllocationCandidates struct { + // AllocationRequests is a list of objects that contain information + // for creating a later allocation claim request. + AllocationRequests []AllocationRequest `json:"allocation_requests"` + + // ProviderSummaries is a dictionary keyed by resource provider UUID of + // inventory/capacity information for providers in the allocation_requests. + ProviderSummaries map[string]ProviderSummary `json:"provider_summaries"` +} + +// AllocationRequest represents a single allocation request within the +// allocation candidates response. +type AllocationRequest struct { + // Allocations is a dictionary of resource allocations keyed by + // resource provider UUID. + Allocations map[string]AllocationRequestResource `json:"allocations"` + + // Mappings is a dictionary associating request group suffixes with a + // list of UUIDs identifying the resource providers that satisfied each group. + // Available in version >= 1.34. + Mappings *map[string][]string `json:"mappings"` +} + +// AllocationRequestResource represents the resources requested from a +// single resource provider within an allocation request. +type AllocationRequestResource struct { + // Resources is a dictionary of resource class names to the amount requested. + Resources map[string]int `json:"resources"` +} + +// ProviderSummary represents the summary of a resource provider's inventory +// and traits. +type ProviderSummary struct { + // Resources is a dictionary of resource class names to capacity/usage info. + Resources map[string]ProviderSummaryResource `json:"resources"` + + // Traits is a list of traits the resource provider has associated with it. + // Available in version >= 1.17. + Traits *[]string `json:"traits"` + + // ParentProviderUUID is the UUID of the immediate parent of the resource provider. + // Available in version >= 1.29. + ParentProviderUUID *string `json:"parent_provider_uuid"` + + // RootProviderUUID is the UUID of the top-most provider in this provider tree. + // Available in version >= 1.29. + RootProviderUUID *string `json:"root_provider_uuid"` +} + +// ProviderSummaryResource represents the capacity and usage of a single +// resource class for a provider. +type ProviderSummaryResource struct { + // Capacity is the amount of the resource that the provider can accommodate. + Capacity int `json:"capacity"` + + // Used is the amount of the resource that has been already allocated. + Used int `json:"used"` +} + +// AllocationCandidatesPage is the page returned from a List call. +type AllocationCandidatesPage struct { + pagination.SinglePageBase +} + +// IsEmpty determines if an AllocationCandidatesPage contains any results. +// It avoids full deserialization so that it works across all microversions, +func (page AllocationCandidatesPage) IsEmpty() (bool, error) { + var s struct { + AllocationRequests []json.RawMessage `json:"allocation_requests"` + } + err := page.ExtractInto(&s) + if err != nil { + return false, err + } + return len(s.AllocationRequests) == 0, nil +} + +// ExtractAllocationCandidates interprets an AllocationCandidatesPage as AllocationCandidates (microversion 1.12+). +func ExtractAllocationCandidates(r pagination.Page) (*AllocationCandidates, error) { + var s AllocationCandidates + err := (r.(AllocationCandidatesPage)).ExtractInto(&s) + return &s, err +} diff --git a/openstack/placement/v1/allocationcandidates/testing/doc.go b/openstack/placement/v1/allocationcandidates/testing/doc.go new file mode 100644 index 0000000000..7603f836a0 --- /dev/null +++ b/openstack/placement/v1/allocationcandidates/testing/doc.go @@ -0,0 +1 @@ +package testing diff --git a/openstack/placement/v1/allocationcandidates/testing/fixtures_test.go b/openstack/placement/v1/allocationcandidates/testing/fixtures_test.go new file mode 100644 index 0000000000..f69a1ffb62 --- /dev/null +++ b/openstack/placement/v1/allocationcandidates/testing/fixtures_test.go @@ -0,0 +1,578 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/v2/internal/ptr" + "github.com/gophercloud/gophercloud/v2/openstack/placement/v1/allocationcandidates" + th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/client" +) + +const AllocationCandidatesBody = ` +{ + "allocation_requests": [ + { + "allocations": { + "rp-uuid-1": { + "resources": { + "VCPU": 1, + "MEMORY_MB": 1024 + } + }, + "rp-uuid-2": { + "resources": { + "DISK_GB": 100 + } + } + }, + "mappings": { + "": ["rp-uuid-1"], + "_DISK": ["rp-uuid-2"] + } + } + ], + "provider_summaries": { + "rp-uuid-1": { + "resources": { + "VCPU": { + "capacity": 16, + "used": 0 + }, + "MEMORY_MB": { + "capacity": 32768, + "used": 0 + } + }, + "traits": ["HW_CPU_X86_AVX2", "HW_CPU_X86_SSE"], + "parent_provider_uuid": null, + "root_provider_uuid": "rp-uuid-1" + }, + "rp-uuid-2": { + "resources": { + "DISK_GB": { + "capacity": 1900, + "used": 0 + } + }, + "traits": ["MISC_SHARES_VIA_AGGREGATE"], + "parent_provider_uuid": null, + "root_provider_uuid": "rp-uuid-2" + } + } +} +` + +const AllocationCandidatesBodyPre134 = ` +{ + "allocation_requests": [ + { + "allocations": { + "rp-uuid-1": { + "resources": { + "VCPU": 1, + "MEMORY_MB": 1024 + } + }, + "rp-uuid-2": { + "resources": { + "DISK_GB": 100 + } + } + } + } + ], + "provider_summaries": { + "rp-uuid-1": { + "resources": { + "VCPU": { + "capacity": 16, + "used": 0 + }, + "MEMORY_MB": { + "capacity": 32768, + "used": 0 + } + }, + "traits": ["HW_CPU_X86_AVX2", "HW_CPU_X86_SSE"], + "parent_provider_uuid": null, + "root_provider_uuid": "rp-uuid-1" + }, + "rp-uuid-2": { + "resources": { + "DISK_GB": { + "capacity": 1900, + "used": 0 + } + }, + "traits": ["MISC_SHARES_VIA_AGGREGATE"], + "parent_provider_uuid": null, + "root_provider_uuid": "rp-uuid-2" + } + } +} +` + +const AllocationCandidatesBodyPre129 = ` +{ + "allocation_requests": [ + { + "allocations": { + "rp-uuid-1": { + "resources": { + "VCPU": 1, + "MEMORY_MB": 1024 + } + }, + "rp-uuid-2": { + "resources": { + "DISK_GB": 100 + } + } + } + } + ], + "provider_summaries": { + "rp-uuid-1": { + "resources": { + "VCPU": { + "capacity": 16, + "used": 0 + }, + "MEMORY_MB": { + "capacity": 32768, + "used": 0 + } + }, + "traits": ["HW_CPU_X86_AVX2", "HW_CPU_X86_SSE"] + }, + "rp-uuid-2": { + "resources": { + "DISK_GB": { + "capacity": 1900, + "used": 0 + } + }, + "traits": ["MISC_SHARES_VIA_AGGREGATE"] + } + } +} +` + +const AllocationCandidatesBodyPre117 = ` +{ + "allocation_requests": [ + { + "allocations": { + "rp-uuid-1": { + "resources": { + "VCPU": 1, + "MEMORY_MB": 1024 + } + }, + "rp-uuid-2": { + "resources": { + "DISK_GB": 100 + } + } + } + } + ], + "provider_summaries": { + "rp-uuid-1": { + "resources": { + "VCPU": { + "capacity": 16, + "used": 0 + }, + "MEMORY_MB": { + "capacity": 32768, + "used": 0 + } + } + }, + "rp-uuid-2": { + "resources": { + "DISK_GB": { + "capacity": 1900, + "used": 0 + } + } + } + } +} +` + +const AllocationCandidatesBody110 = ` +{ + "allocation_requests": [ + { + "allocations": [ + { + "resource_provider": { + "uuid": "rp-uuid-1" + }, + "resources": { + "VCPU": 1, + "MEMORY_MB": 1024 + } + } + ] + } + ], + "provider_summaries": { + "rp-uuid-1": { + "resources": { + "VCPU": { + "capacity": 16, + "used": 0 + }, + "MEMORY_MB": { + "capacity": 32768, + "used": 0 + } + } + } + } +} +` + +const AllocationCandidatesEmptyBody = ` +{ + "allocation_requests": [], + "provider_summaries": {} +} +` + +var ExpectedAllocationCandidates = allocationcandidates.AllocationCandidates{ + AllocationRequests: []allocationcandidates.AllocationRequest{ + { + Allocations: map[string]allocationcandidates.AllocationRequestResource{ + "rp-uuid-1": { + Resources: map[string]int{ + "VCPU": 1, + "MEMORY_MB": 1024, + }, + }, + "rp-uuid-2": { + Resources: map[string]int{ + "DISK_GB": 100, + }, + }, + }, + Mappings: &map[string][]string{ + "": {"rp-uuid-1"}, + "_DISK": {"rp-uuid-2"}, + }, + }, + }, + ProviderSummaries: map[string]allocationcandidates.ProviderSummary{ + "rp-uuid-1": { + Resources: map[string]allocationcandidates.ProviderSummaryResource{ + "VCPU": { + Capacity: 16, + Used: 0, + }, + "MEMORY_MB": { + Capacity: 32768, + Used: 0, + }, + }, + Traits: ptr.To([]string{"HW_CPU_X86_AVX2", "HW_CPU_X86_SSE"}), + ParentProviderUUID: nil, + RootProviderUUID: ptr.To("rp-uuid-1"), + }, + "rp-uuid-2": { + Resources: map[string]allocationcandidates.ProviderSummaryResource{ + "DISK_GB": { + Capacity: 1900, + Used: 0, + }, + }, + Traits: ptr.To([]string{"MISC_SHARES_VIA_AGGREGATE"}), + ParentProviderUUID: nil, + RootProviderUUID: ptr.To("rp-uuid-2"), + }, + }, +} + +var ExpectedAllocationCandidatesPre134 = allocationcandidates.AllocationCandidates{ + AllocationRequests: []allocationcandidates.AllocationRequest{ + { + Allocations: map[string]allocationcandidates.AllocationRequestResource{ + "rp-uuid-1": { + Resources: map[string]int{ + "VCPU": 1, + "MEMORY_MB": 1024, + }, + }, + "rp-uuid-2": { + Resources: map[string]int{ + "DISK_GB": 100, + }, + }, + }, + }, + }, + ProviderSummaries: map[string]allocationcandidates.ProviderSummary{ + "rp-uuid-1": { + Resources: map[string]allocationcandidates.ProviderSummaryResource{ + "VCPU": { + Capacity: 16, + Used: 0, + }, + "MEMORY_MB": { + Capacity: 32768, + Used: 0, + }, + }, + Traits: ptr.To([]string{"HW_CPU_X86_AVX2", "HW_CPU_X86_SSE"}), + ParentProviderUUID: nil, + RootProviderUUID: ptr.To("rp-uuid-1"), + }, + "rp-uuid-2": { + Resources: map[string]allocationcandidates.ProviderSummaryResource{ + "DISK_GB": { + Capacity: 1900, + Used: 0, + }, + }, + Traits: ptr.To([]string{"MISC_SHARES_VIA_AGGREGATE"}), + ParentProviderUUID: nil, + RootProviderUUID: ptr.To("rp-uuid-2"), + }, + }, +} + +var ExpectedAllocationCandidatesPre129 = allocationcandidates.AllocationCandidates{ + AllocationRequests: []allocationcandidates.AllocationRequest{ + { + Allocations: map[string]allocationcandidates.AllocationRequestResource{ + "rp-uuid-1": { + Resources: map[string]int{ + "VCPU": 1, + "MEMORY_MB": 1024, + }, + }, + "rp-uuid-2": { + Resources: map[string]int{ + "DISK_GB": 100, + }, + }, + }, + }, + }, + ProviderSummaries: map[string]allocationcandidates.ProviderSummary{ + "rp-uuid-1": { + Resources: map[string]allocationcandidates.ProviderSummaryResource{ + "VCPU": { + Capacity: 16, + Used: 0, + }, + "MEMORY_MB": { + Capacity: 32768, + Used: 0, + }, + }, + Traits: ptr.To([]string{"HW_CPU_X86_AVX2", "HW_CPU_X86_SSE"}), + }, + "rp-uuid-2": { + Resources: map[string]allocationcandidates.ProviderSummaryResource{ + "DISK_GB": { + Capacity: 1900, + Used: 0, + }, + }, + Traits: ptr.To([]string{"MISC_SHARES_VIA_AGGREGATE"}), + }, + }, +} + +var ExpectedAllocationCandidatesPre117 = allocationcandidates.AllocationCandidates{ + AllocationRequests: []allocationcandidates.AllocationRequest{ + { + Allocations: map[string]allocationcandidates.AllocationRequestResource{ + "rp-uuid-1": { + Resources: map[string]int{ + "VCPU": 1, + "MEMORY_MB": 1024, + }, + }, + "rp-uuid-2": { + Resources: map[string]int{ + "DISK_GB": 100, + }, + }, + }, + }, + }, + ProviderSummaries: map[string]allocationcandidates.ProviderSummary{ + "rp-uuid-1": { + Resources: map[string]allocationcandidates.ProviderSummaryResource{ + "VCPU": { + Capacity: 16, + Used: 0, + }, + "MEMORY_MB": { + Capacity: 32768, + Used: 0, + }, + }, + }, + "rp-uuid-2": { + Resources: map[string]allocationcandidates.ProviderSummaryResource{ + "DISK_GB": { + Capacity: 1900, + Used: 0, + }, + }, + }, + }, +} + +var ExpectedAllocationCandidates110 = allocationcandidates.AllocationCandidates110{ + AllocationRequests: []allocationcandidates.AllocationRequest110{ + { + Allocations: []allocationcandidates.AllocationRequest110Resource{ + { + ResourceProvider: allocationcandidates.AllocationRequest110ResourceProvider{ + UUID: "rp-uuid-1", + }, + Resources: map[string]int{ + "VCPU": 1, + "MEMORY_MB": 1024, + }, + }, + }, + }, + }, + ProviderSummaries: map[string]allocationcandidates.ProviderSummary110{ + "rp-uuid-1": { + Resources: map[string]allocationcandidates.ProviderSummaryResource{ + "VCPU": { + Capacity: 16, + Used: 0, + }, + "MEMORY_MB": { + Capacity: 32768, + Used: 0, + }, + }, + }, + }, +} + +func HandleListAllocationCandidatesSuccess(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/allocation_candidates", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, AllocationCandidatesBody) + }) +} + +func HandleListAllocationCandidatesPre134Success(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/allocation_candidates", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, AllocationCandidatesBodyPre134) + }) +} + +func HandleListAllocationCandidatesPre129Success(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/allocation_candidates", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, AllocationCandidatesBodyPre129) + }) +} + +func HandleListAllocationCandidatesPre117Success(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/allocation_candidates", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, AllocationCandidatesBodyPre117) + }) +} + +func HandleListAllocationCandidates110Success(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/allocation_candidates", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, AllocationCandidatesBody110) + }) +} + +func HandleListAllocationCandidatesEmptySuccess(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/allocation_candidates", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, AllocationCandidatesEmptyBody) + }) +} + +func HandleListAllocationCandidatesBadRequest(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/allocation_candidates", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusBadRequest) + + fmt.Fprint(w, `{"errors": [{"status": 400, "detail": "Invalid resources parameter."}]}`) + }) +} + +func HandleListAllocationCandidatesWithFullQuerySuccess(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/allocation_candidates", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + q := r.URL.Query() + th.AssertEquals(t, "VCPU:1,MEMORY_MB:1024", q.Get("resources")) + th.AssertEquals(t, "5", q.Get("limit")) + th.AssertEquals(t, "isolate", q.Get("group_policy")) + th.AssertEquals(t, "SRIOV_NET_VF:1", q.Get("resources1")) + th.AssertEquals(t, "CUSTOM_PHYSNET1", q.Get("required1")) + // required is repeated; order matches the ListOpts.Required slice order. + th.AssertDeepEquals(t, []string{"HW_CPU_X86_SSE", "!HW_CPU_X86_AVX2"}, q["required"]) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, AllocationCandidatesBody) + }) +} diff --git a/openstack/placement/v1/allocationcandidates/testing/requests_test.go b/openstack/placement/v1/allocationcandidates/testing/requests_test.go new file mode 100644 index 0000000000..cbab71e0dd --- /dev/null +++ b/openstack/placement/v1/allocationcandidates/testing/requests_test.go @@ -0,0 +1,160 @@ +package testing + +import ( + "context" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack/placement/v1/allocationcandidates" + th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/client" +) + +func TestListAllocationCandidatesSuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleListAllocationCandidatesSuccess(t, fakeServer) + + page, err := allocationcandidates.List(client.ServiceClient(fakeServer), allocationcandidates.ListOpts{ + Resources: "VCPU:1,MEMORY_MB:1024", + }).AllPages(context.TODO()) + th.AssertNoErr(t, err) + actual, err := allocationcandidates.ExtractAllocationCandidates(page) + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedAllocationCandidates, *actual) +} + +func TestListAllocationCandidatesPre134Success(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleListAllocationCandidatesPre134Success(t, fakeServer) + + page, err := allocationcandidates.List(client.ServiceClient(fakeServer), allocationcandidates.ListOpts{ + Resources: "VCPU:1,MEMORY_MB:1024", + }).AllPages(context.TODO()) + th.AssertNoErr(t, err) + actual, err := allocationcandidates.ExtractAllocationCandidates(page) + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedAllocationCandidatesPre134, *actual) +} + +func TestListAllocationCandidatesPre129Success(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleListAllocationCandidatesPre129Success(t, fakeServer) + + page, err := allocationcandidates.List(client.ServiceClient(fakeServer), allocationcandidates.ListOpts{ + Resources: "VCPU:1,MEMORY_MB:1024", + }).AllPages(context.TODO()) + th.AssertNoErr(t, err) + actual, err := allocationcandidates.ExtractAllocationCandidates(page) + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedAllocationCandidatesPre129, *actual) +} + +func TestListAllocationCandidatesPre117Success(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleListAllocationCandidatesPre117Success(t, fakeServer) + + page, err := allocationcandidates.List(client.ServiceClient(fakeServer), allocationcandidates.ListOpts{ + Resources: "VCPU:1,MEMORY_MB:1024", + }).AllPages(context.TODO()) + th.AssertNoErr(t, err) + actual, err := allocationcandidates.ExtractAllocationCandidates(page) + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedAllocationCandidatesPre117, *actual) +} + +func TestListAllocationCandidates110Success(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleListAllocationCandidates110Success(t, fakeServer) + + page, err := allocationcandidates.List(client.ServiceClient(fakeServer), allocationcandidates.ListOpts{ + Resources: "VCPU:1,MEMORY_MB:1024", + }).AllPages(context.TODO()) + th.AssertNoErr(t, err) + actual, err := allocationcandidates.ExtractAllocationCandidates110(page) + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedAllocationCandidates110, *actual) +} + +func TestListAllocationCandidatesEmptySuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleListAllocationCandidatesEmptySuccess(t, fakeServer) + + page, err := allocationcandidates.List(client.ServiceClient(fakeServer), allocationcandidates.ListOpts{ + Resources: "VCPU:1,MEMORY_MB:1024", + }).AllPages(context.TODO()) + th.AssertNoErr(t, err) + actual, err := allocationcandidates.ExtractAllocationCandidates(page) + th.AssertNoErr(t, err) + th.AssertEquals(t, 0, len(actual.AllocationRequests)) + th.AssertEquals(t, 0, len(actual.ProviderSummaries)) + + isEmpty, err := page.IsEmpty() + th.AssertNoErr(t, err) + th.AssertTrue(t, isEmpty) +} + +func TestIsEmpty110Success(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleListAllocationCandidates110Success(t, fakeServer) + + page, err := allocationcandidates.List(client.ServiceClient(fakeServer), allocationcandidates.ListOpts{ + Resources: "VCPU:1,MEMORY_MB:1024", + }).AllPages(context.TODO()) + th.AssertNoErr(t, err) + + isEmpty, err := page.IsEmpty() + th.AssertNoErr(t, err) + th.AssertFalse(t, isEmpty) +} + +func TestListAllocationCandidatesBadRequest(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleListAllocationCandidatesBadRequest(t, fakeServer) + + _, err := allocationcandidates.List(client.ServiceClient(fakeServer), allocationcandidates.ListOpts{ + Resources: "INVALID", + }).AllPages(context.TODO()) + th.AssertErr(t, err) + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusBadRequest)) +} + +func TestListAllocationCandidatesWithFullQuerySuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleListAllocationCandidatesWithFullQuerySuccess(t, fakeServer) + + page, err := allocationcandidates.List(client.ServiceClient(fakeServer), allocationcandidates.ListOpts{ + Resources: "VCPU:1,MEMORY_MB:1024", + Required: []string{"HW_CPU_X86_SSE", "!HW_CPU_X86_AVX2"}, + Limit: 5, + GroupPolicy: "isolate", + ResourceGroups: map[string]allocationcandidates.ResourceGroup{ + "1": { + Resources: "SRIOV_NET_VF:1", + Required: []string{"CUSTOM_PHYSNET1"}, + }, + }, + }).AllPages(context.TODO()) + th.AssertNoErr(t, err) + actual, err := allocationcandidates.ExtractAllocationCandidates(page) + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedAllocationCandidates, *actual) +} diff --git a/openstack/placement/v1/allocationcandidates/urls.go b/openstack/placement/v1/allocationcandidates/urls.go new file mode 100644 index 0000000000..fd3f4a6743 --- /dev/null +++ b/openstack/placement/v1/allocationcandidates/urls.go @@ -0,0 +1,9 @@ +package allocationcandidates + +import "github.com/gophercloud/gophercloud/v2" + +const apiName = "allocation_candidates" + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(apiName) +} diff --git a/openstack/placement/v1/allocations/doc.go b/openstack/placement/v1/allocations/doc.go new file mode 100644 index 0000000000..8233418bb1 --- /dev/null +++ b/openstack/placement/v1/allocations/doc.go @@ -0,0 +1,109 @@ +/* +Package allocations manages consumer allocations from the OpenStack Placement service. + +Allocation API requests are available starting from microversion 1.0. + +# Example to get allocations for a consumer + + allocs, err := allocations.Get(context.TODO(), placementClient, consumerUUID).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", allocs) + +# Example to set allocations for a new consumer (microversion 1.28+) + +When creating allocations for a consumer that does not yet exist, set +ConsumerGeneration to nil. It will be serialized as JSON null, telling the +server that no prior allocation is expected. This is different from omitting +the field: nil is required, not optional. + + placementClient.Microversion = "1.28" + + err := allocations.Update(context.TODO(), placementClient, consumerUUID, allocations.UpdateOpts{ + Allocations: map[string]allocations.ProviderAllocationsOpts{ + providerUUID: { + Resources: map[string]int{"VCPU": 2, "MEMORY_MB": 2048}, + }, + }, + ProjectID: projectID, + UserID: userID, + ConsumerGeneration: nil, + }).ExtractErr() + if err != nil { + panic(err) + } + +# Example to update allocations for an existing consumer (microversion 1.28+) + +When updating allocations for an existing consumer, retrieve the current +generation first and pass it in. + + placementClient.Microversion = "1.28" + + existing, err := allocations.Get(context.TODO(), placementClient, consumerUUID).Extract() + if err != nil { + panic(err) + } + + err = allocations.Update(context.TODO(), placementClient, consumerUUID, allocations.UpdateOpts{ + Allocations: map[string]allocations.ProviderAllocationsOpts{ + providerUUID: { + Resources: map[string]int{"VCPU": 4, "MEMORY_MB": 4096}, + }, + }, + ProjectID: *existing.ProjectID, + UserID: *existing.UserID, + ConsumerGeneration: existing.ConsumerGeneration, + }).ExtractErr() + if err != nil { + panic(err) + } + +# Example to delete all allocations for a consumer + +Note: using Update with an empty Allocations map is generally safer because +it is protected by the consumer generation check. Use Delete only when you +do not need that protection. + + err = allocations.Delete(context.TODO(), placementClient, consumerUUID).ExtractErr() + if err != nil { + panic(err) + } + +# Example to atomically set allocations for multiple consumers (microversion 1.13+) + +Manage sets allocations for any number of consumers in a single atomic +request. The map key is the consumer UUID. Use microversion 1.28 or later +for generation-safe writes. + + placementClient.Microversion = "1.28" + + err = allocations.Manage(context.TODO(), placementClient, allocations.ManageOpts{ + consumer1UUID: { + Allocations: map[string]allocations.ProviderAllocationsOpts{ + providerUUID: { + Resources: map[string]int{"VCPU": 2}, + }, + }, + ProjectID: projectID, + UserID: userID, + ConsumerGeneration: nil, + }, + consumer2UUID: { + Allocations: map[string]allocations.ProviderAllocationsOpts{ + providerUUID: { + Resources: map[string]int{"VCPU": 1}, + }, + }, + ProjectID: projectID, + UserID: userID, + ConsumerGeneration: nil, + }, + }).ExtractErr() + if err != nil { + panic(err) + } +*/ +package allocations diff --git a/openstack/placement/v1/allocations/requests.go b/openstack/placement/v1/allocations/requests.go new file mode 100644 index 0000000000..6640681f4c --- /dev/null +++ b/openstack/placement/v1/allocations/requests.go @@ -0,0 +1,137 @@ +package allocations + +import ( + "context" + "encoding/json" + + "github.com/gophercloud/gophercloud/v2" +) + +// Get retrieves the allocations for a specific consumer by its UUID. +func Get(ctx context.Context, client *gophercloud.ServiceClient, consumerUUID string) (r GetResult) { + resp, err := client.Get(ctx, getURL(client, consumerUUID), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// ProviderAllocationsOpts specifies the resources to consume from a single resource +// provider in a write request. +type ProviderAllocationsOpts struct { + // Resources maps resource class names to the integer amount to consume. + Resources map[string]int `json:"resources"` +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToAllocationUpdateMap() (map[string]any, error) +} + +// UpdateOpts specifies the allocation to be set for a consumer. +// +// This requires microversion 1.28 or later. Write operations on allocations +// using earlier microversions are not safe in concurrent environments. +// +// ConsumerGeneration must be set to nil when creating allocations for a new +// consumer (it serializes as JSON null, which signals to the server that no +// prior allocation is expected). For an existing consumer, set it to the +// generation value returned by a prior Get call. A mismatch causes a 409 +// Conflict response, allowing the caller to retry safely. +type UpdateOpts struct { + // Allocations maps resource provider UUIDs to the resources to consume. + Allocations map[string]ProviderAllocationsOpts `json:"allocations"` + + // Required from microversion 1.8. + ProjectID string `json:"project_id"` + + // Required from microversion 1.8. + UserID string `json:"user_id"` + + // ConsumerGeneration must be nil for new consumers (serializes as null) or + // the current generation for existing consumers. + // See the UpdateOpts type documentation for details. + ConsumerGeneration *int `json:"consumer_generation"` + + // Required from microversion 1.38. + ConsumerType string `json:"consumer_type,omitempty"` +} + +// ToAllocationUpdateMap constructs a request body from UpdateOpts. +func (opts UpdateOpts) ToAllocationUpdateMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// Update replaces all allocations for a consumer. The operation is atomic. +// +// Requires microversion 1.28 or later. +func Update(ctx context.Context, client *gophercloud.ServiceClient, consumerUUID string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToAllocationUpdateMap() + if err != nil { + r.Err = err + return + } + resp, err := client.Put(ctx, updateURL(client, consumerUUID), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// Delete removes all allocations for a consumer. Returns 204 on success or +// 404 if the consumer does not exist. +// +// Note: using Update with an empty Allocations map is generally safer because +// it is protected by the consumer generation check, preventing accidental +// deletion under concurrent updates. +func Delete(ctx context.Context, client *gophercloud.ServiceClient, consumerUUID string) (r DeleteResult) { + resp, err := client.Delete(ctx, deleteURL(client, consumerUUID), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// ManageOptsBuilder allows extensions to add additional parameters to the +// Manage request. +type ManageOptsBuilder interface { + ToAllocationManageMap() (map[string]any, error) +} + +// ManageOpts specifies allocations for multiple consumers in a single atomic +// operation. The map key is the consumer UUID; the value describes the +// allocations and consumer metadata for that consumer. +// +// This requires microversion 1.13 or later. For generation-safe writes, use +// microversion 1.28 or later and set ConsumerGeneration on each entry. +// +// Set ConsumerGeneration to nil for new consumers (serializes as JSON null). +// For existing consumers, set it to the generation returned by a prior Get +// call. A mismatch on any entry causes a 409 Conflict for the entire batch. +type ManageOpts map[string]UpdateOpts + +// ToAllocationManageMap constructs a request body from ManageOpts. +func (opts ManageOpts) ToAllocationManageMap() (map[string]any, error) { + b, err := json.Marshal(opts) + if err != nil { + return nil, err + } + var m map[string]any + err = json.Unmarshal(b, &m) + return m, err +} + +// Manage atomically sets allocations for one or more consumers in a single +// request. +// +// Requires microversion 1.13 or later. For generation-safe writes, use +// microversion 1.28 or later. +func Manage(ctx context.Context, client *gophercloud.ServiceClient, opts ManageOptsBuilder) (r ManageResult) { + b, err := opts.ToAllocationManageMap() + if err != nil { + r.Err = err + return + } + resp, err := client.Post(ctx, manageURL(client), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/placement/v1/allocations/results.go b/openstack/placement/v1/allocations/results.go new file mode 100644 index 0000000000..5ca69a8894 --- /dev/null +++ b/openstack/placement/v1/allocations/results.go @@ -0,0 +1,64 @@ +package allocations + +import "github.com/gophercloud/gophercloud/v2" + +// ProviderAllocations represents the per-provider portion of an allocations response. +type ProviderAllocations struct { + Generation int `json:"generation"` + + // Resources maps resource class names to the integer amount consumed from the provider. + Resources map[string]int `json:"resources"` +} + +// Allocations represents the allocations for a single consumer. +// The Allocations field maps resource provider UUIDs to ProviderAllocations, +// describing how much of each resource class is consumed from each provider. +type Allocations struct { + // Allocations maps resource provider UUIDs to the resources consumed from each. + Allocations map[string]ProviderAllocations `json:"allocations"` + + // Available from microversion 1.12. + // Will be absent when listing allocations for a consumer UUID that has no allocations. + ProjectID *string `json:"project_id"` + + // Available from microversion 1.12. + // Will be absent when listing allocations for a consumer UUID that has no allocations. + UserID *string `json:"user_id"` + + // Available from microversion 1.28. + ConsumerGeneration *int `json:"consumer_generation"` + + // Available from microversion 1.38. + ConsumerType *string `json:"consumer_type"` +} + +// GetResult is the result of a Get operation. Call its Extract method +// to interpret it as an Allocations. +type GetResult struct { + gophercloud.Result +} + +// Extract interprets a GetResult as Allocations. +func (r GetResult) Extract() (*Allocations, error) { + var s Allocations + err := r.ExtractInto(&s) + return &s, err +} + +// UpdateResult is the result of an Update operation. Call its ExtractErr +// method to determine if the request succeeded or failed. +type UpdateResult struct { + gophercloud.ErrResult +} + +// DeleteResult is the result of a Delete operation. Call its ExtractErr +// method to determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// ManageResult is the result of a Manage operation. Call its ExtractErr +// method to determine if the request succeeded or failed. +type ManageResult struct { + gophercloud.ErrResult +} diff --git a/openstack/placement/v1/allocations/testing/doc.go b/openstack/placement/v1/allocations/testing/doc.go new file mode 100644 index 0000000000..a06432adf8 --- /dev/null +++ b/openstack/placement/v1/allocations/testing/doc.go @@ -0,0 +1,2 @@ +// Package testing contains allocations unit tests. +package testing diff --git a/openstack/placement/v1/allocations/testing/fixtures_test.go b/openstack/placement/v1/allocations/testing/fixtures_test.go new file mode 100644 index 0000000000..5e75fd4a72 --- /dev/null +++ b/openstack/placement/v1/allocations/testing/fixtures_test.go @@ -0,0 +1,377 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/placement/v1/allocations" + th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/client" +) + +const ConsumerUUID = "ba8f2c8e-0bf7-4a32-aacf-7c11f7f9a321" +const EmptyConsumerUUID = "00000000-0000-0000-0000-000000000000" +const ConflictConsumerUUID = "ffffffff-ffff-ffff-ffff-ffffffffffff" +const NotFoundConsumerUUID = "11111111-1111-1111-1111-111111111111" +const ManageConsumerUUID1 = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" +const ManageConsumerUUID2 = "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb" +const ProviderUUID1 = "7d4f1abe-2f91-4f7a-8872-b70d9fb5c3dd" +const ProviderUUID2 = "f3f97e00-13e1-4c88-a7cd-db3bb4f99357" +const ProjectID = "42a2b0fa980d4f7f873e8f0d8b4e1b0e" +const UserID = "b29d880b2c114d5d9a55748b26b2e41e" + +var GetAllocationsBody = fmt.Sprintf(` +{ + "allocations": { + "%s": { + "generation": 3, + "resources": { + "VCPU": 2, + "MEMORY_MB": 2048 + } + }, + "%s": { + "generation": 7, + "resources": { + "DISK_GB": 50 + } + } + }, + "project_id": "%s", + "user_id": "%s", + "consumer_generation": 2, + "consumer_type": "INSTANCE" +} +`, ProviderUUID1, ProviderUUID2, ProjectID, UserID) + +// GetEmptyAllocationsBody is the response for a consumer UUID that has no +// allocations. Per the Placement API, only the "allocations" key is present; +// project_id, user_id, consumer_generation, and consumer_type are absent. +const GetEmptyAllocationsBody = ` +{ + "allocations": {} +} +` + +var consumerGeneration = 2 +var consumerType = "INSTANCE" +var projectID = ProjectID +var userID = UserID + +var ExpectedAllocations = allocations.Allocations{ + Allocations: map[string]allocations.ProviderAllocations{ + ProviderUUID1: { + Generation: 3, + Resources: map[string]int{ + "VCPU": 2, + "MEMORY_MB": 2048, + }, + }, + ProviderUUID2: { + Generation: 7, + Resources: map[string]int{ + "DISK_GB": 50, + }, + }, + }, + ProjectID: &projectID, + UserID: &userID, + ConsumerGeneration: &consumerGeneration, + ConsumerType: &consumerType, +} + +var ExpectedEmptyAllocations = allocations.Allocations{ + Allocations: map[string]allocations.ProviderAllocations{}, +} + +var UpdateAllocationsRequest = fmt.Sprintf(` +{ + "allocations": { + "%s": { + "resources": { + "VCPU": 2, + "MEMORY_MB": 2048 + } + } + }, + "project_id": "%s", + "user_id": "%s", + "consumer_generation": null +} +`, ProviderUUID1, ProjectID, UserID) + +var UpdateAllocationsExistingRequest = fmt.Sprintf(` +{ + "allocations": { + "%s": { + "resources": { + "VCPU": 2, + "MEMORY_MB": 2048 + } + } + }, + "project_id": "%s", + "user_id": "%s", + "consumer_generation": 1 +} +`, ProviderUUID1, ProjectID, UserID) + +var GetAllocationsAfterUpdateBody = fmt.Sprintf(` +{ + "allocations": { + "%s": { + "generation": 4, + "resources": { + "VCPU": 2, + "MEMORY_MB": 2048 + } + } + }, + "project_id": "%s", + "user_id": "%s", + "consumer_generation": 1 +} +`, ProviderUUID1, ProjectID, UserID) + +var consumerGenerationAfterUpdate = 1 + +var ExpectedAllocationsAfterUpdate = allocations.Allocations{ + Allocations: map[string]allocations.ProviderAllocations{ + ProviderUUID1: { + Generation: 4, + Resources: map[string]int{ + "VCPU": 2, + "MEMORY_MB": 2048, + }, + }, + }, + ProjectID: &projectID, + UserID: &userID, + ConsumerGeneration: &consumerGenerationAfterUpdate, +} + +var ManageAllocationsRequest = fmt.Sprintf(` +{ + "%s": { + "allocations": { + "%s": { + "resources": { + "VCPU": 1 + } + } + }, + "project_id": "%s", + "user_id": "%s", + "consumer_generation": null + }, + "%s": { + "allocations": { + "%s": { + "resources": { + "VCPU": 1 + } + } + }, + "project_id": "%s", + "user_id": "%s", + "consumer_generation": null + } +} +`, ManageConsumerUUID1, ProviderUUID1, ProjectID, UserID, ManageConsumerUUID2, ProviderUUID1, ProjectID, UserID) + +var GetAllocationsAfterManageBody = fmt.Sprintf(` +{ + "allocations": { + "%s": { + "generation": 1, + "resources": { + "VCPU": 1 + } + } + }, + "project_id": "%s", + "user_id": "%s", + "consumer_generation": 1 +} +`, ProviderUUID1, ProjectID, UserID) + +var ExpectedAllocationsAfterManage = allocations.Allocations{ + Allocations: map[string]allocations.ProviderAllocations{ + ProviderUUID1: { + Generation: 1, + Resources: map[string]int{ + "VCPU": 1, + }, + }, + }, + ProjectID: &projectID, + UserID: &userID, + ConsumerGeneration: &consumerGenerationAfterUpdate, +} + +func HandleGetAllocationsSuccess(t *testing.T, fakeServer th.FakeServer) { + url := fmt.Sprintf("/allocations/%s", ConsumerUUID) + + fakeServer.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, GetAllocationsBody) + }) +} + +func HandleGetEmptyAllocationsSuccess(t *testing.T, fakeServer th.FakeServer) { + url := fmt.Sprintf("/allocations/%s", EmptyConsumerUUID) + + fakeServer.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, GetEmptyAllocationsBody) + }) +} + +// HandleUpdateAndGetAllocationsSuccess handles PUT followed by GET on the same URL, +// branching by HTTP method. +func HandleUpdateAndGetAllocationsSuccess(t *testing.T, fakeServer th.FakeServer) { + url := fmt.Sprintf("/allocations/%s", ConsumerUUID) + + fakeServer.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + switch r.Method { + case http.MethodPut: + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, UpdateAllocationsRequest) + w.WriteHeader(http.StatusNoContent) + case http.MethodGet: + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, GetAllocationsAfterUpdateBody) + default: + t.Fatalf("unexpected method: %s", r.Method) + } + }) +} + +// HandleUpdateAllocationsNewConsumerSuccess handles PUT with nil consumer_generation +// (new consumer case), verifying that null is sent in the request body. +func HandleUpdateAllocationsNewConsumerSuccess(t *testing.T, fakeServer th.FakeServer) { + url := fmt.Sprintf("/allocations/%s", EmptyConsumerUUID) + + fakeServer.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + switch r.Method { + case http.MethodPut: + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, UpdateAllocationsRequest) + w.WriteHeader(http.StatusNoContent) + case http.MethodGet: + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, GetAllocationsAfterUpdateBody) + default: + t.Fatalf("unexpected method: %s", r.Method) + } + }) +} + +// HandleUpdateAllocationsConflict simulates a 409 when the consumer_generation +// in the request does not match the server's current value. +func HandleUpdateAllocationsConflict(t *testing.T, fakeServer th.FakeServer) { + url := fmt.Sprintf("/allocations/%s", ConflictConsumerUUID) + + fakeServer.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusConflict) + fmt.Fprint(w, `{"errors":[{"status":409,"title":"Conflict","code":"placement.concurrent_update"}]}`) + }) +} + +// HandleDeleteAndGetAllocationsSuccess handles DELETE followed by GET on the same URL. +// DELETE returns 204; the subsequent GET returns an empty allocations body. +func HandleDeleteAndGetAllocationsSuccess(t *testing.T, fakeServer th.FakeServer) { + url := fmt.Sprintf("/allocations/%s", ConsumerUUID) + + fakeServer.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + switch r.Method { + case http.MethodDelete: + w.WriteHeader(http.StatusNoContent) + case http.MethodGet: + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, GetEmptyAllocationsBody) + default: + t.Fatalf("unexpected method: %s", r.Method) + } + }) +} + +// HandleDeleteAllocationsNotFound simulates a 404 when the consumer does not exist. +func HandleDeleteAllocationsNotFound(t *testing.T, fakeServer th.FakeServer) { + url := fmt.Sprintf("/allocations/%s", NotFoundConsumerUUID) + + fakeServer.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusNotFound) + fmt.Fprint(w, `{"errors":[{"status":404,"title":"Not Found","code":"placement.undefined_code"}]}`) + }) +} + +// HandleManageAllocationsSuccess handles POST to /allocations, verifying the +// request body, then handles GET requests for each managed consumer. +func HandleManageAllocationsSuccess(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/allocations", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ManageAllocationsRequest) + + w.WriteHeader(http.StatusNoContent) + }) + + for _, uuid := range []string{ManageConsumerUUID1, ManageConsumerUUID2} { + uuid := uuid + url := fmt.Sprintf("/allocations/%s", uuid) + fakeServer.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, GetAllocationsAfterManageBody) + }) + } +} + +// HandleManageAllocationsConflict simulates a 409 when a consumer_generation +// in the batch does not match the server's current value. +func HandleManageAllocationsConflict(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/allocations", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusConflict) + fmt.Fprint(w, `{"errors":[{"status":409,"title":"Conflict","code":"placement.concurrent_update"}]}`) + }) +} diff --git a/openstack/placement/v1/allocations/testing/requests_test.go b/openstack/placement/v1/allocations/testing/requests_test.go new file mode 100644 index 0000000000..ce809342f0 --- /dev/null +++ b/openstack/placement/v1/allocations/testing/requests_test.go @@ -0,0 +1,185 @@ +package testing + +import ( + "context" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack/placement/v1/allocations" + th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/client" +) + +func TestGetSuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleGetAllocationsSuccess(t, fakeServer) + + actual, err := allocations.Get(context.TODO(), client.ServiceClient(fakeServer), ConsumerUUID).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedAllocations, *actual) +} + +func TestGetEmptySuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleGetEmptyAllocationsSuccess(t, fakeServer) + + actual, err := allocations.Get(context.TODO(), client.ServiceClient(fakeServer), EmptyConsumerUUID).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedEmptyAllocations, *actual) +} + +func TestUpdateSuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleUpdateAndGetAllocationsSuccess(t, fakeServer) + + err := allocations.Update(context.TODO(), client.ServiceClient(fakeServer), ConsumerUUID, allocations.UpdateOpts{ + Allocations: map[string]allocations.ProviderAllocationsOpts{ + ProviderUUID1: { + Resources: map[string]int{"VCPU": 2, "MEMORY_MB": 2048}, + }, + }, + ProjectID: ProjectID, + UserID: UserID, + ConsumerGeneration: nil, + }).ExtractErr() + th.AssertNoErr(t, err) + + actual, err := allocations.Get(context.TODO(), client.ServiceClient(fakeServer), ConsumerUUID).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedAllocationsAfterUpdate, *actual) +} + +func TestUpdateNewConsumerSuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleUpdateAllocationsNewConsumerSuccess(t, fakeServer) + + // Act: Update with nil ConsumerGeneration; it must be serialized as JSON null, not omitted. + err := allocations.Update(context.TODO(), client.ServiceClient(fakeServer), EmptyConsumerUUID, allocations.UpdateOpts{ + Allocations: map[string]allocations.ProviderAllocationsOpts{ + ProviderUUID1: { + Resources: map[string]int{"VCPU": 2, "MEMORY_MB": 2048}, + }, + }, + ProjectID: ProjectID, + UserID: UserID, + ConsumerGeneration: nil, + }).ExtractErr() + th.AssertNoErr(t, err) + + actual, err := allocations.Get(context.TODO(), client.ServiceClient(fakeServer), EmptyConsumerUUID).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedAllocationsAfterUpdate, *actual) +} + +func TestUpdateConflict(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleUpdateAllocationsConflict(t, fakeServer) + + staleGeneration := 0 + err := allocations.Update(context.TODO(), client.ServiceClient(fakeServer), ConflictConsumerUUID, allocations.UpdateOpts{ + Allocations: map[string]allocations.ProviderAllocationsOpts{ + ProviderUUID1: { + Resources: map[string]int{"VCPU": 1}, + }, + }, + ProjectID: ProjectID, + UserID: UserID, + ConsumerGeneration: &staleGeneration, + }).ExtractErr() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusConflict)) +} + +func TestDeleteSuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleDeleteAndGetAllocationsSuccess(t, fakeServer) + + err := allocations.Delete(context.TODO(), client.ServiceClient(fakeServer), ConsumerUUID).ExtractErr() + th.AssertNoErr(t, err) + + // Assert: Consumer now has no allocations. + actual, err := allocations.Get(context.TODO(), client.ServiceClient(fakeServer), ConsumerUUID).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedEmptyAllocations, *actual) +} + +func TestDeleteNotFound(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleDeleteAllocationsNotFound(t, fakeServer) + + err := allocations.Delete(context.TODO(), client.ServiceClient(fakeServer), NotFoundConsumerUUID).ExtractErr() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) +} + +func TestManageSuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleManageAllocationsSuccess(t, fakeServer) + + err := allocations.Manage(context.TODO(), client.ServiceClient(fakeServer), allocations.ManageOpts{ + ManageConsumerUUID1: { + Allocations: map[string]allocations.ProviderAllocationsOpts{ + ProviderUUID1: { + Resources: map[string]int{"VCPU": 1}, + }, + }, + ProjectID: ProjectID, + UserID: UserID, + ConsumerGeneration: nil, + }, + ManageConsumerUUID2: { + Allocations: map[string]allocations.ProviderAllocationsOpts{ + ProviderUUID1: { + Resources: map[string]int{"VCPU": 1}, + }, + }, + ProjectID: ProjectID, + UserID: UserID, + ConsumerGeneration: nil, + }, + }).ExtractErr() + th.AssertNoErr(t, err) + + for _, uuid := range []string{ManageConsumerUUID1, ManageConsumerUUID2} { + actual, err := allocations.Get(context.TODO(), client.ServiceClient(fakeServer), uuid).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedAllocationsAfterManage, *actual) + } +} + +func TestManageConflict(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleManageAllocationsConflict(t, fakeServer) + + staleGeneration := 0 + err := allocations.Manage(context.TODO(), client.ServiceClient(fakeServer), allocations.ManageOpts{ + ConflictConsumerUUID: { + Allocations: map[string]allocations.ProviderAllocationsOpts{ + ProviderUUID1: { + Resources: map[string]int{"VCPU": 1}, + }, + }, + ProjectID: ProjectID, + UserID: UserID, + ConsumerGeneration: &staleGeneration, + }, + }).ExtractErr() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusConflict)) +} diff --git a/openstack/placement/v1/allocations/urls.go b/openstack/placement/v1/allocations/urls.go new file mode 100644 index 0000000000..267adb143b --- /dev/null +++ b/openstack/placement/v1/allocations/urls.go @@ -0,0 +1,19 @@ +package allocations + +import "github.com/gophercloud/gophercloud/v2" + +func getURL(client *gophercloud.ServiceClient, consumerUUID string) string { + return client.ServiceURL("allocations", consumerUUID) +} + +func updateURL(client *gophercloud.ServiceClient, consumerUUID string) string { + return client.ServiceURL("allocations", consumerUUID) +} + +func deleteURL(client *gophercloud.ServiceClient, consumerUUID string) string { + return client.ServiceURL("allocations", consumerUUID) +} + +func manageURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("allocations") +} diff --git a/openstack/placement/v1/resourceclasses/doc.go b/openstack/placement/v1/resourceclasses/doc.go new file mode 100644 index 0000000000..b1535af4ae --- /dev/null +++ b/openstack/placement/v1/resourceclasses/doc.go @@ -0,0 +1,67 @@ +/* +Package resourceclasses manages resource classes from the OpenStack Placement service. + +Resource Class API requests are available starting from microversion 1.2. + +Example to list resource classes + + placementClient.Microversion = "1.2" + + allPages, err := resourceclasses.List(placementClient).AllPages(context.TODO()) + if err != nil { + panic(err) + } + + allResourceClasses, err := resourceclasses.ExtractResourceClasses(allPages) + if err != nil { + panic(err) + } + + for _, rc := range allResourceClasses { + fmt.Printf("%+v\n", rc) + } + +Example to Get a resource class + + placementClient.Microversion = "1.2" + + resourceClassName := "VCPU" + resourceClass, err := resourceclasses.Get(context.TODO(), placementClient, resourceClassName).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", resourceClass) + +Example to Create a resource class using POST + + placementClient.Microversion = "1.2" + + createOpts := resourceclasses.CreateOpts{ + Name: "CUSTOM_RESOURCE_CLASS", + } + + err := resourceclasses.Create(context.TODO(), placementClient, createOpts).ExtractErr() + if err != nil { + panic(err) + } + +Example to ensure the existence of a resource class using PUT (idempotent creation) + + placementClient.Microversion = "1.7" + + err := resourceclasses.Update(context.TODO(), placementClient, "CUSTOM_RESOURCE_CLASS").ExtractErr() + if err != nil { + panic(err) + } + +Example to delete a resource class + + placementClient.Microversion = "1.2" + + err := resourceclasses.Delete(context.TODO(), placementClient, "CUSTOM_RESOURCE_CLASS").ExtractErr() + if err != nil { + panic(err) + } +*/ +package resourceclasses diff --git a/openstack/placement/v1/resourceclasses/requests.go b/openstack/placement/v1/resourceclasses/requests.go new file mode 100644 index 0000000000..8621eddce5 --- /dev/null +++ b/openstack/placement/v1/resourceclasses/requests.go @@ -0,0 +1,73 @@ +package resourceclasses + +import ( + "context" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/pagination" +) + +// List retrieves a list of resource classes. +func List(client *gophercloud.ServiceClient) pagination.Pager { + url := listURL(client) + + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return ResourceClassesPage{pagination.SinglePageBase(r)} + }) +} + +// Get retrieves the resource class with the provided name. +func Get(ctx context.Context, client *gophercloud.ServiceClient, name string) (r GetResult) { + resp, err := client.Get(ctx, getURL(client, name), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// CreateOptsBuilder allows extensions to add additional parameters to +// the Create request. +type CreateOptsBuilder interface { + ToResourceClassCreateMap() (map[string]any, error) +} + +// CreateOpts represents the attributes of a new resource class. +type CreateOpts struct { + Name string `json:"name" required:"true"` +} + +// ToResourceClassCreateMap formats a CreateOpts into a create request. +func (opts CreateOpts) ToResourceClassCreateMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// Create creates a new resource class. +func Create(ctx context.Context, client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToResourceClassCreateMap() + if err != nil { + r.Err = err + return + } + resp, err := client.Post(ctx, createURL(client), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// Update ensures the existence of a custom resource class with the +// provided name (can be safely called multiple times). +func Update(ctx context.Context, client *gophercloud.ServiceClient, name string) (r UpdateResult) { + resp, err := client.Put(ctx, updateURL(client, name), nil, nil, &gophercloud.RequestOpts{ + OkCodes: []int{201, 204}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// Delete deletes the resource class with the provided name. +func Delete(ctx context.Context, client *gophercloud.ServiceClient, name string) (r DeleteResult) { + resp, err := client.Delete(ctx, deleteURL(client, name), &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/placement/v1/resourceclasses/results.go b/openstack/placement/v1/resourceclasses/results.go new file mode 100644 index 0000000000..61131c372e --- /dev/null +++ b/openstack/placement/v1/resourceclasses/results.go @@ -0,0 +1,80 @@ +package resourceclasses + +import ( + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/pagination" +) + +type Link struct { + Href string `json:"href"` + Rel string `json:"rel"` +} + +type ResourceClass struct { + Name string `json:"name"` + + // Links is a list of links associated with the resource class. + Links []Link `json:"links"` +} + +// resourceClassResult is the response of a base ResourceClass result. +type resourceClassResult struct { + gophercloud.Result +} + +// Extract interprets any resourceClassResult-base result as a ResourceClass. +func (r resourceClassResult) Extract() (*ResourceClass, error) { + var s ResourceClass + err := r.ExtractInto(&s) + return &s, err +} + +// GetResult represents the result of a Get operation. Call its Extract +// method to interpret it as a ResourceClass. +type GetResult struct { + resourceClassResult +} + +// CreateResult is the response from a Create operation. Call its ExtractErr to +// determine if the request succeeded or failed. +type CreateResult struct { + gophercloud.ErrResult +} + +// UpdateResult is the response from an Update operation. Call its ExtractErr to +// determine if the request succeeded or failed. +type UpdateResult struct { + gophercloud.ErrResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr to +// determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// ResourceClassesPage contains a single page of all resource classes from a List call. +type ResourceClassesPage struct { + pagination.SinglePageBase +} + +// IsEmpty satisfies the IsEmpty method of the Page interface. It returns true +// if a List contains no results. +func (r ResourceClassesPage) IsEmpty() (bool, error) { + if r.StatusCode == 204 { + return true, nil + } + + resourceClasses, err := ExtractResourceClasses(r) + return len(resourceClasses) == 0, err +} + +// ExtractResourceClasses takes a List result and extracts the collection of resource classes +// returned by the API. +func ExtractResourceClasses(p pagination.Page) ([]ResourceClass, error) { + var s struct { + ResourceClasses []ResourceClass `json:"resource_classes"` + } + err := (p.(ResourceClassesPage)).ExtractInto(&s) + return s.ResourceClasses, err +} diff --git a/openstack/placement/v1/resourceclasses/testing/doc.go b/openstack/placement/v1/resourceclasses/testing/doc.go new file mode 100644 index 0000000000..2a3dd0b663 --- /dev/null +++ b/openstack/placement/v1/resourceclasses/testing/doc.go @@ -0,0 +1,2 @@ +// Package testing contains resourceclasses unit tests. +package testing diff --git a/openstack/placement/v1/resourceclasses/testing/fixtures_test.go b/openstack/placement/v1/resourceclasses/testing/fixtures_test.go new file mode 100644 index 0000000000..cfd5de8847 --- /dev/null +++ b/openstack/placement/v1/resourceclasses/testing/fixtures_test.go @@ -0,0 +1,201 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/placement/v1/resourceclasses" + th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/client" +) + +const PresentResourceClass = "CUSTOM_RESOURCE_CLASS" +const AbsentResourceClass = "NON_EXISTENT_RC" +const NewResourceClass = "CUSTOM_NEW_RC" + +const ResourceClassGetResult = ` +{ + "links": [ + { + "href": "/placement/resource_classes/CUSTOM_RESOURCE_CLASS", + "rel": "self" + } + ], + "name": "CUSTOM_RESOURCE_CLASS" +} +` + +const ResourceClassesListResult = ` +{ + "resource_classes": [ + { + "name": "VCPU", + "links": [ + { + "href": "/resource_classes/VCPU", + "rel": "self" + } + ] + }, + { + "name": "CUSTOM_RESOURCE_CLASS", + "links": [ + { + "href": "/placement/resource_classes/CUSTOM_RESOURCE_CLASS", + "rel": "self" + } + ] + } + ] +} +` + +var ExpectedResourceClass = resourceclasses.ResourceClass{ + Name: "CUSTOM_RESOURCE_CLASS", + Links: []resourceclasses.Link{ + { + Href: "/placement/resource_classes/CUSTOM_RESOURCE_CLASS", + Rel: "self", + }, + }, +} + +var ExpectedResourceClassesList = []resourceclasses.ResourceClass{ + { + Name: "VCPU", + Links: []resourceclasses.Link{ + { + Href: "/resource_classes/VCPU", + Rel: "self", + }, + }, + }, + { + Name: "CUSTOM_RESOURCE_CLASS", + Links: []resourceclasses.Link{ + { + Href: "/placement/resource_classes/CUSTOM_RESOURCE_CLASS", + Rel: "self", + }, + }, + }, +} + +func HandleListResourceClasses(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/resource_classes", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ResourceClassesListResult) + }) +} + +func HandleGetResourceClassSuccess(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/resource_classes/"+PresentResourceClass, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ResourceClassGetResult) + }) +} + +func HandleGetResourceClassNotFound(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/resource_classes/"+AbsentResourceClass, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNotFound) + }) +} + +func HandleCreateResourceClassSuccess(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/resource_classes", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{"name": "CUSTOM_NEW_RC"}`) + + w.WriteHeader(http.StatusCreated) + }) +} + +func HandleCreateResourceClassConflict(t *testing.T, fakeServer th.FakeServer) { + // We simulate a conflict by trying to create a resource class that already exists. + fakeServer.Mux.HandleFunc("/resource_classes", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusConflict) + }) +} + +func HandleUpdateResourceClassSuccess(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/resource_classes/"+NewResourceClass, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusCreated) + }) +} + +func HandleUpdateResourceClassExists(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/resource_classes/"+PresentResourceClass, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +func HandleUpdateResourceClassNonCustom(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/resource_classes/VCPU", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusBadRequest) + }) +} + +func HandleDeleteResourceClassSuccess(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/resource_classes/"+PresentResourceClass, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +func HandleDeleteResourceClassNotFound(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/resource_classes/"+AbsentResourceClass, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNotFound) + }) +} + +func HandleDeleteResourceClassStandardClass(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/resource_classes/VCPU", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusBadRequest) + }) +} diff --git a/openstack/placement/v1/resourceclasses/testing/requests_test.go b/openstack/placement/v1/resourceclasses/testing/requests_test.go new file mode 100644 index 0000000000..1808665d8b --- /dev/null +++ b/openstack/placement/v1/resourceclasses/testing/requests_test.go @@ -0,0 +1,136 @@ +package testing + +import ( + "context" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack/placement/v1/resourceclasses" + th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/client" +) + +func TestListResourceClasses(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleListResourceClasses(t, fakeServer) + + allPages, err := resourceclasses.List(client.ServiceClient(fakeServer)).AllPages(context.TODO()) + th.AssertNoErr(t, err) + + actual, err := resourceclasses.ExtractResourceClasses(allPages) + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedResourceClassesList, actual) +} + +func TestGetResourceClassSuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleGetResourceClassSuccess(t, fakeServer) + + actual, err := resourceclasses.Get(context.TODO(), client.ServiceClient(fakeServer), PresentResourceClass).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, &ExpectedResourceClass, actual) +} + +func TestGetResourceClassNotFound(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleGetResourceClassNotFound(t, fakeServer) + + _, err := resourceclasses.Get(context.TODO(), client.ServiceClient(fakeServer), AbsentResourceClass).Extract() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) +} + +func TestCreateResourceClassSuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleCreateResourceClassSuccess(t, fakeServer) + + createOpts := resourceclasses.CreateOpts{ + Name: NewResourceClass, + } + + err := resourceclasses.Create(context.TODO(), client.ServiceClient(fakeServer), createOpts).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestCreateResourceClassConflict(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleCreateResourceClassConflict(t, fakeServer) + + createOpts := resourceclasses.CreateOpts{ + Name: PresentResourceClass, + } + + err := resourceclasses.Create(context.TODO(), client.ServiceClient(fakeServer), createOpts).ExtractErr() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusConflict)) +} + +func TestUpdateResourceClassCreateSuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleUpdateResourceClassSuccess(t, fakeServer) + + err := resourceclasses.Update(context.TODO(), client.ServiceClient(fakeServer), NewResourceClass).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestUpdateResourceClassExists(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleUpdateResourceClassExists(t, fakeServer) + + err := resourceclasses.Update(context.TODO(), client.ServiceClient(fakeServer), PresentResourceClass).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestUpdateResourceClassNonCustom(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleUpdateResourceClassNonCustom(t, fakeServer) + + err := resourceclasses.Update(context.TODO(), client.ServiceClient(fakeServer), "VCPU").ExtractErr() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusBadRequest)) +} + +func TestDeleteResourceClassSuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleDeleteResourceClassSuccess(t, fakeServer) + + err := resourceclasses.Delete(context.TODO(), client.ServiceClient(fakeServer), PresentResourceClass).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestDeleteResourceClassNotFound(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleDeleteResourceClassNotFound(t, fakeServer) + + err := resourceclasses.Delete(context.TODO(), client.ServiceClient(fakeServer), AbsentResourceClass).ExtractErr() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) +} + +func TestDeleteResourceClassStandardClass(t *testing.T) { + // Removing standard resource classes is not allowed. + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleDeleteResourceClassStandardClass(t, fakeServer) + + err := resourceclasses.Delete(context.TODO(), client.ServiceClient(fakeServer), "VCPU").ExtractErr() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusBadRequest)) +} diff --git a/openstack/placement/v1/resourceclasses/urls.go b/openstack/placement/v1/resourceclasses/urls.go new file mode 100644 index 0000000000..85d9f77041 --- /dev/null +++ b/openstack/placement/v1/resourceclasses/urls.go @@ -0,0 +1,25 @@ +package resourceclasses + +import ( + "github.com/gophercloud/gophercloud/v2" +) + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("resource_classes") +} + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("resource_classes") +} + +func getURL(client *gophercloud.ServiceClient, name string) string { + return client.ServiceURL("resource_classes", name) +} + +func updateURL(client *gophercloud.ServiceClient, name string) string { + return client.ServiceURL("resource_classes", name) +} + +func deleteURL(client *gophercloud.ServiceClient, name string) string { + return client.ServiceURL("resource_classes", name) +} diff --git a/openstack/placement/v1/resourceproviders/doc.go b/openstack/placement/v1/resourceproviders/doc.go index d659558f15..0b1235a183 100644 --- a/openstack/placement/v1/resourceproviders/doc.go +++ b/openstack/placement/v1/resourceproviders/doc.go @@ -17,12 +17,35 @@ Example to list resource providers fmt.Printf("%+v\n", r) } +Example to list resource providers with repeating member_of and required parameters (microversion >= 1.39) + + placementClient.Microversion = "1.39" + + listOpts := resourceproviders.ListOpts139{ + MemberOf: []string{"42896e0d-205d-4fe3-bd1e-100924931787", "in:7834d585-31d4-486f-be8c-b3c1a58ca710,5e08ea53-c4c6-448e-9334-ac4953de3cfa"}, + Required: []string{"CUSTOM_TRAIT1", "CUSTOM_TRAIT2"}, + } + + allPages, err := resourceproviders.List(placementClient, listOpts).AllPages(context.TODO()) + if err != nil { + panic(err) + } + + allResourceProviders, err := resourceproviders.ExtractResourceProviders(allPages) + if err != nil { + panic(err) + } + + for _, r := range allResourceProviders { + fmt.Printf("%+v\n", r) + } + Example to create resource providers createOpts := resourceproviders.CreateOpts{ - Name: "new-rp", - UUID: "b99b3ab4-3aa6-4fba-b827-69b88b9c544a", - ParentProvider: "c7f50b40-6f32-4d7a-9f32-9384057be83b" + Name: "new-rp", + UUID: "b99b3ab4-3aa6-4fba-b827-69b88b9c544a", + ParentProviderUUID: "c7f50b40-6f32-4d7a-9f32-9384057be83b", } rp, err := resourceproviders.Create(context.TODO(), placementClient, createOpts).Extract() @@ -49,14 +72,35 @@ Example to Get a resource provider Example to Update a resource provider resourceProviderID := "b99b3ab4-3aa6-4fba-b827-69b88b9c544a" + name := "new-rp" + parent := "c7f50b40-6f32-4d7a-9f32-9384057be83b" + + updateOpts := resourceproviders.UpdateOpts{ + Name: &name, + ParentProviderUUID: &parent, + } + + placementClient.Microversion = "1.37" + resourceProvider, err := resourceproviders.Update(context.TODO(), placementClient, resourceProviderID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to transform a provider to a new root provider (set parent_provider_uuid to null). +Note that Gophercloud uses an empty string as a sentinel value to represent JSON null +for this optional field. + + resourceProviderID := "b99b3ab4-3aa6-4fba-b827-69b88b9c544a" + name := "existing-rp" + parent := "" updateOpts := resourceproviders.UpdateOpts{ - Name: "new-rp", - ParentProvider: "c7f50b40-6f32-4d7a-9f32-9384057be83b" + Name: &name, + ParentProviderUUID: &parent, } placementClient.Microversion = "1.37" - resourceProvider, err := resourceproviders.Update(context.TODO(), placementClient, resourceProviderID).Extract() + resourceProvider, err = resourceproviders.Update(context.TODO(), placementClient, resourceProviderID, updateOpts).Extract() if err != nil { panic(err) } @@ -75,6 +119,92 @@ Example to get resource providers inventories panic(err) } +Example to get one resource provider inventory + + rpInventory, err := resourceproviders.GetInventory(context.TODO(), placementClient, resourceProviderID, "VCPU").Extract() + if err != nil { + panic(err) + } + +Example to update (replace) all resource provider inventories + + inventories, err := resourceproviders.GetInventories(context.TODO(), placementClient, resourceProviderID).Extract() + if err != nil { + panic(err) + } + + allocationRatio := float32(16.0) + maxUnit := 4 + minUnit := 1 + reserved := 0 + stepSize := 1 + + updateInventoriesOpts := resourceproviders.UpdateInventoriesOpts{ + ResourceProviderGeneration: inventories.ResourceProviderGeneration, + Inventories: map[string]resourceproviders.InventoryUpdateBase{ + "VCPU": { + Total: 4, + Reserved: &reserved, + MinUnit: &minUnit, + MaxUnit: &maxUnit, + StepSize: &stepSize, + AllocationRatio: &allocationRatio, + }, + }, + } + + rp, err = resourceproviders.UpdateInventories(context.TODO(), placementClient, resourceProviderID, updateInventoriesOpts).Extract() + if err != nil { + panic(err) + } + +Example to update one existing resource provider inventory + + inventories, err := resourceproviders.GetInventories(context.TODO(), placementClient, resourceProviderID).Extract() + if err != nil { + panic(err) + } + + allocationRatio := float32(16.0) + maxUnit := 4 + minUnit := 1 + reserved := 0 + stepSize := 1 + + // UpdateInventory updates an existing resource class inventory. + updateInventoryOpts := resourceproviders.UpdateInventoryOpts{ + ResourceProviderGeneration: inventories.ResourceProviderGeneration, + InventoryUpdateBase: resourceproviders.InventoryUpdateBase{ + Total: 4, + Reserved: &reserved, + MinUnit: &minUnit, + MaxUnit: &maxUnit, + StepSize: &stepSize, + AllocationRatio: &allocationRatio, + }, + } + + rpInventory, err := resourceproviders.UpdateInventory(context.TODO(), placementClient, resourceProviderID, "VCPU", updateInventoryOpts).Extract() + if err != nil { + panic(err) + } + +Example to delete one existing resource provider inventory +Since this request does not accept the resource provider generation, it is not safe to use when multiple threads are managing inventories for a single provider. In such situations use UpdateInventories with the empty inventory. + + err = resourceproviders.DeleteInventory(context.TODO(), placementClient, resourceProviderID, "VCPU").ExtractErr() + if err != nil { + panic(err) + } + +Example to delete all resource provider inventories +Since this request does not accept the resource provider generation, it is not safe to use when multiple threads are managing inventories for a single provider. In such situations use UpdateInventories with an empty inventory map. + + err = resourceproviders.DeleteInventories(context.TODO(), placementClient, resourceProviderID).ExtractErr() + if err != nil { + panic(err) + } + Example to get resource providers traits rp, err := resourceproviders.GetTraits(context.TODO(), placementClient, resourceProviderID).Extract() @@ -88,5 +218,34 @@ Example to get resource providers allocations if err != nil { panic(err) } + +Example to get resource providers aggregates + + placementClient.Microversion = "1.1" + + rp, err := resourceproviders.GetAggregates(context.TODO(), placementClient, resourceProviderID).Extract() + if err != nil { + panic(err) + } + +# Example to update resource providers aggregates + +For microversion 1.18 and earlier the ResourceProviderGeneration is optional and would be ignored if provided, +as it was not supported pre-1.19. For greater safety, it is recommended to use the newer microversion. + + placementClient.Microversion = "1.19" + + updateOpts := resourceproviders.UpdateAggregatesOpts{ + ResourceProviderGeneration: rp.ResourceProviderGeneration, + Aggregates: []string{ + "6d84f6f6-7736-40ff-84d2-7db47f18ea25", + "f11f14bc-6f17-4f0a-b7c2-44b3e685ccf4", + }, + } + + rp, err = resourceproviders.UpdateAggregates(context.TODO(), placementClient, resourceProviderID, updateOpts).Extract() + if err != nil { + panic(err) + } */ package resourceproviders diff --git a/openstack/placement/v1/resourceproviders/microversions.go b/openstack/placement/v1/resourceproviders/microversions.go new file mode 100644 index 0000000000..607688212a --- /dev/null +++ b/openstack/placement/v1/resourceproviders/microversions.go @@ -0,0 +1,40 @@ +package resourceproviders + +import "github.com/gophercloud/gophercloud/v2" + +// ListOpts139 allows filtering resource providers. Filtering is achieved by +// passing in struct field values that map to the resource provider +// attributes you want to see returned. +// ListOpts139 is available in version >= 1.39. +type ListOpts139 struct { + // Name is the name of the resource provider to filter the list + Name string `q:"name"` + + // UUID is the uuid of the resource provider to filter the list + UUID string `q:"uuid"` + + // MemberOf is a list representing aggregate uuids that a provider must be + // associated with to be returned. + // Alternative is defined using the in: syntax, e.g. member_of=in:agg1,agg2,agg3. + // Forbidden aggregates are prefixed with !. + // Starting with microversion 1.24, the member_of parameter may be repeated. + MemberOf []string `q:"member_of"` + + // Resources is a comma-separated list of string indicating an amount of resource + // of a specified class that a provider must have the capacity and availability to serve + Resources string `q:"resources"` + + // InTree is a string that represents a resource provider UUID. The returned resource + // providers will be in the same provider tree as the specified provider. + InTree string `q:"in_tree"` + + // Required is a list of trait names. + // Microversion 1.39 added support for repeating the required parameter and for the in: syntax. + Required []string `q:"required"` +} + +// ToResourceProviderListQuery formats a ListOpts139 into a query string. +func (opts ListOpts139) ToResourceProviderListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} diff --git a/openstack/placement/v1/resourceproviders/requests.go b/openstack/placement/v1/resourceproviders/requests.go index 5c5075e445..0a46f37398 100644 --- a/openstack/placement/v1/resourceproviders/requests.go +++ b/openstack/placement/v1/resourceproviders/requests.go @@ -4,6 +4,7 @@ import ( "context" "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack/utils" "github.com/gophercloud/gophercloud/v2/pagination" ) @@ -125,16 +126,24 @@ type UpdateOptsBuilder interface { type UpdateOpts struct { Name *string `json:"name,omitempty"` // Available in version >= 1.37. It can be set to any existing provider UUID - // except to providers that would cause a loop. Also it can be set to null - // to transform the provider to a new root provider. This operation needs to - // be used carefully. Moving providers can mean that the original rules used - // to create the existing resource allocations may be invalidated by that move. + // except to providers that would cause a loop. Using an empty string + // transforms the provider to a new root provider. ParentProviderUUID *string `json:"parent_provider_uuid,omitempty"` } // ToResourceProviderUpdateMap constructs a request body from UpdateOpts. func (opts UpdateOpts) ToResourceProviderUpdateMap() (map[string]any, error) { - return gophercloud.BuildRequestBody(opts, "") + b, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + // In order to set this to null, use an empty string as a value. + if opts.ParentProviderUUID != nil && *opts.ParentProviderUUID == "" { + b["parent_provider_uuid"] = nil + } + + return b, nil } // Update makes a request against the API to create a resource provider @@ -164,6 +173,100 @@ func GetInventories(ctx context.Context, client *gophercloud.ServiceClient, reso return } +func GetInventory(ctx context.Context, client *gophercloud.ServiceClient, resourceProviderID, resourceClass string) (r GetInventoryResult) { + resp, err := client.Get(ctx, getResourceProviderInventoryURL(client, resourceProviderID, resourceClass), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// UpdateInventoriesOptsBuilder allows extensions to add additional parameters to the +// UpdateInventories request. +type UpdateInventoriesOptsBuilder interface { + ToResourceProviderUpdateInventoriesMap() (map[string]any, error) +} + +// InventoryUpdateBase contains inventory fields shared by update operations. +type InventoryUpdateBase struct { + AllocationRatio *float32 `json:"allocation_ratio,omitempty"` + MaxUnit *int `json:"max_unit,omitempty"` + MinUnit *int `json:"min_unit,omitempty"` + Reserved *int `json:"reserved,omitempty"` + StepSize *int `json:"step_size,omitempty"` + Total int `json:"total"` +} + +// UpdateInventoriesOpts represents options used to update all inventories of a resource provider. +type UpdateInventoriesOpts struct { + ResourceProviderGeneration int `json:"resource_provider_generation"` + Inventories map[string]InventoryUpdateBase `json:"inventories"` +} + +// ToResourceProviderUpdateInventoriesMap constructs a request body from UpdateInventoriesOpts. +func (opts UpdateInventoriesOpts) ToResourceProviderUpdateInventoriesMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// UpdateInventories updates all inventories of a resource provider. +func UpdateInventories(ctx context.Context, client *gophercloud.ServiceClient, resourceProviderID string, opts UpdateInventoriesOptsBuilder) (r GetInventoriesResult) { + b, err := opts.ToResourceProviderUpdateInventoriesMap() + if err != nil { + r.Err = err + return + } + + resp, err := client.Put(ctx, getResourceProviderInventoriesURL(client, resourceProviderID), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// DeleteInventories deletes all inventories from a resource provider. +func DeleteInventories(ctx context.Context, client *gophercloud.ServiceClient, resourceProviderID string) (r DeleteResult) { + resp, err := client.Delete(ctx, deleteResourceProviderInventoriesURL(client, resourceProviderID), &gophercloud.RequestOpts{OkCodes: []int{204}}) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// UpdateInventoryOptsBuilder allows extensions to add additional parameters to the +// UpdateInventory request. +type UpdateInventoryOptsBuilder interface { + ToResourceProviderUpdateInventoryMap() (map[string]any, error) +} + +// UpdateInventoryOpts represents options used to update one inventory of a resource provider. +type UpdateInventoryOpts struct { + ResourceProviderGeneration int `json:"resource_provider_generation"` + InventoryUpdateBase +} + +// ToResourceProviderUpdateInventoryMap constructs a request body from UpdateInventoryOpts. +func (opts UpdateInventoryOpts) ToResourceProviderUpdateInventoryMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +// UpdateInventory updates one inventory of a resource provider. +func UpdateInventory(ctx context.Context, client *gophercloud.ServiceClient, resourceProviderID, resourceClass string, opts UpdateInventoryOptsBuilder) (r UpdateInventoryResult) { + b, err := opts.ToResourceProviderUpdateInventoryMap() + if err != nil { + r.Err = err + return + } + + resp, err := client.Put(ctx, updateResourceProviderInventoryURL(client, resourceProviderID, resourceClass), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// DeleteInventory deletes one inventory from a resource provider. +func DeleteInventory(ctx context.Context, client *gophercloud.ServiceClient, resourceProviderID, resourceClass string) (r DeleteResult) { + resp, err := client.Delete(ctx, deleteResourceProviderInventoryURL(client, resourceProviderID, resourceClass), &gophercloud.RequestOpts{OkCodes: []int{204}}) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + func GetAllocations(ctx context.Context, client *gophercloud.ServiceClient, resourceProviderID string) (r GetAllocationsResult) { resp, err := client.Get(ctx, getResourceProviderAllocationsURL(client, resourceProviderID), &r.Body, nil) _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) @@ -175,3 +278,88 @@ func GetTraits(ctx context.Context, client *gophercloud.ServiceClient, resourceP _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } + +func GetAggregates(ctx context.Context, client *gophercloud.ServiceClient, resourceProviderID string) (r GetAggregatesResult) { + resp, err := client.Get(ctx, getResourceProviderAggregatesURL(client, resourceProviderID), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// UpdateAggregatesOptsBuilder allows extensions to add additional parameters to the +// UpdateAggregates request. +type UpdateAggregatesOptsBuilder interface { + ToResourceProviderUpdateAggregatesMap() (map[string]any, error) +} + +// UpdateAggregatesOpts represents options used to update aggregates of a resource provider. +type UpdateAggregatesOpts struct { + // ResourceProviderGeneration is required from microversion 1.19 and later. + ResourceProviderGeneration *int `json:"resource_provider_generation,omitempty"` + Aggregates []string `json:"aggregates"` +} + +// ToResourceProviderUpdateAggregatesMap constructs a request body from UpdateAggregatesOpts. +func (opts UpdateAggregatesOpts) ToResourceProviderUpdateAggregatesMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +func UpdateAggregates(ctx context.Context, client *gophercloud.ServiceClient, resourceProviderID string, opts UpdateAggregatesOptsBuilder) (r GetAggregatesResult) { + b, err := opts.ToResourceProviderUpdateAggregatesMap() + if err != nil { + r.Err = err + return + } + + var body any = b + if client.Microversion != "" { + _, minor, err := utils.ParseMicroversion(client.Microversion) + if err != nil { + r.Err = err + return + } + // In microversions prior to 1.19, the body was not enveloped. + if minor < 19 { + body = b["aggregates"] + } + } + resp, err := client.Put(ctx, updateResourceProviderAggregatesURL(client, resourceProviderID), body, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// UpdateTraitsOptsBuilder allows extensions to add additional parameters to the +// UpdateTraits request. +type UpdateTraitsOptsBuilder interface { + ToResourceProviderUpdateTraitsMap() (map[string]any, error) +} + +// UpdateTraitsOpts represents options used to update traits of a resource provider. +type UpdateTraitsOpts = ResourceProviderTraits + +// ToResourceProviderUpdateTraitsMap constructs a request body from UpdateTraitsOpts. +func (opts UpdateTraitsOpts) ToResourceProviderUpdateTraitsMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "") +} + +func UpdateTraits(ctx context.Context, client *gophercloud.ServiceClient, resourceProviderID string, opts UpdateTraitsOptsBuilder) (r GetTraitsResult) { + b, err := opts.ToResourceProviderUpdateTraitsMap() + if err != nil { + r.Err = err + return + } + resp, err := client.Put(ctx, getResourceProviderTraitsURL(client, resourceProviderID), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +func DeleteTraits(ctx context.Context, client *gophercloud.ServiceClient, resourceProviderID string) (r DeleteResult) { + resp, err := client.Delete(ctx, getResourceProviderTraitsURL(client, resourceProviderID), &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/placement/v1/resourceproviders/results.go b/openstack/placement/v1/resourceproviders/results.go index 19b71103e1..db24e3cc6f 100644 --- a/openstack/placement/v1/resourceproviders/results.go +++ b/openstack/placement/v1/resourceproviders/results.go @@ -56,6 +56,11 @@ type ResourceProviderInventories struct { Inventories map[string]Inventory `json:"inventories"` } +type ResourceProviderInventory struct { + ResourceProviderGeneration int `json:"resource_provider_generation"` + Inventory +} + type ResourceProviderAllocations struct { ResourceProviderGeneration int `json:"resource_provider_generation"` Allocations map[string]Allocation `json:"allocations"` @@ -66,6 +71,12 @@ type ResourceProviderTraits struct { Traits []string `json:"traits"` } +type ResourceProviderAggregates struct { + // ResourceProviderGeneration is available from microversion 1.19 and later. + ResourceProviderGeneration *int `json:"resource_provider_generation"` + Aggregates []string `json:"aggregates"` +} + // resourceProviderResult is the response of a base ResourceProvider result. type resourceProviderResult struct { gophercloud.Result @@ -153,6 +164,32 @@ func (r GetInventoriesResult) Extract() (*ResourceProviderInventories, error) { return &s, err } +// GetInventoryResult is the response of a Get inventory operation. Call its Extract method +// to interpret it as a ResourceProviderInventory. +type GetInventoryResult struct { + gophercloud.Result +} + +// Extract interprets a GetInventoryResult as a ResourceProviderInventory. +func (r GetInventoryResult) Extract() (*ResourceProviderInventory, error) { + var s ResourceProviderInventory + err := r.ExtractInto(&s) + return &s, err +} + +// UpdateInventoryResult is the response of an Update inventory operation. Call its Extract method +// to interpret it as a ResourceProviderInventory. +type UpdateInventoryResult struct { + gophercloud.Result +} + +// Extract interprets a UpdateInventoryResult as a ResourceProviderInventory. +func (r UpdateInventoryResult) Extract() (*ResourceProviderInventory, error) { + var s ResourceProviderInventory + err := r.ExtractInto(&s) + return &s, err +} + // GetAllocationsResult is the response of a Get allocations operations. Call its Extract method // to interpret it as a ResourceProviderAllocations. type GetAllocationsResult struct { @@ -178,3 +215,16 @@ func (r GetTraitsResult) Extract() (*ResourceProviderTraits, error) { err := r.ExtractInto(&s) return &s, err } + +// GetAggregatesResult is the response of a Get aggregates operations. Call its Extract method +// to interpret it as a ResourceProviderAggregates. +type GetAggregatesResult struct { + gophercloud.Result +} + +// Extract interprets a GetAggregatesResult as a ResourceProviderAggregates. +func (r GetAggregatesResult) Extract() (*ResourceProviderAggregates, error) { + var s ResourceProviderAggregates + err := r.ExtractInto(&s) + return &s, err +} diff --git a/openstack/placement/v1/resourceproviders/testing/fixtures_test.go b/openstack/placement/v1/resourceproviders/testing/fixtures_test.go index e663cab280..ef44663212 100644 --- a/openstack/placement/v1/resourceproviders/testing/fixtures_test.go +++ b/openstack/placement/v1/resourceproviders/testing/fixtures_test.go @@ -5,13 +5,17 @@ import ( "net/http" "testing" + "github.com/gophercloud/gophercloud/v2/internal/ptr" "github.com/gophercloud/gophercloud/v2/openstack/placement/v1/resourceproviders" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) const ResourceProviderTestID = "99c09379-6e52-4ef8-9a95-b9ce6f68452e" +const PresentInventoryResourceClass = "VCPU" +const MissingInventoryResourceClass = "NO_SUCH_CLASS" +const NonExistentRPID = "00000000-0000-0000-0000-000000000000" const ResourceProvidersBody = ` { @@ -128,6 +132,62 @@ const InventoriesBody = ` } ` +const UpdateInventoriesRequest = ` +{ + "resource_provider_generation": 7, + "inventories": { + "DISK_GB": { + "allocation_ratio": 1.0, + "max_unit": 35, + "min_unit": 1, + "reserved": 0, + "step_size": 1, + "total": 35 + }, + "MEMORY_MB": { + "allocation_ratio": 1.5, + "max_unit": 5825, + "min_unit": 1, + "reserved": 512, + "step_size": 1, + "total": 5825 + }, + "VCPU": { + "allocation_ratio": 16.0, + "max_unit": 4, + "min_unit": 1, + "reserved": 0, + "step_size": 1, + "total": 4 + } + } +} +` + +const InventoryBody = ` +{ + "resource_provider_generation": 7, + "allocation_ratio": 16.0, + "max_unit": 4, + "min_unit": 1, + "reserved": 0, + "step_size": 1, + "total": 4 +} +` + +const UpdateInventoryRequest = ` +{ + "resource_provider_generation": 7, + "allocation_ratio": 16.0, + "max_unit": 4, + "min_unit": 1, + "reserved": 0, + "step_size": 1, + "total": 4 +} +` + const AllocationsBody = ` { "allocations": { @@ -164,6 +224,52 @@ const TraitsBody = ` } ` +const AggregatesBody = ` +{ + "resource_provider_generation": 1, + "aggregates": [ + "6d84f6f6-7736-40ff-84d2-7db47f18ea25", + "f11f14bc-6f17-4f0a-b7c2-44b3e685ccf4" + ] +} +` + +const AggregatesBodyPreGeneration = ` +{ + "aggregates": [ + "6d84f6f6-7736-40ff-84d2-7db47f18ea25", + "f11f14bc-6f17-4f0a-b7c2-44b3e685ccf4" + ] +} +` + +const AggregatesUpdateBody = ` +{ + "resource_provider_generation": 1, + "aggregates": [ + "89f68995-4fd8-4f8b-a03e-7d5980762ff2", + "16d0e5f2-7f66-4f32-9040-b09de2f40afd" + ] +} +` + +// AggregatesUpdateRequestPreGeneration is the raw JSON array sent as the PUT request body +// for microversions < 1.19, where the payload is just a list of aggregate UUIDs. +const AggregatesUpdateRequestPreGeneration = `["89f68995-4fd8-4f8b-a03e-7d5980762ff2","16d0e5f2-7f66-4f32-9040-b09de2f40afd"]` + +// AggregatesUpdateBodyWithoutGeneration is the pre-1.19 server response body for an aggregates update. +// Unlike the request, the response is not flat. +const AggregatesUpdateBodyWithoutGeneration = ` +{ + "aggregates": [ + "89f68995-4fd8-4f8b-a03e-7d5980762ff2", + "16d0e5f2-7f66-4f32-9040-b09de2f40afd" + ] +} +` + +const AbsentResourceProviderID = "00000000-0000-0000-0000-000000000000" + var ExpectedResourceProvider1 = resourceproviders.ResourceProvider{ Generation: 1, UUID: "99c09379-6e52-4ef8-9a95-b9ce6f68452e", @@ -236,6 +342,18 @@ var ExpectedInventories = resourceproviders.ResourceProviderInventories{ }, } +var ExpectedInventory = resourceproviders.ResourceProviderInventory{ + ResourceProviderGeneration: 7, + Inventory: resourceproviders.Inventory{ + AllocationRatio: 16.0, + MaxUnit: 4, + MinUnit: 1, + Reserved: 0, + StepSize: 1, + Total: 4, + }, +} + var ExpectedAllocations = resourceproviders.ResourceProviderAllocations{ ResourceProviderGeneration: 12, Allocations: map[string]resourceproviders.Allocation{ @@ -268,55 +386,109 @@ var ExpectedTraits = resourceproviders.ResourceProviderTraits{ }, } -func HandleResourceProviderList(t *testing.T) { - th.Mux.HandleFunc("/resource_providers", +var expectedAggregatesGeneration = 1 + +var ExpectedAggregates = resourceproviders.ResourceProviderAggregates{ + ResourceProviderGeneration: &expectedAggregatesGeneration, + Aggregates: []string{ + "6d84f6f6-7736-40ff-84d2-7db47f18ea25", + "f11f14bc-6f17-4f0a-b7c2-44b3e685ccf4", + }, +} + +var ExpectedAggregatesPreGeneration = resourceproviders.ResourceProviderAggregates{ + ResourceProviderGeneration: nil, + Aggregates: []string{ + "6d84f6f6-7736-40ff-84d2-7db47f18ea25", + "f11f14bc-6f17-4f0a-b7c2-44b3e685ccf4", + }, +} + +var expectedUpdatedAggregatesGeneration = 1 + +var ExpectedUpdatedAggregates = resourceproviders.ResourceProviderAggregates{ + ResourceProviderGeneration: &expectedUpdatedAggregatesGeneration, + Aggregates: []string{ + "89f68995-4fd8-4f8b-a03e-7d5980762ff2", + "16d0e5f2-7f66-4f32-9040-b09de2f40afd", + }, +} + +var ExpectedUpdatedAggregatesPreGeneration = resourceproviders.ResourceProviderAggregates{ + Aggregates: []string{ + "89f68995-4fd8-4f8b-a03e-7d5980762ff2", + "16d0e5f2-7f66-4f32-9040-b09de2f40afd", + }, +} + +func HandleResourceProviderList(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/resource_providers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ResourceProvidersBody) + fmt.Fprint(w, ResourceProvidersBody) }) } -func HandleResourceProviderCreate(t *testing.T) { - th.Mux.HandleFunc("/resource_providers", func(w http.ResponseWriter, r *http.Request) { +func HandleResourceProviderList139(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/resource_providers", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + q := r.URL.Query() + th.AssertDeepEquals(t, + []string{"a09ba171-9405-40ca-bfe1-a8d1208af2ed", "47abce38-8a58-47b3-81e0-c647e37e03ae"}, + q["member_of"]) + th.AssertDeepEquals(t, []string{"HW:A_TRAIT", "CUSTOM_TRAIT"}, q["required"]) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, ResourceProvidersBody) + }) +} + +func HandleResourceProviderCreate(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/resource_providers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ResourceProviderCreateBody) + fmt.Fprint(w, ResourceProviderCreateBody) }) } -func HandleResourceProviderGet(t *testing.T) { - th.Mux.HandleFunc("/resource_providers/99c09379-6e52-4ef8-9a95-b9ce6f68452e", func(w http.ResponseWriter, r *http.Request) { +func HandleResourceProviderGet(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/resource_providers/99c09379-6e52-4ef8-9a95-b9ce6f68452e", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ResourceProviderCreateBody) + fmt.Fprint(w, ResourceProviderCreateBody) }) } -func HandleResourceProviderDelete(t *testing.T) { - th.Mux.HandleFunc("/resource_providers/b99b3ab4-3aa6-4fba-b827-69b88b9c544a", func(w http.ResponseWriter, r *http.Request) { +func HandleResourceProviderDelete(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/resource_providers/b99b3ab4-3aa6-4fba-b827-69b88b9c544a", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusNoContent) }) } -func HandleResourceProviderUpdate(t *testing.T) { - th.Mux.HandleFunc("/resource_providers/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { +func HandleResourceProviderUpdate(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/resource_providers/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ResourceProviderUpdateRequest) @@ -324,66 +496,387 @@ func HandleResourceProviderUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ResourceProviderUpdateResponse) + fmt.Fprint(w, ResourceProviderUpdateResponse) }) } -func HandleResourceProviderGetUsages(t *testing.T) { +func HandleResourceProviderGetUsages(t *testing.T, fakeServer th.FakeServer) { usageTestUrl := fmt.Sprintf("/resource_providers/%s/usages", ResourceProviderTestID) - th.Mux.HandleFunc(usageTestUrl, + fakeServer.Mux.HandleFunc(usageTestUrl, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UsagesBody) + fmt.Fprint(w, UsagesBody) }) } -func HandleResourceProviderGetInventories(t *testing.T) { +func HandleResourceProviderGetInventories(t *testing.T, fakeServer th.FakeServer) { inventoriesTestUrl := fmt.Sprintf("/resource_providers/%s/inventories", ResourceProviderTestID) - th.Mux.HandleFunc(inventoriesTestUrl, + fakeServer.Mux.HandleFunc(inventoriesTestUrl, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, InventoriesBody) + }) +} + +func HandleResourceProviderGetInventory(t *testing.T, fakeServer th.FakeServer) { + inventoryTestURL := fmt.Sprintf("/resource_providers/%s/inventories/%s", ResourceProviderTestID, PresentInventoryResourceClass) + + fakeServer.Mux.HandleFunc(inventoryTestURL, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, InventoriesBody) + fmt.Fprint(w, InventoryBody) }) } -func HandleResourceProviderGetAllocations(t *testing.T) { +func HandleResourceProviderGetInventoryNotFound(t *testing.T, fakeServer th.FakeServer) { + inventoryNotFoundURL := fmt.Sprintf("/resource_providers/%s/inventories/%s", ResourceProviderTestID, MissingInventoryResourceClass) + + fakeServer.Mux.HandleFunc(inventoryNotFoundURL, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + w.WriteHeader(http.StatusNotFound) + }) +} + +func HandleResourceProviderPutInventories(t *testing.T, fakeServer th.FakeServer) { + inventoriesTestURL := fmt.Sprintf("/resource_providers/%s/inventories", ResourceProviderTestID) + + fakeServer.Mux.HandleFunc(inventoriesTestURL, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, UpdateInventoriesRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, InventoriesBody) + }) +} + +func HandleResourceProviderPutInventoriesNotFound(t *testing.T, fakeServer th.FakeServer) { + inventoriesNotFoundURL := fmt.Sprintf("/resource_providers/%s/inventories", NonExistentRPID) + + fakeServer.Mux.HandleFunc(inventoriesNotFoundURL, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusNotFound) + fmt.Fprint(w, `{"errors":[{"status":404,"title":"Not Found"}]}`) + }) +} + +func HandleResourceProviderPutInventory(t *testing.T, fakeServer th.FakeServer) { + inventoryTestURL := fmt.Sprintf("/resource_providers/%s/inventories/%s", ResourceProviderTestID, PresentInventoryResourceClass) + + fakeServer.Mux.HandleFunc(inventoryTestURL, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, UpdateInventoryRequest) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, InventoryBody) + }) +} + +func HandleResourceProviderPutInventoryNotFound(t *testing.T, fakeServer th.FakeServer) { + inventoryNotFoundURL := fmt.Sprintf("/resource_providers/%s/inventories/%s", NonExistentRPID, PresentInventoryResourceClass) + + fakeServer.Mux.HandleFunc(inventoryNotFoundURL, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusNotFound) + fmt.Fprint(w, `{"errors":[{"status":404,"title":"Not Found"}]}`) + }) +} + +func HandleResourceProviderDeleteInventorySuccess(t *testing.T, fakeServer th.FakeServer) { + inventoryDeleteURL := fmt.Sprintf("/resource_providers/%s/inventories/%s", ResourceProviderTestID, PresentInventoryResourceClass) + + fakeServer.Mux.HandleFunc(inventoryDeleteURL, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.AssertEquals(t, "", r.URL.RawQuery) + w.WriteHeader(http.StatusNoContent) + }) +} + +func HandleResourceProviderDeleteInventoryInUse(t *testing.T, fakeServer th.FakeServer) { + inventoryDeleteURL := fmt.Sprintf("/resource_providers/%s/inventories/%s", ResourceProviderTestID, PresentInventoryResourceClass) + + fakeServer.Mux.HandleFunc(inventoryDeleteURL, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.AssertEquals(t, "", r.URL.RawQuery) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusConflict) + fmt.Fprint(w, `{"errors":[{"status":409,"title":"Conflict","detail":"Inventory is in use.","code":"placement.inventory.inuse"}]}`) + }) +} + +func HandleResourceProviderDeleteInventoriesSuccess(t *testing.T, fakeServer th.FakeServer) { + inventoriesDeleteURL := fmt.Sprintf("/resource_providers/%s/inventories", ResourceProviderTestID) + + fakeServer.Mux.HandleFunc(inventoriesDeleteURL, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.AssertEquals(t, "", r.URL.RawQuery) + w.WriteHeader(http.StatusNoContent) + }) +} + +func HandleResourceProviderDeleteInventoriesConflict(t *testing.T, fakeServer th.FakeServer) { + inventoriesDeleteURL := fmt.Sprintf("/resource_providers/%s/inventories", ResourceProviderTestID) + + fakeServer.Mux.HandleFunc(inventoriesDeleteURL, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.AssertEquals(t, "", r.URL.RawQuery) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusConflict) + fmt.Fprint(w, `{"errors":[{"status":409,"title":"Conflict","detail":"Inventory is in use.","code":"placement.inventory.inuse"}]}`) + }) +} + +func HandleResourceProviderGetAllocations(t *testing.T, fakeServer th.FakeServer) { allocationsTestUrl := fmt.Sprintf("/resource_providers/%s/allocations", ResourceProviderTestID) - th.Mux.HandleFunc(allocationsTestUrl, + fakeServer.Mux.HandleFunc(allocationsTestUrl, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, AllocationsBody) + fmt.Fprint(w, AllocationsBody) }) } -func HandleResourceProviderGetTraits(t *testing.T) { +func HandleResourceProviderGetTraits(t *testing.T, fakeServer th.FakeServer) { traitsTestUrl := fmt.Sprintf("/resource_providers/%s/traits", ResourceProviderTestID) - th.Mux.HandleFunc(traitsTestUrl, + fakeServer.Mux.HandleFunc(traitsTestUrl, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, TraitsBody) + }) +} + +func HandleResourceProviderPutTraits(t *testing.T, fakeServer th.FakeServer) { + traitsTestUrl := fmt.Sprintf("/resource_providers/%s/traits", ResourceProviderTestID) + + fakeServer.Mux.HandleFunc(traitsTestUrl, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, TraitsBody) + }) +} + +func HandleResourceProviderDeleteTraits(t *testing.T, fakeServer th.FakeServer) { + traitsTestUrl := fmt.Sprintf("/resource_providers/%s/traits", ResourceProviderTestID) + + fakeServer.Mux.HandleFunc(traitsTestUrl, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + w.WriteHeader(http.StatusNoContent) + }) +} + +func HandleResourceProviderGetAggregatesSuccess(t *testing.T, fakeServer th.FakeServer) { + aggregatesTestURL := fmt.Sprintf("/resource_providers/%s/aggregates", ResourceProviderTestID) + + fakeServer.Mux.HandleFunc(aggregatesTestURL, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, AggregatesBody) + }) +} + +func HandleResourceProviderGetAggregatesPreGenerationSuccess(t *testing.T, fakeServer th.FakeServer) { + aggregatesTestURL := fmt.Sprintf("/resource_providers/%s/aggregates", ResourceProviderTestID) + + fakeServer.Mux.HandleFunc(aggregatesTestURL, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, TraitsBody) + fmt.Fprint(w, AggregatesBodyPreGeneration) }) } + +func HandleResourceProviderGetAggregatesNotFound(t *testing.T, fakeServer th.FakeServer) { + aggregatesTestURL := fmt.Sprintf("/resource_providers/%s/aggregates", AbsentResourceProviderID) + + fakeServer.Mux.HandleFunc(aggregatesTestURL, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNotFound) + }) +} + +func HandleResourceProviderUpdateAndGetAggregatesSuccess(t *testing.T, fakeServer th.FakeServer) { + aggregatesTestURL := fmt.Sprintf("/resource_providers/%s/aggregates", ResourceProviderTestID) + + // This handler must cover PUT and GET because the test updates and then fetches on the same path. + body := AggregatesBody + + fakeServer.Mux.HandleFunc(aggregatesTestURL, + func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, body) + case http.MethodPut: + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, AggregatesUpdateBody) + + body = AggregatesUpdateBody + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, body) + default: + w.WriteHeader(http.StatusMethodNotAllowed) + } + }) +} + +func HandleResourceProviderUpdateAndGetAggregatesPreGenerationSuccess(t *testing.T, fakeServer th.FakeServer) { + aggregatesTestURL := fmt.Sprintf("/resource_providers/%s/aggregates", ResourceProviderTestID) + + // Pre-generation variant of the same update-then-fetch flow on a shared endpoint. + body := AggregatesBodyPreGeneration + + fakeServer.Mux.HandleFunc(aggregatesTestURL, + func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, body) + case http.MethodPut: + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, AggregatesUpdateRequestPreGeneration) + + body = AggregatesUpdateBodyWithoutGeneration + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, body) + default: + w.WriteHeader(http.StatusMethodNotAllowed) + } + }) +} + +func HandleResourceProviderUpdateAggregatesConflict(t *testing.T, fakeServer th.FakeServer) { + aggregatesTestURL := fmt.Sprintf("/resource_providers/%s/aggregates", ResourceProviderTestID) + + fakeServer.Mux.HandleFunc(aggregatesTestURL, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusConflict) + }) +} + +// ToInventoryUpdateBase converts a ResourceProvider Inventory result into its +// corresponding Update (pointer) version. This is used in tests to facilitate +// the mapping between API results (which contain values) and Update operations +// (which use pointers for optionality). +func ToInventoryUpdateBase(inv resourceproviders.Inventory) resourceproviders.InventoryUpdateBase { + return resourceproviders.InventoryUpdateBase{ + AllocationRatio: ptr.To(inv.AllocationRatio), + MaxUnit: ptr.To(inv.MaxUnit), + MinUnit: ptr.To(inv.MinUnit), + Reserved: ptr.To(inv.Reserved), + StepSize: ptr.To(inv.StepSize), + Total: inv.Total, + } +} + +// ToUpdateInventoriesOpts converts a ResourceProviderInventories result into UpdateInventoriesOpts. +func ToUpdateInventoriesOpts(inventories resourceproviders.ResourceProviderInventories) resourceproviders.UpdateInventoriesOpts { + opts := resourceproviders.UpdateInventoriesOpts{ + ResourceProviderGeneration: inventories.ResourceProviderGeneration, + Inventories: make(map[string]resourceproviders.InventoryUpdateBase, len(inventories.Inventories)), + } + + for resourceClass, inv := range inventories.Inventories { + opts.Inventories[resourceClass] = ToInventoryUpdateBase(inv) + } + + return opts +} + +// ToUpdateInventoryOpts converts a ResourceProviderInventory result into UpdateInventoryOpts. +func ToUpdateInventoryOpts(inventory resourceproviders.ResourceProviderInventory) resourceproviders.UpdateInventoryOpts { + return resourceproviders.UpdateInventoryOpts{ + ResourceProviderGeneration: inventory.ResourceProviderGeneration, + InventoryUpdateBase: ToInventoryUpdateBase(inventory.Inventory), + } +} diff --git a/openstack/placement/v1/resourceproviders/testing/requests_test.go b/openstack/placement/v1/resourceproviders/testing/requests_test.go index ad0db1475c..9e24e3a961 100644 --- a/openstack/placement/v1/resourceproviders/testing/requests_test.go +++ b/openstack/placement/v1/resourceproviders/testing/requests_test.go @@ -2,23 +2,25 @@ package testing import ( "context" + "net/http" "testing" + "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/openstack/placement/v1/resourceproviders" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestListResourceProviders(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleResourceProviderList(t) + HandleResourceProviderList(t, fakeServer) count := 0 - err := resourceproviders.List(fake.ServiceClient(), resourceproviders.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := resourceproviders.List(client.ServiceClient(fakeServer), resourceproviders.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := resourceproviders.ExtractResourceProviders(page) @@ -38,11 +40,38 @@ func TestListResourceProviders(t *testing.T) { } } +func TestListResourceProviders139(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleResourceProviderList139(t, fakeServer) + + count := 0 + opts := resourceproviders.ListOpts139{ + MemberOf: []string{"a09ba171-9405-40ca-bfe1-a8d1208af2ed", "47abce38-8a58-47b3-81e0-c647e37e03ae"}, + Required: []string{"HW:A_TRAIT", "CUSTOM_TRAIT"}, + } + err := resourceproviders.List(client.ServiceClient(fakeServer), opts).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + count++ + + actual, err := resourceproviders.ExtractResourceProviders(page) + if err != nil { + t.Errorf("Failed to extract resource providers: %v", err) + return false, err + } + th.AssertDeepEquals(t, ExpectedResourceProviders, actual) + + return true, nil + }) + + th.AssertNoErr(t, err) +} + func TestCreateResourceProvider(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleResourceProviderCreate(t) + HandleResourceProviderCreate(t, fakeServer) expected := ExpectedResourceProvider1 @@ -52,41 +81,41 @@ func TestCreateResourceProvider(t *testing.T) { ParentProviderUUID: ExpectedResourceProvider1.ParentProviderUUID, } - actual, err := resourceproviders.Create(context.TODO(), fake.ServiceClient(), opts).Extract() + actual, err := resourceproviders.Create(context.TODO(), client.ServiceClient(fakeServer), opts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, &expected, actual) } func TestGetResourceProvider(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleResourceProviderGet(t) + HandleResourceProviderGet(t, fakeServer) expected := ExpectedResourceProvider1 - actual, err := resourceproviders.Get(context.TODO(), fake.ServiceClient(), ExpectedResourceProvider1.UUID).Extract() + actual, err := resourceproviders.Get(context.TODO(), client.ServiceClient(fakeServer), ExpectedResourceProvider1.UUID).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, &expected, actual) } func TestDeleteResourceProvider(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleResourceProviderDelete(t) + HandleResourceProviderDelete(t, fakeServer) - res := resourceproviders.Delete(context.TODO(), fake.ServiceClient(), "b99b3ab4-3aa6-4fba-b827-69b88b9c544a") + res := resourceproviders.Delete(context.TODO(), client.ServiceClient(fakeServer), "b99b3ab4-3aa6-4fba-b827-69b88b9c544a") th.AssertNoErr(t, res.Err) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleResourceProviderUpdate(t) + HandleResourceProviderUpdate(t, fakeServer) name := "new_name" parentProviderUUID := "b99b3ab4-3aa6-4fba-b827-69b88b9c544a" @@ -95,53 +124,322 @@ func TestUpdate(t *testing.T) { Name: &name, ParentProviderUUID: &parentProviderUUID, } - rp, err := resourceproviders.Update(context.TODO(), fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", options).Extract() + rp, err := resourceproviders.Update(context.TODO(), client.ServiceClient(fakeServer), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", options).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, rp.Name, name) th.AssertEquals(t, rp.ParentProviderUUID, parentProviderUUID) } +func TestUpdateParentProviderUUID(t *testing.T) { + // 1. Test non-empty UUID + uuid := "b99b3ab4-3aa6-4fba-b827-69b88b9c544a" + opts := resourceproviders.UpdateOpts{ + ParentProviderUUID: &uuid, + } + actual, err := opts.ToResourceProviderUpdateMap() + th.AssertNoErr(t, err) + expected := map[string]any{ + "parent_provider_uuid": uuid, + } + th.AssertDeepEquals(t, expected, actual) + + // 2. Test empty string sentinel (should be null) + empty := "" + opts = resourceproviders.UpdateOpts{ + ParentProviderUUID: &empty, + } + actual, err = opts.ToResourceProviderUpdateMap() + th.AssertNoErr(t, err) + expected = map[string]any{ + "parent_provider_uuid": nil, + } + th.AssertDeepEquals(t, expected, actual) + + // 3. Test nil (should be omitted) + opts = resourceproviders.UpdateOpts{} + actual, err = opts.ToResourceProviderUpdateMap() + th.AssertNoErr(t, err) + expected = map[string]any{} + th.AssertDeepEquals(t, expected, actual) +} + func TestGetResourceProvidersUsages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleResourceProviderGetUsages(t) + HandleResourceProviderGetUsages(t, fakeServer) - actual, err := resourceproviders.GetUsages(context.TODO(), fake.ServiceClient(), ResourceProviderTestID).Extract() + actual, err := resourceproviders.GetUsages(context.TODO(), client.ServiceClient(fakeServer), ResourceProviderTestID).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, ExpectedUsages, *actual) } func TestGetResourceProvidersInventories(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleResourceProviderGetInventories(t, fakeServer) + + actual, err := resourceproviders.GetInventories(context.TODO(), client.ServiceClient(fakeServer), ResourceProviderTestID).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedInventories, *actual) +} + +func TestGetResourceProviderInventory(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleResourceProviderGetInventories(t) + HandleResourceProviderGetInventory(t, fakeServer) - actual, err := resourceproviders.GetInventories(context.TODO(), fake.ServiceClient(), ResourceProviderTestID).Extract() + actual, err := resourceproviders.GetInventory(context.TODO(), client.ServiceClient(fakeServer), ResourceProviderTestID, PresentInventoryResourceClass).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedInventory, *actual) +} + +func TestGetResourceProviderInventoryNotFound(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleResourceProviderGetInventoryNotFound(t, fakeServer) + + _, err := resourceproviders.GetInventory(context.TODO(), client.ServiceClient(fakeServer), ResourceProviderTestID, MissingInventoryResourceClass).Extract() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) +} + +func TestUpdateResourceProvidersInventories(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleResourceProviderPutInventories(t, fakeServer) + + opts := ToUpdateInventoriesOpts(ExpectedInventories) + actual, err := resourceproviders.UpdateInventories(context.TODO(), client.ServiceClient(fakeServer), ResourceProviderTestID, opts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, ExpectedInventories, *actual) } +func TestUpdateResourceProvidersInventoriesNotFound(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleResourceProviderPutInventoriesNotFound(t, fakeServer) + + opts := ToUpdateInventoriesOpts(ExpectedInventories) + _, err := resourceproviders.UpdateInventories(context.TODO(), client.ServiceClient(fakeServer), NonExistentRPID, opts).Extract() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) +} + +func TestUpdateResourceProviderInventory(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleResourceProviderPutInventory(t, fakeServer) + + opts := ToUpdateInventoryOpts(ExpectedInventory) + actual, err := resourceproviders.UpdateInventory(context.TODO(), client.ServiceClient(fakeServer), ResourceProviderTestID, PresentInventoryResourceClass, opts).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedInventory, *actual) +} + +func TestUpdateResourceProviderInventoryNotFound(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleResourceProviderPutInventoryNotFound(t, fakeServer) + + opts := ToUpdateInventoryOpts(ExpectedInventory) + _, err := resourceproviders.UpdateInventory(context.TODO(), client.ServiceClient(fakeServer), NonExistentRPID, PresentInventoryResourceClass, opts).Extract() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) +} + +func TestDeleteResourceProviderInventorySuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleResourceProviderDeleteInventorySuccess(t, fakeServer) + + err := resourceproviders.DeleteInventory(context.TODO(), client.ServiceClient(fakeServer), ResourceProviderTestID, PresentInventoryResourceClass).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestDeleteResourceProviderInventoryInUse(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleResourceProviderDeleteInventoryInUse(t, fakeServer) + + err := resourceproviders.DeleteInventory(context.TODO(), client.ServiceClient(fakeServer), ResourceProviderTestID, PresentInventoryResourceClass).ExtractErr() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusConflict)) +} + +func TestDeleteResourceProviderInventoriesSuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleResourceProviderDeleteInventoriesSuccess(t, fakeServer) + + err := resourceproviders.DeleteInventories(context.TODO(), client.ServiceClient(fakeServer), ResourceProviderTestID).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestDeleteResourceProviderInventoriesConflict(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleResourceProviderDeleteInventoriesConflict(t, fakeServer) + + err := resourceproviders.DeleteInventories(context.TODO(), client.ServiceClient(fakeServer), ResourceProviderTestID).ExtractErr() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusConflict)) +} + func TestGetResourceProvidersAllocations(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - HandleResourceProviderGetAllocations(t) + HandleResourceProviderGetAllocations(t, fakeServer) - actual, err := resourceproviders.GetAllocations(context.TODO(), fake.ServiceClient(), ResourceProviderTestID).Extract() + actual, err := resourceproviders.GetAllocations(context.TODO(), client.ServiceClient(fakeServer), ResourceProviderTestID).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, ExpectedAllocations, *actual) } func TestGetResourceProvidersTraits(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleResourceProviderGetTraits(t, fakeServer) + + actual, err := resourceproviders.GetTraits(context.TODO(), client.ServiceClient(fakeServer), ResourceProviderTestID).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedTraits, *actual) +} - HandleResourceProviderGetTraits(t) +func TestUpdateResourceProvidersTraits(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - actual, err := resourceproviders.GetTraits(context.TODO(), fake.ServiceClient(), ResourceProviderTestID).Extract() + HandleResourceProviderPutTraits(t, fakeServer) + + opts := resourceproviders.UpdateTraitsOpts(ExpectedTraits) + actual, err := resourceproviders.UpdateTraits(context.TODO(), client.ServiceClient(fakeServer), ResourceProviderTestID, opts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, ExpectedTraits, *actual) } + +func TestDeleteResourceProvidersTraits(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleResourceProviderDeleteTraits(t, fakeServer) + + err := resourceproviders.DeleteTraits(context.TODO(), client.ServiceClient(fakeServer), ResourceProviderTestID).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestGetResourceProviderAggregatesSuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleResourceProviderGetAggregatesSuccess(t, fakeServer) + + actual, err := resourceproviders.GetAggregates(context.TODO(), client.ServiceClient(fakeServer), ResourceProviderTestID).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedAggregates, *actual) +} + +func TestGetResourceProviderAggregatesPreGenerationSuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleResourceProviderGetAggregatesPreGenerationSuccess(t, fakeServer) + + actual, err := resourceproviders.GetAggregates(context.TODO(), client.ServiceClient(fakeServer), ResourceProviderTestID).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedAggregatesPreGeneration, *actual) +} + +func TestGetResourceProviderAggregatesNotFound(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleResourceProviderGetAggregatesNotFound(t, fakeServer) + + _, err := resourceproviders.GetAggregates(context.TODO(), client.ServiceClient(fakeServer), AbsentResourceProviderID).Extract() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) +} + +func TestUpdateResourceProviderAggregatesSuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleResourceProviderUpdateAndGetAggregatesSuccess(t, fakeServer) + + sc := client.ServiceClient(fakeServer) + sc.Microversion = "1.19" + + updateOpts := resourceproviders.UpdateAggregatesOpts(ExpectedUpdatedAggregates) + _, err := resourceproviders.UpdateAggregates(context.TODO(), sc, ResourceProviderTestID, updateOpts).Extract() + th.AssertNoErr(t, err) + + actual, err := resourceproviders.GetAggregates(context.TODO(), sc, ResourceProviderTestID).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedUpdatedAggregates, *actual) +} + +func TestUpdateResourceProviderAggregatesPreGenerationSuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleResourceProviderUpdateAndGetAggregatesPreGenerationSuccess(t, fakeServer) + + sc := client.ServiceClient(fakeServer) + sc.Microversion = "1.10" + + updateOpts := resourceproviders.UpdateAggregatesOpts{ + Aggregates: ExpectedUpdatedAggregatesPreGeneration.Aggregates, + } + _, err := resourceproviders.UpdateAggregates(context.TODO(), sc, ResourceProviderTestID, updateOpts).Extract() + th.AssertNoErr(t, err) + + actual, err := resourceproviders.GetAggregates(context.TODO(), sc, ResourceProviderTestID).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedUpdatedAggregatesPreGeneration, *actual) +} + +// TestUpdateResourceProviderAggregatesPreGenerationWithGenerationInOptsSuccess validates that +// when the microversion is < 1.19 but the caller supplies ResourceProviderGeneration in opts, +// the generation field is stripped from the request body and the operation succeeds. +func TestUpdateResourceProviderAggregatesPreGenerationWithGenerationInOptsSuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + // Reuse the same handler: it validates the request body contains only aggregates (no generation). + HandleResourceProviderUpdateAndGetAggregatesPreGenerationSuccess(t, fakeServer) + + sc := client.ServiceClient(fakeServer) + sc.Microversion = "1.10" + + gen := 1 + updateOpts := resourceproviders.UpdateAggregatesOpts{ + ResourceProviderGeneration: &gen, + Aggregates: ExpectedUpdatedAggregatesPreGeneration.Aggregates, + } + _, err := resourceproviders.UpdateAggregates(context.TODO(), sc, ResourceProviderTestID, updateOpts).Extract() + th.AssertNoErr(t, err) + + actual, err := resourceproviders.GetAggregates(context.TODO(), sc, ResourceProviderTestID).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedUpdatedAggregatesPreGeneration, *actual) +} + +func TestUpdateResourceProviderAggregatesConflict(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleResourceProviderUpdateAggregatesConflict(t, fakeServer) + + updateOpts := resourceproviders.UpdateAggregatesOpts(ExpectedUpdatedAggregates) + _, err := resourceproviders.UpdateAggregates(context.TODO(), client.ServiceClient(fakeServer), ResourceProviderTestID, updateOpts).Extract() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusConflict)) +} diff --git a/openstack/placement/v1/resourceproviders/urls.go b/openstack/placement/v1/resourceproviders/urls.go index 037149b684..b55426d4a7 100644 --- a/openstack/placement/v1/resourceproviders/urls.go +++ b/openstack/placement/v1/resourceproviders/urls.go @@ -30,6 +30,22 @@ func getResourceProviderInventoriesURL(client *gophercloud.ServiceClient, resour return client.ServiceURL(apiName, resourceProviderID, "inventories") } +func deleteResourceProviderInventoriesURL(client *gophercloud.ServiceClient, resourceProviderID string) string { + return client.ServiceURL(apiName, resourceProviderID, "inventories") +} + +func getResourceProviderInventoryURL(client *gophercloud.ServiceClient, resourceProviderID, resourceClass string) string { + return client.ServiceURL(apiName, resourceProviderID, "inventories", resourceClass) +} + +func updateResourceProviderInventoryURL(client *gophercloud.ServiceClient, resourceProviderID, resourceClass string) string { + return client.ServiceURL(apiName, resourceProviderID, "inventories", resourceClass) +} + +func deleteResourceProviderInventoryURL(client *gophercloud.ServiceClient, resourceProviderID, resourceClass string) string { + return client.ServiceURL(apiName, resourceProviderID, "inventories", resourceClass) +} + func getResourceProviderAllocationsURL(client *gophercloud.ServiceClient, resourceProviderID string) string { return client.ServiceURL(apiName, resourceProviderID, "allocations") } @@ -37,3 +53,11 @@ func getResourceProviderAllocationsURL(client *gophercloud.ServiceClient, resour func getResourceProviderTraitsURL(client *gophercloud.ServiceClient, resourceProviderID string) string { return client.ServiceURL(apiName, resourceProviderID, "traits") } + +func getResourceProviderAggregatesURL(client *gophercloud.ServiceClient, resourceProviderID string) string { + return client.ServiceURL(apiName, resourceProviderID, "aggregates") +} + +func updateResourceProviderAggregatesURL(client *gophercloud.ServiceClient, resourceProviderID string) string { + return client.ServiceURL(apiName, resourceProviderID, "aggregates") +} diff --git a/openstack/placement/v1/traits/doc.go b/openstack/placement/v1/traits/doc.go new file mode 100644 index 0000000000..d2774d9f29 --- /dev/null +++ b/openstack/placement/v1/traits/doc.go @@ -0,0 +1,66 @@ +/* +Package traits manages traits from the OpenStack Placement service. + +Traits API requests are available starting from microversion 1.6. + +Example to list traits + + placementClient.Microversion = "1.6" + + allPages, err := traits.List(placementClient, traits.ListOpts{}).AllPages(context.TODO()) + if err != nil { + panic(err) + } + + allTraits, err := traits.ExtractTraits(allPages) + if err != nil { + panic(err) + } + + for _, t := range allTraits { + fmt.Println(t) + } + +Example to check if a trait exists + + placementClient.Microversion = "1.6" + + traitName := "CUSTOM_HW_FPGA_CLASS1" + err := traits.Get(context.TODO(), placementClient, traitName).ExtractErr() + if err != nil { + if gophercloud.ResponseCodeIs(err, http.StatusNotFound) { + // 404 Not Found - The trait does not exist + fmt.Println("Trait does not exist.") + } else { + // Another error occurred + panic(err) + } + } else { + fmt.Println("Trait exists!") + } + +Example to create a trait + + placementClient.Microversion = "1.6" + + traitName := "CUSTOM_HW_FPGA_CLASS1" + err := traits.Create(context.TODO(), placementClient, traitName).ExtractErr() + if err != nil { + panic(err) + } else { + fmt.Println("Trait created successfully!") + } + +Example to delete a trait + + placementClient.Microversion = "1.6" + + traitName := "CUSTOM_HW_FPGA_CLASS1" + err := traits.Delete(context.TODO(), placementClient, traitName).ExtractErr() + if err != nil { + panic(err) + } else { + fmt.Println("Trait deleted successfully!") + } +*/ +package traits diff --git a/openstack/placement/v1/traits/requests.go b/openstack/placement/v1/traits/requests.go new file mode 100644 index 0000000000..99e9b58434 --- /dev/null +++ b/openstack/placement/v1/traits/requests.go @@ -0,0 +1,78 @@ +package traits + +import ( + "context" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to +// the List request. +type ListOptsBuilder interface { + ToTraitListQuery() (string, error) +} + +// ListOpts allows the filtering of traits. Filtering is achieved by passing in struct +// field values that map to the trait attributes you want to see returned. +type ListOpts struct { + // Name is a string used to filter traits by name. + // It supports startswith operator to filter the traits whose name begins with + // a specific prefix, e.g. name=startswith:CUSTOM + // in operator filters the traits whose name is in the specified list, + // e.g. name=in:HW_CPU_X86_AVX,HW_CPU_X86_SSE,HW_CPU_X86_INVALID_FEATURE + Name string `q:"name"` + + // Associated is a boolean used to filter traits by whether they are associated with + // at least one resource provider. + Associated *bool `q:"associated"` +} + +// ToTraitListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToTraitListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List retrieves a list of traits. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + + if opts != nil { + query, err := opts.ToTraitListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return TraitsPage{pagination.SinglePageBase(r)} + }) +} + +// Get confirms the existence of a trait. +func Get(ctx context.Context, client *gophercloud.ServiceClient, traitName string) (r GetResult) { + resp, err := client.Get(ctx, getURL(client, traitName), nil, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// Create creates a new trait. +func Create(ctx context.Context, client *gophercloud.ServiceClient, traitName string) (r CreateResult) { + resp, err := client.Put(ctx, createURL(client, traitName), nil, nil, &gophercloud.RequestOpts{ + OkCodes: []int{201, 204}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// Delete deletes the trait specified by name. +func Delete(ctx context.Context, client *gophercloud.ServiceClient, traitName string) (r DeleteResult) { + resp, err := client.Delete(ctx, deleteURL(client, traitName), &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/placement/v1/traits/results.go b/openstack/placement/v1/traits/results.go new file mode 100644 index 0000000000..085de1daf0 --- /dev/null +++ b/openstack/placement/v1/traits/results.go @@ -0,0 +1,50 @@ +package traits + +import ( + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/pagination" +) + +// GetResult is the response from a Get operation. Call its ExtractErr to +// determine if the request succeeded or failed. +type GetResult struct { + gophercloud.ErrResult +} + +// TraitsPage contains a single page of all traits from a List call. +type TraitsPage struct { + pagination.SinglePageBase +} + +// CreateResult is the response from a Create operation. Call its ExtractErr to +// determine if the request succeeded or failed. +type CreateResult struct { + gophercloud.ErrResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr to +// determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// IsEmpty satisfies the IsEmpty method of the Page interface. It returns true +// if a List contains no results. +func (r TraitsPage) IsEmpty() (bool, error) { + if r.StatusCode == 204 { + return true, nil + } + + traits, err := ExtractTraits(r) + return len(traits) == 0, err +} + +// ExtractTraits takes a List result and extracts the collection of traits +// returned by the API. +func ExtractTraits(p pagination.Page) ([]string, error) { + var s struct { + Traits []string `json:"traits"` + } + err := (p.(TraitsPage)).ExtractInto(&s) + return s.Traits, err +} diff --git a/openstack/placement/v1/traits/testing/doc.go b/openstack/placement/v1/traits/testing/doc.go new file mode 100644 index 0000000000..3231ffdf0e --- /dev/null +++ b/openstack/placement/v1/traits/testing/doc.go @@ -0,0 +1,2 @@ +// placement traits +package testing diff --git a/openstack/placement/v1/traits/testing/fixtures_test.go b/openstack/placement/v1/traits/testing/fixtures_test.go new file mode 100644 index 0000000000..3a8eb9446a --- /dev/null +++ b/openstack/placement/v1/traits/testing/fixtures_test.go @@ -0,0 +1,182 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/client" +) + +const PresentTrait = "CUSTOM_HW_FPGA_CLASS1" +const AbsentTrait = "NON_EXISTENT_TRAIT" +const CustomTraitToCreate = "CUSTOM_TRAIT_TO_CREATE" +const CustomTraitToDelete = CustomTraitToCreate +const StandardHardwareTrait = "HW_CPU_X86_AVX" + +const TraitsListResultAll = ` +{ + "traits": [ + "CUSTOM_HW_FPGA_CLASS1", + "CUSTOM_HW_FPGA_CLASS2", + "HW_CPU_X86_AVX" + ] +}` + +const TraitsListFilteredCustomResult = ` +{ + "traits": [ + "CUSTOM_HW_FPGA_CLASS1", + "CUSTOM_HW_FPGA_CLASS2" + ] +}` + +const TraitsListFilteredAssociatedResult = TraitsListResultAll + +var ExpectedTraitsListResultAll = []string{ + "CUSTOM_HW_FPGA_CLASS1", + "CUSTOM_HW_FPGA_CLASS2", + "HW_CPU_X86_AVX", +} + +var ExpectedTraitsListFilteredNameResult = []string{ + "CUSTOM_HW_FPGA_CLASS1", + "CUSTOM_HW_FPGA_CLASS2", +} + +var ExpectedTraitsListFilteredAssociatedResult = ExpectedTraitsListResultAll + +func HandleListTraitsAll(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/traits", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, TraitsListResultAll) + }) +} + +func HandleListTraitsFilteredName(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/traits", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + th.TestFormValues(t, r, map[string]string{"name": "startswith:CUSTOM"}) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, TraitsListFilteredCustomResult) + }) +} + +func HandleListTraitsFilteredAssociated(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/traits", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + th.TestFormValues(t, r, map[string]string{"associated": "true"}) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, TraitsListFilteredAssociatedResult) + }) +} + +func HandleGetTraitSuccess(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/traits/"+PresentTrait, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +func HandleGetTraitNotFound(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/traits/"+AbsentTrait, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNotFound) + }) +} + +func HandleCreateTraitSuccess(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/traits/"+CustomTraitToCreate, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusCreated) + }) +} + +func HandleCreateTraitThatAlreadyExists(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/traits/"+PresentTrait, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// Trait names created via the API must be prefixed with CUSTOM_. +func HandleCreateTraitInvalidName(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/traits/"+AbsentTrait, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusBadRequest) + }) +} + +func HandleDeleteTraitSuccess(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/traits/"+CustomTraitToDelete, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +func HandleDeleteTraitNotFound(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/traits/"+AbsentTrait, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNotFound) + }) +} + +func HandleDeleteStandardTraitFailure(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/traits/"+StandardHardwareTrait, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusBadRequest) + }) +} + +func HandleDeleteTraitInUseFailure(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/traits/"+PresentTrait, + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusConflict) + }) +} diff --git a/openstack/placement/v1/traits/testing/requests_test.go b/openstack/placement/v1/traits/testing/requests_test.go new file mode 100644 index 0000000000..93e76dbc43 --- /dev/null +++ b/openstack/placement/v1/traits/testing/requests_test.go @@ -0,0 +1,171 @@ +package testing + +import ( + "context" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack/placement/v1/traits" + + "github.com/gophercloud/gophercloud/v2/pagination" + th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/client" +) + +func TestListTraitsAll(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleListTraitsAll(t, fakeServer) + + count := 0 + err := traits.List(client.ServiceClient(fakeServer), traits.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + count++ + + actual, err := traits.ExtractTraits(page) + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedTraitsListResultAll, actual) + + return true, nil + }) + + th.AssertNoErr(t, err) + + th.AssertEquals(t, 1, count) +} + +func TestListTraitsFilteredName(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleListTraitsFilteredName(t, fakeServer) + + count := 0 + err := traits.List(client.ServiceClient(fakeServer), traits.ListOpts{Name: "startswith:CUSTOM"}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + count++ + + actual, err := traits.ExtractTraits(page) + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedTraitsListFilteredNameResult, actual) + + return true, nil + }) + + th.AssertNoErr(t, err) + + th.AssertEquals(t, 1, count) +} + +func TestListTraitsFilteredAssociated(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleListTraitsFilteredAssociated(t, fakeServer) + + count := 0 + associated := true + err := traits.List(client.ServiceClient(fakeServer), traits.ListOpts{Associated: &associated}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + count++ + + actual, err := traits.ExtractTraits(page) + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedTraitsListFilteredAssociatedResult, actual) + + return true, nil + }) + + th.AssertNoErr(t, err) + + th.AssertEquals(t, 1, count) +} + +func TestGetTraitSuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleGetTraitSuccess(t, fakeServer) + + err := traits.Get(context.TODO(), client.ServiceClient(fakeServer), PresentTrait).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestGetTraitNotFound(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleGetTraitNotFound(t, fakeServer) + + err := traits.Get(context.TODO(), client.ServiceClient(fakeServer), AbsentTrait).ExtractErr() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) +} + +func TestCreateTraitSuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleCreateTraitSuccess(t, fakeServer) + + err := traits.Create(context.TODO(), client.ServiceClient(fakeServer), CustomTraitToCreate).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestCreateTraitThatAlreadyExists(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleCreateTraitThatAlreadyExists(t, fakeServer) + + err := traits.Create(context.TODO(), client.ServiceClient(fakeServer), PresentTrait).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestCreateTraitInvalidName(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleCreateTraitInvalidName(t, fakeServer) + + err := traits.Create(context.TODO(), client.ServiceClient(fakeServer), AbsentTrait).ExtractErr() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusBadRequest)) +} + +func TestDeleteTraitSuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleDeleteTraitSuccess(t, fakeServer) + + err := traits.Delete(context.TODO(), client.ServiceClient(fakeServer), CustomTraitToDelete).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestDeleteTraitNotFound(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleDeleteTraitNotFound(t, fakeServer) + + err := traits.Delete(context.TODO(), client.ServiceClient(fakeServer), AbsentTrait).ExtractErr() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) +} + +func TestDeleteStandardTraitFailure(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleDeleteStandardTraitFailure(t, fakeServer) + + err := traits.Delete(context.TODO(), client.ServiceClient(fakeServer), StandardHardwareTrait).ExtractErr() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusBadRequest)) +} + +func TestDeleteTraitInUseFailure(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleDeleteTraitInUseFailure(t, fakeServer) + + err := traits.Delete(context.TODO(), client.ServiceClient(fakeServer), PresentTrait).ExtractErr() + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusConflict)) +} diff --git a/openstack/placement/v1/traits/urls.go b/openstack/placement/v1/traits/urls.go new file mode 100644 index 0000000000..2baba47c49 --- /dev/null +++ b/openstack/placement/v1/traits/urls.go @@ -0,0 +1,23 @@ +package traits + +import "github.com/gophercloud/gophercloud/v2" + +const ( + apiName = "traits" +) + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(apiName) +} + +func getURL(client *gophercloud.ServiceClient, traitName string) string { + return client.ServiceURL(apiName, traitName) +} + +func createURL(client *gophercloud.ServiceClient, traitName string) string { + return client.ServiceURL(apiName, traitName) +} + +func deleteURL(client *gophercloud.ServiceClient, traitName string) string { + return client.ServiceURL(apiName, traitName) +} diff --git a/openstack/placement/v1/usages/doc.go b/openstack/placement/v1/usages/doc.go new file mode 100644 index 0000000000..ea629113b4 --- /dev/null +++ b/openstack/placement/v1/usages/doc.go @@ -0,0 +1,49 @@ +/* +Package usages retrieves total resource usage from the OpenStack Placement service. + +Usage API requests are available starting from microversion 1.9. + +# Example to get total usages grouped by consumer type (microversion 1.38+) + + placementClient.Microversion = "1.38" + + totalUsages, err := usages.Get(context.TODO(), placementClient, usages.GetOpts{ + ProjectID: projectID, + }).Extract() + if err != nil { + panic(err) + } + + for consumerType, usage := range totalUsages.Usages { + fmt.Printf("%s: VCPU=%d, consumer_count=%d\n", + consumerType, usage["VCPU"], usage["consumer_count"]) + } + +# Example to get total usages without consumer type grouping (microversion 1.9–1.37) + + placementClient.Microversion = "1.9" + + totalUsages, err := usages.Get(context.TODO(), placementClient, usages.GetOpts{ + ProjectID: projectID, + }).ExtractPre138() + if err != nil { + panic(err) + } + + fmt.Printf("VCPU usage: %d\n", totalUsages.Usages["VCPU"]) + +# Example to get total usages for a specific project and user + + placementClient.Microversion = "1.38" + + totalUsages, err := usages.Get(context.TODO(), placementClient, usages.GetOpts{ + ProjectID: projectID, + UserID: userID, + }).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", totalUsages) +*/ +package usages diff --git a/openstack/placement/v1/usages/microversions.go b/openstack/placement/v1/usages/microversions.go new file mode 100644 index 0000000000..0b83cb06a6 --- /dev/null +++ b/openstack/placement/v1/usages/microversions.go @@ -0,0 +1,16 @@ +package usages + +// UsagesPre138 represents the total resource consumption for a project for +// microversions 1.9 through 1.37. In these versions, usages are not grouped +// by consumer type. +type UsagesPre138 struct { + // Usages maps resource class names to the total integer amount consumed. + Usages map[string]int `json:"usages"` +} + +// ExtractPre138 interprets a GetResult as UsagesPre138 (microversions 1.9–1.37). +func (r GetResult) ExtractPre138() (*UsagesPre138, error) { + var s UsagesPre138 + err := r.ExtractInto(&s) + return &s, err +} diff --git a/openstack/placement/v1/usages/requests.go b/openstack/placement/v1/usages/requests.go new file mode 100644 index 0000000000..0deedf8096 --- /dev/null +++ b/openstack/placement/v1/usages/requests.go @@ -0,0 +1,59 @@ +package usages + +import ( + "context" + "net/http" + + "github.com/gophercloud/gophercloud/v2" +) + +// GetOptsBuilder allows extensions to add additional parameters to the +// Get request. +type GetOptsBuilder interface { + ToUsagesGetQuery() (string, error) +} + +// GetOpts specifies the query parameters for retrieving total usages. +// +// This requires microversion 1.9 or later. +type GetOpts struct { + // ProjectID is required: only usages for this project are returned. + ProjectID string `q:"project_id"` + + // UserID is optional: when set, only usages for this user within the + // project are returned. + UserID string `q:"user_id,omitempty"` + + // ConsumerType is optional: when set, results are filtered to this consumer type. + // Available from microversion 1.38. + ConsumerType string `q:"consumer_type,omitempty"` +} + +// ToUsagesGetQuery formats a GetOpts into a query string. +func (opts GetOpts) ToUsagesGetQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return "", err + } + return q.String(), nil +} + +// Get retrieves the total resource usages for a project (and optionally a user). +// +// Requires microversion 1.9 or later. +func Get(ctx context.Context, client *gophercloud.ServiceClient, opts GetOptsBuilder) (r GetResult) { + url := getURL(client) + if opts != nil { + query, err := opts.ToUsagesGetQuery() + if err != nil { + r.Err = err + return + } + url += query + } + resp, err := client.Get(ctx, url, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{http.StatusOK}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/placement/v1/usages/results.go b/openstack/placement/v1/usages/results.go new file mode 100644 index 0000000000..ad19fee31b --- /dev/null +++ b/openstack/placement/v1/usages/results.go @@ -0,0 +1,33 @@ +package usages + +import "github.com/gophercloud/gophercloud/v2" + +// ConsumerTypeUsage maps resource class names to the total integer amount +// consumed by consumers of a given type. The special key "consumer_count" +// holds the number of consumers of this type. +type ConsumerTypeUsage map[string]int + +// Usages represents the total resource consumption for a project, grouped by +// consumer type. This is the response shape for microversion 1.38 and later. +// +// Each key in the Usages map is a consumer type name (e.g. "INSTANCE"). +// The value is a ConsumerTypeUsage containing resource class totals and a +// consumer_count entry. +type Usages struct { + // Usages maps consumer type names to their aggregated resource usage. + Usages map[string]ConsumerTypeUsage `json:"usages"` +} + +// GetResult is the result of a Get operation. Call its Extract method +// to interpret it as Usages (microversion 1.38+), or ExtractPre138 for +// earlier microversions. +type GetResult struct { + gophercloud.Result +} + +// Extract interprets a GetResult as Usages (microversion 1.38+). +func (r GetResult) Extract() (*Usages, error) { + var s Usages + err := r.ExtractInto(&s) + return &s, err +} diff --git a/openstack/placement/v1/usages/testing/doc.go b/openstack/placement/v1/usages/testing/doc.go new file mode 100644 index 0000000000..ed99c1eafa --- /dev/null +++ b/openstack/placement/v1/usages/testing/doc.go @@ -0,0 +1,2 @@ +// usages unit tests. +package testing diff --git a/openstack/placement/v1/usages/testing/fixtures_test.go b/openstack/placement/v1/usages/testing/fixtures_test.go new file mode 100644 index 0000000000..a1f9de05d4 --- /dev/null +++ b/openstack/placement/v1/usages/testing/fixtures_test.go @@ -0,0 +1,172 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/placement/v1/usages" + th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/client" +) + +const ProjectID = "42a2b0fa980d4f7f873e8f0d8b4e1b0e" +const UserID = "b29d880b2c114d5d9a55748b26b2e41e" + +var GetUsagesBody = ` +{ + "usages": { + "INSTANCE": { + "VCPU": 2, + "MEMORY_MB": 2048, + "consumer_count": 1 + } + } +} +` + +var ExpectedUsages = usages.Usages{ + Usages: map[string]usages.ConsumerTypeUsage{ + "INSTANCE": { + "VCPU": 2, + "MEMORY_MB": 2048, + "consumer_count": 1, + }, + }, +} + +var GetUsagesWithUserBody = ` +{ + "usages": { + "INSTANCE": { + "VCPU": 2, + "MEMORY_MB": 2048, + "consumer_count": 1 + } + } +} +` + +var GetEmptyUsagesBody = ` +{ + "usages": {} +} +` + +var ExpectedEmptyUsages = usages.Usages{ + Usages: map[string]usages.ConsumerTypeUsage{}, +} + +var GetUsagesPre138Body = ` +{ + "usages": { + "VCPU": 2, + "MEMORY_MB": 2048 + } +} +` + +var ExpectedUsagesPre138 = usages.UsagesPre138{ + Usages: map[string]int{ + "VCPU": 2, + "MEMORY_MB": 2048, + }, +} + +var GetEmptyUsagesPre138Body = ` +{ + "usages": {} +} +` + +var ExpectedEmptyUsagesPre138 = usages.UsagesPre138{ + Usages: map[string]int{}, +} + +func HandleGetUsagesSuccess(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/usages", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + th.AssertEquals(t, ProjectID, r.URL.Query().Get("project_id")) + th.AssertEquals(t, "", r.URL.Query().Get("user_id")) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, GetUsagesBody) + }) +} + +func HandleGetUsagesWithUserSuccess(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/usages", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + th.AssertEquals(t, ProjectID, r.URL.Query().Get("project_id")) + th.AssertEquals(t, UserID, r.URL.Query().Get("user_id")) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, GetUsagesWithUserBody) + }) +} + +func HandleGetEmptyUsagesSuccess(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/usages", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + th.AssertEquals(t, ProjectID, r.URL.Query().Get("project_id")) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, GetEmptyUsagesBody) + }) +} + +func HandleGetUsagesPre138Success(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/usages", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + th.AssertEquals(t, ProjectID, r.URL.Query().Get("project_id")) + th.AssertEquals(t, "", r.URL.Query().Get("user_id")) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, GetUsagesPre138Body) + }) +} + +func HandleGetUsagesPre138WithUserSuccess(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/usages", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + th.AssertEquals(t, ProjectID, r.URL.Query().Get("project_id")) + th.AssertEquals(t, UserID, r.URL.Query().Get("user_id")) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, GetUsagesPre138Body) + }) +} + +func HandleGetEmptyUsagesPre138Success(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/usages", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + th.AssertEquals(t, ProjectID, r.URL.Query().Get("project_id")) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprint(w, GetEmptyUsagesPre138Body) + }) +} diff --git a/openstack/placement/v1/usages/testing/requests_test.go b/openstack/placement/v1/usages/testing/requests_test.go new file mode 100644 index 0000000000..022a1b9999 --- /dev/null +++ b/openstack/placement/v1/usages/testing/requests_test.go @@ -0,0 +1,90 @@ +package testing + +import ( + "context" + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/placement/v1/usages" + th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/client" +) + +func TestGetSuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleGetUsagesSuccess(t, fakeServer) + + actual, err := usages.Get(context.TODO(), client.ServiceClient(fakeServer), usages.GetOpts{ + ProjectID: ProjectID, + }).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedUsages, *actual) +} + +func TestGetWithUserSuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleGetUsagesWithUserSuccess(t, fakeServer) + + actual, err := usages.Get(context.TODO(), client.ServiceClient(fakeServer), usages.GetOpts{ + ProjectID: ProjectID, + UserID: UserID, + }).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedUsages, *actual) +} + +func TestGetEmptySuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleGetEmptyUsagesSuccess(t, fakeServer) + + actual, err := usages.Get(context.TODO(), client.ServiceClient(fakeServer), usages.GetOpts{ + ProjectID: ProjectID, + }).Extract() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedEmptyUsages, *actual) +} + +func TestGetPre138Success(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleGetUsagesPre138Success(t, fakeServer) + + actual, err := usages.Get(context.TODO(), client.ServiceClient(fakeServer), usages.GetOpts{ + ProjectID: ProjectID, + }).ExtractPre138() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedUsagesPre138, *actual) +} + +func TestGetPre138WithUserSuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleGetUsagesPre138WithUserSuccess(t, fakeServer) + + actual, err := usages.Get(context.TODO(), client.ServiceClient(fakeServer), usages.GetOpts{ + ProjectID: ProjectID, + UserID: UserID, + }).ExtractPre138() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedUsagesPre138, *actual) +} + +func TestGetPre138EmptySuccess(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + HandleGetEmptyUsagesPre138Success(t, fakeServer) + + actual, err := usages.Get(context.TODO(), client.ServiceClient(fakeServer), usages.GetOpts{ + ProjectID: ProjectID, + }).ExtractPre138() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, ExpectedEmptyUsagesPre138, *actual) +} diff --git a/openstack/placement/v1/usages/urls.go b/openstack/placement/v1/usages/urls.go new file mode 100644 index 0000000000..5de8523797 --- /dev/null +++ b/openstack/placement/v1/usages/urls.go @@ -0,0 +1,7 @@ +package usages + +import "github.com/gophercloud/gophercloud/v2" + +func getURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("usages") +} diff --git a/openstack/sharedfilesystems/apiversions/testing/fixtures_test.go b/openstack/sharedfilesystems/apiversions/testing/fixtures_test.go index 949434480f..ed60efd6f3 100644 --- a/openstack/sharedfilesystems/apiversions/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/apiversions/testing/fixtures_test.go @@ -178,50 +178,50 @@ var ManilaAllAPIVersionResults = []apiversions.APIVersion{ ManilaAPIVersion2Result, } -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { +func MockListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ManilaAllAPIVersionsResponse) + fmt.Fprint(w, ManilaAllAPIVersionsResponse) }) } -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc("/v2/", func(w http.ResponseWriter, r *http.Request) { +func MockGetResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ManilaAPIVersionResponse) + fmt.Fprint(w, ManilaAPIVersionResponse) }) } -func MockGetNoResponse(t *testing.T) { - th.Mux.HandleFunc("/v2/", func(w http.ResponseWriter, r *http.Request) { +func MockGetNoResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ManilaAPIInvalidVersionResponse_1) + fmt.Fprint(w, ManilaAPIInvalidVersionResponse_1) }) } -func MockGetMultipleResponses(t *testing.T) { - th.Mux.HandleFunc("/v2/", func(w http.ResponseWriter, r *http.Request) { +func MockGetMultipleResponses(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/v2/", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ManilaAPIInvalidVersionResponse_2) + fmt.Fprint(w, ManilaAPIInvalidVersionResponse_2) }) } diff --git a/openstack/sharedfilesystems/apiversions/testing/requests_test.go b/openstack/sharedfilesystems/apiversions/testing/requests_test.go index 1e09f7daac..e2ea355901 100644 --- a/openstack/sharedfilesystems/apiversions/testing/requests_test.go +++ b/openstack/sharedfilesystems/apiversions/testing/requests_test.go @@ -10,12 +10,12 @@ import ( ) func TestListAPIVersions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) - allVersions, err := apiversions.List(client.ServiceClient()).AllPages(context.TODO()) + allVersions, err := apiversions.List(client.ServiceClient(fakeServer)).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := apiversions.ExtractAPIVersions(allVersions) @@ -25,33 +25,33 @@ func TestListAPIVersions(t *testing.T) { } func TestGetAPIVersion(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetResponse(t) + MockGetResponse(t, fakeServer) - actual, err := apiversions.Get(context.TODO(), client.ServiceClient(), "v2").Extract() + actual, err := apiversions.Get(context.TODO(), client.ServiceClient(fakeServer), "v2").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, ManilaAPIVersion2Result, *actual) } func TestGetNoAPIVersion(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetNoResponse(t) + MockGetNoResponse(t, fakeServer) - _, err := apiversions.Get(context.TODO(), client.ServiceClient(), "v2").Extract() - th.AssertEquals(t, err.Error(), "Unable to find requested API version") + _, err := apiversions.Get(context.TODO(), client.ServiceClient(fakeServer), "v2").Extract() + th.AssertEquals(t, "Unable to find requested API version", err.Error()) } func TestGetMultipleAPIVersion(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetMultipleResponses(t) + MockGetMultipleResponses(t, fakeServer) - _, err := apiversions.Get(context.TODO(), client.ServiceClient(), "v2").Extract() - th.AssertEquals(t, err.Error(), "Found 2 API versions") + _, err := apiversions.Get(context.TODO(), client.ServiceClient(fakeServer), "v2").Extract() + th.AssertEquals(t, "Found 2 API versions", err.Error()) } diff --git a/openstack/sharedfilesystems/v2/availabilityzones/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/availabilityzones/testing/fixtures_test.go index d6cd25830f..5523f8a025 100644 --- a/openstack/sharedfilesystems/v2/availabilityzones/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/availabilityzones/testing/fixtures_test.go @@ -6,18 +6,18 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/os-availability-zone", func(w http.ResponseWriter, r *http.Request) { +func MockListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/os-availability-zone", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "availability_zones": [ { diff --git a/openstack/sharedfilesystems/v2/availabilityzones/testing/requests_test.go b/openstack/sharedfilesystems/v2/availabilityzones/testing/requests_test.go index 8d69781c3d..e487b21e28 100644 --- a/openstack/sharedfilesystems/v2/availabilityzones/testing/requests_test.go +++ b/openstack/sharedfilesystems/v2/availabilityzones/testing/requests_test.go @@ -12,12 +12,12 @@ import ( // Verifies that availability zones can be listed correctly func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) - allPages, err := availabilityzones.List(client.ServiceClient()).AllPages(context.TODO()) + allPages, err := availabilityzones.List(client.ServiceClient(fakeServer)).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := availabilityzones.ExtractAvailabilityZones(allPages) th.AssertNoErr(t, err) diff --git a/openstack/sharedfilesystems/v2/errors/errors.go b/openstack/sharedfilesystems/v2/errors/errors.go index f01d6bd588..34324b08ba 100644 --- a/openstack/sharedfilesystems/v2/errors/errors.go +++ b/openstack/sharedfilesystems/v2/errors/errors.go @@ -21,6 +21,6 @@ func ExtractErrorInto(rawError error, errorDetails *ErrorDetails) (err error) { if errors.As(rawError, &codeError) { return json.Unmarshal(codeError.Body, errorDetails) } else { - return errors.New("Unable to extract detailed error message") + return errors.New("unable to extract detailed error message") } } diff --git a/openstack/sharedfilesystems/v2/errors/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/errors/testing/fixtures_test.go index 5a6f7f013c..5d70a12829 100644 --- a/openstack/sharedfilesystems/v2/errors/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/errors/testing/fixtures_test.go @@ -6,7 +6,7 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) const shareEndpoint = "/shares" @@ -28,15 +28,15 @@ var createResponse = `{ }` // MockCreateResponse creates a mock response -func MockCreateResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint, func(w http.ResponseWriter, r *http.Request) { +func MockCreateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, createRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusNotFound) - fmt.Fprintf(w, createResponse) + fmt.Fprint(w, createResponse) }) } diff --git a/openstack/sharedfilesystems/v2/errors/testing/request_test.go b/openstack/sharedfilesystems/v2/errors/testing/request_test.go index f405721d7f..035ff50a54 100644 --- a/openstack/sharedfilesystems/v2/errors/testing/request_test.go +++ b/openstack/sharedfilesystems/v2/errors/testing/request_test.go @@ -11,13 +11,13 @@ import ( ) func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockCreateResponse(t) + MockCreateResponse(t, fakeServer) options := &shares.CreateOpts{Size: 1, Name: "my_test_share", ShareProto: "NFS", SnapshotID: "70bfbebc-d3ff-4528-8bbb-58422daa280b"} - _, err := shares.Create(context.TODO(), client.ServiceClient(), options).Extract() + _, err := shares.Create(context.TODO(), client.ServiceClient(fakeServer), options).Extract() if err == nil { t.Fatal("Expected error") @@ -28,8 +28,8 @@ func TestCreate(t *testing.T) { th.AssertNoErr(t, e) for k, msg := range detailedErr { - th.AssertEquals(t, k, "itemNotFound") - th.AssertEquals(t, msg.Code, 404) - th.AssertEquals(t, msg.Message, "ShareSnapshotNotFound: Snapshot 70bfbebc-d3ff-4528-8bbb-58422daa280b could not be found.") + th.AssertEquals(t, "itemNotFound", k) + th.AssertEquals(t, 404, msg.Code) + th.AssertEquals(t, "ShareSnapshotNotFound: Snapshot 70bfbebc-d3ff-4528-8bbb-58422daa280b could not be found.", msg.Message) } } diff --git a/openstack/sharedfilesystems/v2/messages/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/messages/testing/fixtures_test.go index 111b6dbf97..8b1f1fa024 100644 --- a/openstack/sharedfilesystems/v2/messages/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/messages/testing/fixtures_test.go @@ -6,26 +6,26 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) -func MockDeleteResponse(t *testing.T) { - th.Mux.HandleFunc("/messages/messageID", func(w http.ResponseWriter, r *http.Request) { +func MockDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/messages/messageID", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) }) } -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/messages", func(w http.ResponseWriter, r *http.Request) { +func MockListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/messages", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "messages": [ { @@ -59,15 +59,15 @@ func MockListResponse(t *testing.T) { }) } -func MockFilteredListResponse(t *testing.T) { - th.Mux.HandleFunc("/messages", func(w http.ResponseWriter, r *http.Request) { +func MockFilteredListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/messages", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "messages": [ { @@ -88,14 +88,14 @@ func MockFilteredListResponse(t *testing.T) { }) } -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc("/messages/2076373e-13a7-4b84-9e67-15ce8cceaff8", func(w http.ResponseWriter, r *http.Request) { +func MockGetResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/messages/2076373e-13a7-4b84-9e67-15ce8cceaff8", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "message": { "resource_id": "4336d74f-3bdc-4f27-9657-c01ec63680bf", diff --git a/openstack/sharedfilesystems/v2/messages/testing/requests_test.go b/openstack/sharedfilesystems/v2/messages/testing/requests_test.go index 75b0b57dc3..1206d090c3 100644 --- a/openstack/sharedfilesystems/v2/messages/testing/requests_test.go +++ b/openstack/sharedfilesystems/v2/messages/testing/requests_test.go @@ -12,23 +12,23 @@ import ( // Verifies that message deletion works func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDeleteResponse(t) + MockDeleteResponse(t, fakeServer) - res := messages.Delete(context.TODO(), client.ServiceClient(), "messageID") + res := messages.Delete(context.TODO(), client.ServiceClient(fakeServer), "messageID") th.AssertNoErr(t, res.Err) } // Verifies that messages can be listed correctly func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) - allPages, err := messages.List(client.ServiceClient(), &messages.ListOpts{}).AllPages(context.TODO()) + allPages, err := messages.List(client.ServiceClient(fakeServer), &messages.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := messages.ExtractMessages(allPages) th.AssertNoErr(t, err) @@ -66,16 +66,16 @@ func TestList(t *testing.T) { // Verifies that messages list can be called with query parameters func TestFilteredList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockFilteredListResponse(t) + MockFilteredListResponse(t, fakeServer) options := &messages.ListOpts{ RequestID: "req-21767eee-22ca-40a4-b6c0-ae7d35cd434f", } - allPages, err := messages.List(client.ServiceClient(), options).AllPages(context.TODO()) + allPages, err := messages.List(client.ServiceClient(fakeServer), options).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := messages.ExtractMessages(allPages) th.AssertNoErr(t, err) @@ -100,10 +100,10 @@ func TestFilteredList(t *testing.T) { // Verifies that it is possible to get a message func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetResponse(t) + MockGetResponse(t, fakeServer) expected := messages.Message{ ResourceID: "4336d74f-3bdc-4f27-9657-c01ec63680bf", @@ -119,7 +119,7 @@ func TestGet(t *testing.T) { ActionID: "002", } - n, err := messages.Get(context.TODO(), client.ServiceClient(), "2076373e-13a7-4b84-9e67-15ce8cceaff8").Extract() + n, err := messages.Get(context.TODO(), client.ServiceClient(fakeServer), "2076373e-13a7-4b84-9e67-15ce8cceaff8").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &expected, n) diff --git a/openstack/sharedfilesystems/v2/replicas/requests.go b/openstack/sharedfilesystems/v2/replicas/requests.go index 415e67b37d..6e29fd783c 100644 --- a/openstack/sharedfilesystems/v2/replicas/requests.go +++ b/openstack/sharedfilesystems/v2/replicas/requests.go @@ -90,7 +90,7 @@ func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pa return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { p := ReplicaPage{pagination.MarkerPageBase{PageResult: r}} - p.MarkerPageBase.Owner = p + p.Owner = p return p }) } @@ -108,7 +108,7 @@ func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) paginat return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { p := ReplicaPage{pagination.MarkerPageBase{PageResult: r}} - p.MarkerPageBase.Owner = p + p.Owner = p return p }) } diff --git a/openstack/sharedfilesystems/v2/replicas/results.go b/openstack/sharedfilesystems/v2/replicas/results.go index 7b75117747..62ab2c72c2 100644 --- a/openstack/sharedfilesystems/v2/replicas/results.go +++ b/openstack/sharedfilesystems/v2/replicas/results.go @@ -83,7 +83,7 @@ type ReplicaPage struct { } // NextPageURL generates the URL for the page of results after this one. -func (r ReplicaPage) NextPageURL() (string, error) { +func (r ReplicaPage) NextPageURL(endpointURL string) (string, error) { currentURL := r.URL mark, err := r.Owner.LastMarker() if err != nil { @@ -109,7 +109,7 @@ func (r ReplicaPage) LastMarker() (string, error) { return invalidMarker, nil } - u, err := url.Parse(r.URL.String()) + u, err := url.Parse(r.String()) if err != nil { return invalidMarker, err } @@ -160,7 +160,7 @@ func ExtractReplicas(r pagination.Page) ([]Replica, error) { // ExtractReplicasInto similar to ExtractReplicas but operates on a `list` of // replicas. func ExtractReplicasInto(r pagination.Page, v any) error { - return r.(ReplicaPage).Result.ExtractIntoSlicePtr(v, "share_replicas") + return r.(ReplicaPage).ExtractIntoSlicePtr(v, "share_replicas") } // DeleteResult contains the response body and error from a Delete request. diff --git a/openstack/sharedfilesystems/v2/replicas/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/replicas/testing/fixtures_test.go index 00585c9c26..5e14cb28d4 100644 --- a/openstack/sharedfilesystems/v2/replicas/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/replicas/testing/fixtures_test.go @@ -6,7 +6,7 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) const ( @@ -38,25 +38,25 @@ var createResponse = `{ ` // MockCreateResponse creates a mock response -func MockCreateResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint, func(w http.ResponseWriter, r *http.Request) { +func MockCreateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-OpenStack-Manila-API-Version", "2.11") th.TestJSONRequest(t, r, createRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, createResponse) + fmt.Fprint(w, createResponse) }) } // MockDeleteResponse creates a mock delete response -func MockDeleteResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+replicaID, func(w http.ResponseWriter, r *http.Request) { +func MockDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+replicaID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "X-OpenStack-Manila-API-Version", "2.11") w.WriteHeader(http.StatusAccepted) }) @@ -69,10 +69,10 @@ var promoteRequest = `{ } ` -func MockPromoteResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+replicaID+"/action", func(w http.ResponseWriter, r *http.Request) { +func MockPromoteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+replicaID+"/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-OpenStack-Manila-API-Version", "2.11") @@ -86,10 +86,10 @@ var resyncRequest = `{ } ` -func MockResyncResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+replicaID+"/action", func(w http.ResponseWriter, r *http.Request) { +func MockResyncResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+replicaID+"/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-OpenStack-Manila-API-Version", "2.11") @@ -105,10 +105,10 @@ var resetStatusRequest = `{ } ` -func MockResetStatusResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+replicaID+"/action", func(w http.ResponseWriter, r *http.Request) { +func MockResetStatusResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+replicaID+"/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-OpenStack-Manila-API-Version", "2.11") @@ -124,10 +124,10 @@ var resetStateRequest = `{ } ` -func MockResetStateResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+replicaID+"/action", func(w http.ResponseWriter, r *http.Request) { +func MockResetStateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+replicaID+"/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-OpenStack-Manila-API-Version", "2.11") @@ -141,10 +141,10 @@ var deleteRequest = `{ } ` -func MockForceDeleteResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+replicaID+"/action", func(w http.ResponseWriter, r *http.Request) { +func MockForceDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+replicaID+"/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestHeader(t, r, "X-OpenStack-Manila-API-Version", "2.11") @@ -169,13 +169,13 @@ var getResponse = `{ ` // MockGetResponse creates a mock get response -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+replicaID, func(w http.ResponseWriter, r *http.Request) { +func MockGetResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+replicaID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "X-OpenStack-Manila-API-Version", "2.11") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, getResponse) + fmt.Fprint(w, getResponse) }) } @@ -206,10 +206,10 @@ var listResponse = `{ var listEmptyResponse = `{"share_replicas": []}` // MockListResponse creates a mock detailed-list response -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint, func(w http.ResponseWriter, r *http.Request) { +func MockListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "X-OpenStack-Manila-API-Version", "2.11") w.Header().Add("Content-Type", "application/json") @@ -275,10 +275,10 @@ var listDetailResponse = `{ var listDetailEmptyResponse = `{"share_replicas": []}` // MockListDetailResponse creates a mock detailed-list response -func MockListDetailResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/detail", func(w http.ResponseWriter, r *http.Request) { +func MockListDetailResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "X-OpenStack-Manila-API-Version", "2.11") w.Header().Add("Content-Type", "application/json") @@ -323,14 +323,14 @@ var listExportLocationsResponse = `{ ` // MockListExportLocationsResponse creates a mock get export locations response -func MockListExportLocationsResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+replicaID+"/export-locations", func(w http.ResponseWriter, r *http.Request) { +func MockListExportLocationsResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+replicaID+"/export-locations", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") th.TestHeader(t, r, "X-OpenStack-Manila-API-Version", "2.47") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, listExportLocationsResponse) + fmt.Fprint(w, listExportLocationsResponse) }) } @@ -348,13 +348,13 @@ var getExportLocationResponse = `{ ` // MockGetExportLocationResponse creates a mock get export location response -func MockGetExportLocationResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+replicaID+"/export-locations/ae73e762-e8b9-4aad-aad3-23afb7cd6825", func(w http.ResponseWriter, r *http.Request) { +func MockGetExportLocationResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+replicaID+"/export-locations/ae73e762-e8b9-4aad-aad3-23afb7cd6825", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") th.TestHeader(t, r, "X-OpenStack-Manila-API-Version", "2.47") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, getExportLocationResponse) + fmt.Fprint(w, getExportLocationResponse) }) } diff --git a/openstack/sharedfilesystems/v2/replicas/testing/request_test.go b/openstack/sharedfilesystems/v2/replicas/testing/request_test.go index 167bc2c879..64c964935e 100644 --- a/openstack/sharedfilesystems/v2/replicas/testing/request_test.go +++ b/openstack/sharedfilesystems/v2/replicas/testing/request_test.go @@ -11,24 +11,24 @@ import ( "github.com/gophercloud/gophercloud/v2/testhelper/client" ) -func getClient(microVersion string) *gophercloud.ServiceClient { - c := client.ServiceClient() +func getClient(fakeServer th.FakeServer, microVersion string) *gophercloud.ServiceClient { + c := client.ServiceClient(fakeServer) c.Type = "sharev2" c.Microversion = microVersion return c } func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockCreateResponse(t) + MockCreateResponse(t, fakeServer) options := &replicas.CreateOpts{ ShareID: "65a34695-f9e5-4eea-b48d-a0b261d82943", AvailabilityZone: "zone-1", } - actual, err := replicas.Create(context.TODO(), getClient("2.11"), options).Extract() + actual, err := replicas.Create(context.TODO(), getClient(fakeServer, "2.11"), options).Extract() expected := &replicas.Replica{ ID: "3b9c33e8-b136-45c6-84a6-019c8db1d550", @@ -44,32 +44,32 @@ func TestCreate(t *testing.T) { } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDeleteResponse(t) + MockDeleteResponse(t, fakeServer) - result := replicas.Delete(context.TODO(), getClient("2.11"), replicaID) + result := replicas.Delete(context.TODO(), getClient(fakeServer, "2.11"), replicaID) th.AssertNoErr(t, result.Err) } func TestForceDeleteSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockForceDeleteResponse(t) + MockForceDeleteResponse(t, fakeServer) - err := replicas.ForceDelete(context.TODO(), getClient("2.11"), replicaID).ExtractErr() + err := replicas.ForceDelete(context.TODO(), getClient(fakeServer, "2.11"), replicaID).ExtractErr() th.AssertNoErr(t, err) } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetResponse(t) + MockGetResponse(t, fakeServer) - actual, err := replicas.Get(context.TODO(), getClient("2.11"), replicaID).Extract() + actual, err := replicas.Get(context.TODO(), getClient(fakeServer, "2.11"), replicaID).Extract() expected := &replicas.Replica{ AvailabilityZone: "zone-1", @@ -88,15 +88,15 @@ func TestGet(t *testing.T) { } func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) listOpts := &replicas.ListOpts{ ShareID: "65a34695-f9e5-4eea-b48d-a0b261d82943", } - allPages, err := replicas.List(getClient("2.11"), listOpts).AllPages(context.TODO()) + allPages, err := replicas.List(getClient(fakeServer, "2.11"), listOpts).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := replicas.ExtractReplicas(allPages) @@ -127,15 +127,15 @@ func TestList(t *testing.T) { } func TestListDetail(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListDetailResponse(t) + MockListDetailResponse(t, fakeServer) listOpts := &replicas.ListOpts{ ShareID: "65a34695-f9e5-4eea-b48d-a0b261d82943", } - allPages, err := replicas.ListDetail(getClient("2.11"), listOpts).AllPages(context.TODO()) + allPages, err := replicas.ListDetail(getClient(fakeServer, "2.11"), listOpts).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := replicas.ExtractReplicas(allPages) @@ -181,12 +181,12 @@ func TestListDetail(t *testing.T) { } func TestListExportLocationsSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListExportLocationsResponse(t) + MockListExportLocationsResponse(t, fakeServer) - actual, err := replicas.ListExportLocations(context.TODO(), getClient("2.47"), replicaID).Extract() + actual, err := replicas.ListExportLocations(context.TODO(), getClient(fakeServer, "2.47"), replicaID).Extract() expected := []replicas.ExportLocation{ { @@ -210,15 +210,15 @@ func TestListExportLocationsSuccess(t *testing.T) { } func TestGetExportLocationSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetExportLocationResponse(t) + MockGetExportLocationResponse(t, fakeServer) - s, err := replicas.GetExportLocation(context.TODO(), getClient("2.47"), replicaID, "ae73e762-e8b9-4aad-aad3-23afb7cd6825").Extract() + s, err := replicas.GetExportLocation(context.TODO(), getClient(fakeServer, "2.47"), replicaID, "ae73e762-e8b9-4aad-aad3-23afb7cd6825").Extract() th.AssertNoErr(t, err) - th.AssertDeepEquals(t, s, &replicas.ExportLocation{ + th.AssertDeepEquals(t, &replicas.ExportLocation{ Path: "192.168.1.124:/var/lib/manila/mnt/share-3b9c33e8-b136-45c6-84a6-019c8db1d550", ID: "ae73e762-e8b9-4aad-aad3-23afb7cd6825", Preferred: false, @@ -226,45 +226,45 @@ func TestGetExportLocationSuccess(t *testing.T) { AvailabilityZone: "zone-1", CreatedAt: time.Date(2023, time.May, 26, 12, 44, 33, 987960000, time.UTC), UpdatedAt: time.Date(2023, time.May, 26, 12, 44, 33, 958363000, time.UTC), - }) + }, s) } func TestResetStatusSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockResetStatusResponse(t) + MockResetStatusResponse(t, fakeServer) - err := replicas.ResetStatus(context.TODO(), getClient("2.11"), replicaID, &replicas.ResetStatusOpts{Status: "available"}).ExtractErr() + err := replicas.ResetStatus(context.TODO(), getClient(fakeServer, "2.11"), replicaID, &replicas.ResetStatusOpts{Status: "available"}).ExtractErr() th.AssertNoErr(t, err) } func TestResetStateSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockResetStateResponse(t) + MockResetStateResponse(t, fakeServer) - err := replicas.ResetState(context.TODO(), getClient("2.11"), replicaID, &replicas.ResetStateOpts{State: "active"}).ExtractErr() + err := replicas.ResetState(context.TODO(), getClient(fakeServer, "2.11"), replicaID, &replicas.ResetStateOpts{State: "active"}).ExtractErr() th.AssertNoErr(t, err) } func TestResyncSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockResyncResponse(t) + MockResyncResponse(t, fakeServer) - err := replicas.Resync(context.TODO(), getClient("2.11"), replicaID).ExtractErr() + err := replicas.Resync(context.TODO(), getClient(fakeServer, "2.11"), replicaID).ExtractErr() th.AssertNoErr(t, err) } func TestPromoteSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockPromoteResponse(t) + MockPromoteResponse(t, fakeServer) - err := replicas.Promote(context.TODO(), getClient("2.11"), replicaID, &replicas.PromoteOpts{QuiesceWaitTime: 30}).ExtractErr() + err := replicas.Promote(context.TODO(), getClient(fakeServer, "2.11"), replicaID, &replicas.PromoteOpts{QuiesceWaitTime: 30}).ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/sharedfilesystems/v2/schedulerstats/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/schedulerstats/testing/fixtures_test.go index acdd04d53f..ed4b325da4 100644 --- a/openstack/sharedfilesystems/v2/schedulerstats/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/schedulerstats/testing/fixtures_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/schedulerstats" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" "github.com/gophercloud/gophercloud/v2/testhelper/client" ) @@ -264,28 +264,28 @@ var ( } ) -func HandlePoolsListSuccessfully(t *testing.T) { - testhelper.Mux.HandleFunc("/scheduler-stats/pools", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "GET") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) +func HandlePoolsListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/scheduler-stats/pools", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") if err := r.ParseForm(); err != nil { t.Errorf("Failed to parse request form %v", err) } - fmt.Fprintf(w, PoolsListBody) + fmt.Fprint(w, PoolsListBody) }) - testhelper.Mux.HandleFunc("/scheduler-stats/pools/detail", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "GET") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) + fakeServer.Mux.HandleFunc("/scheduler-stats/pools/detail", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") if err := r.ParseForm(); err != nil { t.Errorf("Failed to parse request form %v", err) } - fmt.Fprintf(w, PoolsListBodyDetail) + fmt.Fprint(w, PoolsListBodyDetail) }) } diff --git a/openstack/sharedfilesystems/v2/schedulerstats/testing/requests_test.go b/openstack/sharedfilesystems/v2/schedulerstats/testing/requests_test.go index 5c2ff36ad9..057f4458ab 100644 --- a/openstack/sharedfilesystems/v2/schedulerstats/testing/requests_test.go +++ b/openstack/sharedfilesystems/v2/schedulerstats/testing/requests_test.go @@ -6,58 +6,58 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/schedulerstats" "github.com/gophercloud/gophercloud/v2/pagination" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestListPoolsDetail(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - HandlePoolsListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandlePoolsListSuccessfully(t, fakeServer) pages := 0 - err := schedulerstats.List(client.ServiceClient(), schedulerstats.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := schedulerstats.List(client.ServiceClient(fakeServer), schedulerstats.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := schedulerstats.ExtractPools(page) - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) if len(actual) != 4 { t.Fatalf("Expected 4 backends, got %d", len(actual)) } - testhelper.CheckDeepEquals(t, PoolFake1, actual[0]) - testhelper.CheckDeepEquals(t, PoolFake2, actual[1]) - testhelper.CheckDeepEquals(t, PoolFake3, actual[2]) - testhelper.CheckDeepEquals(t, PoolFake4, actual[3]) + th.CheckDeepEquals(t, PoolFake1, actual[0]) + th.CheckDeepEquals(t, PoolFake2, actual[1]) + th.CheckDeepEquals(t, PoolFake3, actual[2]) + th.CheckDeepEquals(t, PoolFake4, actual[3]) return true, nil }) - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) if pages != 1 { t.Errorf("Expected 1 page, saw %d", pages) } pages = 0 - err = schedulerstats.ListDetail(client.ServiceClient(), schedulerstats.ListDetailOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err = schedulerstats.ListDetail(client.ServiceClient(fakeServer), schedulerstats.ListDetailOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := schedulerstats.ExtractPools(page) - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) if len(actual) != 4 { t.Fatalf("Expected 4 backends, got %d", len(actual)) } - testhelper.CheckDeepEquals(t, PoolDetailFake1, actual[0]) - testhelper.CheckDeepEquals(t, PoolDetailFake2, actual[1]) - testhelper.CheckDeepEquals(t, PoolDetailFake3, actual[2]) - testhelper.CheckDeepEquals(t, PoolDetailFake4, actual[3]) + th.CheckDeepEquals(t, PoolDetailFake1, actual[0]) + th.CheckDeepEquals(t, PoolDetailFake2, actual[1]) + th.CheckDeepEquals(t, PoolDetailFake3, actual[2]) + th.CheckDeepEquals(t, PoolDetailFake4, actual[3]) return true, nil }) - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) if pages != 1 { t.Errorf("Expected 1 page, saw %d", pages) diff --git a/openstack/sharedfilesystems/v2/securityservices/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/securityservices/testing/fixtures_test.go index c0b7645770..1cd713922a 100644 --- a/openstack/sharedfilesystems/v2/securityservices/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/securityservices/testing/fixtures_test.go @@ -6,13 +6,13 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) -func MockCreateResponse(t *testing.T) { - th.Mux.HandleFunc("/security-services", func(w http.ResponseWriter, r *http.Request) { +func MockCreateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/security-services", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -30,7 +30,7 @@ func MockCreateResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "security_service": { "status": "new", @@ -51,23 +51,23 @@ func MockCreateResponse(t *testing.T) { }) } -func MockDeleteResponse(t *testing.T) { - th.Mux.HandleFunc("/security-services/securityServiceID", func(w http.ResponseWriter, r *http.Request) { +func MockDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/security-services/securityServiceID", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) }) } -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/security-services/detail", func(w http.ResponseWriter, r *http.Request) { +func MockListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/security-services/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "security_services": [ { @@ -105,15 +105,15 @@ func MockListResponse(t *testing.T) { }) } -func MockFilteredListResponse(t *testing.T) { - th.Mux.HandleFunc("/security-services/detail", func(w http.ResponseWriter, r *http.Request) { +func MockFilteredListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/security-services/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "security_services": [ { @@ -136,14 +136,14 @@ func MockFilteredListResponse(t *testing.T) { }) } -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc("/security-services/3c829734-0679-4c17-9637-801da48c0d5f", func(w http.ResponseWriter, r *http.Request) { +func MockGetResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/security-services/3c829734-0679-4c17-9637-801da48c0d5f", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "security_service": { "status": "new", @@ -164,12 +164,12 @@ func MockGetResponse(t *testing.T) { }) } -func MockUpdateResponse(t *testing.T) { - th.Mux.HandleFunc("/security-services/securityServiceID", func(w http.ResponseWriter, r *http.Request) { +func MockUpdateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/security-services/securityServiceID", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "security_service": { "status": "new", diff --git a/openstack/sharedfilesystems/v2/securityservices/testing/requests_test.go b/openstack/sharedfilesystems/v2/securityservices/testing/requests_test.go index 391485cdc2..c188258b80 100644 --- a/openstack/sharedfilesystems/v2/securityservices/testing/requests_test.go +++ b/openstack/sharedfilesystems/v2/securityservices/testing/requests_test.go @@ -13,10 +13,10 @@ import ( // Verifies that a security service can be created correctly func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockCreateResponse(t) + MockCreateResponse(t, fakeServer) options := &securityservices.CreateOpts{ Name: "SecServ1", @@ -27,19 +27,22 @@ func TestCreate(t *testing.T) { Type: "kerberos", } - s, err := securityservices.Create(context.TODO(), client.ServiceClient(), options).Extract() + s, err := securityservices.Create(context.TODO(), client.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, s.Name, "SecServ1") - th.AssertEquals(t, s.Description, "Creating my first Security Service") - th.AssertEquals(t, s.User, "demo") - th.AssertEquals(t, s.DNSIP, "10.0.0.0/24") - th.AssertEquals(t, s.Password, "supersecret") - th.AssertEquals(t, s.Type, "kerberos") + th.AssertEquals(t, "SecServ1", s.Name) + th.AssertEquals(t, "Creating my first Security Service", s.Description) + th.AssertEquals(t, "demo", s.User) + th.AssertEquals(t, "10.0.0.0/24", s.DNSIP) + th.AssertEquals(t, "supersecret", s.Password) + th.AssertEquals(t, "kerberos", s.Type) } // Verifies that a security service cannot be created without a type -func TestCreateFails(t *testing.T) { +func TestRequiredCreateOpts(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + options := &securityservices.CreateOpts{ Name: "SecServ1", Description: "Creating my first Security Service", @@ -48,7 +51,7 @@ func TestCreateFails(t *testing.T) { Password: "***", } - _, err := securityservices.Create(context.TODO(), client.ServiceClient(), options).Extract() + _, err := securityservices.Create(context.TODO(), client.ServiceClient(fakeServer), options).Extract() if _, ok := err.(gophercloud.ErrMissingInput); !ok { t.Fatal("ErrMissingInput was expected to occur") } @@ -56,23 +59,23 @@ func TestCreateFails(t *testing.T) { // Verifies that security service deletion works func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDeleteResponse(t) + MockDeleteResponse(t, fakeServer) - res := securityservices.Delete(context.TODO(), client.ServiceClient(), "securityServiceID") + res := securityservices.Delete(context.TODO(), client.ServiceClient(fakeServer), "securityServiceID") th.AssertNoErr(t, res.Err) } // Verifies that security services can be listed correctly func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) - allPages, err := securityservices.List(client.ServiceClient(), &securityservices.ListOpts{}).AllPages(context.TODO()) + allPages, err := securityservices.List(client.ServiceClient(fakeServer), &securityservices.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := securityservices.ExtractSecurityServices(allPages) th.AssertNoErr(t, err) @@ -115,16 +118,16 @@ func TestList(t *testing.T) { // Verifies that security services list can be called with query parameters func TestFilteredList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockFilteredListResponse(t) + MockFilteredListResponse(t, fakeServer) options := &securityservices.ListOpts{ Type: "kerberos", } - allPages, err := securityservices.List(client.ServiceClient(), options).AllPages(context.TODO()) + allPages, err := securityservices.List(client.ServiceClient(fakeServer), options).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := securityservices.ExtractSecurityServices(allPages) th.AssertNoErr(t, err) @@ -152,10 +155,10 @@ func TestFilteredList(t *testing.T) { // Verifies that it is possible to get a security service func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetResponse(t) + MockGetResponse(t, fakeServer) var nilTime time.Time expected := securityservices.SecurityService{ @@ -174,7 +177,7 @@ func TestGet(t *testing.T) { Password: "supersecret", } - n, err := securityservices.Get(context.TODO(), client.ServiceClient(), "3c829734-0679-4c17-9637-801da48c0d5f").Extract() + n, err := securityservices.Get(context.TODO(), client.ServiceClient(fakeServer), "3c829734-0679-4c17-9637-801da48c0d5f").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &expected, n) @@ -182,10 +185,10 @@ func TestGet(t *testing.T) { // Verifies that it is possible to update a security service func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockUpdateResponse(t) + MockUpdateResponse(t, fakeServer) expected := securityservices.SecurityService{ ID: "securityServiceID", Name: "SecServ2", @@ -204,7 +207,7 @@ func TestUpdate(t *testing.T) { name := "SecServ2" options := securityservices.UpdateOpts{Name: &name} - s, err := securityservices.Update(context.TODO(), client.ServiceClient(), "securityServiceID", options).Extract() + s, err := securityservices.Update(context.TODO(), client.ServiceClient(fakeServer), "securityServiceID", options).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &expected, s) } diff --git a/openstack/sharedfilesystems/v2/services/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/services/testing/fixtures_test.go index 67f7b94c78..1c8bd7a57f 100644 --- a/openstack/sharedfilesystems/v2/services/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/services/testing/fixtures_test.go @@ -60,12 +60,12 @@ var SecondFakeService = services.Service{ } // HandleListSuccessfully configures the test server to respond to a List request. -func HandleListSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/services", func(w http.ResponseWriter, r *http.Request) { +func HandleListSuccessfully(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/services", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ServiceListBody) + fmt.Fprint(w, ServiceListBody) }) } diff --git a/openstack/sharedfilesystems/v2/services/testing/requests_test.go b/openstack/sharedfilesystems/v2/services/testing/requests_test.go index 16607415b6..af5e657b46 100644 --- a/openstack/sharedfilesystems/v2/services/testing/requests_test.go +++ b/openstack/sharedfilesystems/v2/services/testing/requests_test.go @@ -6,17 +6,17 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/services" "github.com/gophercloud/gophercloud/v2/pagination" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestListServices(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - HandleListSuccessfully(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListSuccessfully(t, fakeServer) pages := 0 - err := services.List(client.ServiceClient(), services.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := services.List(client.ServiceClient(fakeServer), services.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := services.ExtractServices(page) @@ -27,13 +27,13 @@ func TestListServices(t *testing.T) { if len(actual) != 2 { t.Fatalf("Expected 2 services, got %d", len(actual)) } - testhelper.CheckDeepEquals(t, FirstFakeService, actual[0]) - testhelper.CheckDeepEquals(t, SecondFakeService, actual[1]) + th.CheckDeepEquals(t, FirstFakeService, actual[0]) + th.CheckDeepEquals(t, SecondFakeService, actual[1]) return true, nil }) - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) if pages != 1 { t.Errorf("Expected 1 page, saw %d", pages) diff --git a/openstack/sharedfilesystems/v2/shareaccessrules/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/shareaccessrules/testing/fixtures_test.go index d82014357d..eb5a90a20b 100644 --- a/openstack/sharedfilesystems/v2/shareaccessrules/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/shareaccessrules/testing/fixtures_test.go @@ -6,7 +6,7 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) const ( @@ -33,14 +33,14 @@ var getResponse = `{ } }` -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc(shareAccessRulesEndpoint+"/"+shareAccessRuleID, func(w http.ResponseWriter, r *http.Request) { +func MockGetResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareAccessRulesEndpoint+"/"+shareAccessRuleID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, getResponse) + fmt.Fprint(w, getResponse) }) } diff --git a/openstack/sharedfilesystems/v2/shareaccessrules/testing/requests_test.go b/openstack/sharedfilesystems/v2/shareaccessrules/testing/requests_test.go index 4f86a642d4..d0600a183b 100644 --- a/openstack/sharedfilesystems/v2/shareaccessrules/testing/requests_test.go +++ b/openstack/sharedfilesystems/v2/shareaccessrules/testing/requests_test.go @@ -10,16 +10,15 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/shareaccessrules" th "github.com/gophercloud/gophercloud/v2/testhelper" "github.com/gophercloud/gophercloud/v2/testhelper/client" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetResponse(t) + MockGetResponse(t, fakeServer) - resp := shareaccessrules.Get(context.TODO(), client.ServiceClient(), "507bf114-36f2-4f56-8cf4-857985ca87c1") + resp := shareaccessrules.Get(context.TODO(), client.ServiceClient(fakeServer), "507bf114-36f2-4f56-8cf4-857985ca87c1") th.AssertNoErr(t, resp.Err) accessRule, err := resp.Extract() @@ -42,13 +41,13 @@ func TestGet(t *testing.T) { }, accessRule) } -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc(shareAccessRulesEndpoint, func(w http.ResponseWriter, r *http.Request) { +func MockListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareAccessRulesEndpoint, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, listResponse) + fmt.Fprint(w, listResponse) }) } diff --git a/openstack/sharedfilesystems/v2/sharenetworks/requests.go b/openstack/sharedfilesystems/v2/sharenetworks/requests.go index 6df73b5eed..c340e3ed43 100644 --- a/openstack/sharedfilesystems/v2/sharenetworks/requests.go +++ b/openstack/sharedfilesystems/v2/sharenetworks/requests.go @@ -116,7 +116,7 @@ func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) paginat return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { p := ShareNetworkPage{pagination.MarkerPageBase{PageResult: r}} - p.MarkerPageBase.Owner = p + p.Owner = p return p }) } diff --git a/openstack/sharedfilesystems/v2/sharenetworks/results.go b/openstack/sharedfilesystems/v2/sharenetworks/results.go index 5d3537c007..51c9670a2b 100644 --- a/openstack/sharedfilesystems/v2/sharenetworks/results.go +++ b/openstack/sharedfilesystems/v2/sharenetworks/results.go @@ -70,7 +70,7 @@ type ShareNetworkPage struct { } // NextPageURL generates the URL for the page of results after this one. -func (r ShareNetworkPage) NextPageURL() (string, error) { +func (r ShareNetworkPage) NextPageURL(endpointURL string) (string, error) { currentURL := r.URL mark, err := r.Owner.LastMarker() if err != nil { @@ -94,7 +94,7 @@ func (r ShareNetworkPage) LastMarker() (string, error) { return maxInt, nil } - u, err := url.Parse(r.URL.String()) + u, err := url.Parse(r.String()) if err != nil { return maxInt, err } diff --git a/openstack/sharedfilesystems/v2/sharenetworks/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/sharenetworks/testing/fixtures_test.go index cfa3b1712d..5dc89b8826 100644 --- a/openstack/sharedfilesystems/v2/sharenetworks/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/sharenetworks/testing/fixtures_test.go @@ -6,7 +6,7 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func createReq(name, description, network, subnetwork string) string { @@ -41,10 +41,10 @@ func createResp(name, description, network, subnetwork string) string { }`, name, description, network, subnetwork) } -func MockCreateResponse(t *testing.T) { - th.Mux.HandleFunc("/share-networks", func(w http.ResponseWriter, r *http.Request) { +func MockCreateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/share-networks", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, createReq("my_network", @@ -55,25 +55,25 @@ func MockCreateResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, createResp("my_network", + fmt.Fprint(w, createResp("my_network", "This is my share network", "998b42ee-2cee-4d36-8b95-67b5ca1f2109", "53482b62-2c84-4a53-b6ab-30d9d9800d06")) }) } -func MockDeleteResponse(t *testing.T) { - th.Mux.HandleFunc("/share-networks/fa158a3d-6d9f-4187-9ca5-abbb82646eb2", func(w http.ResponseWriter, r *http.Request) { +func MockDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/share-networks/fa158a3d-6d9f-4187-9ca5-abbb82646eb2", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) }) } -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/share-networks/detail", func(w http.ResponseWriter, r *http.Request) { +func MockListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/share-networks/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -85,7 +85,7 @@ func MockListResponse(t *testing.T) { switch marker { case "": - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "share_networks": [ { "name": "net_my1", @@ -135,7 +135,7 @@ func MockListResponse(t *testing.T) { ] }`) default: - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "share_networks": [] }`) @@ -143,10 +143,10 @@ func MockListResponse(t *testing.T) { }) } -func MockFilteredListResponse(t *testing.T) { - th.Mux.HandleFunc("/share-networks/detail", func(w http.ResponseWriter, r *http.Request) { +func MockFilteredListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/share-networks/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -157,7 +157,7 @@ func MockFilteredListResponse(t *testing.T) { marker := r.Form.Get("offset") switch marker { case "": - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "share_networks": [ { @@ -178,7 +178,7 @@ func MockFilteredListResponse(t *testing.T) { ] }`) case "1": - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "share_networks": [ { @@ -199,7 +199,7 @@ func MockFilteredListResponse(t *testing.T) { ] }`) case "2": - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "share_networks": [ { @@ -220,7 +220,7 @@ func MockFilteredListResponse(t *testing.T) { ] }`) default: - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "share_networks": [] }`) @@ -228,14 +228,14 @@ func MockFilteredListResponse(t *testing.T) { }) } -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc("/share-networks/7f950b52-6141-4a08-bbb5-bb7ffa3ea5fd", func(w http.ResponseWriter, r *http.Request) { +func MockGetResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/share-networks/7f950b52-6141-4a08-bbb5-bb7ffa3ea5fd", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "share_network": { "name": "net_my1", @@ -256,12 +256,12 @@ func MockGetResponse(t *testing.T) { }) } -func MockUpdateNeutronResponse(t *testing.T) { - th.Mux.HandleFunc("/share-networks/713df749-aac0-4a54-af52-10f6c991e80c", func(w http.ResponseWriter, r *http.Request) { +func MockUpdateNeutronResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/share-networks/713df749-aac0-4a54-af52-10f6c991e80c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "share_network": { "name": "net_my2", @@ -283,12 +283,12 @@ func MockUpdateNeutronResponse(t *testing.T) { }) } -func MockUpdateNovaResponse(t *testing.T) { - th.Mux.HandleFunc("/share-networks/713df749-aac0-4a54-af52-10f6c991e80c", func(w http.ResponseWriter, r *http.Request) { +func MockUpdateNovaResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/share-networks/713df749-aac0-4a54-af52-10f6c991e80c", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "share_network": { "name": "net_my2", @@ -310,12 +310,12 @@ func MockUpdateNovaResponse(t *testing.T) { }) } -func MockAddSecurityServiceResponse(t *testing.T) { - th.Mux.HandleFunc("/share-networks/shareNetworkID/action", func(w http.ResponseWriter, r *http.Request) { +func MockAddSecurityServiceResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/share-networks/shareNetworkID/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "share_network": { "name": "net2", @@ -336,12 +336,12 @@ func MockAddSecurityServiceResponse(t *testing.T) { }) } -func MockRemoveSecurityServiceResponse(t *testing.T) { - th.Mux.HandleFunc("/share-networks/shareNetworkID/action", func(w http.ResponseWriter, r *http.Request) { +func MockRemoveSecurityServiceResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/share-networks/shareNetworkID/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "share_network": { "name": "net2", diff --git a/openstack/sharedfilesystems/v2/sharenetworks/testing/requests_test.go b/openstack/sharedfilesystems/v2/sharenetworks/testing/requests_test.go index 1cbbb6df5b..67210f85eb 100644 --- a/openstack/sharedfilesystems/v2/sharenetworks/testing/requests_test.go +++ b/openstack/sharedfilesystems/v2/sharenetworks/testing/requests_test.go @@ -13,10 +13,10 @@ import ( // Verifies that a share network can be created correctly func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockCreateResponse(t) + MockCreateResponse(t, fakeServer) options := &sharenetworks.CreateOpts{ Name: "my_network", @@ -25,34 +25,34 @@ func TestCreate(t *testing.T) { NeutronSubnetID: "53482b62-2c84-4a53-b6ab-30d9d9800d06", } - n, err := sharenetworks.Create(context.TODO(), client.ServiceClient(), options).Extract() + n, err := sharenetworks.Create(context.TODO(), client.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.Name, "my_network") - th.AssertEquals(t, n.Description, "This is my share network") - th.AssertEquals(t, n.NeutronNetID, "998b42ee-2cee-4d36-8b95-67b5ca1f2109") - th.AssertEquals(t, n.NeutronSubnetID, "53482b62-2c84-4a53-b6ab-30d9d9800d06") + th.AssertEquals(t, "my_network", n.Name) + th.AssertEquals(t, "This is my share network", n.Description) + th.AssertEquals(t, "998b42ee-2cee-4d36-8b95-67b5ca1f2109", n.NeutronNetID) + th.AssertEquals(t, "53482b62-2c84-4a53-b6ab-30d9d9800d06", n.NeutronSubnetID) } // Verifies that share network deletion works func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDeleteResponse(t) + MockDeleteResponse(t, fakeServer) - res := sharenetworks.Delete(context.TODO(), client.ServiceClient(), "fa158a3d-6d9f-4187-9ca5-abbb82646eb2") + res := sharenetworks.Delete(context.TODO(), client.ServiceClient(fakeServer), "fa158a3d-6d9f-4187-9ca5-abbb82646eb2") th.AssertNoErr(t, res.Err) } // Verifies that share networks can be listed correctly func TestListDetail(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) - allPages, err := sharenetworks.ListDetail(client.ServiceClient(), &sharenetworks.ListOpts{}).AllPages(context.TODO()) + allPages, err := sharenetworks.ListDetail(client.ServiceClient(fakeServer), &sharenetworks.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := sharenetworks.ExtractShareNetworks(allPages) @@ -112,10 +112,10 @@ func TestListDetail(t *testing.T) { // Verifies that share networks list can be called with query parameters func TestPaginatedListDetail(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockFilteredListResponse(t) + MockFilteredListResponse(t, fakeServer) options := &sharenetworks.ListOpts{ Offset: 0, @@ -124,7 +124,7 @@ func TestPaginatedListDetail(t *testing.T) { count := 0 - err := sharenetworks.ListDetail(client.ServiceClient(), options).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := sharenetworks.ListDetail(client.ServiceClient(fakeServer), options).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ _, err := sharenetworks.ExtractShareNetworks(page) if err != nil { @@ -136,15 +136,15 @@ func TestPaginatedListDetail(t *testing.T) { }) th.AssertNoErr(t, err) - th.AssertEquals(t, count, 3) + th.AssertEquals(t, 3, count) } // Verifies that it is possible to get a share network func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetResponse(t) + MockGetResponse(t, fakeServer) var nilTime time.Time expected := sharenetworks.ShareNetwork{ @@ -163,7 +163,7 @@ func TestGet(t *testing.T) { ProjectID: "16e1ab15c35a457e9c2b2aa189f544e1", } - n, err := sharenetworks.Get(context.TODO(), client.ServiceClient(), "7f950b52-6141-4a08-bbb5-bb7ffa3ea5fd").Extract() + n, err := sharenetworks.Get(context.TODO(), client.ServiceClient(fakeServer), "7f950b52-6141-4a08-bbb5-bb7ffa3ea5fd").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &expected, n) @@ -171,10 +171,10 @@ func TestGet(t *testing.T) { // Verifies that it is possible to update a share network using neutron network func TestUpdateNeutron(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockUpdateNeutronResponse(t) + MockUpdateNeutronResponse(t, fakeServer) expected := sharenetworks.ShareNetwork{ ID: "713df749-aac0-4a54-af52-10f6c991e80c", @@ -201,17 +201,17 @@ func TestUpdateNeutron(t *testing.T) { NeutronSubnetID: "new-neutron-subnet-id", } - v, err := sharenetworks.Update(context.TODO(), client.ServiceClient(), "713df749-aac0-4a54-af52-10f6c991e80c", options).Extract() + v, err := sharenetworks.Update(context.TODO(), client.ServiceClient(fakeServer), "713df749-aac0-4a54-af52-10f6c991e80c", options).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &expected, v) } // Verifies that it is possible to update a share network using nova network func TestUpdateNova(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockUpdateNovaResponse(t) + MockUpdateNovaResponse(t, fakeServer) expected := sharenetworks.ShareNetwork{ ID: "713df749-aac0-4a54-af52-10f6c991e80c", @@ -237,17 +237,17 @@ func TestUpdateNova(t *testing.T) { NovaNetID: "new-nova-id", } - v, err := sharenetworks.Update(context.TODO(), client.ServiceClient(), "713df749-aac0-4a54-af52-10f6c991e80c", options).Extract() + v, err := sharenetworks.Update(context.TODO(), client.ServiceClient(fakeServer), "713df749-aac0-4a54-af52-10f6c991e80c", options).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &expected, v) } // Verifies that it is possible to add a security service to a share network func TestAddSecurityService(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockAddSecurityServiceResponse(t) + MockAddSecurityServiceResponse(t, fakeServer) var nilTime time.Time expected := sharenetworks.ShareNetwork{ @@ -267,19 +267,19 @@ func TestAddSecurityService(t *testing.T) { } options := sharenetworks.AddSecurityServiceOpts{SecurityServiceID: "securityServiceID"} - s, err := sharenetworks.AddSecurityService(context.TODO(), client.ServiceClient(), "shareNetworkID", options).Extract() + s, err := sharenetworks.AddSecurityService(context.TODO(), client.ServiceClient(fakeServer), "shareNetworkID", options).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &expected, s) } // Verifies that it is possible to remove a security service from a share network func TestRemoveSecurityService(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockRemoveSecurityServiceResponse(t) + MockRemoveSecurityServiceResponse(t, fakeServer) options := sharenetworks.RemoveSecurityServiceOpts{SecurityServiceID: "securityServiceID"} - _, err := sharenetworks.RemoveSecurityService(context.TODO(), client.ServiceClient(), "shareNetworkID", options).Extract() + _, err := sharenetworks.RemoveSecurityService(context.TODO(), client.ServiceClient(fakeServer), "shareNetworkID", options).Extract() th.AssertNoErr(t, err) } diff --git a/openstack/sharedfilesystems/v2/shares/requests.go b/openstack/sharedfilesystems/v2/shares/requests.go index d228f6a36c..4cf7621331 100644 --- a/openstack/sharedfilesystems/v2/shares/requests.go +++ b/openstack/sharedfilesystems/v2/shares/requests.go @@ -7,6 +7,22 @@ import ( "github.com/gophercloud/gophercloud/v2/pagination" ) +// SchedulerHints contains options for providing scheduler hints when creating +// a Share. +type SchedulerHints struct { + // DifferentHost will place the share on a different back-end that does not + // host the given shares. + DifferentHost string `json:"different_host,omitempty"` + + // SameHost will place the share on a back-end that hosts the given shares. + SameHost string `json:"same_host,omitempty"` + + // OnlyHost value must be a manage-share service host in + // host@backend#POOL format (admin only). Only available in and beyond + // API version 2.67 + OnlyHost string `json:"only_host,omitempty"` +} + // CreateOptsBuilder allows extensions to add additional parameters to the // Create request. type CreateOptsBuilder interface { @@ -40,6 +56,8 @@ type CreateOpts struct { SnapshotID string `json:"snapshot_id,omitempty"` // Determines whether or not the share is public IsPublic *bool `json:"is_public,omitempty"` + // The UUID of the share group. Available starting from the microversion 2.31 + ShareGroupID string `json:"share_group_id,omitempty"` // Key value pairs of user defined metadata Metadata map[string]string `json:"metadata,omitempty"` // The UUID of the share network to which the share belongs to @@ -48,6 +66,9 @@ type CreateOpts struct { ConsistencyGroupID string `json:"consistency_group_id,omitempty"` // The availability zone of the share AvailabilityZone string `json:"availability_zone,omitempty"` + // SchedulerHints are hints for the scheduler to select the share backend + // Only available in and beyond API version 2.65 + SchedulerHints *SchedulerHints `json:"scheduler_hints,omitempty"` } // ToShareCreateMap assembles a request body based on the contents of a @@ -160,7 +181,7 @@ func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) paginat return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { p := SharePage{pagination.MarkerPageBase{PageResult: r}} - p.MarkerPageBase.Owner = p + p.Owner = p return p }) } diff --git a/openstack/sharedfilesystems/v2/shares/results.go b/openstack/sharedfilesystems/v2/shares/results.go index d3fe9d8437..70c8aaa53d 100644 --- a/openstack/sharedfilesystems/v2/shares/results.go +++ b/openstack/sharedfilesystems/v2/shares/results.go @@ -54,6 +54,8 @@ type Share struct { ShareType string `json:"share_type"` // The name of the share type. ShareTypeName string `json:"share_type_name"` + // The UUID of the share group. Available starting from the microversion 2.31 + ShareGroupID string `json:"share_group_id"` // Size of the share in GB Size int `json:"size"` // UUID of the snapshot from which to create the share @@ -120,7 +122,7 @@ type SharePage struct { } // NextPageURL generates the URL for the page of results after this one. -func (r SharePage) NextPageURL() (string, error) { +func (r SharePage) NextPageURL(endpointURL string) (string, error) { currentURL := r.URL mark, err := r.Owner.LastMarker() if err != nil { @@ -146,7 +148,7 @@ func (r SharePage) LastMarker() (string, error) { return invalidMarker, nil } - u, err := url.Parse(r.URL.String()) + u, err := url.Parse(r.String()) if err != nil { return invalidMarker, err } diff --git a/openstack/sharedfilesystems/v2/shares/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/shares/testing/fixtures_test.go index 89a2c5461e..c7ff0d287c 100644 --- a/openstack/sharedfilesystems/v2/shares/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/shares/testing/fixtures_test.go @@ -6,7 +6,7 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) const ( @@ -18,7 +18,11 @@ var createRequest = `{ "share": { "name": "my_test_share", "size": 1, - "share_proto": "NFS" + "share_proto": "NFS", + "scheduler_hints": { + "same_host": "e268f4aa-d571-43dd-9ab3-f49ad06ffaef", + "different_host": "e268f4aa-d571-43dd-9ab3-f49ad06ffaef" + } } }` @@ -61,7 +65,9 @@ var createResponse = `{ "is_public": true, "metadata": { "project": "my_app", - "aim": "doc" + "aim": "doc", + "__affinity_same_host": "e268f4aa-d571-43dd-9ab3-f49ad06ffaef", + "__affinity_different_host": "e268f4aa-d571-43dd-9ab3-f49ad06ffaef" }, "id": "011d21e2-fbc3-4e4a-9993-9ea223f73264", "description": "My custom share London" @@ -69,24 +75,24 @@ var createResponse = `{ }` // MockCreateResponse creates a mock response -func MockCreateResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint, func(w http.ResponseWriter, r *http.Request) { +func MockCreateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, createRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, createResponse) + fmt.Fprint(w, createResponse) }) } // MockDeleteResponse creates a mock delete response -func MockDeleteResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+shareID, func(w http.ResponseWriter, r *http.Request) { +func MockDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+shareID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) }) } @@ -143,16 +149,16 @@ var updateResponse = ` } ` -func MockUpdateResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+shareID, func(w http.ResponseWriter, r *http.Request) { +func MockUpdateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+shareID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, updateRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, updateResponse) + fmt.Fprint(w, updateResponse) }) } @@ -200,12 +206,12 @@ var getResponse = `{ }` // MockGetResponse creates a mock get response -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+shareID, func(w http.ResponseWriter, r *http.Request) { +func MockGetResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+shareID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, getResponse) + fmt.Fprint(w, getResponse) }) } @@ -257,10 +263,10 @@ var listDetailResponse = `{ var listDetailEmptyResponse = `{"shares": []}` // MockListDetailResponse creates a mock detailed-list response -func MockListDetailResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/detail", func(w http.ResponseWriter, r *http.Request) { +func MockListDetailResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -292,13 +298,13 @@ var listExportLocationsResponse = `{ }` // MockListExportLocationsResponse creates a mock get export locations response -func MockListExportLocationsResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/export_locations", func(w http.ResponseWriter, r *http.Request) { +func MockListExportLocationsResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/export_locations", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, listExportLocationsResponse) + fmt.Fprint(w, listExportLocationsResponse) }) } @@ -313,13 +319,13 @@ var getExportLocationResponse = `{ }` // MockGetExportLocationResponse creates a mock get export location response -func MockGetExportLocationResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/export_locations/80ed63fc-83bc-4afc-b881-da4a345ac83d", func(w http.ResponseWriter, r *http.Request) { +func MockGetExportLocationResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/export_locations/80ed63fc-83bc-4afc-b881-da4a345ac83d", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, getExportLocationResponse) + fmt.Fprint(w, getExportLocationResponse) }) } @@ -344,16 +350,16 @@ var grantAccessResponse = `{ }` // MockGrantAccessResponse creates a mock grant access response -func MockGrantAccessResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/action", func(w http.ResponseWriter, r *http.Request) { +func MockGrantAccessResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, grantAccessRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, grantAccessResponse) + fmt.Fprint(w, grantAccessResponse) }) } @@ -364,10 +370,10 @@ var revokeAccessRequest = `{ }` // MockRevokeAccessResponse creates a mock revoke access response -func MockRevokeAccessResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/action", func(w http.ResponseWriter, r *http.Request) { +func MockRevokeAccessResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, revokeAccessRequest) @@ -395,16 +401,16 @@ var listAccessRightsResponse = `{ }` // MockListAccessRightsResponse creates a mock list access response -func MockListAccessRightsResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/action", func(w http.ResponseWriter, r *http.Request) { +func MockListAccessRightsResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, listAccessRightsRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, listAccessRightsResponse) + fmt.Fprint(w, listAccessRightsResponse) }) } @@ -415,10 +421,10 @@ var extendRequest = `{ }` // MockExtendResponse creates a mock extend share response -func MockExtendResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/action", func(w http.ResponseWriter, r *http.Request) { +func MockExtendResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, extendRequest) @@ -434,10 +440,10 @@ var shrinkRequest = `{ }` // MockShrinkResponse creates a mock shrink share response -func MockShrinkResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/action", func(w http.ResponseWriter, r *http.Request) { +func MockShrinkResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, shrinkRequest) @@ -453,10 +459,10 @@ var getMetadataResponse = `{ }` // MockGetMetadataResponse creates a mock get metadata response -func MockGetMetadataResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/metadata", func(w http.ResponseWriter, r *http.Request) { +func MockGetMetadataResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/metadata", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -471,10 +477,10 @@ var getMetadatumResponse = `{ }` // MockGetMetadatumResponse creates a mock get metadatum response -func MockGetMetadatumResponse(t *testing.T, key string) { - th.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/metadata/"+key, func(w http.ResponseWriter, r *http.Request) { +func MockGetMetadatumResponse(t *testing.T, fakeServer th.FakeServer, key string) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/metadata/"+key, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -495,10 +501,10 @@ var setMetadataResponse = `{ }` // MockSetMetadataResponse creates a mock set metadata response -func MockSetMetadataResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/metadata", func(w http.ResponseWriter, r *http.Request) { +func MockSetMetadataResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/metadata", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, setMetadataRequest) @@ -521,10 +527,10 @@ var updateMetadataResponse = `{ }` // MockUpdateMetadataResponse creates a mock update metadata response -func MockUpdateMetadataResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/metadata", func(w http.ResponseWriter, r *http.Request) { +func MockUpdateMetadataResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/metadata", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, updateMetadataRequest) @@ -535,10 +541,10 @@ func MockUpdateMetadataResponse(t *testing.T) { } // MockDeleteMetadatumResponse creates a mock unset metadata response -func MockDeleteMetadatumResponse(t *testing.T, key string) { - th.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/metadata/"+key, func(w http.ResponseWriter, r *http.Request) { +func MockDeleteMetadatumResponse(t *testing.T, fakeServer th.FakeServer, key string) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/metadata/"+key, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) }) } @@ -550,10 +556,10 @@ var revertRequest = `{ }` // MockRevertResponse creates a mock revert share response -func MockRevertResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/action", func(w http.ResponseWriter, r *http.Request) { +func MockRevertResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, revertRequest) @@ -569,10 +575,10 @@ var resetStatusRequest = `{ }` // MockResetStatusResponse creates a mock reset status share response -func MockResetStatusResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/action", func(w http.ResponseWriter, r *http.Request) { +func MockResetStatusResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, resetStatusRequest) @@ -586,10 +592,10 @@ var forceDeleteRequest = `{ }` // MockForceDeleteResponse creates a mock force delete share response -func MockForceDeleteResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/action", func(w http.ResponseWriter, r *http.Request) { +func MockForceDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, forceDeleteRequest) @@ -603,10 +609,10 @@ var unmanageRequest = `{ }` // MockUnmanageResponse creates a mock unmanage share response -func MockUnmanageResponse(t *testing.T) { - th.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/action", func(w http.ResponseWriter, r *http.Request) { +func MockUnmanageResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(shareEndpoint+"/"+shareID+"/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, unmanageRequest) diff --git a/openstack/sharedfilesystems/v2/shares/testing/request_test.go b/openstack/sharedfilesystems/v2/shares/testing/request_test.go index f9af3da84b..3470f8bbaa 100644 --- a/openstack/sharedfilesystems/v2/shares/testing/request_test.go +++ b/openstack/sharedfilesystems/v2/shares/testing/request_test.go @@ -11,25 +11,35 @@ import ( ) func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockCreateResponse(t) - - options := &shares.CreateOpts{Size: 1, Name: "my_test_share", ShareProto: "NFS"} - n, err := shares.Create(context.TODO(), client.ServiceClient(), options).Extract() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + MockCreateResponse(t, fakeServer) + + options := &shares.CreateOpts{ + Size: 1, + Name: "my_test_share", + ShareProto: "NFS", + SchedulerHints: &shares.SchedulerHints{ + SameHost: "e268f4aa-d571-43dd-9ab3-f49ad06ffaef", + DifferentHost: "e268f4aa-d571-43dd-9ab3-f49ad06ffaef", + }, + } + n, err := shares.Create(context.TODO(), client.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.Name, "my_test_share") - th.AssertEquals(t, n.Size, 1) - th.AssertEquals(t, n.ShareProto, "NFS") + th.AssertEquals(t, "my_test_share", n.Name) + th.AssertEquals(t, 1, n.Size) + th.AssertEquals(t, "NFS", n.ShareProto) + th.AssertEquals(t, "e268f4aa-d571-43dd-9ab3-f49ad06ffaef", n.Metadata["__affinity_same_host"]) + th.AssertEquals(t, "e268f4aa-d571-43dd-9ab3-f49ad06ffaef", n.Metadata["__affinity_different_host"]) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockUpdateResponse(t) + MockUpdateResponse(t, fakeServer) name := "my_new_test_share" description := "" @@ -39,33 +49,33 @@ func TestUpdate(t *testing.T) { DisplayDescription: &description, IsPublic: &iFalse, } - n, err := shares.Update(context.TODO(), client.ServiceClient(), shareID, options).Extract() + n, err := shares.Update(context.TODO(), client.ServiceClient(fakeServer), shareID, options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.Name, "my_new_test_share") - th.AssertEquals(t, n.Description, "") - th.AssertEquals(t, n.IsPublic, false) + th.AssertEquals(t, "my_new_test_share", n.Name) + th.AssertEquals(t, "", n.Description) + th.AssertFalse(t, n.IsPublic) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDeleteResponse(t) + MockDeleteResponse(t, fakeServer) - result := shares.Delete(context.TODO(), client.ServiceClient(), shareID) + result := shares.Delete(context.TODO(), client.ServiceClient(fakeServer), shareID) th.AssertNoErr(t, result.Err) } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetResponse(t) + MockGetResponse(t, fakeServer) - s, err := shares.Get(context.TODO(), client.ServiceClient(), shareID).Extract() + s, err := shares.Get(context.TODO(), client.ServiceClient(fakeServer), shareID).Extract() th.AssertNoErr(t, err) - th.AssertDeepEquals(t, s, &shares.Share{ + th.AssertDeepEquals(t, &shares.Share{ AvailabilityZone: "nova", ShareNetworkID: "713df749-aac0-4a54-af52-10f6c991e80c", ShareServerID: "e268f4aa-d571-43dd-9ab3-f49ad06ffaef", @@ -104,23 +114,23 @@ func TestGet(t *testing.T) { "rel": "bookmark", }, }, - }) + }, s) } func TestListDetail(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListDetailResponse(t) + MockListDetailResponse(t, fakeServer) - allPages, err := shares.ListDetail(client.ServiceClient(), &shares.ListOpts{}).AllPages(context.TODO()) + allPages, err := shares.ListDetail(client.ServiceClient(fakeServer), &shares.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := shares.ExtractShares(allPages) th.AssertNoErr(t, err) - th.AssertDeepEquals(t, actual, []shares.Share{ + th.AssertDeepEquals(t, []shares.Share{ { AvailabilityZone: "nova", ShareNetworkID: "713df749-aac0-4a54-af52-10f6c991e80c", @@ -161,23 +171,23 @@ func TestListDetail(t *testing.T) { }, }, }, - }) + }, actual) } func TestListExportLocationsSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListExportLocationsResponse(t) + MockListExportLocationsResponse(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) // Client c must have Microversion set; minimum supported microversion for List Export Locations is 2.9 c.Microversion = "2.9" s, err := shares.ListExportLocations(context.TODO(), c, shareID).Extract() th.AssertNoErr(t, err) - th.AssertDeepEquals(t, s, []shares.ExportLocation{ + th.AssertDeepEquals(t, []shares.ExportLocation{ { Path: "127.0.0.1:/var/lib/manila/mnt/share-9a922036-ad26-4d27-b955-7a1e285fa74d", ShareInstanceID: "011d21e2-fbc3-4e4a-9993-9ea223f73264", @@ -185,38 +195,38 @@ func TestListExportLocationsSuccess(t *testing.T) { ID: "80ed63fc-83bc-4afc-b881-da4a345ac83d", Preferred: false, }, - }) + }, s) } func TestGetExportLocationSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetExportLocationResponse(t) + MockGetExportLocationResponse(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) // Client c must have Microversion set; minimum supported microversion for Get Export Location is 2.9 c.Microversion = "2.9" s, err := shares.GetExportLocation(context.TODO(), c, shareID, "80ed63fc-83bc-4afc-b881-da4a345ac83d").Extract() th.AssertNoErr(t, err) - th.AssertDeepEquals(t, s, &shares.ExportLocation{ + th.AssertDeepEquals(t, &shares.ExportLocation{ Path: "127.0.0.1:/var/lib/manila/mnt/share-9a922036-ad26-4d27-b955-7a1e285fa74d", ShareInstanceID: "011d21e2-fbc3-4e4a-9993-9ea223f73264", IsAdminOnly: false, ID: "80ed63fc-83bc-4afc-b881-da4a345ac83d", Preferred: false, - }) + }, s) } func TestGrantAcessSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGrantAccessResponse(t) + MockGrantAccessResponse(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) // Client c must have Microversion set; minimum supported microversion for Grant Access is 2.7 c.Microversion = "2.7" @@ -228,7 +238,7 @@ func TestGrantAcessSuccess(t *testing.T) { s, err := shares.GrantAccess(context.TODO(), c, shareID, grantAccessReq).Extract() th.AssertNoErr(t, err) - th.AssertDeepEquals(t, s, &shares.AccessRight{ + th.AssertDeepEquals(t, &shares.AccessRight{ ShareID: "011d21e2-fbc3-4e4a-9993-9ea223f73264", AccessType: "ip", AccessTo: "0.0.0.0/0", @@ -236,16 +246,16 @@ func TestGrantAcessSuccess(t *testing.T) { AccessLevel: "rw", State: "new", ID: "a2f226a5-cee8-430b-8a03-78a59bd84ee8", - }) + }, s) } func TestRevokeAccessSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockRevokeAccessResponse(t) + MockRevokeAccessResponse(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) // Client c must have Microversion set; minimum supported microversion for Revoke Access is 2.7 c.Microversion = "2.7" @@ -256,19 +266,19 @@ func TestRevokeAccessSuccess(t *testing.T) { } func TestListAccessRightsSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListAccessRightsResponse(t) + MockListAccessRightsResponse(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) // Client c must have Microversion set; minimum supported microversion for Grant Access is 2.7 c.Microversion = "2.7" s, err := shares.ListAccessRights(context.TODO(), c, shareID).Extract() th.AssertNoErr(t, err) - th.AssertDeepEquals(t, s, []shares.AccessRight{ + th.AssertDeepEquals(t, []shares.AccessRight{ { ShareID: "011d21e2-fbc3-4e4a-9993-9ea223f73264", AccessType: "ip", @@ -278,16 +288,16 @@ func TestListAccessRightsSuccess(t *testing.T) { State: "new", ID: "a2f226a5-cee8-430b-8a03-78a59bd84ee8", }, - }) + }, s) } func TestExtendSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockExtendResponse(t) + MockExtendResponse(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) // Client c must have Microversion set; minimum supported microversion for Grant Access is 2.7 c.Microversion = "2.7" @@ -296,12 +306,12 @@ func TestExtendSuccess(t *testing.T) { } func TestShrinkSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockShrinkResponse(t) + MockShrinkResponse(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) // Client c must have Microversion set; minimum supported microversion for Grant Access is 2.7 c.Microversion = "2.7" @@ -310,12 +320,12 @@ func TestShrinkSuccess(t *testing.T) { } func TestGetMetadataSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetMetadataResponse(t) + MockGetMetadataResponse(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) actual, err := shares.GetMetadata(context.TODO(), c, shareID).Extract() th.AssertNoErr(t, err) @@ -323,12 +333,12 @@ func TestGetMetadataSuccess(t *testing.T) { } func TestGetMetadatumSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetMetadatumResponse(t, "foo") + MockGetMetadatumResponse(t, fakeServer, "foo") - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) actual, err := shares.GetMetadatum(context.TODO(), c, shareID, "foo").Extract() th.AssertNoErr(t, err) @@ -336,12 +346,12 @@ func TestGetMetadatumSuccess(t *testing.T) { } func TestSetMetadataSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockSetMetadataResponse(t) + MockSetMetadataResponse(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) actual, err := shares.SetMetadata(context.TODO(), c, shareID, &shares.SetMetadataOpts{Metadata: map[string]string{"foo": "bar"}}).Extract() th.AssertNoErr(t, err) @@ -349,12 +359,12 @@ func TestSetMetadataSuccess(t *testing.T) { } func TestUpdateMetadataSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockUpdateMetadataResponse(t) + MockUpdateMetadataResponse(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) actual, err := shares.UpdateMetadata(context.TODO(), c, shareID, &shares.UpdateMetadataOpts{Metadata: map[string]string{"foo": "bar"}}).Extract() th.AssertNoErr(t, err) @@ -362,24 +372,24 @@ func TestUpdateMetadataSuccess(t *testing.T) { } func TestUnsetMetadataSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDeleteMetadatumResponse(t, "foo") + MockDeleteMetadatumResponse(t, fakeServer, "foo") - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) err := shares.DeleteMetadatum(context.TODO(), c, shareID, "foo").ExtractErr() th.AssertNoErr(t, err) } func TestRevertSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockRevertResponse(t) + MockRevertResponse(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) // Client c must have Microversion set; minimum supported microversion for Revert is 2.27 c.Microversion = "2.27" @@ -388,12 +398,12 @@ func TestRevertSuccess(t *testing.T) { } func TestResetStatusSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockResetStatusResponse(t) + MockResetStatusResponse(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) // Client c must have Microversion set; minimum supported microversion for ResetStatus is 2.7 c.Microversion = "2.7" @@ -402,12 +412,12 @@ func TestResetStatusSuccess(t *testing.T) { } func TestForceDeleteSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockForceDeleteResponse(t) + MockForceDeleteResponse(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) // Client c must have Microversion set; minimum supported microversion for ForceDelete is 2.7 c.Microversion = "2.7" @@ -416,12 +426,12 @@ func TestForceDeleteSuccess(t *testing.T) { } func TestUnmanageSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockUnmanageResponse(t) + MockUnmanageResponse(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) // Client c must have Microversion set; minimum supported microversion for Unmanage is 2.7 c.Microversion = "2.7" diff --git a/openstack/sharedfilesystems/v2/sharetransfers/requests.go b/openstack/sharedfilesystems/v2/sharetransfers/requests.go index 642d2a36d2..dd06d01f36 100644 --- a/openstack/sharedfilesystems/v2/sharetransfers/requests.go +++ b/openstack/sharedfilesystems/v2/sharetransfers/requests.go @@ -42,6 +42,12 @@ func Create(ctx context.Context, client *gophercloud.ServiceClient, opts CreateO return } +// AcceptOptsBuilder allows extensions to add additional parameters to the +// Accept request. +type AcceptOptsBuilder interface { + ToAcceptMap() (map[string]any, error) +} + // AcceptOpts contains options for a Share transfer accept reqeust. type AcceptOpts struct { // The auth key of the share transfer to accept. @@ -58,7 +64,7 @@ func (opts AcceptOpts) ToAcceptMap() (map[string]any, error) { } // Accept will accept a share tranfer request based on the values in AcceptOpts. -func Accept(ctx context.Context, client *gophercloud.ServiceClient, id string, opts AcceptOpts) (r AcceptResult) { +func Accept(ctx context.Context, client *gophercloud.ServiceClient, id string, opts AcceptOptsBuilder) (r AcceptResult) { b, err := opts.ToAcceptMap() if err != nil { r.Err = err @@ -138,7 +144,7 @@ func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pa return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { p := TransferPage{pagination.MarkerPageBase{PageResult: r}} - p.MarkerPageBase.Owner = p + p.Owner = p return p }) } @@ -157,7 +163,7 @@ func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) paginat return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { p := TransferPage{pagination.MarkerPageBase{PageResult: r}} - p.MarkerPageBase.Owner = p + p.Owner = p return p }) } diff --git a/openstack/sharedfilesystems/v2/sharetransfers/results.go b/openstack/sharedfilesystems/v2/sharetransfers/results.go index 264951383d..80de739a05 100644 --- a/openstack/sharedfilesystems/v2/sharetransfers/results.go +++ b/openstack/sharedfilesystems/v2/sharetransfers/results.go @@ -62,7 +62,7 @@ func (r commonResult) Extract() (*Transfer, error) { // ExtractInto converts our response data into a transfer struct. func (r commonResult) ExtractInto(v any) error { - return r.Result.ExtractIntoStructPtr(v, "transfer") + return r.ExtractIntoStructPtr(v, "transfer") } // CreateResult contains the response body and error from a Create request. @@ -94,7 +94,7 @@ func ExtractTransfers(r pagination.Page) ([]Transfer, error) { // ExtractTransfersInto similar to ExtractInto but operates on a `list` of transfers func ExtractTransfersInto(r pagination.Page, v any) error { - return r.(TransferPage).Result.ExtractIntoSlicePtr(v, "transfers") + return r.(TransferPage).ExtractIntoSlicePtr(v, "transfers") } // TransferPage is a pagination.pager that is returned from a call to the List function. @@ -103,7 +103,7 @@ type TransferPage struct { } // NextPageURL generates the URL for the page of results after this one. -func (r TransferPage) NextPageURL() (string, error) { +func (r TransferPage) NextPageURL(endpointURL string) (string, error) { currentURL := r.URL mark, err := r.Owner.LastMarker() if err != nil { @@ -129,7 +129,7 @@ func (r TransferPage) LastMarker() (string, error) { return invalidMarker, nil } - u, err := url.Parse(r.URL.String()) + u, err := url.Parse(r.String()) if err != nil { return invalidMarker, err } diff --git a/openstack/sharedfilesystems/v2/sharetransfers/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/sharetransfers/testing/fixtures_test.go index 980292f38f..3175e10ef5 100644 --- a/openstack/sharedfilesystems/v2/sharetransfers/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/sharetransfers/testing/fixtures_test.go @@ -139,20 +139,20 @@ var AcceptResponse = sharetransfers.Transfer{ }, } -func HandleCreateTransfer(t *testing.T) { - th.Mux.HandleFunc("/share-transfers", func(w http.ResponseWriter, r *http.Request) { +func HandleCreateTransfer(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/share-transfers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) } -func HandleAcceptTransfer(t *testing.T) { - th.Mux.HandleFunc("/share-transfers/b8913bfd-a4d3-4ec5-bd8b-fe2dbeef9f4f/accept", func(w http.ResponseWriter, r *http.Request) { +func HandleAcceptTransfer(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/share-transfers/b8913bfd-a4d3-4ec5-bd8b-fe2dbeef9f4f/accept", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") @@ -162,8 +162,8 @@ func HandleAcceptTransfer(t *testing.T) { }) } -func HandleDeleteTransfer(t *testing.T) { - th.Mux.HandleFunc("/share-transfers/b8913bfd-a4d3-4ec5-bd8b-fe2dbeef9f4f", func(w http.ResponseWriter, r *http.Request) { +func HandleDeleteTransfer(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/share-transfers/b8913bfd-a4d3-4ec5-bd8b-fe2dbeef9f4f", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -171,37 +171,37 @@ func HandleDeleteTransfer(t *testing.T) { }) } -func HandleListTransfers(t *testing.T) { - th.Mux.HandleFunc("/share-transfers", func(w http.ResponseWriter, r *http.Request) { +func HandleListTransfers(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/share-transfers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") th.TestFormValues(t, r, map[string]string{"all_tenants": "true"}) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } -func HandleListTransfersDetail(t *testing.T) { - th.Mux.HandleFunc("/share-transfers/detail", func(w http.ResponseWriter, r *http.Request) { +func HandleListTransfersDetail(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/share-transfers/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") th.TestFormValues(t, r, map[string]string{"all_tenants": "true"}) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } -func HandleGetTransfer(t *testing.T) { - th.Mux.HandleFunc("/share-transfers/b8913bfd-a4d3-4ec5-bd8b-fe2dbeef9f4f", func(w http.ResponseWriter, r *http.Request) { +func HandleGetTransfer(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/share-transfers/b8913bfd-a4d3-4ec5-bd8b-fe2dbeef9f4f", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } diff --git a/openstack/sharedfilesystems/v2/sharetransfers/testing/requests_test.go b/openstack/sharedfilesystems/v2/sharetransfers/testing/requests_test.go index a0a2fefcfa..13dc397a62 100644 --- a/openstack/sharedfilesystems/v2/sharetransfers/testing/requests_test.go +++ b/openstack/sharedfilesystems/v2/sharetransfers/testing/requests_test.go @@ -11,43 +11,43 @@ import ( ) func TestCreateTransfer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateTransfer(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleCreateTransfer(t, fakeServer) - actual, err := sharetransfers.Create(context.TODO(), client.ServiceClient(), TransferRequest).Extract() + actual, err := sharetransfers.Create(context.TODO(), client.ServiceClient(fakeServer), TransferRequest).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, TransferResponse, *actual) } func TestAcceptTransfer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleAcceptTransfer(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleAcceptTransfer(t, fakeServer) - err := sharetransfers.Accept(context.TODO(), client.ServiceClient(), TransferResponse.ID, AcceptRequest).ExtractErr() + err := sharetransfers.Accept(context.TODO(), client.ServiceClient(fakeServer), TransferResponse.ID, AcceptRequest).ExtractErr() th.AssertNoErr(t, err) } func TestDeleteTransfer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteTransfer(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleDeleteTransfer(t, fakeServer) - err := sharetransfers.Delete(context.TODO(), client.ServiceClient(), TransferResponse.ID).ExtractErr() + err := sharetransfers.Delete(context.TODO(), client.ServiceClient(fakeServer), TransferResponse.ID).ExtractErr() th.AssertNoErr(t, err) } func TestListTransfers(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListTransfers(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListTransfers(t, fakeServer) expectedResponse := TransferListResponse expectedResponse[0].AuthKey = "" count := 0 - err := sharetransfers.List(client.ServiceClient(), &sharetransfers.ListOpts{AllTenants: true}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := sharetransfers.List(client.ServiceClient(fakeServer), &sharetransfers.ListOpts{AllTenants: true}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := sharetransfers.ExtractTransfers(page) @@ -58,19 +58,19 @@ func TestListTransfers(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListTransfersDetail(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListTransfersDetail(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListTransfersDetail(t, fakeServer) expectedResponse := TransferListResponse expectedResponse[0].AuthKey = "" count := 0 - err := sharetransfers.ListDetail(client.ServiceClient(), &sharetransfers.ListOpts{AllTenants: true}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := sharetransfers.ListDetail(client.ServiceClient(fakeServer), &sharetransfers.ListOpts{AllTenants: true}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { count++ actual, err := sharetransfers.ExtractTransfers(page) @@ -81,18 +81,18 @@ func TestListTransfersDetail(t *testing.T) { return true, nil }) th.AssertNoErr(t, err) - th.CheckEquals(t, count, 1) + th.CheckEquals(t, 1, count) } func TestListTransfersAllPages(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleListTransfers(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleListTransfers(t, fakeServer) expectedResponse := TransferListResponse expectedResponse[0].AuthKey = "" - allPages, err := sharetransfers.List(client.ServiceClient(), &sharetransfers.ListOpts{AllTenants: true}).AllPages(context.TODO()) + allPages, err := sharetransfers.List(client.ServiceClient(fakeServer), &sharetransfers.ListOpts{AllTenants: true}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := sharetransfers.ExtractTransfers(allPages) th.AssertNoErr(t, err) @@ -100,14 +100,14 @@ func TestListTransfersAllPages(t *testing.T) { } func TestGetTransfer(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleGetTransfer(t) + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + HandleGetTransfer(t, fakeServer) expectedResponse := TransferResponse expectedResponse.AuthKey = "" - actual, err := sharetransfers.Get(context.TODO(), client.ServiceClient(), TransferResponse.ID).Extract() + actual, err := sharetransfers.Get(context.TODO(), client.ServiceClient(fakeServer), TransferResponse.ID).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, expectedResponse, *actual) } diff --git a/openstack/sharedfilesystems/v2/sharetypes/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/sharetypes/testing/fixtures_test.go index 767d42807a..6acf017b87 100644 --- a/openstack/sharedfilesystems/v2/sharetypes/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/sharetypes/testing/fixtures_test.go @@ -6,13 +6,13 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) -func MockCreateResponse(t *testing.T) { - th.Mux.HandleFunc("/types", func(w http.ResponseWriter, r *http.Request) { +func MockCreateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -30,7 +30,7 @@ func MockCreateResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volume_type": { "os-share-type-access:is_public": true, @@ -60,23 +60,23 @@ func MockCreateResponse(t *testing.T) { }) } -func MockDeleteResponse(t *testing.T) { - th.Mux.HandleFunc("/types/shareTypeID", func(w http.ResponseWriter, r *http.Request) { +func MockDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types/shareTypeID", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) }) } -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/types", func(w http.ResponseWriter, r *http.Request) { +func MockListResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volume_types": [ { @@ -134,14 +134,14 @@ func MockListResponse(t *testing.T) { }) } -func MockGetDefaultResponse(t *testing.T) { - th.Mux.HandleFunc("/types/default", func(w http.ResponseWriter, r *http.Request) { +func MockGetDefaultResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types/default", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volume_type": { "required_extra_specs": null, @@ -165,14 +165,14 @@ func MockGetDefaultResponse(t *testing.T) { }) } -func MockGetExtraSpecsResponse(t *testing.T) { - th.Mux.HandleFunc("/types/shareTypeID/extra_specs", func(w http.ResponseWriter, r *http.Request) { +func MockGetExtraSpecsResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types/shareTypeID/extra_specs", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "extra_specs": { "snapshot_support": "True", @@ -183,10 +183,10 @@ func MockGetExtraSpecsResponse(t *testing.T) { }) } -func MockSetExtraSpecsResponse(t *testing.T) { - th.Mux.HandleFunc("/types/shareTypeID/extra_specs", func(w http.ResponseWriter, r *http.Request) { +func MockSetExtraSpecsResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types/shareTypeID/extra_specs", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -199,7 +199,7 @@ func MockSetExtraSpecsResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "extra_specs": { "my_key": "my_value" @@ -208,22 +208,22 @@ func MockSetExtraSpecsResponse(t *testing.T) { }) } -func MockUnsetExtraSpecsResponse(t *testing.T) { - th.Mux.HandleFunc("/types/shareTypeID/extra_specs/my_key", func(w http.ResponseWriter, r *http.Request) { +func MockUnsetExtraSpecsResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types/shareTypeID/extra_specs/my_key", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) }) } -func MockShowAccessResponse(t *testing.T) { - th.Mux.HandleFunc("/types/shareTypeID/share_type_access", func(w http.ResponseWriter, r *http.Request) { +func MockShowAccessResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types/shareTypeID/share_type_access", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "share_type_access": [ { @@ -239,10 +239,10 @@ func MockShowAccessResponse(t *testing.T) { }) } -func MockAddAccessResponse(t *testing.T) { - th.Mux.HandleFunc("/types/shareTypeID/action", func(w http.ResponseWriter, r *http.Request) { +func MockAddAccessResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types/shareTypeID/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` @@ -255,10 +255,10 @@ func MockAddAccessResponse(t *testing.T) { }) } -func MockRemoveAccessResponse(t *testing.T) { - th.Mux.HandleFunc("/types/shareTypeID/action", func(w http.ResponseWriter, r *http.Request) { +func MockRemoveAccessResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/types/shareTypeID/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` diff --git a/openstack/sharedfilesystems/v2/sharetypes/testing/requests_test.go b/openstack/sharedfilesystems/v2/sharetypes/testing/requests_test.go index a88145561f..a35e92972b 100644 --- a/openstack/sharedfilesystems/v2/sharetypes/testing/requests_test.go +++ b/openstack/sharedfilesystems/v2/sharetypes/testing/requests_test.go @@ -12,10 +12,10 @@ import ( // Verifies that a share type can be created correctly func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockCreateResponse(t) + MockCreateResponse(t, fakeServer) snapshotSupport := true extraSpecs := sharetypes.ExtraSpecsOpts{ @@ -29,20 +29,23 @@ func TestCreate(t *testing.T) { ExtraSpecs: extraSpecs, } - st, err := sharetypes.Create(context.TODO(), client.ServiceClient(), options).Extract() + st, err := sharetypes.Create(context.TODO(), client.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, st.Name, "my_new_share_type") - th.AssertEquals(t, st.IsPublic, true) + th.AssertEquals(t, "my_new_share_type", st.Name) + th.AssertTrue(t, st.IsPublic) } // Verifies that a share type can't be created if the required parameters are missing -func TestCreateFails(t *testing.T) { +func TestRequiredCreateOpts(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + options := &sharetypes.CreateOpts{ Name: "my_new_share_type", } - _, err := sharetypes.Create(context.TODO(), client.ServiceClient(), options).Extract() + _, err := sharetypes.Create(context.TODO(), client.ServiceClient(fakeServer), options).Extract() if _, ok := err.(gophercloud.ErrMissingInput); !ok { t.Fatal("ErrMissingInput was expected to occur") } @@ -55,7 +58,7 @@ func TestCreateFails(t *testing.T) { ExtraSpecs: extraSpecs, } - _, err = sharetypes.Create(context.TODO(), client.ServiceClient(), options).Extract() + _, err = sharetypes.Create(context.TODO(), client.ServiceClient(fakeServer), options).Extract() if _, ok := err.(gophercloud.ErrMissingInput); !ok { t.Fatal("ErrMissingInput was expected to occur") } @@ -63,22 +66,22 @@ func TestCreateFails(t *testing.T) { // Verifies that share type deletion works func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDeleteResponse(t) - res := sharetypes.Delete(context.TODO(), client.ServiceClient(), "shareTypeID") + MockDeleteResponse(t, fakeServer) + res := sharetypes.Delete(context.TODO(), client.ServiceClient(fakeServer), "shareTypeID") th.AssertNoErr(t, res.Err) } // Verifies that share types can be listed correctly func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListResponse(t) + MockListResponse(t, fakeServer) - allPages, err := sharetypes.List(client.ServiceClient(), &sharetypes.ListOpts{}).AllPages(context.TODO()) + allPages, err := sharetypes.List(client.ServiceClient(fakeServer), &sharetypes.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := sharetypes.ExtractShareTypes(allPages) th.AssertNoErr(t, err) @@ -104,10 +107,10 @@ func TestList(t *testing.T) { // Verifies that it is possible to get the default share type func TestGetDefault(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetDefaultResponse(t) + MockGetDefaultResponse(t, fakeServer) expected := sharetypes.ShareType{ ID: "be27425c-f807-4500-a056-d00721db45cf", @@ -116,59 +119,59 @@ func TestGetDefault(t *testing.T) { RequiredExtraSpecs: map[string]any(nil), } - actual, err := sharetypes.GetDefault(context.TODO(), client.ServiceClient()).Extract() + actual, err := sharetypes.GetDefault(context.TODO(), client.ServiceClient(fakeServer)).Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, &expected, actual) } // Verifies that it is possible to get the extra specifications for a share type func TestGetExtraSpecs(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetExtraSpecsResponse(t) + MockGetExtraSpecsResponse(t, fakeServer) - st, err := sharetypes.GetExtraSpecs(context.TODO(), client.ServiceClient(), "shareTypeID").Extract() + st, err := sharetypes.GetExtraSpecs(context.TODO(), client.ServiceClient(fakeServer), "shareTypeID").Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, st["snapshot_support"], "True") - th.AssertEquals(t, st["driver_handles_share_servers"], "True") - th.AssertEquals(t, st["my_custom_extra_spec"], "False") + th.AssertEquals(t, "True", st["snapshot_support"]) + th.AssertEquals(t, "True", st["driver_handles_share_servers"]) + th.AssertEquals(t, "False", st["my_custom_extra_spec"]) } // Verifies that an extra specs can be added to a share type func TestSetExtraSpecs(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockSetExtraSpecsResponse(t) + MockSetExtraSpecsResponse(t, fakeServer) options := &sharetypes.SetExtraSpecsOpts{ ExtraSpecs: map[string]any{"my_key": "my_value"}, } - es, err := sharetypes.SetExtraSpecs(context.TODO(), client.ServiceClient(), "shareTypeID", options).Extract() + es, err := sharetypes.SetExtraSpecs(context.TODO(), client.ServiceClient(fakeServer), "shareTypeID", options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, es["my_key"], "my_value") + th.AssertEquals(t, "my_value", es["my_key"]) } // Verifies that an extra specification can be unset for a share type func TestUnsetExtraSpecs(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockUnsetExtraSpecsResponse(t) - res := sharetypes.UnsetExtraSpecs(context.TODO(), client.ServiceClient(), "shareTypeID", "my_key") + MockUnsetExtraSpecsResponse(t, fakeServer) + res := sharetypes.UnsetExtraSpecs(context.TODO(), client.ServiceClient(fakeServer), "shareTypeID", "my_key") th.AssertNoErr(t, res.Err) } // Verifies that it is possible to see the access for a share type func TestShowAccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockShowAccessResponse(t) + MockShowAccessResponse(t, fakeServer) expected := []sharetypes.ShareTypeAccess{ { @@ -181,7 +184,7 @@ func TestShowAccess(t *testing.T) { }, } - shareType, err := sharetypes.ShowAccess(context.TODO(), client.ServiceClient(), "shareTypeID").Extract() + shareType, err := sharetypes.ShowAccess(context.TODO(), client.ServiceClient(fakeServer), "shareTypeID").Extract() th.AssertNoErr(t, err) th.CheckDeepEquals(t, expected, shareType) @@ -189,30 +192,30 @@ func TestShowAccess(t *testing.T) { // Verifies that an access can be added to a share type func TestAddAccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockAddAccessResponse(t) + MockAddAccessResponse(t, fakeServer) options := &sharetypes.AccessOpts{ Project: "e1284adea3ee4d2482af5ed214f3ad90", } - err := sharetypes.AddAccess(context.TODO(), client.ServiceClient(), "shareTypeID", options).ExtractErr() + err := sharetypes.AddAccess(context.TODO(), client.ServiceClient(fakeServer), "shareTypeID", options).ExtractErr() th.AssertNoErr(t, err) } // Verifies that an access can be removed from a share type func TestRemoveAccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockRemoveAccessResponse(t) + MockRemoveAccessResponse(t, fakeServer) options := &sharetypes.AccessOpts{ Project: "e1284adea3ee4d2482af5ed214f3ad90", } - err := sharetypes.RemoveAccess(context.TODO(), client.ServiceClient(), "shareTypeID", options).ExtractErr() + err := sharetypes.RemoveAccess(context.TODO(), client.ServiceClient(fakeServer), "shareTypeID", options).ExtractErr() th.AssertNoErr(t, err) } diff --git a/openstack/sharedfilesystems/v2/snapshots/requests.go b/openstack/sharedfilesystems/v2/snapshots/requests.go index db043f1131..7b61dc4260 100644 --- a/openstack/sharedfilesystems/v2/snapshots/requests.go +++ b/openstack/sharedfilesystems/v2/snapshots/requests.go @@ -110,7 +110,7 @@ func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) paginat return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { p := SnapshotPage{pagination.MarkerPageBase{PageResult: r}} - p.MarkerPageBase.Owner = p + p.Owner = p return p }) } diff --git a/openstack/sharedfilesystems/v2/snapshots/results.go b/openstack/sharedfilesystems/v2/snapshots/results.go index 9cd1b65236..ebf4f4addf 100644 --- a/openstack/sharedfilesystems/v2/snapshots/results.go +++ b/openstack/sharedfilesystems/v2/snapshots/results.go @@ -81,7 +81,7 @@ type SnapshotPage struct { } // NextPageURL generates the URL for the page of results after this one. -func (r SnapshotPage) NextPageURL() (string, error) { +func (r SnapshotPage) NextPageURL(endpointURL string) (string, error) { currentURL := r.URL mark, err := r.Owner.LastMarker() if err != nil { @@ -107,7 +107,7 @@ func (r SnapshotPage) LastMarker() (string, error) { return invalidMarker, nil } - u, err := url.Parse(r.URL.String()) + u, err := url.Parse(r.String()) if err != nil { return invalidMarker, err } diff --git a/openstack/sharedfilesystems/v2/snapshots/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/snapshots/testing/fixtures_test.go index e34d1081a4..dc18cf7c91 100644 --- a/openstack/sharedfilesystems/v2/snapshots/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/snapshots/testing/fixtures_test.go @@ -6,7 +6,7 @@ import ( "testing" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) const ( @@ -50,24 +50,24 @@ var createResponse = `{ }` // MockCreateResponse creates a mock response -func MockCreateResponse(t *testing.T) { - th.Mux.HandleFunc(snapshotEndpoint, func(w http.ResponseWriter, r *http.Request) { +func MockCreateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(snapshotEndpoint, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, createRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, createResponse) + fmt.Fprint(w, createResponse) }) } // MockDeleteResponse creates a mock delete response -func MockDeleteResponse(t *testing.T) { - th.Mux.HandleFunc(snapshotEndpoint+"/"+snapshotID, func(w http.ResponseWriter, r *http.Request) { +func MockDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(snapshotEndpoint+"/"+snapshotID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) }) } @@ -105,16 +105,16 @@ var updateResponse = `{ } }` -func MockUpdateResponse(t *testing.T) { - th.Mux.HandleFunc(snapshotEndpoint+"/"+snapshotID, func(w http.ResponseWriter, r *http.Request) { +func MockUpdateResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(snapshotEndpoint+"/"+snapshotID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, updateRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, updateResponse) + fmt.Fprint(w, updateResponse) }) } @@ -145,12 +145,12 @@ var getResponse = `{ }` // MockGetResponse creates a mock get response -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc(snapshotEndpoint+"/"+snapshotID, func(w http.ResponseWriter, r *http.Request) { +func MockGetResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(snapshotEndpoint+"/"+snapshotID, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, getResponse) + fmt.Fprint(w, getResponse) }) } @@ -185,10 +185,10 @@ var listDetailResponse = `{ var listDetailEmptyResponse = `{"snapshots": []}` // MockListDetailResponse creates a mock detailed-list response -func MockListDetailResponse(t *testing.T) { - th.Mux.HandleFunc(snapshotEndpoint+"/detail", func(w http.ResponseWriter, r *http.Request) { +func MockListDetailResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(snapshotEndpoint+"/detail", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -214,10 +214,10 @@ var resetStatusRequest = `{ }` // MockResetStatusResponse creates a mock reset status snapshot response -func MockResetStatusResponse(t *testing.T) { - th.Mux.HandleFunc(snapshotEndpoint+"/"+snapshotID+"/action", func(w http.ResponseWriter, r *http.Request) { +func MockResetStatusResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(snapshotEndpoint+"/"+snapshotID+"/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, resetStatusRequest) @@ -231,10 +231,10 @@ var forceDeleteRequest = `{ }` // MockForceDeleteResponse creates a mock force delete snapshot response -func MockForceDeleteResponse(t *testing.T) { - th.Mux.HandleFunc(snapshotEndpoint+"/"+snapshotID+"/action", func(w http.ResponseWriter, r *http.Request) { +func MockForceDeleteResponse(t *testing.T, fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc(snapshotEndpoint+"/"+snapshotID+"/action", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, forceDeleteRequest) diff --git a/openstack/sharedfilesystems/v2/snapshots/testing/request_test.go b/openstack/sharedfilesystems/v2/snapshots/testing/request_test.go index 759ad0d78d..14700297e2 100644 --- a/openstack/sharedfilesystems/v2/snapshots/testing/request_test.go +++ b/openstack/sharedfilesystems/v2/snapshots/testing/request_test.go @@ -11,27 +11,27 @@ import ( ) func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockCreateResponse(t) + MockCreateResponse(t, fakeServer) options := &snapshots.CreateOpts{ShareID: shareID, Name: "test snapshot", Description: "test description"} - n, err := snapshots.Create(context.TODO(), client.ServiceClient(), options).Extract() + n, err := snapshots.Create(context.TODO(), client.ServiceClient(fakeServer), options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.Name, "test snapshot") - th.AssertEquals(t, n.Description, "test description") - th.AssertEquals(t, n.ShareProto, "NFS") - th.AssertEquals(t, n.ShareSize, 1) - th.AssertEquals(t, n.Size, 1) + th.AssertEquals(t, "test snapshot", n.Name) + th.AssertEquals(t, "test description", n.Description) + th.AssertEquals(t, "NFS", n.ShareProto) + th.AssertEquals(t, 1, n.ShareSize) + th.AssertEquals(t, 1, n.Size) } func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockUpdateResponse(t) + MockUpdateResponse(t, fakeServer) name := "my_new_test_snapshot" description := "" @@ -39,32 +39,32 @@ func TestUpdate(t *testing.T) { DisplayName: &name, DisplayDescription: &description, } - n, err := snapshots.Update(context.TODO(), client.ServiceClient(), snapshotID, options).Extract() + n, err := snapshots.Update(context.TODO(), client.ServiceClient(fakeServer), snapshotID, options).Extract() th.AssertNoErr(t, err) - th.AssertEquals(t, n.Name, "my_new_test_snapshot") - th.AssertEquals(t, n.Description, "") + th.AssertEquals(t, "my_new_test_snapshot", n.Name) + th.AssertEquals(t, "", n.Description) } func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockDeleteResponse(t) + MockDeleteResponse(t, fakeServer) - result := snapshots.Delete(context.TODO(), client.ServiceClient(), snapshotID) + result := snapshots.Delete(context.TODO(), client.ServiceClient(fakeServer), snapshotID) th.AssertNoErr(t, result.Err) } func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockGetResponse(t) + MockGetResponse(t, fakeServer) - s, err := snapshots.Get(context.TODO(), client.ServiceClient(), snapshotID).Extract() + s, err := snapshots.Get(context.TODO(), client.ServiceClient(fakeServer), snapshotID).Extract() th.AssertNoErr(t, err) - th.AssertDeepEquals(t, s, &snapshots.Snapshot{ + th.AssertDeepEquals(t, &snapshots.Snapshot{ ID: snapshotID, Name: "new_app_snapshot", Description: "", @@ -85,23 +85,23 @@ func TestGet(t *testing.T) { "rel": "bookmark", }, }, - }) + }, s) } func TestListDetail(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockListDetailResponse(t) + MockListDetailResponse(t, fakeServer) - allPages, err := snapshots.ListDetail(client.ServiceClient(), &snapshots.ListOpts{}).AllPages(context.TODO()) + allPages, err := snapshots.ListDetail(client.ServiceClient(fakeServer), &snapshots.ListOpts{}).AllPages(context.TODO()) th.AssertNoErr(t, err) actual, err := snapshots.ExtractSnapshots(allPages) th.AssertNoErr(t, err) - th.AssertDeepEquals(t, actual, []snapshots.Snapshot{ + th.AssertDeepEquals(t, []snapshots.Snapshot{ { ID: snapshotID, Name: "new_app_snapshot", @@ -124,28 +124,28 @@ func TestListDetail(t *testing.T) { }, }, }, - }) + }, actual) } func TestResetStatusSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockResetStatusResponse(t) + MockResetStatusResponse(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) err := snapshots.ResetStatus(context.TODO(), c, snapshotID, &snapshots.ResetStatusOpts{Status: "error"}).ExtractErr() th.AssertNoErr(t, err) } func TestForceDeleteSuccess(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - MockForceDeleteResponse(t) + MockForceDeleteResponse(t, fakeServer) - c := client.ServiceClient() + c := client.ServiceClient(fakeServer) err := snapshots.ForceDelete(context.TODO(), c, snapshotID).ExtractErr() th.AssertNoErr(t, err) diff --git a/openstack/testing/client_test.go b/openstack/testing/client_test.go index 2c158dbfc3..e0a5e4cc01 100644 --- a/openstack/testing/client_test.go +++ b/openstack/testing/client_test.go @@ -14,10 +14,10 @@ import ( const ID = "0123456789" func TestAuthenticatedClientV3(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, ` { "versions": { @@ -39,14 +39,14 @@ func TestAuthenticatedClientV3(t *testing.T) { ] } } - `, th.Endpoint()+"v3/", th.Endpoint()+"v2.0/") + `, fakeServer.Endpoint()+"v3/", fakeServer.Endpoint()+"v2.0/") }) - th.Mux.HandleFunc("/v3/auth/tokens", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v3/auth/tokens", func(w http.ResponseWriter, r *http.Request) { w.Header().Add("X-Subject-Token", ID) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, `{ "token": { "expires_at": "2013-02-02T18:30:59.000000Z" } }`) + fmt.Fprint(w, `{ "token": { "expires_at": "2013-02-02T18:30:59.000000Z" } }`) }) options := gophercloud.AuthOptions{ @@ -54,7 +54,7 @@ func TestAuthenticatedClientV3(t *testing.T) { Password: "secret", DomainName: "default", TenantName: "project", - IdentityEndpoint: th.Endpoint(), + IdentityEndpoint: fakeServer.Endpoint(), } client, err := openstack.AuthenticatedClient(context.TODO(), options) th.AssertNoErr(t, err) @@ -62,10 +62,10 @@ func TestAuthenticatedClientV3(t *testing.T) { } func TestAuthenticatedClientV2(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, ` { "versions": { @@ -87,11 +87,11 @@ func TestAuthenticatedClientV2(t *testing.T) { ] } } - `, th.Endpoint()+"v3/", th.Endpoint()+"v2.0/") + `, fakeServer.Endpoint()+"v3/", fakeServer.Endpoint()+"v2.0/") }) - th.Mux.HandleFunc("/v2.0/tokens", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, ` + fakeServer.Mux.HandleFunc("/v2.0/tokens", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, ` { "access": { "token": { @@ -157,7 +157,7 @@ func TestAuthenticatedClientV2(t *testing.T) { options := gophercloud.AuthOptions{ Username: "me", Password: "secret", - IdentityEndpoint: th.Endpoint(), + IdentityEndpoint: fakeServer.Endpoint(), } client, err := openstack.AuthenticatedClient(context.TODO(), options) th.AssertNoErr(t, err) @@ -165,10 +165,10 @@ func TestAuthenticatedClientV2(t *testing.T) { } func TestIdentityAdminV3Client(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, ` { "versions": { @@ -190,14 +190,14 @@ func TestIdentityAdminV3Client(t *testing.T) { ] } } - `, th.Endpoint()+"v3/", th.Endpoint()+"v2.0/") + `, fakeServer.Endpoint()+"v3/", fakeServer.Endpoint()+"v2.0/") }) - th.Mux.HandleFunc("/v3/auth/tokens", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/v3/auth/tokens", func(w http.ResponseWriter, r *http.Request) { w.Header().Add("X-Subject-Token", ID) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "token": { "audit_ids": ["VcxU2JYqT8OzfUVvrjEITQ", "qNUTIJntTzO1-XUk5STybw"], @@ -282,11 +282,11 @@ func TestIdentityAdminV3Client(t *testing.T) { Username: "me", Password: "secret", DomainID: "12345", - IdentityEndpoint: th.Endpoint(), + IdentityEndpoint: fakeServer.Endpoint(), } pc, err := openstack.AuthenticatedClient(context.TODO(), options) th.AssertNoErr(t, err) - sc, err := openstack.NewIdentityV3(pc, gophercloud.EndpointOpts{ + sc, err := openstack.NewIdentityV3(context.TODO(), pc, gophercloud.EndpointOpts{ Availability: gophercloud.AvailabilityAdmin, }) th.AssertNoErr(t, err) diff --git a/openstack/utils/base_endpoint.go b/openstack/utils/base_endpoint.go index 40080f7af2..f219c0bf4d 100644 --- a/openstack/utils/base_endpoint.go +++ b/openstack/utils/base_endpoint.go @@ -6,9 +6,7 @@ import ( "strings" ) -// BaseEndpoint will return a URL without the /vX.Y -// portion of the URL. -func BaseEndpoint(endpoint string) (string, error) { +func parseEndpoint(endpoint string, includeVersion bool) (string, error) { u, err := url.Parse(endpoint) if err != nil { return "", err @@ -21,8 +19,23 @@ func BaseEndpoint(endpoint string) (string, error) { if version := versionRe.FindString(path); version != "" { versionIndex := strings.Index(path, version) + if includeVersion { + versionIndex += len(version) + } u.Path = path[:versionIndex] } return u.String(), nil } + +// BaseEndpoint will return a URL without the /vX.Y +// portion of the URL. +func BaseEndpoint(endpoint string) (string, error) { + return parseEndpoint(endpoint, false) +} + +// BaseVersionedEndpoint will return a URL with the /vX.Y portion of the URL, +// if present, but without a project ID or similar +func BaseVersionedEndpoint(endpoint string) (string, error) { + return parseEndpoint(endpoint, true) +} diff --git a/openstack/utils/choose_version.go b/openstack/utils/choose_version.go index 6c720e57ef..b9298ad844 100644 --- a/openstack/utils/choose_version.go +++ b/openstack/utils/choose_version.go @@ -3,7 +3,6 @@ package utils import ( "context" "fmt" - "strconv" "strings" "github.com/gophercloud/gophercloud/v2" @@ -29,6 +28,7 @@ var goodStatus = map[string]bool{ // It returns the highest-Priority Version, OR exact match with client endpoint, // among the alternatives that are provided, as well as its corresponding endpoint. func ChooseVersion(ctx context.Context, client *gophercloud.ProviderClient, recognized []*Version) (*Version, string, error) { + // TODO(stephenfin): This could be removed since we can accomplish this with GetServiceVersions now. type linkResp struct { Href string `json:"href"` Rel string `json:"rel"` @@ -89,7 +89,7 @@ func ChooseVersion(ctx context.Context, client *gophercloud.ProviderClient, reco // Prefer a version that exactly matches the provided endpoint. if href == identityEndpoint { if href == "" { - return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", value.ID, client.IdentityBase) + return nil, "", fmt.Errorf("endpoint missing in version %s response from %s", value.ID, client.IdentityBase) } return version, href, nil } @@ -106,131 +106,11 @@ func ChooseVersion(ctx context.Context, client *gophercloud.ProviderClient, reco } if highest == nil { - return nil, "", fmt.Errorf("No supported version available from endpoint %s", client.IdentityBase) + return nil, "", fmt.Errorf("no supported version available from endpoint %s", client.IdentityBase) } if endpoint == "" { - return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", highest.ID, client.IdentityBase) + return nil, "", fmt.Errorf("endpoint missing in version %s response from %s", highest.ID, client.IdentityBase) } return highest, endpoint, nil } - -type SupportedMicroversions struct { - MaxMajor int - MaxMinor int - MinMajor int - MinMinor int -} - -// GetSupportedMicroversions returns the minimum and maximum microversion that is supported by the ServiceClient Endpoint. -func GetSupportedMicroversions(ctx context.Context, client *gophercloud.ServiceClient) (SupportedMicroversions, error) { - type valueResp struct { - ID string `json:"id"` - Status string `json:"status"` - Version string `json:"version"` - MinVersion string `json:"min_version"` - } - - type response struct { - Version valueResp `json:"version"` - Versions []valueResp `json:"versions"` - } - var minVersion, maxVersion string - var supportedMicroversions SupportedMicroversions - var resp response - _, err := client.Get(ctx, client.Endpoint, &resp, &gophercloud.RequestOpts{ - OkCodes: []int{200, 300}, - }) - - if err != nil { - return supportedMicroversions, err - } - - if len(resp.Versions) > 0 { - // We are dealing with an unversioned endpoint - // We only handle the case when there is exactly one, and assume it is the correct one - if len(resp.Versions) > 1 { - return supportedMicroversions, fmt.Errorf("unversioned endpoint with multiple alternatives not supported") - } - minVersion = resp.Versions[0].MinVersion - maxVersion = resp.Versions[0].Version - } else { - minVersion = resp.Version.MinVersion - maxVersion = resp.Version.Version - } - - // Return early if the endpoint does not support microversions - if minVersion == "" && maxVersion == "" { - return supportedMicroversions, fmt.Errorf("microversions not supported by ServiceClient Endpoint") - } - - supportedMicroversions.MinMajor, supportedMicroversions.MinMinor, err = ParseMicroversion(minVersion) - if err != nil { - return supportedMicroversions, err - } - - supportedMicroversions.MaxMajor, supportedMicroversions.MaxMinor, err = ParseMicroversion(maxVersion) - if err != nil { - return supportedMicroversions, err - } - - return supportedMicroversions, nil -} - -// RequireMicroversion checks that the required microversion is supported and -// returns a ServiceClient with the microversion set. -func RequireMicroversion(ctx context.Context, client gophercloud.ServiceClient, required string) (gophercloud.ServiceClient, error) { - supportedMicroversions, err := GetSupportedMicroversions(ctx, &client) - if err != nil { - return client, fmt.Errorf("unable to determine supported microversions: %w", err) - } - supported, err := supportedMicroversions.IsSupported(required) - if err != nil { - return client, err - } - if !supported { - return client, fmt.Errorf("microversion %s not supported. Supported versions: %v", required, supportedMicroversions) - } - client.Microversion = required - return client, nil -} - -// IsSupported checks if a microversion falls in the supported interval. -// It returns true if the version is within the interval and false otherwise. -func (supported SupportedMicroversions) IsSupported(version string) (bool, error) { - // Parse the version X.Y into X and Y integers that are easier to compare. - vMajor, vMinor, err := ParseMicroversion(version) - if err != nil { - return false, err - } - - // Check that the major version number is supported. - if (vMajor < supported.MinMajor) || (vMajor > supported.MaxMajor) { - return false, nil - } - - // Check that the minor version number is supported - if (vMinor <= supported.MaxMinor) && (vMinor >= supported.MinMinor) { - return true, nil - } - - return false, nil -} - -// ParseMicroversion parses the version major.minor into separate integers major and minor. -// For example, "2.53" becomes 2 and 53. -func ParseMicroversion(version string) (major int, minor int, err error) { - parts := strings.Split(version, ".") - if len(parts) != 2 { - return 0, 0, fmt.Errorf("invalid microversion format: %q", version) - } - major, err = strconv.Atoi(parts[0]) - if err != nil { - return 0, 0, err - } - minor, err = strconv.Atoi(parts[1]) - if err != nil { - return 0, 0, err - } - return major, minor, nil -} diff --git a/openstack/utils/discovery.go b/openstack/utils/discovery.go new file mode 100644 index 0000000000..86d1d14c34 --- /dev/null +++ b/openstack/utils/discovery.go @@ -0,0 +1,372 @@ +package utils + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "regexp" + "sort" + "strconv" + "strings" + + "github.com/gophercloud/gophercloud/v2" +) + +type Status string + +const ( + StatusCurrent Status = "CURRENT" + StatusSupported Status = "SUPPORTED" + StatusDeprecated Status = "DEPRECATED" + StatusExperimental Status = "EXPERIMENTAL" + StatusUnknown Status = "" +) + +// SupportedVersion stores a normalized form of the API version data. It handles APIs that +// support microversions as well as those that do not. +type SupportedVersion struct { + // Major is the major version number of the API + Major int + // Minor is the minor version number of the API + Minor int + // Status is the status of the API + Status Status + SupportedMicroversions +} + +// SupportedMicroversions stores a normalized form of the maximum and minimum API microversions +// supported by a given service. +type SupportedMicroversions struct { + // MaxMajor is the major version number of the maximum supported API microversion + MaxMajor int + // MaxMinor is the minor version number of the maximum supported API microversion + MaxMinor int + // MinMajor is the major version number of the minimum supported API microversion + MinMajor int + // MinMinor is the minor version number of the minimum supported API microversion + MinMinor int +} + +type version struct { + ID string `json:"id"` + Status string `json:"status"` + Version string `json:"version,omitempty"` + MaxVersion string `json:"max_version,omitempty"` + MinVersion string `json:"min_version"` +} + +type response struct { + Versions []version `json:"-"` +} + +func (r *response) UnmarshalJSON(in []byte) error { + // intermediateResponse is an intermediate struct that allows us to offload the difference + // between a single version document and a multi-version document to the json parser and + // only focus on differences in the latter + type intermediateResponse struct { + ID string `json:"id"` + Version *version `json:"version"` + Versions *json.RawMessage `json:"versions"` + } + + data := intermediateResponse{} + if err := json.Unmarshal(in, &data); err != nil { + return err + } + + // case 1: we have a single enveloped version object + // + // this is the approach used by Manila for single version responses + if data.Version != nil { + r.Versions = []version{*data.Version} + return nil + } + + // case 2: we have an singly enveloped array of version objects + // + // this is the approach used by nova, cinder and glance, among others, for multi-version + // responses + if data.Versions != nil { + var versionArr []version + if err := json.Unmarshal(*data.Versions, &versionArr); err == nil { + r.Versions = versionArr + return nil + } + } + + // case 3: we have an doubly enveloped array of version objects + // + // this is the approach used by keystone and barbican, among others, for multi-version + // responses + if data.Versions != nil { + type values struct { + Values []version `json:"values"` + } + + var valuesObj values + if err := json.Unmarshal(*data.Versions, &valuesObj); err == nil { + r.Versions = valuesObj.Values + return nil + } + } + + // case 4: we have a single unenveloped version object + // + // this is the approach used by most other services for single version responses + if data.ID != "" { + r.Versions = []version{{ID: data.ID}} + return nil + } + + return fmt.Errorf("failed to unmarshal versions document: %s", in) +} + +func extractVersion(endpointURL string) (int, int, error) { + u, err := url.Parse(endpointURL) + if err != nil { + return 0, 0, err + } + + parts := strings.Split(strings.TrimRight(u.Path, "/"), "/") + if len(parts) == 0 { + return 0, 0, fmt.Errorf("expected path with version, got: %s", u.Path) + } + + // first, check the nth path element for a version string + if majorVersion, minorVersion, err := ParseVersion(parts[len(parts)-1]); err == nil { + return majorVersion, minorVersion, nil + } + + // if there are no more parts, quit + if len(parts) == 1 { + // we don't return the error message directly since it might be misleading: at this point + // we might have a *malformed* version identifier rather than *no* version identifier + return 0, 0, fmt.Errorf("failed to infer version from path: %s", u.Path) + } + + // the guidelines say we should use the currently scoped project_id from the token, but we + // don't necessarily have a token yet so we speculatively look at the (n-1)th path element + // (but only that) just as keystoneauth does + // + // https://github.com/openstack/keystoneauth/blob/master/keystoneauth1/discover.py#L1534-L1545 + if majorVersion, minorVersion, err := ParseVersion(parts[len(parts)-1]); err == nil { + return majorVersion, minorVersion, err + } + + // once again, we don't return the error message directly + return 0, 0, fmt.Errorf("failed to infer version from path: %s", u.Path) +} + +// GetServiceVersions returns the versions supported by the ServiceClient Endpoint. +// If the endpoint resolves to an unversioned discovery API, this should return one or more supported versions. +// If the endpoint resolves to a versioned discovery API, this should return exactly one supported version. +func GetServiceVersions(ctx context.Context, client *gophercloud.ProviderClient, endpointURL string, discoverVersions bool) ([]SupportedVersion, error) { + var supportedVersions []SupportedVersion + var endpointVersion *SupportedVersion + + if majorVersion, minorVersion, err := extractVersion(endpointURL); err == nil { + endpointVersion = &SupportedVersion{Major: majorVersion, Minor: minorVersion} + if !discoverVersions { + return append(supportedVersions, *endpointVersion), nil + } + } + + var resp response + _, err := client.Request(ctx, "GET", endpointURL, &gophercloud.RequestOpts{ + JSONResponse: &resp, + OkCodes: []int{200, 300}, + }) + if err != nil { + // we weren't able to find a discovery document but we have version information from the URL + if endpointVersion != nil { + return append(supportedVersions, *endpointVersion), nil + } + return supportedVersions, err + } + + versions := resp.Versions + + for _, version := range versions { + majorVersion, minorVersion, err := ParseVersion(version.ID) + if err != nil { + return supportedVersions, err + } + + status, err := ParseStatus(version.Status) + if err != nil { + return supportedVersions, err + } + + supportedVersion := SupportedVersion{ + Major: majorVersion, + Minor: minorVersion, + Status: status, + } + + // Only normalize the microversions if there are microversions to normalize + if (version.Version != "" || version.MaxVersion != "") && version.MinVersion != "" { + supportedVersion.MinMajor, supportedVersion.MinMinor, err = ParseMicroversion(version.MinVersion) + if err != nil { + return supportedVersions, err + } + + maxVersion := version.Version + if maxVersion == "" { + maxVersion = version.MaxVersion + } + supportedVersion.MaxMajor, supportedVersion.MaxMinor, err = ParseMicroversion(maxVersion) + if err != nil { + return supportedVersions, err + } + } + + supportedVersions = append(supportedVersions, supportedVersion) + } + + sort.Slice(supportedVersions, func(i, j int) bool { + return supportedVersions[i].Major > supportedVersions[j].Major || (supportedVersions[i].Major == supportedVersions[j].Major && + supportedVersions[i].Minor > supportedVersions[j].Minor) + }) + + return supportedVersions, nil +} + +// GetSupportedMicroversions returns the minimum and maximum microversion that is supported by the ServiceClient Endpoint. +func GetSupportedMicroversions(ctx context.Context, client *gophercloud.ServiceClient) (SupportedMicroversions, error) { + var supportedMicroversions SupportedMicroversions + + supportedVersions, err := GetServiceVersions(ctx, client.ProviderClient, client.Endpoint, true) + if err != nil { + return supportedMicroversions, err + } + + // If there are multiple versions then we were handed an unversioned endpoint. These don't + // provide microversion information, so we need to fail. Likewise, if there are no versions + // then something has gone wrong and we also need to fail. + if len(supportedVersions) > 1 { + return supportedMicroversions, fmt.Errorf("unversioned endpoint with multiple alternatives not supported") + } else if len(supportedVersions) == 0 { + return supportedMicroversions, fmt.Errorf("microversions not supported by endpoint") + } + + supportedMicroversions = supportedVersions[0].SupportedMicroversions + + if supportedMicroversions.MaxMajor == 0 && + supportedMicroversions.MaxMinor == 0 && + supportedMicroversions.MinMajor == 0 && + supportedMicroversions.MinMinor == 0 { + return supportedMicroversions, fmt.Errorf("microversions not supported by endpoint") + } + + return supportedMicroversions, err +} + +// RequireMicroversion checks that the required microversion is supported and +// returns a ServiceClient with the microversion set. +func RequireMicroversion(ctx context.Context, client gophercloud.ServiceClient, required string) (gophercloud.ServiceClient, error) { + supportedMicroversions, err := GetSupportedMicroversions(ctx, &client) + if err != nil { + return client, fmt.Errorf("unable to determine supported microversions: %w", err) + } + supported, err := supportedMicroversions.IsSupported(required) + if err != nil { + return client, err + } + if !supported { + return client, fmt.Errorf("microversion %s not supported. Supported versions: %v", required, supportedMicroversions) + } + client.Microversion = required + return client, nil +} + +// IsSupported checks if a microversion falls in the supported interval. +// It returns true if the version is within the interval and false otherwise. +func (supported SupportedMicroversions) IsSupported(version string) (bool, error) { + // Parse the version X.Y into X and Y integers that are easier to compare. + vMajor, vMinor, err := ParseMicroversion(version) + if err != nil { + return false, err + } + + // Check that the major version number is supported. + if (vMajor < supported.MinMajor) || (vMajor > supported.MaxMajor) { + return false, nil + } + + // Check that the minor version number is supported + if (vMinor <= supported.MaxMinor) && (vMinor >= supported.MinMinor) { + return true, nil + } + + return false, nil +} + +// ParseVersion parsed the version strings v{MAJOR} and v{MAJOR}.{MINOR} into separate integers +// major and minor. +// For example, "v2.1" becomes 2 and 1, "v3" becomes 3 and 0, and "1" becomes 1 and 0. +func ParseVersion(version string) (major, minor int, err error) { + if version == "" { + return 0, 0, fmt.Errorf("empty version provided") + } + + // We use the regex indicated by the version discovery guidelines. + // + // https://specs.openstack.org/openstack/api-sig/guidelines/consuming-catalog/version-discovery.html#inferring-version + // + // However, we diverge slightly since not all services include the 'v' prefix (glares at zaqar) + versionRe := regexp.MustCompile(`^v?(?P[0-9]+)(\.(?P[0-9]+))?$`) + + match := versionRe.FindStringSubmatch(version) + if len(match) == 0 { + return 0, 0, fmt.Errorf("invalid format: %q", version) + } + + major, err = strconv.Atoi(match[versionRe.SubexpIndex("major")]) + if err != nil { + return 0, 0, err + } + + minor = 0 + if match[versionRe.SubexpIndex("minor")] != "" { + minor, err = strconv.Atoi(match[versionRe.SubexpIndex("minor")]) + if err != nil { + return 0, 0, err + } + } + + return major, minor, nil +} + +// ParseMicroversion parses the version major.minor into separate integers major and minor. +// For example, "2.53" becomes 2 and 53. +func ParseMicroversion(version string) (major int, minor int, err error) { + parts := strings.Split(version, ".") + if len(parts) != 2 { + return 0, 0, fmt.Errorf("invalid microversion format: %q", version) + } + major, err = strconv.Atoi(parts[0]) + if err != nil { + return 0, 0, err + } + minor, err = strconv.Atoi(parts[1]) + if err != nil { + return 0, 0, err + } + return major, minor, nil +} + +func ParseStatus(status string) (Status, error) { + switch strings.ToUpper(status) { + case "CURRENT", "STABLE": // keystone uses STABLE instead of CURRENT + return StatusCurrent, nil + case "SUPPORTED": + return StatusSupported, nil + case "DEPRECATED": + return StatusDeprecated, nil + case "": + return StatusUnknown, nil + default: + return StatusUnknown, fmt.Errorf("invalid status: %q", status) + } +} diff --git a/openstack/utils/testing/choose_version_test.go b/openstack/utils/testing/choose_version_test.go index 36d8ac37ec..860984f0a5 100644 --- a/openstack/utils/testing/choose_version_test.go +++ b/openstack/utils/testing/choose_version_test.go @@ -2,197 +2,23 @@ package testing import ( "context" - "fmt" - "net/http" - "strings" "testing" "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/openstack/utils" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" ) -func setupVersionHandler() { - testhelper.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, ` - { - "versions": { - "values": [ - { - "status": "stable", - "id": "v3.0", - "links": [ - { "href": "%s/v3.0", "rel": "self" } - ] - }, - { - "status": "stable", - "id": "v2.0", - "links": [ - { "href": "%s/v2.0", "rel": "self" } - ] - } - ] - } - } - `, testhelper.Server.URL, testhelper.Server.URL) - }) - // Compute v2.1 API - testhelper.Mux.HandleFunc("/compute/v2.1/", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, ` - { - "version": { - "id": "v2.1", - "status": "CURRENT", - "version": "2.90", - "min_version": "2.1", - "updated": "2013-07-23T11:33:21Z", - "links": [ - { - "rel": "self", - "href": "%s/compute/v2.1/" - }, - { - "rel": "describedby", - "type": "text/html", - "href": "http://docs.openstack.org/" - } - ], - "media-types": [ - { - "base": "application/json", - "type": "application/vnd.openstack.compute+json;version=2.1" - } - ] - } - } - `, testhelper.Server.URL) - }) - // Compute v2 API - testhelper.Mux.HandleFunc("/compute/v2/", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, ` - { - "version": { - "id": "v2.0", - "status": "SUPPORTED", - "version": "", - "min_version": "", - "updated": "2011-01-21T11:33:21Z", - "links": [ - { - "rel": "self", - "href": "%s/compute/v2/" - }, - { - "rel": "describedby", - "type": "text/html", - "href": "http://docs.openstack.org/" - } - ], - "media-types": [ - { - "base": "application/json", - "type": "application/vnd.openstack.compute+json;version=2" - } - ] - } - } - `, testhelper.Server.URL) - }) - // Ironic API - testhelper.Mux.HandleFunc("/ironic/v1/", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, ` - { - "name": "OpenStack Ironic API", - "description": "Ironic is an OpenStack project which enables the provision and management of baremetal machines.", - "default_version": { - "id": "v1", - "links": [ - { - "href": "%s/ironic/v1/", - "rel": "self" - } - ], - "status": "CURRENT", - "min_version": "1.1", - "version": "1.87" - }, - "versions": [ - { - "id": "v1", - "links": [ - { - "href": "%s/ironic/v1/", - "rel": "self" - } - ], - "status": "CURRENT", - "min_version": "1.1", - "version": "1.87" - } - ] - } - `, testhelper.Server.URL, testhelper.Server.URL) - }) - // Ironic multi-version - testhelper.Mux.HandleFunc("/ironic/v1.2/", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, ` - { - "name": "OpenStack Ironic API", - "description": "Ironic is an OpenStack project which enables the provision and management of baremetal machines.", - "default_version": { - "id": "v1", - "links": [ - { - "href": "%s/ironic/v1/", - "rel": "self" - } - ], - "status": "CURRENT", - "min_version": "1.1", - "version": "1.87" - }, - "versions": [ - { - "id": "v1", - "links": [ - { - "href": "%s/ironic/v1/", - "rel": "self" - } - ], - "status": "CURRENT", - "min_version": "1.1", - "version": "1.87" - }, - { - "id": "v1.2", - "links": [ - { - "href": "%s/ironic/v1/", - "rel": "self" - } - ], - "status": "CURRENT", - "min_version": "1.2", - "version": "1.90" - } - ] - } - `, testhelper.Server.URL, testhelper.Server.URL, testhelper.Server.URL) - }) -} - func TestChooseVersion(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - setupVersionHandler() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + setupIdentityVersionHandler(fakeServer) v2 := &utils.Version{ID: "v2.0", Priority: 2, Suffix: "blarg"} v3 := &utils.Version{ID: "v3.0", Priority: 3, Suffix: "hargl"} c := &gophercloud.ProviderClient{ - IdentityBase: testhelper.Endpoint(), + IdentityBase: fakeServer.Endpoint(), IdentityEndpoint: "", } v, endpoint, err := utils.ChooseVersion(context.TODO(), c, []*utils.Version{v2, v3}) @@ -205,23 +31,23 @@ func TestChooseVersion(t *testing.T) { t.Errorf("Expected %#v to win, but %#v did instead", v3, v) } - expected := testhelper.Endpoint() + "v3.0/" + expected := fakeServer.Endpoint() + "v3.0/" if endpoint != expected { t.Errorf("Expected endpoint [%s], but was [%s] instead", expected, endpoint) } } func TestChooseVersionOpinionatedLink(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - setupVersionHandler() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + setupIdentityVersionHandler(fakeServer) v2 := &utils.Version{ID: "v2.0", Priority: 2, Suffix: "nope"} v3 := &utils.Version{ID: "v3.0", Priority: 3, Suffix: "northis"} c := &gophercloud.ProviderClient{ - IdentityBase: testhelper.Endpoint(), - IdentityEndpoint: testhelper.Endpoint() + "v2.0/", + IdentityBase: fakeServer.Endpoint(), + IdentityEndpoint: fakeServer.Endpoint() + "v2.0/", } v, endpoint, err := utils.ChooseVersion(context.TODO(), c, []*utils.Version{v2, v3}) if err != nil { @@ -232,22 +58,22 @@ func TestChooseVersionOpinionatedLink(t *testing.T) { t.Errorf("Expected %#v to win, but %#v did instead", v2, v) } - expected := testhelper.Endpoint() + "v2.0/" + expected := fakeServer.Endpoint() + "v2.0/" if endpoint != expected { t.Errorf("Expected endpoint [%s], but was [%s] instead", expected, endpoint) } } func TestChooseVersionFromSuffix(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() v2 := &utils.Version{ID: "v2.0", Priority: 2, Suffix: "/v2.0/"} v3 := &utils.Version{ID: "v3.0", Priority: 3, Suffix: "/v3.0/"} c := &gophercloud.ProviderClient{ - IdentityBase: testhelper.Endpoint(), - IdentityEndpoint: testhelper.Endpoint() + "v2.0/", + IdentityBase: fakeServer.Endpoint(), + IdentityEndpoint: fakeServer.Endpoint() + "v2.0/", } v, endpoint, err := utils.ChooseVersion(context.TODO(), c, []*utils.Version{v2, v3}) if err != nil { @@ -258,168 +84,8 @@ func TestChooseVersionFromSuffix(t *testing.T) { t.Errorf("Expected %#v to win, but %#v did instead", v2, v) } - expected := testhelper.Endpoint() + "v2.0/" + expected := fakeServer.Endpoint() + "v2.0/" if endpoint != expected { t.Errorf("Expected endpoint [%s], but was [%s] instead", expected, endpoint) } } - -type getSupportedServiceMicroversions struct { - Endpoint string - ExpectedMax string - ExpectedMin string - ExpectedErr bool -} - -func TestGetSupportedVersions(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - setupVersionHandler() - - tests := []getSupportedServiceMicroversions{ - { - // v2 does not support microversions and returns error - Endpoint: testhelper.Endpoint() + "compute/v2/", - ExpectedMax: "", - ExpectedMin: "", - ExpectedErr: true, - }, - { - Endpoint: testhelper.Endpoint() + "compute/v2.1/", - ExpectedMax: "2.90", - ExpectedMin: "2.1", - ExpectedErr: false, - }, - { - Endpoint: testhelper.Endpoint() + "ironic/v1/", - ExpectedMax: "1.87", - ExpectedMin: "1.1", - ExpectedErr: false, - }, - { - // This endpoint returns multiple versions, which is not supported - Endpoint: testhelper.Endpoint() + "ironic/v1.2/", - ExpectedMax: "not-relevant", - ExpectedMin: "not-relevant", - ExpectedErr: true, - }, - } - - for _, test := range tests { - c := &gophercloud.ProviderClient{ - IdentityBase: testhelper.Endpoint(), - IdentityEndpoint: testhelper.Endpoint() + "v2.0/", - } - - client := &gophercloud.ServiceClient{ - ProviderClient: c, - Endpoint: test.Endpoint, - } - - supported, err := utils.GetSupportedMicroversions(context.TODO(), client) - - if test.ExpectedErr { - if err == nil { - t.Error("Expected error but got none!") - } - // Check for reasonable error message - if !strings.Contains(err.Error(), "not supported") { - t.Error("Expected error to contain 'not supported' but it did not!") - } - // No point parsing and comparing versions after error, so continue to next test case - continue - } else { - if err != nil { - t.Errorf("Expected no error but got %s", err.Error()) - } - } - - min := fmt.Sprintf("%d.%d", supported.MinMajor, supported.MinMinor) - max := fmt.Sprintf("%d.%d", supported.MaxMajor, supported.MaxMinor) - - if (min != test.ExpectedMin) || (max != test.ExpectedMax) { - t.Errorf("Expected min=%s and max=%s but got min=%s and max=%s", test.ExpectedMin, test.ExpectedMax, min, max) - } - } -} - -type microversionSupported struct { - Version string - MinVersion string - MaxVersion string - Supported bool - Error bool -} - -func TestMicroversionSupported(t *testing.T) { - tests := []microversionSupported{ - { - // Checking min version - Version: "2.1", - MinVersion: "2.1", - MaxVersion: "2.90", - Supported: true, - Error: false, - }, - { - // Checking max version - Version: "2.90", - MinVersion: "2.1", - MaxVersion: "2.90", - Supported: true, - Error: false, - }, - { - // Checking too high version - Version: "2.95", - MinVersion: "2.1", - MaxVersion: "2.90", - Supported: false, - Error: false, - }, - { - // Checking too low version - Version: "2.1", - MinVersion: "2.53", - MaxVersion: "2.90", - Supported: false, - Error: false, - }, - { - // Invalid version - Version: "2.1.53", - MinVersion: "2.53", - MaxVersion: "2.90", - Supported: false, - Error: true, - }, - } - - for _, test := range tests { - var err error - var supportedVersions utils.SupportedMicroversions - supportedVersions.MaxMajor, supportedVersions.MaxMinor, err = utils.ParseMicroversion(test.MaxVersion) - if err != nil { - t.Error("Error parsing MaxVersion!") - } - supportedVersions.MinMajor, supportedVersions.MinMinor, err = utils.ParseMicroversion(test.MinVersion) - if err != nil { - t.Error("Error parsing MinVersion!") - } - - supported, err := supportedVersions.IsSupported(test.Version) - if test.Error { - if err == nil { - t.Error("Expected error but got none!") - } - } else { - if err != nil { - t.Errorf("Expected no error but got %s", err.Error()) - } - } - if test.Supported != supported { - t.Errorf("Expected supported=%t to be %t, when version=%s, min=%s and max=%s", - supported, test.Supported, test.Version, test.MinVersion, test.MaxVersion) - } - } -} diff --git a/openstack/utils/testing/discovery_test.go b/openstack/utils/testing/discovery_test.go new file mode 100644 index 0000000000..76cc2aa3eb --- /dev/null +++ b/openstack/utils/testing/discovery_test.go @@ -0,0 +1,490 @@ +package testing + +import ( + "context" + "strings" + "testing" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack/utils" + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func TestGetServiceVersions(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + setupMultiServiceVersionHandler(fakeServer) + + tests := []struct { + name string + endpoint string + discoverVersions bool + expectedVersions []utils.SupportedVersion + expectedErr string + }{ + { + name: "identity unversioned endpoint", + endpoint: fakeServer.Endpoint() + "identity/", + discoverVersions: true, + expectedVersions: []utils.SupportedVersion{ + { + Major: 3, + Minor: 14, + Status: utils.StatusCurrent, + }, + }, + }, + { + name: "identity unversioned endpoint without discovery", + endpoint: fakeServer.Endpoint() + "identity/", + discoverVersions: false, + // we will still run discovery since we can't extract the version from the URL + expectedVersions: []utils.SupportedVersion{ + { + Major: 3, + Minor: 14, + Status: utils.StatusCurrent, + }, + }, + }, + { + name: "identity versioned endpoint", + endpoint: fakeServer.Endpoint() + "identity/v3/", + discoverVersions: true, + expectedVersions: []utils.SupportedVersion{ + { + Major: 3, + Minor: 14, + Status: utils.StatusCurrent, + }, + }, + }, + { + name: "identity versioned endpoint without discovery", + endpoint: fakeServer.Endpoint() + "identity/v3/", + discoverVersions: false, + // we will skip discovery since we can extract a version from the URL + expectedVersions: []utils.SupportedVersion{ + { + Major: 3, + Minor: 0, + Status: utils.StatusUnknown, + }, + }, + }, + { + name: "compute unversioned endpoint", + endpoint: fakeServer.Endpoint() + "compute/", + discoverVersions: true, + expectedVersions: []utils.SupportedVersion{ + { + Major: 2, + Minor: 1, + Status: utils.StatusCurrent, + SupportedMicroversions: utils.SupportedMicroversions{ + MaxMajor: 2, MaxMinor: 90, MinMajor: 2, MinMinor: 1, + }, + }, + { + Major: 2, + Minor: 0, + Status: utils.StatusSupported, + }, + }, + }, + { + name: "compute legacy endpoint", + endpoint: fakeServer.Endpoint() + "compute/v2/", + discoverVersions: true, + expectedVersions: []utils.SupportedVersion{ + { + Major: 2, + Minor: 0, + Status: utils.StatusSupported, + }, + }, + }, + { + name: "compute versioned endpoint", + endpoint: fakeServer.Endpoint() + "compute/v2.1/", + discoverVersions: true, + expectedVersions: []utils.SupportedVersion{ + { + Major: 2, + Minor: 1, + Status: utils.StatusCurrent, + SupportedMicroversions: utils.SupportedMicroversions{ + MaxMajor: 2, MaxMinor: 90, MinMajor: 2, MinMinor: 1, + }, + }, + }, + }, + { + name: "container-infra unversioned endpoint", + endpoint: fakeServer.Endpoint() + "container-infra/", + discoverVersions: true, + expectedVersions: []utils.SupportedVersion{ + { + Major: 1, + Minor: 0, + Status: utils.StatusCurrent, + SupportedMicroversions: utils.SupportedMicroversions{ + MaxMajor: 1, MaxMinor: 11, MinMajor: 1, MinMinor: 1, + }, + }, + }, + }, + { + name: "container-infra versioned endpoint", + endpoint: fakeServer.Endpoint() + "container-infra/v1/", + discoverVersions: true, + expectedVersions: []utils.SupportedVersion{ + { + Major: 1, + Minor: 0, + Status: utils.StatusUnknown, + }, + }, + }, + { + name: "orchestration unversioned endpoint", + endpoint: fakeServer.Endpoint() + "heat-api/", + discoverVersions: true, + expectedVersions: []utils.SupportedVersion{ + { + Major: 1, + Minor: 0, + Status: utils.StatusCurrent, + }, + }, + }, + { + name: "orchestration versioned endpoint", + endpoint: fakeServer.Endpoint() + "heat-api/v1/", + discoverVersions: true, + expectedVersions: []utils.SupportedVersion{ + { + Major: 1, + Minor: 0, + Status: utils.StatusUnknown, + }, + }, + }, + { + name: "workflow unversioned endpoint", + endpoint: fakeServer.Endpoint() + "workflow/", + discoverVersions: true, + expectedVersions: []utils.SupportedVersion{ + { + Major: 2, + Minor: 0, + Status: utils.StatusCurrent, + }, + }, + }, + { + name: "workflow versioned endpoint", + endpoint: fakeServer.Endpoint() + "workflow/v2/", + discoverVersions: true, + expectedVersions: []utils.SupportedVersion{ + { + Major: 2, + Minor: 0, + Status: utils.StatusUnknown, + }, + }, + }, + { + name: "baremetal unversioned endpoint", + endpoint: fakeServer.Endpoint() + "baremetal/", + discoverVersions: true, + expectedVersions: []utils.SupportedVersion{ + { + Major: 1, + Minor: 0, + Status: utils.StatusCurrent, + SupportedMicroversions: utils.SupportedMicroversions{ + MaxMajor: 1, MaxMinor: 87, MinMajor: 1, MinMinor: 1, + }, + }, + }, + }, + { + name: "baremetal versioned endpoint", + endpoint: fakeServer.Endpoint() + "baremetal/v1/", + discoverVersions: true, + expectedVersions: []utils.SupportedVersion{ + { + Major: 1, + Minor: 0, + Status: utils.StatusCurrent, + SupportedMicroversions: utils.SupportedMicroversions{ + MaxMajor: 1, MaxMinor: 87, MinMajor: 1, MinMinor: 1, + }, + }, + }, + }, + { + name: "baremetal versioned endpoint", + endpoint: fakeServer.Endpoint() + "baremetal/v1/", + discoverVersions: true, + expectedVersions: []utils.SupportedVersion{ + { + Major: 1, + Minor: 0, + Status: utils.StatusCurrent, + SupportedMicroversions: utils.SupportedMicroversions{ + MaxMajor: 1, MaxMinor: 87, MinMajor: 1, MinMinor: 1, + }, + }, + }, + }, + { + name: "fictional multi-version endpoint", + endpoint: fakeServer.Endpoint() + "multi-version/v1.2/", + discoverVersions: true, + expectedVersions: []utils.SupportedVersion{ + { + Major: 1, + Minor: 2, + Status: utils.StatusCurrent, + SupportedMicroversions: utils.SupportedMicroversions{ + MaxMajor: 1, MaxMinor: 90, MinMajor: 1, MinMinor: 2, + }, + }, + { + Major: 1, + Minor: 0, + Status: utils.StatusCurrent, + SupportedMicroversions: utils.SupportedMicroversions{ + MaxMajor: 1, MaxMinor: 87, MinMajor: 1, MinMinor: 1, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client := &gophercloud.ProviderClient{} + + actualVersions, err := utils.GetServiceVersions(context.TODO(), client, tt.endpoint, tt.discoverVersions) + + if tt.expectedErr != "" { + th.AssertErr(t, err) + if !strings.Contains(err.Error(), tt.expectedErr) { + t.Errorf("Expected error to contain '%s', got '%s': %+v", tt.expectedErr, err, tt) + } + return + } else { + th.AssertNoErr(t, err) + } + + th.AssertDeepEquals(t, tt.expectedVersions, actualVersions) + }) + } +} + +func TestGetSupportedMicroversions(t *testing.T) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + setupMultiServiceVersionHandler(fakeServer) + + tests := []struct { + name string + endpoint string + expectedVersions utils.SupportedMicroversions + expectedErr string + }{ + { + name: "identity unversioned endpoint", + endpoint: fakeServer.Endpoint() + "identity/", + expectedErr: "not supported", + }, + { + // identity does not support microversions and returns error + name: "identity versioned endpoint", + endpoint: fakeServer.Endpoint() + "identity/v3/", + expectedErr: "not supported", + }, + { + // compute root API does not expose microversion info and returns error + name: "compute unversioned endpoint", + endpoint: fakeServer.Endpoint() + "compute/", + expectedErr: "not supported", + }, + { + // compute v2 does not support microversions and returns error + name: "compute legacy endpoint", + endpoint: fakeServer.Endpoint() + "compute/v2/", + expectedErr: "not supported", + }, + { + name: "compute versioned endpoint", + endpoint: fakeServer.Endpoint() + "compute/v2.1/", + expectedVersions: utils.SupportedMicroversions{ + MaxMajor: 2, MaxMinor: 90, MinMajor: 2, MinMinor: 1, + }, + }, + { + name: "container-infra unversioned endpoint", + endpoint: fakeServer.Endpoint() + "container-infra/", + expectedVersions: utils.SupportedMicroversions{ + MaxMajor: 1, MaxMinor: 11, MinMajor: 1, MinMinor: 1, + }, + }, + { + // container-infra does not expose proper discovery information + name: "container-infra versioned endpoint", + endpoint: fakeServer.Endpoint() + "container-infra/v1/", + expectedErr: "not supported", + }, + { + // orchestration does not support microversions and returns error + name: "orchestration unversioned endpoint", + endpoint: fakeServer.Endpoint() + "heat-api/", + expectedErr: "not supported", + }, + { + // orchestration does not support microversions and returns error + name: "orchestration versioned endpoint", + endpoint: fakeServer.Endpoint() + "heat-api/v1/", + expectedErr: "not supported", + }, + { + // workflow does not support microversions and returns error + name: "workflow unversioned endpoint", + endpoint: fakeServer.Endpoint() + "workflow/", + expectedErr: "not supported", + }, + { + // workflow does not support microversions and returns error + name: "workflow versioned endpoint", + endpoint: fakeServer.Endpoint() + "workflow/v2/", + expectedErr: "not supported", + }, + { + name: "baremetal unversioned endpoint", + endpoint: fakeServer.Endpoint() + "baremetal/", + expectedVersions: utils.SupportedMicroversions{ + MaxMajor: 1, MaxMinor: 87, MinMajor: 1, MinMinor: 1, + }, + }, + { + name: "baremetal versioned endpoint", + endpoint: fakeServer.Endpoint() + "baremetal/v1/", + expectedVersions: utils.SupportedMicroversions{ + MaxMajor: 1, MaxMinor: 87, MinMajor: 1, MinMinor: 1, + }, + }, + { + // This endpoint returns multiple versions, which is not supported + name: "fictional multi-version endpoint", + endpoint: fakeServer.Endpoint() + "multi-version/v1.2/", + expectedErr: "not supported", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &gophercloud.ProviderClient{} + client := &gophercloud.ServiceClient{ + ProviderClient: c, + Endpoint: tt.endpoint, + } + + actualVersions, err := utils.GetSupportedMicroversions(context.TODO(), client) + + if tt.expectedErr != "" { + th.AssertErr(t, err) + if !strings.Contains(err.Error(), tt.expectedErr) { + t.Fatalf("Expected error to contain '%s', got '%s': %+v", tt.expectedErr, err, tt) + } + // No point parsing and comparing versions after error, so continue to next test case + return + } else { + th.AssertNoErr(t, err) + } + + th.AssertDeepEquals(t, tt.expectedVersions, actualVersions) + }) + } +} + +func TestMicroversionSupported(t *testing.T) { + tests := []struct { + name string + version string + minVersion string + maxVersion string + supported bool + expectedError bool + }{ + { + name: "Checking min version", + version: "2.1", + minVersion: "2.1", + maxVersion: "2.90", + supported: true, + expectedError: false, + }, + { + name: "Checking max version", + version: "2.90", + minVersion: "2.1", + maxVersion: "2.90", + supported: true, + expectedError: false, + }, + { + name: "Checking too high version", + version: "2.95", + minVersion: "2.1", + maxVersion: "2.90", + supported: false, + expectedError: false, + }, + { + name: "Checking too low version", + version: "2.1", + minVersion: "2.53", + maxVersion: "2.90", + supported: false, + expectedError: false, + }, + { + name: "Invalid version", + version: "2.1.53", + minVersion: "2.53", + maxVersion: "2.90", + supported: false, + expectedError: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var err error + var supportedVersions utils.SupportedMicroversions + + supportedVersions.MaxMajor, supportedVersions.MaxMinor, err = utils.ParseMicroversion(tt.maxVersion) + if err != nil { + t.Fatal("Error parsing MaxVersion!") + } + + supportedVersions.MinMajor, supportedVersions.MinMinor, err = utils.ParseMicroversion(tt.minVersion) + if err != nil { + t.Fatal("Error parsing MinVersion!") + } + + supported, err := supportedVersions.IsSupported(tt.version) + if tt.expectedError { + th.AssertErr(t, err) + } else { + th.AssertNoErr(t, err) + } + + if tt.supported != supported { + t.Fatalf("Expected supported=%t to be %t, when version=%s, min=%s and max=%s", + supported, tt.supported, tt.version, tt.minVersion, tt.maxVersion) + } + }) + } +} diff --git a/openstack/utils/testing/fixtures_test.go b/openstack/utils/testing/fixtures_test.go new file mode 100644 index 0000000000..0d4a489356 --- /dev/null +++ b/openstack/utils/testing/fixtures_test.go @@ -0,0 +1,523 @@ +package testing + +import ( + "fmt" + "net/http" + + th "github.com/gophercloud/gophercloud/v2/testhelper" +) + +func setupIdentityVersionHandler(fakeServer th.FakeServer) { + fakeServer.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, ` + { + "versions": { + "values": [ + { + "status": "stable", + "id": "v3.0", + "links": [ + { "href": "%s/v3.0", "rel": "self" } + ] + }, + { + "status": "stable", + "id": "v2.0", + "links": [ + { "href": "%s/v2.0", "rel": "self" } + ] + } + ] + } + } + `, fakeServer.Server.URL, fakeServer.Server.URL) + }) +} + +func setupMultiServiceVersionHandler(fakeServer th.FakeServer) { + // Identity root API + fakeServer.Mux.HandleFunc("/identity/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, ` + { + "versions": { + "values": [ + { + "id": "v3.14", + "status": "stable", + "updated": "2020-04-07T00:00:00Z", + "links": [ + { + "rel": "self", + "href": "%s/identity/v3/" + } + ], + "media-types": [ + { + "base": "application/json", + "type": "application/vnd.openstack.identity-v3+json" + } + ] + } + ] + } + } + `, fakeServer.Server.URL) + }) + // Identity v3 API + fakeServer.Mux.HandleFunc("/identity/v3/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, ` + { + "version": { + "id": "v3.14", + "status": "stable", + "updated": "2020-04-07T00:00:00Z", + "links": [ + { + "rel": "self", + "href": "%s/identity/v3/" + } + ], + "media-types": [ + { + "base": "application/json", + "type": "application/vnd.openstack.identity-v3+json" + } + ] + } + } + `, fakeServer.Server.URL) + }) + // Compute root API + fakeServer.Mux.HandleFunc("/compute/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, ` + { + "versions": [ + { + "id": "v2.0", + "status": "SUPPORTED", + "version": "", + "min_version": "", + "updated": "2011-01-21T11:33:21Z", + "links": [ + { + "rel": "self", + "href": "%s/compute/v2/" + } + ] + }, + { + "id": "v2.1", + "status": "CURRENT", + "version": "2.90", + "min_version": "2.1", + "updated": "2013-07-23T11:33:21Z", + "links": [ + { + "rel": "self", + "href": "%s/compute/v2.1/" + } + ] + } + ] + } + `, fakeServer.Server.URL, fakeServer.Server.URL) + }) + // Compute v2 API + fakeServer.Mux.HandleFunc("/compute/v2/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, ` + { + "version": { + "id": "v2.0", + "status": "SUPPORTED", + "version": "", + "min_version": "", + "updated": "2011-01-21T11:33:21Z", + "links": [ + { + "rel": "self", + "href": "%s/compute/v2/" + }, + { + "rel": "describedby", + "type": "text/html", + "href": "http://docs.openstack.org/" + } + ], + "media-types": [ + { + "base": "application/json", + "type": "application/vnd.openstack.compute+json;version=2" + } + ] + } + } + `, fakeServer.Server.URL) + }) + // Compute v2.1 API + fakeServer.Mux.HandleFunc("/compute/v2.1/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, ` + { + "version": { + "id": "v2.1", + "status": "CURRENT", + "version": "2.90", + "min_version": "2.1", + "updated": "2013-07-23T11:33:21Z", + "links": [ + { + "rel": "self", + "href": "%s/compute/v2.1/" + }, + { + "rel": "describedby", + "type": "text/html", + "href": "http://docs.openstack.org/" + } + ], + "media-types": [ + { + "base": "application/json", + "type": "application/vnd.openstack.compute+json;version=2.1" + } + ] + } + } + `, fakeServer.Server.URL) + }) + // Container Infra root API + fakeServer.Mux.HandleFunc("/container-infra/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, ` + { + "name": "OpenStack Magnum API", + "description": "Magnum is an OpenStack project which aims to provide container cluster management.", + "versions": [ + { + "id": "v1", + "links": [ + { + "href": "%s/v1/", + "rel": "self" + } + ], + "status": "CURRENT", + "max_version": "1.11", + "min_version": "1.1" + } + ] + } + `, fakeServer.Server.URL) + }) + // Container Infra v1 API + // + // NOTE(stephenfin): In reality, this returns absolute URLs, but those URLs are wrong since + // they don't respect the Host header. We're using relative URLs because (a) it's probably + // what magnum should be doing and (b) it avoids needing 17 odd arguments to Fprintf + fakeServer.Mux.HandleFunc("/container-infra/v1/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, ` + { + "id": "v1", + "media_types": [ + { + "base": "application/json", + "type": "application/vnd.openstack.magnum.v1+json" + } + ], + "links": [ + { + "href": "/v1/", + "rel": "self" + }, + { + "href": "http://docs.openstack.org/developer/magnum/dev/api-spec-v1.html", + "rel": "describedby", + "type": "text/html" + } + ], + "clustertemplates": [ + { + "href": "/v1/clustertemplates/", + "rel": "self" + }, + { + "href": "/clustertemplates/", + "rel": "bookmark" + } + ], + "clusters": [ + { + "href": "/v1/clusters/", + "rel": "self" + }, + { + "href": "/clusters/", + "rel": "bookmark" + } + ], + "quotas": [ + { + "href": "/v1/quotas/", + "rel": "self" + }, + { + "href": "/quotas/", + "rel": "bookmark" + } + ], + "certificates": [ + { + "href": "/v1/certificates/", + "rel": "self" + }, + { + "href": "/certificates/", + "rel": "bookmark" + } + ], + "mservices": [ + { + "href": "/v1/mservices/", + "rel": "self" + }, + { + "href": "/mservices/", + "rel": "bookmark" + } + ], + "stats": [ + { + "href": "/v1/stats/", + "rel": "self" + }, + { + "href": "/stats/", + "rel": "bookmark" + } + ], + "federations": [ + { + "href": "/v1/federations/", + "rel": "self" + }, + { + "href": "/federations/", + "rel": "bookmark" + } + ], + "nodegroups": [ + { + "href": "/v1/clusters/{cluster_id}/nodegroups", + "rel": "self" + }, + { + "href": "/clusters/{cluster_id}/nodegroups", + "rel": "bookmark" + } + ] + } + `, fakeServer.Server.URL) + }) + // Orchestration root API + fakeServer.Mux.HandleFunc("/heat-api/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, ` + { + "versions": [ + { + "id": "v1.0", + "status": "CURRENT", + "links": [ + { + "rel": "self", + "href": "%s/heat-api/v1/" + } + ] + } + ] + } + `, fakeServer.Server.URL) + }) + // Orchestration v1 API (non-existent) + fakeServer.Mux.HandleFunc("/heat-api/v1/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusNotFound) + }) + // Workflow root API + // + // In reality, this deploys under a port rather than a path (as of Epoxy) but we don't want to + // have to run multiple fake servers so this will do. + fakeServer.Mux.HandleFunc("/workflow/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, ` + { + "versions": [ + { + "id": "v2.0", + "status": "CURRENT", + "links": [ + { + "href": "%s/workflow/v2", + "target": "v2", + "rel": "self" + } + ] + } + ] + } + `, fakeServer.Server.URL) + }) + // Workflow v1 API (invalid version document) + fakeServer.Mux.HandleFunc("/workflow/v2/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, ` + { + "uri": "%s/v2" + } + `, fakeServer.Server.URL) + }) + // Baremetal root API + fakeServer.Mux.HandleFunc("/baremetal/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, ` + { + "name": "OpenStack Ironic API", + "description": "Ironic is an OpenStack project which enables the provision and management of baremetal machines.", + "default_version": { + "id": "v1", + "links": [ + { + "href": "%s/baremetal/v1/", + "rel": "self" + } + ], + "status": "CURRENT", + "min_version": "1.1", + "version": "1.87" + }, + "versions": [ + { + "id": "v1", + "links": [ + { + "href": "%s/baremetal/v1/", + "rel": "self" + } + ], + "status": "CURRENT", + "min_version": "1.1", + "version": "1.87" + } + ] + } + `, fakeServer.Server.URL, fakeServer.Server.URL) + }) + // Baremetal v1 API + // + // NOTE(stephenfin): In reality, this returns absolute URLs and unlike Magnum those URLs are + // correctly formatted. We're using relative URLs for many of these because, once again, it + // avoids needing loads of arguments to Fprintf + fakeServer.Mux.HandleFunc("/baremetal/v1/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, ` + { + "id": "v1", + "links": [ + { + "href": "/baremetal/v1/", + "rel": "self" + }, + { + "href": "https://docs.openstack.org//ironic/latest/contributor//webapi.html", + "rel": "describedby", + "type": "text/html" + } + ], + "media_types": { + "base": "application/json", + "type": "application/vnd.openstack.ironic.v1+json" + }, + "chassis": [ + { + "href": "/baremetal/v1/chassis/", + "rel": "self" + }, + { + "href": "/baremetal/chassis/", + "rel": "bookmark" + } + ], + "nodes": [ + { + "href": "/baremetal/v1/nodes/", + "rel": "self" + }, + { + "href": "/baremetal/nodes/", + "rel": "bookmark" + } + ], + "ports": [ + { + "href": "/baremetal/v1/ports/", + "rel": "self" + }, + { + "href": "/baremetal/ports/", + "rel": "bookmark" + } + ], + "drivers": [ + { + "href": "/baremetal/v1/drivers/", + "rel": "self" + }, + { + "href": "/baremetal/drivers/", + "rel": "bookmark" + } + ], + "version": { + "id": "v1", + "links": [ + { + "href": "%s/baremetal/v1/", + "rel": "self" + } + ], + "status": "CURRENT", + "min_version": "1.1", + "version": "1.87" + } + } + `, fakeServer.Server.URL) + }) + // Fictional multi-version API + fakeServer.Mux.HandleFunc("/multi-version/v1.2/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, ` + { + "name": "Multi-version API", + "description": "A fictional API with multiple microversions.", + "versions": [ + { + "id": "v1", + "links": [ + { + "href": "%s/multi-version/v1/", + "rel": "self" + } + ], + "status": "CURRENT", + "min_version": "1.1", + "version": "1.87" + }, + { + "id": "v1.2", + "links": [ + { + "href": "%s/multi-version/v1/", + "rel": "self" + } + ], + "status": "CURRENT", + "min_version": "1.2", + "version": "1.90" + } + ] + } + `, fakeServer.Server.URL, fakeServer.Server.URL) + }) +} diff --git a/openstack/workflow/v2/crontriggers/results.go b/openstack/workflow/v2/crontriggers/results.go index 16ec7d1c9f..94500969ed 100644 --- a/openstack/workflow/v2/crontriggers/results.go +++ b/openstack/workflow/v2/crontriggers/results.go @@ -139,7 +139,7 @@ func (r CronTriggerPage) IsEmpty() (bool, error) { } // NextPageURL finds the next page URL in a page in order to navigate to the next page of results. -func (r CronTriggerPage) NextPageURL() (string, error) { +func (r CronTriggerPage) NextPageURL(endpointURL string) (string, error) { var s struct { Next string `json:"next"` } diff --git a/openstack/workflow/v2/crontriggers/testing/requests_test.go b/openstack/workflow/v2/crontriggers/testing/requests_test.go index fd3bac58e0..0926f97482 100644 --- a/openstack/workflow/v2/crontriggers/testing/requests_test.go +++ b/openstack/workflow/v2/crontriggers/testing/requests_test.go @@ -12,20 +12,20 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/workflow/v2/crontriggers" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestCreateCronTrigger(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/cron_triggers", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/cron_triggers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusCreated) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "created_at": "2018-09-12 15:48:18", "first_execution_time": "2018-09-12 17:48:00", @@ -58,7 +58,7 @@ func TestCreateCronTrigger(t *testing.T) { }, } - actual, err := crontriggers.Create(context.TODO(), fake.ServiceClient(), opts).Extract() + actual, err := crontriggers.Create(context.TODO(), client.ServiceClient(fakeServer), opts).Extract() if err != nil { t.Fatalf("Unable to create cron trigger: %v", err) } @@ -89,28 +89,28 @@ func TestCreateCronTrigger(t *testing.T) { } func TestDeleteCronTrigger(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/cron_triggers/0520ffd8-f7f1-4f2e-845b-55d953a1cf46", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/cron_triggers/0520ffd8-f7f1-4f2e-845b-55d953a1cf46", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) }) - res := crontriggers.Delete(context.TODO(), fake.ServiceClient(), "0520ffd8-f7f1-4f2e-845b-55d953a1cf46") + res := crontriggers.Delete(context.TODO(), client.ServiceClient(fakeServer), "0520ffd8-f7f1-4f2e-845b-55d953a1cf46") th.AssertNoErr(t, res.Err) } func TestGetCronTrigger(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - th.Mux.HandleFunc("/cron_triggers/0520ffd8-f7f1-4f2e-845b-55d953a1cf46", func(w http.ResponseWriter, r *http.Request) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + fakeServer.Mux.HandleFunc("/cron_triggers/0520ffd8-f7f1-4f2e-845b-55d953a1cf46", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "created_at": "2018-09-12 15:48:18", "first_execution_time": "2018-09-12 17:48:00", @@ -129,7 +129,7 @@ func TestGetCronTrigger(t *testing.T) { } `) }) - actual, err := crontriggers.Get(context.TODO(), fake.ServiceClient(), "0520ffd8-f7f1-4f2e-845b-55d953a1cf46").Extract() + actual, err := crontriggers.Get(context.TODO(), client.ServiceClient(fakeServer), "0520ffd8-f7f1-4f2e-845b-55d953a1cf46").Extract() if err != nil { t.Fatalf("Unable to get cron trigger: %v", err) } @@ -161,11 +161,11 @@ func TestGetCronTrigger(t *testing.T) { } func TestListCronTriggers(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - th.Mux.HandleFunc("/cron_triggers", func(w http.ResponseWriter, r *http.Request) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + fakeServer.Mux.HandleFunc("/cron_triggers", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") if err := r.ParseForm(); err != nil { t.Errorf("Failed to parse request form %v", err) @@ -193,16 +193,16 @@ func TestListCronTriggers(t *testing.T) { } ], "next": "%s/cron_triggers?marker=0520ffd8-f7f1-4f2e-845b-55d953a1cf46" - }`, th.Server.URL) + }`, fakeServer.Server.URL) case "0520ffd8-f7f1-4f2e-845b-55d953a1cf46": - fmt.Fprintf(w, `{ "cron_triggers": [] }`) + fmt.Fprint(w, `{ "cron_triggers": [] }`) default: t.Fatalf("Unexpected marker: [%s]", marker) } }) pages := 0 // Get all cron triggers - err := crontriggers.List(fake.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := crontriggers.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := crontriggers.ExtractCronTriggers(page) if err != nil { diff --git a/openstack/workflow/v2/executions/results.go b/openstack/workflow/v2/executions/results.go index 0111a195ee..7f7894e287 100644 --- a/openstack/workflow/v2/executions/results.go +++ b/openstack/workflow/v2/executions/results.go @@ -141,7 +141,7 @@ func (r ExecutionPage) IsEmpty() (bool, error) { } // NextPageURL finds the next page URL in a page in order to navigate to the next page of results. -func (r ExecutionPage) NextPageURL() (string, error) { +func (r ExecutionPage) NextPageURL(endpointURL string) (string, error) { var s struct { Next string `json:"next"` } diff --git a/openstack/workflow/v2/executions/testing/requests_test.go b/openstack/workflow/v2/executions/testing/requests_test.go index d0b4641793..6ba94a9aa1 100644 --- a/openstack/workflow/v2/executions/testing/requests_test.go +++ b/openstack/workflow/v2/executions/testing/requests_test.go @@ -12,20 +12,20 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/workflow/v2/executions" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestCreateExecution(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/executions", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/executions", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusCreated) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "created_at": "2018-09-12 14:48:49", "description": "description", @@ -54,7 +54,7 @@ func TestCreateExecution(t *testing.T) { Description: "description", } - actual, err := executions.Create(context.TODO(), fake.ServiceClient(), opts).Extract() + actual, err := executions.Create(context.TODO(), client.ServiceClient(fakeServer), opts).Extract() if err != nil { t.Fatalf("Unable to create execution: %v", err) } @@ -84,15 +84,15 @@ func TestCreateExecution(t *testing.T) { } func TestGetExecution(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/executions/50bb59f1-eb77-4017-a77f-6d575b002667", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/executions/50bb59f1-eb77-4017-a77f-6d575b002667", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "created_at": "2018-09-12 14:48:49", "description": "description", @@ -113,7 +113,7 @@ func TestGetExecution(t *testing.T) { `) }) - actual, err := executions.Get(context.TODO(), fake.ServiceClient(), "50bb59f1-eb77-4017-a77f-6d575b002667").Extract() + actual, err := executions.Get(context.TODO(), client.ServiceClient(fakeServer), "50bb59f1-eb77-4017-a77f-6d575b002667").Extract() if err != nil { t.Fatalf("Unable to get execution: %v", err) } @@ -143,23 +143,23 @@ func TestGetExecution(t *testing.T) { } func TestDeleteExecution(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - th.Mux.HandleFunc("/executions/1", func(w http.ResponseWriter, r *http.Request) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + fakeServer.Mux.HandleFunc("/executions/1", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) }) - res := executions.Delete(context.TODO(), fake.ServiceClient(), "1") + res := executions.Delete(context.TODO(), client.ServiceClient(fakeServer), "1") th.AssertNoErr(t, res.Err) } func TestListExecutions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - th.Mux.HandleFunc("/executions", func(w http.ResponseWriter, r *http.Request) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + fakeServer.Mux.HandleFunc("/executions", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") if err := r.ParseForm(); err != nil { t.Errorf("Failed to parse request form %v", err) @@ -187,16 +187,16 @@ func TestListExecutions(t *testing.T) { } ], "next": "%s/executions?marker=50bb59f1-eb77-4017-a77f-6d575b002667" - }`, th.Server.URL) + }`, fakeServer.Server.URL) case "50bb59f1-eb77-4017-a77f-6d575b002667": - fmt.Fprintf(w, `{ "executions": [] }`) + fmt.Fprint(w, `{ "executions": [] }`) default: t.Fatalf("Unexpected marker: [%s]", marker) } }) pages := 0 // Get all executions - err := executions.List(fake.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := executions.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := executions.ExtractExecutions(page) if err != nil { diff --git a/openstack/workflow/v2/workflows/doc.go b/openstack/workflow/v2/workflows/doc.go index 52638a3469..bc82714a6b 100644 --- a/openstack/workflow/v2/workflows/doc.go +++ b/openstack/workflow/v2/workflows/doc.go @@ -65,7 +65,7 @@ Create a workflow Delete a workflow - res := workflows.Delete(fake.ServiceClient(), "604a3a1e-94e3-4066-a34a-aa56873ef236") + res := workflows.Delete(fake.ServiceClient(fakeServer), "604a3a1e-94e3-4066-a34a-aa56873ef236") if res.Err != nil { panic(res.Err) } diff --git a/openstack/workflow/v2/workflows/results.go b/openstack/workflow/v2/workflows/results.go index c0c9cf1d27..a001c3b56f 100644 --- a/openstack/workflow/v2/workflows/results.go +++ b/openstack/workflow/v2/workflows/results.go @@ -115,7 +115,7 @@ func (r WorkflowPage) IsEmpty() (bool, error) { } // NextPageURL finds the next page URL in a page in order to navigate to the next page of results. -func (r WorkflowPage) NextPageURL() (string, error) { +func (r WorkflowPage) NextPageURL(endpointURL string) (string, error) { var s struct { Next string `json:"next"` } diff --git a/openstack/workflow/v2/workflows/testing/requests_test.go b/openstack/workflow/v2/workflows/testing/requests_test.go index 78f0f78a09..d194631b34 100644 --- a/openstack/workflow/v2/workflows/testing/requests_test.go +++ b/openstack/workflow/v2/workflows/testing/requests_test.go @@ -13,12 +13,12 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/workflow/v2/workflows" "github.com/gophercloud/gophercloud/v2/pagination" th "github.com/gophercloud/gophercloud/v2/testhelper" - fake "github.com/gophercloud/gophercloud/v2/testhelper/client" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) func TestCreateWorkflow(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() definition := `--- version: '2.0' @@ -33,9 +33,9 @@ workflow_echo: test: action: std.echo output="<% $.msg %>"` - th.Mux.HandleFunc("/workflows", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/workflows", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Content-Type", "text/plain") th.TestFormValues(t, r, map[string]string{ "namespace": "some-namespace", @@ -46,11 +46,11 @@ workflow_echo: w.WriteHeader(http.StatusCreated) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "workflows": [ { "created_at": "2018-09-12 15:48:17", - "definition": "---\nversion: '2.0'\n\nworkflow_echo:\n description: Simple workflow example\n type: direct\n\n input:\n - msg\n\n tasks:\n test:\n action: std.echo output=\"<%% $.msg %%>\"", + "definition": "---\nversion: '2.0'\n\nworkflow_echo:\n description: Simple workflow example\n type: direct\n\n input:\n - msg\n\n tasks:\n test:\n action: std.echo output=\"<% $.msg %>\"", "id": "604a3a1e-94e3-4066-a34a-aa56873ef236", "input": "msg", "name": "workflow_echo", @@ -70,7 +70,7 @@ workflow_echo: Definition: strings.NewReader(definition), } - actual, err := workflows.Create(context.TODO(), fake.ServiceClient(), opts).Extract() + actual, err := workflows.Create(context.TODO(), client.ServiceClient(fakeServer), opts).Extract() if err != nil { t.Fatalf("Unable to create workflow: %v", err) } @@ -97,31 +97,31 @@ workflow_echo: } func TestDeleteWorkflow(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/workflows/604a3a1e-94e3-4066-a34a-aa56873ef236", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/workflows/604a3a1e-94e3-4066-a34a-aa56873ef236", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusAccepted) }) - res := workflows.Delete(context.TODO(), fake.ServiceClient(), "604a3a1e-94e3-4066-a34a-aa56873ef236") + res := workflows.Delete(context.TODO(), client.ServiceClient(fakeServer), "604a3a1e-94e3-4066-a34a-aa56873ef236") th.AssertNoErr(t, res.Err) } func TestGetWorkflow(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - th.Mux.HandleFunc("/workflows/1", func(w http.ResponseWriter, r *http.Request) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + fakeServer.Mux.HandleFunc("/workflows/1", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "created_at": "2018-09-12 15:48:17", - "definition": "---\nversion: '2.0'\n\nworkflow_echo:\n description: Simple workflow example\n type: direct\n\n input:\n - msg\n\n tasks:\n test:\n action: std.echo output=\"<%% $.msg %%>\"", + "definition": "---\nversion: '2.0'\n\nworkflow_echo:\n description: Simple workflow example\n type: direct\n\n input:\n - msg\n\n tasks:\n test:\n action: std.echo output=\"<% $.msg %>\"", "id": "604a3a1e-94e3-4066-a34a-aa56873ef236", "input": "msg", "name": "workflow_echo", @@ -133,7 +133,7 @@ func TestGetWorkflow(t *testing.T) { } `) }) - actual, err := workflows.Get(context.TODO(), fake.ServiceClient(), "1").Extract() + actual, err := workflows.Get(context.TODO(), client.ServiceClient(fakeServer), "1").Extract() if err != nil { t.Fatalf("Unable to get workflow: %v", err) } @@ -157,11 +157,11 @@ func TestGetWorkflow(t *testing.T) { } func TestListWorkflows(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - th.Mux.HandleFunc("/workflows", func(w http.ResponseWriter, r *http.Request) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + fakeServer.Mux.HandleFunc("/workflows", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") if err := r.ParseForm(); err != nil { t.Errorf("Failed to parse request form %v", err) @@ -185,16 +185,16 @@ func TestListWorkflows(t *testing.T) { "updated_at": "2018-09-12 15:48:17" } ] - }`, th.Server.URL) + }`, fakeServer.Server.URL) case "604a3a1e-94e3-4066-a34a-aa56873ef236": - fmt.Fprintf(w, `{ "workflows": [] }`) + fmt.Fprint(w, `{ "workflows": [] }`) default: t.Fatalf("Unexpected marker: [%s]", marker) } }) pages := 0 // Get all workflows - err := workflows.List(fake.ServiceClient(), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { + err := workflows.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { pages++ actual, err := workflows.ExtractWorkflows(page) if err != nil { diff --git a/pagination/linked.go b/pagination/linked.go index 7e4de4f7ae..36a6c92367 100644 --- a/pagination/linked.go +++ b/pagination/linked.go @@ -21,7 +21,7 @@ type LinkedPageBase struct { // NextPageURL extracts the pagination structure from a JSON response and returns the "next" link, if one is present. // It assumes that the links are available in a "links" element of the top-level response object. // If this is not the case, override NextPageURL on your result type. -func (current LinkedPageBase) NextPageURL() (string, error) { +func (current LinkedPageBase) NextPageURL(endpointURL string) (string, error) { var path []string var key string diff --git a/pagination/marker.go b/pagination/marker.go index 1d101fe2db..26e8fbbdb9 100644 --- a/pagination/marker.go +++ b/pagination/marker.go @@ -25,7 +25,7 @@ type MarkerPageBase struct { } // NextPageURL generates the URL for the page of results after this one. -func (current MarkerPageBase) NextPageURL() (string, error) { +func (current MarkerPageBase) NextPageURL(endpointURL string) (string, error) { currentURL := current.URL mark, err := current.Owner.LastMarker() diff --git a/pagination/pager.go b/pagination/pager.go index 3581012566..2bb2c62c5d 100644 --- a/pagination/pager.go +++ b/pagination/pager.go @@ -13,7 +13,7 @@ import ( var ( // ErrPageNotAvailable is returned from a Pager when a next or previous page is requested, but does not exist. - ErrPageNotAvailable = errors.New("The requested page does not exist.") + ErrPageNotAvailable = errors.New("the requested page does not exist") ) // Page must be satisfied by the result type of any resource collection. @@ -25,7 +25,7 @@ var ( type Page interface { // NextPageURL generates the URL for the page of data that follows this collection. // Return "" if no such page exists. - NextPageURL() (string, error) + NextPageURL(endpointURL string) (string, error) // IsEmpty returns true if this Page has no items in it. IsEmpty() (bool, error) @@ -123,7 +123,7 @@ func (p Pager) EachPage(ctx context.Context, handler func(context.Context, Page) return nil } - currentURL, err = currentPage.NextPageURL() + currentURL, err = currentPage.NextPageURL(p.client.ServiceURL()) if err != nil { return err } diff --git a/pagination/single.go b/pagination/single.go index 416621121b..d3e518fb3a 100644 --- a/pagination/single.go +++ b/pagination/single.go @@ -11,7 +11,7 @@ import ( type SinglePageBase PageResult // NextPageURL always returns "" to indicate that there are no more pages to return. -func (current SinglePageBase) NextPageURL() (string, error) { +func (current SinglePageBase) NextPageURL(endpointURL string) (string, error) { return "", nil } diff --git a/pagination/testing/linked_test.go b/pagination/testing/linked_test.go index 83673f19b8..ec0476c7dd 100644 --- a/pagination/testing/linked_test.go +++ b/pagination/testing/linked_test.go @@ -8,7 +8,8 @@ import ( "testing" "github.com/gophercloud/gophercloud/v2/pagination" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) // LinkedPager sample and test cases. @@ -30,36 +31,36 @@ func ExtractLinkedInts(r pagination.Page) ([]int, error) { return s.Ints, err } -func createLinked() pagination.Pager { - testhelper.SetupHTTP() - - testhelper.Mux.HandleFunc("/page1", func(w http.ResponseWriter, r *http.Request) { +func createLinked(fakeServer th.FakeServer) pagination.Pager { + fakeServer.Mux.HandleFunc("/page1", func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, `{ "ints": [1, 2, 3], "links": { "next": "%s/page2" } }`, testhelper.Server.URL) + fmt.Fprintf(w, `{ "ints": [1, 2, 3], "links": { "next": "%s/page2" } }`, fakeServer.Server.URL) }) - testhelper.Mux.HandleFunc("/page2", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/page2", func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, `{ "ints": [4, 5, 6], "links": { "next": "%s/page3" } }`, testhelper.Server.URL) + fmt.Fprintf(w, `{ "ints": [4, 5, 6], "links": { "next": "%s/page3" } }`, fakeServer.Server.URL) }) - testhelper.Mux.HandleFunc("/page3", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/page3", func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, `{ "ints": [7, 8, 9], "links": { "next": null } }`) + fmt.Fprint(w, `{ "ints": [7, 8, 9], "links": { "next": null } }`) }) - client := createClient() + client := client.ServiceClient(fakeServer) createPage := func(r pagination.PageResult) pagination.Page { return LinkedPageResult{pagination.LinkedPageBase{PageResult: r}} } - return pagination.NewPager(client, testhelper.Server.URL+"/page1", createPage) + return pagination.NewPager(client, fakeServer.Server.URL+"/page1", createPage) } func TestEnumerateLinked(t *testing.T) { - pager := createLinked() - defer testhelper.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + pager := createLinked(fakeServer) callCount := 0 err := pager.EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { @@ -100,14 +101,16 @@ func TestEnumerateLinked(t *testing.T) { } func TestAllPagesLinked(t *testing.T) { - pager := createLinked() - defer testhelper.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + pager := createLinked(fakeServer) page, err := pager.AllPages(context.TODO()) - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) expected := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} actual, err := ExtractLinkedInts(page) - testhelper.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, expected, actual) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, expected, actual) } diff --git a/pagination/testing/marker_test.go b/pagination/testing/marker_test.go index a1084ac12f..f46ba9d602 100644 --- a/pagination/testing/marker_test.go +++ b/pagination/testing/marker_test.go @@ -8,7 +8,8 @@ import ( "testing" "github.com/gophercloud/gophercloud/v2/pagination" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) // MarkerPager sample and test cases. @@ -36,21 +37,19 @@ func (r MarkerPageResult) LastMarker() (string, error) { return results[len(results)-1], nil } -func createMarkerPaged(t *testing.T) pagination.Pager { - testhelper.SetupHTTP() - - testhelper.Mux.HandleFunc("/page", func(w http.ResponseWriter, r *http.Request) { +func createMarkerPaged(t *testing.T, fakeServer th.FakeServer) pagination.Pager { + fakeServer.Mux.HandleFunc("/page", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { t.Errorf("Failed to parse request form %v", err) } ms := r.Form["marker"] switch { case len(ms) == 0: - fmt.Fprintf(w, "aaa\nbbb\nccc") + fmt.Fprint(w, "aaa\nbbb\nccc") case len(ms) == 1 && ms[0] == "ccc": - fmt.Fprintf(w, "ddd\neee\nfff") + fmt.Fprint(w, "ddd\neee\nfff") case len(ms) == 1 && ms[0] == "fff": - fmt.Fprintf(w, "ggg\nhhh\niii") + fmt.Fprint(w, "ggg\nhhh\niii") case len(ms) == 1 && ms[0] == "iii": w.WriteHeader(http.StatusNoContent) default: @@ -58,15 +57,15 @@ func createMarkerPaged(t *testing.T) pagination.Pager { } }) - client := createClient() + client := client.ServiceClient(fakeServer) createPage := func(r pagination.PageResult) pagination.Page { p := MarkerPageResult{pagination.MarkerPageBase{PageResult: r}} - p.MarkerPageBase.Owner = p + p.Owner = p return p } - return pagination.NewPager(client, testhelper.Server.URL+"/page", createPage) + return pagination.NewPager(client, fakeServer.Server.URL+"/page", createPage) } func ExtractMarkerStrings(page pagination.Page) ([]string, error) { @@ -82,8 +81,10 @@ func ExtractMarkerStrings(page pagination.Page) ([]string, error) { } func TestEnumerateMarker(t *testing.T) { - pager := createMarkerPaged(t) - defer testhelper.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + pager := createMarkerPaged(t, fakeServer) callCount := 0 err := pager.EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { @@ -107,24 +108,26 @@ func TestEnumerateMarker(t *testing.T) { return false, nil } - testhelper.CheckDeepEquals(t, expected, actual) + th.CheckDeepEquals(t, expected, actual) callCount++ return true, nil }) - testhelper.AssertNoErr(t, err) - testhelper.AssertEquals(t, callCount, 3) + th.AssertNoErr(t, err) + th.AssertEquals(t, 3, callCount) } func TestAllPagesMarker(t *testing.T) { - pager := createMarkerPaged(t) - defer testhelper.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + pager := createMarkerPaged(t, fakeServer) page, err := pager.AllPages(context.TODO()) - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) expected := []string{"aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg", "hhh", "iii"} actual, err := ExtractMarkerStrings(page) - testhelper.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, expected, actual) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, expected, actual) } diff --git a/pagination/testing/pagination_test.go b/pagination/testing/pagination_test.go deleted file mode 100644 index b730676d4e..0000000000 --- a/pagination/testing/pagination_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package testing - -import ( - "github.com/gophercloud/gophercloud/v2" - "github.com/gophercloud/gophercloud/v2/testhelper" -) - -func createClient() *gophercloud.ServiceClient { - return &gophercloud.ServiceClient{ - ProviderClient: &gophercloud.ProviderClient{TokenID: "abc123"}, - Endpoint: testhelper.Endpoint(), - } -} diff --git a/pagination/testing/single_test.go b/pagination/testing/single_test.go index 21daedff0a..4a7f010dc0 100644 --- a/pagination/testing/single_test.go +++ b/pagination/testing/single_test.go @@ -7,7 +7,8 @@ import ( "testing" "github.com/gophercloud/gophercloud/v2/pagination" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" + "github.com/gophercloud/gophercloud/v2/testhelper/client" ) // SinglePage sample and test cases. @@ -32,49 +33,52 @@ func ExtractSingleInts(r pagination.Page) ([]int, error) { return s.Ints, err } -func setupSinglePaged() pagination.Pager { - testhelper.SetupHTTP() - client := createClient() +func setupSinglePaged(fakeServer th.FakeServer) pagination.Pager { + client := client.ServiceClient(fakeServer) - testhelper.Mux.HandleFunc("/only", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/only", func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, `{ "ints": [1, 2, 3] }`) + fmt.Fprint(w, `{ "ints": [1, 2, 3] }`) }) createPage := func(r pagination.PageResult) pagination.Page { return SinglePageResult{pagination.SinglePageBase(r)} } - return pagination.NewPager(client, testhelper.Server.URL+"/only", createPage) + return pagination.NewPager(client, fakeServer.Server.URL+"/only", createPage) } func TestEnumerateSinglePaged(t *testing.T) { callCount := 0 - pager := setupSinglePaged() - defer testhelper.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + pager := setupSinglePaged(fakeServer) err := pager.EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { callCount++ expected := []int{1, 2, 3} actual, err := ExtractSingleInts(page) - testhelper.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, expected, actual) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, expected, actual) return true, nil }) - testhelper.CheckNoErr(t, err) - testhelper.CheckEquals(t, 1, callCount) + th.CheckNoErr(t, err) + th.CheckEquals(t, 1, callCount) } func TestAllPagesSingle(t *testing.T) { - pager := setupSinglePaged() - defer testhelper.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + + pager := setupSinglePaged(fakeServer) page, err := pager.AllPages(context.TODO()) - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) expected := []int{1, 2, 3} actual, err := ExtractSingleInts(page) - testhelper.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, expected, actual) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, expected, actual) } diff --git a/params.go b/params.go index 09b322a6a2..ddb61b3d08 100644 --- a/params.go +++ b/params.go @@ -11,9 +11,11 @@ import ( ) /* -BuildRequestBody builds a map[string]interface from the given `struct`. If -parent is not an empty string, the final map[string]interface returned will -encapsulate the built one. For example: +BuildRequestBody builds a map[string]interface from the given `struct`, or +collection of `structs`. If parent is not an empty string, the final +map[string]interface returned will encapsulate the built one. Parent is +required when passing a list of `structs`. +For example: disk := 1 createOpts := flavors.CreateOpts{ @@ -27,24 +29,47 @@ encapsulate the built one. For example: body, err := gophercloud.BuildRequestBody(createOpts, "flavor") -The above example can be run as-is, however it is recommended to look at how + + opts := []rules.CreateOpts{ + { + Direction: "ingress", + PortRangeMin: 80, + EtherType: rules.EtherType4, + PortRangeMax: 80, + Protocol: "tcp", + SecGroupID: "a7734e61-b545-452d-a3cd-0189cbd9747a", + }, + { + Direction: "ingress", + PortRangeMin: 443, + EtherType: rules.EtherType4, + PortRangeMax: 443, + Protocol: "tcp", + SecGroupID: "a7734e61-b545-452d-a3cd-0189cbd9747a", + }, + } + + body, err := gophercloud.BuildRequestBody(opts, "security_group_rules") + +The above examples can be run as-is, however it is recommended to look at how BuildRequestBody is used within Gophercloud to more fully understand how it fits within the request process as a whole rather than use it directly as shown above. */ func BuildRequestBody(opts any, parent string) (map[string]any, error) { optsValue := reflect.ValueOf(opts) - if optsValue.Kind() == reflect.Ptr { + if optsValue.Kind() == reflect.Pointer { optsValue = optsValue.Elem() } optsType := reflect.TypeOf(opts) - if optsType.Kind() == reflect.Ptr { + if optsType.Kind() == reflect.Pointer { optsType = optsType.Elem() } optsMap := make(map[string]any) - if optsValue.Kind() == reflect.Struct { + switch optsValue.Kind() { + case reflect.Struct: //fmt.Printf("optsValue.Kind() is a reflect.Struct: %+v\n", optsValue.Kind()) for i := 0; i < optsValue.NumField(); i++ { v := optsValue.Field(i) @@ -79,12 +104,12 @@ func BuildRequestBody(opts any, parent string) (map[string]any, error) { if reflect.ValueOf(xorField.Interface()) == reflect.Zero(xorField.Type()) { xorFieldIsZero = true } else { - if xorField.Kind() == reflect.Ptr { + if xorField.Kind() == reflect.Pointer { xorField = xorField.Elem() } xorFieldIsZero = isZero(xorField) } - if !(zero != xorFieldIsZero) { + if zero == xorFieldIsZero { err := ErrMissingInput{} err.Argument = fmt.Sprintf("%s/%s", f.Name, xorTag) err.Info = fmt.Sprintf("Exactly one of %s and %s must be provided", f.Name, xorTag) @@ -101,7 +126,7 @@ func BuildRequestBody(opts any, parent string) (map[string]any, error) { if reflect.ValueOf(orField.Interface()) == reflect.Zero(orField.Type()) { orFieldIsZero = true } else { - if orField.Kind() == reflect.Ptr { + if orField.Kind() == reflect.Pointer { orField = orField.Elem() } orFieldIsZero = isZero(orField) @@ -120,15 +145,15 @@ func BuildRequestBody(opts any, parent string) (map[string]any, error) { continue } - if v.Kind() == reflect.Slice || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Slice) { + if v.Kind() == reflect.Slice || (v.Kind() == reflect.Pointer && v.Elem().Kind() == reflect.Slice) { sliceValue := v - if sliceValue.Kind() == reflect.Ptr { + if sliceValue.Kind() == reflect.Pointer { sliceValue = sliceValue.Elem() } for i := 0; i < sliceValue.Len(); i++ { element := sliceValue.Index(i) - if element.Kind() == reflect.Struct || (element.Kind() == reflect.Ptr && element.Elem().Kind() == reflect.Struct) { + if element.Kind() == reflect.Struct || (element.Kind() == reflect.Pointer && element.Elem().Kind() == reflect.Struct) { _, err := BuildRequestBody(element.Interface(), "") if err != nil { return nil, err @@ -136,7 +161,7 @@ func BuildRequestBody(opts any, parent string) (map[string]any, error) { } } } - if v.Kind() == reflect.Struct || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct) { + if v.Kind() == reflect.Struct || (v.Kind() == reflect.Pointer && v.Elem().Kind() == reflect.Struct) { if zero { //fmt.Printf("value before change: %+v\n", optsValue.Field(i)) if jsonTag != "" { @@ -144,7 +169,7 @@ func BuildRequestBody(opts any, parent string) (map[string]any, error) { if len(jsonTagPieces) > 1 && jsonTagPieces[1] == "omitempty" { if v.CanSet() { if !v.IsNil() { - if v.Kind() == reflect.Ptr { + if v.Kind() == reflect.Pointer { v.Set(reflect.Zero(v.Type())) } } @@ -184,9 +209,22 @@ func BuildRequestBody(opts any, parent string) (map[string]any, error) { } //fmt.Printf("optsMap after parent added: %+v\n", optsMap) return optsMap, nil + case reflect.Slice, reflect.Array: + optsMaps := make([]map[string]any, optsValue.Len()) + for i := 0; i < optsValue.Len(); i++ { + b, err := BuildRequestBody(optsValue.Index(i).Interface(), "") + if err != nil { + return nil, err + } + optsMaps[i] = b + } + if parent == "" { + return nil, fmt.Errorf("parent is required when passing an array or a slice") + } + return map[string]any{parent: optsMaps}, nil } - // Return an error if the underlying type of 'opts' isn't a struct. - return nil, fmt.Errorf("Options type is not a struct.") + // Return an error if we can't work with the underlying type of 'opts' + return nil, fmt.Errorf("options type is not a struct, a slice, or an array") } // EnabledState is a convenience type, mostly used in Create and Update @@ -254,7 +292,7 @@ func MaybeInt(original int) *int { /* func isUnderlyingStructZero(v reflect.Value) bool { switch v.Kind() { - case reflect.Ptr: + case reflect.Pointer: return isUnderlyingStructZero(v.Elem()) default: return isZero(v) @@ -267,7 +305,7 @@ var t time.Time func isZero(v reflect.Value) bool { //fmt.Printf("\n\nchecking isZero for value: %+v\n", v) switch v.Kind() { - case reflect.Ptr: + case reflect.Pointer: if v.IsNil() { return true } @@ -327,12 +365,12 @@ Slice are handled in one of two ways: */ func BuildQueryString(opts any) (*url.URL, error) { optsValue := reflect.ValueOf(opts) - if optsValue.Kind() == reflect.Ptr { + if optsValue.Kind() == reflect.Pointer { optsValue = optsValue.Elem() } optsType := reflect.TypeOf(opts) - if optsType.Kind() == reflect.Ptr { + if optsType.Kind() == reflect.Pointer { optsType = optsType.Elem() } @@ -352,7 +390,7 @@ func BuildQueryString(opts any) (*url.URL, error) { if !isZero(v) { loop: switch v.Kind() { - case reflect.Ptr: + case reflect.Pointer: v = v.Elem() goto loop case reflect.String: @@ -391,7 +429,7 @@ func BuildQueryString(opts any) (*url.URL, error) { } else { // if the field has a 'required' tag, it can't have a zero-value if requiredTag := f.Tag.Get("required"); requiredTag == "true" { - return &url.URL{}, fmt.Errorf("Required query parameter [%s] not set.", f.Name) + return &url.URL{}, fmt.Errorf("required query parameter [%s] not set", f.Name) } } } @@ -400,7 +438,7 @@ func BuildQueryString(opts any) (*url.URL, error) { return &url.URL{RawQuery: params.Encode()}, nil } // Return an error if the underlying type of 'opts' isn't a struct. - return nil, fmt.Errorf("Options type is not a struct.") + return nil, fmt.Errorf("options type is not a struct") } /* @@ -433,12 +471,12 @@ booleans and string values are supported. */ func BuildHeaders(opts any) (map[string]string, error) { optsValue := reflect.ValueOf(opts) - if optsValue.Kind() == reflect.Ptr { + if optsValue.Kind() == reflect.Pointer { optsValue = optsValue.Elem() } optsType := reflect.TypeOf(opts) - if optsType.Kind() == reflect.Ptr { + if optsType.Kind() == reflect.Pointer { optsType = optsType.Elem() } @@ -455,7 +493,7 @@ func BuildHeaders(opts any) (map[string]string, error) { // if the field is set, add it to the slice of query pieces if !isZero(v) { - if v.Kind() == reflect.Ptr { + if v.Kind() == reflect.Pointer { v = v.Elem() } switch v.Kind() { @@ -471,7 +509,7 @@ func BuildHeaders(opts any) (map[string]string, error) { } else { // if the field has a 'required' tag, it can't have a zero-value if requiredTag := f.Tag.Get("required"); requiredTag == "true" { - return optsMap, fmt.Errorf("Required header [%s] not set.", f.Name) + return optsMap, fmt.Errorf("required header [%s] not set", f.Name) } } } @@ -480,7 +518,7 @@ func BuildHeaders(opts any) (map[string]string, error) { return optsMap, nil } // Return an error if the underlying type of 'opts' isn't a struct. - return optsMap, fmt.Errorf("Options type is not a struct.") + return optsMap, fmt.Errorf("options type is not a struct") } // IDSliceToQueryString takes a slice of elements and converts them into a query diff --git a/provider_client.go b/provider_client.go index f8c4928cbd..a75592a2d1 100644 --- a/provider_client.go +++ b/provider_client.go @@ -7,13 +7,14 @@ import ( "errors" "io" "net/http" + "slices" "strings" "sync" ) // DefaultUserAgent is the default User-Agent string set in the request header. const ( - DefaultUserAgent = "gophercloud/v2.0.0" + DefaultUserAgent = "gophercloud/v3.0.0-UNRELEASED" DefaultMaxBackoffRetries = 60 ) @@ -437,16 +438,8 @@ func (client *ProviderClient) doRequest(ctx context.Context, method, url string, okc = defaultOkCodes(method) } - // Validate the HTTP response status. - var ok bool - for _, code := range okc { - if resp.StatusCode == code { - ok = true - break - } - } - - if !ok { + // Check the response code against the acceptable codes + if !slices.Contains(okc, resp.StatusCode) { body, _ := io.ReadAll(resp.Body) resp.Body.Close() respErr := ErrUnexpectedResponseCode{ diff --git a/results.go b/results.go index 9e6f630abb..3ca191683e 100644 --- a/results.go +++ b/results.go @@ -84,7 +84,7 @@ func (r Result) extractIntoPtr(to any, label string) error { } toValue := reflect.ValueOf(to) - if toValue.Kind() == reflect.Ptr { + if toValue.Kind() == reflect.Pointer { toValue = toValue.Elem() } @@ -117,6 +117,9 @@ func (r Result) extractIntoPtr(to any, label string) error { // a struct that is never used, but it's good enough to // trigger the UnmarshalJSON method. for i := 0; i < newType.NumField(); i++ { + if newType.Field(i).Kind() != reflect.Struct { + continue + } s := newType.Field(i).Addr().Interface() // Unmarshal is used rather than NewDecoder to also work @@ -184,15 +187,24 @@ func (r Result) ExtractIntoStructPtr(to any, label string) error { return r.Err } + if to == nil { + return fmt.Errorf("expected pointer, got %T", to) + } + t := reflect.TypeOf(to) - if k := t.Kind(); k != reflect.Ptr { - return fmt.Errorf("Expected pointer, got %v", k) + if k := t.Kind(); k != reflect.Pointer { + return fmt.Errorf("expected pointer, got %v", k) } + + if reflect.ValueOf(to).IsNil() { + return fmt.Errorf("expected pointer, got %T", to) + } + switch t.Elem().Kind() { case reflect.Struct: return r.extractIntoPtr(to, label) default: - return fmt.Errorf("Expected pointer to struct, got: %v", t) + return fmt.Errorf("expected pointer to struct, got: %v", t) } } @@ -210,15 +222,24 @@ func (r Result) ExtractIntoSlicePtr(to any, label string) error { return r.Err } + if to == nil { + return fmt.Errorf("expected pointer, got %T", to) + } + t := reflect.TypeOf(to) - if k := t.Kind(); k != reflect.Ptr { - return fmt.Errorf("Expected pointer, got %v", k) + if k := t.Kind(); k != reflect.Pointer { + return fmt.Errorf("expected pointer, got %v", k) } + + if reflect.ValueOf(to).IsNil() { + return fmt.Errorf("expected pointer, got %T", to) + } + switch t.Elem().Kind() { case reflect.Slice: return r.extractIntoPtr(to, label) default: - return fmt.Errorf("Expected pointer to slice, got: %v", t) + return fmt.Errorf("expected pointer to slice, got: %v", t) } } diff --git a/script/acceptancetest b/script/acceptancetest deleted file mode 100755 index 234f5b0bb6..0000000000 --- a/script/acceptancetest +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -# -# Run acceptance tests. - -# We intentionally don't set '-u' (error on unbound variables) or '-e' (exit on -# first failure) initially since DevStack is not designed to run with these -# flags and things crash and burn *spectacularly* 🔥🔥🔥 -set -xo pipefail - -source $(dirname $0)/stackenv - -# ...but we can do it after the fact -set -eu - -timeout="60m" -failed= - -LOG_DIR=${LOG_DIR:-} -if [[ -z "${LOG_DIR}" ]]; then - echo "LOG_DIR not set, will set a temp directory" - LOG_DIR=/tmp/devstack-logs -fi -mkdir -p ${LOG_DIR} - -go test -v -timeout $timeout -tags "fixtures acceptance" ${PACKAGE:-./internal/acceptance/openstack/...} $@ |& tee -a ${LOG_DIR}/acceptance_tests.log diff --git a/script/bootstrap b/script/bootstrap deleted file mode 100755 index 78a195dcf7..0000000000 --- a/script/bootstrap +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -# -# This script helps new contributors set up their local workstation for -# gophercloud development and contributions. - -# Create the environment -export GOPATH=$HOME/go/gophercloud -mkdir -p $GOPATH - -# Download gophercloud into that environment -go get github.com/gophercloud/gophercloud -cd $GOPATH/src/github.com/gophercloud/gophercloud -git checkout master - -# Write out the env.sh convenience file. -cd $GOPATH -cat <env.sh -#!/bin/bash -export GOPATH=$(pwd) -export GOPHERCLOUD=$GOPATH/src/github.com/gophercloud/gophercloud -EOF -chmod a+x env.sh - -# Make changes immediately available as a convenience. -. ./env.sh diff --git a/script/configure-required-github-jobs b/script/configure-required-github-jobs new file mode 100755 index 0000000000..0ec9251491 --- /dev/null +++ b/script/configure-required-github-jobs @@ -0,0 +1,184 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +WORKFLOWS_DIR="${REPO_ROOT}/.github/workflows" +DRY_RUN=false + +usage() { + cat <&2; usage >&2; exit 1 ;; + esac +done + +command -v yq &>/dev/null || { echo "ERROR: yq not installed (https://github.com/mikefarah/yq)"; exit 1; } +command -v jq &>/dev/null || { echo "ERROR: jq not installed"; exit 1; } +command -v gh &>/dev/null || { echo "ERROR: gh CLI not installed"; exit 1; } +gh auth status &>/dev/null || { echo "ERROR: run 'gh auth login' first"; exit 1; } + +# Our non-required status check settings are defined here. These should likely +# never change. +declare -A EXPECTED_SETTINGS=( + [".enforce_admins.enabled"]="true" + [".required_linear_history.enabled"]="false" + [".allow_force_pushes.enabled"]="false" + [".allow_deletions.enabled"]="false" + [".required_conversation_resolution.enabled"]="true" + [".required_status_checks.strict"]="false" + [".required_pull_request_reviews.required_approving_review_count"]="1" + [".required_pull_request_reviews.dismiss_stale_reviews"]="true" + [".required_pull_request_reviews.require_last_push_approval"]="true" +) + +# Check names listed here are excluded from required status checks even if +# their workflow has an `on: merge_group` trigger. Add entries using the exact +# name format: "" (with resolved matrix values). +SKIP_CHECKS=( + "Magnum on OpenStack master" # https://github.com/gophercloud/gophercloud/issues/3818 + "Neutron on OpenStack master" # https://github.com/gophercloud/gophercloud/issues/3817 + "Zun on OpenStack master" # https://bugs.launchpad.net/zun/+bug/2158372 + "finish" # coveralls parallel-finish job, not a test gate +) + +protection="$(gh api \ + -H "Accept: application/vnd.github+json" \ + "/repos/gophercloud/gophercloud/branches/main/protection")" + +settings_ok=true +for path in "${!EXPECTED_SETTINGS[@]}"; do + actual="$(jq -r "${path}" <<< "${protection}")" + expected="${EXPECTED_SETTINGS[${path}]}" + if [[ "${actual}" != "${expected}" ]]; then + echo "ERROR: branch protection drift: ${path} = ${actual} (expected ${expected})" >&2 + settings_ok=false + fi +done +if ! "${settings_ok}"; then + echo "Branch protection settings have drifted." >&2 + exit 1 +fi + +echo "Non-required status check branch protection settings are as expected." +echo + +current_contexts=() +while IFS= read -r ctx; do + current_contexts+=("${ctx}") +done < <(jq -r '.required_status_checks.contexts[]?' <<< "${protection}" | sort) + +updated_contexts=() +for f in "${WORKFLOWS_DIR}"/*.yaml; do + # Only consider workflows that run in the merge queue + grep -q "merge_group" "${f}" || continue + + while IFS= read -r job_id; do + # Use the job's display name if set, otherwise fall back to the job ID + job_name="$(yq ".jobs[\"${job_id}\"].name // \"${job_id}\"" "${f}")" + + # Check whether this job has a matrix 'include' with a 'name' field + matrix_names="$(yq ".jobs[\"${job_id}\"].strategy.matrix.include[].name" "${f}" 2>/dev/null || true)" + + if [[ -n "${matrix_names}" ]]; then + # Expand each matrix entry, replacing ${{ matrix.name }} in the job name + while IFS= read -r matrix_val; do + resolved="${job_name//\$\{\{ matrix.name \}\}/${matrix_val}}" + updated_contexts+=("${resolved}") + done <<< "${matrix_names}" + else + updated_contexts+=("${job_name}") + fi + done < <(yq '.jobs | keys | .[]' "${f}") +done + +filtered_contexts=() +for ctx in "${updated_contexts[@]}"; do + skip=false + for skip_ctx in "${SKIP_CHECKS[@]}"; do + [[ "${ctx}" == "${skip_ctx}" ]] && { skip=true; break; } + done + "${skip}" || filtered_contexts+=("${ctx}") +done + +added_contexts=() +removed_contexts=() + +for ctx in "${filtered_contexts[@]}"; do + found=false + for cur in "${current_contexts[@]}"; do + [[ "${ctx}" == "${cur}" ]] && { found=true; break; } + done + "${found}" || added_contexts+=("${ctx}") +done + +for cur in "${current_contexts[@]}"; do + found=false + for ctx in "${filtered_contexts[@]}"; do + [[ "${cur}" == "${ctx}" ]] && { found=true; break; } + done + "${found}" || removed_contexts+=("${cur}") +done + +if [[ ${#added_contexts[@]} -eq 0 && ${#removed_contexts[@]} -eq 0 ]]; then + echo "Required status checks: no changes (${#filtered_contexts[@]} checks already configured)." +else + echo "Required status checks diff:" + for ctx in "${removed_contexts[@]}"; do printf ' - %s\n' "${ctx}"; done + for ctx in "${added_contexts[@]}"; do printf ' + %s\n' "${ctx}"; done +fi + +if "${DRY_RUN}"; then + echo "" + echo "Dry-run mode — not applying." + exit 0 +fi + +echo "" +echo "Applying to gophercloud/gophercloud:main ..." + +contexts_json="$(printf '%s\n' "${filtered_contexts[@]}" | jq -R . | jq -s .)" + +payload="$(jq -n \ + --argjson contexts "${contexts_json}" \ + '{ + required_status_checks: {strict: false, contexts: $contexts}, + enforce_admins: true, + required_pull_request_reviews: { + required_approving_review_count: 1, + dismiss_stale_reviews: true, + require_last_push_approval: true + }, + restrictions: null, + required_linear_history: false, + allow_force_pushes: false, + allow_deletions: false, + required_conversation_resolution: true + }')" + +gh api \ + --method PUT \ + -H "Accept: application/vnd.github+json" \ + "/repos/gophercloud/gophercloud/branches/main/protection" \ + --input - <<< "${payload}" + +echo "Done. Verify at: https://github.com/gophercloud/gophercloud/settings/branches" diff --git a/script/coverage b/script/coverage deleted file mode 100755 index cd4dec06e5..0000000000 --- a/script/coverage +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -# -# Run unit tests with coverage enabled. - -set -euxo pipefail - -n=1 -for testpkg in $(go list ./testing ./.../testing); do - covpkg="${testpkg/"/testing"/}" - go test -covermode count -coverprofile "testing_"$n.coverprofile -coverpkg "$covpkg" "$testpkg" 2>/dev/null - n=$((n+1)) -done - -base_pkg=$(go list) -# Look for additional test files -for path in $(find . -path '*/testing' -prune -o -path '*/internal' -prune -o -name '*_test.go' -exec dirname {} \; | uniq); do - pkg="${base_pkg}${path:1}" - go test -covermode count -coverprofile "testing_"$n.coverprofile -coverpkg "$pkg" "$pkg" 2>/dev/null - n=$((n+1)) -done - -gocovmerge `ls *.coverprofile` > cover.out -rm ./*.coverprofile diff --git a/script/format b/script/format deleted file mode 100755 index 1bbf74ce39..0000000000 --- a/script/format +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash -# -# Run 'go fmt' to warn about unformatted code - -set -euxo pipefail - -go fmt ./... diff --git a/script/getenvvar b/script/getenvvar new file mode 100755 index 0000000000..502745242b --- /dev/null +++ b/script/getenvvar @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# /// script +# requires-python = ">=3.13" +# dependencies = [ +# "pyyaml>=6", +# ] +# /// + +""" +Set environment variables required for the CI jobs by inspection of the +clouds.yaml file. This is useful where you only have this file. + +To set variables: + + $ eval $(./script/getenvvar) + +To unset them: + + $ unset $(compgen -v | grep OS_) +""" + +import argparse +import os +from pathlib import Path +import sys + +import yaml + +parser = argparse.ArgumentParser() +parser.add_argument( + 'cloud', + default=os.getenv('OS_CLOUD'), + nargs='?', + help="Cloud to export credentials for", +) + +args = parser.parse_args() + +for p in ( + Path('clouds.yaml'), + Path('~/.config/openstack/clouds.yaml').expanduser(), + Path('/etc/openstack/clouds.yaml'), +): + if not p.exists(): + continue + + with p.open() as fh: + data = yaml.safe_load(fh) + break +else: + print('Could not find clouds.yaml file', file=sys.stderr) + sys.exit(1) + +if not args.cloud: + print('Need to provide cloud argument or set OS_CLOUD', file=sys.stderr) + sys.exit(1) + +if args.cloud not in data.get('clouds', {}) or {}: + print(f'Could not find cloud {args.cloud} in {str(p)}', file=sys.stderr) + sys.exit(1) + +cloud = data['clouds'][args.cloud] + +if 'auth' not in cloud: + print(f'Missing auth section for cloud {cloud}', file=sys.stderr) + sys.exit(1) + +auth = cloud['auth'] + +if 'username' not in auth or 'password' not in auth: + print('Only password authentication supported', file=sys.stderr) + sys.exit(1) + +# FIXME: This should work but does not, since the check for auth credentials +# is just 'OS_USERNAME == admin' + +# user_id = auth.get('user_id') +# project_id = auth.get('project_id') +# if not user_id or not project_id: +# import openstack +# conn = openstack.connect(args.cloud) +# auth_ref = conn.config.get_auth().get_auth_ref(conn.session) +# +# if not user_id: +# user_id = auth_ref.user_id +# +# if not project_id: +# project_id = auth_ref.project_id +# +# result = f""" +# unset OS_CLOUD +# export OS_AUTH_URL={auth['auth_url']} +# export OS_USERID={user_id} +# export OS_PASSWORD={auth['password']} +# export OS_PROJECT_ID={project_id} +# export OS_REGION_NAME={cloud['region_name']} +# """.strip() + +result = f""" +unset OS_CLOUD; +export OS_AUTH_URL={auth['auth_url']}; +export OS_USERNAME={auth['username']}; +export OS_PASSWORD={auth['password']}; +export OS_PROJECT_NAME={auth['project_name']}; +export OS_DOMAIN_ID={auth['user_domain_id']}; +export OS_REGION_NAME={cloud['region_name']}; +""" + +print(result.strip()) diff --git a/script/stackenv b/script/stackenv index 6e2a32ebeb..0dffd4ed1e 100644 --- a/script/stackenv +++ b/script/stackenv @@ -59,6 +59,17 @@ export OS_FLAVOR_ID="$_FLAVOR_ID" export OS_FLAVOR_ID_RESIZE="$_FLAVOR_ALT_ID" EOL +if _=$(openstack service list | grep database); then + _DB_DATASTORE_TYPE=$(openstack datastore list -f value -c Name | head -1) + if [[ -n "$_DB_DATASTORE_TYPE" ]]; then + _DB_DATASTORE_VERSION=$(openstack datastore version list "$_DB_DATASTORE_TYPE" -f value -c Name | head -1) + cat >> "openrc" < v2 { logFatal(t, fmt.Sprintf("The first value \"%v\" is greater than the second value \"%v\"", v1, v2)) } } @@ -432,7 +499,7 @@ func AssertIntLesserOrEqual(t *testing.T, v1 int, v2 int) { func AssertIntGreaterOrEqual(t *testing.T, v1 int, v2 int) { t.Helper() - if !(v1 >= v2) { + if v1 < v2 { logFatal(t, fmt.Sprintf("The first value \"%v\" is lesser than the second value \"%v\"", v1, v2)) } } diff --git a/testhelper/fixture/helper.go b/testhelper/fixture/helper.go index a0f3b5c7b2..1967b0f48d 100644 --- a/testhelper/fixture/helper.go +++ b/testhelper/fixture/helper.go @@ -9,8 +9,8 @@ import ( "github.com/gophercloud/gophercloud/v2/testhelper/client" ) -func SetupHandler(t *testing.T, url, method, requestBody, responseBody string, status int) { - th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { +func SetupHandler(t *testing.T, fakeServer th.FakeServer, url, method, requestBody, responseBody string, status int) { + fakeServer.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, method) th.TestHeader(t, r, "X-Auth-Token", client.TokenID) @@ -25,7 +25,7 @@ func SetupHandler(t *testing.T, url, method, requestBody, responseBody string, s w.WriteHeader(status) if responseBody != "" { - fmt.Fprintf(w, responseBody) + fmt.Fprint(w, responseBody) } }) } diff --git a/testhelper/http_responses.go b/testhelper/http_responses.go index 28ecb1e53e..d0034a5a7b 100644 --- a/testhelper/http_responses.go +++ b/testhelper/http_responses.go @@ -13,48 +13,28 @@ import ( "testing" ) -var ( +type FakeServer struct { // Mux is a multiplexer that can be used to register handlers. Mux *http.ServeMux // Server is an in-memory HTTP server for testing. Server *httptest.Server -) - -// SetupPersistentPortHTTP prepares the Mux and Server listening specific port. -func SetupPersistentPortHTTP(t *testing.T, port int) { - l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) - if err != nil { - t.Errorf("Failed to listen to 127.0.0.1:%d: %s", port, err) - } - Mux = http.NewServeMux() - Server = httptest.NewUnstartedServer(Mux) - Server.Listener = l - Server.Start() } -// SetupHTTP prepares the Mux and Server. -func SetupHTTP() { - Mux = http.NewServeMux() - Server = httptest.NewServer(Mux) -} - -// TeardownHTTP releases HTTP-related resources. -func TeardownHTTP() { - Server.Close() +func (fakeServer FakeServer) Teardown() { + fakeServer.Server.Close() } -// Endpoint returns a fake endpoint that will actually target the Mux. -func Endpoint() string { - return Server.URL + "/" +func (fakeServer FakeServer) Endpoint() string { + return fakeServer.Server.URL + "/" } // Serves a static content at baseURL/relPath -func ServeFile(t *testing.T, baseURL, relPath, contentType, content string) string { +func (fakeServer FakeServer) ServeFile(t *testing.T, baseURL, relPath, contentType, content string) string { rawURL := strings.Join([]string{baseURL, relPath}, "/") parsedURL, err := url.Parse(rawURL) AssertNoErr(t, err) - Mux.HandleFunc(parsedURL.Path, func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc(parsedURL.Path, func(w http.ResponseWriter, r *http.Request) { TestMethod(t, r, "GET") w.Header().Set("Content-Type", contentType) w.WriteHeader(http.StatusOK) @@ -64,6 +44,34 @@ func ServeFile(t *testing.T, baseURL, relPath, contentType, content string) stri return rawURL } +// SetupPersistentPortHTTP prepares the Mux and Server listening specific port. +func SetupPersistentPortHTTP(t *testing.T, port int) FakeServer { + mux := http.NewServeMux() + server := httptest.NewUnstartedServer(mux) + l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) + if err != nil { + t.Fatalf("Failed to listen to 127.0.0.1:%d: %s", port, err) + } + server.Listener = l + server.Start() + + return FakeServer{ + Mux: mux, + Server: server, + } +} + +// SetupHTTP prepares the Mux and Server. +func SetupHTTP() FakeServer { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + + return FakeServer{ + Mux: mux, + Server: server, + } +} + // TestFormValues ensures that all the URL parameters given to the http.Request are the same as values. func TestFormValues(t *testing.T, r *http.Request, values map[string]string) { want := url.Values{} diff --git a/testing/auth_options_test.go b/testing/auth_options_test.go index 1f9b230b50..67b06174e6 100644 --- a/testing/auth_options_test.go +++ b/testing/auth_options_test.go @@ -1,7 +1,6 @@ package testing import ( - "reflect" "testing" "github.com/gophercloud/gophercloud/v2" @@ -15,11 +14,12 @@ func TestToTokenV3ScopeMap(t *testing.T) { domainName := "Default" var successCases = []struct { + name string opts gophercloud.AuthOptions expected map[string]any }{ - // System-scoped { + "System-scoped", gophercloud.AuthOptions{ Scope: &gophercloud.AuthScope{ System: true, @@ -31,8 +31,8 @@ func TestToTokenV3ScopeMap(t *testing.T) { }, }, }, - // Trust-scoped { + "Trust-scoped", gophercloud.AuthOptions{ Scope: &gophercloud.AuthScope{ TrustID: "05144328-1f7d-46a9-a978-17eaad187077", @@ -44,8 +44,8 @@ func TestToTokenV3ScopeMap(t *testing.T) { }, }, }, - // Project-scoped (ID) { + "Project-scoped (ID)", gophercloud.AuthOptions{ Scope: &gophercloud.AuthScope{ ProjectID: projectID, @@ -57,8 +57,8 @@ func TestToTokenV3ScopeMap(t *testing.T) { }, }, }, - // Project-scoped (name) { + "Project-scoped (name)", gophercloud.AuthOptions{ Scope: &gophercloud.AuthScope{ ProjectName: projectName, @@ -74,8 +74,8 @@ func TestToTokenV3ScopeMap(t *testing.T) { }, }, }, - // Domain-scoped (ID) { + "Domain-scoped (ID)", gophercloud.AuthOptions{ Scope: &gophercloud.AuthScope{ DomainID: domainID, @@ -87,8 +87,8 @@ func TestToTokenV3ScopeMap(t *testing.T) { }, }, }, - // Domain-scoped (name) { + "Domain-scoped (name)", gophercloud.AuthOptions{ Scope: &gophercloud.AuthScope{ DomainName: domainName, @@ -100,8 +100,8 @@ func TestToTokenV3ScopeMap(t *testing.T) { }, }, }, - // Empty with project fallback (ID) { + "Empty with project fallback (ID)", gophercloud.AuthOptions{ TenantID: projectID, Scope: nil, @@ -112,8 +112,8 @@ func TestToTokenV3ScopeMap(t *testing.T) { }, }, }, - // Empty with project fallback (name) { + "Empty with project fallback (name)", gophercloud.AuthOptions{ TenantName: projectName, DomainName: domainName, @@ -128,8 +128,8 @@ func TestToTokenV3ScopeMap(t *testing.T) { }, }, }, - // Empty without fallback { + "Empty without fallback", gophercloud.AuthOptions{ Scope: nil, }, @@ -137,17 +137,20 @@ func TestToTokenV3ScopeMap(t *testing.T) { }, } for _, successCase := range successCases { - actual, err := successCase.opts.ToTokenV3ScopeMap() - th.AssertNoErr(t, err) - th.AssertDeepEquals(t, successCase.expected, actual) + t.Run(successCase.name, func(t *testing.T) { + actual, err := successCase.opts.ToTokenV3ScopeMap() + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, successCase.expected, actual) + }) } var failCases = []struct { + name string opts gophercloud.AuthOptions expected error }{ - // Project-scoped with name but missing domain ID/name { + "Project-scoped with name but missing domain ID/name", gophercloud.AuthOptions{ Scope: &gophercloud.AuthScope{ ProjectName: "admin", @@ -155,8 +158,8 @@ func TestToTokenV3ScopeMap(t *testing.T) { }, gophercloud.ErrScopeDomainIDOrDomainName{}, }, - // Project-scoped with both project name and project ID { + "Project-scoped with both project name and project ID", gophercloud.AuthOptions{ Scope: &gophercloud.AuthScope{ ProjectName: "admin", @@ -166,8 +169,8 @@ func TestToTokenV3ScopeMap(t *testing.T) { }, gophercloud.ErrScopeProjectIDOrProjectName{}, }, - // Project-scoped with name and unnecessary domain ID { + "Project-scoped with name and unnecessary domain ID", gophercloud.AuthOptions{ Scope: &gophercloud.AuthScope{ ProjectID: "685038cd-3c25-4faf-8f9b-78c18e503190", @@ -176,8 +179,8 @@ func TestToTokenV3ScopeMap(t *testing.T) { }, gophercloud.ErrScopeProjectIDAlone{}, }, - // Project-scoped with name and unnecessary domain name { + "Project-scoped with name and unnecessary domain name", gophercloud.AuthOptions{ Scope: &gophercloud.AuthScope{ ProjectID: "685038cd-3c25-4faf-8f9b-78c18e503190", @@ -186,8 +189,8 @@ func TestToTokenV3ScopeMap(t *testing.T) { }, gophercloud.ErrScopeProjectIDAlone{}, }, - // Domain-scoped with both domain name and domain ID { + "Domain-scoped with both domain name and domain ID", gophercloud.AuthOptions{ Scope: &gophercloud.AuthScope{ DomainID: "e4b515b8-e453-49d8-9cce-4bec244fa84e", @@ -198,7 +201,9 @@ func TestToTokenV3ScopeMap(t *testing.T) { }, } for _, failCase := range failCases { - _, err := failCase.opts.ToTokenV3ScopeMap() - th.AssertDeepEquals(t, reflect.TypeOf(failCase.expected), reflect.TypeOf(err)) + t.Run(failCase.name, func(t *testing.T) { + _, err := failCase.opts.ToTokenV3ScopeMap() + th.AssertTypeEquals(t, failCase.expected, err) + }) } } diff --git a/testing/endpoint_search_test.go b/testing/endpoint_search_test.go index 278955ecdf..c1ae9b92db 100644 --- a/testing/endpoint_search_test.go +++ b/testing/endpoint_search_test.go @@ -10,11 +10,11 @@ import ( func TestApplyDefaultsToEndpointOpts(t *testing.T) { eo := gophercloud.EndpointOpts{Availability: gophercloud.AvailabilityPublic} eo.ApplyDefaults("compute") - expected := gophercloud.EndpointOpts{Availability: gophercloud.AvailabilityPublic, Type: "compute"} + expected := gophercloud.EndpointOpts{Availability: gophercloud.AvailabilityPublic, Type: "compute", Aliases: []string{}} th.CheckDeepEquals(t, expected, eo) eo = gophercloud.EndpointOpts{Type: "compute"} eo.ApplyDefaults("object-store") - expected = gophercloud.EndpointOpts{Availability: gophercloud.AvailabilityPublic, Type: "compute"} + expected = gophercloud.EndpointOpts{Availability: gophercloud.AvailabilityPublic, Type: "compute", Aliases: []string{}} th.CheckDeepEquals(t, expected, eo) } diff --git a/testing/errors_test.go b/testing/errors_test.go index 21e6b2c2e2..a27d5f0bbc 100644 --- a/testing/errors_test.go +++ b/testing/errors_test.go @@ -19,12 +19,12 @@ func TestErrUnexpectedResponseCode(t *testing.T) { ResponseHeader: nil, } - th.AssertEquals(t, err.GetStatusCode(), 404) - th.AssertEquals(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound), true) - th.AssertEquals(t, gophercloud.ResponseCodeIs(err, http.StatusInternalServerError), false) + th.AssertEquals(t, 404, err.GetStatusCode()) + th.AssertTrue(t, gophercloud.ResponseCodeIs(err, http.StatusNotFound)) + th.AssertFalse(t, gophercloud.ResponseCodeIs(err, http.StatusInternalServerError)) //even if application code wraps our error, ResponseCodeIs() should still work errWrapped := fmt.Errorf("could not frobnicate the foobar: %w", err) - th.AssertEquals(t, gophercloud.ResponseCodeIs(errWrapped, http.StatusNotFound), true) - th.AssertEquals(t, gophercloud.ResponseCodeIs(errWrapped, http.StatusInternalServerError), false) + th.AssertTrue(t, gophercloud.ResponseCodeIs(errWrapped, http.StatusNotFound)) + th.AssertFalse(t, gophercloud.ResponseCodeIs(errWrapped, http.StatusInternalServerError)) } diff --git a/testing/params_test.go b/testing/params_test.go index 9e74bdd9ea..146fc27025 100644 --- a/testing/params_test.go +++ b/testing/params_test.go @@ -2,7 +2,6 @@ package testing import ( "net/url" - "reflect" "testing" "time" @@ -167,10 +166,12 @@ func TestBuildRequestBody(t *testing.T) { } var successCases = []struct { + name string opts AuthOptions expected map[string]any }{ { + "Password", AuthOptions{ PasswordCredentials: &PasswordCredentials{ Username: "me", @@ -187,6 +188,7 @@ func TestBuildRequestBody(t *testing.T) { }, }, { + "Token", AuthOptions{ TokenCredentials: &TokenCredentials{ ID: "1234567", @@ -203,16 +205,20 @@ func TestBuildRequestBody(t *testing.T) { } for _, successCase := range successCases { - actual, err := gophercloud.BuildRequestBody(successCase.opts, "auth") - th.AssertNoErr(t, err) - th.AssertDeepEquals(t, successCase.expected, actual) + t.Run(successCase.name, func(t *testing.T) { + actual, err := gophercloud.BuildRequestBody(successCase.opts, "auth") + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, successCase.expected, actual) + }) } var failCases = []struct { + name string opts AuthOptions expected error }{ { + "Conflicting tenant name and ID", AuthOptions{ TenantID: "987654321", TenantName: "me", @@ -220,6 +226,7 @@ func TestBuildRequestBody(t *testing.T) { gophercloud.ErrMissingInput{}, }, { + "Conflicting password and token auth", AuthOptions{ TokenCredentials: &TokenCredentials{ ID: "1234567", @@ -232,6 +239,7 @@ func TestBuildRequestBody(t *testing.T) { gophercloud.ErrMissingInput{}, }, { + "Missing Username or UserID", AuthOptions{ PasswordCredentials: &PasswordCredentials{ Password: "swordfish", @@ -240,6 +248,7 @@ func TestBuildRequestBody(t *testing.T) { gophercloud.ErrMissingInput{}, }, { + "Missing filler fields", AuthOptions{ PasswordCredentials: &PasswordCredentials{ Username: "me", @@ -254,8 +263,10 @@ func TestBuildRequestBody(t *testing.T) { } for _, failCase := range failCases { - _, err := gophercloud.BuildRequestBody(failCase.opts, "auth") - th.AssertDeepEquals(t, reflect.TypeOf(failCase.expected), reflect.TypeOf(err)) + t.Run(failCase.name, func(t *testing.T) { + _, err := gophercloud.BuildRequestBody(failCase.opts, "auth") + th.AssertTypeEquals(t, failCase.expected, err) + }) } createdAt := time.Date(2018, 1, 4, 10, 00, 12, 0, time.UTC) diff --git a/testing/provider_client_test.go b/testing/provider_client_test.go index 18704fd4b1..f72d47c4c8 100644 --- a/testing/provider_client_test.go +++ b/testing/provider_client_test.go @@ -79,10 +79,10 @@ func TestConcurrentReauth(t *testing.T) { return nil } - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { if r.Header.Get("X-Auth-Token") != postreauthTok { w.WriteHeader(http.StatusUnauthorized) info.mut.Lock() @@ -99,7 +99,7 @@ func TestConcurrentReauth(t *testing.T) { } w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) wg := new(sync.WaitGroup) @@ -113,7 +113,7 @@ func TestConcurrentReauth(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - resp, err := p.Request(context.TODO(), "GET", fmt.Sprintf("%s/route", th.Endpoint()), reqopts) + resp, err := p.Request(context.TODO(), "GET", fmt.Sprintf("%s/route", fakeServer.Endpoint()), reqopts) th.CheckNoErr(t, err) if resp == nil { t.Errorf("got a nil response") @@ -161,7 +161,7 @@ func TestReauthEndLoop(t *testing.T) { if info.reauthAttempts > 5 { info.maxReauthReached = true - return fmt.Errorf("Max reauthentication attempts reached") + return fmt.Errorf("max reauthentication attempts reached") } p.SetThrowaway(true) p.AuthenticatedHeaders() @@ -171,10 +171,10 @@ func TestReauthEndLoop(t *testing.T) { return nil } - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { // route always return 401 w.WriteHeader(http.StatusUnauthorized) }) @@ -190,7 +190,7 @@ func TestReauthEndLoop(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - _, err := p.Request(context.TODO(), "GET", fmt.Sprintf("%s/route", th.Endpoint()), reqopts) + _, err := p.Request(context.TODO(), "GET", fmt.Sprintf("%s/route", fakeServer.Endpoint()), reqopts) mut.Lock() defer mut.Unlock() @@ -210,10 +210,10 @@ func TestReauthEndLoop(t *testing.T) { } wg.Wait() - th.AssertEquals(t, info.reauthAttempts, 6) - th.AssertEquals(t, info.maxReauthReached, true) - th.AssertEquals(t, errAfter > 1, true) - th.AssertEquals(t, errUnable < 20, true) + th.AssertEquals(t, 6, info.reauthAttempts) + th.AssertTrue(t, info.maxReauthReached) + th.AssertTrue(t, errAfter > 1) + th.AssertTrue(t, errUnable < 20) } func TestRequestThatCameDuringReauthWaitsUntilItIsCompleted(t *testing.T) { @@ -256,10 +256,10 @@ func TestRequestThatCameDuringReauthWaitsUntilItIsCompleted(t *testing.T) { return nil } - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { if r.Header.Get("X-Auth-Token") != postreauthTok { info.mut.Lock() info.failedAuths++ @@ -276,7 +276,7 @@ func TestRequestThatCameDuringReauthWaitsUntilItIsCompleted(t *testing.T) { } w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) wg := new(sync.WaitGroup) @@ -293,7 +293,7 @@ func TestRequestThatCameDuringReauthWaitsUntilItIsCompleted(t *testing.T) { if i != 0 { <-info.reauthCh } - resp, err := p.Request(context.TODO(), "GET", fmt.Sprintf("%s/route", th.Endpoint()), reqopts) + resp, err := p.Request(context.TODO(), "GET", fmt.Sprintf("%s/route", fakeServer.Endpoint()), reqopts) th.CheckNoErr(t, err) if resp == nil { t.Errorf("got a nil response") @@ -339,13 +339,13 @@ func TestRequestReauthsAtMostOnce(t *testing.T) { return nil } - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() requestCounter := 0 var requestCounterMutex sync.Mutex - th.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { requestCounterMutex.Lock() requestCounter++ //avoid infinite loop @@ -363,7 +363,7 @@ func TestRequestReauthsAtMostOnce(t *testing.T) { // the part before the colon), but when encountering another 401 response, we // did not attempt reauthentication again and just passed that 401 response to // the caller as ErrDefault401. - _, err := p.Request(context.TODO(), "GET", th.Endpoint()+"/route", &gophercloud.RequestOpts{}) + _, err := p.Request(context.TODO(), "GET", fakeServer.Endpoint()+"/route", &gophercloud.RequestOpts{}) expectedErrorRx := regexp.MustCompile(`^Successfully re-authenticated, but got error executing request: Expected HTTP response code \[200\] when accessing \[GET http://[^/]*//route\], but got 401 instead: unauthorized$`) if !expectedErrorRx.MatchString(err.Error()) { t.Errorf("expected error that looks like %q, but got %q", expectedErrorRx.String(), err.Error()) @@ -501,17 +501,17 @@ func TestRequestRetry(t *testing.T) { p.RetryBackoffFunc = retryBackoffTest(&retryCounter, t) - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Retry-After", "1") //always reply 429 http.Error(w, "retry later", http.StatusTooManyRequests) }) - _, err := p.Request(context.TODO(), "GET", th.Endpoint()+"/route", &gophercloud.RequestOpts{}) + _, err := p.Request(context.TODO(), "GET", fakeServer.Endpoint()+"/route", &gophercloud.RequestOpts{}) if err == nil { t.Fatal("expecting error, got nil") } @@ -528,17 +528,17 @@ func TestRequestRetryHTTPDate(t *testing.T) { p.RetryBackoffFunc = retryBackoffTest(&retryCounter, t) - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Retry-After", time.Now().Add(1*time.Second).UTC().Format(http.TimeFormat)) //always reply 429 http.Error(w, "retry later", http.StatusTooManyRequests) }) - _, err := p.Request(context.TODO(), "GET", th.Endpoint()+"/route", &gophercloud.RequestOpts{}) + _, err := p.Request(context.TODO(), "GET", fakeServer.Endpoint()+"/route", &gophercloud.RequestOpts{}) if err == nil { t.Fatal("expecting error, got nil") } @@ -555,21 +555,21 @@ func TestRequestRetryError(t *testing.T) { p.RetryBackoffFunc = retryBackoffTest(&retryCounter, t) - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Retry-After", "foo bar") //always reply 429 http.Error(w, "retry later", http.StatusTooManyRequests) }) - _, err := p.Request(context.TODO(), "GET", th.Endpoint()+"/route", &gophercloud.RequestOpts{}) + _, err := p.Request(context.TODO(), "GET", fakeServer.Endpoint()+"/route", &gophercloud.RequestOpts{}) if err == nil { t.Fatal("expecting error, got nil") } - th.AssertEquals(t, retryCounter, uint(0)) + th.AssertEquals(t, uint(0), retryCounter) } func TestRequestRetrySuccess(t *testing.T) { @@ -582,19 +582,19 @@ func TestRequestRetrySuccess(t *testing.T) { p.RetryBackoffFunc = retryBackoffTest(&retryCounter, t) - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { //always reply 200 http.Error(w, "retry later", http.StatusOK) }) - _, err := p.Request(context.TODO(), "GET", th.Endpoint()+"/route", &gophercloud.RequestOpts{}) + _, err := p.Request(context.TODO(), "GET", fakeServer.Endpoint()+"/route", &gophercloud.RequestOpts{}) if err != nil { t.Fatal(err) } - th.AssertEquals(t, retryCounter, uint(0)) + th.AssertEquals(t, uint(0), retryCounter) } func TestRequestRetryContext(t *testing.T) { @@ -614,17 +614,17 @@ func TestRequestRetryContext(t *testing.T) { p.RetryBackoffFunc = retryBackoffTest(&retryCounter, t) - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() - th.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Retry-After", "1") //always reply 429 http.Error(w, "retry later", http.StatusTooManyRequests) }) - _, err := p.Request(ctx, "GET", th.Endpoint()+"/route", &gophercloud.RequestOpts{}) + _, err := p.Request(ctx, "GET", fakeServer.Endpoint()+"/route", &gophercloud.RequestOpts{}) if err == nil { t.Fatal("expecting error, got nil") } @@ -643,11 +643,11 @@ func TestRequestGeneralRetry(t *testing.T) { return nil } - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() count := 0 - th.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { if count < 3 { http.Error(w, "bad gateway", http.StatusBadGateway) count += 1 @@ -656,7 +656,7 @@ func TestRequestGeneralRetry(t *testing.T) { } }) - _, err := p.Request(context.TODO(), "GET", th.Endpoint()+"/route", &gophercloud.RequestOpts{}) + _, err := p.Request(context.TODO(), "GET", fakeServer.Endpoint()+"/route", &gophercloud.RequestOpts{}) if err != nil { t.Fatal("expecting nil, got err") } @@ -671,11 +671,11 @@ func TestRequestGeneralRetryAbort(t *testing.T) { return err } - th.SetupHTTP() - defer th.TeardownHTTP() + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() count := 0 - th.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { + fakeServer.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { if count < 3 { http.Error(w, "bad gateway", http.StatusBadGateway) count += 1 @@ -684,7 +684,7 @@ func TestRequestGeneralRetryAbort(t *testing.T) { } }) - _, err := p.Request(context.TODO(), "GET", th.Endpoint()+"/route", &gophercloud.RequestOpts{}) + _, err := p.Request(context.TODO(), "GET", fakeServer.Endpoint()+"/route", &gophercloud.RequestOpts{}) if err == nil { t.Fatal("expecting err, got nil") } diff --git a/testing/results_test.go b/testing/results_test.go index 21ef44c802..51ba60499b 100644 --- a/testing/results_test.go +++ b/testing/results_test.go @@ -113,6 +113,40 @@ func TestUnmarshalAnonymousStructs(t *testing.T) { th.AssertEquals(t, "Canada unmarshalled", actual.Location) } +func TestUnmarshalNilStruct(t *testing.T) { + var x *TestPerson + var y TestPerson + + err1 := gophercloud.Result{}.ExtractIntoStructPtr(&x, "") + err2 := gophercloud.Result{}.ExtractIntoStructPtr(nil, "") + err3 := gophercloud.Result{}.ExtractIntoStructPtr(y, "") + err4 := gophercloud.Result{}.ExtractIntoStructPtr(&y, "") + err5 := gophercloud.Result{}.ExtractIntoStructPtr(x, "") + + th.AssertErr(t, err1) + th.AssertErr(t, err2) + th.AssertErr(t, err3) + th.AssertNoErr(t, err4) + th.AssertErr(t, err5) +} + +func TestUnmarshalNilSlice(t *testing.T) { + var x *[]TestPerson + var y []TestPerson + + err1 := gophercloud.Result{}.ExtractIntoSlicePtr(&x, "") + err2 := gophercloud.Result{}.ExtractIntoSlicePtr(nil, "") + err3 := gophercloud.Result{}.ExtractIntoSlicePtr(y, "") + err4 := gophercloud.Result{}.ExtractIntoSlicePtr(&y, "") + err5 := gophercloud.Result{}.ExtractIntoSlicePtr(x, "") + + th.AssertErr(t, err1) + th.AssertErr(t, err2) + th.AssertErr(t, err3) + th.AssertNoErr(t, err4) + th.AssertErr(t, err5) +} + // TestUnmarshalSliceofAnonymousStructs tests if UnmarshalJSON is called on each // of the anonymous structs contained in an overarching struct slice. func TestUnmarshalSliceOfAnonymousStructs(t *testing.T) { diff --git a/testing/service_client_test.go b/testing/service_client_test.go index 6253b698d2..f0877b977d 100644 --- a/testing/service_client_test.go +++ b/testing/service_client_test.go @@ -18,9 +18,9 @@ func TestServiceURL(t *testing.T) { } func TestMoreHeaders(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - th.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { + fakeServer := th.SetupHTTP() + defer fakeServer.Teardown() + fakeServer.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) @@ -29,7 +29,7 @@ func TestMoreHeaders(t *testing.T) { "custom": "header", } c.ProviderClient = new(gophercloud.ProviderClient) - resp, err := c.Get(context.TODO(), fmt.Sprintf("%s/route", th.Endpoint()), nil, nil) + resp, err := c.Get(context.TODO(), fmt.Sprintf("%s/route", fakeServer.Endpoint()), nil, nil) th.AssertNoErr(t, err) - th.AssertEquals(t, resp.Request.Header.Get("custom"), "header") + th.AssertEquals(t, "header", resp.Request.Header.Get("custom")) } diff --git a/testing/util_test.go b/testing/util_test.go index 6b6b425f45..93fa2efb8f 100644 --- a/testing/util_test.go +++ b/testing/util_test.go @@ -47,9 +47,9 @@ func TestWaitForError(t *testing.T) { defer cancel() err := gophercloud.WaitFor(ctx, func(context.Context) (bool, error) { - return false, errors.New("Error has occurred") + return false, errors.New("error has occurred") }) - th.AssertEquals(t, "Error has occurred", err.Error()) + th.AssertEquals(t, "error has occurred", err.Error()) } func TestWaitForPredicateExceed(t *testing.T) { @@ -67,7 +67,7 @@ func TestWaitForPredicateExceed(t *testing.T) { return true, ctx.Err() case <-time.After(4 * time.Second): - return false, errors.New("Just wasting time") + return false, errors.New("just wasting time") } }) th.AssertErrIs(t, err, context.DeadlineExceeded) diff --git a/util.go b/util.go index ad8a7dfaaa..d11a723b1b 100644 --- a/util.go +++ b/util.go @@ -37,9 +37,6 @@ func NormalizePathURL(basePath, rawPath string) (string, error) { absPathSys = filepath.Join(basePath, rawPath) u.Path = filepath.ToSlash(absPathSys) - if err != nil { - return "", err - } u.Scheme = "file" return u.String(), nil }