diff --git a/.github/dependabot.yml b/.github/dependabot.yaml similarity index 100% rename from .github/dependabot.yml rename to .github/dependabot.yaml 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 8a908009e2..ad21863831 100644 --- a/.github/labels.yaml +++ b/.github/labels.yaml @@ -1,12 +1,9 @@ - color: '30ABB9' description: This PR will be backported to v1 name: backport-v1 -- 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: 'E99695' + description: This PR will be backported to v2 + name: backport-v2 - color: 'BCF611' description: A good issue for first-time contributors name: good first issue @@ -19,6 +16,101 @@ - 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 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/check-pr-labels.yaml b/.github/workflows/check-pr-labels.yaml new file mode 100644 index 0000000000..4fbb7fc0b8 --- /dev/null +++ b/.github/workflows/check-pr-labels.yaml @@ -0,0 +1,21 @@ +name: Ready +on: + pull_request_target: + types: + - labeled + - opened + - reopened + - synchronize + - unlabeled + +jobs: + hold: + if: github.event.pull_request.merged == false + runs-on: ubuntu-latest + steps: + - if: > + contains(github.event.pull_request.labels.*.name, 'hold') + run: 'false' + - if: > + !contains(github.event.pull_request.labels.*.name, 'hold') + run: 'true' diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yaml similarity index 100% rename from .github/workflows/codeql-analysis.yml rename to .github/workflows/codeql-analysis.yaml diff --git a/.github/workflows/functional-baremetal.yaml b/.github/workflows/functional-baremetal.yaml index ddaeb1fd14..eb04f8c920 100644 --- a/.github/workflows/functional-baremetal.yaml +++ b/.github/workflows/functional-baremetal.yaml @@ -3,6 +3,7 @@ on: pull_request: paths: - '**baremetal**' + - '.github/workflows/functional-baremetal.yaml' schedule: - cron: '0 0 */3 * *' jobs: @@ -14,43 +15,24 @@ jobs: - 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: "dalmatian" + openstack_version: "stable/2024.2" + ubuntu_version: "22.04" 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: "" runs-on: ubuntu-${{ matrix.ubuntu_version }} name: Deploy OpenStack ${{ matrix.name }} with Ironic and run baremetal acceptance tests 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' - name: Work around broken dnsmasq run: sudo apt-get purge -y dnsmasq-base - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + uses: gophercloud/devstack-action@v0.17 with: branch: ${{ matrix.openstack_version }} conf_overrides: | @@ -91,15 +73,15 @@ jobs: - name: Checkout go uses: actions/setup-go@v5 with: - go-version: '^1.20' + go-version: '^1.22' - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-baremetal 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() @@ -107,5 +89,5 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: functional-baremetal-${{ matrix.name }} + name: functional-baremetal-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* diff --git a/.github/workflows/functional-basic.yaml b/.github/workflows/functional-basic.yaml index cc134dfb91..80808ac181 100644 --- a/.github/workflows/functional-basic.yaml +++ b/.github/workflows/functional-basic.yaml @@ -18,37 +18,34 @@ jobs: openstack_version: "master" ubuntu_version: "22.04" additional_services: "openstack-cli-server" + - name: "dalmatian" + openstack_version: "stable/2024.2" + 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: "" runs-on: ubuntu-${{ matrix.ubuntu_version }} name: Deploy OpenStack ${{ matrix.name }} with defaults and run basic acceptance tests steps: - name: Checkout Gophercloud uses: actions/checkout@v4 - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + uses: gophercloud/devstack-action@v0.17 with: branch: ${{ matrix.openstack_version }} enabled_services: 's-account,s-container,s-object,s-proxy,${{ matrix.additional_services }}' - name: Checkout go uses: actions/setup-go@v5 with: - go-version: '^1.20' + go-version: '^1.22' - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-basic env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: './internal/acceptance/openstack' OS_BRANCH: ${{ matrix.openstack_version }} - name: Generate logs on failure run: ./script/collectlogs @@ -57,5 +54,5 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: functional-basic-${{ matrix.name }} + name: functional-basic-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* diff --git a/.github/workflows/functional-blockstorage.yaml b/.github/workflows/functional-blockstorage.yaml index daf2bb31ce..1a36b0da04 100644 --- a/.github/workflows/functional-blockstorage.yaml +++ b/.github/workflows/functional-blockstorage.yaml @@ -3,6 +3,7 @@ on: pull_request: paths: - '**blockstorage**' + - '.github/workflows/functional-blockstorage.yaml' schedule: - cron: '0 0 */3 * *' jobs: @@ -15,25 +16,21 @@ jobs: openstack_version: "master" ubuntu_version: "22.04" additional_services: "openstack-cli-server" + - name: "dalmatian" + openstack_version: "stable/2024.2" + 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: "" runs-on: ubuntu-${{ matrix.ubuntu_version }} name: Deploy OpenStack ${{ matrix.name }} with Cinder and run blockstorage acceptance tests steps: - name: Checkout Gophercloud uses: actions/checkout@v4 - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + uses: gophercloud/devstack-action@v0.17 with: branch: ${{ matrix.openstack_version }} conf_overrides: | @@ -42,12 +39,13 @@ jobs: - name: Checkout go uses: actions/setup-go@v5 with: - go-version: '^1.20' + go-version: '^1.22' - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-blockstorage env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/blockstorage/..." OS_BRANCH: ${{ matrix.openstack_version }} - name: Generate logs on failure run: ./script/collectlogs @@ -56,5 +54,5 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: functional-blockstorage-${{ matrix.name }} + name: functional-blockstorage-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* diff --git a/.github/workflows/functional-compute.yaml b/.github/workflows/functional-compute.yaml index 0065891bdb..87f0ef7e67 100644 --- a/.github/workflows/functional-compute.yaml +++ b/.github/workflows/functional-compute.yaml @@ -3,6 +3,7 @@ on: pull_request: paths: - '**compute**' + - '.github/workflows/functional-compute.yaml' schedule: - cron: '0 0 */3 * *' jobs: @@ -15,25 +16,21 @@ jobs: openstack_version: "master" ubuntu_version: "22.04" additional_services: "openstack-cli-server" + - name: "dalmatian" + openstack_version: "stable/2024.2" + 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: "" runs-on: ubuntu-${{ matrix.ubuntu_version }} name: Deploy OpenStack ${{ matrix.name }} with Nova and run compute acceptance tests steps: - name: Checkout Gophercloud uses: actions/checkout@v4 - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + uses: gophercloud/devstack-action@v0.17 with: branch: ${{ matrix.openstack_version }} conf_overrides: | @@ -42,12 +39,13 @@ jobs: - name: Checkout go uses: actions/setup-go@v5 with: - go-version: '^1.20' + go-version: '^1.22' - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-compute env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/compute/..." OS_BRANCH: ${{ matrix.openstack_version }} - name: Generate logs on failure run: ./script/collectlogs @@ -56,5 +54,5 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: functional-compute-${{ matrix.name }} + name: functional-compute-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* diff --git a/.github/workflows/functional-containerinfra.yaml b/.github/workflows/functional-containerinfra.yaml index d42add38cb..ad3c275b66 100644 --- a/.github/workflows/functional-containerinfra.yaml +++ b/.github/workflows/functional-containerinfra.yaml @@ -3,6 +3,7 @@ on: pull_request: paths: - '**containerinfra**' + - '.github/workflows/functional-containerinfra.yaml' schedule: - cron: '0 0 */3 * *' jobs: @@ -15,9 +16,22 @@ jobs: openstack_version: "master" ubuntu_version: "22.04" devstack_conf_overrides: | + # ensure we're using a working version of setuptools + if [ -n "\$TOP_DIR" ]; then + sed -i 's/setuptools\[core\]$/setuptools[core]==79.0.1/g' \$TOP_DIR/lib/infra \$TOP_DIR/inc/python + sed -i 's/pip_install "-U" "pbr"/pip_install "-U" "pbr" "setuptools[core]==79.0.1"/g' \$TOP_DIR/lib/infra + fi + enable_plugin magnum https://github.com/openstack/magnum master MAGNUMCLIENT_BRANCH=master additional_services: "openstack-cli-server" + - name: "dalmatian" + openstack_version: "stable/2024.2" + ubuntu_version: "22.04" + devstack_conf_overrides: | + enable_plugin magnum https://github.com/openstack/magnum stable/2024.2 + MAGNUMCLIENT_BRANCH=stable/2024.2 + additional_services: "openstack-cli-server" - name: "caracal" openstack_version: "stable/2024.1" ubuntu_version: "22.04" @@ -25,27 +39,13 @@ jobs: 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" - 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: "" runs-on: ubuntu-${{ matrix.ubuntu_version }} name: Deploy OpenStack ${{ matrix.name }} with Magnum and run containerinfra acceptance tests steps: - name: Checkout Gophercloud uses: actions/checkout@v4 - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + uses: gophercloud/devstack-action@v0.17 with: branch: ${{ matrix.openstack_version }} conf_overrides: | @@ -54,17 +54,19 @@ 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 }}" - name: Checkout go uses: actions/setup-go@v5 with: - go-version: '^1.20' + go-version: '^1.22' - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-containerinfra env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/containerinfra/..." OS_BRANCH: ${{ matrix.openstack_version }} - name: Generate logs on failure run: ./script/collectlogs @@ -73,5 +75,5 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: functional-containerinfra-${{ matrix.name }} + name: functional-containerinfra-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* diff --git a/.github/workflows/functional-dns.yaml b/.github/workflows/functional-dns.yaml index 9145ae0198..43e8123cc0 100644 --- a/.github/workflows/functional-dns.yaml +++ b/.github/workflows/functional-dns.yaml @@ -2,8 +2,8 @@ name: functional-dns on: pull_request: paths: - - '**openstack/dns**' - - '**functional-dns.yaml' + - '**dns**' + - '.github/workflows/functional-dns.yaml' schedule: - cron: '0 0 */3 * *' jobs: @@ -15,40 +15,45 @@ jobs: - name: "master" openstack_version: "master" ubuntu_version: "22.04" + devstack_conf_overrides: | + # ensure we're using a working version of setuptools + if [ -n "\$TOP_DIR" ]; then + sed -i 's/setuptools\[core\]$/setuptools[core]==79.0.1/g' \$TOP_DIR/lib/infra \$TOP_DIR/inc/python + sed -i 's/pip_install "-U" "pbr"/pip_install "-U" "pbr" "setuptools[core]==79.0.1"/g' \$TOP_DIR/lib/infra + fi + additional_services: "openstack-cli-server" + - name: "dalmatian" + openstack_version: "stable/2024.2" + 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: "" runs-on: ubuntu-${{ matrix.ubuntu_version }} name: Deploy OpenStack ${{ matrix.name }} with Designate and run dns acceptance tests steps: - name: Checkout Gophercloud uses: actions/checkout@v4 - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + uses: gophercloud/devstack-action@v0.17 with: branch: ${{ matrix.openstack_version }} conf_overrides: | enable_plugin designate https://github.com/openstack/designate ${{ matrix.openstack_version }} + + ${{ matrix.devstack_conf_overrides }} enabled_services: "designate,designate-central,designate-api,designate-worker,designate-producer,designate-mdns,${{ matrix.additional_services }}" - name: Checkout go uses: actions/setup-go@v5 with: - go-version: '^1.20' + go-version: '^1.22' - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-dns env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/dns/..." OS_BRANCH: ${{ matrix.openstack_version }} - name: Generate logs on failure run: ./script/collectlogs @@ -57,5 +62,5 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: functional-dns-${{ matrix.name }} + name: functional-dns-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* diff --git a/.github/workflows/functional-fwaas_v2.yaml b/.github/workflows/functional-fwaas_v2.yaml index 6accf2828c..cb84f9f465 100644 --- a/.github/workflows/functional-fwaas_v2.yaml +++ b/.github/workflows/functional-fwaas_v2.yaml @@ -1,8 +1,13 @@ +# 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: pull_request: paths: - '**networking/v2/extensions/fwaas_v2**' + - '.github/workflows/functional-fwaas_v2.yaml' schedule: - cron: '0 0 */3 * *' jobs: @@ -15,25 +20,29 @@ jobs: openstack_version: "master" ubuntu_version: "22.04" additional_services: "openstack-cli-server" + - name: "dalmatian" + openstack_version: "stable/2024.2" + 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: "" runs-on: ubuntu-${{ matrix.ubuntu_version }} name: Deploy OpenStack ${{ matrix.name }} with enabled FWaaS_v2 and run networking acceptance tests steps: - name: Checkout Gophercloud uses: actions/checkout@v4 + - name: Create additional neutron policies + 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 + uses: gophercloud/devstack-action@v0.17 with: branch: ${{ matrix.openstack_version }} conf_overrides: | @@ -43,16 +52,21 @@ jobs: Q_ML2_PLUGIN_TYPE_DRIVERS=flat,gre,vlan,vxlan Q_ML2_TENANT_NETWORK_TYPE=vxlan Q_TUNNEL_TYPES=vxlan,gre + + [[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,${{ matrix.additional_services }}' - name: Checkout go uses: actions/setup-go@v5 with: - go-version: '^1.20' + go-version: '^1.22' - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-networking 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 @@ -61,5 +75,5 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: functional-fwaas_v2-${{ matrix.name }} + name: functional-fwaas_v2-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* diff --git a/.github/workflows/functional-identity.yaml b/.github/workflows/functional-identity.yaml index ebe76b40a0..d3cb23f02c 100644 --- a/.github/workflows/functional-identity.yaml +++ b/.github/workflows/functional-identity.yaml @@ -3,6 +3,7 @@ on: pull_request: paths: - '**identity**' + - '.github/workflows/functional-identity.yaml' schedule: - cron: '0 0 */3 * *' jobs: @@ -15,37 +16,34 @@ jobs: openstack_version: "master" ubuntu_version: "22.04" additional_services: "openstack-cli-server" + - name: "dalmatian" + openstack_version: "stable/2024.2" + 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: "" runs-on: ubuntu-${{ matrix.ubuntu_version }} name: Deploy OpenStack ${{ matrix.name }} with Keystone and run identity acceptance tests steps: - name: Checkout Gophercloud uses: actions/checkout@v4 - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + uses: gophercloud/devstack-action@v0.17 with: branch: ${{ matrix.openstack_version }} enabled_services: "${{ matrix.additional_services }}" - name: Checkout go uses: actions/setup-go@v5 with: - go-version: '^1.20' + go-version: '^1.22' - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-identity env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/identity/..." OS_BRANCH: ${{ matrix.openstack_version }} - name: Generate logs on failure run: ./script/collectlogs @@ -54,5 +52,5 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: functional-identity-${{ matrix.name }} + name: functional-identity-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* diff --git a/.github/workflows/functional-image.yaml b/.github/workflows/functional-image.yaml index 70ab27eeef..26579dd4e7 100644 --- a/.github/workflows/functional-image.yaml +++ b/.github/workflows/functional-image.yaml @@ -3,6 +3,7 @@ on: pull_request: paths: - '**image**' + - '.github/workflows/functional-image.yaml' schedule: - cron: '0 0 */3 * *' jobs: @@ -15,37 +16,34 @@ jobs: openstack_version: "master" ubuntu_version: "22.04" additional_services: "openstack-cli-server" + - name: "dalmatian" + openstack_version: "stable/2024.2" + 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: "" runs-on: ubuntu-${{ matrix.ubuntu_version }} name: Deploy OpenStack ${{ matrix.name }} with Glance and run image acceptance tests steps: - name: Checkout Gophercloud uses: actions/checkout@v4 - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + uses: gophercloud/devstack-action@v0.17 with: branch: ${{ matrix.openstack_version }} enabled_services: "${{ matrix.additional_services }}" - name: Checkout go uses: actions/setup-go@v5 with: - go-version: '^1.20' + go-version: '^1.22' - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-image env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/image/..." OS_BRANCH: ${{ matrix.openstack_version }} - name: Generate logs on failure run: ./script/collectlogs @@ -54,5 +52,5 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: functional-image-${{ matrix.name }} + name: functional-image-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* diff --git a/.github/workflows/functional-keymanager.yaml b/.github/workflows/functional-keymanager.yaml index de2c95c503..099939e5a3 100644 --- a/.github/workflows/functional-keymanager.yaml +++ b/.github/workflows/functional-keymanager.yaml @@ -3,6 +3,7 @@ on: pull_request: paths: - '**keymanager**' + - '.github/workflows/functional-keymanager.yaml' schedule: - cron: '0 0 */3 * *' jobs: @@ -14,40 +15,45 @@ jobs: - name: "master" openstack_version: "master" ubuntu_version: "22.04" + devstack_conf_overrides: | + # ensure we're using a working version of setuptools + if [ -n "\$TOP_DIR" ]; then + sed -i 's/setuptools\[core\]$/setuptools[core]==79.0.1/g' \$TOP_DIR/lib/infra \$TOP_DIR/inc/python + sed -i 's/pip_install "-U" "pbr"/pip_install "-U" "pbr" "setuptools[core]==79.0.1"/g' \$TOP_DIR/lib/infra + fi + additional_services: "openstack-cli-server" + - name: "dalmatian" + openstack_version: "stable/2024.2" + 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: "" runs-on: ubuntu-${{ matrix.ubuntu_version }} name: Deploy OpenStack ${{ matrix.name }} with Barbican and run keymanager acceptance tests steps: - name: Checkout Gophercloud uses: actions/checkout@v4 - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + uses: gophercloud/devstack-action@v0.17 with: branch: ${{ matrix.openstack_version }} conf_overrides: | enable_plugin barbican https://github.com/openstack/barbican ${{ matrix.openstack_version }} + + ${{ matrix.devstack_conf_overrides }} enabled_services: "barbican-svc,barbican-retry,barbican-keystone-listener,${{ matrix.additional_services }}" - name: Checkout go uses: actions/setup-go@v5 with: - go-version: '^1.20' + go-version: '^1.22' - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-keymanager env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/keymanager/..." OS_BRANCH: ${{ matrix.openstack_version }} - name: Generate logs on failure run: ./script/collectlogs @@ -56,5 +62,5 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: functional-keymanager-${{ matrix.name }} + name: functional-keymanager-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* diff --git a/.github/workflows/functional-loadbalancer.yaml b/.github/workflows/functional-loadbalancer.yaml index b93014dadd..6b720aecac 100644 --- a/.github/workflows/functional-loadbalancer.yaml +++ b/.github/workflows/functional-loadbalancer.yaml @@ -3,6 +3,7 @@ on: pull_request: paths: - '**loadbalancer**' + - '.github/workflows/functional-loadbalancer.yaml' schedule: - cron: '0 0 */3 * *' jobs: @@ -14,41 +15,46 @@ jobs: - name: "master" openstack_version: "master" ubuntu_version: "22.04" + devstack_conf_overrides: | + # ensure we're using a working version of setuptools + if [ -n "\$TOP_DIR" ]; then + sed -i 's/setuptools\[core\]$/setuptools[core]==79.0.1/g' \$TOP_DIR/lib/infra \$TOP_DIR/inc/python + sed -i 's/pip_install "-U" "pbr"/pip_install "-U" "pbr" "setuptools[core]==79.0.1"/g' \$TOP_DIR/lib/infra + fi + additional_services: "openstack-cli-server" + - name: "dalmatian" + openstack_version: "stable/2024.2" + 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: "" runs-on: ubuntu-${{ matrix.ubuntu_version }} name: Deploy OpenStack ${{ matrix.name }} with Octavia and run loadbalancer acceptance tests steps: - name: Checkout Gophercloud uses: actions/checkout@v4 - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + uses: gophercloud/devstack-action@v0.17 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 }} + + ${{ matrix.devstack_conf_overrides }} enabled_services: "octavia,o-api,o-cw,o-hk,o-hm,o-da,neutron-qos,${{ matrix.additional_services }}" - name: Checkout go uses: actions/setup-go@v5 with: - go-version: '^1.20' + go-version: '^1.22' - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-loadbalancer env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/loadbalancer/..." OS_BRANCH: ${{ matrix.openstack_version }} - name: Generate logs on failure run: ./script/collectlogs @@ -57,5 +63,5 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: functional-loadbalancer-${{ matrix.name }} + name: functional-loadbalancer-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* diff --git a/.github/workflows/functional-messaging.yaml b/.github/workflows/functional-messaging.yaml index 6703dbb835..0ddb5f1efd 100644 --- a/.github/workflows/functional-messaging.yaml +++ b/.github/workflows/functional-messaging.yaml @@ -3,6 +3,7 @@ on: pull_request: paths: - '**messaging**' + - '.github/workflows/functional-messaging.yaml' schedule: - cron: '0 0 */3 * *' jobs: @@ -15,25 +16,21 @@ jobs: openstack_version: "master" ubuntu_version: "22.04" additional_services: "openstack-cli-server" + - name: "dalmatian" + openstack_version: "stable/2024.2" + 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: "" runs-on: ubuntu-${{ matrix.ubuntu_version }} name: Deploy OpenStack ${{ matrix.name }} with Zaqar and run messaging acceptance tests steps: - name: Checkout Gophercloud uses: actions/checkout@v4 - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + uses: gophercloud/devstack-action@v0.17 with: branch: ${{ matrix.openstack_version }} conf_overrides: | @@ -43,12 +40,13 @@ jobs: - name: Checkout go uses: actions/setup-go@v5 with: - go-version: '^1.20' + go-version: '^1.22' - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-messaging env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/messaging/..." OS_BRANCH: ${{ matrix.openstack_version }} - name: Generate logs on failure run: ./script/collectlogs @@ -57,5 +55,5 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: functional-messaging-${{ matrix.name }} + name: functional-messaging-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* diff --git a/.github/workflows/functional-networking.yaml b/.github/workflows/functional-networking.yaml index feb896eb45..a0608a2b98 100644 --- a/.github/workflows/functional-networking.yaml +++ b/.github/workflows/functional-networking.yaml @@ -3,6 +3,7 @@ on: pull_request: paths: - '**networking**' + - '.github/workflows/functional-networking.yaml' schedule: - cron: '0 0 */3 * *' jobs: @@ -15,18 +16,14 @@ jobs: openstack_version: "master" ubuntu_version: "22.04" additional_services: "openstack-cli-server" + - name: "dalmatian" + openstack_version: "stable/2024.2" + 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: "" runs-on: ubuntu-${{ matrix.ubuntu_version }} name: Deploy OpenStack ${{ matrix.name }} with Neutron and run networking acceptance tests steps: @@ -41,7 +38,7 @@ jobs: "update_port:binding:profile": "rule:admin_only or rule:service_api" EOF - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + uses: gophercloud/devstack-action@v0.17 with: branch: ${{ matrix.openstack_version }} conf_overrides: | @@ -57,12 +54,13 @@ jobs: - name: Checkout go uses: actions/setup-go@v5 with: - go-version: '^1.20' + go-version: '^1.22' - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-networking env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/networking/..." OS_BRANCH: ${{ matrix.openstack_version }} - name: Generate logs on failure run: ./script/collectlogs @@ -71,5 +69,5 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: functional-networking-${{ matrix.name }} + name: functional-networking-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* diff --git a/.github/workflows/functional-objectstorage.yaml b/.github/workflows/functional-objectstorage.yaml index ebb63e8a41..b22f5ef454 100644 --- a/.github/workflows/functional-objectstorage.yaml +++ b/.github/workflows/functional-objectstorage.yaml @@ -3,6 +3,7 @@ on: pull_request: paths: - '**objectstorage**' + - '.github/workflows/functional-objectstorage.yaml' schedule: - cron: '0 0 */3 * *' jobs: @@ -15,25 +16,21 @@ jobs: openstack_version: "master" ubuntu_version: "22.04" additional_services: "openstack-cli-server" + - name: "dalmatian" + openstack_version: "stable/2024.2" + 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: "" runs-on: ubuntu-${{ matrix.ubuntu_version }} name: Deploy OpenStack ${{ matrix.name }} with Swift and run objectstorage acceptance tests steps: - name: Checkout Gophercloud uses: actions/checkout@v4 - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + uses: gophercloud/devstack-action@v0.17 with: branch: ${{ matrix.openstack_version }} conf_overrides: | @@ -46,12 +43,13 @@ jobs: - name: Checkout go uses: actions/setup-go@v5 with: - go-version: '^1.20' + go-version: '^1.22' - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-objectstorage env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/objectstorage/..." OS_BRANCH: ${{ matrix.openstack_version }} - name: Generate logs on failure run: ./script/collectlogs @@ -60,5 +58,5 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: functional-objectstorage-${{ matrix.name }} + name: functional-objectstorage-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* diff --git a/.github/workflows/functional-orchestration.yaml b/.github/workflows/functional-orchestration.yaml index 8e7bc6b8aa..a301e45688 100644 --- a/.github/workflows/functional-orchestration.yaml +++ b/.github/workflows/functional-orchestration.yaml @@ -3,6 +3,7 @@ on: pull_request: paths: - '**orchestration**' + - '.github/workflows/functional-orchestration.yaml' schedule: - cron: '0 0 */3 * *' jobs: @@ -15,25 +16,21 @@ jobs: openstack_version: "master" ubuntu_version: "22.04" additional_services: "openstack-cli-server" + - name: "dalmatian" + openstack_version: "stable/2024.2" + 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: "" runs-on: ubuntu-${{ matrix.ubuntu_version }} name: Deploy OpenStack ${{ matrix.name }} with Heat and run orchestration acceptance tests steps: - name: Checkout Gophercloud uses: actions/checkout@v4 - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + uses: gophercloud/devstack-action@v0.17 with: branch: ${{ matrix.openstack_version }} conf_overrides: | @@ -42,12 +39,13 @@ jobs: - name: Checkout go uses: actions/setup-go@v5 with: - go-version: '^1.20' + go-version: '^1.22' - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-orchestration env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/orchestration/..." OS_BRANCH: ${{ matrix.openstack_version }} - name: Generate logs on failure run: ./script/collectlogs @@ -56,5 +54,5 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: functional-orchestration-${{ matrix.name }} + name: functional-orchestration-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* diff --git a/.github/workflows/functional-placement.yaml b/.github/workflows/functional-placement.yaml index d407413ac0..c6356e95e2 100644 --- a/.github/workflows/functional-placement.yaml +++ b/.github/workflows/functional-placement.yaml @@ -3,6 +3,7 @@ on: pull_request: paths: - '**placement**' + - '.github/workflows/functional-placement.yaml' schedule: - cron: '0 0 */3 * *' jobs: @@ -15,37 +16,34 @@ jobs: openstack_version: "master" ubuntu_version: "22.04" additional_services: "openstack-cli-server" + - name: "dalmatian" + openstack_version: "stable/2024.2" + 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: "" runs-on: ubuntu-${{ matrix.ubuntu_version }} name: Deploy OpenStack ${{ matrix.name }} with Placement and run placement acceptance tests steps: - name: Checkout Gophercloud uses: actions/checkout@v4 - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + uses: gophercloud/devstack-action@v0.17 with: branch: ${{ matrix.openstack_version }} enabled_services: "${{ matrix.additional_services }}" - name: Checkout go uses: actions/setup-go@v5 with: - go-version: '^1.20' + go-version: '^1.22' - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-placement env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/placement/..." OS_BRANCH: ${{ matrix.openstack_version }} - name: Generate logs on failure run: ./script/collectlogs @@ -54,5 +52,5 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: functional-placement-${{ matrix.name }} + name: functional-placement-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* diff --git a/.github/workflows/functional-sharedfilesystems.yaml b/.github/workflows/functional-sharedfilesystems.yaml index e46a39f0ee..08aca0f0e8 100644 --- a/.github/workflows/functional-sharedfilesystems.yaml +++ b/.github/workflows/functional-sharedfilesystems.yaml @@ -3,6 +3,7 @@ on: pull_request: paths: - '**sharedfilesystems**' + - '.github/workflows/functional-sharedfilesystems.yaml' schedule: - cron: '0 0 */3 * *' jobs: @@ -14,26 +15,28 @@ jobs: - name: "master" openstack_version: "master" ubuntu_version: "22.04" + devstack_conf_overrides: | + # ensure we're using a working version of setuptools + if [ -n "\$TOP_DIR" ]; then + sed -i 's/setuptools\[core\]$/setuptools[core]==79.0.1/g' \$TOP_DIR/lib/infra \$TOP_DIR/inc/python + sed -i 's/pip_install "-U" "pbr"/pip_install "-U" "pbr" "setuptools[core]==79.0.1"/g' \$TOP_DIR/lib/infra + fi + additional_services: "openstack-cli-server" + - name: "dalmatian" + openstack_version: "stable/2024.2" + 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: "" runs-on: ubuntu-${{ matrix.ubuntu_version }} name: Deploy OpenStack ${{ matrix.name }} with Manila and run sharedfilesystems acceptance tests steps: - name: Checkout Gophercloud uses: actions/checkout@v4 - name: Deploy devstack - uses: EmilienM/devstack-action@e82a9cbead099cba72f99537e82a360c3e319c69 + uses: gophercloud/devstack-action@v0.17 with: branch: ${{ matrix.openstack_version }} conf_overrides: | @@ -52,16 +55,19 @@ 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 + + ${{ matrix.devstack_conf_overrides }} enabled_services: "${{ matrix.additional_services }}" - name: Checkout go uses: actions/setup-go@v5 with: - go-version: '^1.20' + go-version: '^1.22' - name: Run Gophercloud acceptance tests - run: ./script/acceptancetest + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-sharedfilesystems env: DEVSTACK_PATH: ${{ github.workspace }}/devstack - PACKAGE: "./internal/acceptance/openstack/sharedfilesystems/..." OS_BRANCH: ${{ matrix.openstack_version }} - name: Generate logs on failure run: ./script/collectlogs @@ -70,5 +76,5 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: functional-sharedfilesystems-${{ matrix.name }} + name: functional-sharedfilesystems-${{ matrix.name }}-${{ github.run_id }} path: /tmp/devstack-logs/* diff --git a/.github/workflows/functional-workflow.yaml b/.github/workflows/functional-workflow.yaml new file mode 100644 index 0000000000..d4d389581b --- /dev/null +++ b/.github/workflows/functional-workflow.yaml @@ -0,0 +1,61 @@ +name: functional-workflow +on: + pull_request: + paths: + - '**workflow**' + - '.github/workflows/functional-workflow.yaml' + schedule: + - cron: '0 0 */3 * *' +jobs: + functional-workflow: + strategy: + fail-fast: false + matrix: + include: + - name: "master" + openstack_version: "master" + ubuntu_version: "22.04" + mistral_plugin_version: "master" + additional_services: "openstack-cli-server" + - name: "dalmatian" + openstack_version: "stable/2024.2" + ubuntu_version: "22.04" + mistral_plugin_version: "stable/2024.2" + additional_services: "openstack-cli-server" + - name: "caracal" + openstack_version: "stable/2024.1" + ubuntu_version: "22.04" + mistral_plugin_version: "stable/2024.1" + additional_services: "" + runs-on: ubuntu-${{ matrix.ubuntu_version }} + name: Deploy OpenStack ${{ matrix.name }} with Mistral and run workflow acceptance tests + steps: + - name: Checkout Gophercloud + uses: actions/checkout@v4 + - name: Deploy devstack + uses: gophercloud/devstack-action@v0.17 + 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,${{ matrix.additional_services }}" + - name: Checkout go + uses: actions/setup-go@v5 + with: + go-version: '^1.22' + - name: Run Gophercloud acceptance tests + run: | + source ${{ github.workspace }}/script/stackenv + make acceptance-workflow + env: + DEVSTACK_PATH: ${{ github.workspace }}/devstack + OS_BRANCH: ${{ matrix.openstack_version }} + - name: Generate logs on failure + run: ./script/collectlogs + if: failure() + - name: Upload logs artifacts on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: functional-workflow-${{ matrix.name }}-${{ github.run_id }} + path: /tmp/devstack-logs/* diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yaml similarity index 100% rename from .github/workflows/greetings.yml rename to .github/workflows/greetings.yaml diff --git a/.github/workflows/label-issue.yaml b/.github/workflows/label-issue.yaml new file mode 100644 index 0000000000..45ab4cbe34 --- /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..44d46b0e15 --- /dev/null +++ b/.github/workflows/label-pr.yaml @@ -0,0 +1,87 @@ +name: Label PR +on: + pull_request_target: + types: + - opened + - synchronize + - reopened + +jobs: + semver: + runs-on: ubuntu-latest + steps: + - 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' + + - 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 label semver:patch + if: steps.go-apidiff.outputs.semver-type == 'patch' + run: gh pr edit "$NUMBER" --add-label "semver:patch" --remove-label "semver:major,semver:minor" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.pull_request.number }} + + - name: Add label semver:minor + if: steps.go-apidiff.outputs.semver-type == 'minor' + run: gh pr edit "$NUMBER" --add-label "semver:minor" --remove-label "semver:major,semver:patch" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.pull_request.number }} + + - name: Add label semver:major + if: steps.go-apidiff.outputs.semver-type == 'major' + run: gh pr edit "$NUMBER" --add-label "semver:major" --remove-label "semver:minor,semver:patch" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.pull_request.number }} + + - name: Report failure + if: failure() + run: | + gh pr edit "$NUMBER" --remove-label "semver:major,semver:minor,semver:patch" + gh issue comment "$NUMBER" --body "$BODY" + exit 1 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.pull_request.number }} + BODY: > + Failed to assess the semver bump. See [logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details. + + edits: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v5 diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 665b887187..ed7399819d 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -1,27 +1,23 @@ -on: [push, pull_request] name: Linters +on: + - push + - pull_request permissions: contents: read - jobs: test: runs-on: ubuntu-latest - steps: + - name: Checkout Gophercloud + uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: '1' - - - uses: actions/checkout@v4 - - - name: Run go fmt + - name: Run linters run: | - ./script/format - - - name: Run go vet - run: | - ./script/vet - + make lint + # TODO: Use 'go mod tidy -diff' instead once go 1.23 is out + # https://github.com/golang/go/issues/27005 - 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 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/unit.yml b/.github/workflows/unit.yaml similarity index 86% rename from .github/workflows/unit.yml rename to .github/workflows/unit.yaml index 11fddb5f68..abab3a5237 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yaml @@ -1,8 +1,9 @@ -on: [push, pull_request] name: Unit Testing +on: + - push + - pull_request permissions: contents: read - jobs: test: permissions: @@ -15,35 +16,29 @@ jobs: go-version: - "1.22.3" - "1" - steps: + - name: Checkout Gophercloud + uses: actions/checkout@v4 - name: Setup Go ${{ matrix.go-version }} uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} - - - uses: actions/checkout@v4 - - 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 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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d7dfbc469..73fe513468 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,70 @@ +## v2.7.0 (2025-04-03) + +* [GH-3306](https://github.com/gophercloud/gophercloud/pull/3306) [v2] identity: Add Get endpoint by ID +* [GH-3325](https://github.com/gophercloud/gophercloud/pull/3325) [v2] Switch to a version of gocovmerge compatible with go 1.22 +* [GH-3327](https://github.com/gophercloud/gophercloud/pull/3327) Merge pull request #3209 from shiftstack/proper-service-discovery +* [GH-3328](https://github.com/gophercloud/gophercloud/pull/3328) [v2] Improve support for `network standard-attr-*` extensions +* [GH-3330](https://github.com/gophercloud/gophercloud/pull/3330) [v2] Enhance Snapshot struct and add ListDetail function in V3 blockstorage +* [GH-3333](https://github.com/gophercloud/gophercloud/pull/3333) [v2] vpnaas: add support for more ciphers (auth, encryption, pfs modes) +* [GH-3334](https://github.com/gophercloud/gophercloud/pull/3334) [v2] Added support for VIF's in Baremetal +* [GH-3335](https://github.com/gophercloud/gophercloud/pull/3335) [v2] Baremetal virtual media Get API + +## v2.6.0 (2025-03-03) + +* [GH-3309](https://github.com/gophercloud/gophercloud/pull/3309) Backport: Added support for hypervisor_hostname to v2 + +## v2.5.0 (2025-02-11) + +* [GH-3278](https://github.com/gophercloud/gophercloud/pull/3278) [v2] test: Ensure that randomly created secgroup rules don't conflict +* [GH-3287](https://github.com/gophercloud/gophercloud/pull/3287) [v2] Fix panic in ExtractIntoStructPtr +* [GH-3288](https://github.com/gophercloud/gophercloud/pull/3288) [v2] Fix JSON field name hints in APIVersion structs +* [GH-3292](https://github.com/gophercloud/gophercloud/pull/3292) [v2] Add permissions to the label-issue workflow +* [GH-3294](https://github.com/gophercloud/gophercloud/pull/3294) [v2] Add support for zone sharing in DNS v2 +* [GH-3296](https://github.com/gophercloud/gophercloud/pull/3296) build(deps): bump golang.org/x/crypto from 0.30.0 to 0.31.0 +* [GH-3297](https://github.com/gophercloud/gophercloud/pull/3297) [v2] build(deps): bump golang.org/x/crypto from 0.31.0 to 0.32.0 +* [GH-3298](https://github.com/gophercloud/gophercloud/pull/3298) [v2] build(deps): bump golang.org/x/crypto from 0.32.0 to 0.33.0 + +## v2.4.0 (2024-12-18) + +* [GH-3270](https://github.com/gophercloud/gophercloud/pull/3270) [v2] SG rules: implement bulk create +* [GH-3273](https://github.com/gophercloud/gophercloud/pull/3273) [v2] Add missing fields in Objectstorage and compute API + +## v2.3.0 (2024-12-06) + +* [GH-3213](https://github.com/gophercloud/gophercloud/pull/3213) [v2] Handle nova api version > 2.87 for hypervisor +* [GH-3236](https://github.com/gophercloud/gophercloud/pull/3236) [v2] Added required fields for Node API Parity +* [GH-3248](https://github.com/gophercloud/gophercloud/pull/3248) [v2] Add support for disable_power_off +* [GH-3261](https://github.com/gophercloud/gophercloud/pull/3261) [v2] Misc cleanups +* [GH-3262](https://github.com/gophercloud/gophercloud/pull/3262) [v2] Address govet 1.24 issue + +## v2.2.0 (2024-10-18) + +* [GH-3176](https://github.com/gophercloud/gophercloud/pull/3176) [v2] [containerinfra]: add "MasterLBEnabled" in Cluster results +* [GH-3181](https://github.com/gophercloud/gophercloud/pull/3181) [v2] octavia: add new options to health monitors +* [GH-3182](https://github.com/gophercloud/gophercloud/pull/3182) [v2] octavia: add new security options to pools and listeners +* [GH-3195](https://github.com/gophercloud/gophercloud/pull/3195) [v2] [core]: handle empty response body +* [GH-3196](https://github.com/gophercloud/gophercloud/pull/3196) [v2] [fwaas_v2]: proper ParseResponse handling +* [GH-3198](https://github.com/gophercloud/gophercloud/pull/3198) [v2] compute: Fix expected and actual test results +* [GH-3199](https://github.com/gophercloud/gophercloud/pull/3199) [v2] [octavia] add an ability to filter flavors and flavorprofiles +* [GH-3214](https://github.com/gophercloud/gophercloud/pull/3214) [v2] [manila] add scheduler_hints to the shares CreateOpts +* [GH-3215](https://github.com/gophercloud/gophercloud/pull/3215) [v2] [manila] add share_group_id to share's CreateOpts +* [GH-3219](https://github.com/gophercloud/gophercloud/pull/3219) [v2] docs: Remove outdated godoc + +## v2.1.1 (2024-09-18) + +* [GH-3161](https://github.com/gophercloud/gophercloud/pull/3161) [v2] fix: create security group rule with any protocol + +## 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/Makefile b/Makefile index 128beec005..86356b78ab 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,8 @@ undefine GOFLAGS -GOLANGCI_LINT_VERSION?=v1.57.1 +GOLANGCI_LINT_VERSION?=v1.62.2 +GO_TEST?=go run gotest.tools/gotestsum@latest --format testname -- +TIMEOUT := "60m" ifeq ($(shell command -v podman 2> /dev/null),) RUNNER=docker @@ -8,102 +10,113 @@ 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) ./... .PHONY: unit coverage: - go test -covermode count -coverprofile cover.out -coverpkg=./... ./... + $(GO_TEST) -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-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) -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-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-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..ca47f5b0ba 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # 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) - Gophercloud is a Go SDK for OpenStack. Join us on kubernetes slack, on [#gophercloud](https://kubernetes.slack.com/archives/C05G4NJ6P6X). Visit [slack.k8s.io](https://slack.k8s.io) for an invitation. 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/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/endpoint_search.go b/endpoint_search.go index 2fbc3c97f1..8818e769b8 100644 --- a/endpoint_search.go +++ b/endpoint_search.go @@ -1,5 +1,7 @@ package gophercloud +import "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 +24,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 +57,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,6 +67,13 @@ 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. @@ -73,4 +108,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 = 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 = aliases + } + } + } + } +} + +func (eo *EndpointOpts) Types() []string { + return append([]string{eo.Type}, eo.Aliases...) } diff --git a/go.mod b/go.mod index d4ac50a838..ac4634f884 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module github.com/gophercloud/gophercloud/v2 go 1.22 require ( - golang.org/x/crypto v0.25.0 + golang.org/x/crypto v0.33.0 gopkg.in/yaml.v2 v2.4.0 ) -require golang.org/x/sys v0.22.0 // indirect +require golang.org/x/sys v0.30.0 // indirect diff --git a/go.sum b/go.sum index 1e1f48a722..0bfa14296f 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,9 @@ -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= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= 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= diff --git a/internal/acceptance/README.md b/internal/acceptance/README.md index cbbbac7f39..febf394f10 100644 --- a/internal/acceptance/README.md +++ b/internal/acceptance/README.md @@ -91,7 +91,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..f981ffcea2 100644 --- a/internal/acceptance/clients/clients.go +++ b/internal/acceptance/clients/clients.go @@ -107,16 +107,14 @@ 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 { + text := "You're missing some important setup:\n * These environment variables must be provided: %s\n" + return nil, fmt.Errorf(text, strings.Join(missing, ", ")) + } - return nil, fmt.Errorf(text) + if notDistinct != "" { + text := "You're missing some important setup:\n * %s\n" + return nil, fmt.Errorf(text, notDistinct) } return &AcceptanceTestChoices{ diff --git a/internal/acceptance/clients/conditions.go b/internal/acceptance/clients/conditions.go index 619d31a94e..6077f4bdef 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/") @@ -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/") diff --git a/internal/acceptance/openstack/baremetal/httpbasic/allocations_test.go b/internal/acceptance/openstack/baremetal/httpbasic/allocations_test.go index 932c067ad0..faa667d05b 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) 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..a0c656d7f7 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) @@ -49,7 +49,7 @@ func TestNodesCreateDestroy(t *testing.T) { func TestNodesUpdate(t *testing.T) { clients.RequireLong(t) - clients.RequireIronicHTTPBasic(t) + RequireIronicHTTPBasic(t) client, err := clients.NewBareMetalV1HTTPBasic() th.AssertNoErr(t, err) @@ -74,7 +74,7 @@ func TestNodesUpdate(t *testing.T) { 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) diff --git a/internal/acceptance/openstack/baremetal/httpbasic/ports_test.go b/internal/acceptance/openstack/baremetal/httpbasic/ports_test.go index 9912c49322..7773f8b23e 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) @@ -52,7 +52,7 @@ func TestPortsCreateDestroy(t *testing.T) { func TestPortsUpdate(t *testing.T) { clients.RequireLong(t) - clients.RequireIronicHTTPBasic(t) + RequireIronicHTTPBasic(t) client, err := clients.NewBareMetalV1HTTPBasic() th.AssertNoErr(t, err) diff --git a/internal/acceptance/openstack/baremetal/noauth/allocations_test.go b/internal/acceptance/openstack/baremetal/noauth/allocations_test.go index e6cb33ac3d..d0b34737c5 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) 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..64b9826a03 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) @@ -49,7 +49,7 @@ func TestNodesCreateDestroy(t *testing.T) { func TestNodesUpdate(t *testing.T) { clients.RequireLong(t) - clients.RequireIronicNoAuth(t) + RequireIronicNoAuth(t) client, err := clients.NewBareMetalV1NoAuthClient() th.AssertNoErr(t, err) @@ -74,7 +74,7 @@ func TestNodesUpdate(t *testing.T) { 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/ports_test.go b/internal/acceptance/openstack/baremetal/noauth/ports_test.go index 980b2dec32..3f10b3b85c 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) @@ -52,7 +52,7 @@ func TestPortsCreateDestroy(t *testing.T) { func TestPortsUpdate(t *testing.T) { clients.RequireLong(t) - clients.RequireIronicNoAuth(t) + RequireIronicNoAuth(t) client, err := clients.NewBareMetalV1NoAuthClient() th.AssertNoErr(t, err) diff --git a/internal/acceptance/openstack/baremetal/v1/nodes_test.go b/internal/acceptance/openstack/baremetal/v1/nodes_test.go index 669ea69cdc..be48ee9197 100644 --- a/internal/acceptance/openstack/baremetal/v1/nodes_test.go +++ b/internal/acceptance/openstack/baremetal/v1/nodes_test.go @@ -219,7 +219,7 @@ func TestNodesVirtualMedia(t *testing.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/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/v3/snapshots_test.go b/internal/acceptance/openstack/blockstorage/v3/snapshots_test.go index bdb4c8fed3..18f045fc30 100644 --- a/internal/acceptance/openstack/blockstorage/v3/snapshots_test.go +++ b/internal/acceptance/openstack/blockstorage/v3/snapshots_test.go @@ -73,7 +73,24 @@ func TestSnapshots(t *testing.T) { 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.AssertEquals(t, found, true) + + return true, nil + }) th.AssertNoErr(t, err) } diff --git a/internal/acceptance/openstack/client_test.go b/internal/acceptance/openstack/client_test.go index 7902961c55..3b252a2853 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(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, diff --git a/internal/acceptance/openstack/compute/v2/attachinterfaces_test.go b/internal/acceptance/openstack/compute/v2/attachinterfaces_test.go index cf9b1a973e..95ad0ebf2d 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.AssertEquals(t, true, found) } diff --git a/internal/acceptance/openstack/compute/v2/availabilityzones_test.go b/internal/acceptance/openstack/compute/v2/availabilityzones_test.go index b3af24b0a1..3b37db49b0 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.AssertEquals(t, true, found) } func TestAvailabilityZonesListDetail(t *testing.T) { @@ -55,5 +55,5 @@ func TestAvailabilityZonesListDetail(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertEquals(t, true, 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..4a5ec655a2 100644 --- a/internal/acceptance/openstack/compute/v2/compute.go +++ b/internal/acceptance/openstack/compute/v2/compute.go @@ -84,8 +84,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 +137,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 } @@ -169,12 +169,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, 1, flavor.RAM) + th.AssertEquals(t, 1, flavor.Disk) + th.AssertEquals(t, 1, flavor.VCPUs) + th.AssertEquals(t, true, flavor.IsPublic) + th.AssertEquals(t, flavorDescription, flavor.Description) return flavor, nil } @@ -213,7 +213,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 +263,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 } @@ -292,11 +292,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, 1, flavor.RAM) + th.AssertEquals(t, 1, flavor.Disk) + th.AssertEquals(t, 1, flavor.VCPUs) + th.AssertEquals(t, false, flavor.IsPublic) return flavor, nil } @@ -318,7 +318,7 @@ 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) return securityGroup, nil } @@ -344,9 +344,9 @@ 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) return rule, nil } @@ -403,9 +403,9 @@ 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"]) return newServer, nil } @@ -457,8 +457,8 @@ 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"]) return newServer, nil } @@ -560,8 +560,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 +584,7 @@ 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) return sg, nil } @@ -612,7 +612,7 @@ 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) return sg, nil } @@ -662,9 +662,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 } @@ -711,9 +711,9 @@ 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"]) return newServer, nil } @@ -910,8 +910,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 } @@ -1070,9 +1070,9 @@ 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"]) 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/extension_test.go b/internal/acceptance/openstack/compute/v2/extension_test.go index 0339b251fb..8040282289 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.AssertEquals(t, true, 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..412149adfe 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.AssertEquals(t, true, 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) { @@ -140,7 +140,7 @@ func TestFlavorsCreateUpdateDelete(t *testing.T) { 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/instance_actions_test.go b/internal/acceptance/openstack/compute/v2/instance_actions_test.go index fb43cd72d6..01dd9fc72c 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.AssertEquals(t, true, found) } func TestInstanceActionsMicroversions(t *testing.T) { @@ -88,7 +88,7 @@ func TestInstanceActionsMicroversions(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertEquals(t, true, 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..09d28f24f5 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.AssertEquals(t, true, 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.AssertEquals(t, true, 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..53d94b538e 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) { diff --git a/internal/acceptance/openstack/compute/v2/secgroup_test.go b/internal/acceptance/openstack/compute/v2/secgroup_test.go index 0809bb0dda..dd6b6dd85e 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.AssertEquals(t, true, found) } func TestSecGroupsCRUD(t *testing.T) { @@ -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.AssertEquals(t, true, 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.AssertEquals(t, false, found) } diff --git a/internal/acceptance/openstack/compute/v2/servergroup_test.go b/internal/acceptance/openstack/compute/v2/servergroup_test.go index 8d23ea8135..09d351a7b4 100644 --- a/internal/acceptance/openstack/compute/v2/servergroup_test.go +++ b/internal/acceptance/openstack/compute/v2/servergroup_test.go @@ -41,7 +41,7 @@ func TestServergroupsCreateDelete(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertEquals(t, true, found) } func TestServergroupsAffinityPolicy(t *testing.T) { @@ -100,5 +100,5 @@ func TestServergroupsMicroversionCreateDelete(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertEquals(t, true, found) } diff --git a/internal/acceptance/openstack/compute/v2/servers_test.go b/internal/acceptance/openstack/compute/v2/servers_test.go index 9aa8133de9..0c5d034491 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.AssertEquals(t, true, 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.AssertEquals(t, false, created.LaunchedAt.IsZero()) + th.AssertEquals(t, true, created.TerminatedAt.IsZero()) } func TestServersWithoutImageRef(t *testing.T) { @@ -139,7 +139,7 @@ func TestServersUpdate(t *testing.T) { 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,7 @@ 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) if err = WaitForComputeStatus(client, rebuilt, "REBUILD"); err != nil { t.Fatal(err) @@ -347,7 +347,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 +379,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 +447,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.AssertEquals(t, true, err != nil) t.Logf("Attempting to unlock server %s", server.ID) err = servers.Unlock(context.TODO(), client, server.ID).ExtractErr() @@ -565,13 +565,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.AssertEquals(t, true, *created.ReservationID != "") + th.AssertEquals(t, 0, *created.LaunchIndex) + th.AssertEquals(t, true, *created.RAMDiskID == "") + th.AssertEquals(t, true, *created.KernelID == "") + th.AssertEquals(t, true, *created.Hostname != "") + th.AssertEquals(t, true, *created.RootDeviceName != "") + th.AssertEquals(t, true, created.Userdata == nil) } func TestServerNoNetworkCreateDestroy(t *testing.T) { @@ -604,7 +604,7 @@ func TestServerNoNetworkCreateDestroy(t *testing.T) { } } - th.AssertEquals(t, found, true) + th.AssertEquals(t, true, found) allAddressPages, err := servers.ListAddresses(client, server.ID).AllPages(context.TODO()) 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 155255fb43..7b8d8725b5 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.AssertEquals(t, true, found) } func TestServicesListWithOpts(t *testing.T) { @@ -55,69 +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) -} - -func TestServicesUpdate(t *testing.T) { - clients.RequireAdmin(t) - - client, err := clients.NewComputeV2Client() - th.AssertNoErr(t, err) - - listOpts := services.ListOpts{ - Binary: "nova-compute", - } - - client.Microversion = "2.53" - allPages, err := services.List(client, listOpts).AllPages(context.TODO()) - th.AssertNoErr(t, err) - - allServices, err := services.ExtractServices(allPages) - th.AssertNoErr(t, err) - - // disable all services - for _, service := range allServices { - opts := services.UpdateOpts{ - Status: services.ServiceDisabled, - } - updated, err := services.Update(context.TODO(), client, service.ID, opts).Extract() - th.AssertNoErr(t, err) - - th.AssertEquals(t, updated.ID, service.ID) - } - - // verify all services are disabled - allPages, err = services.List(client, listOpts).AllPages(context.TODO()) - th.AssertNoErr(t, err) - - allServices, err = services.ExtractServices(allPages) - th.AssertNoErr(t, err) - - for _, service := range allServices { - th.AssertEquals(t, service.Status, "disabled") - } - - // reenable all services - allPages, err = services.List(client, listOpts).AllPages(context.TODO()) - th.AssertNoErr(t, err) - - allServices, err = services.ExtractServices(allPages) - th.AssertNoErr(t, err) - - for _, service := range allServices { - opts := services.UpdateOpts{ - Status: services.ServiceEnabled, - } - updated, err := services.Update(context.TODO(), client, service.ID, opts).Extract() - th.AssertNoErr(t, err) - - th.AssertEquals(t, updated.ID, service.ID) - } + th.AssertEquals(t, true, 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/containerinfra/v1/clusters_test.go b/internal/acceptance/openstack/containerinfra/v1/clusters_test.go index 7a277b318a..d12c31bf4a 100644 --- a/internal/acceptance/openstack/containerinfra/v1/clusters_test.go +++ b/internal/acceptance/openstack/containerinfra/v1/clusters_test.go @@ -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.AssertEquals(t, newCluster.MasterLBEnabled, false) allPagesDetail, err := clusters.ListDetail(client, nil).AllPages(context.TODO()) th.AssertNoErr(t, err) diff --git a/internal/acceptance/openstack/containerinfra/v1/containerinfra.go b/internal/acceptance/openstack/containerinfra/v1/containerinfra.go index 9b26c0ad2d..84ee98e87d 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) @@ -68,6 +73,7 @@ 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) 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..f43523340a 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() @@ -37,7 +37,7 @@ func TestExtensionsList(t *testing.T) { } 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/role_test.go b/internal/acceptance/openstack/identity/v2/role_test.go index 2eed42a3c3..49635f7f98 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() @@ -53,7 +53,7 @@ func TestRolesAddToUser(t *testing.T) { } func TestRolesList(t *testing.T) { - clients.RequireIdentityV2(t) + RequireIdentityV2(t) clients.RequireAdmin(t) client, err := clients.NewIdentityV2AdminClient() diff --git a/internal/acceptance/openstack/identity/v2/tenant_test.go b/internal/acceptance/openstack/identity/v2/tenant_test.go index d390f14d35..2c7092e91d 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() @@ -38,7 +38,7 @@ func TestTenantsList(t *testing.T) { } 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..e0f614e511 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() @@ -38,7 +38,7 @@ func TestUsersList(t *testing.T) { } 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..7a4ad01e1c 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, @@ -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, diff --git a/internal/acceptance/openstack/identity/v3/credentials_test.go b/internal/acceptance/openstack/identity/v3/credentials_test.go index a1167ac647..ce0362abee 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, @@ -101,10 +101,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, 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..8dd9e5bad1 100644 --- a/internal/acceptance/openstack/identity/v3/endpoint_test.go +++ b/internal/acceptance/openstack/identity/v3/endpoint_test.go @@ -39,6 +39,29 @@ func TestEndpointsList(t *testing.T) { th.AssertEquals(t, found, true) } +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, e.Name, e.Name) +} + func TestEndpointsNavigateCatalog(t *testing.T) { clients.RequireAdmin(t) diff --git a/internal/acceptance/openstack/identity/v3/federation_test.go b/internal/acceptance/openstack/identity/v3/federation_test.go index b52d54cc30..35986dc15c 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) diff --git a/internal/acceptance/openstack/identity/v3/oauth1_test.go b/internal/acceptance/openstack/identity/v3/oauth1_test.go index da6c8be148..125fdd0588 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, diff --git a/internal/acceptance/openstack/identity/v3/token_test.go b/internal/acceptance/openstack/identity/v3/token_test.go index d5274ff6b1..1bc436caa7 100644 --- a/internal/acceptance/openstack/identity/v3/token_test.go +++ b/internal/acceptance/openstack/identity/v3/token_test.go @@ -24,8 +24,16 @@ 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() diff --git a/internal/acceptance/openstack/identity/v3/trusts_test.go b/internal/acceptance/openstack/identity/v3/trusts_test.go index 39c7191eea..2eb659d55b 100644 --- a/internal/acceptance/openstack/identity/v3/trusts_test.go +++ b/internal/acceptance/openstack/identity/v3/trusts_test.go @@ -29,9 +29,16 @@ 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() diff --git a/internal/acceptance/openstack/loadbalancer/v2/loadbalancer.go b/internal/acceptance/openstack/loadbalancer/v2/loadbalancer.go index e8596506ef..db067a81c1 100644 --- a/internal/acceptance/openstack/loadbalancer/v2/loadbalancer.go +++ b/internal/acceptance/openstack/loadbalancer/v2/loadbalancer.go @@ -213,6 +213,7 @@ func CreateLoadBalancerFullyPopulated(t *testing.T, client *gophercloud.ServiceC MaxRetries: 5, MaxRetriesDown: 4, Type: monitors.TypeHTTP, + HTTPVersion: "1.0", }, }, L7Policies: []l7policies.CreateOpts{{ @@ -271,6 +272,13 @@ func CreateLoadBalancerFullyPopulated(t *testing.T, client *gophercloud.ServiceC th.AssertEquals(t, lb.Pools[0].Members[0].ProtocolPort, memberPort) th.AssertEquals(t, lb.Pools[0].Members[0].Weight, memberWeight) + th.AssertEquals(t, lb.Pools[0].Monitor.Delay, 10) + th.AssertEquals(t, lb.Pools[0].Monitor.Timeout, 5) + th.AssertEquals(t, lb.Pools[0].Monitor.MaxRetries, 5) + th.AssertEquals(t, lb.Pools[0].Monitor.MaxRetriesDown, 4) + th.AssertEquals(t, lb.Pools[0].Monitor.Type, string(monitors.TypeHTTP)) + th.AssertEquals(t, lb.Pools[0].Monitor.HTTPVersion, "1.0") + if len(tags) > 0 { th.AssertDeepEquals(t, lb.Tags, tags) } @@ -332,6 +340,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() @@ -351,6 +360,7 @@ func CreateMonitor(t *testing.T, client *gophercloud.ServiceClient, lb *loadbala th.AssertEquals(t, monitor.Timeout, 5) th.AssertEquals(t, monitor.MaxRetries, 5) th.AssertEquals(t, monitor.MaxRetriesDown, 4) + th.AssertEquals(t, monitor.HTTPVersion, "1.1") return monitor, nil } diff --git a/internal/acceptance/openstack/networking/v2/conditions.go b/internal/acceptance/openstack/networking/v2/conditions.go index 036cba6044..6ce6b145f3 100644 --- a/internal/acceptance/openstack/networking/v2/conditions.go +++ b/internal/acceptance/openstack/networking/v2/conditions.go @@ -2,6 +2,7 @@ package v2 import ( "context" + "os" "testing" "github.com/gophercloud/gophercloud/v2" @@ -16,3 +17,11 @@ func RequireNeutronExtension(t *testing.T, client *gophercloud.ServiceClient, ex t.Skipf("this test requires %s Neutron extension", extension) } } + +// 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") + } +} 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..068ea001c5 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) { +func TestBGPAgentCRUD(t *testing.T) { timeout := 15 * time.Minute 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", 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..baad9a8a5e 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) 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..2ab5584d5b 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) 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..3c915901e8 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) @@ -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) 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..fd239f8a40 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/dns/dns_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/dns/dns_test.go @@ -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() diff --git a/internal/acceptance/openstack/networking/v2/extensions/extensions.go b/internal/acceptance/openstack/networking/v2/extensions/extensions.go index 4de1401f4d..f50878abeb 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/extensions.go +++ b/internal/acceptance/openstack/networking/v2/extensions/extensions.go @@ -43,8 +43,8 @@ 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) return network, nil } @@ -74,9 +74,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 +101,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 } @@ -140,6 +140,44 @@ func CreateSecurityGroupRule(t *testing.T, client *gophercloud.ServiceClient, se return rule, nil } +// CreateSecurityGroupRulesBulk will create security group rules with a random name +// and random port between 80 and 99. +// 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 3 { + description := "Rule description" + fromPort := tools.RandomInt(1080, 1089) + toPort := tools.RandomInt(1090, 1099) + + sgRulesCreateOpts[i] = rules.CreateOpts{ + Description: 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 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..5f4d771614 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" @@ -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/layer3.go b/internal/acceptance/openstack/networking/v2/extensions/layer3/layer3.go index 1edc2d0cc8..c45bb08069 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/layer3/layer3.go +++ b/internal/acceptance/openstack/networking/v2/extensions/layer3/layer3.go @@ -75,7 +75,9 @@ func CreatePortForwarding(t *testing.T, client *gophercloud.ServiceClient, fipID fixedIP := portFixedIPs[0] internalIP := fixedIP.IPAddress + pfDescription := "Test description" createOpts := &portforwarding.CreateOpts{ + Description: pfDescription, Protocol: "tcp", InternalPort: 25, ExternalPort: 2230, 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 bd9ccf3ad4..d6f3e9595e 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/layer3/portforwardings_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/layer3/portforwardings_test.go @@ -15,7 +15,7 @@ import ( ) func TestLayer3PortForwardingsCreateDelete(t *testing.T) { - clients.RequirePortForwarding(t) + networking.RequirePortForwarding(t) client, err := clients.NewNetworkV2Client() th.AssertNoErr(t, err) @@ -56,6 +56,7 @@ 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") defer DeletePortForwarding(t, client, fip.ID, pf.ID) tools.PrintResource(t, pf) @@ -63,6 +64,7 @@ func TestLayer3PortForwardingsCreateDelete(t *testing.T) { th.AssertNoErr(t, err) updateOpts := portforwarding.UpdateOpts{ + Description: new(string), Protocol: "udp", InternalPort: 30, ExternalPort: 678, @@ -73,6 +75,7 @@ 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, "") allPages, err := portforwarding.List(client, portforwarding.ListOpts{}, fip.ID).AllPages(context.TODO()) th.AssertNoErr(t, err) 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..5930be2a1e 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" @@ -213,3 +214,70 @@ func TestLayer3RouterAgents(t *testing.T) { th.AssertEquals(t, found, true) } + +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) +} 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/qos/policies/policies_test.go b/internal/acceptance/openstack/networking/v2/extensions/qos/policies/policies_test.go index fc9563df38..9ee2304c75 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" @@ -59,3 +60,68 @@ func TestPoliciesCRUD(t *testing.T) { th.AssertEquals(t, found, true) } + +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/security_test.go b/internal/acceptance/openstack/networking/v2/extensions/security_test.go index 7d0d967e46..33fc3f176d 100644 --- a/internal/acceptance/openstack/networking/v2/extensions/security_test.go +++ b/internal/acceptance/openstack/networking/v2/extensions/security_test.go @@ -4,6 +4,7 @@ package extensions import ( "context" + "strings" "testing" "github.com/gophercloud/gophercloud/v2/internal/acceptance/clients" @@ -20,11 +21,18 @@ 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) 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" @@ -32,6 +40,7 @@ func TestSecurityGroupsCreateUpdateDelete(t *testing.T) { updateOpts := groups.UpdateOpts{ Name: name, Description: &description, + Stateful: new(bool), } newGroup, err := groups.Update(context.TODO(), client, group.ID, updateOpts).Extract() @@ -40,6 +49,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) listOpts := groups.ListOpts{} allPages, err := groups.List(client, listOpts).AllPages(context.TODO()) @@ -84,3 +94,65 @@ 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{ + Name: newName, + 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: newName, + 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, newName) + th.AssertEquals(t, group.Description, newDescription) +} 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..8c9cb3120b 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" @@ -55,3 +56,63 @@ func TestSubnetPoolsCRUD(t *testing.T) { th.AssertEquals(t, found, true) } + +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/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/vpnaas/group_test.go b/internal/acceptance/openstack/networking/v2/extensions/vpnaas/group_test.go index 8fe5e0638c..862324736f 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) 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..eeb573edf0 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) 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..74dc5a3758 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) 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/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_test.go b/internal/acceptance/openstack/sharedfilesystems/v2/replicas_test.go index 41913ac266..8432afeb77 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 { @@ -198,7 +198,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 +231,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 +272,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/tools/tools.go b/internal/acceptance/tools/tools.go index 8b0fad2ea6..75ac004473 100644 --- a/internal/acceptance/tools/tools.go +++ b/internal/acceptance/tools/tools.go @@ -81,5 +81,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/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..d0e11553c2 100644 --- a/openstack/baremetal/apiversions/testing/fixtures_test.go +++ b/openstack/baremetal/apiversions/testing/fixtures_test.go @@ -89,7 +89,7 @@ func MockListResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, IronicAPIAllVersionResponse) + fmt.Fprint(w, IronicAPIAllVersionResponse) }) } @@ -101,6 +101,6 @@ func MockGetResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, IronicAPIVersionResponse) + fmt.Fprint(w, IronicAPIVersionResponse) }) } diff --git a/openstack/baremetal/v1/allocations/testing/fixtures_test.go b/openstack/baremetal/v1/allocations/testing/fixtures_test.go index 6b8655169b..7ee9fa4834 100644 --- a/openstack/baremetal/v1/allocations/testing/fixtures_test.go +++ b/openstack/baremetal/v1/allocations/testing/fixtures_test.go @@ -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) } @@ -145,7 +145,7 @@ 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) }) } @@ -165,6 +165,6 @@ func HandleAllocationGetSuccessfully(t *testing.T) { 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/conductors/testing/fixtures_test.go b/openstack/baremetal/v1/conductors/testing/fixtures_test.go index 8e59ddaab4..fd66a34d4b 100644 --- a/openstack/baremetal/v1/conductors/testing/fixtures_test.go +++ b/openstack/baremetal/v1/conductors/testing/fixtures_test.go @@ -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) } @@ -170,7 +170,7 @@ func HandleConductorListDetailSuccessfully(t *testing.T) { t.Errorf("Failed to parse request form %v", err) } - fmt.Fprintf(w, ConductorListDetailBody) + fmt.Fprint(w, ConductorListDetailBody) }) } @@ -180,6 +180,6 @@ func HandleConductorGetSuccessfully(t *testing.T) { 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/drivers/testing/fixtures_test.go b/openstack/baremetal/v1/drivers/testing/fixtures_test.go index a06bcee928..6c6d0fcc7e 100644 --- a/openstack/baremetal/v1/drivers/testing/fixtures_test.go +++ b/openstack/baremetal/v1/drivers/testing/fixtures_test.go @@ -377,7 +377,7 @@ func HandleListDriversSuccessfully(t *testing.T) { t.Errorf("Failed to parse request form %v", err) } - fmt.Fprintf(w, ListDriversBody) + fmt.Fprint(w, ListDriversBody) }) } @@ -388,7 +388,7 @@ func HandleGetDriverDetailsSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleDriverDetails) + fmt.Fprint(w, SingleDriverDetails) }) } @@ -399,7 +399,7 @@ func HandleGetDriverPropertiesSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleDriverProperties) + fmt.Fprint(w, SingleDriverProperties) }) } @@ -410,6 +410,6 @@ func HandleGetDriverDiskPropertiesSuccessfully(t *testing.T) { 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/nodes/requests.go b/openstack/baremetal/v1/nodes/requests.go index f79721b4e7..6268f5037e 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" ) @@ -274,6 +274,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 +1002,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..b33517a388 100644 --- a/openstack/baremetal/v1/nodes/results.go +++ b/openstack/baremetal/v1/nodes/results.go @@ -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"` @@ -183,8 +190,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 +218,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 +245,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 +263,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 +308,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 @@ -653,3 +710,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..371a4f00a9 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", @@ -149,6 +150,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 +196,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 +251,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 +297,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 +352,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, @@ -456,6 +465,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 +866,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 +875,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 +900,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") @@ -917,23 +950,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 +1010,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 +1146,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 +1216,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{ @@ -1297,10 +1431,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) } @@ -1317,7 +1451,7 @@ func HandleNodeListDetailSuccessfully(t *testing.T) { t.Errorf("Failed to parse request form %v", err) } - fmt.Fprintf(w, NodeListDetailBody) + fmt.Fprint(w, NodeListDetailBody) }) } @@ -1344,7 +1478,7 @@ 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) }) } @@ -1364,7 +1498,7 @@ func HandleNodeGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleNodeBody) + fmt.Fprint(w, SingleNodeBody) }) } @@ -1376,7 +1510,7 @@ func HandleNodeUpdateSuccessfully(t *testing.T, response string) { 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) }) } @@ -1386,7 +1520,7 @@ func HandleNodeValidateSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, NodeValidationBody) + fmt.Fprint(w, NodeValidationBody) }) } @@ -1418,7 +1552,7 @@ func HandleGetBootDeviceSuccessfully(t *testing.T) { 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) }) } @@ -1428,7 +1562,7 @@ func HandleGetSupportedBootDeviceSuccessfully(t *testing.T) { 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) }) } @@ -1560,7 +1694,7 @@ func HandleListBIOSSettingsSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, NodeBIOSSettingsBody) + fmt.Fprint(w, NodeBIOSSettingsBody) }) } @@ -1570,7 +1704,7 @@ func HandleListDetailBIOSSettingsSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, NodeDetailBIOSSettingsBody) + fmt.Fprint(w, NodeDetailBIOSSettingsBody) }) } @@ -1580,7 +1714,7 @@ func HandleGetBIOSSettingSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, NodeSingleBIOSSettingBody) + fmt.Fprint(w, NodeSingleBIOSSettingBody) }) } @@ -1590,7 +1724,7 @@ func HandleGetVendorPassthruMethodsSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, NodeVendorPassthruMethodsBody) + fmt.Fprint(w, NodeVendorPassthruMethodsBody) }) } @@ -1601,7 +1735,7 @@ func HandleGetAllSubscriptionsVendorPassthruSuccessfully(t *testing.T) { 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) }) } @@ -1617,7 +1751,7 @@ func HandleGetSubscriptionVendorPassthruSuccessfully(t *testing.T) { } `) - fmt.Fprintf(w, NodeGetSubscriptionVendorPassthruBody) + fmt.Fprint(w, NodeGetSubscriptionVendorPassthruBody) }) } @@ -1637,7 +1771,7 @@ func HandleCreateSubscriptionVendorPassthruAllParametersSuccessfully(t *testing. } `) - fmt.Fprintf(w, NodeCreateSubscriptionVendorPassthruAllParametersBody) + fmt.Fprint(w, NodeCreateSubscriptionVendorPassthruAllParametersBody) }) } @@ -1653,7 +1787,7 @@ func HandleCreateSubscriptionVendorPassthruRequiredParametersSuccessfully(t *tes } `) - fmt.Fprintf(w, NodeCreateSubscriptionVendorPassthruRequiredParametersBody) + fmt.Fprint(w, NodeCreateSubscriptionVendorPassthruRequiredParametersBody) }) } @@ -1698,7 +1832,7 @@ func HandleGetInventorySuccessfully(t *testing.T) { 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) }) } @@ -1708,7 +1842,7 @@ func HandleListFirmwareSuccessfully(t *testing.T) { 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) }) } @@ -1737,3 +1871,91 @@ func HandleDetachVirtualMediaSuccessfully(t *testing.T, withType bool) { w.WriteHeader(http.StatusNoContent) }) } + +func HandleGetVirtualMediaSuccessfully(t *testing.T, attached bool) { + th.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) { + th.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) { + th.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) { + th.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) { + th.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) { + th.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..24452be0c2 100644 --- a/openstack/baremetal/v1/nodes/testing/requests_test.go +++ b/openstack/baremetal/v1/nodes/testing/requests_test.go @@ -829,3 +829,103 @@ func TestVirtualMediaDetachWithTypes(t *testing.T) { err := nodes.DetachVirtualMedia(context.TODO(), c, "1234asdf", opts).ExtractErr() th.AssertNoErr(t, err) } + +func TestVirtualMediaGetAttached(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetVirtualMediaSuccessfully(t, true) + + c := client.ServiceClient() + err := nodes.GetVirtualMedia(context.TODO(), c, "1234asdf").Err + th.AssertNoErr(t, err) +} + +func TestVirtualMediaGetNotAttached(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetVirtualMediaSuccessfully(t, false) + + c := client.ServiceClient() + err := nodes.GetVirtualMedia(context.TODO(), c, "1234asdf").Err + th.AssertNoErr(t, err) +} + +func TestListVirtualInterfaces(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListVirtualInterfacesSuccessfully(t) + + c := client.ServiceClient() + 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) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleAttachVirtualInterfaceSuccessfully(t) + + c := client.ServiceClient() + 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) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleAttachVirtualInterfaceWithPortSuccessfully(t) + + c := client.ServiceClient() + 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) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleAttachVirtualInterfaceWithPortgroupSuccessfully(t) + + c := client.ServiceClient() + 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) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDetachVirtualInterfaceSuccessfully(t) + + c := client.ServiceClient() + 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, err.Error(), "cannot specify both port_uuid and portgroup_uuid") +} diff --git a/openstack/baremetal/v1/nodes/testing/results_test.go b/openstack/baremetal/v1/nodes/testing/results_test.go index 7c683ebecb..acdb59e774 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) + 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) + 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) + 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) + 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) + 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/ports/testing/fixtures_test.go b/openstack/baremetal/v1/ports/testing/fixtures_test.go index 492aa42aa6..a6221ecd4a 100644 --- a/openstack/baremetal/v1/ports/testing/fixtures_test.go +++ b/openstack/baremetal/v1/ports/testing/fixtures_test.go @@ -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) } @@ -201,7 +201,7 @@ func HandlePortListDetailSuccessfully(t *testing.T) { t.Errorf("Failed to parse request form %v", err) } - fmt.Fprintf(w, PortListDetailBody) + fmt.Fprint(w, PortListDetailBody) }) } @@ -219,7 +219,7 @@ 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) }) } @@ -239,7 +239,7 @@ func HandlePortGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SinglePortBody) + fmt.Fprint(w, SinglePortBody) }) } @@ -251,6 +251,6 @@ func HandlePortUpdateSuccessfully(t *testing.T, response string) { 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/baremetalintrospection/v1/introspection/testing/fixtures.go b/openstack/baremetalintrospection/v1/introspection/testing/fixtures.go index af2ad9e7f5..56a4299aef 100644 --- a/openstack/baremetalintrospection/v1/introspection/testing/fixtures.go +++ b/openstack/baremetalintrospection/v1/introspection/testing/fixtures.go @@ -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) @@ -484,7 +484,7 @@ func HandleGetIntrospectionStatusSuccessfully(t *testing.T) { 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) }) } @@ -513,7 +513,7 @@ func HandleGetIntrospectionDataSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, IntrospectionDataJSONSample) + fmt.Fprint(w, IntrospectionDataJSONSample) }) } diff --git a/openstack/blockstorage/apiversions/testing/fixtures_test.go b/openstack/blockstorage/apiversions/testing/fixtures_test.go index 4a368e9da9..ed78b1e472 100644 --- a/openstack/blockstorage/apiversions/testing/fixtures_test.go +++ b/openstack/blockstorage/apiversions/testing/fixtures_test.go @@ -124,7 +124,7 @@ func MockListResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, APIListResponse) + fmt.Fprint(w, APIListResponse) }) } @@ -136,6 +136,6 @@ func MockListOldResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, APIListOldResponse) + fmt.Fprint(w, APIListOldResponse) }) } diff --git a/openstack/blockstorage/noauth/requests.go b/openstack/blockstorage/noauth/requests.go index 87f2d9b7f9..cf887553f0 100644 --- a/openstack/blockstorage/noauth/requests.go +++ b/openstack/blockstorage/noauth/requests.go @@ -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..5c48bdce2e 100644 --- a/openstack/blockstorage/v2/availabilityzones/testing/fixtures_test.go +++ b/openstack/blockstorage/v2/availabilityzones/testing/fixtures_test.go @@ -47,6 +47,6 @@ func HandleGetSuccessfully(t *testing.T) { 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/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/testing/fixtures_test.go b/openstack/blockstorage/v2/backups/testing/fixtures_test.go index 2f24173418..4f1f45c122 100644 --- a/openstack/blockstorage/v2/backups/testing/fixtures_test.go +++ b/openstack/blockstorage/v2/backups/testing/fixtures_test.go @@ -210,7 +210,7 @@ func MockListResponse(t *testing.T) { case "": fmt.Fprintf(w, ListResponse, th.Server.URL) case "1": - fmt.Fprintf(w, `{"backups": []}`) + fmt.Fprint(w, `{"backups": []}`) default: t.Fatalf("Unexpected marker: [%s]", marker) } @@ -233,7 +233,7 @@ func MockListDetailResponse(t *testing.T) { case "": fmt.Fprintf(w, ListDetailResponse, th.Server.URL) case "1": - fmt.Fprintf(w, `{"backups": []}`) + fmt.Fprint(w, `{"backups": []}`) default: t.Fatalf("Unexpected marker: [%s]", marker) } @@ -247,7 +247,7 @@ func MockGetResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) } @@ -262,7 +262,7 @@ func MockCreateResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) } @@ -277,7 +277,7 @@ func MockRestoreResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, RestoreResponse) + fmt.Fprint(w, RestoreResponse) }) } @@ -298,7 +298,7 @@ func MockExportResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ExportResponse) + fmt.Fprint(w, ExportResponse) }) } @@ -313,7 +313,7 @@ func MockImportResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ImportResponse) + fmt.Fprint(w, ImportResponse) }) } diff --git a/openstack/blockstorage/v2/limits/testing/fixtures_test.go b/openstack/blockstorage/v2/limits/testing/fixtures_test.go index c3f8bf803d..bdd1308a17 100644 --- a/openstack/blockstorage/v2/limits/testing/fixtures_test.go +++ b/openstack/blockstorage/v2/limits/testing/fixtures_test.go @@ -124,6 +124,6 @@ func HandleGetSuccessfully(t *testing.T) { 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/quotasets/testing/fixtures_test.go b/openstack/blockstorage/v2/quotasets/testing/fixtures_test.go index 066e1729c8..854ff08b63 100644 --- a/openstack/blockstorage/v2/quotasets/testing/fixtures_test.go +++ b/openstack/blockstorage/v2/quotasets/testing/fixtures_test.go @@ -164,7 +164,7 @@ func HandleSuccessfulRequest(t *testing.T, httpMethod, uriPath, jsonOutput strin th.TestFormValues(t, r, uriQueryParams) } - fmt.Fprintf(w, jsonOutput) + fmt.Fprint(w, jsonOutput) }) } diff --git a/openstack/blockstorage/v2/schedulerstats/testing/fixtures_test.go b/openstack/blockstorage/v2/schedulerstats/testing/fixtures_test.go index 6f0317ef60..05512dbf6d 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" ) @@ -94,9 +94,9 @@ 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) + th.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..7a6cb8b450 100644 --- a/openstack/blockstorage/v2/schedulerstats/testing/requests_test.go +++ b/openstack/blockstorage/v2/schedulerstats/testing/requests_test.go @@ -6,13 +6,13 @@ 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() + th.SetupHTTP() + defer th.TeardownHTTP() HandleStoragePoolsListSuccessfully(t) pages := 0 @@ -20,18 +20,18 @@ func TestListStoragePoolsDetail(t *testing.T) { 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..41fb5bc614 100644 --- a/openstack/blockstorage/v2/services/testing/fixtures_test.go +++ b/openstack/blockstorage/v2/services/testing/fixtures_test.go @@ -92,6 +92,6 @@ func HandleListSuccessfully(t *testing.T) { 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..d1277a99bf 100644 --- a/openstack/blockstorage/v2/services/testing/requests_test.go +++ b/openstack/blockstorage/v2/services/testing/requests_test.go @@ -6,13 +6,13 @@ 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() + th.SetupHTTP() + defer th.TeardownHTTP() HandleListSuccessfully(t) pages := 0 @@ -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..b2e5cf4c06 100644 --- a/openstack/blockstorage/v2/snapshots/testing/fixtures_test.go +++ b/openstack/blockstorage/v2/snapshots/testing/fixtures_test.go @@ -17,7 +17,7 @@ func MockListResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "snapshots": [ { @@ -51,7 +51,7 @@ func MockGetResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "snapshot": { "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", @@ -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", @@ -115,7 +115,7 @@ func MockUpdateMetadataResponse(t *testing.T) { } `) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "metadata": { "key": "v1" 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/testing/fixtures_test.go b/openstack/blockstorage/v2/transfers/testing/fixtures_test.go index 6a150508eb..866d5068fc 100644 --- a/openstack/blockstorage/v2/transfers/testing/fixtures_test.go +++ b/openstack/blockstorage/v2/transfers/testing/fixtures_test.go @@ -167,7 +167,7 @@ func HandleCreateTransfer(t *testing.T) { th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) } @@ -179,7 +179,7 @@ func HandleAcceptTransfer(t *testing.T) { th.TestJSONRequest(t, r, AcceptTransferRequest) w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, AcceptTransferResponse) + fmt.Fprint(w, AcceptTransferResponse) }) } @@ -200,7 +200,7 @@ func HandleListTransfers(t *testing.T) { th.TestFormValues(t, r, map[string]string{"all_tenants": "true"}) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } @@ -211,6 +211,6 @@ func HandleGetTransfer(t *testing.T) { 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/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/testing/fixtures_test.go b/openstack/blockstorage/v2/volumes/testing/fixtures_test.go index 80677d2e40..d6688f7850 100644 --- a/openstack/blockstorage/v2/volumes/testing/fixtures_test.go +++ b/openstack/blockstorage/v2/volumes/testing/fixtures_test.go @@ -17,7 +17,7 @@ func MockListResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volumes": [ { @@ -93,7 +93,7 @@ func MockGetResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volume": { "volume_type": "lvmdriver-1", @@ -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, @@ -192,7 +192,7 @@ func MockUpdateResponse(t *testing.T) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volume": { "name": "vol-002" @@ -223,7 +223,7 @@ func MockAttachResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } @@ -243,7 +243,7 @@ func MockBeginDetachingResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } @@ -263,7 +263,7 @@ func MockDetachResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } @@ -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", @@ -335,7 +335,7 @@ func MockReserveResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } @@ -355,7 +355,7 @@ func MockUnreserveResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } @@ -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": [ @@ -443,7 +443,7 @@ func MockTerminateConnectionResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } @@ -466,7 +466,7 @@ func MockExtendSizeResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } @@ -497,7 +497,7 @@ func MockSetImageMetadataResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } @@ -560,7 +560,7 @@ func MockChangeTypeResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } diff --git a/openstack/blockstorage/v3/attachments/testing/fixtures_test.go b/openstack/blockstorage/v3/attachments/testing/fixtures_test.go index d489ce16ef..18471c3b38 100644 --- a/openstack/blockstorage/v3/attachments/testing/fixtures_test.go +++ b/openstack/blockstorage/v3/attachments/testing/fixtures_test.go @@ -64,7 +64,7 @@ func MockListResponse(t *testing.T) { } `, th.Server.URL) case "1": - fmt.Fprintf(w, `{"volumes": []}`) + fmt.Fprint(w, `{"volumes": []}`) default: t.Fatalf("Unexpected marker: [%s]", marker) } @@ -78,7 +78,7 @@ func MockGetResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "attachment": { "status": "attaching", @@ -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", @@ -171,7 +171,7 @@ func MockUpdateResponse(t *testing.T) { `) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "attachment": { "status": "attaching", @@ -202,7 +202,7 @@ func MockUpdateEmptyResponse(t *testing.T) { `) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "attachment": { "status": "attaching", diff --git a/openstack/blockstorage/v3/availabilityzones/testing/fixtures_test.go b/openstack/blockstorage/v3/availabilityzones/testing/fixtures_test.go index 98f93db1d3..780c7dec26 100644 --- a/openstack/blockstorage/v3/availabilityzones/testing/fixtures_test.go +++ b/openstack/blockstorage/v3/availabilityzones/testing/fixtures_test.go @@ -47,6 +47,6 @@ func HandleGetSuccessfully(t *testing.T) { 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/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/testing/fixtures_test.go b/openstack/blockstorage/v3/backups/testing/fixtures_test.go index 13fc1d61d9..9bbaec87d4 100644 --- a/openstack/blockstorage/v3/backups/testing/fixtures_test.go +++ b/openstack/blockstorage/v3/backups/testing/fixtures_test.go @@ -210,7 +210,7 @@ func MockListResponse(t *testing.T) { case "": fmt.Fprintf(w, ListResponse, th.Server.URL) case "1": - fmt.Fprintf(w, `{"backups": []}`) + fmt.Fprint(w, `{"backups": []}`) default: t.Fatalf("Unexpected marker: [%s]", marker) } @@ -233,7 +233,7 @@ func MockListDetailResponse(t *testing.T) { case "": fmt.Fprintf(w, ListDetailResponse, th.Server.URL) case "1": - fmt.Fprintf(w, `{"backups": []}`) + fmt.Fprint(w, `{"backups": []}`) default: t.Fatalf("Unexpected marker: [%s]", marker) } @@ -247,7 +247,7 @@ func MockGetResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) } @@ -262,7 +262,7 @@ func MockCreateResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) } @@ -277,7 +277,7 @@ func MockRestoreResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, RestoreResponse) + fmt.Fprint(w, RestoreResponse) }) } @@ -298,7 +298,7 @@ func MockExportResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ExportResponse) + fmt.Fprint(w, ExportResponse) }) } @@ -313,7 +313,7 @@ func MockImportResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ImportResponse) + fmt.Fprint(w, ImportResponse) }) } diff --git a/openstack/blockstorage/v3/limits/testing/fixtures_test.go b/openstack/blockstorage/v3/limits/testing/fixtures_test.go index 5d3b647427..68fe8ae833 100644 --- a/openstack/blockstorage/v3/limits/testing/fixtures_test.go +++ b/openstack/blockstorage/v3/limits/testing/fixtures_test.go @@ -124,6 +124,6 @@ func HandleGetSuccessfully(t *testing.T) { 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/qos/testing/fixtures_test.go b/openstack/blockstorage/v3/qos/testing/fixtures_test.go index 8f9d2e2d51..e6cd002cea 100644 --- a/openstack/blockstorage/v3/qos/testing/fixtures_test.go +++ b/openstack/blockstorage/v3/qos/testing/fixtures_test.go @@ -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", @@ -110,7 +110,7 @@ func MockListResponse(t *testing.T) { } `, th.Server.URL) case "2": - fmt.Fprintf(w, `{ "qos_specs": [] }`) + fmt.Fprint(w, `{ "qos_specs": [] }`) default: t.Fatalf("Unexpected marker: [%s]", marker) } @@ -124,7 +124,7 @@ func MockGetResponse(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", @@ -172,7 +172,7 @@ func MockUpdateResponse(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateBody) + fmt.Fprint(w, UpdateBody) }) } @@ -219,7 +219,7 @@ func MockListAssociationsResponse(t *testing.T) { 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, ` { "qos_associations": [ { diff --git a/openstack/blockstorage/v3/quotasets/testing/fixtures_test.go b/openstack/blockstorage/v3/quotasets/testing/fixtures_test.go index df029ff8e1..103afef6ef 100644 --- a/openstack/blockstorage/v3/quotasets/testing/fixtures_test.go +++ b/openstack/blockstorage/v3/quotasets/testing/fixtures_test.go @@ -164,7 +164,7 @@ func HandleSuccessfulRequest(t *testing.T, httpMethod, uriPath, jsonOutput strin th.TestFormValues(t, r, uriQueryParams) } - fmt.Fprintf(w, jsonOutput) + fmt.Fprint(w, jsonOutput) }) } diff --git a/openstack/blockstorage/v3/schedulerstats/testing/fixtures_test.go b/openstack/blockstorage/v3/schedulerstats/testing/fixtures_test.go index df0ccaa7f5..18462bfb20 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" ) @@ -94,9 +94,9 @@ 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) + th.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..c125763276 100644 --- a/openstack/blockstorage/v3/schedulerstats/testing/requests_test.go +++ b/openstack/blockstorage/v3/schedulerstats/testing/requests_test.go @@ -6,13 +6,13 @@ 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() + th.SetupHTTP() + defer th.TeardownHTTP() HandleStoragePoolsListSuccessfully(t) pages := 0 @@ -20,18 +20,18 @@ func TestListStoragePoolsDetail(t *testing.T) { 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..63771ff032 100644 --- a/openstack/blockstorage/v3/services/testing/fixtures_test.go +++ b/openstack/blockstorage/v3/services/testing/fixtures_test.go @@ -92,6 +92,6 @@ func HandleListSuccessfully(t *testing.T) { 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..4e291da96b 100644 --- a/openstack/blockstorage/v3/services/testing/requests_test.go +++ b/openstack/blockstorage/v3/services/testing/requests_test.go @@ -6,13 +6,13 @@ 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() + th.SetupHTTP() + defer th.TeardownHTTP() HandleListSuccessfully(t) pages := 0 @@ -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..87a2dec2c6 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. diff --git a/openstack/blockstorage/v3/snapshots/testing/fixtures_test.go b/openstack/blockstorage/v3/snapshots/testing/fixtures_test.go index 8bbc45e77d..e1bfd37584 100644 --- a/openstack/blockstorage/v3/snapshots/testing/fixtures_test.go +++ b/openstack/blockstorage/v3/snapshots/testing/fixtures_test.go @@ -54,7 +54,63 @@ func MockListResponse(t *testing.T) { } `, th.Server.URL) case "1": - fmt.Fprintf(w, `{"snapshots": []}`) + fmt.Fprint(w, `{"snapshots": []}`) + default: + t.Fatalf("Unexpected marker: [%s]", marker) + } + }) +} + +func MockListDetailsResponse(t *testing.T) { + th.Mux.HandleFunc("/snapshots/detail", 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) + + 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) } @@ -69,7 +125,7 @@ func MockGetResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "snapshot": { "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", @@ -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", @@ -135,7 +191,7 @@ func MockUpdateMetadataResponse(t *testing.T) { } `) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "metadata": { "key": "v1" @@ -162,7 +218,7 @@ func MockUpdateResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "snapshot": { "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", diff --git a/openstack/blockstorage/v3/snapshots/testing/requests_test.go b/openstack/blockstorage/v3/snapshots/testing/requests_test.go index e0ca0b8e0f..aca65ba7a0 100644 --- a/openstack/blockstorage/v3/snapshots/testing/requests_test.go +++ b/openstack/blockstorage/v3/snapshots/testing/requests_test.go @@ -59,6 +59,62 @@ func TestList(t *testing.T) { } } +func TestDetailList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockListDetailsResponse(t) + + count := 0 + + err := snapshots.ListDetail(client.ServiceClient(), &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() 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/testing/fixtures_test.go b/openstack/blockstorage/v3/transfers/testing/fixtures_test.go index b2c11f60c7..859e454eca 100644 --- a/openstack/blockstorage/v3/transfers/testing/fixtures_test.go +++ b/openstack/blockstorage/v3/transfers/testing/fixtures_test.go @@ -167,7 +167,7 @@ func HandleCreateTransfer(t *testing.T) { th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) } @@ -179,7 +179,7 @@ func HandleAcceptTransfer(t *testing.T) { th.TestJSONRequest(t, r, AcceptTransferRequest) w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, AcceptTransferResponse) + fmt.Fprint(w, AcceptTransferResponse) }) } @@ -200,7 +200,7 @@ func HandleListTransfers(t *testing.T) { th.TestFormValues(t, r, map[string]string{"all_tenants": "true"}) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } @@ -211,6 +211,6 @@ func HandleGetTransfer(t *testing.T) { 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/volumes/requests.go b/openstack/blockstorage/v3/volumes/requests.go index 77210943b5..4dc2c1be7e 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 diff --git a/openstack/blockstorage/v3/volumes/testing/fixtures_test.go b/openstack/blockstorage/v3/volumes/testing/fixtures_test.go index 4efb9e08f4..9b4630a978 100644 --- a/openstack/blockstorage/v3/volumes/testing/fixtures_test.go +++ b/openstack/blockstorage/v3/volumes/testing/fixtures_test.go @@ -95,7 +95,7 @@ func MockListResponse(t *testing.T) { } `, th.Server.URL) case "1": - fmt.Fprintf(w, `{"volumes": []}`) + fmt.Fprint(w, `{"volumes": []}`) default: t.Fatalf("Unexpected marker: [%s]", marker) } @@ -109,7 +109,7 @@ func MockGetResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volume": { "volume_type": "lvmdriver-1", @@ -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, @@ -211,7 +211,7 @@ func MockUpdateResponse(t *testing.T) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volume": { "name": "vol-002" @@ -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, @@ -287,7 +287,7 @@ func MockAttachResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } @@ -307,7 +307,7 @@ func MockBeginDetachingResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } @@ -327,7 +327,7 @@ func MockDetachResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } @@ -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", @@ -399,7 +399,7 @@ func MockReserveResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } @@ -419,7 +419,7 @@ func MockUnreserveResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } @@ -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": [ @@ -507,7 +507,7 @@ func MockTerminateConnectionResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } @@ -530,7 +530,7 @@ func MockExtendSizeResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } @@ -561,7 +561,7 @@ func MockSetImageMetadataResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } @@ -624,7 +624,7 @@ func MockChangeTypeResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } diff --git a/openstack/blockstorage/v3/volumetypes/testing/fixtures_test.go b/openstack/blockstorage/v3/volumetypes/testing/fixtures_test.go index e1db5dfbd4..c7eb37893e 100644 --- a/openstack/blockstorage/v3/volumetypes/testing/fixtures_test.go +++ b/openstack/blockstorage/v3/volumetypes/testing/fixtures_test.go @@ -58,7 +58,7 @@ func MockListResponse(t *testing.T) { } `, th.Server.URL) case "1": - fmt.Fprintf(w, `{"volume_types": []}`) + fmt.Fprint(w, `{"volume_types": []}`) default: t.Fatalf("Unexpected marker: [%s]", marker) } @@ -72,7 +72,7 @@ func MockGetResponse(t *testing.T) { 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", @@ -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", @@ -143,7 +143,7 @@ func MockUpdateResponse(t *testing.T) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volume_type": { "name": "vol-type-002", @@ -203,7 +203,7 @@ func HandleExtraSpecsListSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ExtraSpecsGetBody) + fmt.Fprint(w, ExtraSpecsGetBody) }) } @@ -215,7 +215,7 @@ func HandleExtraSpecGetSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetExtraSpecBody) + fmt.Fprint(w, GetExtraSpecBody) }) } @@ -233,7 +233,7 @@ func HandleExtraSpecsCreateSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ExtraSpecsGetBody) + fmt.Fprint(w, ExtraSpecsGetBody) }) } @@ -248,7 +248,7 @@ func HandleExtraSpecUpdateSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdatedExtraSpecBody) + fmt.Fprint(w, UpdatedExtraSpecBody) }) } @@ -281,7 +281,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", @@ -324,7 +324,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", @@ -345,7 +345,7 @@ func MockEncryptionGetResponse(t *testing.T) { 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", @@ -370,7 +370,7 @@ func MockEncryptionGetSpecResponse(t *testing.T) { 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..f222be2395 100644 --- a/openstack/blockstorage/v3/volumetypes/testing/requests_test.go +++ b/openstack/blockstorage/v3/volumetypes/testing/requests_test.go @@ -190,7 +190,7 @@ func TestVolumeTypeListAccesses(t *testing.T) { 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": [ { diff --git a/openstack/client.go b/openstack/client.go index 43b569d3b4..122a3ee699 100644 --- a/openstack/client.go +++ b/openstack/client.go @@ -344,6 +344,7 @@ func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOp }, nil } +// TODO(stephenfin): Allow passing aliases to all New${SERVICE}V${VERSION} methods in v3 func initClientOpts(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts, clientType string) (*gophercloud.ServiceClient, error) { sc := new(gophercloud.ServiceClient) eo.ApplyDefaults(clientType) @@ -393,6 +394,7 @@ func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpt 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) { @@ -402,17 +404,17 @@ func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.Endpoi // 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") + return initClientOpts(client, eo, "block-storage") } // 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") + return initClientOpts(client, eo, "block-storage") } // 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") + return initClientOpts(client, eo, "shared-file-system") } // NewOrchestrationV1 creates a ServiceClient that may be used to access the v1 @@ -457,14 +459,14 @@ func NewLoadBalancerV2(client *gophercloud.ProviderClient, eo gophercloud.Endpoi // 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") + sc, err := initClientOpts(client, eo, "message") 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") + return initClientOpts(client, eo, "application-container") } // NewKeyManagerV1 creates a ServiceClient that may be used with the v1 key @@ -478,12 +480,12 @@ func NewKeyManagerV1(client *gophercloud.ProviderClient, eo gophercloud.Endpoint // 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") + return initClientOpts(client, eo, "container-infrastructure-management") } // 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") + return initClientOpts(client, eo, "workflow") } // NewPlacementV1 creates a ServiceClient that may be used with the placement package. diff --git a/openstack/common/extensions/testing/fixtures.go b/openstack/common/extensions/testing/fixtures.go index cfcdbe90e9..91f6a041b1 100644 --- a/openstack/common/extensions/testing/fixtures.go +++ b/openstack/common/extensions/testing/fixtures.go @@ -71,7 +71,7 @@ func HandleListExtensionsSuccessfully(t *testing.T) { w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } @@ -85,6 +85,6 @@ func HandleGetExtensionSuccessfully(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } diff --git a/openstack/compute/apiversions/testing/fixtures_test.go b/openstack/compute/apiversions/testing/fixtures_test.go index c7f4b1a977..49162c77ce 100644 --- a/openstack/compute/apiversions/testing/fixtures_test.go +++ b/openstack/compute/apiversions/testing/fixtures_test.go @@ -169,7 +169,7 @@ func MockListResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, NovaAllAPIVersionsResponse) + fmt.Fprint(w, NovaAllAPIVersionsResponse) }) } @@ -181,7 +181,7 @@ func MockGetResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, NovaAPIVersionResponse_21) + fmt.Fprint(w, NovaAPIVersionResponse_21) }) } @@ -193,6 +193,6 @@ func MockGetMultipleResponses(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, NovaAPIInvalidVersionResponse) + fmt.Fprint(w, NovaAPIInvalidVersionResponse) }) } diff --git a/openstack/compute/v2/aggregates/requests.go b/openstack/compute/v2/aggregates/requests.go index 7111c203b0..634b9fa97d 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,6 +70,12 @@ 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"` @@ -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/testing/fixtures_test.go b/openstack/compute/v2/aggregates/testing/fixtures_test.go index a67678404f..11cebc8915 100644 --- a/openstack/compute/v2/aggregates/testing/fixtures_test.go +++ b/openstack/compute/v2/aggregates/testing/fixtures_test.go @@ -264,7 +264,7 @@ func HandleListSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, AggregateListBody) + fmt.Fprint(w, AggregateListBody) }) } @@ -274,7 +274,7 @@ func HandleCreateSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, AggregateCreateBody) + fmt.Fprint(w, AggregateCreateBody) }) } @@ -295,7 +295,7 @@ func HandleGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, AggregateGetBody) + fmt.Fprint(w, AggregateGetBody) }) } @@ -306,7 +306,7 @@ func HandleUpdateSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, AggregateUpdateBody) + fmt.Fprint(w, AggregateUpdateBody) }) } @@ -317,7 +317,7 @@ func HandleAddHostSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, AggregateAddHostBody) + fmt.Fprint(w, AggregateAddHostBody) }) } @@ -328,7 +328,7 @@ func HandleRemoveHostSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, AggregateRemoveHostBody) + fmt.Fprint(w, AggregateRemoveHostBody) }) } @@ -339,6 +339,6 @@ func HandleSetMetadataSuccessfully(t *testing.T) { 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/attachinterfaces/testing/fixtures_test.go b/openstack/compute/v2/attachinterfaces/testing/fixtures_test.go index 77bc48f55e..59a03d57d2 100644 --- a/openstack/compute/v2/attachinterfaces/testing/fixtures_test.go +++ b/openstack/compute/v2/attachinterfaces/testing/fixtures_test.go @@ -69,7 +69,7 @@ func HandleInterfaceListSuccessfully(t *testing.T) { 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", @@ -99,7 +99,7 @@ func HandleInterfaceGetSuccessfully(t *testing.T) { 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", @@ -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", diff --git a/openstack/compute/v2/availabilityzones/testing/fixtures_test.go b/openstack/compute/v2/availabilityzones/testing/fixtures_test.go index 059364bcc0..a36c64649c 100644 --- a/openstack/compute/v2/availabilityzones/testing/fixtures_test.go +++ b/openstack/compute/v2/availabilityzones/testing/fixtures_test.go @@ -180,7 +180,7 @@ func HandleGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -192,6 +192,6 @@ func HandleGetDetailSuccessfully(t *testing.T) { 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/extensions/testing/fixtures_test.go b/openstack/compute/v2/extensions/testing/fixtures_test.go index 6827dab20c..b1622af764 100644 --- a/openstack/compute/v2/extensions/testing/fixtures_test.go +++ b/openstack/compute/v2/extensions/testing/fixtures_test.go @@ -16,7 +16,7 @@ func HandleListExtensionsSuccessfully(t *testing.T) { w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "extensions": [ { @@ -41,7 +41,7 @@ func HandleGetExtensionsSuccessfully(t *testing.T) { 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/testing/fixtures_test.go b/openstack/compute/v2/flavors/testing/fixtures_test.go index 94ac8c8a59..a92cdcd1d8 100644 --- a/openstack/compute/v2/flavors/testing/fixtures_test.go +++ b/openstack/compute/v2/flavors/testing/fixtures_test.go @@ -57,7 +57,7 @@ func HandleExtraSpecsListSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ExtraSpecsGetBody) + fmt.Fprint(w, ExtraSpecsGetBody) }) } @@ -69,7 +69,7 @@ func HandleExtraSpecGetSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetExtraSpecBody) + fmt.Fprint(w, GetExtraSpecBody) }) } @@ -87,7 +87,7 @@ func HandleExtraSpecsCreateSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ExtraSpecsGetBody) + fmt.Fprint(w, ExtraSpecsGetBody) }) } @@ -102,7 +102,7 @@ func HandleExtraSpecUpdateSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdatedExtraSpecBody) + fmt.Fprint(w, UpdatedExtraSpecBody) }) } diff --git a/openstack/compute/v2/flavors/testing/requests_test.go b/openstack/compute/v2/flavors/testing/requests_test.go index d22e3dfa9c..efd6e4bcee 100644 --- a/openstack/compute/v2/flavors/testing/requests_test.go +++ b/openstack/compute/v2/flavors/testing/requests_test.go @@ -76,7 +76,7 @@ func TestListFlavors(t *testing.T) { } `, th.Server.URL) case "2": - fmt.Fprintf(w, `{ "flavors": [] }`) + fmt.Fprint(w, `{ "flavors": [] }`) default: t.Fatalf("Unexpected marker: [%s]", marker) } @@ -121,7 +121,7 @@ func TestGetFlavor(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "flavor": { "id": "1", @@ -170,7 +170,7 @@ func TestCreateFlavor(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "flavor": { "id": "1", @@ -225,7 +225,7 @@ func TestUpdateFlavor(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "flavor": { "id": "1", @@ -288,7 +288,7 @@ func TestFlavorAccessesList(t *testing.T) { 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, ` { "flavor_access": [ { @@ -336,7 +336,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": [ { @@ -385,7 +385,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": [] } diff --git a/openstack/compute/v2/hypervisors/results.go b/openstack/compute/v2/hypervisors/results.go index 2e8d244763..ee6046c6c0 100644 --- a/openstack/compute/v2/hypervisors/results.go +++ b/openstack/compute/v2/hypervisors/results.go @@ -158,27 +158,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 + } } } @@ -193,22 +197,28 @@ func (r *Hypervisor) UnmarshalJSON(b []byte) error { return fmt.Errorf("Hypervisor version 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("Free disk GB 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("Local GB has unexpected type: %T", t) + } } // OpenStack Compute service returns ID in string representation since diff --git a/openstack/compute/v2/hypervisors/testing/fixtures_test.go b/openstack/compute/v2/hypervisors/testing/fixtures_test.go index 1942c842e0..fa0b93a3f8 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" ) @@ -346,6 +346,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 = ` @@ -492,6 +522,7 @@ var ( } HypervisorEmptyCPUInfo = hypervisors.Hypervisor{ + CPUInfo: hypervisors.CPUInfo{}, CurrentWorkload: 0, Status: "enabled", State: "up", @@ -517,6 +548,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, @@ -542,74 +600,84 @@ 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) + th.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) + th.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) + th.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) + fmt.Fprint(w, HypervisorListBody) }) } 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{ + th.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) { - 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) + th.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, HypervisorGetBody) }) } 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) + th.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, HypervisorGetEmptyCPUInfoBody) + }) +} + +func HandleHypervisorAfterV287ResponseSuccessfully(t *testing.T) { + th.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) + th.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..0303f7f1d0 100644 --- a/openstack/compute/v2/hypervisors/testing/requests_test.go +++ b/openstack/compute/v2/hypervisors/testing/requests_test.go @@ -6,13 +6,13 @@ 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() + th.SetupHTTP() + defer th.TeardownHTTP() HandleHypervisorListPre253Successfully(t) pages := 0 @@ -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,21 +42,21 @@ func TestListHypervisorsPre253(t *testing.T) { } func TestListAllHypervisorsPre253(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + th.SetupHTTP() + defer th.TeardownHTTP() HandleHypervisorListPre253Successfully(t) allPages, err := hypervisors.List(client.ServiceClient(), hypervisors.ListOpts{}).AllPages(context.TODO()) - testhelper.AssertNoErr(t, err) + 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() + th.SetupHTTP() + defer th.TeardownHTTP() HandleHypervisorListSuccessfully(t) pages := 0 @@ -72,13 +72,13 @@ func TestListHypervisors(t *testing.T) { if len(actual) != 2 { t.Fatalf("Expected 2 hypervisors, got %d", len(actual)) } - testhelper.CheckDeepEquals(t, HypervisorFake, actual[0]) - testhelper.CheckDeepEquals(t, HypervisorFake, actual[1]) + th.CheckDeepEquals(t, HypervisorFake, actual[0]) + th.CheckDeepEquals(t, HypervisorFake, actual[1]) return true, nil }) - testhelper.AssertNoErr(t, err) + th.AssertNoErr(t, err) if pages != 1 { t.Errorf("Expected 1 page, saw %d", pages) @@ -86,76 +86,88 @@ func TestListHypervisors(t *testing.T) { } func TestListAllHypervisors(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + th.SetupHTTP() + defer th.TeardownHTTP() HandleHypervisorListSuccessfully(t) allPages, err := hypervisors.List(client.ServiceClient(), hypervisors.ListOpts{}).AllPages(context.TODO()) - testhelper.AssertNoErr(t, err) + 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() + th.SetupHTTP() + defer th.TeardownHTTP() HandleHypervisorListWithParametersSuccessfully(t) with_servers := true allPages, err := hypervisors.List(client.ServiceClient(), hypervisors.ListOpts{WithServers: &with_servers}).AllPages(context.TODO()) - testhelper.AssertNoErr(t, err) + 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() + th.SetupHTTP() + defer th.TeardownHTTP() HandleHypervisorsStatisticsSuccessfully(t) expected := HypervisorsStatisticsExpected actual, err := hypervisors.GetStatistics(context.TODO(), client.ServiceClient()).Extract() - testhelper.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, &expected, actual) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &expected, actual) } func TestGetHypervisor(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + th.SetupHTTP() + defer th.TeardownHTTP() HandleHypervisorGetSuccessfully(t) expected := HypervisorFake actual, err := hypervisors.Get(context.TODO(), client.ServiceClient(), expected.ID).Extract() - testhelper.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, &expected, actual) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &expected, actual) } func TestGetHypervisorEmptyCPUInfo(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + th.SetupHTTP() + defer th.TeardownHTTP() HandleHypervisorGetEmptyCPUInfoSuccessfully(t) expected := HypervisorEmptyCPUInfo actual, err := hypervisors.Get(context.TODO(), client.ServiceClient(), expected.ID).Extract() - testhelper.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, &expected, actual) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &expected, actual) +} + +func TestGetHypervisorAfterV287Response(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleHypervisorAfterV287ResponseSuccessfully(t) + + expected := HypervisorAfterV287Response + + actual, err := hypervisors.Get(context.TODO(), client.ServiceClient(), expected.ID).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &expected, actual) } func TestHypervisorsUptime(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + th.SetupHTTP() + defer th.TeardownHTTP() HandleHypervisorUptimeSuccessfully(t) expected := HypervisorUptimeExpected actual, err := hypervisors.GetUptime(context.TODO(), client.ServiceClient(), HypervisorFake.ID).Extract() - testhelper.AssertNoErr(t, err) - testhelper.CheckDeepEquals(t, &expected, actual) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, &expected, actual) } diff --git a/openstack/compute/v2/instanceactions/testing/fixtures_test.go b/openstack/compute/v2/instanceactions/testing/fixtures_test.go index 3b24801c38..cbbf6def28 100644 --- a/openstack/compute/v2/instanceactions/testing/fixtures_test.go +++ b/openstack/compute/v2/instanceactions/testing/fixtures_test.go @@ -40,7 +40,7 @@ func HandleInstanceActionListSuccessfully(t *testing.T) { 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", @@ -100,7 +100,7 @@ func HandleInstanceActionGetSuccessfully(t *testing.T) { 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/keypairs/testing/fixtures_test.go b/openstack/compute/v2/keypairs/testing/fixtures_test.go index 41532c03cc..92460e9259 100644 --- a/openstack/compute/v2/keypairs/testing/fixtures_test.go +++ b/openstack/compute/v2/keypairs/testing/fixtures_test.go @@ -157,7 +157,7 @@ func HandleListSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } @@ -169,11 +169,11 @@ func HandleGetSuccessfully(t *testing.T) { 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) } }) @@ -188,7 +188,7 @@ func HandleCreateSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, `{ "keypair": { "name": "createdkey" } }`) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, CreateOutput) + fmt.Fprint(w, CreateOutput) }) } @@ -201,7 +201,7 @@ func HandleCreateSuccessfullyOtherUser(t *testing.T) { 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) }) } @@ -221,7 +221,7 @@ func HandleImportSuccessfully(t *testing.T) { `) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ImportOutput) + fmt.Fprint(w, ImportOutput) }) } diff --git a/openstack/compute/v2/limits/testing/fixtures_test.go b/openstack/compute/v2/limits/testing/fixtures_test.go index 3751c2d8b6..ee04c72442 100644 --- a/openstack/compute/v2/limits/testing/fixtures_test.go +++ b/openstack/compute/v2/limits/testing/fixtures_test.go @@ -75,6 +75,6 @@ func HandleGetSuccessfully(t *testing.T) { 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/quotasets/testing/fixtures_test.go b/openstack/compute/v2/quotasets/testing/fixtures_test.go index d1c714714b..5d0ef24931 100644 --- a/openstack/compute/v2/quotasets/testing/fixtures_test.go +++ b/openstack/compute/v2/quotasets/testing/fixtures_test.go @@ -167,7 +167,7 @@ func HandleGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -177,7 +177,7 @@ func HandleGetDetailSuccessfully(t *testing.T) { 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) }) } @@ -188,7 +188,7 @@ func HandlePutSuccessfully(t *testing.T) { 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) }) } @@ -199,7 +199,7 @@ func HandlePartialPutSuccessfully(t *testing.T) { 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) }) } diff --git a/openstack/compute/v2/remoteconsoles/testing/requests_test.go b/openstack/compute/v2/remoteconsoles/testing/requests_test.go index e96b897d0f..3a49e4d46f 100644 --- a/openstack/compute/v2/remoteconsoles/testing/requests_test.go +++ b/openstack/compute/v2/remoteconsoles/testing/requests_test.go @@ -25,7 +25,7 @@ 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{ diff --git a/openstack/compute/v2/secgroups/testing/fixtures_test.go b/openstack/compute/v2/secgroups/testing/fixtures_test.go index 0b35cc71d8..df3614b66b 100644 --- a/openstack/compute/v2/secgroups/testing/fixtures_test.go +++ b/openstack/compute/v2/secgroups/testing/fixtures_test.go @@ -33,7 +33,7 @@ func mockListGroupsResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, listGroupsJSON) + fmt.Fprint(w, listGroupsJSON) }) } @@ -46,7 +46,7 @@ func mockListGroupsByServerResponse(t *testing.T, serverID string) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, listGroupsJSON) + fmt.Fprint(w, listGroupsJSON) }) } @@ -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", @@ -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", @@ -122,7 +122,7 @@ func mockGetGroupsResponse(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": "default", @@ -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, @@ -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, diff --git a/openstack/compute/v2/servergroups/testing/fixtures_test.go b/openstack/compute/v2/servergroups/testing/fixtures_test.go index de36c3ff89..2ad519023a 100644 --- a/openstack/compute/v2/servergroups/testing/fixtures_test.go +++ b/openstack/compute/v2/servergroups/testing/fixtures_test.go @@ -148,7 +148,7 @@ func HandleListSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } @@ -160,7 +160,7 @@ func HandleGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -172,7 +172,7 @@ func HandleGetMicroversionSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetOutputMicroversion) + fmt.Fprint(w, GetOutputMicroversion) }) } @@ -194,7 +194,7 @@ func HandleCreateSuccessfully(t *testing.T) { `) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, CreateOutput) + fmt.Fprint(w, CreateOutput) }) } @@ -220,7 +220,7 @@ func HandleCreateMicroversionSuccessfully(t *testing.T) { `) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, CreateOutputMicroversion) + fmt.Fprint(w, CreateOutputMicroversion) }) } diff --git a/openstack/compute/v2/servers/requests.go b/openstack/compute/v2/servers/requests.go index dd3b132d1d..44e8cccaeb 100644 --- a/openstack/compute/v2/servers/requests.go +++ b/openstack/compute/v2/servers/requests.go @@ -508,6 +508,9 @@ type CreateOpts struct { // DiskConfig [optional] controls how the created server's disk is partitioned. DiskConfig DiskConfig `json:"OS-DCF:diskConfig,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 diff --git a/openstack/compute/v2/servers/results.go b/openstack/compute/v2/servers/results.go index dc51564176..385001c8dd 100644 --- a/openstack/compute/v2/servers/results.go +++ b/openstack/compute/v2/servers/results.go @@ -279,6 +279,10 @@ 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"` } type AttachedVolume struct { diff --git a/openstack/compute/v2/servers/testing/fixtures_test.go b/openstack/compute/v2/servers/testing/fixtures_test.go index 2ce6282b02..a9c3980d88 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" @@ -741,7 +744,7 @@ 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) }) } @@ -761,7 +764,7 @@ 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) { @@ -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,7 +807,7 @@ func HandleServerCreationSuccessfully(t *testing.T, response string) { } `) case "2": - fmt.Fprintf(w, `{ "images": [] }`) + fmt.Fprint(w, `{ "images": [] }`) default: t.Fatalf("Unexpected marker: [%s]", marker) } @@ -850,7 +853,7 @@ func HandleServerCreationSuccessfully(t *testing.T, response string) { } `, th.Server.URL) case "2": - fmt.Fprintf(w, `{ "flavors": [] }`) + fmt.Fprint(w, `{ "flavors": [] }`) default: t.Fatalf("Unexpected marker: [%s]", marker) } @@ -875,7 +878,7 @@ 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) }) } @@ -896,7 +899,7 @@ 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) }) } @@ -915,7 +918,7 @@ 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) }) } @@ -936,7 +939,7 @@ 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) }) } @@ -959,7 +962,7 @@ 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) }) } @@ -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) } @@ -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) } @@ -1036,7 +1039,7 @@ func HandleServerGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleServerBody) + fmt.Fprint(w, SingleServerBody) }) } @@ -1048,7 +1051,7 @@ func HandleServerGetFaultSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, FaultyServerBody) + fmt.Fprint(w, FaultyServerBody) }) } @@ -1061,7 +1064,7 @@ func HandleServerUpdateSuccessfully(t *testing.T) { th.TestHeader(t, r, "Content-Type", "application/json") th.TestJSONRequest(t, r, `{ "server": { "name": "new-name" } }`) - fmt.Fprintf(w, SingleServerBody) + fmt.Fprint(w, SingleServerBody) }) } @@ -1097,7 +1100,7 @@ func HandleShowConsoleOutputSuccessfully(t *testing.T, response string) { w.WriteHeader(http.StatusOK) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, response) + fmt.Fprint(w, response) }) } @@ -1119,7 +1122,7 @@ 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) }) } @@ -1243,7 +1246,7 @@ func HandleAddressListSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "addresses": { "public": [ { @@ -1285,7 +1288,7 @@ func HandleNetworkAddressListSuccessfully(t *testing.T) { 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, @@ -1317,7 +1320,7 @@ func HandlePasswordGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, ServerPasswordBody) + fmt.Fprint(w, ServerPasswordBody) }) } @@ -1332,6 +1335,6 @@ func HandleServerWithTagsCreationSuccessfully(t *testing.T) { w.WriteHeader(http.StatusAccepted) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, SingleServerWithTagsBody) + fmt.Fprint(w, SingleServerWithTagsBody) }) } diff --git a/openstack/compute/v2/servers/testing/requests_test.go b/openstack/compute/v2/servers/testing/requests_test.go index b6ebefce5e..66a33dd688 100644 --- a/openstack/compute/v2/servers/testing/requests_test.go +++ b/openstack/compute/v2/servers/testing/requests_test.go @@ -774,6 +774,7 @@ func TestGetFaultyServer(t *testing.T) { FaultyServer := ServerDerp FaultyServer.Fault = DerpFault + FaultyServer.Locked = nil th.CheckDeepEquals(t, FaultyServer, *actual) } @@ -1145,6 +1146,7 @@ func TestCreateServerWithTags(t *testing.T) { tags := []string{"foo", "bar"} ServerDerpTags := ServerDerp ServerDerpTags.Tags = &tags + ServerDerpTags.Locked = nil createOpts := servers.CreateOpts{ Name: "derp", @@ -1158,3 +1160,25 @@ 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) +} diff --git a/openstack/compute/v2/services/requests.go b/openstack/compute/v2/services/requests.go index 7f6ac1d6d8..32be17cb70 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. @@ -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..93b124df90 100644 --- a/openstack/compute/v2/services/testing/fixtures_test.go +++ b/openstack/compute/v2/services/testing/fixtures_test.go @@ -259,7 +259,7 @@ func HandleListPre253Successfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ServiceListBodyPre253) + fmt.Fprint(w, ServiceListBodyPre253) }) } @@ -271,7 +271,7 @@ func HandleListSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ServiceListBody) + fmt.Fprint(w, ServiceListBody) }) } @@ -285,7 +285,7 @@ func HandleUpdateSuccessfully(t *testing.T) { th.TestHeader(t, r, "Content-Type", "application/json") th.TestJSONRequest(t, r, `{"status": "disabled"}`) - fmt.Fprintf(w, ServiceUpdate) + fmt.Fprint(w, ServiceUpdate) }) } diff --git a/openstack/compute/v2/services/testing/requests_test.go b/openstack/compute/v2/services/testing/requests_test.go index c201c9f4d6..df7a9e9a17 100644 --- a/openstack/compute/v2/services/testing/requests_test.go +++ b/openstack/compute/v2/services/testing/requests_test.go @@ -6,13 +6,13 @@ 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() + th.SetupHTTP() + defer th.TeardownHTTP() HandleListPre253Successfully(t) pages := 0 @@ -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,8 +43,8 @@ func TestListServicesPre253(t *testing.T) { } func TestListServices(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + th.SetupHTTP() + defer th.TeardownHTTP() HandleListSuccessfully(t) pages := 0 @@ -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,8 +79,8 @@ func TestListServices(t *testing.T) { } func TestUpdateService(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + th.SetupHTTP() + defer th.TeardownHTTP() HandleUpdateSuccessfully(t) client := client.ServiceClient() @@ -89,16 +89,16 @@ func TestUpdateService(t *testing.T) { t.Fatalf("Unexpected Update error: %v", err) } - testhelper.CheckDeepEquals(t, FakeServiceUpdateBody, *actual) + th.CheckDeepEquals(t, FakeServiceUpdateBody, *actual) } func TestDeleteService(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + th.SetupHTTP() + defer th.TeardownHTTP() HandleDeleteSuccessfully(t) client := client.ServiceClient() 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..dc524a2306 100644 --- a/openstack/compute/v2/tags/testing/requests_test.go +++ b/openstack/compute/v2/tags/testing/requests_test.go @@ -22,7 +22,7 @@ func TestList(t *testing.T) { 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) }) @@ -78,7 +78,7 @@ func TestReplaceAll(t *testing.T) { 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) }) diff --git a/openstack/compute/v2/volumeattach/testing/fixtures_test.go b/openstack/compute/v2/volumeattach/testing/fixtures_test.go index c5f0435a96..d9ada1e8c1 100644 --- a/openstack/compute/v2/volumeattach/testing/fixtures_test.go +++ b/openstack/compute/v2/volumeattach/testing/fixtures_test.go @@ -62,7 +62,7 @@ func HandleListSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } @@ -74,7 +74,7 @@ func HandleGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -96,7 +96,7 @@ func HandleCreateSuccessfully(t *testing.T) { `) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, CreateOutput) + fmt.Fprint(w, CreateOutput) }) } diff --git a/openstack/config/clouds/clouds.go b/openstack/config/clouds/clouds.go index e890b5b5ce..1466058097 100644 --- a/openstack/config/clouds/clouds.go +++ b/openstack/config/clouds/clouds.go @@ -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/` // @@ -148,6 +148,13 @@ func Parse(opts ...ParseOption) (gophercloud.AuthOptions, gophercloud.EndpointOp endpointType := coalesce(options.endpointType, cloud.EndpointType, cloud.Interface) + var scope *gophercloud.AuthScope + if trustID := cloud.AuthInfo.TrustID; trustID != "" { + scope = &gophercloud.AuthScope{ + TrustID: trustID, + } + } + return gophercloud.AuthOptions{ IdentityEndpoint: coalesce(options.authURL, cloud.AuthInfo.AuthURL), Username: coalesce(options.username, cloud.AuthInfo.Username), @@ -158,7 +165,7 @@ func Parse(opts ...ParseOption) (gophercloud.AuthOptions, gophercloud.EndpointOp TenantID: coalesce(options.projectID, cloud.AuthInfo.ProjectID), TenantName: coalesce(options.projectName, cloud.AuthInfo.ProjectName), TokenID: coalesce(options.token, cloud.AuthInfo.Token), - Scope: options.scope, + Scope: coalesce(options.scope, scope), ApplicationCredentialID: coalesce(options.applicationCredentialID, cloud.AuthInfo.ApplicationCredentialID), ApplicationCredentialName: coalesce(options.applicationCredentialName, cloud.AuthInfo.ApplicationCredentialName), ApplicationCredentialSecret: coalesce(options.applicationCredentialSecret, cloud.AuthInfo.ApplicationCredentialSecret), @@ -182,15 +189,16 @@ func computeAvailability(endpointType string) gophercloud.Availability { return gophercloud.AvailabilityPublic } -// coalesce returns the first argument that is not the empty string, or the -// empty string. -func coalesce(items ...string) string { +// coalesce returns the first argument that is not the zero value for its type, +// or the zero value for its type. +func coalesce[T comparable](items ...T) T { + var t T for _, item := range items { - if item != "" { + if item != t { return item } } - return "" + return t } // mergeClouds merges two Clouds recursively (the AuthInfo also gets merged). diff --git a/openstack/config/clouds/types.go b/openstack/config/clouds/types.go index fc2da75925..93fc093268 100644 --- a/openstack/config/clouds/types.go +++ b/openstack/config/clouds/types.go @@ -123,6 +123,9 @@ type AuthInfo struct { // been specified and a domain is required for scope. DefaultDomain string `yaml:"default_domain,omitempty" json:"default_domain,omitempty"` + // TrustID is the ID of the trust to use as a trustee. + TrustID string `yaml:"trust_id,omitempty" json:"trust_id,omitempty"` + // AllowReauth should be set to true if you grant permission for Gophercloud to // cache your credentials in memory, and to allow Gophercloud to attempt to // re-authenticate automatically if/when your token expires. If you set it to diff --git a/openstack/container/v1/capsules/testing/fixtures_test.go b/openstack/container/v1/capsules/testing/fixtures_test.go index fe7b008013..779a41a0df 100644 --- a/openstack/container/v1/capsules/testing/fixtures_test.go +++ b/openstack/container/v1/capsules/testing/fixtures_test.go @@ -609,7 +609,7 @@ func HandleCapsuleGetOldTimeSuccessfully(t *testing.T) { w.WriteHeader(http.StatusOK) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, CapsuleGetBody_OldTime) + fmt.Fprint(w, CapsuleGetBody_OldTime) }) } @@ -621,7 +621,7 @@ func HandleCapsuleGetNewTimeSuccessfully(t *testing.T) { w.WriteHeader(http.StatusOK) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, CapsuleGetBody_NewTime) + fmt.Fprint(w, CapsuleGetBody_NewTime) }) } @@ -633,7 +633,7 @@ func HandleCapsuleCreateSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, CapsuleGetBody_NewTime) + fmt.Fprint(w, CapsuleGetBody_NewTime) }) } @@ -645,7 +645,7 @@ func HandleCapsuleListSuccessfully(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, CapsuleListBody) + fmt.Fprint(w, CapsuleListBody) }) } @@ -657,7 +657,7 @@ func HandleCapsuleV132ListSuccessfully(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, CapsuleV132ListBody) + fmt.Fprint(w, CapsuleV132ListBody) }) } diff --git a/openstack/containerinfra/apiversions/testing/fixtures_test.go b/openstack/containerinfra/apiversions/testing/fixtures_test.go index e95058151d..f42844214c 100644 --- a/openstack/containerinfra/apiversions/testing/fixtures_test.go +++ b/openstack/containerinfra/apiversions/testing/fixtures_test.go @@ -71,7 +71,7 @@ func MockListResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, MagnumAllAPIVersionsResponse) + fmt.Fprint(w, MagnumAllAPIVersionsResponse) }) } @@ -83,6 +83,6 @@ func MockGetResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, MagnumAPIVersionResponse) + fmt.Fprint(w, MagnumAPIVersionResponse) }) } diff --git a/openstack/containerinfra/v1/certificates/testing/fixtures_test.go b/openstack/containerinfra/v1/certificates/testing/fixtures_test.go index 3f88c34242..b51d77cbd4 100644 --- a/openstack/containerinfra/v1/certificates/testing/fixtures_test.go +++ b/openstack/containerinfra/v1/certificates/testing/fixtures_test.go @@ -106,6 +106,6 @@ func HandleUpdateCertificateSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) } diff --git a/openstack/containerinfra/v1/clusters/results.go b/openstack/containerinfra/v1/clusters/results.go index 6c235dcd23..04adc8ea48 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"` diff --git a/openstack/containerinfra/v1/clusters/testing/fixtures_test.go b/openstack/containerinfra/v1/clusters/testing/fixtures_test.go index 640533e2c8..ccbfddfc6d 100644 --- a/openstack/containerinfra/v1/clusters/testing/fixtures_test.go +++ b/openstack/containerinfra/v1/clusters/testing/fixtures_test.go @@ -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", @@ -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", diff --git a/openstack/containerinfra/v1/clustertemplates/testing/fixtures_test.go b/openstack/containerinfra/v1/clustertemplates/testing/fixtures_test.go index f269d99cb3..8028ff836f 100644 --- a/openstack/containerinfra/v1/clustertemplates/testing/fixtures_test.go +++ b/openstack/containerinfra/v1/clustertemplates/testing/fixtures_test.go @@ -278,6 +278,28 @@ func HandleCreateClusterTemplateSuccessfully(t *testing.T) { th.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.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") diff --git a/openstack/containerinfra/v1/nodegroups/testing/fixtures_test.go b/openstack/containerinfra/v1/nodegroups/testing/fixtures_test.go index 51e63cc318..90de8b6fe6 100644 --- a/openstack/containerinfra/v1/nodegroups/testing/fixtures_test.go +++ b/openstack/containerinfra/v1/nodegroups/testing/fixtures_test.go @@ -221,7 +221,7 @@ func handleCreateNodeGroupSuccess(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, nodeGroupCreateResponse) + fmt.Fprint(w, nodeGroupCreateResponse) }) } @@ -233,7 +233,7 @@ func handleCreateNodeGroupDuplicate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusConflict) - fmt.Fprintf(w, nodeGroupCreateDuplicateResponse) + fmt.Fprint(w, nodeGroupCreateDuplicateResponse) }) } @@ -245,7 +245,7 @@ func handleCreateNodeGroupMaster(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(w, nodeGroupCreateMasterResponse) + fmt.Fprint(w, nodeGroupCreateMasterResponse) }) } @@ -257,7 +257,7 @@ func handleCreateNodeGroupBadSizes(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusConflict) - fmt.Fprintf(w, nodeGroupCreateBadSizesResponse) + fmt.Fprint(w, nodeGroupCreateBadSizesResponse) }) } @@ -269,7 +269,7 @@ func handleUpdateNodeGroupSuccess(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, nodeGroupUpdateResponse) + fmt.Fprint(w, nodeGroupUpdateResponse) }) } @@ -281,7 +281,7 @@ func handleUpdateNodeGroupInternal(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(w, nodeGroupUpdateInternalResponse) + fmt.Fprint(w, nodeGroupUpdateInternalResponse) }) } @@ -293,7 +293,7 @@ func handleUpdateNodeGroupBadField(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(w, nodeGroupUpdateBadFieldResponse) + fmt.Fprint(w, nodeGroupUpdateBadFieldResponse) }) } @@ -305,7 +305,7 @@ func handleUpdateNodeGroupBadMin(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusConflict) - fmt.Fprintf(w, nodeGroupUpdateBadMinResponse) + fmt.Fprint(w, nodeGroupUpdateBadMinResponse) }) } @@ -326,7 +326,7 @@ func handleDeleteNodeGroupNotFound(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusNotFound) - fmt.Fprintf(w, nodeGroupDeleteNotFoundResponse) + fmt.Fprint(w, nodeGroupDeleteNotFoundResponse) }) } @@ -338,7 +338,7 @@ func handleDeleteNodeGroupClusterNotFound(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusNotFound) - fmt.Fprintf(w, nodeGroupDeleteClusterNotFoundResponse) + fmt.Fprint(w, nodeGroupDeleteClusterNotFoundResponse) }) } @@ -350,7 +350,7 @@ func handleDeleteNodeGroupDefault(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(w, nodeGroupDeleteDefaultResponse) + fmt.Fprint(w, nodeGroupDeleteDefaultResponse) }) } diff --git a/openstack/dns/v2/recordsets/testing/fixtures_test.go b/openstack/dns/v2/recordsets/testing/fixtures_test.go index 9d2289f100..71d1d0898e 100644 --- a/openstack/dns/v2/recordsets/testing/fixtures_test.go +++ b/openstack/dns/v2/recordsets/testing/fixtures_test.go @@ -212,9 +212,9 @@ func HandleListByZoneSuccessfully(t *testing.T) { marker := r.Form.Get("marker") switch marker { case "f7b10e9b-0cae-4a91-b162-562bc6096648": - fmt.Fprintf(w, ListByZoneOutputLimited) + fmt.Fprint(w, ListByZoneOutputLimited) case "": - fmt.Fprintf(w, ListByZoneOutput) + fmt.Fprint(w, ListByZoneOutput) } }) } @@ -227,7 +227,7 @@ func HandleGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -282,7 +282,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) }) } @@ -334,7 +334,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) }) } @@ -373,6 +373,6 @@ func HandleDeleteSuccessfully(t *testing.T) { 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/transfer/accept/testing/fixtures_test.go b/openstack/dns/v2/transfer/accept/testing/fixtures_test.go index b8c3fe5888..3dae54e6aa 100644 --- a/openstack/dns/v2/transfer/accept/testing/fixtures_test.go +++ b/openstack/dns/v2/transfer/accept/testing/fixtures_test.go @@ -133,7 +133,7 @@ func HandleListSuccessfully(t *testing.T) { 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) }) } @@ -145,7 +145,7 @@ func HandleFilteredListSuccessfully(t *testing.T) { 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) }) } @@ -157,7 +157,7 @@ func HandleGetSuccessfully(t *testing.T) { 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) }) } @@ -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..3de1f2aca3 100644 --- a/openstack/dns/v2/transfer/request/testing/fixtures_test.go +++ b/openstack/dns/v2/transfer/request/testing/fixtures_test.go @@ -119,7 +119,7 @@ func HandleListSuccessfully(t *testing.T) { 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) }) } @@ -131,7 +131,7 @@ func HandleGetSuccessfully(t *testing.T) { 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) }) } @@ -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) }) } @@ -215,7 +215,7 @@ func HandleUpdateSuccessfully(t *testing.T) { w.WriteHeader(http.StatusOK) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, UpdatedTransferRequestResponse) + fmt.Fprint(w, UpdatedTransferRequestResponse) }) } diff --git a/openstack/dns/v2/zones/requests.go b/openstack/dns/v2/zones/requests.go index fba4371ced..b8ac4cf8a5 100644 --- a/openstack/dns/v2/zones/requests.go +++ b/openstack/dns/v2/zones/requests.go @@ -178,3 +178,45 @@ func Delete(ctx context.Context, client *gophercloud.ServiceClient, zoneID strin _, 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 gophercloud.ErrResult) { + body, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + r.Err = err + return + } + + resp, err := client.Post(ctx, zoneShareURL(client, zoneID), body, nil, &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, zoneUnshareURL(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..7ccf5ace9b 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 { diff --git a/openstack/dns/v2/zones/testing/fixtures_test.go b/openstack/dns/v2/zones/testing/fixtures_test.go index d7d9120643..6b0ee7ccf7 100644 --- a/openstack/dns/v2/zones/testing/fixtures_test.go +++ b/openstack/dns/v2/zones/testing/fixtures_test.go @@ -149,7 +149,7 @@ func HandleListSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } @@ -160,7 +160,7 @@ func HandleGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -212,7 +212,7 @@ func HandleCreateSuccessfully(t *testing.T) { w.WriteHeader(http.StatusCreated) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, CreateZoneResponse) + fmt.Fprint(w, CreateZoneResponse) }) } @@ -259,7 +259,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) }) } @@ -297,6 +297,6 @@ func HandleDeleteSuccessfully(t *testing.T) { 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/zones/testing/requests_test.go b/openstack/dns/v2/zones/testing/requests_test.go index 0b64e81ecf..b75b99e967 100644 --- a/openstack/dns/v2/zones/testing/requests_test.go +++ b/openstack/dns/v2/zones/testing/requests_test.go @@ -2,6 +2,9 @@ package testing import ( "context" + "encoding/json" + "io" + "net/http" "testing" "github.com/gophercloud/gophercloud/v2/openstack/dns/v2/zones" @@ -105,3 +108,41 @@ func TestDelete(t *testing.T) { th.AssertNoErr(t, err) th.CheckDeepEquals(t, &DeletedZone, actual) } + +func TestShare(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/zones/zone-id/shares", func(w http.ResponseWriter, r *http.Request) { + th.AssertEquals(t, r.Method, "POST") + + 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) + }) + + opts := zones.ShareZoneOpts{TargetProjectID: "project-id"} + err := zones.Share(context.TODO(), client.ServiceClient(), "zone-id", opts).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestUnshare(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/zones/zone-id/shares/share-id", func(w http.ResponseWriter, r *http.Request) { + th.AssertEquals(t, r.Method, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + err := zones.Unshare(context.TODO(), client.ServiceClient(), "zone-id", "share-id").ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/openstack/dns/v2/zones/urls.go b/openstack/dns/v2/zones/urls.go index d157b30ef3..ba156882f8 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) } + +// zoneShareURL returns the URL for sharing a zone. +func zoneShareURL(c *gophercloud.ServiceClient, zoneID string) string { + return c.ServiceURL("zones", zoneID, "shares") +} + +// zoneUnshareURL returns the URL for unsharing a zone. +func zoneUnshareURL(c *gophercloud.ServiceClient, zoneID, shareID string) string { + return c.ServiceURL("zones", zoneID, "shares", shareID) +} diff --git a/openstack/endpoint_location.go b/openstack/endpoint_location.go index 2cdbd3e7f7..14cff0d755 100644 --- a/openstack/endpoint_location.go +++ b/openstack/endpoint_location.go @@ -1,6 +1,8 @@ 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" @@ -20,7 +22,7 @@ func V2EndpointURL(catalog *tokens2.ServiceCatalog, opts gophercloud.EndpointOpt // 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) 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) @@ -74,7 +76,7 @@ func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpt // Name if provided, and Region if provided. var endpoints = make([]tokens3.Endpoint, 0, 1) 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 && diff --git a/openstack/identity/v2/extensions/testing/fixtures_test.go b/openstack/identity/v2/extensions/testing/fixtures_test.go index 3733eba312..8e9bfa14da 100644 --- a/openstack/identity/v2/extensions/testing/fixtures_test.go +++ b/openstack/identity/v2/extensions/testing/fixtures_test.go @@ -37,7 +37,7 @@ func HandleListExtensionsSuccessfully(t *testing.T) { w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "extensions": { "values": [ diff --git a/openstack/identity/v2/roles/testing/fixtures_test.go b/openstack/identity/v2/roles/testing/fixtures_test.go index 0b5af6e541..170e80099c 100644 --- a/openstack/identity/v2/roles/testing/fixtures_test.go +++ b/openstack/identity/v2/roles/testing/fixtures_test.go @@ -17,7 +17,7 @@ func MockListRoleResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "roles": [ { diff --git a/openstack/identity/v2/tenants/testing/fixtures_test.go b/openstack/identity/v2/tenants/testing/fixtures_test.go index 7c50152faa..e7de74ccd5 100644 --- a/openstack/identity/v2/tenants/testing/fixtures_test.go +++ b/openstack/identity/v2/tenants/testing/fixtures_test.go @@ -59,7 +59,7 @@ func HandleListTenantsSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } @@ -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", @@ -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", @@ -141,7 +141,7 @@ func mockGetTenantResponse(t *testing.T) { 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/tokens/testing/fixtures_test.go b/openstack/identity/v2/tokens/testing/fixtures_test.go index 8eb7d6f7ea..1ee6d32e75 100644 --- a/openstack/identity/v2/tokens/testing/fixtures_test.go +++ b/openstack/identity/v2/tokens/testing/fixtures_test.go @@ -152,7 +152,7 @@ func HandleTokenPost(t *testing.T, requestJSON string) { } w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, TokenCreationResponse) + fmt.Fprint(w, TokenCreationResponse) }) } @@ -165,7 +165,7 @@ func HandleTokenGet(t *testing.T, token string) { th.TestHeader(t, r, "X-Auth-Token", thclient.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, TokenGetResponse) + fmt.Fprint(w, TokenGetResponse) }) } diff --git a/openstack/identity/v2/users/testing/fixtures_test.go b/openstack/identity/v2/users/testing/fixtures_test.go index f38f05dba2..3e5285360b 100644 --- a/openstack/identity/v2/users/testing/fixtures_test.go +++ b/openstack/identity/v2/users/testing/fixtures_test.go @@ -17,7 +17,7 @@ func MockListUserResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "users":[ { @@ -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", @@ -83,7 +83,7 @@ func mockGetUserResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "user": { "name": "new_user", @@ -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", @@ -145,7 +145,7 @@ func mockListRolesResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "roles": [ { diff --git a/openstack/identity/v3/applicationcredentials/testing/fixtures_test.go b/openstack/identity/v3/applicationcredentials/testing/fixtures_test.go index fabb021a0c..ae06c1bc57 100644 --- a/openstack/identity/v3/applicationcredentials/testing/fixtures_test.go +++ b/openstack/identity/v3/applicationcredentials/testing/fixtures_test.go @@ -415,7 +415,7 @@ func HandleListApplicationCredentialsSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } @@ -429,7 +429,7 @@ func HandleGetApplicationCredentialSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -442,7 +442,7 @@ func HandleCreateApplicationCredentialSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) } @@ -455,7 +455,7 @@ func HandleCreateNoSecretApplicationCredentialSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, CreateNoSecretRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateNoSecretResponse) + fmt.Fprint(w, CreateNoSecretResponse) }) } @@ -466,7 +466,7 @@ func HandleCreateUnrestrictedApplicationCredentialSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, CreateUnrestrictedRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateUnrestrictedResponse) + fmt.Fprint(w, CreateUnrestrictedResponse) }) } @@ -491,7 +491,7 @@ func HandleListAccessRulesSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListAccessRulesOutput) + fmt.Fprint(w, ListAccessRulesOutput) }) } @@ -505,7 +505,7 @@ func HandleGetAccessRuleSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetAccessRuleOutput) + fmt.Fprint(w, GetAccessRuleOutput) }) } diff --git a/openstack/identity/v3/catalog/testing/fixtures_test.go b/openstack/identity/v3/catalog/testing/fixtures_test.go index 541fcac4af..ad9e9d9ce2 100644 --- a/openstack/identity/v3/catalog/testing/fixtures_test.go +++ b/openstack/identity/v3/catalog/testing/fixtures_test.go @@ -85,6 +85,6 @@ func HandleListCatalogSuccessfully(t *testing.T) { 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/testing/fixtures_test.go b/openstack/identity/v3/credentials/testing/fixtures_test.go index 8da4769ed6..e32d77d0db 100644 --- a/openstack/identity/v3/credentials/testing/fixtures_test.go +++ b/openstack/identity/v3/credentials/testing/fixtures_test.go @@ -161,7 +161,7 @@ func HandleListCredentialsSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } @@ -175,7 +175,7 @@ func HandleGetCredentialSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -188,7 +188,7 @@ func HandleCreateCredentialSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -212,6 +212,6 @@ func HandleUpdateCredentialSuccessfully(t *testing.T) { 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/fixtures_test.go b/openstack/identity/v3/domains/testing/fixtures_test.go index f5025a15c8..a35813a23c 100644 --- a/openstack/identity/v3/domains/testing/fixtures_test.go +++ b/openstack/identity/v3/domains/testing/fixtures_test.go @@ -189,7 +189,7 @@ func HandleListAvailableDomainsSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListAvailableOutput) + fmt.Fprint(w, ListAvailableOutput) }) } @@ -203,7 +203,7 @@ func HandleListDomainsSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } @@ -217,7 +217,7 @@ func HandleGetDomainSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -230,7 +230,7 @@ func HandleCreateDomainSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -254,6 +254,6 @@ func HandleUpdateDomainSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }) } 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/testing/fixtures_test.go b/openstack/identity/v3/ec2credentials/testing/fixtures_test.go index 738161477d..72a82e1ff4 100644 --- a/openstack/identity/v3/ec2credentials/testing/fixtures_test.go +++ b/openstack/identity/v3/ec2credentials/testing/fixtures_test.go @@ -119,7 +119,7 @@ func HandleListEC2CredentialsSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } @@ -133,7 +133,7 @@ func HandleGetEC2CredentialSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -146,7 +146,7 @@ func HandleCreateEC2CredentialSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) } diff --git a/openstack/identity/v3/ec2tokens/testing/requests_test.go b/openstack/identity/v3/ec2tokens/testing/requests_test.go index 188329daab..8fe0b3c3f8 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() + th.SetupHTTP() + defer th.TeardownHTTP() client := gophercloud.ServiceClient{ ProviderClient: &gophercloud.ProviderClient{}, - Endpoint: testhelper.Endpoint(), + Endpoint: th.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) + th.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..4386bb9bc9 100644 --- a/openstack/identity/v3/endpoints/requests.go +++ b/openstack/identity/v3/endpoints/requests.go @@ -90,6 +90,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) diff --git a/openstack/identity/v3/endpoints/results.go b/openstack/identity/v3/endpoints/results.go index 9efec30af2..19d279ebc6 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 { diff --git a/openstack/identity/v3/endpoints/testing/requests_test.go b/openstack/identity/v3/endpoints/testing/requests_test.go index 8ea857bad4..ad138acd49 100644 --- a/openstack/identity/v3/endpoints/testing/requests_test.go +++ b/openstack/identity/v3/endpoints/testing/requests_test.go @@ -20,35 +20,31 @@ func TestCreateSuccessful(t *testing.T) { th.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", + "name": "the-endiest-of-points", + "region": "underground", + "url": "https://1.2.3.4:9000/", + "service_id": "asdfasdfasdfasdf" + } + }`) 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": 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/" + } + }`) }) actual, err := endpoints.Create(context.TODO(), client.ServiceClient(), endpoints.CreateOpts{ @@ -82,40 +78,38 @@ func TestListEndpoints(t *testing.T) { 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/" + }, + { + "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 } - `) + }`) }) count := 0 @@ -154,6 +148,47 @@ func TestListEndpoints(t *testing.T) { th.AssertEquals(t, 1, count) } +func TestGetEndpoint(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.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/" + } + }`) + }) + + actual, err := endpoints.Get(context.TODO(), client.ServiceClient(), "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/", + } + th.AssertDeepEquals(t, expected, actual) +} + func TestUpdateEndpoint(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() @@ -161,17 +196,14 @@ func TestUpdateEndpoint(t *testing.T) { th.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", + th.TestJSONRequest(t, r, `{ + "endpoint": { + "name": "renamed", "region": "somewhere-else" - } - } - `) + } + }`) - fmt.Fprintf(w, ` - { + fmt.Fprint(w, `{ "endpoint": { "id": "12", "interface": "public", @@ -184,8 +216,7 @@ func TestUpdateEndpoint(t *testing.T) { "service_id": "asdfasdfasdfasdf", "url": "https://1.2.3.4:9000/" } - } - `) + }`) }) actual, err := endpoints.Update(context.TODO(), client.ServiceClient(), "12", endpoints.UpdateOpts{ diff --git a/openstack/identity/v3/federation/testing/fixtures_test.go b/openstack/identity/v3/federation/testing/fixtures_test.go index 225cc57997..229d2f489a 100644 --- a/openstack/identity/v3/federation/testing/fixtures_test.go +++ b/openstack/identity/v3/federation/testing/fixtures_test.go @@ -289,7 +289,7 @@ func HandleListMappingsSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } @@ -302,7 +302,7 @@ func HandleCreateMappingSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateOutput) + fmt.Fprint(w, CreateOutput) }) } @@ -316,7 +316,7 @@ func HandleGetMappingSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -329,7 +329,7 @@ func HandleUpdateMappingSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }) } diff --git a/openstack/identity/v3/groups/testing/fixtures_test.go b/openstack/identity/v3/groups/testing/fixtures_test.go index 0357a1fe06..415d81555f 100644 --- a/openstack/identity/v3/groups/testing/fixtures_test.go +++ b/openstack/identity/v3/groups/testing/fixtures_test.go @@ -160,7 +160,7 @@ func HandleListGroupsSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } @@ -174,7 +174,7 @@ func HandleGetGroupSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -187,7 +187,7 @@ func HandleCreateGroupSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -200,7 +200,7 @@ func HandleUpdateGroupSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }) } diff --git a/openstack/identity/v3/limits/testing/fixtures_test.go b/openstack/identity/v3/limits/testing/fixtures_test.go index 6275235b54..5f8cbff160 100644 --- a/openstack/identity/v3/limits/testing/fixtures_test.go +++ b/openstack/identity/v3/limits/testing/fixtures_test.go @@ -183,7 +183,7 @@ func HandleGetEnforcementModelSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetEnforcementModelOutput) + fmt.Fprint(w, GetEnforcementModelOutput) }) } @@ -197,7 +197,7 @@ func HandleListLimitsSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } @@ -210,7 +210,7 @@ func HandleCreateLimitSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateOutput) + fmt.Fprint(w, CreateOutput) }) } @@ -224,7 +224,7 @@ func HandleGetLimitSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -237,7 +237,7 @@ func HandleUpdateLimitSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }) } 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/testing/fixtures_test.go b/openstack/identity/v3/oauth1/testing/fixtures_test.go index 8a2bfe5cdb..b8d5dc3327 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" ) @@ -221,39 +221,39 @@ 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) + th.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) + th.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) + th.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) }) @@ -262,14 +262,14 @@ 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) + th.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) }) } @@ -299,14 +299,14 @@ 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) + th.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) }) } @@ -319,32 +319,32 @@ 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, "") + th.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) + th.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) }) } @@ -357,39 +357,39 @@ 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, "") + th.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) + th.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) + th.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) }) @@ -398,57 +398,57 @@ 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) + th.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) + th.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) + th.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"]}}}`) + th.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/policies/testing/fixtures_test.go b/openstack/identity/v3/policies/testing/fixtures_test.go index 2ebc6c6c41..d261ed865c 100644 --- a/openstack/identity/v3/policies/testing/fixtures_test.go +++ b/openstack/identity/v3/policies/testing/fixtures_test.go @@ -164,9 +164,9 @@ func HandleListPoliciesSuccessfully(t *testing.T) { 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) } @@ -182,7 +182,7 @@ func HandleCreatePolicySuccessfully(t *testing.T) { th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -197,7 +197,7 @@ func HandleGetPolicySuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }, ) } @@ -212,7 +212,7 @@ func HandleUpdatePolicySuccessfully(t *testing.T) { th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }, ) } diff --git a/openstack/identity/v3/projectendpoints/testing/requests_test.go b/openstack/identity/v3/projectendpoints/testing/requests_test.go index 9a9c17139f..2e8f6aad4c 100644 --- a/openstack/identity/v3/projectendpoints/testing/requests_test.go +++ b/openstack/identity/v3/projectendpoints/testing/requests_test.go @@ -37,7 +37,7 @@ func TestListEndpoints(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "endpoints": [ { 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..6fb8448a9f 100644 --- a/openstack/identity/v3/projects/requests.go +++ b/openstack/identity/v3/projects/requests.go @@ -276,8 +276,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/testing/fixtures_test.go b/openstack/identity/v3/projects/testing/fixtures_test.go index a54edfe21f..568305008a 100644 --- a/openstack/identity/v3/projects/testing/fixtures_test.go +++ b/openstack/identity/v3/projects/testing/fixtures_test.go @@ -285,7 +285,7 @@ func HandleListAvailableProjectsSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListAvailableOutput) + fmt.Fprint(w, ListAvailableOutput) }) } @@ -299,7 +299,7 @@ func HandleListProjectsSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } @@ -313,7 +313,7 @@ func HandleGetProjectSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -326,7 +326,7 @@ func HandleCreateProjectSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -350,7 +350,7 @@ func HandleUpdateProjectSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }) } @@ -360,7 +360,7 @@ func HandleListProjectTagsSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListTagsOutput) + fmt.Fprint(w, ListTagsOutput) }) } @@ -371,7 +371,7 @@ func HandleModifyProjectTagsSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, ModifyProjectTagsRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ModifyProjectTagsOutput) + fmt.Fprint(w, ModifyProjectTagsOutput) }) } func HandleDeleteProjectTagsSuccessfully(t *testing.T) { diff --git a/openstack/identity/v3/regions/testing/fixtures_test.go b/openstack/identity/v3/regions/testing/fixtures_test.go index a4c3a40c38..633ae86dcb 100644 --- a/openstack/identity/v3/regions/testing/fixtures_test.go +++ b/openstack/identity/v3/regions/testing/fixtures_test.go @@ -172,7 +172,7 @@ func HandleListRegionsSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } @@ -186,7 +186,7 @@ func HandleGetRegionSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -199,7 +199,7 @@ func HandleCreateRegionSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -212,7 +212,7 @@ func HandleUpdateRegionSuccessfully(t *testing.T) { 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/fixtures_test.go b/openstack/identity/v3/registeredlimits/testing/fixtures_test.go index 37e11ed6eb..1bcfc5e867 100644 --- a/openstack/identity/v3/registeredlimits/testing/fixtures_test.go +++ b/openstack/identity/v3/registeredlimits/testing/fixtures_test.go @@ -163,7 +163,7 @@ func HandleListRegisteredLimitsSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } @@ -177,7 +177,7 @@ func HandleGetRegisteredLimitSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -190,7 +190,7 @@ func HandleCreateRegisteredLimitSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateOutput) + fmt.Fprint(w, CreateOutput) }) } @@ -214,6 +214,6 @@ func HandleUpdateRegisteredLimitSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }) } diff --git a/openstack/identity/v3/roles/testing/fixtures_test.go b/openstack/identity/v3/roles/testing/fixtures_test.go index 755f14d4e8..32fe745a94 100644 --- a/openstack/identity/v3/roles/testing/fixtures_test.go +++ b/openstack/identity/v3/roles/testing/fixtures_test.go @@ -350,7 +350,7 @@ func HandleListRolesSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } @@ -364,7 +364,7 @@ func HandleGetRoleSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -377,7 +377,7 @@ func HandleCreateRoleSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -390,7 +390,7 @@ func HandleUpdateRoleSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }) } @@ -499,7 +499,7 @@ func HandleListRoleAssignmentsSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListAssignmentOutput) + fmt.Fprint(w, ListAssignmentOutput) }) } @@ -514,7 +514,7 @@ func HandleListRoleAssignmentsWithNamesSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListAssignmentWithNamesOutput) + fmt.Fprint(w, ListAssignmentWithNamesOutput) }) } @@ -529,7 +529,7 @@ func HandleListRoleAssignmentsWithSubtreeSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListAssignmentOutput) + fmt.Fprint(w, ListAssignmentOutput) }) } @@ -557,7 +557,7 @@ func HandleListAssignmentsOnResourceSuccessfully_ProjectsUsers(t *testing.T) { 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) @@ -571,7 +571,7 @@ func HandleListAssignmentsOnResourceSuccessfully_ProjectsGroups(t *testing.T) { 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) @@ -585,7 +585,7 @@ func HandleListAssignmentsOnResourceSuccessfully_DomainsUsers(t *testing.T) { 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) @@ -599,7 +599,7 @@ func HandleListAssignmentsOnResourceSuccessfully_DomainsGroups(t *testing.T) { 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) @@ -635,7 +635,7 @@ func HandleCreateRoleInferenceRule(t *testing.T) { 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) @@ -713,7 +713,7 @@ func HandleListRoleInferenceRules(t *testing.T) { 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) @@ -737,7 +737,7 @@ func HandleGetRoleInferenceRule(t *testing.T) { 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) diff --git a/openstack/identity/v3/services/testing/fixtures_test.go b/openstack/identity/v3/services/testing/fixtures_test.go index da713482f8..0a9274c500 100644 --- a/openstack/identity/v3/services/testing/fixtures_test.go +++ b/openstack/identity/v3/services/testing/fixtures_test.go @@ -164,7 +164,7 @@ func HandleListServicesSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } @@ -178,7 +178,7 @@ func HandleGetServiceSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -191,7 +191,7 @@ func HandleCreateServiceSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -204,6 +204,6 @@ func HandleUpdateServiceSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }) } diff --git a/openstack/identity/v3/tokens/testing/fixtures.go b/openstack/identity/v3/tokens/testing/fixtures.go index 0b4ccafa26..00797867c9 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,6 @@ 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 } diff --git a/openstack/identity/v3/tokens/testing/requests_test.go b/openstack/identity/v3/tokens/testing/requests_test.go index 9a9a59fc14..8bb00fbffa 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() + th.SetupHTTP() + defer th.TeardownHTTP() client := gophercloud.ServiceClient{ ProviderClient: &gophercloud.ProviderClient{}, - Endpoint: testhelper.Endpoint(), + Endpoint: th.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) + th.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() + th.SetupHTTP() + defer th.TeardownHTTP() client := gophercloud.ServiceClient{ ProviderClient: &gophercloud.ProviderClient{}, - Endpoint: testhelper.Endpoint(), + Endpoint: th.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() + th.SetupHTTP() + defer th.TeardownHTTP() 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) + th.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{ @@ -389,7 +389,7 @@ func TestCreateUserIDPasswordTrustID(t *testing.T) { 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 { @@ -405,7 +405,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 +513,19 @@ func TestCreatePasswordTOTPProjectNameAndDomainNameScope(t *testing.T) { } func TestCreateExtractsTokenFromResponse(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + th.SetupHTTP() + defer th.TeardownHTTP() client := gophercloud.ServiceClient{ ProviderClient: &gophercloud.ProviderClient{}, - Endpoint: testhelper.Endpoint(), + Endpoint: th.Endpoint(), } - testhelper.Mux.HandleFunc("/auth/tokens", func(w http.ResponseWriter, r *http.Request) { + th.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,25 +652,25 @@ func TestCreateFailureEmptyScope(t *testing.T) { */ func TestGetRequest(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + th.SetupHTTP() + defer th.TeardownHTTP() client := gophercloud.ServiceClient{ ProviderClient: &gophercloud.ProviderClient{ TokenID: "12345abcdef", }, - Endpoint: testhelper.Endpoint(), + Endpoint: th.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") + th.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" } } `) }) @@ -691,15 +691,15 @@ func prepareAuthTokenHandler(t *testing.T, expectedMethod string, status int) go ProviderClient: &gophercloud.ProviderClient{ TokenID: "12345abcdef", }, - Endpoint: testhelper.Endpoint(), + Endpoint: th.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") + th.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,8 +708,8 @@ func prepareAuthTokenHandler(t *testing.T, expectedMethod string, status int) go } func TestValidateRequestSuccessful(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + th.SetupHTTP() + defer th.TeardownHTTP() client := prepareAuthTokenHandler(t, "HEAD", http.StatusNoContent) ok, err := tokens.Validate(context.TODO(), &client, "abcdef12345") @@ -723,8 +723,8 @@ func TestValidateRequestSuccessful(t *testing.T) { } func TestValidateRequestFailure(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + th.SetupHTTP() + defer th.TeardownHTTP() client := prepareAuthTokenHandler(t, "HEAD", http.StatusNotFound) ok, err := tokens.Validate(context.TODO(), &client, "abcdef12345") @@ -738,8 +738,8 @@ func TestValidateRequestFailure(t *testing.T) { } func TestValidateRequestError(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + th.SetupHTTP() + defer th.TeardownHTTP() client := prepareAuthTokenHandler(t, "HEAD", http.StatusMethodNotAllowed) _, err := tokens.Validate(context.TODO(), &client, "abcdef12345") @@ -749,17 +749,17 @@ func TestValidateRequestError(t *testing.T) { } func TestRevokeRequestSuccessful(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + th.SetupHTTP() + defer th.TeardownHTTP() client := prepareAuthTokenHandler(t, "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() + th.SetupHTTP() + defer th.TeardownHTTP() client := prepareAuthTokenHandler(t, "DELETE", http.StatusNotFound) res := tokens.Revoke(context.TODO(), &client, "abcdef12345") @@ -769,20 +769,20 @@ func TestRevokeRequestError(t *testing.T) { } func TestNoTokenInResponse(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + th.SetupHTTP() + defer th.TeardownHTTP() client := gophercloud.ServiceClient{ ProviderClient: &gophercloud.ProviderClient{}, - Endpoint: testhelper.Endpoint(), + Endpoint: th.Endpoint(), } - testhelper.Mux.HandleFunc("/auth/tokens", func(w http.ResponseWriter, r *http.Request) { + th.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..c45581ffde 100644 --- a/openstack/identity/v3/tokens/testing/results_test.go +++ b/openstack/identity/v3/tokens/testing/results_test.go @@ -3,59 +3,59 @@ 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) } diff --git a/openstack/identity/v3/trusts/testing/fixtures_test.go b/openstack/identity/v3/trusts/testing/fixtures_test.go index 19ede76569..3e7e77fc3c 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" ) @@ -201,37 +201,37 @@ 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) + th.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) + th.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) + th.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) }) @@ -240,14 +240,14 @@ 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) + th.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) }) } @@ -310,52 +310,52 @@ 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) + th.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) + th.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) + th.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) + th.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/users/testing/fixtures_test.go b/openstack/identity/v3/users/testing/fixtures_test.go index 023124cb79..faff58d4d0 100644 --- a/openstack/identity/v3/users/testing/fixtures_test.go +++ b/openstack/identity/v3/users/testing/fixtures_test.go @@ -382,7 +382,7 @@ func HandleListUsersSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } @@ -396,7 +396,7 @@ func HandleGetUserSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -409,7 +409,7 @@ func HandleCreateUserSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, GetOutput) + fmt.Fprint(w, GetOutput) }) } @@ -422,7 +422,7 @@ func HandleCreateNoOptionsUserSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, CreateNoOptionsRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, GetOutputNoOptions) + fmt.Fprint(w, GetOutputNoOptions) }) } @@ -435,7 +435,7 @@ func HandleUpdateUserSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateOutput) + fmt.Fprint(w, UpdateOutput) }) } @@ -472,7 +472,7 @@ func HandleListUserGroupsSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListGroupsOutput) + fmt.Fprint(w, ListGroupsOutput) }) } @@ -519,7 +519,7 @@ func HandleListUserProjectsSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListProjectsOutput) + fmt.Fprint(w, ListProjectsOutput) }) } @@ -533,6 +533,6 @@ func HandleListInGroupSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } diff --git a/openstack/image/v2/imageimport/testing/requests_test.go b/openstack/image/v2/imageimport/testing/requests_test.go index ae353ad0ac..74df12e548 100644 --- a/openstack/image/v2/imageimport/testing/requests_test.go +++ b/openstack/image/v2/imageimport/testing/requests_test.go @@ -22,7 +22,7 @@ func TestGet(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ImportGetResult) + fmt.Fprint(w, ImportGetResult) }) validImportMethods := []string{ @@ -49,7 +49,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, `{}`) + fmt.Fprint(w, `{}`) }) opts := imageimport.CreateOpts{ diff --git a/openstack/image/v2/images/testing/fixtures_test.go b/openstack/image/v2/images/testing/fixtures_test.go index 0c9a7b0318..693f6b9965 100644 --- a/openstack/image/v2/images/testing/fixtures_test.go +++ b/openstack/image/v2/images/testing/fixtures_test.go @@ -130,7 +130,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 +149,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", @@ -176,7 +176,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, @@ -224,7 +224,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", @@ -258,7 +258,7 @@ func HandleImageGetSuccessfully(t *testing.T) { 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": [], @@ -347,7 +347,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", @@ -389,7 +389,7 @@ func HandleImageListByTagsSuccessfully(t *testing.T) { w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "images": [ { "status": "active", @@ -449,7 +449,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/members/testing/fixtures_test.go b/openstack/image/v2/members/testing/fixtures_test.go index 4100b186f4..6e0bd326e7 100644 --- a/openstack/image/v2/members/testing/fixtures_test.go +++ b/openstack/image/v2/members/testing/fixtures_test.go @@ -18,7 +18,7 @@ func HandleCreateImageMemberSuccessfully(t *testing.T) { 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", @@ -37,7 +37,7 @@ func HandleImageMemberList(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "members": [ { "created_at": "2013-10-07T17:58:03Z", @@ -68,7 +68,7 @@ func HandleImageMemberEmptyList(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, `{ + fmt.Fprint(w, `{ "members": [], "schema": "/v2/schemas/members" }`) @@ -82,7 +82,7 @@ func HandleImageMemberDetails(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", fakeclient.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", @@ -120,7 +120,7 @@ func HandleImageMemberUpdate(t *testing.T) *CallsCounter { 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/tasks/testing/requests_test.go b/openstack/image/v2/tasks/testing/requests_test.go index feb74068bc..f930304427 100644 --- a/openstack/image/v2/tasks/testing/requests_test.go +++ b/openstack/image/v2/tasks/testing/requests_test.go @@ -24,7 +24,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, TasksListResult) + fmt.Fprint(w, TasksListResult) }) count := 0 @@ -64,7 +64,7 @@ func TestGet(t *testing.T) { 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() @@ -102,7 +102,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, TaskCreateResult) + fmt.Fprint(w, TaskCreateResult) }) opts := tasks.CreateOpts{ diff --git a/openstack/keymanager/v1/acls/testing/fixtures_test.go b/openstack/keymanager/v1/acls/testing/fixtures_test.go index 7db2b37b4f..89b6ea7d4d 100644 --- a/openstack/keymanager/v1/acls/testing/fixtures_test.go +++ b/openstack/keymanager/v1/acls/testing/fixtures_test.go @@ -75,7 +75,7 @@ func HandleGetSecretACLSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) } @@ -89,7 +89,7 @@ func HandleGetContainerACLSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) } @@ -102,7 +102,7 @@ func HandleSetSecretACLSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, SetRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, SecretSetResponse) + fmt.Fprint(w, SecretSetResponse) }) } @@ -115,7 +115,7 @@ func HandleSetContainerACLSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, SetRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ContainerSetResponse) + fmt.Fprint(w, ContainerSetResponse) }) } @@ -128,7 +128,7 @@ func HandleUpdateSecretACLSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, SecretSetResponse) + fmt.Fprint(w, SecretSetResponse) }) } @@ -141,7 +141,7 @@ func HandleUpdateContainerACLSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, UpdateRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ContainerSetResponse) + fmt.Fprint(w, ContainerSetResponse) }) } diff --git a/openstack/keymanager/v1/containers/testing/fixtures_test.go b/openstack/keymanager/v1/containers/testing/fixtures_test.go index 4b3997f786..d290a5a9a6 100644 --- a/openstack/keymanager/v1/containers/testing/fixtures_test.go +++ b/openstack/keymanager/v1/containers/testing/fixtures_test.go @@ -235,7 +235,7 @@ func HandleListContainersSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) } @@ -249,7 +249,7 @@ func HandleGetContainerSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) } @@ -262,7 +262,7 @@ func HandleCreateContainerSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) } @@ -288,7 +288,7 @@ func HandleListConsumersSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListConsumersResponse) + fmt.Fprint(w, ListConsumersResponse) }) } @@ -302,7 +302,7 @@ func HandleCreateConsumerSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, CreateConsumerRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, CreateConsumerResponse) + fmt.Fprint(w, CreateConsumerResponse) }) } @@ -316,6 +316,6 @@ func HandleDeleteConsumerSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, CreateConsumerRequest) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) } diff --git a/openstack/keymanager/v1/orders/testing/fixtures_test.go b/openstack/keymanager/v1/orders/testing/fixtures_test.go index 636fe7eb3f..7eb14e0edf 100644 --- a/openstack/keymanager/v1/orders/testing/fixtures_test.go +++ b/openstack/keymanager/v1/orders/testing/fixtures_test.go @@ -143,7 +143,7 @@ func HandleListOrdersSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) } @@ -157,7 +157,7 @@ func HandleGetOrderSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) } @@ -170,7 +170,7 @@ func HandleCreateOrderSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) } diff --git a/openstack/keymanager/v1/secrets/testing/fixtures_test.go b/openstack/keymanager/v1/secrets/testing/fixtures_test.go index ff13102354..ed0e9ffad0 100644 --- a/openstack/keymanager/v1/secrets/testing/fixtures_test.go +++ b/openstack/keymanager/v1/secrets/testing/fixtures_test.go @@ -198,7 +198,7 @@ func HandleListSecretsSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) } @@ -212,7 +212,7 @@ func HandleGetSecretSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) } @@ -225,7 +225,7 @@ func HandleGetPayloadSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetPayloadResponse) + fmt.Fprint(w, GetPayloadResponse) }) } @@ -238,7 +238,7 @@ func HandleCreateSecretSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) } @@ -277,7 +277,7 @@ func HandleGetMetadataSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetMetadataResponse) + fmt.Fprint(w, GetMetadataResponse) }) } @@ -293,7 +293,7 @@ func HandleCreateMetadataSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateMetadataResponse) + fmt.Fprint(w, CreateMetadataResponse) }) } @@ -308,7 +308,7 @@ func HandleGetMetadatumSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, MetadatumResponse) + fmt.Fprint(w, MetadatumResponse) }) } @@ -324,7 +324,7 @@ func HandleCreateMetadatumSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, MetadatumResponse) + fmt.Fprint(w, MetadatumResponse) }) } @@ -340,7 +340,7 @@ func HandleUpdateMetadatumSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, MetadatumResponse) + fmt.Fprint(w, MetadatumResponse) }) } diff --git a/openstack/loadbalancer/v2/amphorae/testing/fixtures_test.go b/openstack/loadbalancer/v2/amphorae/testing/fixtures_test.go index 8ffafa253e..049f0697bf 100644 --- a/openstack/loadbalancer/v2/amphorae/testing/fixtures_test.go +++ b/openstack/loadbalancer/v2/amphorae/testing/fixtures_test.go @@ -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) } @@ -166,7 +166,7 @@ func HandleAmphoraGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleAmphoraBody) + fmt.Fprint(w, SingleAmphoraBody) }) } 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..b427378d95 100644 --- a/openstack/loadbalancer/v2/apiversions/testing/fixture.go +++ b/openstack/loadbalancer/v2/apiversions/testing/fixture.go @@ -88,6 +88,6 @@ func MockListResponse(t *testing.T) { 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/flavorprofiles/requests.go b/openstack/loadbalancer/v2/flavorprofiles/requests.go index 594b79739a..364f9ccd38 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"` } @@ -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/testing/fixtures.go b/openstack/loadbalancer/v2/flavorprofiles/testing/fixtures.go index adbdf1b7b3..3558a21919 100644 --- a/openstack/loadbalancer/v2/flavorprofiles/testing/fixtures.go +++ b/openstack/loadbalancer/v2/flavorprofiles/testing/fixtures.go @@ -94,9 +94,9 @@ 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) } @@ -117,7 +117,7 @@ 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) }) } @@ -127,7 +127,7 @@ func HandleFlavorProfileGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleFlavorProfileBody) + fmt.Fprint(w, SingleFlavorProfileBody) }) } @@ -154,6 +154,6 @@ func HandleFlavorProfileUpdateSuccessfully(t *testing.T) { } }`) - fmt.Fprintf(w, PostUpdateFlavorBody) + fmt.Fprint(w, PostUpdateFlavorBody) }) } diff --git a/openstack/loadbalancer/v2/flavors/requests.go b/openstack/loadbalancer/v2/flavors/requests.go index 6c4859116c..67196a5202 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"` } @@ -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/testing/fixtures.go b/openstack/loadbalancer/v2/flavors/testing/fixtures.go index a3169d7ece..1ec935c156 100644 --- a/openstack/loadbalancer/v2/flavors/testing/fixtures.go +++ b/openstack/loadbalancer/v2/flavors/testing/fixtures.go @@ -102,9 +102,9 @@ 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) } @@ -126,7 +126,7 @@ 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) }) } @@ -136,7 +136,7 @@ func HandleFlavorGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleFlavorBody) + fmt.Fprint(w, SingleFlavorBody) }) } @@ -163,6 +163,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..bc73b1271d 100644 --- a/openstack/loadbalancer/v2/flavors/testing/requests_test.go +++ b/openstack/loadbalancer/v2/flavors/testing/requests_test.go @@ -2,6 +2,8 @@ package testing import ( "context" + "fmt" + "net/http" "testing" "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/flavors" @@ -9,6 +11,7 @@ import ( 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) { @@ -41,6 +44,50 @@ func TestListFlavors(t *testing.T) { } } +func TestListFlavorsEnabled(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + func() { + testCases := []string{ + "true", + "false", + "", + } + + cases := 0 + th.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(), 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() 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/testing/fixtures_test.go b/openstack/loadbalancer/v2/l7policies/testing/fixtures_test.go index a1773287f9..56b35cd0e0 100644 --- a/openstack/loadbalancer/v2/l7policies/testing/fixtures_test.go +++ b/openstack/loadbalancer/v2/l7policies/testing/fixtures_test.go @@ -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) }) } @@ -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) } @@ -234,7 +234,7 @@ func HandleL7PolicyGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleL7PolicyBody) + fmt.Fprint(w, SingleL7PolicyBody) }) } @@ -263,7 +263,7 @@ func HandleL7PolicyUpdateSuccessfully(t *testing.T) { } }`) - fmt.Fprintf(w, PostUpdateL7PolicyBody) + fmt.Fprint(w, PostUpdateL7PolicyBody) }) } @@ -281,7 +281,7 @@ func HandleL7PolicyUpdateNullRedirectURLSuccessfully(t *testing.T) { } }`) - fmt.Fprintf(w, PostUpdateL7PolicyNullRedirectURLBody) + fmt.Fprint(w, PostUpdateL7PolicyNullRedirectURLBody) }) } @@ -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) }) } @@ -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) } @@ -378,7 +378,7 @@ func HandleRuleGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleRuleBody) + fmt.Fprint(w, SingleRuleBody) }) } @@ -425,6 +425,6 @@ func HandleRuleUpdateSuccessfully(t *testing.T) { } }`) - fmt.Fprintf(w, PostUpdateRuleBody) + fmt.Fprint(w, PostUpdateRuleBody) }) } 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..0bd08f783a 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"` } diff --git a/openstack/loadbalancer/v2/listeners/testing/fixtures_test.go b/openstack/loadbalancer/v2/listeners/testing/fixtures_test.go index edda9b2bac..9f85649411 100644 --- a/openstack/loadbalancer/v2/listeners/testing/fixtures_test.go +++ b/openstack/loadbalancer/v2/listeners/testing/fixtures_test.go @@ -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) } @@ -255,7 +255,7 @@ 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) }) } @@ -266,7 +266,7 @@ func HandleListenerGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleListenerBody) + fmt.Fprint(w, SingleListenerBody) }) } @@ -304,7 +304,7 @@ func HandleListenerUpdateSuccessfully(t *testing.T) { } }`) - fmt.Fprintf(w, PostUpdateListenerBody) + fmt.Fprint(w, PostUpdateListenerBody) }) } @@ -315,6 +315,6 @@ func HandleListenerGetStatsTree(t *testing.T) { 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/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/testing/fixtures_test.go b/openstack/loadbalancer/v2/loadbalancers/testing/fixtures_test.go index d4f714f7c5..76ef3ec2a7 100644 --- a/openstack/loadbalancer/v2/loadbalancers/testing/fixtures_test.go +++ b/openstack/loadbalancer/v2/loadbalancers/testing/fixtures_test.go @@ -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) } @@ -533,7 +533,7 @@ 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) }) } @@ -559,7 +559,7 @@ 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) }) } @@ -570,7 +570,7 @@ func HandleLoadbalancerGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleLoadbalancerBody) + fmt.Fprint(w, SingleLoadbalancerBody) }) } @@ -581,7 +581,7 @@ func HandleLoadbalancerGetStatusesTree(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, GetLoadbalancerStatusesBody) + fmt.Fprint(w, GetLoadbalancerStatusesBody) }) } @@ -609,7 +609,7 @@ func HandleLoadbalancerUpdateSuccessfully(t *testing.T) { } }`) - fmt.Fprintf(w, PostUpdateLoadbalancerBody) + fmt.Fprint(w, PostUpdateLoadbalancerBody) }) } @@ -620,7 +620,7 @@ func HandleLoadbalancerGetStatsTree(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, GetLoadbalancerStatsBody) + fmt.Fprint(w, GetLoadbalancerStatsBody) }) } 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..6e8563faaa 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 { diff --git a/openstack/loadbalancer/v2/monitors/testing/fixtures_test.go b/openstack/loadbalancer/v2/monitors/testing/fixtures_test.go index 9526b9cbca..b56d44dbe6 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{}, @@ -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) } @@ -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,7 +196,7 @@ 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) }) } @@ -195,7 +207,7 @@ func HandleHealthmonitorGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleHealthmonitorBody) + fmt.Fprint(w, SingleHealthmonitorBody) }) } @@ -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..1af0aaf9a8 100644 --- a/openstack/loadbalancer/v2/monitors/testing/requests_test.go +++ b/openstack/loadbalancer/v2/monitors/testing/requests_test.go @@ -60,6 +60,7 @@ func TestCreateHealthmonitor(t *testing.T) { actual, err := monitors.Create(context.TODO(), fake.ServiceClient(), 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", 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..bff336ea4a 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"` diff --git a/openstack/loadbalancer/v2/pools/testing/fixtures_test.go b/openstack/loadbalancer/v2/pools/testing/fixtures_test.go index 47d7c373f8..bcc582a638 100644 --- a/openstack/loadbalancer/v2/pools/testing/fixtures_test.go +++ b/openstack/loadbalancer/v2/pools/testing/fixtures_test.go @@ -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) } @@ -172,7 +172,7 @@ 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) }) } @@ -183,7 +183,7 @@ func HandlePoolGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SinglePoolBody) + fmt.Fprint(w, SinglePoolBody) }) } @@ -211,7 +211,7 @@ func HandlePoolUpdateSuccessfully(t *testing.T) { } }`) - fmt.Fprintf(w, PostUpdatePoolBody) + fmt.Fprint(w, PostUpdatePoolBody) }) } @@ -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) } @@ -370,7 +370,7 @@ 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) }) } @@ -381,7 +381,7 @@ func HandleMemberGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", client.TokenID) th.TestHeader(t, r, "Accept", "application/json") - fmt.Fprintf(w, SingleMemberBody) + fmt.Fprint(w, SingleMemberBody) }) } @@ -409,7 +409,7 @@ func HandleMemberUpdateSuccessfully(t *testing.T) { } }`) - fmt.Fprintf(w, PostUpdateMemberBody) + fmt.Fprint(w, PostUpdateMemberBody) }) } diff --git a/openstack/loadbalancer/v2/providers/testing/fixtures_test.go b/openstack/loadbalancer/v2/providers/testing/fixtures_test.go index 64db9a39bf..b8a857e618 100644 --- a/openstack/loadbalancer/v2/providers/testing/fixtures_test.go +++ b/openstack/loadbalancer/v2/providers/testing/fixtures_test.go @@ -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/quotas/testing/requests_test.go b/openstack/loadbalancer/v2/quotas/testing/requests_test.go index 64c819aecf..6f990428fc 100644 --- a/openstack/loadbalancer/v2/quotas/testing/requests_test.go +++ b/openstack/loadbalancer/v2/quotas/testing/requests_test.go @@ -23,7 +23,7 @@ func TestGet_1(t *testing.T) { 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() @@ -42,7 +42,7 @@ func TestGet_2(t *testing.T) { 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() @@ -61,7 +61,7 @@ func TestUpdate_1(t *testing.T) { 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{ @@ -89,7 +89,7 @@ func TestUpdate_2(t *testing.T) { 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{ diff --git a/openstack/messaging/v2/claims/testing/fixtures_test.go b/openstack/messaging/v2/claims/testing/fixtures_test.go index f713687892..5912ff64e6 100644 --- a/openstack/messaging/v2/claims/testing/fixtures_test.go +++ b/openstack/messaging/v2/claims/testing/fixtures_test.go @@ -93,7 +93,7 @@ func HandleCreateSuccessfully(t *testing.T) { w.WriteHeader(http.StatusCreated) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, CreateClaimResponse) + fmt.Fprint(w, CreateClaimResponse) }) } @@ -117,7 +117,7 @@ func HandleGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetClaimResponse) + fmt.Fprint(w, GetClaimResponse) }) } diff --git a/openstack/messaging/v2/messages/testing/fixtures_test.go b/openstack/messaging/v2/messages/testing/fixtures_test.go index a5a82db253..95cd47592b 100644 --- a/openstack/messaging/v2/messages/testing/fixtures_test.go +++ b/openstack/messaging/v2/messages/testing/fixtures_test.go @@ -229,7 +229,7 @@ func HandleCreateSuccessfully(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, CreateMessageResponse) + fmt.Fprint(w, CreateMessageResponse) }) } @@ -245,7 +245,7 @@ func HandleListSuccessfully(t *testing.T) { 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): @@ -262,7 +262,7 @@ func HandleGetMessagesSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetMessagesResponse) + fmt.Fprint(w, GetMessagesResponse) }) } @@ -274,7 +274,7 @@ func HandleGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetMessageResponse) + fmt.Fprint(w, GetMessageResponse) }) } @@ -299,7 +299,7 @@ func HandlePopSuccessfully(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, PopMessageResponse) + fmt.Fprint(w, PopMessageResponse) }) } diff --git a/openstack/messaging/v2/queues/testing/fixtures_test.go b/openstack/messaging/v2/queues/testing/fixtures_test.go index 2e9cf5e752..7abfdc35ba 100644 --- a/openstack/messaging/v2/queues/testing/fixtures_test.go +++ b/openstack/messaging/v2/queues/testing/fixtures_test.go @@ -226,7 +226,7 @@ func HandleListSuccessfully(t *testing.T) { 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": @@ -256,7 +256,7 @@ func HandleUpdateSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, UpdateQueueRequest) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, UpdateQueueResponse) + fmt.Fprint(w, UpdateQueueResponse) }) } @@ -268,7 +268,7 @@ func HandleGetSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetQueueResponse) + fmt.Fprint(w, GetQueueResponse) }) } @@ -290,7 +290,7 @@ func HandleGetStatsSuccessfully(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, GetStatsResponse) + fmt.Fprint(w, GetStatsResponse) }) } @@ -303,7 +303,7 @@ func HandleShareSuccessfully(t *testing.T) { th.TestJSONRequest(t, r, CreateShareRequest) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, CreateShareResponse) + fmt.Fprint(w, CreateShareResponse) }) } 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..dbdf61d9fd 100644 --- a/openstack/networking/v2/apiversions/testing/requests_test.go +++ b/openstack/networking/v2/apiversions/testing/requests_test.go @@ -23,7 +23,7 @@ func TestListVersions(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "versions": [ { @@ -96,7 +96,7 @@ func TestAPIInfo(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "resources": [ { diff --git a/openstack/networking/v2/extensions/agents/testing/requests_test.go b/openstack/networking/v2/extensions/agents/testing/requests_test.go index 070ff1b184..41380edac7 100644 --- a/openstack/networking/v2/extensions/agents/testing/requests_test.go +++ b/openstack/networking/v2/extensions/agents/testing/requests_test.go @@ -25,7 +25,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, AgentsListResult) + fmt.Fprint(w, AgentsListResult) }) count := 0 @@ -66,7 +66,7 @@ func TestGet(t *testing.T) { 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() @@ -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 @@ -149,7 +149,7 @@ func TestListDHCPNetworks(t *testing.T) { 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() @@ -223,7 +223,7 @@ 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 @@ -305,7 +305,7 @@ func TestListDRAgentHostingBGPSpeakers(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListDRAgentHostingBGPSpeakersResult) + fmt.Fprint(w, ListDRAgentHostingBGPSpeakersResult) }) count := 0 @@ -342,7 +342,7 @@ func TestListL3Routers(t *testing.T) { 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() diff --git a/openstack/networking/v2/extensions/attributestags/testing/requests_test.go b/openstack/networking/v2/extensions/attributestags/testing/requests_test.go index e9dc3498fb..5f20bdff5f 100644 --- a/openstack/networking/v2/extensions/attributestags/testing/requests_test.go +++ b/openstack/networking/v2/extensions/attributestags/testing/requests_test.go @@ -25,7 +25,7 @@ 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{ @@ -48,7 +48,7 @@ func TestList(t *testing.T) { 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() diff --git a/openstack/networking/v2/extensions/bgp/peers/requests.go b/openstack/networking/v2/extensions/bgp/peers/requests.go index eef38c0b79..589c4f7ad8 100644 --- a/openstack/networking/v2/extensions/bgp/peers/requests.go +++ b/openstack/networking/v2/extensions/bgp/peers/requests.go @@ -43,7 +43,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 @@ -79,7 +79,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/testing/requests_test.go b/openstack/networking/v2/extensions/bgp/peers/testing/requests_test.go index 3ec3faa391..f24a0a1a0c 100644 --- a/openstack/networking/v2/extensions/bgp/peers/testing/requests_test.go +++ b/openstack/networking/v2/extensions/bgp/peers/testing/requests_test.go @@ -23,7 +23,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListBGPPeersResult) + fmt.Fprint(w, ListBGPPeersResult) }) count := 0 @@ -54,7 +54,7 @@ func TestGet(t *testing.T) { 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() @@ -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 @@ -124,7 +124,7 @@ 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 diff --git a/openstack/networking/v2/extensions/bgp/speakers/requests.go b/openstack/networking/v2/extensions/bgp/speakers/requests.go index 4c352c2cc3..8597602a7d 100644 --- a/openstack/networking/v2/extensions/bgp/speakers/requests.go +++ b/openstack/networking/v2/extensions/bgp/speakers/requests.go @@ -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 @@ -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/testing/requests_test.go b/openstack/networking/v2/extensions/bgp/speakers/testing/requests_test.go index 9e8df52b0c..0ace6b2403 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" @@ -24,7 +23,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListBGPSpeakerResult) + fmt.Fprint(w, ListBGPSpeakerResult) }) count := 0 @@ -55,7 +54,7 @@ func TestGet(t *testing.T) { 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() @@ -75,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) }) opts := speakers.CreateOpts{ @@ -125,7 +124,7 @@ func TestUpdate(t *testing.T) { 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) } else if r.Method == "PUT" { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) @@ -135,7 +134,7 @@ func TestUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UpdateBGPSpeakerResponse) + fmt.Fprint(w, UpdateBGPSpeakerResponse) } else { panic("Unexpected Request") } @@ -169,7 +168,7 @@ 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} @@ -195,7 +194,7 @@ 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) + th.AssertNoErr(t, err) } func TestGetAdvertisedRoutes(t *testing.T) { @@ -208,7 +207,7 @@ func TestGetAdvertisedRoutes(t *testing.T) { 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 @@ -250,7 +249,7 @@ 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} @@ -274,10 +273,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) + th.AssertNoErr(t, err) } diff --git a/openstack/networking/v2/extensions/bgpvpns/requests.go b/openstack/networking/v2/extensions/bgpvpns/requests.go index 587961edee..374b4daf5b 100644 --- a/openstack/networking/v2/extensions/bgpvpns/requests.go +++ b/openstack/networking/v2/extensions/bgpvpns/requests.go @@ -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 diff --git a/openstack/networking/v2/extensions/bgpvpns/testing/requests_test.go b/openstack/networking/v2/extensions/bgpvpns/testing/requests_test.go index 859441f6f4..aacc57aa90 100644 --- a/openstack/networking/v2/extensions/bgpvpns/testing/requests_test.go +++ b/openstack/networking/v2/extensions/bgpvpns/testing/requests_test.go @@ -27,13 +27,15 @@ func TestList(t *testing.T) { 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 @@ -65,7 +67,7 @@ func TestGet(t *testing.T) { 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() @@ -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{ @@ -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" @@ -177,12 +179,14 @@ func TestListNetworkAssociations(t *testing.T) { 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 @@ -219,7 +223,7 @@ 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{ @@ -241,7 +245,7 @@ func TestGetNetworkAssociation(t *testing.T) { 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() @@ -278,12 +282,14 @@ func TestListRouterAssociations(t *testing.T) { 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 @@ -320,7 +326,7 @@ 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{ @@ -342,7 +348,7 @@ func TestGetRouterAssociation(t *testing.T) { 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() @@ -364,7 +370,7 @@ 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{ @@ -404,12 +410,14 @@ func TestListPortAssociations(t *testing.T) { 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 @@ -446,7 +454,7 @@ 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{ @@ -468,7 +476,7 @@ func TestGetPortAssociation(t *testing.T) { 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() @@ -490,7 +498,7 @@ 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{ diff --git a/openstack/networking/v2/extensions/dns/testing/fixtures_test.go b/openstack/networking/v2/extensions/dns/testing/fixtures_test.go index 340c90083c..85679382de 100644 --- a/openstack/networking/v2/extensions/dns/testing/fixtures_test.go +++ b/openstack/networking/v2/extensions/dns/testing/fixtures_test.go @@ -78,7 +78,7 @@ func PortHandleListSuccessfully(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, porttest.ListResponse) + fmt.Fprint(w, porttest.ListResponse) }) } @@ -90,7 +90,7 @@ func PortHandleGet(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, porttest.GetResponse) + fmt.Fprint(w, porttest.GetResponse) }) } @@ -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", @@ -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", @@ -229,7 +229,7 @@ func FloatingIPHandleList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, floatingiptest.ListResponseDNS) + fmt.Fprint(w, floatingiptest.ListResponseDNS) }) } @@ -241,7 +241,7 @@ func FloatingIPHandleGet(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, fmt.Sprintf(`{"floatingip": %s}`, floatingiptest.FipDNS)) + fmt.Fprint(w, fmt.Sprintf(`{"floatingip": %s}`, floatingiptest.FipDNS)) }) } @@ -264,7 +264,7 @@ 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.Fprint(w, fmt.Sprintf(`{"floatingip": %s}`, floatingiptest.FipDNS)) }) } @@ -278,7 +278,7 @@ func NetworkHandleList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, networktest.ListResponse) + fmt.Fprint(w, networktest.ListResponse) }) } @@ -290,7 +290,7 @@ func NetworkHandleGet(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, networktest.GetResponse) + fmt.Fprint(w, networktest.GetResponse) }) } @@ -304,7 +304,7 @@ func NetworkHandleCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, NetworkCreateResponse) + fmt.Fprint(w, NetworkCreateResponse) }) } @@ -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/external/testing/results_test.go b/openstack/networking/v2/extensions/external/testing/results_test.go index 12062e53d8..bf5c02d636 100644 --- a/openstack/networking/v2/extensions/external/testing/results_test.go +++ b/openstack/networking/v2/extensions/external/testing/results_test.go @@ -24,7 +24,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, nettest.ListResponse) + fmt.Fprint(w, nettest.ListResponse) }) type NetworkWithExternalExt struct { @@ -54,7 +54,7 @@ func TestGet(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, nettest.GetResponse) + fmt.Fprint(w, nettest.GetResponse) }) var s struct { @@ -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 @@ -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 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/testing/requests_test.go b/openstack/networking/v2/extensions/fwaas_v2/groups/testing/requests_test.go index 59768a756d..540f80aaf4 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 @@ -23,7 +23,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "firewall_groups": [ { @@ -136,7 +136,7 @@ func TestGet(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", @@ -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", @@ -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", @@ -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", @@ -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", 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/testing/requests_test.go b/openstack/networking/v2/extensions/fwaas_v2/policies/testing/requests_test.go index 3141f19795..217c062f4c 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 @@ -24,7 +24,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "firewall_policies": [ { @@ -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", @@ -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", @@ -252,7 +252,7 @@ func TestGet(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "firewall_policy":{ "name": "www", @@ -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", @@ -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", 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..f251c563c0 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 @@ -24,7 +24,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "firewall_rules": [ { @@ -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", @@ -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, @@ -264,7 +264,7 @@ func TestGet(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "firewall_rule":{ "protocol": "tcp", @@ -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", 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..815a0c0f08 100644 --- a/openstack/networking/v2/extensions/layer3/addressscopes/testing/requests_test.go +++ b/openstack/networking/v2/extensions/layer3/addressscopes/testing/requests_test.go @@ -23,7 +23,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, AddressScopesListResult) + fmt.Fprint(w, AddressScopesListResult) }) count := 0 @@ -63,7 +63,7 @@ func TestGet(t *testing.T) { 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() @@ -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{ @@ -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 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..35d7516e1c 100644 --- a/openstack/networking/v2/extensions/layer3/extraroutes/testing/requests_test.go +++ b/openstack/networking/v2/extensions/layer3/extraroutes/testing/requests_test.go @@ -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", @@ -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", 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..7ea6160032 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 { 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..40f5054b99 100644 --- a/openstack/networking/v2/extensions/layer3/floatingips/testing/requests_test.go +++ b/openstack/networking/v2/extensions/layer3/floatingips/testing/requests_test.go @@ -24,7 +24,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) count := 0 @@ -88,7 +88,7 @@ func TestInvalidNextPageURLs(t *testing.T) { th.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) { @@ -134,7 +134,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", @@ -189,7 +189,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", @@ -243,7 +243,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, @@ -289,7 +289,7 @@ func TestGet(t *testing.T) { 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", @@ -342,7 +342,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", @@ -384,7 +384,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", diff --git a/openstack/networking/v2/extensions/layer3/portforwarding/requests.go b/openstack/networking/v2/extensions/layer3/portforwarding/requests.go index 42f90332ac..1b07b6b7c9 100644 --- a/openstack/networking/v2/extensions/layer3/portforwarding/requests.go +++ b/openstack/networking/v2/extensions/layer3/portforwarding/requests.go @@ -18,6 +18,7 @@ type ListOptsBuilder interface { // either `asc' or `desc'. Marker and Limit are used for pagination. type ListOpts struct { ID string `q:"id"` + Description string `q:"description"` InternalPortID string `q:"internal_port_id"` ExternalPort string `q:"external_port"` InternalIPAddress string `q:"internal_ip_address"` @@ -63,6 +64,7 @@ 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. 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"` @@ -97,22 +99,18 @@ func Create(ctx context.Context, c *gophercloud.ServiceClient, floatingIpId stri // UpdateOpts contains the values used when updating a port forwarding resource. type UpdateOpts struct { - InternalPortID string `json:"internal_port_id,omitempty"` - InternalIPAddress string `json:"internal_ip_address,omitempty"` - InternalPort int `json:"internal_port,omitempty"` - ExternalPort int `json:"external_port,omitempty"` - Protocol string `json:"protocol,omitempty"` + 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"` + ExternalPort int `json:"external_port,omitempty"` + Protocol string `json:"protocol,omitempty"` } // ToPortForwardingUpdateMap allows UpdateOpts to satisfy the UpdateOptsBuilder // interface func (opts UpdateOpts) ToPortForwardingUpdateMap() (map[string]any, error) { - b, err := gophercloud.BuildRequestBody(opts, "port_forwarding") - if err != nil { - return nil, err - } - - return b, nil + return gophercloud.BuildRequestBody(opts, "port_forwarding") } // UpdateOptsBuilder allows extensions to add additional parameters to the diff --git a/openstack/networking/v2/extensions/layer3/portforwarding/results.go b/openstack/networking/v2/extensions/layer3/portforwarding/results.go index 17c5281acc..89e71efd0e 100644 --- a/openstack/networking/v2/extensions/layer3/portforwarding/results.go +++ b/openstack/networking/v2/extensions/layer3/portforwarding/results.go @@ -9,6 +9,10 @@ type PortForwarding struct { // The ID of the floating IP port forwarding ID string `json:"id"` + // A text describing the rule, which helps users to manage/find easily + // theirs rules. + Description string `json:"description"` + // The ID of the Neutron port associated to the floating IP port forwarding. InternalPortID string `json:"internal_port_id"` 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..dc2aa8c49b 100644 --- a/openstack/networking/v2/extensions/layer3/portforwarding/testing/requests_test.go +++ b/openstack/networking/v2/extensions/layer3/portforwarding/testing/requests_test.go @@ -23,7 +23,7 @@ func TestPortForwardingList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) count := 0 @@ -91,7 +91,7 @@ func TestCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "port_forwarding": { "protocol": "tcp", @@ -134,7 +134,7 @@ func TestGet(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "port_forwarding": { "protocol": "tcp", @@ -196,7 +196,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", diff --git a/openstack/networking/v2/extensions/layer3/routers/requests.go b/openstack/networking/v2/extensions/layer3/routers/requests.go index dcc7977a46..5d31ec1138 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,12 +54,12 @@ 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) +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + q, err := opts.ToRouterListQuery() if err != nil { return pagination.Pager{Err: err} } - u := rootURL(c) + q.String() + u := rootURL(c) + q return pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page { return RouterPage{pagination.LinkedPageBase{PageResult: r}} }) @@ -112,6 +129,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 +152,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/routers/results.go b/openstack/networking/v2/extensions/layer3/routers/results.go index 25e8ab7b59..14845524c0 100644 --- a/openstack/networking/v2/extensions/layer3/routers/results.go +++ b/openstack/networking/v2/extensions/layer3/routers/results.go @@ -77,6 +77,15 @@ 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:"created_at"` + + // Timestamp when the router was last updated + UpdatedAt time.Time `json:"updated_at"` } // RouterPage is the page returned by a pager when traversing over a @@ -113,11 +122,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).Result.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..6ff1242366 100644 --- a/openstack/networking/v2/extensions/layer3/routers/testing/requests_test.go +++ b/openstack/networking/v2/extensions/layer3/routers/testing/requests_test.go @@ -24,7 +24,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "routers": [ { @@ -156,7 +156,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", @@ -224,7 +224,7 @@ func TestGet(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "router": { "status": "ACTIVE", @@ -301,7 +301,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", @@ -367,7 +367,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", @@ -422,7 +422,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", @@ -481,7 +481,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", @@ -530,7 +530,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", @@ -561,7 +561,7 @@ func TestListL3Agents(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "agents": [ { diff --git a/openstack/networking/v2/extensions/mtu/testing/results_test.go b/openstack/networking/v2/extensions/mtu/testing/results_test.go index 273bdb5166..916bbc00f8 100644 --- a/openstack/networking/v2/extensions/mtu/testing/results_test.go +++ b/openstack/networking/v2/extensions/mtu/testing/results_test.go @@ -29,7 +29,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, nettest.ListResponse) + fmt.Fprint(w, nettest.ListResponse) }) type NetworkWithMTUExt struct { @@ -59,7 +59,7 @@ func TestGet(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, nettest.GetResponse) + fmt.Fprint(w, nettest.GetResponse) }) var s NetworkMTU @@ -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 @@ -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 diff --git a/openstack/networking/v2/extensions/networkipavailabilities/testing/requests_test.go b/openstack/networking/v2/extensions/networkipavailabilities/testing/requests_test.go index 86166b5e33..cdd6d8d4e2 100644 --- a/openstack/networking/v2/extensions/networkipavailabilities/testing/requests_test.go +++ b/openstack/networking/v2/extensions/networkipavailabilities/testing/requests_test.go @@ -24,7 +24,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, NetworkIPAvailabilityListResult) + fmt.Fprint(w, NetworkIPAvailabilityListResult) }) count := 0 @@ -65,7 +65,7 @@ func TestGet(t *testing.T) { 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() diff --git a/openstack/networking/v2/extensions/portsbinding/testing/fixtures_test.go b/openstack/networking/v2/extensions/portsbinding/testing/fixtures_test.go index 494fe49091..44e2b5db9b 100644 --- a/openstack/networking/v2/extensions/portsbinding/testing/fixtures_test.go +++ b/openstack/networking/v2/extensions/portsbinding/testing/fixtures_test.go @@ -18,7 +18,7 @@ func HandleListSuccessfully(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, porttest.ListResponse) + fmt.Fprint(w, porttest.ListResponse) }) } @@ -30,7 +30,7 @@ func HandleGet(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, porttest.GetResponse) + fmt.Fprint(w, porttest.GetResponse) }) } @@ -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", @@ -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/provider/testing/results_test.go b/openstack/networking/v2/extensions/provider/testing/results_test.go index 4e21481de1..acbf9f7a1f 100644 --- a/openstack/networking/v2/extensions/provider/testing/results_test.go +++ b/openstack/networking/v2/extensions/provider/testing/results_test.go @@ -25,7 +25,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, nettest.ListResponse) + fmt.Fprint(w, nettest.ListResponse) }) type NetworkWithExt struct { @@ -60,7 +60,7 @@ func TestGet(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, nettest.GetResponse) + fmt.Fprint(w, nettest.GetResponse) }) var s struct { @@ -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 { @@ -142,7 +142,7 @@ func TestCreateWithMultipleProvider(t *testing.T) { `) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "network": { "status": "ACTIVE", @@ -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 { 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..c35853bac8 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. 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..4ad4cf3088 100644 --- a/openstack/networking/v2/extensions/qos/policies/testing/requests_test.go +++ b/openstack/networking/v2/extensions/qos/policies/testing/requests_test.go @@ -26,7 +26,7 @@ func TestGetPort(t *testing.T) { 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) }) @@ -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) }) @@ -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) }) @@ -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) }) @@ -166,7 +166,7 @@ func TestGetNetwork(t *testing.T) { 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) }) @@ -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) }) @@ -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) }) @@ -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) }) @@ -307,7 +307,7 @@ func TestListPolicies(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListPoliciesResponse) + fmt.Fprint(w, ListPoliciesResponse) }) count := 0 @@ -347,7 +347,7 @@ func TestGetPolicy(t *testing.T) { 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() @@ -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{ @@ -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 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..20de755132 100644 --- a/openstack/networking/v2/extensions/qos/rules/testing/requests_test.go +++ b/openstack/networking/v2/extensions/qos/rules/testing/requests_test.go @@ -23,7 +23,7 @@ func TestListBandwidthLimitRule(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, BandwidthLimitRulesListResult) + fmt.Fprint(w, BandwidthLimitRulesListResult) }) count := 0 @@ -71,7 +71,7 @@ func TestGetBandwidthLimitRule(t *testing.T) { 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() @@ -97,7 +97,7 @@ 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{ @@ -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 @@ -166,7 +166,7 @@ func TestListDSCPMarkingRule(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, DSCPMarkingRulesListResult) + fmt.Fprint(w, DSCPMarkingRulesListResult) }) count := 0 @@ -212,7 +212,7 @@ func TestGetDSCPMarkingRule(t *testing.T) { 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() @@ -236,7 +236,7 @@ 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{ @@ -263,7 +263,7 @@ 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 @@ -302,7 +302,7 @@ func TestListMinimumBandwidthRule(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, MinimumBandwidthRulesListResult) + fmt.Fprint(w, MinimumBandwidthRulesListResult) }) count := 0 @@ -349,7 +349,7 @@ func TestGetMinimumBandwidthRule(t *testing.T) { 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() @@ -374,7 +374,7 @@ 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{ @@ -400,7 +400,7 @@ 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 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..7bea9f6b66 100644 --- a/openstack/networking/v2/extensions/qos/ruletypes/testing/requests_test.go +++ b/openstack/networking/v2/extensions/qos/ruletypes/testing/requests_test.go @@ -52,7 +52,7 @@ func TestGetRuleType(t *testing.T) { 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) }) diff --git a/openstack/networking/v2/extensions/quotas/testing/requests_test.go b/openstack/networking/v2/extensions/quotas/testing/requests_test.go index 7ed3100242..a5714d17fe 100644 --- a/openstack/networking/v2/extensions/quotas/testing/requests_test.go +++ b/openstack/networking/v2/extensions/quotas/testing/requests_test.go @@ -23,7 +23,7 @@ func TestGet(t *testing.T) { 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() @@ -42,7 +42,7 @@ func TestGetDetail(t *testing.T) { 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() @@ -61,7 +61,7 @@ func TestUpdate(t *testing.T) { 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{ diff --git a/openstack/networking/v2/extensions/rbacpolicies/testing/requests_test.go b/openstack/networking/v2/extensions/rbacpolicies/testing/requests_test.go index 7503bc7a74..9fc04412f4 100644 --- a/openstack/networking/v2/extensions/rbacpolicies/testing/requests_test.go +++ b/openstack/networking/v2/extensions/rbacpolicies/testing/requests_test.go @@ -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{ @@ -51,7 +51,7 @@ func TestGet(t *testing.T) { 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() @@ -70,7 +70,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) client := fake.ServiceClient() @@ -106,7 +106,7 @@ func TestListWithAllPages(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) client := fake.ServiceClient() @@ -159,7 +159,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) }) options := rbacpolicies.UpdateOpts{TargetTenant: "9d766060b6354c9e8e2da44cab0e8f38"} diff --git a/openstack/networking/v2/extensions/security/groups/requests.go b/openstack/networking/v2/extensions/security/groups/requests.go index a45403f13b..95f7bfabdf 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" @@ -13,19 +14,21 @@ import ( // 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"` - 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"` } // List returns a Pager which allows you to iterate over a collection of @@ -63,6 +66,9 @@ type CreateOpts struct { // Describes the security group. Description string `json:"description,omitempty"` + + // Stateful indicates if the security group is stateful or stateless. + Stateful *bool `json:"stateful,omitempty"` } // ToSecGroupCreateMap builds a request body from CreateOpts. @@ -97,6 +103,14 @@ type UpdateOpts struct { // 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. @@ -111,9 +125,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 6f1a4e1620..6a8a16fa86 100644 --- a/openstack/networking/v2/extensions/security/groups/results.go +++ b/openstack/networking/v2/extensions/security/groups/results.go @@ -25,6 +25,9 @@ type SecGroup struct { // traffic entering and leaving the group. Rules []rules.SecGroupRule `json:"security_group_rules"` + // Indicates if the security group is stateful or stateless. + Stateful bool `json:"stateful"` + // TenantID is the project owner of the security group. TenantID string `json:"tenant_id"` @@ -38,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 { 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..f816f0d6d2 100644 --- a/openstack/networking/v2/extensions/security/groups/testing/requests_test.go +++ b/openstack/networking/v2/extensions/security/groups/testing/requests_test.go @@ -24,7 +24,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, SecurityGroupListResponse) + fmt.Fprint(w, SecurityGroupListResponse) }) count := 0 @@ -64,7 +64,7 @@ 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"} @@ -87,7 +87,7 @@ 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"} @@ -112,7 +112,7 @@ func TestGet(t *testing.T) { 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() diff --git a/openstack/networking/v2/extensions/security/rules/requests.go b/openstack/networking/v2/extensions/security/rules/requests.go index 78b67b0df0..2bb8ffc6c2 100644 --- a/openstack/networking/v2/extensions/security/rules/requests.go +++ b/openstack/networking/v2/extensions/security/rules/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 { + 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 @@ -29,17 +35,27 @@ type ListOpts struct { 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) +func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + q, err := opts.ToSecGroupListQuery() if err != nil { return pagination.Pager{Err: err} } - u := rootURL(c) + q.String() + u := rootURL(c) + q return pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page { return SecGroupRulePage{pagination.LinkedPageBase{PageResult: r}} }) @@ -77,7 +93,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 @@ -150,6 +166,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(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..8a3355dfe0 100644 --- a/openstack/networking/v2/extensions/security/rules/results.go +++ b/openstack/networking/v2/extensions/security/rules/results.go @@ -1,6 +1,8 @@ package rules import ( + "time" + "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/pagination" ) @@ -56,6 +58,15 @@ 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:"created_at"` + + // Timestamp when the rule was last updated + UpdatedAt time.Time `json:"updated_at"` } // SecGroupRulePage is the page returned by a pager when traversing over a @@ -103,6 +114,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 +127,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..10aa461908 100644 --- a/openstack/networking/v2/extensions/security/rules/testing/requests_test.go +++ b/openstack/networking/v2/extensions/security/rules/testing/requests_test.go @@ -23,7 +23,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "security_group_rules": [ { @@ -131,7 +131,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", @@ -164,6 +164,159 @@ func TestCreate(t *testing.T) { th.AssertNoErr(t, err) } +func TestCreateAnyProtocol(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.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(), opts).Extract() + th.AssertNoErr(t, err) +} + +func TestCreateBulk(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.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(), opts).Extract() + th.AssertNoErr(t, err) +} + func TestRequiredCreateOpts(t *testing.T) { res := rules.Create(context.TODO(), fake.ServiceClient(), rules.CreateOpts{Direction: rules.DirIngress}) if res.Err == nil { @@ -194,7 +347,7 @@ func TestGet(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "security_group_rule": { "direction": "egress", 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/testing/requests_test.go b/openstack/networking/v2/extensions/subnetpools/testing/requests_test.go index 50f479b9d1..2d8df70180 100644 --- a/openstack/networking/v2/extensions/subnetpools/testing/requests_test.go +++ b/openstack/networking/v2/extensions/subnetpools/testing/requests_test.go @@ -24,7 +24,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, SubnetPoolsListResult) + fmt.Fprint(w, SubnetPoolsListResult) }) count := 0 @@ -66,7 +66,7 @@ func TestGet(t *testing.T) { 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() @@ -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{ @@ -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 := "" diff --git a/openstack/networking/v2/extensions/testing/delegate_test.go b/openstack/networking/v2/extensions/testing/delegate_test.go index eb130cd4b0..9f3421f272 100644 --- a/openstack/networking/v2/extensions/testing/delegate_test.go +++ b/openstack/networking/v2/extensions/testing/delegate_test.go @@ -23,7 +23,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "extensions": [ { @@ -83,7 +83,7 @@ func TestGet(t *testing.T) { 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/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..81e6bef8c2 100644 --- a/openstack/networking/v2/extensions/trunks/testing/requests_test.go +++ b/openstack/networking/v2/extensions/trunks/testing/requests_test.go @@ -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 @@ -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 @@ -115,7 +115,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) client := fake.ServiceClient() @@ -153,7 +153,7 @@ func TestGet(t *testing.T) { 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() @@ -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 @@ -207,7 +207,7 @@ func TestGetSubports(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListSubportsResponse) + fmt.Fprint(w, ListSubportsResponse) }) client := fake.ServiceClient() @@ -259,7 +259,7 @@ 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() @@ -287,7 +287,7 @@ 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() diff --git a/openstack/networking/v2/extensions/vlantransparent/testing/requests_test.go b/openstack/networking/v2/extensions/vlantransparent/testing/requests_test.go index d298894de7..dc5d55043a 100644 --- a/openstack/networking/v2/extensions/vlantransparent/testing/requests_test.go +++ b/openstack/networking/v2/extensions/vlantransparent/testing/requests_test.go @@ -23,7 +23,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, NetworksVLANTransparentListResult) + fmt.Fprint(w, NetworksVLANTransparentListResult) }) type networkVLANTransparentExt struct { @@ -59,7 +59,7 @@ func TestGet(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, NetworksVLANTransparentGetResult) + fmt.Fprint(w, NetworksVLANTransparentGetResult) }) var s struct { @@ -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 @@ -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 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..dbf3ba515e 100644 --- a/openstack/networking/v2/extensions/vpnaas/endpointgroups/testing/requests_test.go +++ b/openstack/networking/v2/extensions/vpnaas/endpointgroups/testing/requests_test.go @@ -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": "", @@ -90,7 +90,7 @@ func TestGet(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "endpoint_group": { "description": "", @@ -135,7 +135,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "endpoint_groups": [ { @@ -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", 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/testing/requests_test.go b/openstack/networking/v2/extensions/vpnaas/ikepolicies/testing/requests_test.go index 74b9427cc1..e8e63dfde7 100644 --- a/openstack/networking/v2/extensions/vpnaas/ikepolicies/testing/requests_test.go +++ b/openstack/networking/v2/extensions/vpnaas/ikepolicies/testing/requests_test.go @@ -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", @@ -97,7 +97,7 @@ func TestGet(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "ikepolicy":{ "name": "policy", @@ -165,7 +165,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "ikepolicies": [ { @@ -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", 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/testing/requests_test.go b/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/testing/requests_test.go index 0363c7c63e..35c09a4ad5 100644 --- a/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/testing/requests_test.go +++ b/openstack/networking/v2/extensions/vpnaas/ipsecpolicies/testing/requests_test.go @@ -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", @@ -111,7 +111,7 @@ func TestGet(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "ipsecpolicy": { "name": "ipsecpolicy1", @@ -180,7 +180,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "ipsecpolicies": [ { @@ -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": { 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..c0a33e8d0f 100644 --- a/openstack/networking/v2/extensions/vpnaas/services/testing/requests_test.go +++ b/openstack/networking/v2/extensions/vpnaas/services/testing/requests_test.go @@ -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", @@ -91,7 +91,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "vpnservices":[ { @@ -154,7 +154,7 @@ func TestGet(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", @@ -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", 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..58279b75d5 100644 --- a/openstack/networking/v2/extensions/vpnaas/siteconnections/testing/requests_test.go +++ b/openstack/networking/v2/extensions/vpnaas/siteconnections/testing/requests_test.go @@ -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", @@ -150,7 +150,7 @@ func TestGet(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "ipsec_site_connection": { "status": "PENDING_CREATE", @@ -227,7 +227,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "ipsec_site_connections":[ { @@ -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": { 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/testing/requests_test.go b/openstack/networking/v2/networks/testing/requests_test.go index c28dd2ed0f..f4e905a64f 100644 --- a/openstack/networking/v2/networks/testing/requests_test.go +++ b/openstack/networking/v2/networks/testing/requests_test.go @@ -25,7 +25,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) client := fake.ServiceClient() @@ -62,7 +62,7 @@ func TestListWithExtensions(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) client := fake.ServiceClient() @@ -101,7 +101,7 @@ func TestGet(t *testing.T) { 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() @@ -122,7 +122,7 @@ func TestGetWithExtensions(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) var networkWithExtensions struct { @@ -150,7 +150,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 @@ -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 @@ -205,7 +205,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, iFalse := true, false @@ -237,7 +237,7 @@ 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) { @@ -251,7 +251,7 @@ 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 @@ -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 { @@ -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 { diff --git a/openstack/networking/v2/ports/requests.go b/openstack/networking/v2/ports/requests.go index 218c2897f7..bfff2dffb2 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 } diff --git a/openstack/networking/v2/ports/testing/requests_test.go b/openstack/networking/v2/ports/testing/requests_test.go index 7c3569b70b..1b77780610 100644 --- a/openstack/networking/v2/ports/testing/requests_test.go +++ b/openstack/networking/v2/ports/testing/requests_test.go @@ -27,7 +27,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) count := 0 @@ -86,7 +86,7 @@ func TestListWithExtensions(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListResponse) + fmt.Fprint(w, ListResponse) }) type portWithExt struct { @@ -117,7 +117,7 @@ func TestGet(t *testing.T) { 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() @@ -152,7 +152,7 @@ func TestGetWithExtensions(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetResponse) + fmt.Fprint(w, GetResponse) }) var portWithExtensions struct { @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -483,7 +483,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 { @@ -531,7 +531,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) }) name := "new_port_name" @@ -573,7 +573,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" @@ -614,7 +614,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 @@ -642,7 +642,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{ @@ -669,7 +669,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 { @@ -707,7 +707,7 @@ 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) { th.TestMethod(t, r, "PUT") @@ -720,7 +720,7 @@ 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" @@ -757,7 +757,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" @@ -799,7 +799,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" @@ -837,7 +837,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" @@ -887,7 +887,7 @@ func TestGetWithExtraDHCPOpts(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, GetWithExtraDHCPOptsResponse) + fmt.Fprint(w, GetWithExtraDHCPOptsResponse) }) var s struct { @@ -933,7 +933,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 @@ -996,7 +996,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" diff --git a/openstack/networking/v2/subnets/requests.go b/openstack/networking/v2/subnets/requests.go index 20f747b5a9..150afd7394 100644 --- a/openstack/networking/v2/subnets/requests.go +++ b/openstack/networking/v2/subnets/requests.go @@ -20,27 +20,29 @@ type ListOptsBuilder interface { // by a particular subnet attribute. SortDir sets the direction, and is either // `asc' or `desc'. Marker and Limit are used for pagination. type ListOpts struct { - Name string `q:"name"` - Description string `q:"description"` - EnableDHCP *bool `q:"enable_dhcp"` - NetworkID string `q:"network_id"` - TenantID string `q:"tenant_id"` - ProjectID string `q:"project_id"` - IPVersion int `q:"ip_version"` - GatewayIP string `q:"gateway_ip"` - CIDR string `q:"cidr"` - IPv6AddressMode string `q:"ipv6_address_mode"` - IPv6RAMode string `q:"ipv6_ra_mode"` - ID string `q:"id"` - SubnetPoolID string `q:"subnetpool_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"` + Name string `q:"name"` + Description string `q:"description"` + DNSPublishFixedIP *bool `q:"dns_publish_fixed_ip"` + EnableDHCP *bool `q:"enable_dhcp"` + NetworkID string `q:"network_id"` + TenantID string `q:"tenant_id"` + ProjectID string `q:"project_id"` + IPVersion int `q:"ip_version"` + GatewayIP string `q:"gateway_ip"` + CIDR string `q:"cidr"` + IPv6AddressMode string `q:"ipv6_address_mode"` + IPv6RAMode string `q:"ipv6_ra_mode"` + ID string `q:"id"` + SubnetPoolID string `q:"subnetpool_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"` } // ToSubnetListQuery formats a ListOpts into a query string. @@ -123,6 +125,9 @@ type CreateOpts struct { // DNSNameservers are the nameservers to be set via DHCP. DNSNameservers []string `json:"dns_nameservers,omitempty"` + // DNSPublishFixedIP will either enable or disable the publication of fixed IPs to the DNS + DNSPublishFixedIP *bool `json:"dns_publish_fixed_ip,omitempty"` + // ServiceTypes are the service types associated with the subnet. ServiceTypes []string `json:"service_types,omitempty"` @@ -198,6 +203,9 @@ type UpdateOpts struct { // DNSNameservers are the nameservers to be set via DHCP. DNSNameservers *[]string `json:"dns_nameservers,omitempty"` + // DNSPublishFixedIP will either enable or disable the publication of fixed IPs to the DNS + DNSPublishFixedIP *bool `json:"dns_publish_fixed_ip,omitempty"` + // ServiceTypes are the service types associated with the subnet. ServiceTypes *[]string `json:"service_types,omitempty"` diff --git a/openstack/networking/v2/subnets/results.go b/openstack/networking/v2/subnets/results.go index a105c75015..01c6acc070 100644 --- a/openstack/networking/v2/subnets/results.go +++ b/openstack/networking/v2/subnets/results.go @@ -1,6 +1,8 @@ package subnets import ( + "time" + "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/pagination" ) @@ -83,6 +85,9 @@ type Subnet struct { // DNS name servers used by hosts in this subnet. DNSNameservers []string `json:"dns_nameservers"` + // Specifies whether the fixed IP addresses are published to the DNS. + DNSPublishFixedIP bool `json:"dns_publish_fixed_ip"` + // Service types associated with the subnet. ServiceTypes []string `json:"service_types"` @@ -118,6 +123,12 @@ type Subnet struct { // RevisionNumber optionally set via extensions/standard-attr-revisions RevisionNumber int `json:"revision_number"` + + // Timestamp when the subnet was created + CreatedAt time.Time `json:"created_at"` + + // Timestamp when the subnet was last updated + UpdatedAt time.Time `json:"updated_at"` } // SubnetPage is the page returned by a pager when traversing over a collection diff --git a/openstack/networking/v2/subnets/testing/fixtures_test.go b/openstack/networking/v2/subnets/testing/fixtures_test.go index e6079975ec..a44f1cc77e 100644 --- a/openstack/networking/v2/subnets/testing/fixtures_test.go +++ b/openstack/networking/v2/subnets/testing/fixtures_test.go @@ -6,90 +6,94 @@ import ( const SubnetListResult = ` { - "subnets": [ - { - "name": "private-subnet", - "enable_dhcp": true, - "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", - "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", - "dns_nameservers": [], - "allocation_pools": [ - { - "start": "10.0.0.2", - "end": "10.0.0.254" - } - ], - "host_routes": [], - "ip_version": 4, - "gateway_ip": "10.0.0.1", - "cidr": "10.0.0.0/24", - "id": "08eae331-0402-425a-923c-34f7cfe39c1b" - }, - { - "name": "my_subnet", - "enable_dhcp": true, - "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", - "tenant_id": "4fd44f30292945e481c7b8a0c8908869", - "dns_nameservers": [], - "allocation_pools": [ - { - "start": "192.0.0.2", - "end": "192.255.255.254" - } - ], - "host_routes": [], - "ip_version": 4, - "gateway_ip": "192.0.0.1", - "cidr": "192.0.0.0/8", - "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0b" - }, - { - "name": "my_gatewayless_subnet", - "enable_dhcp": true, - "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a23", - "tenant_id": "4fd44f30292945e481c7b8a0c8908869", - "dns_nameservers": [], - "allocation_pools": [ - { - "start": "192.168.1.2", - "end": "192.168.1.254" - } - ], - "host_routes": [], - "ip_version": 4, - "gateway_ip": null, - "cidr": "192.168.1.0/24", - "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0c" - }, - { - "name": "my_subnet_with_subnetpool", - "enable_dhcp": false, - "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a23", - "tenant_id": "4fd44f30292945e481c7b8a0c8908869", - "dns_nameservers": [], - "allocation_pools": [ - { - "start": "10.11.12.2", - "end": "10.11.12.254" - } - ], - "host_routes": [], - "ip_version": 4, - "gateway_ip": null, - "cidr": "10.11.12.0/24", - "id": "38186a51-f373-4bbc-838b-6eaa1aa13eac", - "subnetpool_id": "b80340c7-9960-4f67-a99c-02501656284b" - } - ] + "subnets": [ + { + "name": "private-subnet", + "enable_dhcp": true, + "dns_publish_fixed_ip": true, + "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", + "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", + "dns_nameservers": [], + "allocation_pools": [ + { + "start": "10.0.0.2", + "end": "10.0.0.254" + } + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": "10.0.0.1", + "cidr": "10.0.0.0/24", + "id": "08eae331-0402-425a-923c-34f7cfe39c1b" + }, + { + "name": "my_subnet", + "enable_dhcp": true, + "dns_publish_fixed_ip": true, + "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", + "tenant_id": "4fd44f30292945e481c7b8a0c8908869", + "dns_nameservers": [], + "allocation_pools": [ + { + "start": "192.0.0.2", + "end": "192.255.255.254" + } + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": "192.0.0.1", + "cidr": "192.0.0.0/8", + "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0b" + }, + { + "name": "my_gatewayless_subnet", + "enable_dhcp": true, + "dns_publish_fixed_ip": true, + "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a23", + "tenant_id": "4fd44f30292945e481c7b8a0c8908869", + "dns_nameservers": [], + "allocation_pools": [ + { + "start": "192.168.1.2", + "end": "192.168.1.254" + } + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": null, + "cidr": "192.168.1.0/24", + "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0c" + }, + { + "name": "my_subnet_with_subnetpool", + "enable_dhcp": false, + "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a23", + "tenant_id": "4fd44f30292945e481c7b8a0c8908869", + "dns_nameservers": [], + "allocation_pools": [ + { + "start": "10.11.12.2", + "end": "10.11.12.254" + } + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": null, + "cidr": "10.11.12.0/24", + "id": "38186a51-f373-4bbc-838b-6eaa1aa13eac", + "subnetpool_id": "b80340c7-9960-4f67-a99c-02501656284b" + } + ] } ` var Subnet1 = subnets.Subnet{ - Name: "private-subnet", - EnableDHCP: true, - NetworkID: "db193ab3-96e3-4cb3-8fc5-05f4296d0324", - TenantID: "26a7980765d0414dbc1fc1f88cdb7e6e", - DNSNameservers: []string{}, + Name: "private-subnet", + EnableDHCP: true, + NetworkID: "db193ab3-96e3-4cb3-8fc5-05f4296d0324", + TenantID: "26a7980765d0414dbc1fc1f88cdb7e6e", + DNSNameservers: []string{}, + DNSPublishFixedIP: true, AllocationPools: []subnets.AllocationPool{ { Start: "10.0.0.2", @@ -104,11 +108,13 @@ var Subnet1 = subnets.Subnet{ } var Subnet2 = subnets.Subnet{ - Name: "my_subnet", - EnableDHCP: true, - NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a22", - TenantID: "4fd44f30292945e481c7b8a0c8908869", - DNSNameservers: []string{}, + Name: "my_subnet", + EnableDHCP: true, + + NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a22", + TenantID: "4fd44f30292945e481c7b8a0c8908869", + DNSNameservers: []string{}, + DNSPublishFixedIP: true, AllocationPools: []subnets.AllocationPool{ { Start: "192.0.0.2", @@ -123,11 +129,12 @@ var Subnet2 = subnets.Subnet{ } var Subnet3 = subnets.Subnet{ - Name: "my_gatewayless_subnet", - EnableDHCP: true, - NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a23", - TenantID: "4fd44f30292945e481c7b8a0c8908869", - DNSNameservers: []string{}, + Name: "my_gatewayless_subnet", + EnableDHCP: true, + NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a23", + TenantID: "4fd44f30292945e481c7b8a0c8908869", + DNSNameservers: []string{}, + DNSPublishFixedIP: true, AllocationPools: []subnets.AllocationPool{ { Start: "192.168.1.2", @@ -142,11 +149,12 @@ var Subnet3 = subnets.Subnet{ } var Subnet4 = subnets.Subnet{ - Name: "my_subnet_with_subnetpool", - EnableDHCP: false, - NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a23", - TenantID: "4fd44f30292945e481c7b8a0c8908869", - DNSNameservers: []string{}, + Name: "my_subnet_with_subnetpool", + EnableDHCP: false, + NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a23", + TenantID: "4fd44f30292945e481c7b8a0c8908869", + DNSNameservers: []string{}, + DNSPublishFixedIP: false, AllocationPools: []subnets.AllocationPool{ { Start: "10.11.12.2", @@ -163,409 +171,414 @@ var Subnet4 = subnets.Subnet{ const SubnetGetResult = ` { - "subnet": { - "name": "my_subnet", - "enable_dhcp": true, - "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", - "tenant_id": "4fd44f30292945e481c7b8a0c8908869", - "dns_nameservers": [], - "allocation_pools": [ - { - "start": "192.0.0.2", - "end": "192.255.255.254" - } - ], - "host_routes": [], - "ip_version": 4, - "gateway_ip": "192.0.0.1", - "cidr": "192.0.0.0/8", - "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0b", - "subnetpool_id": "b80340c7-9960-4f67-a99c-02501656284b" - } + "subnet": { + "name": "my_subnet", + "enable_dhcp": true, + "dns_publish_fixed_ip": true, + "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", + "tenant_id": "4fd44f30292945e481c7b8a0c8908869", + "dns_nameservers": [], + "allocation_pools": [ + { + "start": "192.0.0.2", + "end": "192.255.255.254" + } + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": "192.0.0.1", + "cidr": "192.0.0.0/8", + "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0b", + "subnetpool_id": "b80340c7-9960-4f67-a99c-02501656284b" + } } ` const SubnetCreateRequest = ` { - "subnet": { - "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", - "ip_version": 4, - "gateway_ip": "192.168.199.1", - "cidr": "192.168.199.0/24", - "dns_nameservers": ["foo"], - "service_types": ["network:routed"], - "allocation_pools": [ - { - "start": "192.168.199.2", - "end": "192.168.199.254" - } - ], - "host_routes": [{"destination":"","nexthop": "bar"}], - "subnetpool_id": "b80340c7-9960-4f67-a99c-02501656284b" - } + "subnet": { + "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", + "ip_version": 4, + "dns_publish_fixed_ip": true, + "gateway_ip": "192.168.199.1", + "cidr": "192.168.199.0/24", + "dns_nameservers": ["foo"], + "service_types": ["network:routed"], + "allocation_pools": [ + { + "start": "192.168.199.2", + "end": "192.168.199.254" + } + ], + "host_routes": [{"destination":"","nexthop": "bar"}], + "subnetpool_id": "b80340c7-9960-4f67-a99c-02501656284b" + } } ` const SubnetCreateResult = ` { - "subnet": { - "name": "", - "enable_dhcp": true, - "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", - "tenant_id": "4fd44f30292945e481c7b8a0c8908869", - "dns_nameservers": ["foo"], - "service_types": ["network:routed"], - "allocation_pools": [ - { - "start": "192.168.199.2", - "end": "192.168.199.254" - } - ], - "host_routes": [], - "ip_version": 4, - "gateway_ip": "192.168.199.1", - "cidr": "192.168.199.0/24", - "id": "3b80198d-4f7b-4f77-9ef5-774d54e17126", - "subnetpool_id": "b80340c7-9960-4f67-a99c-02501656284b" - } + "subnet": { + "name": "", + "enable_dhcp": true, + "dns_publish_fixed_ip": true, + "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", + "tenant_id": "4fd44f30292945e481c7b8a0c8908869", + "dns_nameservers": ["foo"], + "service_types": ["network:routed"], + "allocation_pools": [ + { + "start": "192.168.199.2", + "end": "192.168.199.254" + } + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": "192.168.199.1", + "cidr": "192.168.199.0/24", + "id": "3b80198d-4f7b-4f77-9ef5-774d54e17126", + "subnetpool_id": "b80340c7-9960-4f67-a99c-02501656284b" + } } ` const SubnetCreateWithNoGatewayRequest = ` { - "subnet": { - "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a23", - "ip_version": 4, - "cidr": "192.168.1.0/24", - "gateway_ip": null, - "allocation_pools": [ - { - "start": "192.168.1.2", - "end": "192.168.1.254" - } - ] - } + "subnet": { + "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a23", + "ip_version": 4, + "cidr": "192.168.1.0/24", + "gateway_ip": null, + "allocation_pools": [ + { + "start": "192.168.1.2", + "end": "192.168.1.254" + } + ] + } } ` const SubnetCreateWithNoGatewayResponse = ` { - "subnet": { - "name": "", - "enable_dhcp": true, - "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a23", - "tenant_id": "4fd44f30292945e481c7b8a0c8908869", - "allocation_pools": [ - { - "start": "192.168.1.2", - "end": "192.168.1.254" - } - ], - "host_routes": [], - "ip_version": 4, - "gateway_ip": null, - "cidr": "192.168.1.0/24", - "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0c" - } + "subnet": { + "name": "", + "enable_dhcp": true, + "dns_publish_fixed_ip": true, + "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a23", + "tenant_id": "4fd44f30292945e481c7b8a0c8908869", + "allocation_pools": [ + { + "start": "192.168.1.2", + "end": "192.168.1.254" + } + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": null, + "cidr": "192.168.1.0/24", + "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0c" + } } ` const SubnetCreateWithDefaultGatewayRequest = ` { - "subnet": { - "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a23", - "ip_version": 4, - "cidr": "192.168.1.0/24", - "allocation_pools": [ - { - "start": "192.168.1.2", - "end": "192.168.1.254" - } - ] - } + "subnet": { + "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a23", + "ip_version": 4, + "cidr": "192.168.1.0/24", + "allocation_pools": [ + { + "start": "192.168.1.2", + "end": "192.168.1.254" + } + ] + } } ` const SubnetCreateWithDefaultGatewayResponse = ` { - "subnet": { - "name": "", - "enable_dhcp": true, - "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a23", - "tenant_id": "4fd44f30292945e481c7b8a0c8908869", - "allocation_pools": [ - { - "start": "192.168.1.2", - "end": "192.168.1.254" - } - ], - "host_routes": [], - "ip_version": 4, - "gateway_ip": "192.168.1.1", - "cidr": "192.168.1.0/24", - "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0c" - } + "subnet": { + "name": "", + "enable_dhcp": true, + "dns_publish_fixed_ip": true, + "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a23", + "tenant_id": "4fd44f30292945e481c7b8a0c8908869", + "allocation_pools": [ + { + "start": "192.168.1.2", + "end": "192.168.1.254" + } + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": "192.168.1.1", + "cidr": "192.168.1.0/24", + "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0c" + } } ` const SubnetCreateWithIPv6RaAddressModeRequest = ` { - "subnet": { - "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", - "ip_version": 6, - "gateway_ip": "2001:db8:0:a::1", - "cidr": "2001:db8:0:a:0:0:0:0/64", - "ipv6_address_mode": "slaac", - "ipv6_ra_mode": "slaac" - } + "subnet": { + "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", + "ip_version": 6, + "gateway_ip": "2001:db8:0:a::1", + "cidr": "2001:db8:0:a:0:0:0:0/64", + "ipv6_address_mode": "slaac", + "ipv6_ra_mode": "slaac" + } } ` const SubnetCreateWithIPv6RaAddressModeResponse = ` { - "subnet": { - "name": "", - "enable_dhcp": true, - "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", - "tenant_id": "4fd44f30292945e481c7b8a0c8908869", - "dns_nameservers": [], - "host_routes": [], - "ip_version": 6, - "gateway_ip": "2001:db8:0:a::1", - "cidr": "2001:db8:0:a:0:0:0:0/64", - "id": "3b80198d-4f7b-4f77-9ef5-774d54e17126", - "ipv6_address_mode": "slaac", - "ipv6_ra_mode": "slaac" - } + "subnet": { + "name": "", + "enable_dhcp": true, + "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", + "tenant_id": "4fd44f30292945e481c7b8a0c8908869", + "dns_nameservers": [], + "host_routes": [], + "ip_version": 6, + "gateway_ip": "2001:db8:0:a::1", + "cidr": "2001:db8:0:a:0:0:0:0/64", + "id": "3b80198d-4f7b-4f77-9ef5-774d54e17126", + "ipv6_address_mode": "slaac", + "ipv6_ra_mode": "slaac" + } } ` const SubnetCreateRequestWithNoCIDR = ` { - "subnet": { - "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", - "ip_version": 4, - "dns_nameservers": ["foo"], - "host_routes": [{"destination":"","nexthop": "bar"}], - "subnetpool_id": "b80340c7-9960-4f67-a99c-02501656284b" - } + "subnet": { + "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", + "ip_version": 4, + "dns_nameservers": ["foo"], + "host_routes": [{"destination":"","nexthop": "bar"}], + "subnetpool_id": "b80340c7-9960-4f67-a99c-02501656284b" + } } ` const SubnetCreateRequestWithPrefixlen = ` { - "subnet": { - "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", - "ip_version": 4, - "dns_nameservers": ["foo"], - "host_routes": [{"destination":"","nexthop": "bar"}], - "subnetpool_id": "b80340c7-9960-4f67-a99c-02501656284b", - "prefixlen": 12 - } + "subnet": { + "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", + "ip_version": 4, + "dns_nameservers": ["foo"], + "host_routes": [{"destination":"","nexthop": "bar"}], + "subnetpool_id": "b80340c7-9960-4f67-a99c-02501656284b", + "prefixlen": 12 + } } ` const SubnetUpdateRequest = ` { - "subnet": { - "name": "my_new_subnet", - "dns_nameservers": ["foo"], - "host_routes": [{"destination":"","nexthop": "bar"}] - } + "subnet": { + "name": "my_new_subnet", + "dns_nameservers": ["foo"], + "host_routes": [{"destination":"","nexthop": "bar"}] + } } ` const SubnetUpdateResponse = ` { - "subnet": { - "name": "my_new_subnet", - "enable_dhcp": true, - "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", - "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", - "dns_nameservers": [], - "allocation_pools": [ - { - "start": "10.0.0.2", - "end": "10.0.0.254" - } - ], - "host_routes": [], - "ip_version": 4, - "gateway_ip": "10.0.0.1", - "cidr": "10.0.0.0/24", - "id": "08eae331-0402-425a-923c-34f7cfe39c1b" - } + "subnet": { + "name": "my_new_subnet", + "enable_dhcp": true, + "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", + "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", + "dns_nameservers": [], + "allocation_pools": [ + { + "start": "10.0.0.2", + "end": "10.0.0.254" + } + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": "10.0.0.1", + "cidr": "10.0.0.0/24", + "id": "08eae331-0402-425a-923c-34f7cfe39c1b" + } } ` const SubnetUpdateGatewayRequest = ` { - "subnet": { - "name": "my_new_subnet", - "gateway_ip": "10.0.0.1" - } + "subnet": { + "name": "my_new_subnet", + "gateway_ip": "10.0.0.1" + } } ` const SubnetUpdateGatewayResponse = ` { - "subnet": { - "name": "my_new_subnet", - "enable_dhcp": true, - "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", - "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", - "dns_nameservers": [], - "allocation_pools": [ - { - "start": "10.0.0.2", - "end": "10.0.0.254" - } - ], - "host_routes": [], - "ip_version": 4, - "gateway_ip": "10.0.0.1", - "cidr": "10.0.0.0/24", - "id": "08eae331-0402-425a-923c-34f7cfe39c1b" - } + "subnet": { + "name": "my_new_subnet", + "enable_dhcp": true, + "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", + "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", + "dns_nameservers": [], + "allocation_pools": [ + { + "start": "10.0.0.2", + "end": "10.0.0.254" + } + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": "10.0.0.1", + "cidr": "10.0.0.0/24", + "id": "08eae331-0402-425a-923c-34f7cfe39c1b" + } } ` const SubnetUpdateRemoveGatewayRequest = ` { - "subnet": { - "name": "my_new_subnet", - "gateway_ip": null - } + "subnet": { + "name": "my_new_subnet", + "gateway_ip": null + } } ` const SubnetUpdateRemoveGatewayResponse = ` { - "subnet": { - "name": "my_new_subnet", - "enable_dhcp": true, - "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", - "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", - "dns_nameservers": [], - "allocation_pools": [ - { - "start": "10.0.0.2", - "end": "10.0.0.254" - } - ], - "host_routes": [], - "ip_version": 4, - "gateway_ip": null, - "cidr": "10.0.0.0/24", - "id": "08eae331-0402-425a-923c-34f7cfe39c1b" - } + "subnet": { + "name": "my_new_subnet", + "enable_dhcp": true, + "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", + "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", + "dns_nameservers": [], + "allocation_pools": [ + { + "start": "10.0.0.2", + "end": "10.0.0.254" + } + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": null, + "cidr": "10.0.0.0/24", + "id": "08eae331-0402-425a-923c-34f7cfe39c1b" + } } ` const SubnetUpdateHostRoutesRequest = ` { - "subnet": { - "name": "my_new_subnet", - "host_routes": [ - { - "destination": "192.168.1.1/24", - "nexthop": "bar" - } - ] - } + "subnet": { + "name": "my_new_subnet", + "host_routes": [ + { + "destination": "192.168.1.1/24", + "nexthop": "bar" + } + ] + } } ` const SubnetUpdateHostRoutesResponse = ` { - "subnet": { - "name": "my_new_subnet", - "enable_dhcp": true, - "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", - "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", - "dns_nameservers": [], - "allocation_pools": [ - { - "start": "10.0.0.2", - "end": "10.0.0.254" - } - ], - "ip_version": 4, - "gateway_ip": "10.0.0.1", - "host_routes": [ - { - "destination": "192.168.1.1/24", - "nexthop": "bar" - } - ], - "cidr": "10.0.0.0/24", - "id": "08eae331-0402-425a-923c-34f7cfe39c1b" - } + "subnet": { + "name": "my_new_subnet", + "enable_dhcp": true, + "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", + "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", + "dns_nameservers": [], + "allocation_pools": [ + { + "start": "10.0.0.2", + "end": "10.0.0.254" + } + ], + "ip_version": 4, + "gateway_ip": "10.0.0.1", + "host_routes": [ + { + "destination": "192.168.1.1/24", + "nexthop": "bar" + } + ], + "cidr": "10.0.0.0/24", + "id": "08eae331-0402-425a-923c-34f7cfe39c1b" + } } ` const SubnetUpdateRemoveHostRoutesRequest = ` { - "subnet": { - "host_routes": [] - } + "subnet": { + "host_routes": [] + } } ` const SubnetUpdateRemoveHostRoutesResponse = ` { - "subnet": { - "name": "my_new_subnet", - "enable_dhcp": true, - "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", - "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", - "dns_nameservers": [], - "allocation_pools": [ - { - "start": "10.0.0.2", - "end": "10.0.0.254" - } - ], - "host_routes": [], - "ip_version": 4, - "gateway_ip": null, - "cidr": "10.0.0.0/24", - "id": "08eae331-0402-425a-923c-34f7cfe39c1b" - } + "subnet": { + "name": "my_new_subnet", + "enable_dhcp": true, + "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", + "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", + "dns_nameservers": [], + "allocation_pools": [ + { + "start": "10.0.0.2", + "end": "10.0.0.254" + } + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": null, + "cidr": "10.0.0.0/24", + "id": "08eae331-0402-425a-923c-34f7cfe39c1b" + } } ` const SubnetUpdateAllocationPoolRequest = ` { - "subnet": { - "name": "my_new_subnet", - "allocation_pools": [ - { - "start": "10.1.0.2", - "end": "10.1.0.254" - } - ] - } + "subnet": { + "name": "my_new_subnet", + "allocation_pools": [ + { + "start": "10.1.0.2", + "end": "10.1.0.254" + } + ] + } } ` const SubnetUpdateAllocationPoolResponse = ` { - "subnet": { - "name": "my_new_subnet", - "enable_dhcp": true, - "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", - "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", - "dns_nameservers": [], - "allocation_pools": [ - { - "start": "10.1.0.2", - "end": "10.1.0.254" - } - ], - "host_routes": [], - "ip_version": 4, - "gateway_ip": "10.0.0.1", - "cidr": "10.0.0.0/24", - "id": "08eae331-0402-425a-923c-34f7cfe39c1b" - } + "subnet": { + "name": "my_new_subnet", + "enable_dhcp": true, + "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", + "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", + "dns_nameservers": [], + "allocation_pools": [ + { + "start": "10.1.0.2", + "end": "10.1.0.254" + } + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": "10.0.0.1", + "cidr": "10.0.0.0/24", + "id": "08eae331-0402-425a-923c-34f7cfe39c1b" + } } ` diff --git a/openstack/networking/v2/subnets/testing/requests_test.go b/openstack/networking/v2/subnets/testing/requests_test.go index c4bac8dc0c..41ec607cd8 100644 --- a/openstack/networking/v2/subnets/testing/requests_test.go +++ b/openstack/networking/v2/subnets/testing/requests_test.go @@ -23,7 +23,7 @@ func TestList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, SubnetListResult) + fmt.Fprint(w, SubnetListResult) }) count := 0 @@ -65,7 +65,7 @@ func TestGet(t *testing.T) { 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() @@ -104,10 +104,11 @@ 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" + var dnsPublishFixedIP = true opts := subnets.CreateOpts{ NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a22", IPVersion: 4, @@ -119,8 +120,9 @@ func TestCreate(t *testing.T) { End: "192.168.199.254", }, }, - DNSNameservers: []string{"foo"}, - ServiceTypes: []string{"network:routed"}, + DNSNameservers: []string{"foo"}, + DNSPublishFixedIP: &dnsPublishFixedIP, + ServiceTypes: []string{"network:routed"}, HostRoutes: []subnets.HostRoute{ {NextHop: "bar"}, }, @@ -130,6 +132,7 @@ func TestCreate(t *testing.T) { 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") @@ -163,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 = "" @@ -214,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{ @@ -263,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" @@ -304,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{ @@ -320,6 +323,7 @@ func TestCreateWithNoCIDR(t *testing.T) { 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") @@ -352,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,6 +373,7 @@ func TestCreateWithPrefixlen(t *testing.T) { 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") @@ -418,7 +423,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"} @@ -451,7 +456,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" @@ -482,7 +487,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 = "" @@ -513,7 +518,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{ @@ -550,7 +555,7 @@ 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{} @@ -579,7 +584,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" @@ -620,7 +625,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) }) th.Mux.HandleFunc("/v2.0/subnets/08eae331-0402-425a-923c-34f7cfe39c1c", func(w http.ResponseWriter, r *http.Request) { @@ -634,7 +639,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"} diff --git a/openstack/objectstorage/v1/containers/results.go b/openstack/objectstorage/v1/containers/results.go index 8e71c15063..fddfa26bf1 100644 --- a/openstack/objectstorage/v1/containers/results.go +++ b/openstack/objectstorage/v1/containers/results.go @@ -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..35c0f734cf 100644 --- a/openstack/objectstorage/v1/containers/testing/fixtures.go +++ b/openstack/objectstorage/v1/containers/testing/fixtures.go @@ -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) } @@ -158,7 +158,7 @@ func HandleBulkDeleteSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, bulkDeleteResponse) + fmt.Fprint(w, bulkDeleteResponse) }) } @@ -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..fedf0f2fe6 100644 --- a/openstack/objectstorage/v1/containers/testing/requests_test.go +++ b/openstack/objectstorage/v1/containers/testing/requests_test.go @@ -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) diff --git a/openstack/objectstorage/v1/objects/testing/fixtures_test.go b/openstack/objectstorage/v1/objects/testing/fixtures_test.go index fd24937f6c..f4546d6ab0 100644 --- a/openstack/objectstorage/v1/objects/testing/fixtures_test.go +++ b/openstack/objectstorage/v1/objects/testing/fixtures_test.go @@ -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") }) } @@ -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) } @@ -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) } @@ -323,7 +323,7 @@ func HandleBulkDeleteSuccessfully(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, bulkDeleteResponse) + fmt.Fprint(w, bulkDeleteResponse) }) } diff --git a/openstack/objectstorage/v1/swauth/testing/fixtures_test.go b/openstack/objectstorage/v1/swauth/testing/fixtures_test.go index b8e0b33f24..01bbfd1a84 100644 --- a/openstack/objectstorage/v1/swauth/testing/fixtures_test.go +++ b/openstack/objectstorage/v1/swauth/testing/fixtures_test.go @@ -24,6 +24,6 @@ func HandleAuthSuccessfully(t *testing.T, authOpts swauth.AuthOpts) { 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/orchestration/v1/apiversions/testing/requests_test.go b/openstack/orchestration/v1/apiversions/testing/requests_test.go index ec4ad6d228..d7149f1cbd 100644 --- a/openstack/orchestration/v1/apiversions/testing/requests_test.go +++ b/openstack/orchestration/v1/apiversions/testing/requests_test.go @@ -24,7 +24,7 @@ func TestListVersions(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "versions": [ { diff --git a/openstack/orchestration/v1/buildinfo/testing/fixtures_test.go b/openstack/orchestration/v1/buildinfo/testing/fixtures_test.go index 72527032d8..74a91e4df3 100644 --- a/openstack/orchestration/v1/buildinfo/testing/fixtures_test.go +++ b/openstack/orchestration/v1/buildinfo/testing/fixtures_test.go @@ -41,6 +41,6 @@ func HandleGetSuccessfully(t *testing.T, output string) { 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/fixtures_test.go b/openstack/orchestration/v1/stackevents/testing/fixtures_test.go index 4c22428a9a..a0e31b4ba5 100644 --- a/openstack/orchestration/v1/stackevents/testing/fixtures_test.go +++ b/openstack/orchestration/v1/stackevents/testing/fixtures_test.go @@ -128,7 +128,7 @@ func HandleFindSuccessfully(t *testing.T, output string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } @@ -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) } @@ -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) } @@ -449,6 +449,6 @@ func HandleGetSuccessfully(t *testing.T, output string) { 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/stackresources/testing/fixtures_test.go b/openstack/orchestration/v1/stackresources/testing/fixtures_test.go index a306d8de8b..fa1e66575e 100644 --- a/openstack/orchestration/v1/stackresources/testing/fixtures_test.go +++ b/openstack/orchestration/v1/stackresources/testing/fixtures_test.go @@ -82,7 +82,7 @@ func HandleFindSuccessfully(t *testing.T, output string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } @@ -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) } @@ -227,7 +227,7 @@ func HandleGetSuccessfully(t *testing.T, output string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } @@ -256,7 +256,7 @@ func HandleMetadataSuccessfully(t *testing.T, output string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } @@ -309,7 +309,7 @@ func HandleListTypesSuccessfully(t *testing.T, output string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } @@ -370,7 +370,7 @@ func HandleGetSchemaSuccessfully(t *testing.T, output string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } @@ -440,7 +440,7 @@ func HandleGetTemplateSuccessfully(t *testing.T, output string) { 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/fixtures_test.go b/openstack/orchestration/v1/stacks/testing/fixtures_test.go index 2244b7d89d..9baed3aced 100644 --- a/openstack/orchestration/v1/stacks/testing/fixtures_test.go +++ b/openstack/orchestration/v1/stacks/testing/fixtures_test.go @@ -48,7 +48,7 @@ func HandleCreateSuccessfully(t *testing.T, output string) { th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } @@ -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) } @@ -221,7 +221,7 @@ func HandleGetSuccessfully(t *testing.T, output string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } @@ -233,7 +233,7 @@ func HandleFindSuccessfully(t *testing.T, output string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } @@ -309,7 +309,7 @@ func HandlePreviewSuccessfully(t *testing.T, output string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } @@ -432,6 +432,6 @@ func HandleAbandonSuccessfully(t *testing.T, output string) { 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/utils_test.go b/openstack/orchestration/v1/stacks/utils_test.go index 07fbb90cba..b7dd86bf0e 100644 --- a/openstack/orchestration/v1/stacks/utils_test.go +++ b/openstack/orchestration/v1/stacks/utils_test.go @@ -68,7 +68,7 @@ func TestFetch(t *testing.T) { 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()} diff --git a/openstack/orchestration/v1/stacktemplates/testing/fixtures_test.go b/openstack/orchestration/v1/stacktemplates/testing/fixtures_test.go index 5b9c9ad345..2e6505702e 100644 --- a/openstack/orchestration/v1/stacktemplates/testing/fixtures_test.go +++ b/openstack/orchestration/v1/stacktemplates/testing/fixtures_test.go @@ -48,7 +48,7 @@ func HandleGetSuccessfully(t *testing.T, output string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } @@ -91,6 +91,6 @@ func HandleValidateSuccessfully(t *testing.T, output string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, output) + fmt.Fprint(w, output) }) } diff --git a/openstack/placement/v1/resourceproviders/testing/fixtures_test.go b/openstack/placement/v1/resourceproviders/testing/fixtures_test.go index e663cab280..8766d88e27 100644 --- a/openstack/placement/v1/resourceproviders/testing/fixtures_test.go +++ b/openstack/placement/v1/resourceproviders/testing/fixtures_test.go @@ -277,7 +277,7 @@ func HandleResourceProviderList(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ResourceProvidersBody) + fmt.Fprint(w, ResourceProvidersBody) }) } @@ -289,7 +289,7 @@ func HandleResourceProviderCreate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ResourceProviderCreateBody) + fmt.Fprint(w, ResourceProviderCreateBody) }) } @@ -301,7 +301,7 @@ func HandleResourceProviderGet(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ResourceProviderCreateBody) + fmt.Fprint(w, ResourceProviderCreateBody) }) } @@ -324,7 +324,7 @@ func HandleResourceProviderUpdate(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ResourceProviderUpdateResponse) + fmt.Fprint(w, ResourceProviderUpdateResponse) }) } @@ -339,7 +339,7 @@ func HandleResourceProviderGetUsages(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, UsagesBody) + fmt.Fprint(w, UsagesBody) }) } @@ -354,7 +354,7 @@ func HandleResourceProviderGetInventories(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, InventoriesBody) + fmt.Fprint(w, InventoriesBody) }) } @@ -369,7 +369,7 @@ func HandleResourceProviderGetAllocations(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, AllocationsBody) + fmt.Fprint(w, AllocationsBody) }) } @@ -384,6 +384,6 @@ func HandleResourceProviderGetTraits(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, TraitsBody) + fmt.Fprint(w, TraitsBody) }) } diff --git a/openstack/sharedfilesystems/apiversions/testing/fixtures_test.go b/openstack/sharedfilesystems/apiversions/testing/fixtures_test.go index 949434480f..37826cab70 100644 --- a/openstack/sharedfilesystems/apiversions/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/apiversions/testing/fixtures_test.go @@ -186,7 +186,7 @@ func MockListResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ManilaAllAPIVersionsResponse) + fmt.Fprint(w, ManilaAllAPIVersionsResponse) }) } @@ -198,7 +198,7 @@ func MockGetResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ManilaAPIVersionResponse) + fmt.Fprint(w, ManilaAPIVersionResponse) }) } @@ -210,7 +210,7 @@ func MockGetNoResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ManilaAPIInvalidVersionResponse_1) + fmt.Fprint(w, ManilaAPIInvalidVersionResponse_1) }) } @@ -222,6 +222,6 @@ func MockGetMultipleResponses(t *testing.T) { 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/v2/availabilityzones/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/availabilityzones/testing/fixtures_test.go index d6cd25830f..fa18c4cfc9 100644 --- a/openstack/sharedfilesystems/v2/availabilityzones/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/availabilityzones/testing/fixtures_test.go @@ -17,7 +17,7 @@ func MockListResponse(t *testing.T) { 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/errors/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/errors/testing/fixtures_test.go index 5a6f7f013c..3adbc89b25 100644 --- a/openstack/sharedfilesystems/v2/errors/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/errors/testing/fixtures_test.go @@ -37,6 +37,6 @@ func MockCreateResponse(t *testing.T) { 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/messages/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/messages/testing/fixtures_test.go index 111b6dbf97..123bad35ce 100644 --- a/openstack/sharedfilesystems/v2/messages/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/messages/testing/fixtures_test.go @@ -25,7 +25,7 @@ func MockListResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "messages": [ { @@ -67,7 +67,7 @@ func MockFilteredListResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "messages": [ { @@ -95,7 +95,7 @@ func MockGetResponse(t *testing.T) { 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/replicas/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/replicas/testing/fixtures_test.go index 00585c9c26..41fa0164c9 100644 --- a/openstack/sharedfilesystems/v2/replicas/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/replicas/testing/fixtures_test.go @@ -48,7 +48,7 @@ func MockCreateResponse(t *testing.T) { th.TestJSONRequest(t, r, createRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, createResponse) + fmt.Fprint(w, createResponse) }) } @@ -175,7 +175,7 @@ func MockGetResponse(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "X-OpenStack-Manila-API-Version", "2.11") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, getResponse) + fmt.Fprint(w, getResponse) }) } @@ -330,7 +330,7 @@ func MockListExportLocationsResponse(t *testing.T) { 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) }) } @@ -355,6 +355,6 @@ func MockGetExportLocationResponse(t *testing.T) { 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/schedulerstats/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/schedulerstats/testing/fixtures_test.go index acdd04d53f..fdd861d9c8 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" ) @@ -265,27 +265,27 @@ 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) + th.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) + th.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..d6b826ed74 100644 --- a/openstack/sharedfilesystems/v2/schedulerstats/testing/requests_test.go +++ b/openstack/sharedfilesystems/v2/schedulerstats/testing/requests_test.go @@ -6,13 +6,13 @@ 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() + th.SetupHTTP() + defer th.TeardownHTTP() HandlePoolsListSuccessfully(t) pages := 0 @@ -20,20 +20,20 @@ func TestListPoolsDetail(t *testing.T) { 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) @@ -44,20 +44,20 @@ func TestListPoolsDetail(t *testing.T) { 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..279fcee4a4 100644 --- a/openstack/sharedfilesystems/v2/securityservices/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/securityservices/testing/fixtures_test.go @@ -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", @@ -67,7 +67,7 @@ func MockListResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "security_services": [ { @@ -113,7 +113,7 @@ func MockFilteredListResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "security_services": [ { @@ -143,7 +143,7 @@ func MockGetResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "security_service": { "status": "new", @@ -169,7 +169,7 @@ func MockUpdateResponse(t *testing.T) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "security_service": { "status": "new", diff --git a/openstack/sharedfilesystems/v2/services/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/services/testing/fixtures_test.go index 67f7b94c78..9916b033fc 100644 --- a/openstack/sharedfilesystems/v2/services/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/services/testing/fixtures_test.go @@ -66,6 +66,6 @@ func HandleListSuccessfully(t *testing.T) { 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..d4845ca09e 100644 --- a/openstack/sharedfilesystems/v2/services/testing/requests_test.go +++ b/openstack/sharedfilesystems/v2/services/testing/requests_test.go @@ -6,13 +6,13 @@ 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() + th.SetupHTTP() + defer th.TeardownHTTP() HandleListSuccessfully(t) pages := 0 @@ -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..cc63ffac9c 100644 --- a/openstack/sharedfilesystems/v2/shareaccessrules/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/shareaccessrules/testing/fixtures_test.go @@ -40,7 +40,7 @@ func MockGetResponse(t *testing.T) { 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..7566788e40 100644 --- a/openstack/sharedfilesystems/v2/shareaccessrules/testing/requests_test.go +++ b/openstack/sharedfilesystems/v2/shareaccessrules/testing/requests_test.go @@ -49,6 +49,6 @@ func MockListResponse(t *testing.T) { 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/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/sharenetworks/testing/fixtures_test.go index cfa3b1712d..eb608f9b6a 100644 --- a/openstack/sharedfilesystems/v2/sharenetworks/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/sharenetworks/testing/fixtures_test.go @@ -55,7 +55,7 @@ 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")) @@ -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": [] }`) @@ -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": [] }`) @@ -235,7 +235,7 @@ func MockGetResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "share_network": { "name": "net_my1", @@ -261,7 +261,7 @@ func MockUpdateNeutronResponse(t *testing.T) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "share_network": { "name": "net_my2", @@ -288,7 +288,7 @@ func MockUpdateNovaResponse(t *testing.T) { th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "share_network": { "name": "net_my2", @@ -315,7 +315,7 @@ func MockAddSecurityServiceResponse(t *testing.T) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "share_network": { "name": "net2", @@ -341,7 +341,7 @@ func MockRemoveSecurityServiceResponse(t *testing.T) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "share_network": { "name": "net2", diff --git a/openstack/sharedfilesystems/v2/shares/requests.go b/openstack/sharedfilesystems/v2/shares/requests.go index d228f6a36c..5d680a642b 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 diff --git a/openstack/sharedfilesystems/v2/shares/results.go b/openstack/sharedfilesystems/v2/shares/results.go index d3fe9d8437..7c1b621327 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 diff --git a/openstack/sharedfilesystems/v2/shares/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/shares/testing/fixtures_test.go index 89a2c5461e..36db8b7a22 100644 --- a/openstack/sharedfilesystems/v2/shares/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/shares/testing/fixtures_test.go @@ -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" @@ -78,7 +84,7 @@ func MockCreateResponse(t *testing.T) { th.TestJSONRequest(t, r, createRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, createResponse) + fmt.Fprint(w, createResponse) }) } @@ -152,7 +158,7 @@ func MockUpdateResponse(t *testing.T) { th.TestJSONRequest(t, r, updateRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, updateResponse) + fmt.Fprint(w, updateResponse) }) } @@ -205,7 +211,7 @@ func MockGetResponse(t *testing.T) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, getResponse) + fmt.Fprint(w, getResponse) }) } @@ -298,7 +304,7 @@ func MockListExportLocationsResponse(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, listExportLocationsResponse) + fmt.Fprint(w, listExportLocationsResponse) }) } @@ -319,7 +325,7 @@ func MockGetExportLocationResponse(t *testing.T) { th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, getExportLocationResponse) + fmt.Fprint(w, getExportLocationResponse) }) } @@ -353,7 +359,7 @@ func MockGrantAccessResponse(t *testing.T) { th.TestJSONRequest(t, r, grantAccessRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, grantAccessResponse) + fmt.Fprint(w, grantAccessResponse) }) } @@ -404,7 +410,7 @@ func MockListAccessRightsResponse(t *testing.T) { th.TestJSONRequest(t, r, listAccessRightsRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, listAccessRightsResponse) + fmt.Fprint(w, listAccessRightsResponse) }) } diff --git a/openstack/sharedfilesystems/v2/shares/testing/request_test.go b/openstack/sharedfilesystems/v2/shares/testing/request_test.go index f9af3da84b..396c73a795 100644 --- a/openstack/sharedfilesystems/v2/shares/testing/request_test.go +++ b/openstack/sharedfilesystems/v2/shares/testing/request_test.go @@ -16,13 +16,23 @@ func TestCreate(t *testing.T) { MockCreateResponse(t) - options := &shares.CreateOpts{Size: 1, Name: "my_test_share", ShareProto: "NFS"} + 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(), 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, n.Metadata["__affinity_same_host"], "e268f4aa-d571-43dd-9ab3-f49ad06ffaef") + th.AssertEquals(t, n.Metadata["__affinity_different_host"], "e268f4aa-d571-43dd-9ab3-f49ad06ffaef") } func TestUpdate(t *testing.T) { diff --git a/openstack/sharedfilesystems/v2/sharetransfers/requests.go b/openstack/sharedfilesystems/v2/sharetransfers/requests.go index 642d2a36d2..88b1950eb2 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 diff --git a/openstack/sharedfilesystems/v2/sharetransfers/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/sharetransfers/testing/fixtures_test.go index 980292f38f..342c4c5101 100644 --- a/openstack/sharedfilesystems/v2/sharetransfers/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/sharetransfers/testing/fixtures_test.go @@ -147,7 +147,7 @@ func HandleCreateTransfer(t *testing.T) { th.TestJSONRequest(t, r, CreateRequest) w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, CreateResponse) + fmt.Fprint(w, CreateResponse) }) } @@ -179,7 +179,7 @@ func HandleListTransfers(t *testing.T) { th.TestFormValues(t, r, map[string]string{"all_tenants": "true"}) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } @@ -191,7 +191,7 @@ func HandleListTransfersDetail(t *testing.T) { th.TestFormValues(t, r, map[string]string{"all_tenants": "true"}) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ListOutput) + fmt.Fprint(w, ListOutput) }) } @@ -202,6 +202,6 @@ func HandleGetTransfer(t *testing.T) { 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/sharetypes/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/sharetypes/testing/fixtures_test.go index 767d42807a..17fb21fa78 100644 --- a/openstack/sharedfilesystems/v2/sharetypes/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/sharetypes/testing/fixtures_test.go @@ -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, @@ -76,7 +76,7 @@ func MockListResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volume_types": [ { @@ -141,7 +141,7 @@ func MockGetDefaultResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "volume_type": { "required_extra_specs": null, @@ -172,7 +172,7 @@ func MockGetExtraSpecsResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "extra_specs": { "snapshot_support": "True", @@ -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" @@ -223,7 +223,7 @@ func MockShowAccessResponse(t *testing.T) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "share_type_access": [ { diff --git a/openstack/sharedfilesystems/v2/snapshots/testing/fixtures_test.go b/openstack/sharedfilesystems/v2/snapshots/testing/fixtures_test.go index e34d1081a4..88e94c371a 100644 --- a/openstack/sharedfilesystems/v2/snapshots/testing/fixtures_test.go +++ b/openstack/sharedfilesystems/v2/snapshots/testing/fixtures_test.go @@ -59,7 +59,7 @@ func MockCreateResponse(t *testing.T) { th.TestJSONRequest(t, r, createRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) - fmt.Fprintf(w, createResponse) + fmt.Fprint(w, createResponse) }) } @@ -114,7 +114,7 @@ func MockUpdateResponse(t *testing.T) { th.TestJSONRequest(t, r, updateRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, updateResponse) + fmt.Fprint(w, updateResponse) }) } @@ -150,7 +150,7 @@ func MockGetResponse(t *testing.T) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, getResponse) + fmt.Fprint(w, getResponse) }) } diff --git a/openstack/testing/client_test.go b/openstack/testing/client_test.go index 2c158dbfc3..75192a524c 100644 --- a/openstack/testing/client_test.go +++ b/openstack/testing/client_test.go @@ -46,7 +46,7 @@ func TestAuthenticatedClientV3(t *testing.T) { 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{ @@ -91,7 +91,7 @@ func TestAuthenticatedClientV2(t *testing.T) { }) th.Mux.HandleFunc("/v2.0/tokens", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "access": { "token": { @@ -197,7 +197,7 @@ func TestIdentityAdminV3Client(t *testing.T) { w.Header().Add("X-Subject-Token", ID) w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "token": { "audit_ids": ["VcxU2JYqT8OzfUVvrjEITQ", "qNUTIJntTzO1-XUk5STybw"], diff --git a/openstack/utils/testing/choose_version_test.go b/openstack/utils/testing/choose_version_test.go index 36d8ac37ec..c237e4e036 100644 --- a/openstack/utils/testing/choose_version_test.go +++ b/openstack/utils/testing/choose_version_test.go @@ -9,11 +9,11 @@ import ( "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) { + th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, ` { "versions": { @@ -35,10 +35,10 @@ func setupVersionHandler() { ] } } - `, testhelper.Server.URL, testhelper.Server.URL) + `, th.Server.URL, th.Server.URL) }) // Compute v2.1 API - testhelper.Mux.HandleFunc("/compute/v2.1/", func(w http.ResponseWriter, r *http.Request) { + th.Mux.HandleFunc("/compute/v2.1/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, ` { "version": { @@ -66,10 +66,10 @@ func setupVersionHandler() { ] } } - `, testhelper.Server.URL) + `, th.Server.URL) }) // Compute v2 API - testhelper.Mux.HandleFunc("/compute/v2/", func(w http.ResponseWriter, r *http.Request) { + th.Mux.HandleFunc("/compute/v2/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, ` { "version": { @@ -97,10 +97,10 @@ func setupVersionHandler() { ] } } - `, testhelper.Server.URL) + `, th.Server.URL) }) // Ironic API - testhelper.Mux.HandleFunc("/ironic/v1/", func(w http.ResponseWriter, r *http.Request) { + th.Mux.HandleFunc("/ironic/v1/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, ` { "name": "OpenStack Ironic API", @@ -132,10 +132,10 @@ func setupVersionHandler() { } ] } - `, testhelper.Server.URL, testhelper.Server.URL) + `, th.Server.URL, th.Server.URL) }) // Ironic multi-version - testhelper.Mux.HandleFunc("/ironic/v1.2/", func(w http.ResponseWriter, r *http.Request) { + th.Mux.HandleFunc("/ironic/v1.2/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, ` { "name": "OpenStack Ironic API", @@ -179,20 +179,20 @@ func setupVersionHandler() { } ] } - `, testhelper.Server.URL, testhelper.Server.URL, testhelper.Server.URL) + `, th.Server.URL, th.Server.URL, th.Server.URL) }) } func TestChooseVersion(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + th.SetupHTTP() + defer th.TeardownHTTP() setupVersionHandler() 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: th.Endpoint(), IdentityEndpoint: "", } v, endpoint, err := utils.ChooseVersion(context.TODO(), c, []*utils.Version{v2, v3}) @@ -205,23 +205,23 @@ func TestChooseVersion(t *testing.T) { t.Errorf("Expected %#v to win, but %#v did instead", v3, v) } - expected := testhelper.Endpoint() + "v3.0/" + expected := th.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() + th.SetupHTTP() + defer th.TeardownHTTP() setupVersionHandler() 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: th.Endpoint(), + IdentityEndpoint: th.Endpoint() + "v2.0/", } v, endpoint, err := utils.ChooseVersion(context.TODO(), c, []*utils.Version{v2, v3}) if err != nil { @@ -232,22 +232,22 @@ func TestChooseVersionOpinionatedLink(t *testing.T) { t.Errorf("Expected %#v to win, but %#v did instead", v2, v) } - expected := testhelper.Endpoint() + "v2.0/" + expected := th.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() + th.SetupHTTP() + defer th.TeardownHTTP() 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: th.Endpoint(), + IdentityEndpoint: th.Endpoint() + "v2.0/", } v, endpoint, err := utils.ChooseVersion(context.TODO(), c, []*utils.Version{v2, v3}) if err != nil { @@ -258,7 +258,7 @@ func TestChooseVersionFromSuffix(t *testing.T) { t.Errorf("Expected %#v to win, but %#v did instead", v2, v) } - expected := testhelper.Endpoint() + "v2.0/" + expected := th.Endpoint() + "v2.0/" if endpoint != expected { t.Errorf("Expected endpoint [%s], but was [%s] instead", expected, endpoint) } @@ -272,33 +272,33 @@ type getSupportedServiceMicroversions struct { } func TestGetSupportedVersions(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() + th.SetupHTTP() + defer th.TeardownHTTP() setupVersionHandler() tests := []getSupportedServiceMicroversions{ { // v2 does not support microversions and returns error - Endpoint: testhelper.Endpoint() + "compute/v2/", + Endpoint: th.Endpoint() + "compute/v2/", ExpectedMax: "", ExpectedMin: "", ExpectedErr: true, }, { - Endpoint: testhelper.Endpoint() + "compute/v2.1/", + Endpoint: th.Endpoint() + "compute/v2.1/", ExpectedMax: "2.90", ExpectedMin: "2.1", ExpectedErr: false, }, { - Endpoint: testhelper.Endpoint() + "ironic/v1/", + Endpoint: th.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/", + Endpoint: th.Endpoint() + "ironic/v1.2/", ExpectedMax: "not-relevant", ExpectedMin: "not-relevant", ExpectedErr: true, @@ -307,8 +307,8 @@ func TestGetSupportedVersions(t *testing.T) { for _, test := range tests { c := &gophercloud.ProviderClient{ - IdentityBase: testhelper.Endpoint(), - IdentityEndpoint: testhelper.Endpoint() + "v2.0/", + IdentityBase: th.Endpoint(), + IdentityEndpoint: th.Endpoint() + "v2.0/", } client := &gophercloud.ServiceClient{ diff --git a/openstack/workflow/v2/crontriggers/testing/requests_test.go b/openstack/workflow/v2/crontriggers/testing/requests_test.go index fd3bac58e0..8267e2bc79 100644 --- a/openstack/workflow/v2/crontriggers/testing/requests_test.go +++ b/openstack/workflow/v2/crontriggers/testing/requests_test.go @@ -25,7 +25,7 @@ func TestCreateCronTrigger(t *testing.T) { 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", @@ -110,7 +110,7 @@ func TestGetCronTrigger(t *testing.T) { 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, ` { "created_at": "2018-09-12 15:48:18", "first_execution_time": "2018-09-12 17:48:00", @@ -195,7 +195,7 @@ func TestListCronTriggers(t *testing.T) { "next": "%s/cron_triggers?marker=0520ffd8-f7f1-4f2e-845b-55d953a1cf46" }`, th.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) } diff --git a/openstack/workflow/v2/executions/testing/requests_test.go b/openstack/workflow/v2/executions/testing/requests_test.go index d0b4641793..86a4700457 100644 --- a/openstack/workflow/v2/executions/testing/requests_test.go +++ b/openstack/workflow/v2/executions/testing/requests_test.go @@ -25,7 +25,7 @@ func TestCreateExecution(t *testing.T) { 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", @@ -92,7 +92,7 @@ func TestGetExecution(t *testing.T) { th.TestHeader(t, r, "X-Auth-token", fake.TokenID) w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` + fmt.Fprint(w, ` { "created_at": "2018-09-12 14:48:49", "description": "description", @@ -189,7 +189,7 @@ func TestListExecutions(t *testing.T) { "next": "%s/executions?marker=50bb59f1-eb77-4017-a77f-6d575b002667" }`, th.Server.URL) case "50bb59f1-eb77-4017-a77f-6d575b002667": - fmt.Fprintf(w, `{ "executions": [] }`) + fmt.Fprint(w, `{ "executions": [] }`) default: t.Fatalf("Unexpected marker: [%s]", marker) } diff --git a/openstack/workflow/v2/workflows/testing/requests_test.go b/openstack/workflow/v2/workflows/testing/requests_test.go index 78f0f78a09..a5d656685f 100644 --- a/openstack/workflow/v2/workflows/testing/requests_test.go +++ b/openstack/workflow/v2/workflows/testing/requests_test.go @@ -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", @@ -118,10 +118,10 @@ func TestGetWorkflow(t *testing.T) { 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, ` { "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", @@ -187,7 +187,7 @@ func TestListWorkflows(t *testing.T) { ] }`, th.Server.URL) case "604a3a1e-94e3-4066-a34a-aa56873ef236": - fmt.Fprintf(w, `{ "workflows": [] }`) + fmt.Fprint(w, `{ "workflows": [] }`) default: t.Fatalf("Unexpected marker: [%s]", marker) } diff --git a/pagination/testing/linked_test.go b/pagination/testing/linked_test.go index 83673f19b8..e28c2b0483 100644 --- a/pagination/testing/linked_test.go +++ b/pagination/testing/linked_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/gophercloud/gophercloud/v2/pagination" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" ) // LinkedPager sample and test cases. @@ -31,21 +31,21 @@ func ExtractLinkedInts(r pagination.Page) ([]int, error) { } func createLinked() pagination.Pager { - testhelper.SetupHTTP() + th.SetupHTTP() - testhelper.Mux.HandleFunc("/page1", func(w http.ResponseWriter, r *http.Request) { + th.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" } }`, th.Server.URL) }) - testhelper.Mux.HandleFunc("/page2", func(w http.ResponseWriter, r *http.Request) { + th.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" } }`, th.Server.URL) }) - testhelper.Mux.HandleFunc("/page3", func(w http.ResponseWriter, r *http.Request) { + th.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() @@ -54,12 +54,12 @@ func createLinked() pagination.Pager { return LinkedPageResult{pagination.LinkedPageBase{PageResult: r}} } - return pagination.NewPager(client, testhelper.Server.URL+"/page1", createPage) + return pagination.NewPager(client, th.Server.URL+"/page1", createPage) } func TestEnumerateLinked(t *testing.T) { pager := createLinked() - defer testhelper.TeardownHTTP() + defer th.TeardownHTTP() callCount := 0 err := pager.EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { @@ -101,13 +101,13 @@ func TestEnumerateLinked(t *testing.T) { func TestAllPagesLinked(t *testing.T) { pager := createLinked() - defer testhelper.TeardownHTTP() + defer th.TeardownHTTP() 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..a41915175d 100644 --- a/pagination/testing/marker_test.go +++ b/pagination/testing/marker_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/gophercloud/gophercloud/v2/pagination" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" ) // MarkerPager sample and test cases. @@ -37,20 +37,20 @@ func (r MarkerPageResult) LastMarker() (string, error) { } func createMarkerPaged(t *testing.T) pagination.Pager { - testhelper.SetupHTTP() + th.SetupHTTP() - testhelper.Mux.HandleFunc("/page", func(w http.ResponseWriter, r *http.Request) { + th.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: @@ -66,7 +66,7 @@ func createMarkerPaged(t *testing.T) pagination.Pager { return p } - return pagination.NewPager(client, testhelper.Server.URL+"/page", createPage) + return pagination.NewPager(client, th.Server.URL+"/page", createPage) } func ExtractMarkerStrings(page pagination.Page) ([]string, error) { @@ -83,7 +83,7 @@ func ExtractMarkerStrings(page pagination.Page) ([]string, error) { func TestEnumerateMarker(t *testing.T) { pager := createMarkerPaged(t) - defer testhelper.TeardownHTTP() + defer th.TeardownHTTP() callCount := 0 err := pager.EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) { @@ -107,24 +107,24 @@ 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, callCount, 3) } func TestAllPagesMarker(t *testing.T) { pager := createMarkerPaged(t) - defer testhelper.TeardownHTTP() + defer th.TeardownHTTP() 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 index b730676d4e..e10ef56847 100644 --- a/pagination/testing/pagination_test.go +++ b/pagination/testing/pagination_test.go @@ -2,12 +2,12 @@ package testing import ( "github.com/gophercloud/gophercloud/v2" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" ) func createClient() *gophercloud.ServiceClient { return &gophercloud.ServiceClient{ ProviderClient: &gophercloud.ProviderClient{TokenID: "abc123"}, - Endpoint: testhelper.Endpoint(), + Endpoint: th.Endpoint(), } } diff --git a/pagination/testing/single_test.go b/pagination/testing/single_test.go index 21daedff0a..090324a573 100644 --- a/pagination/testing/single_test.go +++ b/pagination/testing/single_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/gophercloud/gophercloud/v2/pagination" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" ) // SinglePage sample and test cases. @@ -33,48 +33,48 @@ func ExtractSingleInts(r pagination.Page) ([]int, error) { } func setupSinglePaged() pagination.Pager { - testhelper.SetupHTTP() + th.SetupHTTP() client := createClient() - testhelper.Mux.HandleFunc("/only", func(w http.ResponseWriter, r *http.Request) { + th.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, th.Server.URL+"/only", createPage) } func TestEnumerateSinglePaged(t *testing.T) { callCount := 0 pager := setupSinglePaged() - defer testhelper.TeardownHTTP() + defer th.TeardownHTTP() 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() + defer th.TeardownHTTP() 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..4a2ed6c942 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,7 +29,29 @@ 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. @@ -44,7 +68,8 @@ func BuildRequestBody(opts any, parent string) (map[string]any, error) { } 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) @@ -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 diff --git a/provider_client.go b/provider_client.go index f8c4928cbd..52fcd38ab3 100644 --- a/provider_client.go +++ b/provider_client.go @@ -13,7 +13,7 @@ import ( // DefaultUserAgent is the default User-Agent string set in the request header. const ( - DefaultUserAgent = "gophercloud/v2.0.0" + DefaultUserAgent = "gophercloud/v2.7.0" DefaultMaxBackoffRetries = 60 ) diff --git a/results.go b/results.go index 9e6f630abb..b12c15a026 100644 --- a/results.go +++ b/results.go @@ -184,10 +184,19 @@ 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 reflect.ValueOf(to).IsNil() { + return fmt.Errorf("Expected pointer, got %T", to) + } + switch t.Elem().Kind() { case reflect.Struct: return r.extractIntoPtr(to, label) @@ -210,10 +219,19 @@ 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 reflect.ValueOf(to).IsNil() { + return fmt.Errorf("Expected pointer, got %T", to) + } + switch t.Elem().Kind() { case reflect.Slice: return r.extractIntoPtr(to, label) 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/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..dbaa08f445 --- /dev/null +++ b/script/getenvvar @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 + +""" +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 +from pathlib import Path +import sys + +import yaml + +p = Path('~/.config/openstack/clouds.yaml').expanduser() +parser = argparse.ArgumentParser() +parser.add_argument( + 'cloud', + help="Cloud to export credentials for", +) + +args = parser.parse_args() + +with p.open() as fh: + data = yaml.safe_load(fh) + +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) diff --git a/script/test b/script/test deleted file mode 100755 index 77f6af5017..0000000000 --- a/script/test +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -# -# Run all the tests. - -set -euxo pipefail - -go test -v -tags 'acceptance fixtures' ./... $@ diff --git a/script/unittest b/script/unittest deleted file mode 100755 index 2763e0efd9..0000000000 --- a/script/unittest +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -# -# Run the unit tests. - -set -euxo pipefail - -# Do extra rounds of testing to help identify reauth concurrency issues. -# All other packages are tested in the `coverage` tests. -go test -v -race -count=5 ./testing $@ diff --git a/script/vet b/script/vet deleted file mode 100755 index 4f3db40a28..0000000000 --- a/script/vet +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -# -# Run go vet. - -set -euxo pipefail - -GOFLAGS="-tags=acceptance" -go vet ./... diff --git a/service_client.go b/service_client.go index 11b80108c3..c1f9f41d4d 100644 --- a/service_client.go +++ b/service_client.go @@ -115,13 +115,17 @@ func (client *ServiceClient) Head(ctx context.Context, url string, opts *Request } func (client *ServiceClient) setMicroversionHeader(opts *RequestOpts) { + serviceType := client.Type + switch client.Type { case "compute": opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion - case "sharev2": + case "shared-file-system", "sharev2", "share": opts.MoreHeaders["X-OpenStack-Manila-API-Version"] = client.Microversion - case "volume": + case "block-storage", "block-store", "volume", "volumev3": opts.MoreHeaders["X-OpenStack-Volume-API-Version"] = client.Microversion + // cinder should accept block-storage but (as of Dalmatian) does not + serviceType = "volume" case "baremetal": opts.MoreHeaders["X-OpenStack-Ironic-API-Version"] = client.Microversion case "baremetal-introspection": @@ -129,7 +133,7 @@ func (client *ServiceClient) setMicroversionHeader(opts *RequestOpts) { } if client.Type != "" { - opts.MoreHeaders["OpenStack-API-Version"] = client.Type + " " + client.Microversion + opts.MoreHeaders["OpenStack-API-Version"] = serviceType + " " + client.Microversion } } diff --git a/testhelper/client/fake.go b/testhelper/client/fake.go index 6e25f759d7..29514b0a65 100644 --- a/testhelper/client/fake.go +++ b/testhelper/client/fake.go @@ -2,7 +2,7 @@ package client import ( "github.com/gophercloud/gophercloud/v2" - "github.com/gophercloud/gophercloud/v2/testhelper" + th "github.com/gophercloud/gophercloud/v2/testhelper" ) // Fake token to use. @@ -12,6 +12,6 @@ const TokenID = "cbc36478b0bd8e67e89469c7749d4127" func ServiceClient() *gophercloud.ServiceClient { return &gophercloud.ServiceClient{ ProviderClient: &gophercloud.ProviderClient{TokenID: TokenID}, - Endpoint: testhelper.Endpoint(), + Endpoint: th.Endpoint(), } } diff --git a/testhelper/convenience.go b/testhelper/convenience.go index 3b3e63979d..1ea727045f 100644 --- a/testhelper/convenience.go +++ b/testhelper/convenience.go @@ -232,6 +232,16 @@ func CheckEquals(t *testing.T, expected, actual any) { } } +// AssertDeepEquals - like Equals - performs a comparison - but on more complex +// structures that requires deeper inspection +func AssertTypeEquals(t *testing.T, expected, actual any) { + t.Helper() + + if reflect.TypeOf(expected) != reflect.TypeOf(actual) { + logFatal(t, fmt.Sprintf("expected %s but got %s", green(expected), yellow(actual))) + } +} + // AssertDeepEquals - like Equals - performs a comparison - but on more complex // structures that requires deeper inspection func AssertDeepEquals(t *testing.T, expected, actual any) { diff --git a/testhelper/fixture/helper.go b/testhelper/fixture/helper.go index a0f3b5c7b2..7c84378770 100644 --- a/testhelper/fixture/helper.go +++ b/testhelper/fixture/helper.go @@ -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/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/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..44fc37756c 100644 --- a/testing/provider_client_test.go +++ b/testing/provider_client_test.go @@ -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) @@ -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) 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) {